[clang] 9069164 - [llvm][DebugInfo] Avoid attaching retained nodes to unrelated subprograms in DIBuilder (#180294)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 9 07:23:48 PST 2026
Author: Vladislav Dzhidzhoev
Date: 2026-02-09T16:23:42+01:00
New Revision: 90691641e6419c285671f183628a0e16792df43c
URL: https://github.com/llvm/llvm-project/commit/90691641e6419c285671f183628a0e16792df43c
DIFF: https://github.com/llvm/llvm-project/commit/90691641e6419c285671f183628a0e16792df43c.diff
LOG: [llvm][DebugInfo] Avoid attaching retained nodes to unrelated subprograms in DIBuilder (#180294)
Fix a regression introduced by
https://github.com/llvm/llvm-project/pull/165032, where DIBuilder could
attach local metadata nodes to the wrong subprogram during finalization.
DIBuilder records freshly created local variables, labels, and types in
`DIBuilder::SubprogramTrackedNodes`, and later attaches them to their
parent subprogram's retainedNodes in `finalizeSubprogram()`.
However, a temporary local type created via
`createReplaceableCompositeType()` may later be replaced by a type with
a different scope.
DIBuilder does not currently verify that the scopes of the original and
replacement types match.
As a result, local types can be incorrectly attached to the
retainedNodes of an unrelated subprogram. This issue is observable in
clang with limited debug info mode (see
`clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp`).
This patch updates `DIBuilder::finalizeSubprogram()` to verify that
tracked metadata nodes still belong to the subprogram being finalized,
and avoids adding nodes whose scopes no longer match to retainedNodes
field of an unrelated subprogram.
Added:
clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp
Modified:
llvm/lib/IR/DIBuilder.cpp
llvm/unittests/IR/IRBuilderTest.cpp
Removed:
################################################################################
diff --git a/clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp b/clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp
new file mode 100644
index 0000000000000..ba9e482c56f30
--- /dev/null
+++ b/clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm -debug-info-kind=limited -dwarf-version=5 -O0 -disable-llvm-passes %s -o - \
+// RUN: | FileCheck %s
+
+// When compiling this with limited debug info, a replaceable forward declaration DICompositeType
+// for "n" is created in the scope of base object constructor (C2) of class l, and it's not immediately
+// replaced with a distinct node (the type is not "completed").
+// Later, it gets replaced with distinct definition DICompositeType, which is created in the scope of
+// complete object constructor (C1).
+//
+// In contrast to that, in standalone debug info mode, the complete definition DICompositeType
+// for "n" is created sooner, right in the context of C2.
+//
+// Check that DIBuilder processes the limited debug info case correctly, and doesn't add the same
+// local type to retainedNodes fields of both DISubprograms (C1 and C2).
+
+// CHECK: ![[C2:[0-9]+]] = distinct !DISubprogram(name: "l", linkageName: "_ZN1lC2Ev", {{.*}}, retainedNodes: ![[EMPTY:[0-9]+]])
+// CHECK: ![[EMPTY]] = !{}
+// CHECK: ![[N:[0-9]+]] = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "n",
+// CHECK: ![[C1:[0-9]+]] = distinct !DISubprogram(name: "l", linkageName: "_ZN1lC1Ev", {{.*}}, retainedNodes: ![[RN:[0-9]+]])
+// CHECK: ![[RN]] = !{![[N]]}
+
+template <class d>
+struct k {
+ void i() {
+ new d;
+ }
+};
+
+struct l {
+ l();
+};
+
+l::l() {
+ struct n {};
+ k<n> m;
+ m.i();
+}
diff --git a/llvm/lib/IR/DIBuilder.cpp b/llvm/lib/IR/DIBuilder.cpp
index ec6fd8e8b895a..38cf3f552b83d 100644
--- a/llvm/lib/IR/DIBuilder.cpp
+++ b/llvm/lib/IR/DIBuilder.cpp
@@ -53,10 +53,23 @@ void DIBuilder::trackIfUnresolved(MDNode *N) {
void DIBuilder::finalizeSubprogram(DISubprogram *SP) {
auto PN = SubprogramTrackedNodes.find(SP);
- if (PN != SubprogramTrackedNodes.end())
- SP->replaceRetainedNodes(
- MDTuple::get(VMContext, SmallVector<Metadata *, 16>(PN->second.begin(),
- PN->second.end())));
+ if (PN == SubprogramTrackedNodes.end())
+ return;
+
+ SmallVector<Metadata *, 16> RetainedNodes;
+ for (MDNode *N : PN->second) {
+ // If the tracked node N was temporary, and the DIBuilder user replaced it
+ // with a node that does not belong to SP or is non-local, do not add N to
+ // SP's retainedNodes list.
+ DILocalScope *Scope = dyn_cast_or_null<DILocalScope>(
+ DISubprogram::getRawRetainedNodeScope(N));
+ if (!Scope || Scope->getSubprogram() != SP)
+ continue;
+
+ RetainedNodes.push_back(N);
+ }
+
+ SP->replaceRetainedNodes(MDTuple::get(VMContext, RetainedNodes));
}
void DIBuilder::finalize() {
diff --git a/llvm/unittests/IR/IRBuilderTest.cpp b/llvm/unittests/IR/IRBuilderTest.cpp
index 2d21e6f8b7148..4361594050668 100644
--- a/llvm/unittests/IR/IRBuilderTest.cpp
+++ b/llvm/unittests/IR/IRBuilderTest.cpp
@@ -1368,4 +1368,51 @@ TEST_F(IRBuilderTest, CTAD) {
IRBuilder Builder7(BB, BB->end());
static_assert(std::is_same_v<decltype(Builder7), IRBuilder<>>);
}
+
+TEST_F(IRBuilderTest, finalizeSubprogram) {
+ IRBuilder<> Builder(BB);
+ DIBuilder DIB(*M);
+ auto File = DIB.createFile("main.c", "/");
+ auto CU = DIB.createCompileUnit(
+ DISourceLanguageName(dwarf::DW_LANG_C_plus_plus), File, "clang",
+ /*isOptimized=*/true, /*Flags=*/"",
+ /*Runtime Version=*/0);
+ auto FuncType = DIB.createSubroutineType(DIB.getOrCreateTypeArray({}));
+ auto FooSP = DIB.createFunction(
+ CU, "foo", /*LinkageName=*/"", File,
+ /*LineNo=*/1, FuncType, /*ScopeLine=*/2, DINode::FlagZero,
+ DISubprogram::SPFlagDefinition | DISubprogram::SPFlagOptimized);
+
+ F->setSubprogram(FooSP);
+ AllocaInst *I = Builder.CreateAlloca(Builder.getInt8Ty());
+ ReturnInst *R = Builder.CreateRetVoid();
+ I->setDebugLoc(DILocation::get(Ctx, 3, 2, FooSP));
+ R->setDebugLoc(DILocation::get(Ctx, 4, 2, FooSP));
+
+ auto BarSP = DIB.createFunction(
+ CU, "bar", /*LinkageName=*/"", File,
+ /*LineNo=*/1, FuncType, /*ScopeLine=*/2, DINode::FlagZero,
+ DISubprogram::SPFlagDefinition | DISubprogram::SPFlagOptimized);
+
+ // Create a temporary structure in scope of FooSP.
+ llvm::TempDIType ForwardDeclaredType =
+ llvm::TempDIType(DIB.createReplaceableCompositeType(
+ llvm::dwarf::DW_TAG_structure_type, "MyType", FooSP, File, 0, 0, 8, 8,
+ {}, "UniqueIdentifier"));
+
+ // Instantiate the real structure in scope of BarSP.
+ DICompositeType *Type = DIB.createStructType(
+ BarSP, "MyType", File, 0, 8, 8, {}, {}, {}, 0, {}, "UniqueIdentifier");
+ // Replace the temporary type with the real type.
+ DIB.replaceTemporary(std::move(ForwardDeclaredType), Type);
+
+ DIB.finalize();
+ EXPECT_FALSE(verifyModule(*M));
+
+ // After finalization, MyType should appear in retainedNodes of BarSP,
+ // not in FooSP's.
+ EXPECT_EQ(BarSP->getRetainedNodes().size(), 1u);
+ EXPECT_EQ(BarSP->getRetainedNodes()[0], Type);
+ EXPECT_TRUE(FooSP->getRetainedNodes().empty());
+}
}
More information about the cfe-commits
mailing list