[Lldb-commits] [lldb] [lldb] Resolve ObjC property backing storage in dwim-print (PR #225204)

Piotr Jeremicz via lldb-commits lldb-commits at lists.llvm.org
Mon Sep 21 14:48:26 PDT 2026


https://github.com/piotrekjeremicz updated https://github.com/llvm/llvm-project/pull/225204

>From 780ae8ef6d73e058210962fd17de6c0dc32bffa1 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Fri, 11 Sep 2026 14:39:07 +0200
Subject: [PATCH 1/5] [lldb][DWARF] Attach synthesized ivar to Objective-C
 property decls

---
 .../SymbolFile/DWARF/DWARFASTParserClang.cpp  | 72 +++++++++++++++++--
 .../SymbolFile/DWARF/DWARFASTParserClang.h    |  9 +++
 .../TypeSystem/Clang/TypeSystemClang.cpp      | 15 ++--
 3 files changed, 81 insertions(+), 15 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index b60f1d9e41958a..c039aa3234a07f 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -48,6 +48,7 @@
 #include "clang/AST/Type.h"
 #include "clang/Basic/Specifiers.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringMap.h"
 #include "llvm/DebugInfo/DWARF/DWARFAddressRange.h"
 #include "llvm/DebugInfo/DWARF/DWARFTypePrinter.h"
 #include "llvm/Demangle/Demangle.h"
@@ -1983,21 +1984,41 @@ class DWARFASTParserClang::DelayedAddObjCClassProperty {
                                                 // required if you don't have an
                                                 // ivar decl
       const char *property_setter_name, const char *property_getter_name,
-      uint32_t property_attributes, ClangASTMetadata metadata)
+      uint32_t property_attributes, ClangASTMetadata metadata,
+      llvm::StringRef property_ivar_name)
       : m_class_opaque_type(class_opaque_type), m_property_name(property_name),
         m_property_opaque_type(property_opaque_type),
         m_property_setter_name(property_setter_name),
         m_property_getter_name(property_getter_name),
-        m_property_attributes(property_attributes), m_metadata(metadata) {}
+        m_property_attributes(property_attributes), m_metadata(metadata),
+        m_property_ivar_name(property_ivar_name) {}
 
   bool Finalize() {
     return TypeSystemClang::AddObjCClassProperty(
         m_class_opaque_type, m_property_name, m_property_opaque_type,
-        /*ivar_decl=*/nullptr, m_property_setter_name, m_property_getter_name,
+        FindIvarDecl(), m_property_setter_name, m_property_getter_name,
         m_property_attributes, m_metadata);
   }
 
 private:
+  clang::ObjCIvarDecl *FindIvarDecl() {
+    if (m_property_ivar_name.empty())
+      return nullptr;
+
+    clang::ObjCInterfaceDecl *class_interface_decl =
+        TypeSystemClang::GetAsObjCInterfaceDecl(m_class_opaque_type);
+    if (!class_interface_decl)
+      return nullptr;
+
+    auto ast = m_class_opaque_type.GetTypeSystem<TypeSystemClang>();
+    if (!ast)
+      return nullptr;
+
+    clang::IdentifierInfo &ivar_ident =
+        ast->getASTContext().Idents.get(m_property_ivar_name);
+    return class_interface_decl->lookupInstanceVariable(&ivar_ident);
+  }
+
   CompilerType m_class_opaque_type;
   const char *m_property_name;
   CompilerType m_property_opaque_type;
@@ -2005,6 +2026,7 @@ class DWARFASTParserClang::DelayedAddObjCClassProperty {
   const char *m_property_getter_name;
   uint32_t m_property_attributes;
   ClangASTMetadata m_metadata;
+  llvm::StringRef m_property_ivar_name;
 };
 
 static std::optional<clang::APValue> MakeAPValue(const clang::ASTContext &ast,
@@ -2831,9 +2853,39 @@ PropertyAttributes::PropertyAttributes(const DWARFDIE &die) {
   }
 }
 
