[clang] [lldb] [lldb] Support objc_runtime_name annotations in DWARF (PR #233648)
Adrian Prantl via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 19 17:54:14 PDT 2026
https://github.com/adrian-prantl created https://github.com/llvm/llvm-project/pull/233648
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
>From 3bbca897985ea26c5a8a20e8238fda01aa1381d7 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Mon, 19 Oct 2026 17:32:46 -0700
Subject: [PATCH 1/2] [clang][DebugInfo] Record objc_runtime_name of ObjC
interfaces
An Objective-C class with a custom objc_runtime_name exports its
class, metaclass, and ivar offset symbols under the runtime name, but
the debug info only records the source name.
This causes problems in LLDB's expression evaluator since it will use
the name recorded in the debug info for any ObjC metadata symbols.
This patch uses DW_TAG_LLVM_annotation to record the runtime name in
the interface.
rdar://187396633
Assisted-by: Claude
---
clang/lib/CodeGen/CGDebugInfo.cpp | 14 ++++-
clang/test/DebugInfo/ObjC/objc-runtime-name.m | 52 +++++++++++++++++++
2 files changed, 65 insertions(+), 1 deletion(-)
create mode 100644 clang/test/DebugInfo/ObjC/objc-runtime-name.m
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: {{$}}
>From f4a1196210d0b7ddbe6d5b5443da6852ebce508d Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Mon, 19 Oct 2026 17:51:55 -0700
Subject: [PATCH 2/2] [lldb] Support objc_runtime_name annotations in DWARF
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
---
.../SymbolFile/DWARF/DWARFASTParserClang.cpp | 20 +++++++++++++
.../API/lang/objc/objc-runtime-name/Makefile | 4 +++
.../objc-runtime-name/TestObjCRuntimeName.py | 23 ++++++++++++++
.../API/lang/objc/objc-runtime-name/main.m | 30 +++++++++++++++++++
4 files changed, 77 insertions(+)
create mode 100644 lldb/test/API/lang/objc/objc-runtime-name/Makefile
create mode 100644 lldb/test/API/lang/objc/objc-runtime-name/TestObjCRuntimeName.py
create mode 100644 lldb/test/API/lang/objc/objc-runtime-name/main.m
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;
+}
More information about the cfe-commits
mailing list