Skip to content

Conversation

@cmtice
Copy link
Contributor

@cmtice cmtice commented Jun 11, 2025

This updates the DIL implementation to handle smart pointers (accessing field members and dereferencing) in the same way the current 'frame variable' implementation does. It also adds tests for handling smart pointers, as well as some additional DIL tests.

This updates the DIL implementation to handle smart pointers (accessing
field members and dereferencing) in the same way the current 'frame
variable' implementation does.  It also adds tests for handling smart pointers,
as well as some additional DIL tests.
@cmtice cmtice requested a review from JDevlieghere as a code owner June 11, 2025 21:26
@llvmbot llvmbot added the lldb label Jun 11, 2025
@cmtice cmtice requested review from kuilpd and labath June 11, 2025 21:26
@llvmbot
Copy link
Member

llvmbot commented Jun 11, 2025

@llvm/pr-subscribers-lldb

Author: None (cmtice)

Changes

This updates the DIL implementation to handle smart pointers (accessing field members and dereferencing) in the same way the current 'frame variable' implementation does. It also adds tests for handling smart pointers, as well as some additional DIL tests.


Patch is 20.27 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/143786.diff

21 Files Affected:

  • (modified) lldb/source/ValueObject/DILEval.cpp (+8-1)
  • (added) lldb/test/API/commands/frame/var-dil/basics/BitField/Makefile (+3)
  • (added) lldb/test/API/commands/frame/var-dil/basics/BitField/TestFrameVarDILBitField.py (+38)
  • (added) lldb/test/API/commands/frame/var-dil/basics/BitField/main.cpp (+44)
  • (added) lldb/test/API/commands/frame/var-dil/basics/Indirection/Makefile (+3)
  • (added) lldb/test/API/commands/frame/var-dil/basics/Indirection/TestFrameVarDILIndirection.py (+36)
  • (added) lldb/test/API/commands/frame/var-dil/basics/Indirection/main.cpp (+14)
  • (added) lldb/test/API/commands/frame/var-dil/basics/PointerDereference/Makefile (+3)
  • (added) lldb/test/API/commands/frame/var-dil/basics/PointerDereference/TestFrameVarDILPointerDereference.py (+30)
  • (added) lldb/test/API/commands/frame/var-dil/basics/PointerDereference/main.cpp (+32)
  • (added) lldb/test/API/commands/frame/var-dil/basics/QualifiedId/Makefile (+3)
  • (added) lldb/test/API/commands/frame/var-dil/basics/QualifiedId/TestFrameVarDILQualifiedId.py (+29)
  • (added) lldb/test/API/commands/frame/var-dil/basics/QualifiedId/main.cpp (+18)
  • (added) lldb/test/API/commands/frame/var-dil/basics/SharedPtr/Makefile (+6)
  • (added) lldb/test/API/commands/frame/var-dil/basics/SharedPtr/TestFrameVarDILSharedPtr.py (+36)
  • (added) lldb/test/API/commands/frame/var-dil/basics/SharedPtr/TestFrameVarDILSharedPtrDeref.py (+29)
  • (added) lldb/test/API/commands/frame/var-dil/basics/SharedPtr/main.cpp (+26)
  • (added) lldb/test/API/commands/frame/var-dil/basics/UniquePtr/Makefile (+6)
  • (added) lldb/test/API/commands/frame/var-dil/basics/UniquePtr/TestFrameVarDILUniquePtr.py (+28)
  • (added) lldb/test/API/commands/frame/var-dil/basics/UniquePtr/TestFrameVarDILUniquePtrDeref.py (+29)
  • (added) lldb/test/API/commands/frame/var-dil/basics/UniquePtr/main.cpp (+25)
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index c8cb54aa18a93..adc34e25766b3 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -253,6 +253,12 @@ Interpreter::Visit(const UnaryOpNode *node) {
       rhs = dynamic_rhs;
 
     lldb::ValueObjectSP child_sp = rhs->Dereference(error);
+    if (!child_sp && m_use_synthetic) {
+      if (lldb::ValueObjectSP synth_obj_sp = rhs->GetSyntheticValue()) {
+        error.Clear();
+        child_sp = synth_obj_sp->Dereference(error);
+      }
+    }
     if (error.Fail())
       return llvm::make_error<DILDiagnosticError>(m_expr, error.AsCString(),
                                                   node->GetLocation());
@@ -280,6 +286,7 @@ Interpreter::Visit(const MemberOfNode *node) {
   auto base_or_err = Evaluate(node->GetBase());
   if (!base_or_err)
     return base_or_err;
+  bool expr_is_ptr = node->GetIsArrow();
   lldb::ValueObjectSP base = *base_or_err;
 
   // Perform some basic type & correctness checking.
@@ -319,11 +326,11 @@ Interpreter::Visit(const MemberOfNode *node) {
         return llvm::make_error<DILDiagnosticError>(
             m_expr, errMsg, node->GetLocation(), node->GetFieldName().size());
       }
+      expr_is_ptr = false;
     }
   }
 
   if (m_check_ptr_vs_member) {
-    bool expr_is_ptr = node->GetIsArrow();
     bool base_is_ptr = base->IsPointerType();
 
     if (expr_is_ptr != base_is_ptr) {
diff --git a/lldb/test/API/commands/frame/var-dil/basics/BitField/Makefile b/lldb/test/API/commands/frame/var-dil/basics/BitField/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/BitField/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/frame/var-dil/basics/BitField/TestFrameVarDILBitField.py b/lldb/test/API/commands/frame/var-dil/basics/BitField/TestFrameVarDILBitField.py
new file mode 100644
index 0000000000000..8b08ec7f62d5c
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/BitField/TestFrameVarDILBitField.py
@@ -0,0 +1,38 @@
+"""
+Make sure 'frame var' using DIL parser/evaluator works for bit fields.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+import os
+import shutil
+import time
+
+class TestFrameVarDILBitField(TestBase):
+    # If your test case doesn't stress debug info, then
+    # set this to true.  That way it won't be run once for
+    # each debug info format.
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_frame_var(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "Set a breakpoint here",
+                                          lldb.SBFileSpec("main.cpp"))
+
+        self.runCmd("settings set target.experimental.use-DIL true")
+        self.expect_var_path("bf.a", value="1023")
+        self.expect_var_path("bf.b", value="9")
+        self.expect_var_path("bf.c", value="false")
+        self.expect_var_path("bf.d", value="true")
+
+        self.expect_var_path("abf.a", value="1023")
+        self.expect_var_path("abf.b", value="'\\x0f'")
+        self.expect_var_path("abf.c", value="3")
+
+        # Perform an operation to ensure we actually read the value.
+        # Address-of is not allowed for bit-fields.
+        self.expect("frame variable '&bf.a'", error=True,
+                    substrs=["'bf.a' doesn't have a valid address"])
diff --git a/lldb/test/API/commands/frame/var-dil/basics/BitField/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/BitField/main.cpp
new file mode 100644
index 0000000000000..bd9b23a44ebbe
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/BitField/main.cpp
@@ -0,0 +1,44 @@
+#include <cstdint>
+
+int
+main(int argc, char **argv)
+{
+  enum BitFieldEnum : uint32_t { kZero, kOne };
+
+  struct BitFieldStruct {
+    uint16_t a : 10;
+    uint32_t b : 4;
+    bool c : 1;
+    bool d : 1;
+    int32_t e : 32;
+    uint32_t f : 32;
+    uint32_t g : 31;
+    uint64_t h : 31;
+    uint64_t i : 33;
+    BitFieldEnum j : 10;
+  };
+
+  BitFieldStruct bf;
+  bf.a = 0b1111111111;
+  bf.b = 0b1001;
+  bf.c = 0b0;
+  bf.d = 0b1;
+  bf.e = 0b1;
+  bf.f = 0b1;
+  bf.g = 0b1;
+  bf.h = 0b1;
+  bf.i = 0b1;
+  bf.j = BitFieldEnum::kOne;
+
+  struct AlignedBitFieldStruct {
+    uint16_t a : 10;
+    uint8_t b : 4;
+    unsigned char : 0;
+    uint16_t c : 2;
+  };
+
+  uint32_t data = ~0;
+  AlignedBitFieldStruct abf = (AlignedBitFieldStruct&)data;
+
+  return 0; // Set a breakpoint here
+}
diff --git a/lldb/test/API/commands/frame/var-dil/basics/Indirection/Makefile b/lldb/test/API/commands/frame/var-dil/basics/Indirection/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/Indirection/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/frame/var-dil/basics/Indirection/TestFrameVarDILIndirection.py b/lldb/test/API/commands/frame/var-dil/basics/Indirection/TestFrameVarDILIndirection.py
new file mode 100644
index 0000000000000..13acc4232bd7c
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/Indirection/TestFrameVarDILIndirection.py
@@ -0,0 +1,36 @@
+"""
+Make sure 'frame var' using DIL parser/evaluator works for indirection.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+import os
+import shutil
+import time
+
+class TestFrameVarDILIndirection(TestBase):
+    # If your test case doesn't stress debug info, then
+    # set this to true.  That way it won't be run once for
+    # each debug info format.
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_frame_var(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "Set a breakpoint here",
+                                          lldb.SBFileSpec("main.cpp"))
+
+        self.runCmd("settings set target.experimental.use-DIL true")
+        self.expect_var_path("*p", value="1")
+        self.expect_var_path("p", type="int *")
+        self.expect_var_path("*my_p", value="1")
+        self.expect_var_path("my_p", type="myp")
+        self.expect_var_path("*my_pr", type="int *")
+        self.expect_var_path("my_pr", type="mypr")
+
+        self.expect("frame variable '*1'", error=True,
+                    substrs=["Unexpected token: <'1' (numeric_constant)>"])
+        self.expect("frame variable '*val'", error=True,
+                    substrs=["dereference failed: not a pointer, reference or array type: (int) val"])
diff --git a/lldb/test/API/commands/frame/var-dil/basics/Indirection/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/Indirection/main.cpp
new file mode 100644
index 0000000000000..827968a71519a
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/Indirection/main.cpp
@@ -0,0 +1,14 @@
+int
+main(int argc, char **argv)
+{
+  int val = 1;
+  int* p = &val;
+
+  typedef int* myp;
+  myp my_p = &val;
+
+  typedef int*& mypr;
+  mypr my_pr = p;
+
+  return 0; // Set a breakpoint here
+}
diff --git a/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/Makefile b/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/TestFrameVarDILPointerDereference.py b/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/TestFrameVarDILPointerDereference.py
new file mode 100644
index 0000000000000..7f5acc26dd40b
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/TestFrameVarDILPointerDereference.py
@@ -0,0 +1,30 @@
+"""
+Test DIL pointer dereferencing.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+import os
+import shutil
+import time
+
+class TestFrameVarDILPointerDereference(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_frame_var(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
+        )
+
+        self.runCmd("settings set target.experimental.use-DIL true")
+        self.expect_var_path("*p_int0", value="0")
+        self.expect_var_path("*cp_int5", value="5")
+        self.expect_var_path("&pp_void0[2]", type="void **")
+        self.expect_var_path("**pp_int0", value="0")
+        self.expect_var_path("&**pp_int0", type="int *")
+        self.expect("frame variable '&p_void[0]'", error=True,
+                    substrs=["subscript of pointer to incomplete type 'void'"])
diff --git a/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/main.cpp
new file mode 100644
index 0000000000000..719fa81b60830
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/PointerDereference/main.cpp
@@ -0,0 +1,32 @@
+#include <cstddef>
+
+int main (int argc, char **argv) {
+    int* p_null = nullptr;
+  const char* p_char1 = "hello";
+
+  typedef const char* my_char_ptr;
+  my_char_ptr my_p_char1 = p_char1;
+
+  int offset = 5;
+  int array[10];
+  array[0] = 0;
+  array[offset] = offset;
+
+  int(&array_ref)[10] = array;
+
+  int* p_int0 = &array[0];
+  int** pp_int0 = &p_int0;
+  const int* cp_int0 = &array[0];
+  const int* cp_int5 = &array[offset];
+
+  typedef int* td_int_ptr_t;
+  td_int_ptr_t td_int_ptr0 = &array[0];
+
+  void* p_void = (void*)p_char1;
+  void** pp_void0 = &p_void;
+  void** pp_void1 = pp_void0 + 1;
+
+  std::nullptr_t std_nullptr_t = nullptr;
+
+  return 0; // Set a breakpoint here
+}
diff --git a/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/Makefile b/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/TestFrameVarDILQualifiedId.py b/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/TestFrameVarDILQualifiedId.py
new file mode 100644
index 0000000000000..3d91198ea3ab9
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/TestFrameVarDILQualifiedId.py
@@ -0,0 +1,29 @@
+"""
+Make sure 'frame var' using DIL parser/evaluator works for namespaces.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+import os
+import shutil
+import time
+
+class TestFrameVarDILQualifiedId(TestBase):
+    # If your test case doesn't stress debug info, then
+    # set this to true.  That way it won't be run once for
+    # each debug info format.
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_frame_var(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "Set a breakpoint here",
+                                          lldb.SBFileSpec("main.cpp"))
+
+        self.runCmd("settings set target.experimental.use-DIL true")
+        self.expect_var_path("::ns::i", value="1")
+        self.expect_var_path("ns::i", value="1")
+        self.expect_var_path("::ns::ns::i", value="2")
+        self.expect_var_path("ns::ns::i", value="2")
diff --git a/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/main.cpp
new file mode 100644
index 0000000000000..3316d0b807868
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/QualifiedId/main.cpp
@@ -0,0 +1,18 @@
+namespace ns {
+
+int i = 1;
+
+namespace ns {
+
+int i = 2;
+
+}  // namespace ns
+
+}  // namespace ns
+
+int
+main(int argc, char **argv)
+{
+
+  return 0; // Set a breakpoint here
+}
diff --git a/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/Makefile b/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/Makefile
new file mode 100644
index 0000000000000..af1baa7931b39
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/Makefile
@@ -0,0 +1,6 @@
+CXX_SOURCES := main.cpp
+CXXFLAGS_EXTRAS := -std=c++14
+
+USE_LIBCPP := 1
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/TestFrameVarDILSharedPtr.py b/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/TestFrameVarDILSharedPtr.py
new file mode 100644
index 0000000000000..94fe0340dc98c
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/TestFrameVarDILSharedPtr.py
@@ -0,0 +1,36 @@
+"""
+Make sure 'frame var' using DIL parser/evaluator works for shared/weak  pointers.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+import os
+import shutil
+import time
+
+class TestFrameVarDILSharedPtr(TestBase):
+    # If your test case doesn't stress debug info, then
+    # set this to true.  That way it won't be run once for
+    # each debug info format.
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_frame_var(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "Set a breakpoint here",
+                                          lldb.SBFileSpec("main.cpp"))
+
+        self.runCmd("settings set target.experimental.use-DIL true")
+        self.expect_var_path("ptr_node.__ptr_",
+                    type="std::shared_ptr<NodeS>::element_type *")
+        self.expect_var_path("ptr_node.__ptr_->value", value="1")
+        self.expect_var_path("ptr_node.__ptr_->next.__ptr_->value", value="2")
+
+        self.expect_var_path("ptr_int.__ptr_",
+                    type="std::shared_ptr<int>::element_type *")
+        self.expect_var_path("*ptr_int.__ptr_", value="1")
+        self.expect_var_path("ptr_int_weak.__ptr_",
+                    type="std::weak_ptr<int>::element_type *")
+        self.expect_var_path("*ptr_int_weak.__ptr_", value="1")
diff --git a/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/TestFrameVarDILSharedPtrDeref.py b/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/TestFrameVarDILSharedPtrDeref.py
new file mode 100644
index 0000000000000..32b389bec8fa4
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/TestFrameVarDILSharedPtrDeref.py
@@ -0,0 +1,29 @@
+"""
+Make sure 'frame var' using DIL parser/evaluator works for shared pointer deref.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+import os
+import shutil
+import time
+
+class TestFrameVarDILSharedPtrDeref(TestBase):
+    # If your test case doesn't stress debug info, then
+    # set this to true.  That way it won't be run once for
+    # each debug info format.
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_frame_var(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "Set a breakpoint here",
+                                          lldb.SBFileSpec("main.cpp"))
+
+        self.runCmd("settings set target.experimental.use-DIL true")
+        self.expect_var_path("ptr_node->value", value="1")
+        self.expect_var_path("ptr_node->next->value", value="2")
+        self.expect_var_path("(*ptr_node).value", value="1")
+        self.expect_var_path("(*(*ptr_node).next).value", value="2")
diff --git a/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/main.cpp
new file mode 100644
index 0000000000000..642f5172a37f6
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/SharedPtr/main.cpp
@@ -0,0 +1,26 @@
+#include <memory>
+
+int
+main(int argc, char **argv)
+{
+
+  struct NodeS {
+    std::shared_ptr<NodeS> next;
+    int value;
+  };
+  auto ptr_node = std::shared_ptr<NodeS>(new NodeS{nullptr, 2});
+  ptr_node = std::shared_ptr<NodeS>(new NodeS{std::move(ptr_node), 1});
+
+  std::shared_ptr<char> ptr_null;
+  auto ptr_int = std::make_shared<int>(1);
+  auto ptr_float = std::make_shared<float>(1.1f);
+
+  std::weak_ptr<int> ptr_int_weak = ptr_int;
+
+  std::shared_ptr<void> ptr_void = ptr_int;
+
+  // TestSharedPtr
+  // TestSharedPtrDeref
+  // TestSharedPtrCompare
+  return 0; // Set a breakpoint here
+}
diff --git a/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/Makefile b/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/Makefile
new file mode 100644
index 0000000000000..af1baa7931b39
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/Makefile
@@ -0,0 +1,6 @@
+CXX_SOURCES := main.cpp
+CXXFLAGS_EXTRAS := -std=c++14
+
+USE_LIBCPP := 1
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/TestFrameVarDILUniquePtr.py b/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/TestFrameVarDILUniquePtr.py
new file mode 100644
index 0000000000000..b7f9627f8a8c0
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/TestFrameVarDILUniquePtr.py
@@ -0,0 +1,28 @@
+"""
+Make sure 'frame var' using DIL parser/evaluator works for unique pointers.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+import os
+import shutil
+import time
+
+class TestFrameVarDILUniquePtr(TestBase):
+    # If your test case doesn't stress debug info, then
+    # set this to true.  That way it won't be run once for
+    # each debug info format.
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_frame_var(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "Set a breakpoint here",
+                                          lldb.SBFileSpec("main.cpp"))
+
+        self.runCmd("settings set target.experimental.use-DIL true")
+        self.expect_var_path("ptr_node.__ptr_", type="NodeU *")
+        self.expect_var_path("ptr_node.__ptr_->value", value="1")
+        self.expect_var_path("ptr_node.__ptr_->next.__ptr_->value", value="2")
diff --git a/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/TestFrameVarDILUniquePtrDeref.py b/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/TestFrameVarDILUniquePtrDeref.py
new file mode 100644
index 0000000000000..065fb9df33444
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/TestFrameVarDILUniquePtrDeref.py
@@ -0,0 +1,29 @@
+"""
+Make sure 'frame var' using DIL parser/evaluator works for unique pointer derefs.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+import os
+import shutil
+import time
+
+class TestFrameVarDILUniquePtrDeref(TestBase):
+    # If your test case doesn't stress debug info, then
+    # set this to true.  That way it won't be run once for
+    # each debug info format.
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_frame_var(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "Set a breakpoint here",
+                                          lldb.SBFileSpec("main.cpp"))
+
+        self.runCmd("settings set target.experimental.use-DIL true")
+        self.expect_var_path("ptr_node->value", value="1")
+        self.expect_var_path("ptr_node->next->value", value="2")
+        self.expect_var_path("(*ptr_node).value", value="1")
+        self.expect_var_path("(*(*ptr_node).next).value", value="2")
diff --git a/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/main.cpp
new file mode 100644
index 0000000000000..f20f6f116a084
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/basics/UniquePtr/main.cpp
@@ -0,0 +1,25 @@
+#include <memory>
+
+int
+main(int argc, char **argv)
+{
+
+  struct NodeU {
+    std::unique_ptr<NodeU> next;
+    int value;
+  };
+  auto ptr_node = std::unique_ptr<NodeU>(new NodeU{nullptr, 2});
+  ptr_node = std::unique_ptr<NodeU>(new NodeU{std::move(ptr_node), 1});
+
+  std::unique_ptr<char> ptr_null;
+  auto ptr_int = std::make_unique<int>(1);
+  auto ptr_float = std::make_unique<float>(1.1f)...
[truncated]

@cmtice cmtice requested a review from asl June 11, 2025 21:27
@github-actions
Copy link

github-actions bot commented Jun 11, 2025

✅ With the latest revision this PR passed the Python code formatter.

@github-actions
Copy link

github-actions bot commented Jun 11, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good, but it seems that you need to reformat it (both c++ and python).

Instead of hardcoding libc++ implementation details, it would be better to write your own "smart" pointer. libc++ changes the internal representation fairly frequently, and we shouldn't rely on it where it isn't necessary. If this test is meant to stay here ~forever, I think that's what we should do, but I'm not sure what is the long-term future of these tests.

Have you thought about what happens to them when we flip the flag (and maybe after the old implementation is removed)? I would expect that most of this functionality is already tested elsewhere, so I have a feeling that some sort of deduplication would be in order. And since you've found this issue by running the test suite, I assume that this is tested elsewhere (maybe inside the std::shared_ptr formatter test?)..

cmtice added 3 commits June 15, 2025 22:22
Test synthetic format provider similar to LibCxx smart pointers,
but independent of actual smart pointer implementation (so not brittle test).
@cmtice
Copy link
Contributor Author

cmtice commented Jun 20, 2025

I think I have addressed all the review comments; please take another look.

@cmtice cmtice requested review from jimingham and werat June 20, 2025 15:39
Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good. I just have issues with the test(s). Please, see inline comments.

// Fake smart pointer definition.
class smart_ptr {
public:
NodeS *__ptr_;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically a reserved identifier. Let's just use ptr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 18 to 21
value_type = internal_child.GetType().GetPointerType()
cast_ptr_sp = internal_child.Cast(value_type)
value = internal_child.Dereference()
return value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast, is a noop, right? Any reason to not remove it?

Suggested change
value_type = internal_child.GetType().GetPointerType()
cast_ptr_sp = internal_child.Cast(value_type)
value = internal_child.Dereference()
return value
return internal_child.Dereference()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

@labath labath Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a bit of an issue with the test name and location, I think it sets up the wrong expectations. The DIL should be language agnostic, so it shouldn't work (only) with (c++) smart pointers. Even in c++ you have non-pointer types that can be "dereferenced" (std::optional, for one).

The DIL should work with with any type which provides a synthetic dereference operator, so if we're going to have a folder for all DIL tests, this test (which uses a type with a synthetic deref op) should be there. And to drive the point home, I think it ought to be called something like TestSyntheticDeref.py. I don't have an issue with the name smart_ptr in main.cpp, but I would like to the python test code to reflect the genericness of this feature (in the same way that the actual implementation does not reference any particular type).

OTOH, I believe tests for std::shared/unique_ptr would be better off in test/API/functionalities/data-formatter/data-formatter-stl, where we that operations on that specific type work. Then you can compare them with the existing test for these types (and their data formatters). If your testing something not covered by the existing test, just add it there. Otherwise, delete it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to move most of the tests around as you suggested. I have kept the dil smart pointer tests separate from the existing stl tests (although I moved them over to the data-formattter-stl/libcxx tree). They do test functionality that the existing tests don't.

Comment on lines +25 to +29
self.expect_var_path("*p_int0", value="0")
self.expect_var_path("*cp_int5", value="5")
self.expect_var_path("&pp_void0[2]", type="void **")
self.expect_var_path("**pp_int0", value="0")
self.expect_var_path("&**pp_int0", type="int *")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the goal here is to explicitly check handling pointer arithmetic, you may want to add additional checks for the values you get this way. expect_var_path returns the SBValue it has located, so you could do something like:

pp_void0_2_got = self.expect_var_path("&pp_void0[2]", type="void **")
pp_void0_2_exp = self.expect_var_path("pp_void0_2", type="void **") # Initialized in C++ code to point to the same value
self.assertEqual(pp_void0_2_got.GetValueAsAddress(), pp_void0_2_exp.GetValueAsAddress())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! Done.

cmtice added 2 commits June 23, 2025 22:58
- Moved	DIL SharedPtr and UniquePtr tests to
 API/functionalities/data-formatter/data-formatter-stl/libcxx/dil_shared_ptr
and
 API/functionalities/data-formatter/data-formatter-stl/libcxx/dil_unique_ptr
- Moved	my new test to API/command/frame/var-dil/basics/SyntheticDereference.
- Changed 'smart_ptr' to 'wrap_ptr' in new test.
- Changed internal field name from '__ptr_' to 'ptr.
- Remove dead code from	test.
- Update pointer dereference test to test some expected	vs actual addresses,
  as suggested.
Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Could you also merge them into the existing tests (as a separate test method)? The existing tests are of the same type ("break and inspect a bunch of variables") so it should be easy enough and this way one can see all tests for a type in single place.

Comment on lines 20 to 22
// TestSharedPtr
// TestSharedPtrDeref
// TestSharedPtrCompare
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment on lines 21 to 23
// TestUniquePtr
// TestUniquePtrDeref
// TestUniquePtrCompare
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

@cmtice
Copy link
Contributor Author

cmtice commented Jun 27, 2025

Thanks. Could you also merge them into the existing tests (as a separate test method)? The existing tests are of the same type ("break and inspect a bunch of variables") so it should be easy enough and this way one can see all tests for a type in single place.

Done? I think?

Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant adding a new test method (def test_something(self): self.build(); self.run(); your code), but this is fine too, especially if the switch to the new implementation is imminent.

@cmtice cmtice merged commit da2969b into llvm:main Jun 27, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants