[clang] [lldb] [lldb] Support objc_runtime_name annotations in DWARF (PR #233648)

via cfe-commits cfe-commits at lists.llvm.org
Mon Oct 19 17:54:51 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-codegen

Author: Adrian Prantl (adrian-prantl)

<details>
<summary>Changes</summary>

An Objective-C class with a custom objc_runtime_name exports its
class, metaclass, and ivar offset symbols under the runtime name.
LLDB reconstructs the interface from DWARF with only the source name,
so expressions that reference such a class fail to find these
symbols.

This patch reads the DW_TAG_LLVM_annotation that clang now emits for
the runtime name and attaches an ObjCRuntimeNameAttr to the
reconstructed ObjCInterfaceDecl.

rdar://190472538
Assisted-by: Claude

---
Full diff: https://github.com/llvm/llvm-project/pull/233648.diff


6 Files Affected:

- (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+13-1) 
- (added) clang/test/DebugInfo/ObjC/objc-runtime-name.m (+52) 
- (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp (+20) 
- (added) lldb/test/API/lang/objc/objc-runtime-name/Makefile (+4) 
- (added) lldb/test/API/lang/objc/objc-runtime-name/TestObjCRuntimeName.py (+23) 
- (added) lldb/test/API/lang/objc/objc-runtime-name/main.m (+30) 


``````````diff
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 93428777b9b24..487b7ad3192d0 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -3693,10 +3693,22 @@ llvm::DIType *CGDebugInfo::CreateTypeDefinition(const ObjCInterfaceType *Ty,
   if (ID->getImplementation())
     Flags |= llvm::DINode::FlagObjcClassComplete;
 
+  llvm::DINodeArray Annotations;
+  const auto *RuntimeName = ID->getAttr<ObjCRuntimeNameAttr>();
+  if (RuntimeName && RuntimeName->getMetadataName() != ID->getName()) {
+    llvm::LLVMContext &Ctx = CGM.getLLVMContext();
+    llvm::Metadata *Ops[2] = {
+        llvm::MDString::get(Ctx, "objc_runtime_name"),
+        llvm::MDString::get(Ctx, RuntimeName->getMetadataName())};
+    Annotations = DBuilder.getOrCreateArray({llvm::MDNode::get(Ctx, Ops)});
+  }
+
   llvm::DIScope *Mod = getParentModuleOrNull(ID);
   llvm::DICompositeType *RealDecl = DBuilder.createStructType(
       Mod ? Mod : Unit, ID->getName(), DefUnit, Line, Size, Align, Flags,
-      nullptr, llvm::DINodeArray(), RuntimeLang);
+      nullptr, llvm::DINodeArray(), RuntimeLang, /*VTableHolder=*/nullptr,
+      /*UniqueIdentifier=*/"", /*Specification=*/nullptr,
+      /*NumExtraInhabitants=*/0, Annotations);
 
   QualType QTy(Ty, 0);
   TypeCache[QTy.getAsOpaquePtr()].reset(RealDecl);
diff --git a/clang/test/DebugInfo/ObjC/objc-runtime-name.m b/clang/test/DebugInfo/ObjC/objc-runtime-name.m
new file mode 100644
index 0000000000000..1102969e75799
--- /dev/null
+++ b/clang/test/DebugInfo/ObjC/objc-runtime-name.m
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 -emit-llvm -debug-info-kind=limited %s -o %t.ll
+// RUN: FileCheck %s < %t.ll
+// RUN: FileCheck %s --check-prefix=ROOT < %t.ll
+// RUN: FileCheck %s --check-prefix=SAME < %t.ll
+
+// The Objective-C runtime name of a class is recorded in the debug info when
+// it differs from the source name.
+
+__attribute__((objc_root_class))
+ at interface Root
+ at end
+
+ at implementation Root
+ at end
+
+__attribute__((objc_runtime_name("_$s4main7DefinedC")))
+ at interface Defined : Root
+ at end
+
+ at implementation Defined
+ at end
+
+// A class implemented in another TU, such as a Swift class declared in a
+// generated header, is completed at the end of the TU and must carry the
+// runtime name as well.
+__attribute__((objc_runtime_name("_$s4main8DeclaredC")))
+ at interface Declared : Root
+ at end
+
+Declared *d;
+
+__attribute__((objc_runtime_name("Same")))
+ at interface Same : Root
+ at end
+
+ at implementation Same
+ at end
+
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "Defined",{{.*}}annotations: ![[DEFINED:[0-9]+]]
+// CHECK-DAG: ![[DEFINED]] = !{![[DEFINED_NAME:[0-9]+]]}
+// CHECK-DAG: ![[DEFINED_NAME]] = !{!"objc_runtime_name", !"_$s4main7DefinedC"}
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "Declared",{{.*}}annotations: ![[DECLARED:[0-9]+]]
+// CHECK-DAG: ![[DECLARED]] = !{![[DECLARED_NAME:[0-9]+]]}
+// CHECK-DAG: ![[DECLARED_NAME]] = !{!"objc_runtime_name", !"_$s4main8DeclaredC"}
+
+// ROOT: !DICompositeType(tag: DW_TAG_structure_type, name: "Root",
+// ROOT-NOT: annotations:
+// ROOT-SAME: {{$}}
+
+// SAME: !DICompositeType(tag: DW_TAG_structure_type, name: "Same",
+// SAME-NOT: annotations:
+// SAME-SAME: {{$}}
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index b60f1d9e41958..695fe4a1eec83 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -1748,6 +1748,25 @@ void DWARFASTParserClang::GetUniqueTypeNameAndDeclaration(
   unique_typename = ConstString(qualified_name);
 }
 
+/// The ASTImporter copies attributes only when a decl is first imported, so
+/// this must run before the decl is imported into an expression context.
+static void ApplyObjCRuntimeName(const DWARFDIE &die, CompilerType type) {
+  clang::ObjCInterfaceDecl *decl =
+      TypeSystemClang::GetAsObjCInterfaceDecl(type);
+  if (!decl)
+    return;
+  for (DWARFDIE child : die.children()) {
+    if (child.Tag() != DW_TAG_LLVM_annotation ||
+        llvm::StringRef(child.GetName()) != "objc_runtime_name")
+      continue;
+    if (const char *runtime_name =
+            child.GetAttributeValueAsString(DW_AT_const_value, nullptr))
+      decl->addAttr(clang::ObjCRuntimeNameAttr::CreateImplicit(
+          decl->getASTContext(), runtime_name));
+    return;
+  }
+}
+
 TypeSP DWARFASTParserClang::ParseStructureLikeDIE(
     const SymbolContext &sc, const DWARFDIE &die,
     const ParsedDWARFTypeAttributes &attrs) {
@@ -1905,6 +1924,7 @@ TypeSP DWARFASTParserClang::ParseStructureLikeDIE(
     clang_type = m_ast.CreateRecordType(
         containing_decl_ctx, GetOwningClangModule(die), attrs.name.GetCString(),
         tag_decl_kind, attrs.class_language, metadata, attrs.exports_symbols);
+    ApplyObjCRuntimeName(die, clang_type);
   }
 
   TypeSP type_sp = dwarf->MakeType(
diff --git a/lldb/test/API/lang/objc/objc-runtime-name/Makefile b/lldb/test/API/lang/objc/objc-runtime-name/Makefile
new file mode 100644
index 0000000000000..66119b1b48248
--- /dev/null
+++ b/lldb/test/API/lang/objc/objc-runtime-name/Makefile
@@ -0,0 +1,4 @@
+OBJC_SOURCES := main.m
+LD_EXTRAS := -framework Foundation
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/objc/objc-runtime-name/TestObjCRuntimeName.py b/lldb/test/API/lang/objc/objc-runtime-name/TestObjCRuntimeName.py
new file mode 100644
index 0000000000000..1bb7d0c5994ec
--- /dev/null
+++ b/lldb/test/API/lang/objc/objc-runtime-name/TestObjCRuntimeName.py
@@ -0,0 +1,23 @@
+"""
+Test expressions involving a class whose Objective-C runtime name differs from
+its source name, as is the case for Swift classes exposed to Objective-C.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestObjCRuntimeName(TestBase):
+    def test(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.m"))
+
+        # Expressions in a method are wrapped in a category on the class of
+        # self, which references the class symbol.
+        self.expect_expr("self.value", result_type="int", result_value="23")
+        # Ivar access goes through the ivar offset symbol.
+        self.expect_expr("self->_value", result_type="int", result_value="23")
+        # Class message sends go through the class symbol.
+        self.expect_expr("[Renamed classValue]", result_type="int", result_value="42")
diff --git a/lldb/test/API/lang/objc/objc-runtime-name/main.m b/lldb/test/API/lang/objc/objc-runtime-name/main.m
new file mode 100644
index 0000000000000..1ab453b0c5e9f
--- /dev/null
+++ b/lldb/test/API/lang/objc/objc-runtime-name/main.m
@@ -0,0 +1,30 @@
+#import <Foundation/Foundation.h>
+
+// This is how a Swift class is declared in a generated Objective-C header.
+__attribute__((objc_runtime_name("_$s4main7RenamedC")))
+ at interface Renamed : NSObject
+ at property int value;
++ (int)classValue;
+ at end
+
+ at implementation Renamed
++ (int)classValue {
+  return 42;
+}
+ at end
+
+ at interface Renamed (Category)
+- (int)stop;
+ at end
+
+ at implementation Renamed (Category)
+- (int)stop {
+  return self.value; // break here
+}
+ at end
+
+int main() {
+  Renamed *r = [[Renamed alloc] init];
+  r.value = 23;
+  return [r stop] == 23 ? 0 : 1;
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/233648


More information about the cfe-commits mailing list