[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