tberghammer created this revision.

Improve data formatter for libstdcpp unique_ptr

- Fix infinite loop when there is a reference loop created from smart pointers
- Respect pointer depth argument in frame variable command
- Support dereferencing unique_ptr in the frame variable command
- Support operator-> for unique_ptr in the frame variable command

Note: After submitting this change I plan to add the same functionality for the 
other smart pointers as well (shared_ptr/weak_ptr, libcpp/libstdcpp)


https://reviews.llvm.org/D30272

Files:
  include/lldb/Core/ValueObject.h
  include/lldb/DataFormatters/ValueObjectPrinter.h
  include/lldb/Target/Language.h
  
packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libstdcpp/unique_ptr/TestDataFormatterStdUniquePtr.py
  
packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libstdcpp/unique_ptr/main.cpp
  source/Core/ValueObject.cpp
  source/DataFormatters/ValueObjectPrinter.cpp
  source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
  source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
  source/Target/Language.cpp
  source/Target/StackFrame.cpp

Index: source/Target/StackFrame.cpp
===================================================================
--- source/Target/StackFrame.cpp
+++ source/Target/StackFrame.cpp
@@ -604,8 +604,10 @@
     // Calculate the next separator index ahead of time
     ValueObjectSP child_valobj_sp;
     const char separator_type = var_expr[0];
+    bool expr_is_ptr = false;
     switch (separator_type) {
     case '-':
+      expr_is_ptr = true;
       if (var_expr.size() >= 2 && var_expr[1] != '>')
         return ValueObjectSP();
 
@@ -622,11 +624,26 @@
           return ValueObjectSP();
         }
       }
+
+      // If we have operator-> and it is a pointer like value object then we
+      // dereference it and then handle it as operator.
+      if (valobj_sp->IsPointerLikeObject()) {
+        Error deref_error;
+        ValueObjectSP deref_sp = valobj_sp->Dereference(deref_error);
+        if (deref_sp && error.Success()) {
+          valobj_sp = deref_sp;
+          expr_is_ptr = false;
+        } else {
+          error.SetErrorStringWithFormat(
+              "Failed to dereference a pointer like object: %s",
+              deref_error.AsCString());
+          return ValueObjectSP();
+        }
+      }
+
       var_expr = var_expr.drop_front(); // Remove the '-'
       LLVM_FALLTHROUGH;
     case '.': {
-      const bool expr_is_ptr = var_expr[0] == '>';
-
       var_expr = var_expr.drop_front(); // Remove the '.' or '>'
       separator_idx = var_expr.find_first_of(".-[");
       ConstString child_name(var_expr.substr(0, var_expr.find_first_of(".-[")));
Index: source/Target/Language.cpp
===================================================================
--- source/Target/Language.cpp
+++ source/Target/Language.cpp
@@ -413,6 +413,9 @@
   s.Printf("Exception breakpoint (catch: %s throw: %s)",
            catch_on ? "on" : "off", throw_on ? "on" : "off");
 }
+
+bool Language::IsPointerLikeObject(ValueObject &valobj) { return false; }
+
 //----------------------------------------------------------------------
 // Constructor
 //----------------------------------------------------------------------
Index: source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
===================================================================
--- source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
+++ source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
@@ -101,6 +101,8 @@
   HardcodedFormatters::HardcodedSyntheticFinder
   GetHardcodedSynthetics() override;
 
+  bool IsPointerLikeObject(ValueObject &valobj) override;
+
   //------------------------------------------------------------------
   // Static Functions
   //------------------------------------------------------------------
Index: source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
===================================================================
--- source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1164,3 +1164,12 @@
 
   return g_formatters;
 }
+
+bool CPlusPlusLanguage::IsPointerLikeObject(ValueObject &valobj) {
+  static RegularExpression unique_ptr_regex("^std::unique_ptr<.+>(( )?&)?$");
+
+  llvm::StringRef name(valobj.GetCompilerType().GetTypeName().GetStringRef());
+  if (unique_ptr_regex.Execute(name))
+    return true;
+  return false;
+}
Index: source/DataFormatters/ValueObjectPrinter.cpp
===================================================================
--- source/DataFormatters/ValueObjectPrinter.cpp
+++ source/DataFormatters/ValueObjectPrinter.cpp
@@ -63,6 +63,7 @@
   m_is_ptr = eLazyBoolCalculate;
   m_is_ref = eLazyBoolCalculate;
   m_is_aggregate = eLazyBoolCalculate;
+  m_is_pointer_like = eLazyBoolCalculate;
   m_is_instance_ptr = eLazyBoolCalculate;
   m_summary_formatter = {nullptr, false};
   m_value.assign("");
