dump-class-layout reports wrong padding in many cases
authorsimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 7 Jul 2018 15:52:49 +0000 (15:52 +0000)
committersimon.fraser@apple.com <simon.fraser@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 7 Jul 2018 15:52:49 +0000 (15:52 +0000)
https://bugs.webkit.org/show_bug.cgi?id=185801

Reviewed by Dan Bates.

Enhance dump-class-layout, fixing bugs and adding unit tests. This also includes
the patch from bug 187141.

Move the code that uses lldb to fetch the class layout to its own module,
lldb_dump_class_layout.py, so it can be unit tested. Change this code
to build up a data structure for the class. This is necessary since correct
padding computation is easier with second traversal over the data structure.

Try to deal with the empty base class optimization, which is necessary to report
correct padding in classes using std::unique_ptr, and correctly handle virtual base classes.

The ClassLayoutBase class knows how to test for equality and generate a string
or string list representation of itself. The derived ClassLayout class knows
how to build up the class layout using the lldb Python bindings.

Finally we wrap the lldb debugger instance in LLDBDebuggerInstance so that we don't
have to make a new one for each unit test. The tests have to run serially.

Add to lldbWebKitTester a .cpp file that contains a set of C++ classes to unit-test
dump-class-layout.

* Scripts/dump-class-layout:
(main):
* lldb/dump_class_layout_unittest.py:
(destroy_cached_debug_session):
(TestDumpClassLayout):
(TestDumpClassLayout.setUpClass):
(TestDumpClassLayout.setUp):
(TestDumpClassLayout.serial_test_BasicClassLayout):
(serial_test_PaddingBetweenClassMembers):
(serial_test_BoolPaddingClass):
(serial_test_ClassWithEmptyClassMembers):
(serial_test_SimpleVirtualClass):
(serial_test_SimpleVirtualClassWithNonVirtualBase):
(serial_test_InterleavedVirtualNonVirtual):
(serial_test_ClassWithTwoVirtualBaseClasses):
(serial_test_ClassWithClassMembers):
(serial_test_ClassWithBitfields):
(serial_test_ClassWithUniquePtrs):
(serial_test_ClassWithOptionals):
(TestDumpClassLayout.test_BasicClassLayout): Deleted.
* lldb/lldbWebKitTester/DumpClassLayoutTesting.cpp:
(EmptyClass::doStuff):
(VirtualBaseClass::~VirtualBaseClass):
(VirtualBaseClass2::~VirtualBaseClass2):
(SimpleVirtualClass::~SimpleVirtualClass):
(SimpleVirtualClass::doStuff):
(SimpleVirtualClassWithNonVirtualBase::~SimpleVirtualClassWithNonVirtualBase):
(SimpleVirtualClassWithNonVirtualBase::doStuff):
(avoidClassDeadStripping):
* lldb/lldb_dump_class_layout.py:
(ansi_colors):
(ClassLayoutBase):
(ClassLayoutBase.__init__):
(ClassLayoutBase.__ne__):
(ClassLayoutBase.__eq__):
(ClassLayoutBase._to_string_recursive):
(ClassLayoutBase.as_string_list):
(ClassLayoutBase.as_string):
(ClassLayoutBase.dump):
(ClassLayoutExpected):
(ClassLayoutExpected.__init__):
(ClassLayout):
(ClassLayout.__init__):
(ClassLayout._has_polymorphic_base_class):
(ClassLayout._parse):
(ClassLayout._probably_has_empty_base_class_optimization):
(ClassLayout._compute_padding_recursive):
(ClassLayout._compute_padding):
(LLDBDebuggerInstance):
(LLDBDebuggerInstance.__init__):
(LLDBDebuggerInstance.__del__):
(LLDBDebuggerInstance._get_first_file_architecture):
(LLDBDebuggerInstance.layout_for_classname):
(ClassLayoutDumper): Deleted.
(ClassLayoutDumper.__init__): Deleted.
(ClassLayoutDumper._get_first_file_architecture): Deleted.
(ClassLayoutDumper.verify_type): Deleted.
(ClassLayoutDumper.verify_type_recursive): Deleted.
(ClassLayoutDumper._class_layout_as_string): Deleted.
(ClassLayoutDumper.dump_to_string): Deleted.
(ClassLayoutDumper.dump): Deleted.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@233614 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/dump-class-layout
Tools/Scripts/webkitpy/test/main.py
Tools/lldb/dump_class_layout_unittest.py [new file with mode: 0755]
Tools/lldb/lldbWebKitTester/DumpClassLayoutTesting.cpp [new file with mode: 0644]
Tools/lldb/lldbWebKitTester/DumpClassLayoutTesting.h [new file with mode: 0644]
Tools/lldb/lldbWebKitTester/lldbWebKitTester.xcodeproj/project.pbxproj
Tools/lldb/lldbWebKitTester/main.cpp
Tools/lldb/lldb_dump_class_layout.py [new file with mode: 0755]

index 3f0edef..fc53fba 100644 (file)
@@ -1,3 +1,93 @@
+2018-06-30  Simon Fraser  <simon.fraser@apple.com>
+
+        dump-class-layout reports wrong padding in many cases
+        https://bugs.webkit.org/show_bug.cgi?id=185801
+
+        Reviewed by Dan Bates.
+        
+        Enhance dump-class-layout, fixing bugs and adding unit tests. This also includes
+        the patch from bug 187141.
+        
+        Move the code that uses lldb to fetch the class layout to its own module,
+        lldb_dump_class_layout.py, so it can be unit tested. Change this code
+        to build up a data structure for the class. This is necessary since correct
+        padding computation is easier with second traversal over the data structure.
+        
+        Try to deal with the empty base class optimization, which is necessary to report
+        correct padding in classes using std::unique_ptr, and correctly handle virtual base classes.
+        
+        The ClassLayoutBase class knows how to test for equality and generate a string
+        or string list representation of itself. The derived ClassLayout class knows
+        how to build up the class layout using the lldb Python bindings.
+        
+        Finally we wrap the lldb debugger instance in LLDBDebuggerInstance so that we don't
+        have to make a new one for each unit test. The tests have to run serially.
+        
+        Add to lldbWebKitTester a .cpp file that contains a set of C++ classes to unit-test
+        dump-class-layout.
+
+        * Scripts/dump-class-layout:
+        (main):
+        * lldb/dump_class_layout_unittest.py:
+        (destroy_cached_debug_session):
+        (TestDumpClassLayout):
+        (TestDumpClassLayout.setUpClass):
+        (TestDumpClassLayout.setUp):
+        (TestDumpClassLayout.serial_test_BasicClassLayout):
+        (serial_test_PaddingBetweenClassMembers):
+        (serial_test_BoolPaddingClass):
+        (serial_test_ClassWithEmptyClassMembers):
+        (serial_test_SimpleVirtualClass):
+        (serial_test_SimpleVirtualClassWithNonVirtualBase):
+        (serial_test_InterleavedVirtualNonVirtual):
+        (serial_test_ClassWithTwoVirtualBaseClasses):
+        (serial_test_ClassWithClassMembers):
+        (serial_test_ClassWithBitfields):
+        (serial_test_ClassWithUniquePtrs):
+        (serial_test_ClassWithOptionals):
+        (TestDumpClassLayout.test_BasicClassLayout): Deleted.
+        * lldb/lldbWebKitTester/DumpClassLayoutTesting.cpp:
+        (EmptyClass::doStuff):
+        (VirtualBaseClass::~VirtualBaseClass):
+        (VirtualBaseClass2::~VirtualBaseClass2):
+        (SimpleVirtualClass::~SimpleVirtualClass):
+        (SimpleVirtualClass::doStuff):
+        (SimpleVirtualClassWithNonVirtualBase::~SimpleVirtualClassWithNonVirtualBase):
+        (SimpleVirtualClassWithNonVirtualBase::doStuff):
+        (avoidClassDeadStripping):
+        * lldb/lldb_dump_class_layout.py:
+        (ansi_colors):
+        (ClassLayoutBase):
+        (ClassLayoutBase.__init__):
+        (ClassLayoutBase.__ne__):
+        (ClassLayoutBase.__eq__):
+        (ClassLayoutBase._to_string_recursive):
+        (ClassLayoutBase.as_string_list):
+        (ClassLayoutBase.as_string):
+        (ClassLayoutBase.dump):
+        (ClassLayoutExpected):
+        (ClassLayoutExpected.__init__):
+        (ClassLayout):
+        (ClassLayout.__init__):
+        (ClassLayout._has_polymorphic_base_class):
+        (ClassLayout._parse):
+        (ClassLayout._probably_has_empty_base_class_optimization):
+        (ClassLayout._compute_padding_recursive):
+        (ClassLayout._compute_padding):
+        (LLDBDebuggerInstance):
+        (LLDBDebuggerInstance.__init__):
+        (LLDBDebuggerInstance.__del__):
+        (LLDBDebuggerInstance._get_first_file_architecture):
+        (LLDBDebuggerInstance.layout_for_classname):
+        (ClassLayoutDumper): Deleted.
+        (ClassLayoutDumper.__init__): Deleted.
+        (ClassLayoutDumper._get_first_file_architecture): Deleted.
+        (ClassLayoutDumper.verify_type): Deleted.
+        (ClassLayoutDumper.verify_type_recursive): Deleted.
+        (ClassLayoutDumper._class_layout_as_string): Deleted.
+        (ClassLayoutDumper.dump_to_string): Deleted.
+        (ClassLayoutDumper.dump): Deleted.
+
 2018-07-06  Christopher Reid  <chris.reid@sony.com>
 
         [WinCairo] WebKit MiniBrowser crashes when attempting to navigate to certain URLs