+DWARFASTParserClang::PropertyBackingStorageNames
+DWARFASTParserClang::ParsePropertyBackingStorageNames(
+    const DWARFDIE &parent_die) {
+  PropertyBackingStorageNames property_backing_names;
+
+  for (DWARFDIE die : parent_die.children()) {
+    if (die.Tag() != DW_TAG_property)
+      continue;
+
+    const char *prop_name = die.GetName();
+    if (!prop_name)
+      continue;
+
+    for (DWARFDIE child_die : die.children()) {
+      if (child_die.Tag() != DW_TAG_property_getter)
+        continue;
+
+      if (DWARFDIE backing_die = child_die.GetAttributeValueAsReferenceDIE(
+              DW_AT_property_forward)) {
+        if (const char *backing_name = backing_die.GetName())
+          property_backing_names[prop_name] = backing_name;
+      }
+      break;
+    }
+  }
+
+  return property_backing_names;
+}
+
 void DWARFASTParserClang::ParseObjCProperty(
     const DWARFDIE &die, const DWARFDIE &parent_die,
     const lldb_private::CompilerType &class_clang_type,
+    const PropertyBackingStorageNames &property_backing_names,
     DelayedPropertyList &delayed_properties) {
   // This function can only parse DW_TAG_APPLE_property.
   assert(die.Tag() == DW_TAG_APPLE_property);
@@ -2859,12 +2911,18 @@ void DWARFASTParserClang::ParseObjCProperty(
     return;
   }
 
+  llvm::StringRef property_ivar_name;
+  auto backing_name_it = property_backing_names.find(propAttrs.prop_name);
+  if (backing_name_it != property_backing_names.end())
+    property_ivar_name = backing_name_it->second;
+
   ClangASTMetadata metadata;
   metadata.SetUserID(die.GetID());
   delayed_properties.emplace_back(
       class_clang_type, propAttrs.prop_name,
       member_type->GetLayoutCompilerType(), propAttrs.prop_setter_name,
-      propAttrs.prop_getter_name, propAttrs.prop_attributes, metadata);
+      propAttrs.prop_getter_name, propAttrs.prop_attributes, metadata,
+      property_ivar_name);
 }
 
 llvm::Expected<llvm::APInt> DWARFASTParserClang::ExtractIntFromFormValue(
@@ -3140,12 +3198,16 @@ bool DWARFASTParserClang::ParseChildMembers(
   if (ast == nullptr)
     return false;
 
+  const PropertyBackingStorageNames property_backing_names =
+      ParsePropertyBackingStorageNames(parent_die);
+
   for (DWARFDIE die : parent_die.children()) {
     dw_tag_t tag = die.Tag();
 
     switch (tag) {
     case DW_TAG_APPLE_property:
-      ParseObjCProperty(die, parent_die, class_clang_type, delayed_properties);
+      ParseObjCProperty(die, parent_die, class_clang_type,
+                        property_backing_names, delayed_properties);
       break;
 
     case DW_TAG_variant_part:
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
index 03c431c73fb6ff..1d4569a7588eca 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
@@ -14,6 +14,7 @@
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
 
 #include "DWARFASTParser.h"
 #include "DWARFDIE.h"
@@ -137,6 +138,8 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
   class DelayedAddObjCClassProperty;
   typedef std::vector<DelayedAddObjCClassProperty> DelayedPropertyList;
 
+  typedef llvm::StringMap<llvm::StringRef> PropertyBackingStorageNames;
+
   typedef llvm::DenseMap<
       const lldb_private::plugin::dwarf::DWARFDebugInfoEntry *,
       clang::DeclContext *>
@@ -204,6 +207,9 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
       DelayedPropertyList &delayed_properties,
       lldb_private::ClangASTImporter::LayoutInfo &layout_info);
 
+  PropertyBackingStorageNames ParsePropertyBackingStorageNames(
+      const lldb_private::plugin::dwarf::DWARFDIE &parent_die);
+
   void ParseChildParameters(
       clang::DeclContext *containing_decl_ctx,
       const lldb_private::plugin::dwarf::DWARFDIE &parent_die,
@@ -399,12 +405,15 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
   /// \param parent_die The parent DIE.
   /// \param class_clang_type The Objective-C class that will contain the
   /// created property.
+  /// \param property_backing_names Map from property name to backing member
+  /// name, as produced by ParsePropertyBackingStorageNames.
   /// \param delayed_properties The list of delayed properties that the result
   /// will be appended to.
   void
   ParseObjCProperty(const lldb_private::plugin::dwarf::DWARFDIE &die,
                     const lldb_private::plugin::dwarf::DWARFDIE &parent_die,
                     const lldb_private::CompilerType &class_clang_type,
+                    const PropertyBackingStorageNames &property_backing_names,
                     DelayedPropertyList &delayed_properties);
 
   void
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index 5255ec835c0a44..ac84a66b3a129d 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -7939,21 +7939,16 @@ bool TypeSystemClang::AddObjCClassProperty(
   if (!class_interface_decl || !property_clang_type_to_access.IsValid())
     return false;
 
-  clang::TypeSourceInfo *prop_type_source;
-  if (ivar_decl)
-    prop_type_source = clang_ast.getTrivialTypeSourceInfo(ivar_decl->getType());
-  else
-    prop_type_source = clang_ast.getTrivialTypeSourceInfo(
-        ClangUtil::GetQualType(property_clang_type));
+  clang::QualType property_qual_type =
+      ClangUtil::GetQualType(property_clang_type_to_access);
+  clang::TypeSourceInfo *prop_type_source =
+      clang_ast.getTrivialTypeSourceInfo(property_qual_type);
 
   clang::ObjCPropertyDecl *property_decl =
       clang::ObjCPropertyDecl::CreateDeserialized(clang_ast, GlobalDeclID());
   property_decl->setDeclContext(class_interface_decl);
   property_decl->setDeclName(&clang_ast.Idents.get(property_name));
-  property_decl->setType(ivar_decl
-                             ? ivar_decl->getType()
-                             : ClangUtil::GetQualType(property_clang_type),
-                         prop_type_source);
+  property_decl->setType(property_qual_type, prop_type_source);
   SetMemberOwningModule(property_decl, class_interface_decl);
 
   if (!property_decl)

>From 37c191ca4cbc041c650ba16e0ebf47ff18bf16f1 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Fri, 11 Sep 2026 22:18:40 +0200
Subject: [PATCH 2/5] [lldb] Add CompilerType::GetPropertyBackingStorageName
 for ObjC properties

---
 lldb/include/lldb/Symbol/CompilerType.h       |  4 +++
 lldb/include/lldb/Symbol/TypeSystem.h         | 12 +++++++
 .../TypeSystem/Clang/TypeSystemClang.cpp      | 35 +++++++++++++++++++
 .../TypeSystem/Clang/TypeSystemClang.h        |  4 +++
 lldb/source/Symbol/CompilerType.cpp           | 10 ++++++
 5 files changed, 65 insertions(+)

diff --git a/lldb/include/lldb/Symbol/CompilerType.h b/lldb/include/lldb/Symbol/CompilerType.h
index 168946cf17475e..ffeacd49ffa69b 100644
--- a/lldb/include/lldb/Symbol/CompilerType.h
+++ b/lldb/include/lldb/Symbol/CompilerType.h
@@ -485,6 +485,10 @@ class CompilerType {
 
   CompilerType GetDirectNestedTypeWithName(llvm::StringRef name) const;
 
+  /// See TypeSystem::GetPropertyBackingStorageName.
+  llvm::StringRef
+  GetPropertyBackingStorageName(llvm::StringRef property_name) const;
+
   /// Return the number of template arguments the type has.
   /// If expand_pack is true, then variadic argument packs are automatically
   /// expanded to their supplied arguments. If it is false an argument pack
diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h
index 9ec0f13814975a..3c141b9fc0dd3d 100644
--- a/lldb/include/lldb/Symbol/TypeSystem.h
+++ b/lldb/include/lldb/Symbol/TypeSystem.h
@@ -403,6 +403,18 @@ class TypeSystem : public PluginInterface,
     return CompilerType();
   }
 
+  /// Returns the name of the member that backs the named property on
+  /// \a type, if this type system knows of one (e.g. an ivar reached via
+  /// DW_AT_property_forward on a DWARF DW_TAG_property).
+  ///
+  /// \return An empty string if \a type has no property named
+  /// \a property_name, or if that property has no known backing storage.
+  virtual llvm::StringRef
+  GetPropertyBackingStorageName(lldb::opaque_compiler_type_t type,
+                                llvm::StringRef property_name) {
+    return llvm::StringRef();
+  }
+
   virtual bool IsTemplateType(lldb::opaque_compiler_type_t type);
 
   virtual size_t GetNumTemplateArguments(lldb::opaque_compiler_type_t type,
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index ac84a66b3a129d..2f4b4a65433763 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -7090,6 +7090,41 @@ TypeSystemClang::GetDirectNestedTypeWithName(lldb::opaque_compiler_type_t type,
   return CompilerType();
 }
 
+llvm::StringRef TypeSystemClang::GetPropertyBackingStorageName(
+    lldb::opaque_compiler_type_t type, llvm::StringRef property_name) {
+  if (!type || property_name.empty())
+    return llvm::StringRef();
+
+  CompilerType compiler_type(weak_from_this(), type);
+
+  // Callers will usually hand us an object pointer (e.g. `Foo *`); strip it
+  // to get at the interface itself.
+  CompilerType class_type;
+  if (IsObjCObjectPointerType(compiler_type, &class_type))
+    compiler_type = class_type;
+
+  if (!GetCompleteType(compiler_type.GetOpaqueQualType()))
+    return llvm::StringRef();
+
+  clang::ObjCInterfaceDecl *class_interface_decl =
+      GetAsObjCInterfaceDecl(compiler_type);
+  if (!class_interface_decl)
+    return llvm::StringRef();
+
+  clang::IdentifierInfo &property_ident =
+      getASTContext().Idents.get(property_name);
+  clang::ObjCPropertyDecl *property_decl =
+      class_interface_decl->FindPropertyDeclaration(
+          &property_ident, clang::ObjCPropertyQueryKind::OBJC_PR_query_instance);
+  if (!property_decl)
+    return llvm::StringRef();
+
+  if (clang::ObjCIvarDecl *ivar_decl = property_decl->getPropertyIvarDecl())
+    return ivar_decl->getName();
+
+  return llvm::StringRef();
+}
+
 bool TypeSystemClang::IsTemplateType(lldb::opaque_compiler_type_t type) {
   if (!type)
     return false;
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
index 28e79f2b0b11f8..cfeac83d3c8398 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
@@ -927,6 +927,10 @@ class TypeSystemClang : public TypeSystem {
   CompilerType GetDirectNestedTypeWithName(lldb::opaque_compiler_type_t type,
                                            llvm::StringRef name) override;
 
+  llvm::StringRef
+  GetPropertyBackingStorageName(lldb::opaque_compiler_type_t type,
+                                llvm::StringRef property_name) override;
+
   bool IsTemplateType(lldb::opaque_compiler_type_t type) override;
 
   size_t GetNumTemplateArguments(lldb::opaque_compiler_type_t type,
diff --git a/lldb/source/Symbol/CompilerType.cpp b/lldb/source/Symbol/CompilerType.cpp
index 6f20bfa89ed38c..8e6f7c03191d5d 100644
--- a/lldb/source/Symbol/CompilerType.cpp
+++ b/lldb/source/Symbol/CompilerType.cpp
@@ -970,6 +970,16 @@ CompilerType::GetDirectNestedTypeWithName(llvm::StringRef name) const {
   return CompilerType();
 }
 
+llvm::StringRef
+CompilerType::GetPropertyBackingStorageName(llvm::StringRef property_name) const {
+  if (IsValid() && !property_name.empty()) {
+    if (auto type_system_sp = GetTypeSystem())
+      return type_system_sp->GetPropertyBackingStorageName(m_type,
+                                                            property_name);
+  }
+  return llvm::StringRef();
+}
+
 size_t CompilerType::GetNumTemplateArguments(bool expand_pack) const {
   if (IsValid()) {
     if (auto type_system_sp = GetTypeSystem())

>From 1912b436ff0717383f593d0da562de1f6936dd22 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Mon, 21 Sep 2026 23:00:23 +0200
Subject: [PATCH 3/5] [lldb] Teach dwim-print to resolve ObjC property backing
 storage

---
 .../Commands/CommandObjectDWIMPrint.cpp       | 87 +++++++++++++++++--
 lldb/source/Commands/CommandObjectDWIMPrint.h |  6 ++
 2 files changed, 84 insertions(+), 9 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
index 1b0b4c7881cfc1..9df520e4b4089d 100644
--- a/lldb/source/Commands/CommandObjectDWIMPrint.cpp
+++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
@@ -22,6 +22,7 @@
 #include "lldb/lldb-defines.h"
 #include "lldb/lldb-enumerations.h"
 #include "lldb/lldb-forward.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 
 #include <regex>
@@ -51,6 +52,54 @@ CommandObjectDWIMPrint::CommandObjectDWIMPrint(CommandInterpreter &interpreter)
 
 Options *CommandObjectDWIMPrint::GetOptions() { return &m_option_group; }
 
+std::string CommandObjectDWIMPrint::RewritePathForBackingStorage(
+    llvm::StringRef expr, StackFrame &frame,
+    lldb::DynamicValueType use_dynamic) {
+  llvm::SmallVector<llvm::StringRef, 4> components;
+  expr.split(components, '.');
+  if (components.empty())
+    return {};
+
+  VariableSP var_sp;
+  Status status;
+  ValueObjectSP valobj_sp = frame.GetValueForVariableExpressionPath(
+      components[0], use_dynamic,
+      StackFrame::eExpressionPathOptionsAllowDirectIVarAccess |
+          StackFrame::eExpressionPathOptionsDisallowGlobals,
+      var_sp, status, lldb::eDILModeSimple);
+  if (!valobj_sp || !status.Success() || valobj_sp->GetError().Fail())
+    return {};
+
+  std::string rewritten_path = components[0].str();
+  bool did_rewrite = false;
+
+  for (llvm::StringRef component :
+       llvm::ArrayRef(components).drop_front()) {
+    ValueObjectSP child_sp = valobj_sp->GetChildMemberWithName(component);
+    llvm::StringRef name_used = component;
+    if (!child_sp) {
+      llvm::StringRef backing_name =
+          valobj_sp->GetCompilerType().GetPropertyBackingStorageName(
+              component);
+      if (backing_name.empty())
+        return {};
+      child_sp = valobj_sp->GetChildMemberWithName(backing_name);
+      if (!child_sp)
+        return {};
+      name_used = backing_name;
+      did_rewrite = true;
+    }
+    rewritten_path += '.';
+    rewritten_path += name_used;
+    valobj_sp = child_sp;
+  }
+
+  if (!did_rewrite)
+    return {};
+
+  return rewritten_path;
+}
+
 void CommandObjectDWIMPrint::DoExecute(StringRef command,
                                        CommandReturnObject &result) {
   m_option_group.NotifyOptionParsingStarting(&m_exe_ctx);
@@ -165,14 +214,34 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
   const bool try_variable_path =
       expr.find_first_of("*&->[]") == StringRef::npos;
   if (frame && try_variable_path) {
-    VariableSP var_sp;
-    Status status;
-    auto valobj_sp = frame->GetValueForVariableExpressionPath(
-        expr, eval_options.GetUseDynamic(),
-        StackFrame::eExpressionPathOptionsAllowDirectIVarAccess |
-            StackFrame::eExpressionPathOptionsDisallowGlobals,
-        var_sp, status, lldb::eDILModeSimple);
-    if (valobj_sp && status.Success() && valobj_sp->GetError().Success()) {
+    auto try_variable_expr = [&](llvm::StringRef path) -> ValueObjectSP {
+      VariableSP var_sp;
+      Status status;
+      auto valobj_sp = frame->GetValueForVariableExpressionPath(
+          path, eval_options.GetUseDynamic(),
+          StackFrame::eExpressionPathOptionsAllowDirectIVarAccess |
+              StackFrame::eExpressionPathOptionsDisallowGlobals,
+          var_sp, status, lldb::eDILModeSimple);
+      if (valobj_sp && status.Success() && valobj_sp->GetError().Success())
+        return valobj_sp;
+      return nullptr;
+    };
+
+    StringRef used_path = expr;
+    ValueObjectSP valobj_sp = try_variable_expr(expr);
+
+    std::string rewritten_path;
+    if (!valobj_sp) {
+      rewritten_path = RewritePathForBackingStorage(
+          expr, *frame, eval_options.GetUseDynamic());
+      if (!rewritten_path.empty()) {
+        valobj_sp = try_variable_expr(rewritten_path);
+        if (valobj_sp)
+          used_path = rewritten_path;
+      }
+    }
+
+    if (valobj_sp) {
       if (!suppress_result) {
         if (auto persisted_valobj = valobj_sp->Persist())
           valobj_sp = persisted_valobj;
@@ -183,7 +252,7 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
         if (args.HasArgs())
           flags = args.GetArgString();
         result.AppendNoteWithFormatv("ran `frame variable {0}{1}`", flags,
-                                     expr);
+                                     used_path);
       }
 
       dump_val_object(*valobj_sp);
diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.h b/lldb/source/Commands/CommandObjectDWIMPrint.h
index 01ba9c225e3301..b5e82bb692eac1 100644
--- a/lldb/source/Commands/CommandObjectDWIMPrint.h
+++ b/lldb/source/Commands/CommandObjectDWIMPrint.h
@@ -17,6 +17,8 @@
 
 namespace lldb_private {
 
+class StackFrame;
+
 /// Implements `dwim-print`, a printing command that chooses the most direct,
 /// efficient, and resilient means of printing a given expression.
 ///
@@ -42,6 +44,10 @@ class CommandObjectDWIMPrint : public CommandObjectRaw {
 private:
   void DoExecute(llvm::StringRef command, CommandReturnObject &result) override;
 
+  std::string
+  RewritePathForBackingStorage(llvm::StringRef expr, StackFrame &frame,
+                               lldb::DynamicValueType use_dynamic);
+
   OptionGroupOptions m_option_group;
   OptionGroupFormat m_format_options = lldb::eFormatDefault;
   OptionGroupValueObjectDisplay m_varobj_options;

>From 81b5c6909c49943a317d64c00166abb5e01781c7 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Mon, 21 Sep 2026 23:32:16 +0200
Subject: [PATCH 4/5] [lldb] Added DWIM tests

---
 .../dwim-print/objc/TestDWIMPrintObjC.py      | 26 +++++++++++++++++++
 lldb/test/API/commands/dwim-print/objc/main.m | 20 ++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/lldb/test/API/commands/dwim-print/objc/TestDWIMPrintObjC.py b/lldb/test/API/commands/dwim-print/objc/TestDWIMPrintObjC.py
index a25a1925272933..1ff57a2fd061f9 100644
--- a/lldb/test/API/commands/dwim-print/objc/TestDWIMPrintObjC.py
+++ b/lldb/test/API/commands/dwim-print/objc/TestDWIMPrintObjC.py
@@ -25,3 +25,29 @@ def test_with_summary(self):
         self.runCmd("type summary add -s 'Parent of ${var._child._name}' 'Parent *'")
         self.expect("dwim-print parent", matching=False, substrs=["_child = 0x"])
         self.expect("dwim-print parent", substrs=['Parent of @"Seven"'])
+
+    @requireDarwin
+    def test_property_backing_storage(self):
+        """A property backed by a differently-named ivar (DW_TAG_property /
+        DW_AT_property_forward) should take the frame-variable fast path,
+        instead of unconditionally falling back to `expression`. Covers the
+        same three synthesis variants as
+        clang/test/DebugInfo/ObjC/property-backing-storage.m."""
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "break here for backing storage", lldb.SBFileSpec("main.m")
+        )
+        self.runCmd("settings set dwim-print-verbosity full")
+
+        self.expect(
+            "dwim-print obj.declaredBacking",
+            substrs=["ran `frame variable obj._customDeclaredIvar`", "(int) 42"],
+        )
+        self.expect(
+            "dwim-print obj.undeclaredBacking",
+            substrs=["ran `frame variable obj._customUndeclaredIvar`", "(int) 7"],
+        )
+        self.expect(
+            "dwim-print obj.implicitBacking",
+            substrs=["ran `frame variable obj._implicitBacking`", "(int) 99"],
+        )
diff --git a/lldb/test/API/commands/dwim-print/objc/main.m b/lldb/test/API/commands/dwim-print/objc/main.m
index ac7efcf0f07abd..2695a77be060ac 100644
--- a/lldb/test/API/commands/dwim-print/objc/main.m
+++ b/lldb/test/API/commands/dwim-print/objc/main.m
@@ -14,11 +14,31 @@ @interface Parent : NSObject
 @implementation Parent
 @end
 
+ at interface C : NSObject {
+  int _customDeclaredIvar;
+}
+ at property int declaredBacking;
+ at property int undeclaredBacking;
+ at property int implicitBacking;
+ at end
+
+ at implementation C
+ at synthesize declaredBacking = _customDeclaredIvar;
+ at synthesize undeclaredBacking = _customUndeclaredIvar;
+// implicitBacking: no @synthesize -> compiler auto-creates _implicitBacking.
+ at end
+
 int main(int argc, char **argv) {
   Child *child = [Child new];
   child.name = @"Seven";
   Parent *parent = [Parent new];
   parent.child = child;
   puts("break here");
+
+  C *obj = [C new];
+  obj.declaredBacking = 42;
+  obj.undeclaredBacking = 7;
+  obj.implicitBacking = 99;
+  puts("break here for backing storage");
   return 0;
 }

>From 17ba9d4862a76fa34b8f4ba57e8f38a1c82ed8a1 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Mon, 21 Sep 2026 23:48:11 +0200
Subject: [PATCH 5/5] Removed unnecessary comments

---
 lldb/include/lldb/Symbol/CompilerType.h                  | 1 -
 lldb/include/lldb/Symbol/TypeSystem.h                    | 6 ------
 lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp | 2 --
 lldb/test/API/commands/dwim-print/objc/main.m            | 1 -
 4 files changed, 10 deletions(-)

diff --git a/lldb/include/lldb/Symbol/CompilerType.h b/lldb/include/lldb/Symbol/CompilerType.h
index ffeacd49ffa69b..da22b35a619bf2 100644
--- a/lldb/include/lldb/Symbol/CompilerType.h
+++ b/lldb/include/lldb/Symbol/CompilerType.h
@@ -485,7 +485,6 @@ class CompilerType {
 
   CompilerType GetDirectNestedTypeWithName(llvm::StringRef name) const;
 
-  /// See TypeSystem::GetPropertyBackingStorageName.
   llvm::StringRef
   GetPropertyBackingStorageName(llvm::StringRef property_name) const;
 
diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h
index 3c141b9fc0dd3d..400bb0726a6274 100644
--- a/lldb/include/lldb/Symbol/TypeSystem.h
+++ b/lldb/include/lldb/Symbol/TypeSystem.h
@@ -403,12 +403,6 @@ class TypeSystem : public PluginInterface,
     return CompilerType();
   }
 
-  /// Returns the name of the member that backs the named property on
-  /// \a type, if this type system knows of one (e.g. an ivar reached via
-  /// DW_AT_property_forward on a DWARF DW_TAG_property).
-  ///
-  /// \return An empty string if \a type has no property named
-  /// \a property_name, or if that property has no known backing storage.
   virtual llvm::StringRef
   GetPropertyBackingStorageName(lldb::opaque_compiler_type_t type,
                                 llvm::StringRef property_name) {
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index 2f4b4a65433763..b27d69c1fb02ad 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -7097,8 +7097,6 @@ llvm::StringRef TypeSystemClang::GetPropertyBackingStorageName(
 
   CompilerType compiler_type(weak_from_this(), type);
 
-  // Callers will usually hand us an object pointer (e.g. `Foo *`); strip it
-  // to get at the interface itself.
   CompilerType class_type;
   if (IsObjCObjectPointerType(compiler_type, &class_type))
     compiler_type = class_type;
diff --git a/lldb/test/API/commands/dwim-print/objc/main.m b/lldb/test/API/commands/dwim-print/objc/main.m
index 2695a77be060ac..45d39d6a952d41 100644
--- a/lldb/test/API/commands/dwim-print/objc/main.m
+++ b/lldb/test/API/commands/dwim-print/objc/main.m
@@ -25,7 +25,6 @@ @interface C : NSObject {
 @implementation C
 @synthesize declaredBacking = _customDeclaredIvar;
 @synthesize undeclaredBacking = _customUndeclaredIvar;
-// implicitBacking: no @synthesize -> compiler auto-creates _implicitBacking.
 @end
 
 int main(int argc, char **argv) {



More information about the lldb-commits mailing list