[Lldb-commits] [lldb] 2ea3c8a - [formatters] Add a deque formatter for libstdcpp and fix the libcxx one

Walter Erquinigo via lldb-commits lldb-commits at lists.llvm.org
Mon Dec 6 13:42:14 PST 2021


Author: Walter Erquinigo
Date: 2021-12-06T13:42:03-08:00
New Revision: 2ea3c8a50add5436cf939d59c3235408ca0255c1

URL: https://github.com/llvm/llvm-project/commit/2ea3c8a50add5436cf939d59c3235408ca0255c1
DIFF: https://github.com/llvm/llvm-project/commit/2ea3c8a50add5436cf939d59c3235408ca0255c1.diff

LOG: [formatters] Add a deque formatter for libstdcpp and fix the libcxx one

This adds the formatters for libstdcpp's deque as a python
implementation. It adds comprehensive tests for the two different
storage strategies deque uses. Besides that, this fixes a couple of bugs
in the libcxx implementation. Finally, both implementation run against
the same tests.

This is a minor improvement on top of Danil Stefaniuc's formatter.

Added: 
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/Makefile
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/main.cpp

Modified: 
    lldb/examples/synthetic/gnu_libstdcpp.py
    lldb/examples/synthetic/libcxx.py
    lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp

Removed: 
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/Makefile
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/TestDataFormatterLibcxxDeque.py
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/main.cpp


################################################################################
diff  --git a/lldb/examples/synthetic/gnu_libstdcpp.py b/lldb/examples/synthetic/gnu_libstdcpp.py
index 87db880288265..022071322d058 100644
--- a/lldb/examples/synthetic/gnu_libstdcpp.py
+++ b/lldb/examples/synthetic/gnu_libstdcpp.py
@@ -684,3 +684,135 @@ def has_children(self):
         return True
 
 _list_uses_loop_detector = True
+
+class StdDequeSynthProvider:
+    def __init__(self, valobj, d):
+        self.valobj = valobj
+        self.pointer_size = self.valobj.GetProcess().GetAddressByteSize()
+        self.count = None
+        self.block_size = -1
+        self.element_size = -1
+        self.find_block_size()
+
+
+    def find_block_size(self):
+        # in order to use the deque we must have the block size, or else
+        # it's impossible to know what memory addresses are valid
+        self.element_type = self.valobj.GetType().GetTemplateArgumentType(0)
+        if not self.element_type.IsValid():
+            return
+        self.element_size = self.element_type.GetByteSize()
+        # The block size (i.e. number of elements per subarray) is defined in
+        # this piece of code, so we need to replicate it.
+        #
+        # #define _GLIBCXX_DEQUE_BUF_SIZE 512
+        #
+        # return (__size < _GLIBCXX_DEQUE_BUF_SIZE
+	    #   ? size_t(_GLIBCXX_DEQUE_BUF_SIZE / __size) : size_t(1));
+        if self.element_size < 512:
+            self.block_size = 512 // self.element_size
+        else:
+            self.block_size = 1
+
+    def num_children(self):
+        if self.count is None:
+            return 0
+        return self.count
+
+    def has_children(self):
+        return True
+
+    def get_child_index(self, name):
+        try:
+            return int(name.lstrip('[').rstrip(']'))
+        except:
+            return -1
+
+    def get_child_at_index(self, index):
+        if index < 0 or self.count is None:
+            return None
+        if index >= self.num_children():
+            return None
+        try:
+            name = '[' + str(index) + ']'
+            # We first look for the element in the first subarray,
+            # which might be incomplete.
+            if index < self.first_node_size:
+                # The following statement is valid because self.first_elem is the pointer
+                # to the first element
+                return self.first_elem.CreateChildAtOffset(name, index * self.element_size, self.element_type)
+
+            # Now the rest of the subarrays except for maybe the last one
+            # are going to be complete, so the final expression is simpler
+            i, j = divmod(index - self.first_node_size, self.block_size)
+
+            # We first move to the beginning of the node/subarray were our element is
+            node = self.start_node.CreateChildAtOffset(
+                '',
+                (1 + i) * self.valobj.GetProcess().GetAddressByteSize(),
+                self.element_type.GetPointerType())
+            return node.CreateChildAtOffset(name, j * self.element_size, self.element_type)
+
+        except:
+            return None
+
+    def update(self):
+        logger = lldb.formatters.Logger.Logger()
+        self.count = 0
+        try:
+            # A deque is effectively a two-dim array, with fixed width.
+            # However, only a subset of this memory contains valid data
+            # since a deque may have some slack at the front and back in
+            # order to have O(1) insertion at both ends.
+            # The rows in active use are delimited by '_M_start' and
+            # '_M_finish'.
+            #
+            # To find the elements that are actually constructed, the 'start'
+            # variable tells which element in this NxM array is the 0th
+            # one.
+            if self.block_size < 0 or self.element_size < 0:
+                return False
+
+            count = 0
+
+            impl = self.valobj.GetChildMemberWithName('_M_impl')
+
+            # we calculate the size of the first node (i.e. first internal array)
+            self.start = impl.GetChildMemberWithName('_M_start')
+            self.start_node = self.start.GetChildMemberWithName('_M_node')
+            first_node_address = self.start_node.GetValueAsUnsigned(0)
+            first_node_last_elem = self.start.GetChildMemberWithName('_M_last').GetValueAsUnsigned(0)
+            self.first_elem = self.start.GetChildMemberWithName('_M_cur')
+            first_node_first_elem = self.first_elem.GetValueAsUnsigned(0)
+
+
+            finish = impl.GetChildMemberWithName('_M_finish')
+            last_node_address = finish.GetChildMemberWithName('_M_node').GetValueAsUnsigned(0)
+            last_node_first_elem = finish.GetChildMemberWithName('_M_first').GetValueAsUnsigned(0)
+            last_node_last_elem = finish.GetChildMemberWithName('_M_cur').GetValueAsUnsigned(0)
+
+            if first_node_first_elem == 0 or first_node_last_elem == 0 or first_node_first_elem > first_node_last_elem:
+                return False
+            if last_node_first_elem == 0 or last_node_last_elem == 0 or last_node_first_elem > last_node_last_elem:
+                return False
+
+
+            if last_node_address == first_node_address:
+                self.first_node_size = (last_node_last_elem - first_node_first_elem) // self.element_size
+                count += self.first_node_size
+            else:
+                self.first_node_size = (first_node_last_elem - first_node_first_elem) // self.element_size
+                count += self.first_node_size
+
+                # we calculate the size of the last node
+                finish = impl.GetChildMemberWithName('_M_finish')
+                last_node_address = finish.GetChildMemberWithName('_M_node').GetValueAsUnsigned(0)
+                count += (last_node_last_elem - last_node_first_elem) // self.element_size
+
+                # we calculate the size of the intermediate nodes
+                num_intermediate_nodes = (last_node_address - first_node_address - 1) // self.valobj.GetProcess().GetAddressByteSize()
+                count += self.block_size * num_intermediate_nodes
+            self.count = count
+        except:
+            pass
+        return False

diff  --git a/lldb/examples/synthetic/libcxx.py b/lldb/examples/synthetic/libcxx.py
index 97593725a2467..531d26784afc2 100644
--- a/lldb/examples/synthetic/libcxx.py
+++ b/lldb/examples/synthetic/libcxx.py
@@ -657,16 +657,15 @@ def find_block_size(self):
         #    static const 
diff erence_type __block_size = sizeof(value_type) < 256 ? 4096 / sizeof(value_type) : 16;
         # }
         if self.element_size < 256:
-            self.block_size = 4096 / self.element_size
+            self.block_size = 4096 // self.element_size
         else:
             self.block_size = 16
 
     def num_children(self):
-        global _deque_capping_size
         logger = lldb.formatters.Logger.Logger()
         if self.count is None:
             return 0
-        return min(self.count, _deque_capping_size)
+        return self.count
 
     def has_children(self):
         return True
@@ -687,9 +686,10 @@ def get_child_at_index(self, index):
             return None
         try:
             i, j = divmod(self.start + index, self.block_size)
+
             return self.first.CreateValueFromExpression(
                 '[' + str(index) + ']', '*(*(%s + %d) + %d)' %
-                (self.first.get_expr_path(), i, j))
+                (self.map_begin.get_expr_path(), i, j))
         except:
             return None
 
@@ -727,12 +727,14 @@ def update(self):
                 '__start_').GetValueAsUnsigned(0)
             first = map_.GetChildMemberWithName('__first_')
             map_first = first.GetValueAsUnsigned(0)
-            map_begin = map_.GetChildMemberWithName(
-                '__begin_').GetValueAsUnsigned(0)
+            self.map_begin = map_.GetChildMemberWithName(
+                '__begin_')
+            map_begin = self.map_begin.GetValueAsUnsigned(0)
             map_end = map_.GetChildMemberWithName(
                 '__end_').GetValueAsUnsigned(0)
             map_endcap = self._get_value_of_compressed_pair(
                     map_.GetChildMemberWithName( '__end_cap_'))