@@ -208,6 +209,13 @@
   return m_is_aggregate == eLazyBoolYes;
 }
 
+bool ValueObjectPrinter::IsPointerLikeObject() {
+  if (m_is_pointer_like == eLazyBoolCalculate)
+    m_is_pointer_like =
+        m_valobj->IsPointerLikeObject() ? eLazyBoolYes : eLazyBoolNo;
+  return m_is_pointer_like == eLazyBoolYes;
+}
+
 bool ValueObjectPrinter::IsInstancePointer() {
   // you need to do this check on the value's clang type
   if (m_is_instance_ptr == eLazyBoolCalculate)
@@ -503,8 +511,8 @@
 bool ValueObjectPrinter::ShouldPrintChildren(
     bool is_failed_description,
     DumpValueObjectOptions::PointerDepth &curr_ptr_depth) {
-  const bool is_ref = IsRef();
   const bool is_ptr = IsPtr();
+  const bool is_ref_or_ptr_like = IsRef() || IsPointerLikeObject();
   const bool is_uninit = IsUninitialized();
 
   if (is_uninit)
@@ -529,16 +537,16 @@
     // Use a new temporary pointer depth in case we override the
     // current pointer depth below...
 
-    if (is_ptr || is_ref) {
+    if (is_ptr || is_ref_or_ptr_like) {
       // We have a pointer or reference whose value is an address.
       // Make sure that address is not NULL
       AddressType ptr_address_type;
       if (m_valobj->GetPointerValue(&ptr_address_type) == 0)
         return false;
 
       const bool is_root_level = m_curr_depth == 0;
 
-      if (is_ref && is_root_level) {
+      if (is_ref_or_ptr_like && is_root_level) {
         // If this is the root object (depth is zero) that we are showing
         // and it is a reference, and no pointer depth has been supplied
         // print out what it references. Don't do this at deeper depths
@@ -584,7 +592,8 @@
     const DumpValueObjectOptions::PointerDepth &curr_ptr_depth) {
   const uint32_t consumed_depth = (!m_options.m_pointer_as_array) ? 1 : 0;
   const bool does_consume_ptr_depth =
-      ((IsPtr() && !m_options.m_pointer_as_array) || IsRef());
+      ((IsPtr() && !m_options.m_pointer_as_array) || IsRef() ||
+       IsPointerLikeObject());
 
   DumpValueObjectOptions child_options(m_options);
   child_options.SetFormat(m_options.m_format)
Index: source/Core/ValueObject.cpp
===================================================================
--- source/Core/ValueObject.cpp
+++ source/Core/ValueObject.cpp
@@ -1709,6 +1709,13 @@
   return GetCompilerType().IsPointerOrReferenceType();
 }
 
+bool ValueObject::IsPointerLikeObject() {
+  Language *language = Language::FindPlugin(GetObjectRuntimeLanguage());
+  if (!language)
+    return false;
+  return language->IsPointerLikeObject(*this);
+}
+
 bool ValueObject::IsPossibleDynamicType() {
   ExecutionContext exe_ctx(GetExecutionContextRef());
   Process *process = exe_ctx.GetProcessPtr();
@@ -2889,6 +2896,8 @@
           child_is_base_class, child_is_deref_of_parent, eAddressTypeInvalid,
           language_flags);
     }
+  } else if (IsPointerLikeObject() && HasSyntheticValue()) {
+    m_deref_valobj = GetSyntheticValue()->GetChildAtIndex(0, true).get();
   }
 
   if (m_deref_valobj) {
Index: packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libstdcpp/unique_ptr/main.cpp
===================================================================
--- packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libstdcpp/unique_ptr/main.cpp
+++ packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libstdcpp/unique_ptr/main.cpp
@@ -8,6 +8,11 @@
   int b;
 };
 
+struct Foo {
+  int data;
+  std::unique_ptr<Foo> fp;
+};
+
 int main() {
   std::unique_ptr<char> nup;
   std::unique_ptr<int> iup(new int{123});
@@ -18,5 +23,11 @@
   std::unique_ptr<std::string, Deleter> sdp(new std::string("baz"),
                                             Deleter{3, 4});
 
+  // Set up a structure where we have a loop in the unique_ptr chain.
+  Foo* f1 = new Foo{1};
+  Foo* f2 = new Foo{2};
+  f1->fp.reset(f2);
+  f2->fp.reset(f1);
+
   return 0; // Set break point at this line.
 }
Index: packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libstdcpp/unique_ptr/TestDataFormatterStdUniquePtr.py
===================================================================
--- packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libstdcpp/unique_ptr/TestDataFormatterStdUniquePtr.py
+++ packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libstdcpp/unique_ptr/TestDataFormatterStdUniquePtr.py
@@ -40,7 +40,7 @@
         self.expect("frame variable ndp", substrs=['ndp = nullptr'])
         self.expect("frame variable idp", substrs=['idp = 0x', 'object = 456', 'deleter = ', 'a = 1', 'b = 2'])
         self.expect("frame variable sdp", substrs=['sdp = 0x', 'object = "baz"', 'deleter = ', 'a = 3', 'b = 4'])
-        
+
         self.assertEqual(123, frame.GetValueForVariablePath("iup.object").GetValueAsUnsigned())
         self.assertFalse(frame.GetValueForVariablePath("iup.deleter").IsValid())
 
@@ -59,3 +59,31 @@
         self.assertTrue(sdp_deleter.IsValid())
         self.assertEqual(3, sdp_deleter.GetChildMemberWithName("a").GetValueAsUnsigned())
         self.assertEqual(4, sdp_deleter.GetChildMemberWithName("b").GetValueAsUnsigned())
+
+    @skipIfFreeBSD
+    @skipIfWindows  # libstdcpp not ported to Windows
+    @skipIfDarwin  # doesn't compile on Darwin
+    def test_recursive_unique_ptr(self):
+        # Tests that LLDB can handle when we have a loop in the unique_ptr
+        # reference chain and that it correctly handles the different options
+        # for the frame variable command in this case.
+        self.build()
+        self.runCmd("file a.out", CURRENT_EXECUTABLE_SET)
+
+        lldbutil.run_break_set_by_source_regexp(
+            self, "Set break point at this line.")
+        self.runCmd("run", RUN_SUCCEEDED)
+
+        # The stop reason of the thread should be breakpoint.
+        self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
+                    substrs=['stopped', 'stop reason = breakpoint'])
+
+        self.expect("frame variable f1->fp",
+                    substrs=['data = 2', 'fp = 0x'])
+        self.expect("frame variable --ptr-depth=2 f1->fp",
+                    substrs=['data = 2', 'fp = 0x', 'object = {', 'data = 1'])
+        self.expect("frame variable f1->fp->data",
+                    substrs=['f1->fp->data = 2'])
+        self.expect("frame variable *f1->fp",
+                    substrs=['(Foo) *f1->fp', 'data = 2', 'fp = 0x'])
+
Index: include/lldb/Target/Language.h
===================================================================
--- include/lldb/Target/Language.h
+++ include/lldb/Target/Language.h
@@ -1,5 +1,4 @@
-//===-- Language.h ---------------------------------------------------*- C++
-//-*-===//
+//===-- Language.h ----------------------------------------------*- C++ -*-===//
 //
 //                     The LLVM Compiler Infrastructure
 //
@@ -221,6 +220,10 @@
   virtual void GetExceptionResolverDescription(bool catch_on, bool throw_on,
                                                Stream &s);
 
+  // Returns true if the specified value object is a class type what behaves
+  // like a pointer in the language (e.g. std::shared_ptr in c++).
+  virtual bool IsPointerLikeObject(ValueObject &valobj);
+
   static void GetDefaultExceptionResolverDescription(bool catch_on,
                                                      bool throw_on, Stream &s);
 
Index: include/lldb/DataFormatters/ValueObjectPrinter.h
===================================================================
--- include/lldb/DataFormatters/ValueObjectPrinter.h
+++ include/lldb/DataFormatters/ValueObjectPrinter.h
@@ -83,6 +83,8 @@
 
   bool IsInstancePointer();
 
+  bool IsPointerLikeObject();
+
   bool IsAggregate();
 
   bool PrintValidationMarkerIfNeeded();
@@ -148,6 +150,7 @@
   LazyBool m_is_ptr;
   LazyBool m_is_ref;
   LazyBool m_is_aggregate;
+  LazyBool m_is_pointer_like;
   LazyBool m_is_instance_ptr;
   std::pair<TypeSummaryImpl *, bool> m_summary_formatter;
   std::string m_value;
Index: include/lldb/Core/ValueObject.h
===================================================================
--- include/lldb/Core/ValueObject.h
+++ include/lldb/Core/ValueObject.h
@@ -374,6 +374,8 @@
 
   virtual bool IsPointerOrReferenceType();
 
+  virtual bool IsPointerLikeObject();
+
   virtual bool IsPossibleDynamicType();
 
   bool IsNilReference();
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to