index 6f6eba4..005ff92 100755 (executable)
@@ -31,9 +31,11 @@ import os
 import subprocess
 from sets import Set
 
-from webkitpy.common.system.systemhost import SystemHost
-sys.path.append(SystemHost().path_to_lldb_python_directory())
-import lldb
+up = os.path.dirname
+tools_directory = up(up(os.path.abspath(__file__)))
+sys.path.insert(0, os.path.join(tools_directory, "lldb"))
+
+from lldb_dump_class_layout import LLDBDebuggerInstance, ClassLayout
 
 framework = "WebCore"
 build_directory = ""
@@ -44,120 +46,6 @@ def webkit_build_dir():
     scriptpath = os.path.dirname(os.path.realpath(__file__))
     return subprocess.check_output([os.path.join(scriptpath, "webkit-build-directory"), "--top-level"]).strip()
 
-def verify_type(target, type):
-    typename = type.GetName()
-    seenOffset = Set()
-    (end_offset, padding) = verify_type_recursive(target, type, None, 0, 0, 0, seenOffset)
-    byte_size = type.GetByteSize()
-    print 'Total byte size: %u' % (byte_size)
-    print 'Total pad bytes: %u' % (padding)
-    if padding > 0:
-        print 'Padding percentage: %2.2f %%' % ((float(padding) / float(byte_size)) * 100.0)
-    print
-
-def verify_type_recursive(target, type, member_name, depth, base_offset, padding, seenOffset):
-    prev_end_offset = base_offset
-    typename = type.GetName()
-    byte_size = type.GetByteSize()
-    if member_name and member_name != typename:
-        print '%+4u <%3u> %s%s %s;' % (base_offset, byte_size, '    ' * depth, typename, member_name)
-    else:
-        print '%+4u {%3u} %s%s' % (base_offset, byte_size, '    ' * depth, typename)
-
-    members = type.members
-    if members:
-        for member_idx, member in enumerate(members):
-            member_type = member.GetType()
-            member_canonical_type = member_type.GetCanonicalType()
-            member_type_class = member_canonical_type.GetTypeClass()
-            member_name = member.GetName()
-            member_offset = member.GetOffsetInBytes()
-            member_total_offset = member_offset + base_offset
-            member_byte_size = member_type.GetByteSize()
-            member_is_class_or_struct = False
-
-            if (member_offset, member_name) in seenOffset:
-                continue
-            seenOffset.add((member_offset, member_name))
-
-            if member_type_class == lldb.eTypeClassStruct or member_type_class == lldb.eTypeClassClass:
-                member_is_class_or_struct = True
-            if member_idx == 0 and member_offset == target.GetAddressByteSize() and type.IsPolymorphicClass():
-                ptr_size = target.GetAddressByteSize()
-                print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, '    ' * (depth + 1))
-                prev_end_offset = ptr_size
-            else:
-                if prev_end_offset < member_total_offset:
-                    member_padding = member_total_offset - prev_end_offset
-                    padding = padding + member_padding
-                    print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, member_padding, '    ' * (depth + 1))
-
-            if member_is_class_or_struct:
-                (prev_end_offset, padding) = verify_type_recursive(target, member_canonical_type, member_name, depth + 1, member_total_offset, padding, seenOffset)
-            else:
-                prev_end_offset = member_total_offset + member_byte_size
-                member_typename = member_type.GetName()
-                if member.IsBitfield():
-                    print '%+4u <%3u> %s%s:%u %s;' % (member_total_offset, member_byte_size, '    ' * (depth + 1), member_typename, member.GetBitfieldSizeInBits(), member_name)
-                else:
-                    print '%+4u <%3u> %s%s %s;' % (member_total_offset, member_byte_size, '    ' * (depth + 1), member_typename, member_name)
-
-        if prev_end_offset < byte_size:
-            last_member_padding = byte_size - prev_end_offset
-            print '%+4u <%3u> %s<PADDING>' % (prev_end_offset, last_member_padding, '    ' * (depth + 1))
-            padding += last_member_padding
-    else:
-        if type.IsPolymorphicClass():
-            ptr_size = target.GetAddressByteSize()
-            print '%+4u <%3u> %s__vtbl_ptr_type * _vptr;' % (prev_end_offset, ptr_size, '    ' * (depth + 1))
-            prev_end_offset = ptr_size
-        prev_end_offset = base_offset + byte_size
-
-    return (prev_end_offset, padding)
-
-def get_first_file_architecture(framework_path):
-    p = re.compile('shared library +(\w+)$')
-    file_result = subprocess.check_output(["file", framework_path]).split('\n')
-    arches = []
-    for line in file_result:
-        match = p.search(line)
-        if match:
-            arches.append(match.group(1));
-
-    if len(arches) > 1:
-        print 'Found architectures %s, using %s' % (arches, arches[0])
-
-    if len(arches) > 0:
-        return arches[0]
-    
-    return lldb.LLDB_ARCH_DEFAULT
-
-def dump_class(framework_path, classname, architecture):
-    debugger = lldb.SBDebugger.Create()
-    debugger.SetAsync(False)
-    if not architecture:
-        architecture = get_first_file_architecture(framework_path)
-
-    target = debugger.CreateTargetWithFileAndArch(framework_path, architecture)
-    if not target:
-        print "Failed to make target for " + framework_path;
-        sys.exit(1)
-
-    module = target.GetModuleAtIndex(0)
-    if not module:
-        print "Failed to get first module in " + framework_path;
-        sys.exit(1)
-
-    types = module.FindTypes(classname)
-    if types.GetSize():
-        print 'Found %u types matching "%s" in "%s" for %s' % (len(types), classname, module.file, architecture)
-        for type in types:
-            verify_type(target, type)
-    else:
-        print 'error: no type matches "%s" in "%s"' % (classname, module.file)
-
-    lldb.SBDebugger.Destroy(debugger)
-
 def main():
     parser = argparse.ArgumentParser(description='Dumps the in-memory layout of the given class or classes, showing padding holes.')
     parser.add_argument('framework', metavar='framework',
@@ -184,7 +72,11 @@ def main():
         build_dir = args.build_directory
 
     target_path = os.path.join(build_dir, args.config, args.framework + ".framework", args.framework);
-    dump_class(target_path, args.classname, args.arch)
+    
+    lldb_instance = LLDBDebuggerInstance(target_path, args.arch)
+    class_layout = lldb_instance.layout_for_classname(args.classname)
+    class_layout.dump()
+
 
 if __name__ == "__main__":
     main()
index 4e0b8e4..edb909a 100644 (file)
@@ -291,6 +291,12 @@ class _Loader(unittest.TestLoader):
     test_method_prefixes = []
 
     def getTestCaseNames(self, testCaseClass):
+        should_skip_class_method = getattr(testCaseClass, "shouldSkip", None)
+        if callable(should_skip_class_method):
+            if testCaseClass.shouldSkip():
+                _log.info('Skipping tests in %s' % (testCaseClass.__name__))
+                return []
+
         def isTestMethod(attrname, testCaseClass=testCaseClass):
             if not hasattr(getattr(testCaseClass, attrname), '__call__'):
                 return False
diff --git a/Tools/lldb/dump_class_layout_unittest.py b/Tools/lldb/dump_class_layout_unittest.py
new file mode 100755 (executable)
index 0000000..af80ccf
--- /dev/null
@@ -0,0 +1,316 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2018 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import atexit
+import difflib
+import lldb
+import os
+import sys
+import unittest
+
+from webkitpy.common.system.systemhost import SystemHost
+from webkitpy.port.config import Config
+
+from lldb_dump_class_layout import LLDBDebuggerInstance, ClassLayoutBase
+
+_host = SystemHost()
+
+# Run these tests with ./Tools/Scripts/test-webkitpy dump_class_layout_unittest
+# Run a single test with e.g. ./Tools/Scripts/test-webkitpy dump_class_layout_unittest.TestDumpClassLayout.serial_test_ClassWithUniquePtrs
+# Compare with clang's output: clang++ -Xclang -fdump-record-layouts DumpClassLayoutTesting.cpp
+
+debugger_instance = None
+
+
+@atexit.register
+def destroy_cached_debug_session():
+    debugger_instance = None
+
+
+class TestDumpClassLayout(unittest.TestCase):
+    @classmethod
+    def shouldSkip(cls):
+        return not _host.platform.is_mac()
+
+    @classmethod
+    def setUpClass(cls):
+        global debugger_instance
+        if not debugger_instance:
+            LLDB_WEBKIT_TESTER_NAME = 'lldbWebKitTester'
+
+            config = Config(_host.executive, _host.filesystem)
+            lldbWebKitTesterExecutable = os.path.join(config.build_directory(config.default_configuration()), LLDB_WEBKIT_TESTER_NAME)
+
+            architecture = 'x86_64'
+            debugger_instance = LLDBDebuggerInstance(lldbWebKitTesterExecutable, architecture)
+            if not debugger_instance:
+                print 'Failed to create lldb debugger instance for %s' % (lldbWebKitTesterExecutable)
+
+    def setUp(self):
+        super(TestDumpClassLayout, self).setUp()
+        self.maxDiff = None
+        self.addTypeEqualityFunc(str, self.assertMultiLineEqual)
+
+    def serial_test_BasicClassLayout(self):
+        EXPECTED_RESULT = """  +0 <  8> BasicClassLayout
+  +0 <  4>   int intMember
+  +4 <  1>   bool boolMember
+  +5 <  3>   <PADDING: 3 bytes>
+Total byte size: 8
+Total pad bytes: 3
+Padding percentage: 37.50 %"""
+        actual_layout = debugger_instance.layout_for_classname('BasicClassLayout')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_PaddingBetweenClassMembers(self):
+        EXPECTED_RESULT = """  +0 < 16> PaddingBetweenClassMembers
+  +0 <  8>     BasicClassLayout basic1
+  +0 <  4>       int intMember
+  +4 <  1>       bool boolMember
+  +5 <  3>   <PADDING: 3 bytes>
+  +8 <  8>     BasicClassLayout basic2
+  +8 <  4>       int intMember
+ +12 <  1>       bool boolMember
+ +13 <  3>   <PADDING: 3 bytes>
+Total byte size: 16
+Total pad bytes: 6
+Padding percentage: 37.50 %"""
+        actual_layout = debugger_instance.layout_for_classname('PaddingBetweenClassMembers')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_BoolPaddingClass(self):
+        EXPECTED_RESULT = """  +0 < 12> BoolPaddingClass
+  +0 <  1>   bool bool1
+  +1 <  1>   bool bool2
+  +2 <  1>   bool bool3
+  +3 <  1>   <PADDING: 1 byte>
+  +4 <  8>     BoolMemberFirst memberClass
+  +4 <  1>       bool boolMember
+  +9 <  3>       <PADDING: 3 bytes>
+  +8 <  4>       int intMember
+Total byte size: 12
+Total pad bytes: 4
+Padding percentage: 33.33 %"""
+        actual_layout = debugger_instance.layout_for_classname('BoolPaddingClass')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_ClassWithEmptyClassMembers(self):
+        EXPECTED_RESULT = """  +0 < 12> ClassWithEmptyClassMembers
+  +0 <  4>   int intMember
+  +4 <  1>     EmptyClass empty1
+  +4 <  1>   <PADDING: 1 byte>
+  +5 <  1>   bool boolMember
+  +6 <  1>     EmptyClass empty2
+  +6 <  2>   <PADDING: 2 bytes>
+  +8 <  4>   int intMember2
+Total byte size: 12
+Total pad bytes: 3
+Padding percentage: 25.00 %"""
+        actual_layout = debugger_instance.layout_for_classname('ClassWithEmptyClassMembers')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_SimpleVirtualClass(self):
+        EXPECTED_RESULT = """  +0 < 24> SimpleVirtualClass
+  +0 <  8>    __vtbl_ptr_type * _vptr
+  +8 <  4>   int intMember
+ +12 <  4>   <PADDING: 4 bytes>
+ +16 <  8>   double doubleMember
+Total byte size: 24
+Total pad bytes: 4
+Padding percentage: 16.67 %"""
+        actual_layout = debugger_instance.layout_for_classname('SimpleVirtualClass')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_VirtualClassWithNonVirtualBase(self):
+        EXPECTED_RESULT = """  +0 < 24> VirtualClassWithNonVirtualBase
+  +0 <  8>    __vtbl_ptr_type * _vptr
+  +8 <  8>     BasicClassLayout BasicClassLayout
+  +8 <  4>       int intMember
+ +12 <  1>       bool boolMember
+ +13 <  3>   <PADDING: 3 bytes>
+ +16 <  8>   double doubleMember
+Total byte size: 24
+Total pad bytes: 3
+Padding percentage: 12.50 %"""
+        actual_layout = debugger_instance.layout_for_classname('VirtualClassWithNonVirtualBase')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_InterleavedVirtualNonVirtual(self):
+        EXPECTED_RESULT = """  +0 < 16> InterleavedVirtualNonVirtual
+  +0 < 16>     ClassWithVirtualBase ClassWithVirtualBase
+  +0 <  8>         VirtualBaseClass VirtualBaseClass
+  +0 <  8>            __vtbl_ptr_type * _vptr
+  +8 <  1>       bool boolMember
+  +9 <  1>   bool boolMember
+ +10 <  6>   <PADDING: 6 bytes>
+Total byte size: 16
+Total pad bytes: 6
+Padding percentage: 37.50 %"""
+        actual_layout = debugger_instance.layout_for_classname('InterleavedVirtualNonVirtual')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_ClassWithTwoVirtualBaseClasses(self):
+        EXPECTED_RESULT = """  +0 < 24> ClassWithTwoVirtualBaseClasses
+  +0 <  8>     VirtualBaseClass VirtualBaseClass
+  +0 <  8>        __vtbl_ptr_type * _vptr
+  +8 <  8>     VirtualBaseClass2 VirtualBaseClass2
+  +8 <  8>        __vtbl_ptr_type * _vptr
+ +16 <  1>   bool boolMember
+ +17 <  7>   <PADDING: 7 bytes>
+Total byte size: 24
+Total pad bytes: 7
+Padding percentage: 29.17 %"""
+        actual_layout = debugger_instance.layout_for_classname('ClassWithTwoVirtualBaseClasses')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_ClassWithVirtualInheritance(self):
+        EXPECTED_RESULT = """  +0 < 64> ClassWithVirtualInheritance
+  +0 < 32>     VirtualInheritingA VirtualInheritingA
+  +0 <  8>        __vtbl_ptr_type * _vptr
+  +8 <  4>       int intMemberA
+ +12 <  4>   <PADDING: 4 bytes>
+ +16 < 40>     VirtualInheritingB VirtualInheritingB
+ +16 <  8>        __vtbl_ptr_type * _vptr
+ +24 <  8>         BasicClassLayout BasicClassLayout
+ +24 <  4>           int intMember
+ +28 <  1>           bool boolMember
+ +45 <  3>       <PADDING: 3 bytes>
+ +32 <  4>       int intMemberB
+ +36 <  4>   <PADDING: 4 bytes>
+ +40 <  8>   double derivedMember
+ +48 < 16>     VirtualBase VirtualBase
+ +48 <  8>        __vtbl_ptr_type * _vptr
+ +56 <  1>       bool baseMember
+ +57 <  7>   <PADDING: 7 bytes>
+Total byte size: 64
+Total pad bytes: 18
+Padding percentage: 28.12 %"""
+        actual_layout = debugger_instance.layout_for_classname('ClassWithVirtualInheritance')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_ClassWithInheritanceAndClassMember(self):
+        EXPECTED_RESULT = """  +0 < 80> ClassWithInheritanceAndClassMember
+  +0 < 32>     VirtualInheritingA VirtualInheritingA
+  +0 <  8>        __vtbl_ptr_type * _vptr
+  +8 <  4>       int intMemberA
+ +12 <  4>   <PADDING: 4 bytes>
+ +16 < 40>     VirtualInheritingB dataMember
+ +16 <  8>        __vtbl_ptr_type * _vptr
+ +24 <  8>         BasicClassLayout BasicClassLayout
+ +24 <  4>           int intMember
+ +28 <  1>           bool boolMember
+ +45 <  3>       <PADDING: 3 bytes>
+ +32 <  4>       int intMemberB
+ +52 <  4>       <PADDING: 4 bytes>
+ +40 < 16>         VirtualBase VirtualBase
+ +40 <  8>            __vtbl_ptr_type * _vptr
+ +48 <  1>           bool baseMember
+ +49 <  7>   <PADDING: 7 bytes>
+ +56 <  8>   double derivedMember
+ +64 < 16>     VirtualBase VirtualBase
+ +64 <  8>        __vtbl_ptr_type * _vptr
+ +72 <  1>       bool baseMember
+ +73 <  7>   <PADDING: 7 bytes>
+Total byte size: 80
+Total pad bytes: 25
+Padding percentage: 31.25 %"""
+        actual_layout = debugger_instance.layout_for_classname('ClassWithInheritanceAndClassMember')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_DerivedClassWithIndirectVirtualInheritance(self):
+        EXPECTED_RESULT = """  +0 < 72> DerivedClassWithIndirectVirtualInheritance
+  +0 < 64>     ClassWithVirtualInheritance ClassWithVirtualInheritance
+  +0 < 32>         VirtualInheritingA VirtualInheritingA
+  +0 <  8>            __vtbl_ptr_type * _vptr
+  +8 <  4>           int intMemberA
+ +12 <  4>       <PADDING: 4 bytes>
+ +16 < 40>         VirtualInheritingB VirtualInheritingB
+ +16 <  8>            __vtbl_ptr_type * _vptr
+ +24 <  8>             BasicClassLayout BasicClassLayout
+ +24 <  4>               int intMember
+ +28 <  1>               bool boolMember
+ +45 <  3>           <PADDING: 3 bytes>
+ +32 <  4>           int intMemberB
+ +36 <  4>       <PADDING: 4 bytes>
+ +40 <  8>       double derivedMember
+ +48 <  8>   long mostDerivedMember
+ +56 < 16>     VirtualBase VirtualBase
+ +56 <  8>        __vtbl_ptr_type * _vptr
+ +64 <  1>       bool baseMember
+ +65 <  7>   <PADDING: 7 bytes>
+Total byte size: 72
+Total pad bytes: 18
+Padding percentage: 25.00 %"""
+        actual_layout = debugger_instance.layout_for_classname('DerivedClassWithIndirectVirtualInheritance')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_ClassWithClassMembers(self):
+        EXPECTED_RESULT = """  +0 < 72> ClassWithClassMembers
+  +0 <  1>   bool boolMember
+  +1 <  3>   <PADDING: 3 bytes>
+  +4 <  8>     BasicClassLayout classMember
+  +4 <  4>       int intMember
+  +8 <  1>       bool boolMember
+  +9 <  7>   <PADDING: 7 bytes>
+ +16 < 24>     ClassWithTwoVirtualBaseClasses virtualClassesMember
+ +16 <  8>         VirtualBaseClass VirtualBaseClass
+ +16 <  8>            __vtbl_ptr_type * _vptr
+ +24 <  8>         VirtualBaseClass2 VirtualBaseClass2
+ +24 <  8>            __vtbl_ptr_type * _vptr
+ +32 <  1>       bool boolMember
+ +33 <  7>   <PADDING: 7 bytes>
+ +40 <  8>   double doubleMember
+ +48 < 16>     ClassWithVirtualBase virtualClassMember
+ +48 <  8>         VirtualBaseClass VirtualBaseClass
+ +48 <  8>            __vtbl_ptr_type * _vptr
+ +56 <  1>       bool boolMember
+ +57 <  7>   <PADDING: 7 bytes>
+ +64 <  4>   int intMember
+ +68 <  4>   <PADDING: 4 bytes>
+Total byte size: 72
+Total pad bytes: 28
+Padding percentage: 38.89 %"""
+        actual_layout = debugger_instance.layout_for_classname('ClassWithClassMembers')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
+
+    def serial_test_ClassWithBitfields(self):
+        EXPECTED_RESULT = """  +0 < 12> ClassWithBitfields
+  +0 <  1>   bool boolMember
+  +1 <  4>   unsigned int bitfield1 : 1
+  +1 <  4>   unsigned int bitfield2 : 2
+  +1 <  4>   unsigned int bitfield3 : 1
+  +1 <  1>   bool bitfield4 : 1
+  +1 <  1>   bool bitfield5 : 2
+  +1 <  1>   bool bitfield6 : 1
+  +2 <  2>   <PADDING: 2 bytes>
+  +4 <  4>   int intMember
+  +8 <  4>   unsigned int bitfield7 : 1
+  +8 <  1>   bool bitfield8 : 1
+  +9 <  3>   <PADDING: 3 bytes>
+Total byte size: 12
+Total pad bytes: 5
+Padding percentage: 41.67 %"""
+        actual_layout = debugger_instance.layout_for_classname('ClassWithBitfields')
+        self.assertEqual(EXPECTED_RESULT, actual_layout.as_string())
diff --git a/Tools/lldb/lldbWebKitTester/DumpClassLayoutTesting.cpp b/Tools/lldb/lldbWebKitTester/DumpClassLayoutTesting.cpp
new file mode 100644 (file)
index 0000000..ce48eca
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DumpClassLayoutTesting.h"
+
+#include <memory>
+#include <wtf/Optional.h>
+
+/*
+*** Dumping AST Record Layout
+         0 | class BasicClassLayout
+         0 |   int intMember
+         4 |   _Bool boolMember
+           | [sizeof=8, dsize=5, align=4,
+           |  nvsize=5, nvalign=4]
+*/
+class BasicClassLayout {
+    int intMember;
+    bool boolMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class PaddingBetweenClassMembers
+         0 |   class BasicClassLayout basic1
+         0 |     int intMember
+         4 |     _Bool boolMember
+         8 |   class BasicClassLayout basic2
+         8 |     int intMember
+        12 |     _Bool boolMember
+           | [sizeof=16, dsize=16, align=4,
+           |  nvsize=16, nvalign=4]
+*/
+class PaddingBetweenClassMembers {
+    BasicClassLayout basic1;
+    BasicClassLayout basic2;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class BoolMemberFirst
+         0 |   _Bool boolMember
+         4 |   int intMember
+           | [sizeof=8, dsize=8, align=4,
+           |  nvsize=8, nvalign=4]
+*/
+class BoolMemberFirst {
+    bool boolMember;
+    int intMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class BoolPaddingClass
+         0 |   _Bool bool1
+         1 |   _Bool bool2
+         2 |   _Bool bool3
+         4 |   class BoolMemberFirst memberClass
+         4 |     _Bool boolMember
+         8 |     int intMember
+           | [sizeof=12, dsize=12, align=4,
+           |  nvsize=12, nvalign=4]
+*/
+class BoolPaddingClass {
+    bool bool1;
+    bool bool2;
+    bool bool3;
+    BoolMemberFirst memberClass;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class EmptyClass (empty)
+           | [sizeof=1, dsize=1, align=1,
+           |  nvsize=1, nvalign=1]
+*/
+class EmptyClass {
+    void doStuff() { }
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class ClassWithEmptyClassMembers
+         0 |   int intMember
+         4 |   class EmptyClass empty1 (empty)
+         5 |   _Bool boolMember
+         6 |   class EmptyClass empty2 (empty)
+         8 |   int intMember2
+           | [sizeof=12, dsize=12, align=4,
+           |  nvsize=12, nvalign=4]
+*/
+class ClassWithEmptyClassMembers {
+    int intMember;
+    EmptyClass empty1;
+    bool boolMember;
+    EmptyClass empty2;
+    int intMember2;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class VirtualBaseClass
+         0 |   (VirtualBaseClass vtable pointer)
+           | [sizeof=8, dsize=8, align=8,
+           |  nvsize=8, nvalign=8]
+*/
+class VirtualBaseClass {
+public:
+    virtual ~VirtualBaseClass() { }
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class VirtualBaseClass2
+         0 |   (VirtualBaseClass2 vtable pointer)
+           | [sizeof=8, dsize=8, align=8,
+           |  nvsize=8, nvalign=8]
+*/
+class VirtualBaseClass2 {
+public:
+    virtual ~VirtualBaseClass2() { }
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class SimpleVirtualClass
+         0 |   (SimpleVirtualClass vtable pointer)
+         8 |   int intMember
+        16 |   double doubleMember
+           | [sizeof=24, dsize=24, align=8,
+           |  nvsize=24, nvalign=8]
+*/
+class SimpleVirtualClass {
+public:
+    virtual ~SimpleVirtualClass() { }
+    virtual void doStuff() { }
+    int intMember;
+    double doubleMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class VirtualClassWithNonVirtualBase
+         0 |   (VirtualClassWithNonVirtualBase vtable pointer)
+         8 |   class BasicClassLayout (base)
+         8 |     int intMember
+        12 |     _Bool boolMember
+        16 |   double doubleMember
+           | [sizeof=24, dsize=24, align=8,
+           |  nvsize=24, nvalign=8]
+*/
+class VirtualClassWithNonVirtualBase : public BasicClassLayout  {
+public:
+    virtual ~VirtualClassWithNonVirtualBase() { }
+    virtual void doStuff() { }
+    double doubleMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class ClassWithVirtualBase
+         0 |   class VirtualBaseClass (primary base)
+         0 |     (VirtualBaseClass vtable pointer)
+         8 |   _Bool boolMember
+           | [sizeof=16, dsize=9, align=8,
+           |  nvsize=9, nvalign=8]
+*/
+class ClassWithVirtualBase: public VirtualBaseClass {
+public:
+    ClassWithVirtualBase() { }
+    virtual void doStuff() { }
+    bool boolMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class InterleavedVirtualNonVirtual
+         0 |   class ClassWithVirtualBase (primary base)
+         0 |     class VirtualBaseClass (primary base)
+         0 |       (VirtualBaseClass vtable pointer)
+         8 |     _Bool boolMember
+         9 |   _Bool boolMember
+           | [sizeof=16, dsize=10, align=8,
+           |  nvsize=10, nvalign=8]
+*/
+class InterleavedVirtualNonVirtual: public ClassWithVirtualBase {
+public:
+    InterleavedVirtualNonVirtual() { }
+    virtual ~InterleavedVirtualNonVirtual() { }
+    bool boolMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class ClassWithTwoVirtualBaseClasses
+         0 |   class VirtualBaseClass (primary base)
+         0 |     (VirtualBaseClass vtable pointer)
+         8 |   class VirtualBaseClass2 (base)
+         8 |     (VirtualBaseClass2 vtable pointer)
+        16 |   _Bool boolMember
+           | [sizeof=24, dsize=17, align=8,
+           |  nvsize=17, nvalign=8]
+*/
+class ClassWithTwoVirtualBaseClasses: public VirtualBaseClass, VirtualBaseClass2 {
+public:
+    ClassWithTwoVirtualBaseClasses() { }
+    bool boolMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class VirtualBase
+         0 |   (VirtualBase vtable pointer)
+         8 |   _Bool baseMember
+           | [sizeof=16, dsize=9, align=8,
+           |  nvsize=9, nvalign=8]
+*/
+class VirtualBase {
+public:
+    virtual ~VirtualBase() { }
+    bool baseMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class VirtualInheritingA
+         0 |   (VirtualInheritingA vtable pointer)
+         8 |   int intMemberA
+        16 |   class VirtualBase (virtual base)
+        16 |     (VirtualBase vtable pointer)
+        24 |     _Bool baseMember
+           | [sizeof=32, dsize=25, align=8,
+           |  nvsize=12, nvalign=8]
+*/
+class VirtualInheritingA: virtual public VirtualBase {
+public:
+    VirtualInheritingA() { }
+    int intMemberA;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class VirtualInheritingB
+         0 |   (VirtualInheritingB vtable pointer)
+         8 |   class BasicClassLayout (base)
+         8 |     int intMember
+        12 |     _Bool boolMember
+        16 |   int intMemberB
+        24 |   class VirtualBase (virtual base)
+        24 |     (VirtualBase vtable pointer)
+        32 |     _Bool baseMember
+           | [sizeof=40, dsize=33, align=8,
+           |  nvsize=20, nvalign=8]
+*/
+class VirtualInheritingB: public virtual VirtualBase, public BasicClassLayout {
+public:
+    VirtualInheritingB() { }
+    int intMemberB;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class ClassWithVirtualInheritance
+         0 |   class VirtualInheritingA (primary base)
+         0 |     (VirtualInheritingA vtable pointer)
+         8 |     int intMemberA
+        16 |   class VirtualInheritingB (base)
+        16 |     (VirtualInheritingB vtable pointer)
+        24 |     class BasicClassLayout (base)
+        24 |       int intMember
+        28 |       _Bool boolMember
+        32 |     int intMemberB
+        40 |   double derivedMember
+        48 |   class VirtualBase (virtual base)
+        48 |     (VirtualBase vtable pointer)
+        56 |     _Bool baseMember
+           | [sizeof=64, dsize=57, align=8,
+           |  nvsize=48, nvalign=8]
+*/
+class ClassWithVirtualInheritance : public VirtualInheritingA, public VirtualInheritingB {
+public:
+    double derivedMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class DerivedClassWithIndirectVirtualInheritance
+         0 |   class ClassWithVirtualInheritance (primary base)
+         0 |     class VirtualInheritingA (primary base)
+         0 |       (VirtualInheritingA vtable pointer)
+         8 |       int intMemberA
+        16 |     class VirtualInheritingB (base)
+        16 |       (VirtualInheritingB vtable pointer)
+        24 |       class BasicClassLayout (base)
+        24 |         int intMember
+        28 |         _Bool boolMember
+        32 |       int intMemberB
+        40 |     double derivedMember
+        48 |   long mostDerivedMember
+        56 |   class VirtualBase (virtual base)
+        56 |     (VirtualBase vtable pointer)
+        64 |     _Bool baseMember
+           | [sizeof=72, dsize=65, align=8,
+           |  nvsize=56, nvalign=8]
+*/
+class DerivedClassWithIndirectVirtualInheritance : public ClassWithVirtualInheritance {
+public:
+    long mostDerivedMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class ClassWithInheritanceAndClassMember
+         0 |   class VirtualInheritingA (primary base)
+         0 |     (VirtualInheritingA vtable pointer)
+         8 |     int intMemberA
+        16 |   class VirtualInheritingB dataMember
+        16 |     (VirtualInheritingB vtable pointer)
+        24 |     class BasicClassLayout (base)
+        24 |       int intMember
+        28 |       _Bool boolMember
+        32 |     int intMemberB
+        40 |     class VirtualBase (virtual base)
+        40 |       (VirtualBase vtable pointer)
+        48 |       _Bool baseMember
+        56 |   double derivedMember
+        64 |   class VirtualBase (virtual base)
+        64 |     (VirtualBase vtable pointer)
+        72 |     _Bool baseMember
+           | [sizeof=80, dsize=73, align=8,
+           |  nvsize=64, nvalign=8]
+*/
+class ClassWithInheritanceAndClassMember : public VirtualInheritingA {
+public:
+    VirtualInheritingB dataMember;
+    double derivedMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class ClassWithClassMembers
+         0 |   _Bool boolMember
+         4 |   class BasicClassLayout classMember
+         4 |     int intMember
+         8 |     _Bool boolMember
+        16 |   class ClassWithTwoVirtualBaseClasses virtualClassesMember
+        16 |     class VirtualBaseClass (primary base)
+        16 |       (VirtualBaseClass vtable pointer)
+        24 |     class VirtualBaseClass2 (base)
+        24 |       (VirtualBaseClass2 vtable pointer)
+        32 |     _Bool boolMember
+        40 |   double doubleMember
+        48 |   class ClassWithVirtualBase virtualClassMember
+        48 |     class VirtualBaseClass (primary base)
+        48 |       (VirtualBaseClass vtable pointer)
+        56 |     _Bool boolMember
+        64 |   int intMember
+           | [sizeof=72, dsize=68, align=8,
+           |  nvsize=68, nvalign=8]
+
+*/
+class ClassWithClassMembers {
+    bool boolMember;
+    BasicClassLayout classMember;
+    ClassWithTwoVirtualBaseClasses virtualClassesMember;
+    double doubleMember;
+    ClassWithVirtualBase virtualClassMember;
+    int intMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class ClassWithPointerMember
+         0 |   _Bool boolMember
+         8 |   class BasicClassLayout * classMemberPointer
+        16 |   int intMember
+           | [sizeof=24, dsize=20, align=8,
+           |  nvsize=20, nvalign=8]
+*/
+class ClassWithPointerMember {
+    bool boolMember;
+    BasicClassLayout* classMemberPointer;
+    int intMember;
+};
+
+/*
+*** Dumping AST Record Layout
+         0 | class ClassWithBitfields
+         0 |   _Bool boolMember
+     1:0-0 |   unsigned int bitfield1
+     1:1-2 |   unsigned int bitfield2
+     1:3-3 |   unsigned int bitfield3
+     1:4-4 |   _Bool bitfield4
+     1:5-6 |   _Bool bitfield5
+     1:7-7 |   _Bool bitfield6
+         4 |   int intMember
+     8:0-0 |   unsigned int bitfield7
+     8:1-1 |   _Bool bitfield8
+           | [sizeof=12, dsize=9, align=4,
+           |  nvsize=9, nvalign=4]
+*/
+class ClassWithBitfields {
+    bool boolMember;
+
+    unsigned bitfield1 : 1;
+    unsigned bitfield2 : 2;
+    unsigned bitfield3 : 1;
+    bool bitfield4 : 1;
+    bool bitfield5 : 2;
+    bool bitfield6 : 1;
+
+    int intMember;
+
+    unsigned bitfield7 : 1;
+    bool bitfield8 : 1;
+};
+
+void avoidClassDeadStripping()
+{
+    BasicClassLayout basicClassInstance;
+    BoolPaddingClass boolPaddingClassInstance;
+    ClassWithEmptyClassMembers classWithEmptyClassMembersInstance;
+    PaddingBetweenClassMembers paddingBetweenClassMembersInstance;
+    SimpleVirtualClass simpleVirtualClassInstance;
+    InterleavedVirtualNonVirtual interleavedVirtualNonVirtualInstance;
+    VirtualClassWithNonVirtualBase virtualClassWithNonVirtualBaseInstance;
+    ClassWithVirtualBase classWithVirtualBaseInstance;
+    ClassWithVirtualInheritance classWithVirtualInheritanceInstance;
+    DerivedClassWithIndirectVirtualInheritance derivedClassWithIndirectVirtualInheritanceInstance;
+    ClassWithInheritanceAndClassMember classWithInheritanceAndClassMemberInstance;
+    ClassWithClassMembers classWithClassMembersInstance;
+    ClassWithPointerMember classWithPointerMemberInstance;
+    ClassWithBitfields classWithBitfieldsInstance;
+}
diff --git a/Tools/lldb/lldbWebKitTester/DumpClassLayoutTesting.h b/Tools/lldb/lldbWebKitTester/DumpClassLayoutTesting.h
new file mode 100644 (file)
index 0000000..aeaa7b9
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+void avoidClassDeadStripping();
index c52a15e..283299a 100644 (file)
@@ -7,6 +7,7 @@
        objects = {
 
 /* Begin PBXBuildFile section */
+               0FC1C35420E5391E001E9FB0 /* DumpClassLayoutTesting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0FC1C35320E5391E001E9FB0 /* DumpClassLayoutTesting.cpp */; };
                7CB6844B1AFA7978002B305C /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7CB6844A1AFA7978002B305C /* main.cpp */; };
                CE6C3AFF20C0B17B003E33D8 /* libWTF.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CE6C3AFE20C0B17B003E33D8 /* libWTF.a */; };
                CE6C3B0120C0C444003E33D8 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CE6C3B0020C0C443003E33D8 /* libicucore.dylib */; };
@@ -25,6 +26,8 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+               0FC1C35320E5391E001E9FB0 /* DumpClassLayoutTesting.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DumpClassLayoutTesting.cpp; sourceTree = "<group>"; };
+               0FC1C35520E53932001E9FB0 /* DumpClassLayoutTesting.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DumpClassLayoutTesting.h; sourceTree = "<group>"; };
                7C2227511AFC4D9C008C3338 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = "<group>"; };
                7C2227521AFC4D9C008C3338 /* lldbWebKitTester.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = lldbWebKitTester.xcconfig; sourceTree = "<group>"; };
                7C2227531AFC4D9C008C3338 /* DebugRelease.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DebugRelease.xcconfig; sourceTree = "<group>"; };
@@ -78,6 +81,8 @@
                CE3F4424205C66D9007195B3 /* lldbWebKitTester */ = {
                        isa = PBXGroup;
                        children = (
+                               0FC1C35320E5391E001E9FB0 /* DumpClassLayoutTesting.cpp */,
+                               0FC1C35520E53932001E9FB0 /* DumpClassLayoutTesting.h */,
                                7CB6844A1AFA7978002B305C /* main.cpp */,
                        );
                        name = lldbWebKitTester;
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               0FC1C35420E5391E001E9FB0 /* DumpClassLayoutTesting.cpp in Sources */,
                                7CB6844B1AFA7978002B305C /* main.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
index 4e11ab7..9c72441 100644 (file)
@@ -23,6 +23,7 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include "DumpClassLayoutTesting.h"
 #include <stdio.h>
 #include <wtf/text/StringBuilder.h>
 #include <wtf/text/WTFString.h>
@@ -65,6 +66,7 @@ static void testSummaryProviders()
 
 int main(int argc, const char* argv[])
 {
+    avoidClassDeadStripping();
     testSummaryProviders();
     fprintf(stderr, "This executable does nothing and is only meant for debugging lldb_webkit.py.\n");
     return 0;
diff --git a/Tools/lldb/lldb_dump_class_layout.py b/Tools/lldb/lldb_dump_class_layout.py
new file mode 100755 (executable)
index 0000000..3ffa3c1
--- /dev/null
@@ -0,0 +1,391 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2018 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import re
+import subprocess
+import sys
+
+from webkitpy.common.system.systemhost import SystemHost
+sys.path.append(SystemHost().path_to_lldb_python_directory())
+import lldb
+
+# lldb Python reference:
+# <https://lldb.llvm.org/python_reference/>
+
+
+class AnsiColors:
+    BLUE = '\033[94m'
+    WARNING = '\033[93m'
+    ENDCOLOR = '\033[0m'
+
+
+class ClassLayoutBase(object):
+
+    MEMBER_NAME_KEY = 'name'
+    MEMBER_TYPE_KEY = 'type'
+    MEMBER_TYPE_CLASS = 'type_class'  # lldb.eTypeClassStruct etc. Values here: <https://lldb.llvm.org/python_reference/_lldb%27-module.html#eTypeClassAny>
+    MEMBER_CLASS_INSTANCE = 'class_instance'
+    MEMBER_BYTE_SIZE = 'byte_size'
+    MEMBER_OFFSET = 'offset'  # offset is local to this class.
+    MEMBER_IS_BITFIELD = 'is_bitfield'
+    MEMBER_BITFIELD_BIT_SIZE = 'bit_size'
+    PADDING_TYPE = '<PADDING>'
+    PADDING_NAME = ''
+
+    def __init__(self, typename):
+        self.typename = typename
+        self.total_byte_size = 0
+        self.total_pad_bytes = 0
+        self.data_members = []
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            return False
+
+        if self.total_byte_size != other.total_byte_size:
+            return False
+
+        if self.total_pad_bytes != other.total_pad_bytes:
+            return False
+
+        if len(self.data_members) != other.data_members:
+            return False
+
+        for i in range(len(self.data_members)):
+            self_member = self.data_members[i]
+            other_member = other.data_members[i]
+            if self_member != other_member:
+                return False
+
+        return True
+
+    def _to_string_recursive(self, str_list, colorize, member_name=None, depth=0, total_offset=0):
+        type_start = AnsiColors.BLUE if colorize else ''
+        warn_start = AnsiColors.WARNING if colorize else ''
+        color_end = AnsiColors.ENDCOLOR if colorize else ''
+
+        if member_name:
+            str_list.append('%+4u <%3u> %s%s%s%s %s' % (total_offset, self.total_byte_size, '    ' * depth, type_start, self.typename, color_end, member_name))
+        else:
+            str_list.append('%+4u <%3u> %s%s%s%s' % (total_offset, self.total_byte_size, '    ' * depth, type_start, self.typename, color_end))
+
+        start_offset = total_offset
+
+        for data_member in self.data_members:
+            member_total_offset = start_offset + data_member[self.MEMBER_OFFSET]
+            if self.MEMBER_CLASS_INSTANCE in data_member:
+                data_member[self.MEMBER_CLASS_INSTANCE]._to_string_recursive(str_list, colorize, data_member[self.MEMBER_NAME_KEY], depth + 1, member_total_offset)
+            else:
+                byte_size = data_member[self.MEMBER_BYTE_SIZE]
+
+                if self.MEMBER_IS_BITFIELD in data_member:
+                    str_list.append('%+4u <%3u> %s  %s %s : %d' % (member_total_offset, byte_size, '    ' * depth, data_member[self.MEMBER_TYPE_KEY], data_member[self.MEMBER_NAME_KEY], data_member[self.MEMBER_BITFIELD_BIT_SIZE]))
+                elif data_member[self.MEMBER_TYPE_KEY] == self.PADDING_TYPE:
+                    str_list.append('%+4u <%3u> %s  %s<PADDING: %d %s>%s' % (member_total_offset, byte_size, '    ' * depth, warn_start, byte_size, 'bytes' if byte_size > 1 else 'byte', color_end))
+                else:
+                    str_list.append('%+4u <%3u> %s  %s %s' % (member_total_offset, byte_size, '    ' * depth, data_member[self.MEMBER_TYPE_KEY], data_member[self.MEMBER_NAME_KEY]))
+
+    def as_string_list(self, colorize=False):
+        str_list = []
+        self._to_string_recursive(str_list, colorize)
+        str_list.append('Total byte size: %d' % (self.total_byte_size))
+        str_list.append('Total pad bytes: %d' % (self.total_pad_bytes))
+        if self.total_pad_bytes > 0:
+            str_list.append('Padding percentage: %2.2f %%' % ((float(self.total_pad_bytes) / float(self.total_byte_size)) * 100.0))
+        return str_list
+
+    def as_string(self, colorize=False):
+        return '\n'.join(self.as_string_list(colorize))
+
+    def dump(self, colorize=True):
+        print self.as_string(colorize)
+
+
+class ClassLayout(ClassLayoutBase):
+    "Stores the layout of a class or struct."
+
+    def __init__(self, target, type, containerClass=None, derivedClass=None):
+        super(ClassLayout, self).__init__(type.GetName())
+
+        self.target = target
+        self.type = type
+        self.total_byte_size = self.type.GetByteSize()
+        self.pointer_size = self.target.GetAddressByteSize()
+        self.total_pad_bytes = 0
+        self.data_members = []
+        self.virtual_base_classes = self._virtual_base_classes_dictionary()
+        self._parse(containerClass, derivedClass)
+        if containerClass == None and derivedClass == None:
+            self.total_pad_bytes = self._compute_padding()
+
+    def _has_polymorphic_non_virtual_base_class(self):
+        num_base_classes = self.type.GetNumberOfDirectBaseClasses()
+        for i in range(num_base_classes):
+            base_class = self.type.GetDirectBaseClassAtIndex(i)
+            if base_class.GetName() in self.virtual_base_classes:
+                continue
+
+            if base_class.GetType().IsPolymorphicClass():
+                return True
+
+        return False
+
+    def _virtual_base_classes_dictionary(self):
+        result = {}
+        num_virtual_base_classes = self.type.GetNumberOfVirtualBaseClasses()
+        for i in range(num_virtual_base_classes):
+            virtual_base = self.type.GetVirtualBaseClassAtIndex(i)
+            result[virtual_base.GetName()] = ClassLayout(self.target, virtual_base.GetType(), self)
+        return result
+
+    def _parse(self, containerClass=None, derivedClass=None):
+        # It's moot where we actually show the vtable pointer, but to match clang -fdump-record-layouts, assign it to the
+        # base-most polymorphic class (unless virtual inheritance is involved).
+        if self.type.IsPolymorphicClass() and not self._has_polymorphic_non_virtual_base_class():
+            data_member = {
+                self.MEMBER_NAME_KEY : '__vtbl_ptr_type * _vptr',
+                self.MEMBER_TYPE_KEY : '',
+                self.MEMBER_BYTE_SIZE : self.pointer_size,
+                self.MEMBER_OFFSET : 0
+            }
+            self.data_members.append(data_member)
+
+        num_direct_base_classes = self.type.GetNumberOfDirectBaseClasses()
+        if num_direct_base_classes > 0:
+            for i in range(num_direct_base_classes):
+                direct_base = self.type.GetDirectBaseClassAtIndex(i)
+
+                # virtual base classes are also considered direct base classes, but we need to skip those here.
+                if direct_base.GetName() in self.virtual_base_classes:
+                    continue
+
+                member_type = direct_base.GetType()
+                member_typename = member_type.GetName()
+                member_canonical_type = member_type.GetCanonicalType()
+                member_type_class = member_canonical_type.GetTypeClass()
+
+                member_name = direct_base.GetName()
+                member_offset = direct_base.GetOffsetInBytes()
+                member_byte_size = member_type.GetByteSize()
+
+                base_class = ClassLayout(self.target, member_type, None, self)
+
+                data_member = {
+                    self.MEMBER_NAME_KEY : member_name,
+                    self.MEMBER_TYPE_KEY : member_typename,
+                    self.MEMBER_TYPE_CLASS : member_type_class,
+                    self.MEMBER_CLASS_INSTANCE : base_class,
+                    self.MEMBER_BYTE_SIZE : member_byte_size,
+                    self.MEMBER_OFFSET : member_offset,
+                }
+
+                self.data_members.append(data_member)
+
+        num_fields = self.type.GetNumberOfFields()
+        for i in range(num_fields):
+            field = self.type.GetFieldAtIndex(i)
+
+            member_type = field.GetType()
+            member_typename = member_type.GetName()
+            member_canonical_type = member_type.GetCanonicalType()
+            member_type_class = member_canonical_type.GetTypeClass()
+
+            member_name = field.GetName()
+            member_offset = field.GetOffsetInBytes()
+            member_byte_size = member_type.GetByteSize()
+
+            data_member = {
+                self.MEMBER_NAME_KEY : member_name,
+                self.MEMBER_TYPE_KEY : member_typename,
+                self.MEMBER_TYPE_CLASS : member_type_class,
+                self.MEMBER_BYTE_SIZE : member_byte_size,
+                self.MEMBER_OFFSET : member_offset,
+            }
+
+            if field.IsBitfield():
+                data_member[self.MEMBER_IS_BITFIELD] = True
+                data_member[self.MEMBER_BITFIELD_BIT_SIZE] = field.GetBitfieldSizeInBits()
+            elif member_type_class == lldb.eTypeClassStruct or member_type_class == lldb.eTypeClassClass:
+                nested_class = ClassLayout(self.target, member_type, self)
+                data_member[self.MEMBER_CLASS_INSTANCE] = nested_class
+
+            self.data_members.append(data_member)
+
+        # "For each distinct base class that is specified virtual, the most derived object contains only one base class subobject of that type,
+        # even if the class appears many times in the inheritance hierarchy (as long as it is inherited virtual every time)."
+        num_virtual_base_classes = self.type.GetNumberOfVirtualBaseClasses()
+        if derivedClass == None and num_virtual_base_classes > 0:
+            for i in range(num_virtual_base_classes):
+                virtual_base = self.type.GetVirtualBaseClassAtIndex(i)
+                member_type = virtual_base.GetType()
+                member_typename = member_type.GetName()
+                member_canonical_type = member_type.GetCanonicalType()
+                member_type_class = member_canonical_type.GetTypeClass()
+
+                member_name = virtual_base.GetName()
+                member_offset = virtual_base.GetOffsetInBytes()
+                member_byte_size = member_type.GetByteSize()
+
+                nested_class = ClassLayout(self.target, member_type, None, self)
+
+                data_member = {
+                    self.MEMBER_NAME_KEY : member_name,
+                    self.MEMBER_TYPE_KEY : member_typename,
+                    self.MEMBER_TYPE_CLASS : member_type_class,
+                    self.MEMBER_CLASS_INSTANCE : nested_class,
+                    self.MEMBER_BYTE_SIZE : member_byte_size,
+                    self.MEMBER_OFFSET : member_offset,
+                }
+
+                self.data_members.append(data_member)
+
+    # clang -fdump-record-layouts shows "(empty)" for such classes, but I can't find any way to access this information via lldb.
+    def _probably_has_empty_base_class_optimization(self):
+        if self.total_byte_size > 1:
+            return False
+
+        if len(self.data_members) > 1:
+            return False
+
+        if len(self.data_members) == 1:
+            data_member = self.data_members[0]
+            if self.MEMBER_CLASS_INSTANCE in data_member:
+                return data_member[self.MEMBER_CLASS_INSTANCE]._probably_has_empty_base_class_optimization()
+
+        return True
+
+    def _compute_padding_recursive(self, total_offset=0, depth=0, containerClass=None):
+        padding_bytes = 0
+        start_offset = total_offset
+        current_offset = total_offset
+
+        i = 0
+        while i < len(self.data_members):
+            data_member = self.data_members[i]
+            member_offset = data_member[self.MEMBER_OFFSET]
+
+            probably_empty_base_class = False
+            if self.MEMBER_CLASS_INSTANCE in data_member:
+                probably_empty_base_class = member_offset == 0 and data_member[self.MEMBER_CLASS_INSTANCE]._probably_has_empty_base_class_optimization()
+
+            byte_size = data_member[self.MEMBER_BYTE_SIZE]
+
+            if not probably_empty_base_class:
+                padding_size = start_offset + member_offset - current_offset
+
+                if padding_size > 0:
+                    padding_member = {
+                        self.MEMBER_NAME_KEY : self.PADDING_NAME,
+                        self.MEMBER_TYPE_KEY : self.PADDING_TYPE,
+                        self.MEMBER_BYTE_SIZE : padding_size,
+                        self.MEMBER_OFFSET : current_offset,
+                    }
+
+                    self.data_members.insert(i, padding_member)
+                    padding_bytes += padding_size
+                    i += 1
+
+                current_offset = start_offset + member_offset
+
+            if self.MEMBER_CLASS_INSTANCE in data_member:
+                [padding, offset] = data_member[self.MEMBER_CLASS_INSTANCE]._compute_padding_recursive(current_offset, depth + 1, self)
+                padding_bytes += padding
+                current_offset = offset
+            else:
+                current_offset += byte_size
+
+            i += 1
+
+        # Look for padding at the end.
+        if containerClass == None:
+            padding_size = self.total_byte_size - current_offset
+            if padding_size > 0:
+                padding_member = {
+                    self.MEMBER_NAME_KEY : self.PADDING_NAME,
+                    self.MEMBER_TYPE_KEY : self.PADDING_TYPE,
+                    self.MEMBER_BYTE_SIZE : padding_size,
+                    self.MEMBER_OFFSET : current_offset,
+                }
+                self.data_members.append(padding_member)
+                padding_bytes += padding_size
+
+        return [padding_bytes, current_offset]
+
+    def _compute_padding(self):
+        [padding, offset] = self._compute_padding_recursive()
+        return padding
+
+
+class LLDBDebuggerInstance:
+    "Wraps an instance of lldb.SBDebugger and vends ClassLayouts"
+
+    def __init__(self, binary_path, architecture):
+        self.binary_path = binary_path
+        self.architecture = architecture
+
+        self.debugger = lldb.SBDebugger.Create()
+        self.debugger.SetAsync(False)
+        architecture = self.architecture
+        if not architecture:
+            architecture = self._get_first_file_architecture()
+
+        self.target = self.debugger.CreateTargetWithFileAndArch(str(self.binary_path), architecture)
+        if not self.target:
+            print "Failed to make target for " + self.binary_path
+
+        self.module = self.target.GetModuleAtIndex(0)
+        if not self.module:
+            print "Failed to get first module in " + self.binary_path
+
+    def __del__(self):
+        if lldb:
+            lldb.SBDebugger.Destroy(self.debugger)
+
+    def _get_first_file_architecture(self):
+        p = re.compile('shared library +(\w+)$')
+        file_result = subprocess.check_output(["file", self.binary_path]).split('\n')
+        arches = []
+        for line in file_result:
+            match = p.search(line)
+            if match:
+                arches.append(match.group(1))
+
+        if len(arches) > 0:
+            return arches[0]
+
+        return lldb.LLDB_ARCH_DEFAULT
+
+    def layout_for_classname(self, classname):
+        types = self.module.FindTypes(classname)
+        if types.GetSize():
+            # There can be more that one type with a given name, but for now just return the first one.
+            return ClassLayout(self.target, types.GetTypeAtIndex(0))
+
+        print 'error: no type matches "%s" in "%s"' % (classname, self.module.file)
+        return None