+
             # check consistency
             if not map_first <= map_begin <= map_end <= map_endcap:
                 logger.write("map pointers are not monotonic")
@@ -750,18 +752,7 @@ def update(self):
             if junk:
                 logger.write("begin-first doesnt align correctly")
                 return
-            if not start_row * \
-                    self.block_size <= start < (start_row + 1) * self.block_size:
-                logger.write("0th element must be in the 'begin' row")
-                return
-            end_row = start_row + active_rows
-            if not count:
-                if active_rows:
-                    logger.write("empty deque but begin!=end")
-                    return
-            elif not (end_row - 1) * self.block_size <= start + count < end_row * self.block_size:
-                logger.write("nth element must be before the 'end' row")
-                return
+
             logger.write(
                 "update success: count=%r, start=%r, first=%r" %
                 (count, start, first))
@@ -774,6 +765,7 @@ def update(self):
             self.start = None
             self.map_first = None
             self.map_begin = None
+        return False
 
 
 class stdsharedptr_SynthProvider:
@@ -873,4 +865,3 @@ def __lldb_init_module(debugger, dict):
 _map_capping_size = 255
 _list_capping_size = 255
 _list_uses_loop_detector = True
-_deque_capping_size = 255

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index f1925990e94ac..589fb3190ac6a 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -903,6 +903,11 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       SyntheticChildrenSP(new ScriptedSyntheticChildren(
           stl_synth_flags,
           "lldb.formatters.cpp.gnu_libstdcpp.StdMapLikeSynthProvider")));
+  cpp_category_sp->GetRegexTypeSyntheticsContainer()->Add(
+      RegularExpression("^std::deque<.+>(( )?&)?$"),
+      SyntheticChildrenSP(new ScriptedSyntheticChildren(
+          stl_deref_flags,
+          "lldb.formatters.cpp.gnu_libstdcpp.StdDequeSynthProvider")));
   cpp_category_sp->GetRegexTypeSyntheticsContainer()->Add(
       RegularExpression("^std::set<.+> >(( )?&)?$"),
       SyntheticChildrenSP(new ScriptedSyntheticChildren(
@@ -962,6 +967,10 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       RegularExpression("^std::set<.+> >(( )?&)?$"),
       TypeSummaryImplSP(
           new StringSummaryFormat(stl_summary_flags, "size=${svar%#}")));
+  cpp_category_sp->GetRegexTypeSummariesContainer()->Add(
+      RegularExpression("^std::deque<.+>(( )?&)?$"),
+      TypeSummaryImplSP(
+          new StringSummaryFormat(stl_summary_flags, "size=${svar%#}")));
   cpp_category_sp->GetRegexTypeSummariesContainer()->Add(
       RegularExpression("^std::multimap<.+> >(( )?&)?$"),
       TypeSummaryImplSP(

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/Makefile
similarity index 73%
rename from lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/Makefile
rename to lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/Makefile
index c5df567e01a2a..99998b20bcb05 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/Makefile
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/Makefile
@@ -1,5 +1,3 @@
 CXX_SOURCES := main.cpp
 
-USE_LIBCPP := 1
-
 include Makefile.rules

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py
new file mode 100644
index 0000000000000..a995d842d4d65
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/TestDataFormatterGenericDeque.py
@@ -0,0 +1,76 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+USE_LIBSTDCPP = "USE_LIBSTDCPP"
+USE_LIBCPP = "USE_LIBCPP"
+
+class GenericDequeDataFormatterTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def findVariable(self, name):
+        var = self.frame().FindVariable(name)
+        self.assertTrue(var.IsValid())
+        return var
+
+    def getVariableType(self, name):
+        var = self.findVariable(name)
+        return var.GetType().GetDisplayTypeName()
+
+    def check_size(self, var_name, size):
+        var = self.findVariable(var_name)
+        self.assertEqual(var.GetNumChildren(), size)
+
+
+    def do_test(self, stdlib_type):
+        self.build(dictionary={stdlib_type: '1'})
+        lldbutil.run_to_source_breakpoint(self, "break here",
+                                          lldb.SBFileSpec("main.cpp"))
+
+        self.expect_expr("empty", result_children=[])
+        self.expect_expr("deque_1", result_children=[
+            ValueCheck(name="[0]", value="1"),
+        ])
+        self.expect_expr("deque_3", result_children=[
+            ValueCheck(name="[0]", value="3"),
+            ValueCheck(name="[1]", value="1"),
+            ValueCheck(name="[2]", value="2")
+        ])
+
+        self.check_size("deque_200_small", 200)
+        for i in range(0, 100):
+            self.expect_var_path("deque_200_small[%d]"%(i), children=[
+                ValueCheck(name="a", value=str(-99 + i)),
+                ValueCheck(name="b", value=str(-100 + i)),
+                ValueCheck(name="c", value=str(-101 + i)),
+            ])
+            self.expect_var_path("deque_200_small[%d]"%(i + 100), children=[
+                ValueCheck(name="a", value=str(i)),
+                ValueCheck(name="b", value=str(1 + i)),
+                ValueCheck(name="c", value=str(2 + i)),
+            ])
+
+        self.check_size("deque_200_large", 200)
+        for i in range(0, 100):
+            self.expect_var_path("deque_200_large[%d]"%(i), children=[
+                ValueCheck(name="a", value=str(-99 + i)),
+                ValueCheck(name="b", value=str(-100 + i)),
+                ValueCheck(name="c", value=str(-101 + i)),
+                ValueCheck(name="d")
+            ])
+            self.expect_var_path("deque_200_large[%d]"%(i + 100), children=[
+                ValueCheck(name="a", value=str(i)),
+                ValueCheck(name="b", value=str(1 + i)),
+                ValueCheck(name="c", value=str(2 + i)),
+                ValueCheck(name="d")
+            ])
+
+    @add_test_categories(["libstdcxx"])
+    def test_libstdcpp(self):
+        self.do_test(USE_LIBSTDCPP)
+
+    @add_test_categories(["libc++"])
+    def test_libcpp(self):
+         self.do_test(USE_LIBCPP)

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/main.cpp
new file mode 100644
index 0000000000000..b948fe1b4b375
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/deque/main.cpp
@@ -0,0 +1,41 @@
+#include <cstdio>
+#include <deque>
+
+struct Foo_small {
+  int a;
+  int b;
+  int c;
+
+  Foo_small(int a, int b, int c) : a(a), b(b), c(c) {}
+};
+
+struct Foo_large {
+  int a;
+  int b;
+  int c;
+  char d[1000] = {0};
+
+  Foo_large(int a, int b, int c) : a(a), b(b), c(c) {}
+};
+
+template <typename T> T fill(T deque) {
+  for (int i = 0; i < 100; i++) {
+    deque.push_back({i, i + 1, i + 2});
+    deque.push_front({-i, -(i + 1), -(i + 2)});
+  }
+  return deque;
+}
+
+int main() {
+  std::deque<int> empty;
+  std::deque<int> deque_1 = {1};
+  std::deque<int> deque_3 = {3, 1, 2};
+
+  std::deque<Foo_small> deque_200_small;
+  deque_200_small = fill<std::deque<Foo_small>>(deque_200_small);
+
+  std::deque<Foo_large> deque_200_large;
+  deque_200_large = fill<std::deque<Foo_large>>(deque_200_large);
+
+  return empty.size() + deque_1.front() + deque_3.front(); // break here
+}

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/TestDataFormatterLibcxxDeque.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/TestDataFormatterLibcxxDeque.py
deleted file mode 100644
index b9949288c9892..0000000000000
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/TestDataFormatterLibcxxDeque.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import lldb
-from lldbsuite.test.decorators import *
-from lldbsuite.test.lldbtest import *
-from lldbsuite.test import lldbutil
-
-
-class LibcxxDequeDataFormatterTestCase(TestBase):
-
-    mydir = TestBase.compute_mydir(__file__)
-
-    @add_test_categories(["libc++"])
-    def test(self):
-        self.build()
-        lldbutil.run_to_source_breakpoint(self, "break here",
-                                          lldb.SBFileSpec("main.cpp"))
-
-        self.expect_expr("empty", result_children=[])
-        self.expect_expr("deque_1", result_children=[
-            ValueCheck(name="[0]", value="1"),
-        ])
-        self.expect_expr("deque_3", result_children=[
-            ValueCheck(name="[0]", value="3"),
-            ValueCheck(name="[1]", value="1"),
-            ValueCheck(name="[2]", value="2")
-        ])

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/main.cpp
deleted file mode 100644
index 43c3f374a0f98..0000000000000
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/deque/main.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#include <deque>
-
-int main() {
-  std::deque<int> empty;
-  std::deque<int> deque_1 = {1};
-  std::deque<int> deque_3 = {3, 1, 2};
-  return empty.size() + deque_1.front() + deque_3.front(); // break here
-}


        


More information about the lldb-commits mailing list