[clang] [llvm] [llvm][DebugInfo] Avoid attaching retained nodes to unrelated subprograms in DIBuilder (PR #180294)
Vladislav Dzhidzhoev via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 9 07:22:58 PST 2026
https://github.com/dzhidzhoev updated https://github.com/llvm/llvm-project/pull/180294
>From e71e943d96559f3b86143e4e4e41c390e2fbc1b1 Mon Sep 17 00:00:00 2001
From: Vladislav Dzhidzhoev <vdzhidzhoev at accesssoftek.com>
Date: Fri, 6 Feb 2026 22:48:10 +0100
Subject: [PATCH 1/3] [llvm][DebugInfo] Avoid attaching retained nodes from
unrelated scopes in DIBuilder
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 tracks local variables, labels, and types in
`DIBuilder::SubprogramTrackedNodes` and later attaches them to their parent
subprogram's retainedNodes in `finalizeSubprogram()`.
However, temporary local types 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 skips nodes
whose scopes no longer match.
---
.../DebugInfo/CXX/ctor-homing-local-type.cpp | 40 ++++++++++++++++
llvm/lib/IR/DIBuilder.cpp | 25 ++++++++--
llvm/unittests/IR/IRBuilderTest.cpp | 47 +++++++++++++++++++
3 files changed, 108 insertions(+), 4 deletions(-)
create mode 100644 clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp
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..83d1c0548bfbb
--- /dev/null
+++ b/clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp
@@ -0,0 +1,40 @@
+// 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).
+
+// FIXME: Should we ensure that DICompositeType is emitted in the same scope in both limited and
+// standalone modes?
+
+// 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..ee9ea84f132db 100644
--- a/llvm/lib/IR/DIBuilder.cpp
+++ b/llvm/lib/IR/DIBuilder.cpp
@@ -53,10 +53,27 @@ 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()) {
+ SmallVector<Metadata *, 16> RetainedNodes(PN->second.begin(),
+ PN->second.end());
+
+ // If the tracked node PN was temporary, and the DIBuilder user replaced it
+ // with a node that does not belong to SP or is non-local, do not add PN to
+ // SP's retainedNodes list.
+ auto IsNodeAlien = [SP](Metadata *M) {
+ MDNode *N = cast<MDNode>(M);
+ DILocalScope *Scope = dyn_cast_or_null<DILocalScope>(
+ DISubprogram::getRawRetainedNodeScope(N));
+ if (!Scope)
+ return true;
+ return Scope->getSubprogram() != SP;
+ };
+ RetainedNodes.erase(
+ std::remove_if(RetainedNodes.begin(), RetainedNodes.end(), IsNodeAlien),
+ RetainedNodes.end());
+
+ 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());
+}
}
>From dd8ce25e71965082c74b745158769846341331c4 Mon Sep 17 00:00:00 2001
From: Vladislav Dzhidzhoev <vdzhidzhoev at accesssoftek.com>
Date: Sat, 7 Feb 2026 00:04:33 +0100
Subject: [PATCH 2/3] Simplify code
---
llvm/lib/IR/DIBuilder.cpp | 32 ++++++++++++++------------------
1 file changed, 14 insertions(+), 18 deletions(-)
diff --git a/llvm/lib/IR/DIBuilder.cpp b/llvm/lib/IR/DIBuilder.cpp
index ee9ea84f132db..38cf3f552b83d 100644
--- a/llvm/lib/IR/DIBuilder.cpp
+++ b/llvm/lib/IR/DIBuilder.cpp
@@ -53,27 +53,23 @@ void DIBuilder::trackIfUnresolved(MDNode *N) {
void DIBuilder::finalizeSubprogram(DISubprogram *SP) {
auto PN = SubprogramTrackedNodes.find(SP);
- if (PN != SubprogramTrackedNodes.end()) {
- SmallVector<Metadata *, 16> RetainedNodes(PN->second.begin(),
- PN->second.end());
+ if (PN == SubprogramTrackedNodes.end())
+ return;
- // If the tracked node PN was temporary, and the DIBuilder user replaced it
- // with a node that does not belong to SP or is non-local, do not add PN to
+ 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.
- auto IsNodeAlien = [SP](Metadata *M) {
- MDNode *N = cast<MDNode>(M);
- DILocalScope *Scope = dyn_cast_or_null<DILocalScope>(
- DISubprogram::getRawRetainedNodeScope(N));
- if (!Scope)
- return true;
- return Scope->getSubprogram() != SP;
- };
- RetainedNodes.erase(
- std::remove_if(RetainedNodes.begin(), RetainedNodes.end(), IsNodeAlien),
- RetainedNodes.end());
-
- SP->replaceRetainedNodes(MDTuple::get(VMContext, RetainedNodes));
+ 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() {
>From 7d5dbd0392571762fd6d04adedb9b89dd2037bd6 Mon Sep 17 00:00:00 2001
From: Vladislav Dzhidzhoev <vdzhidzhoev at accesssoftek.com>
Date: Mon, 9 Feb 2026 16:22:41 +0100
Subject: [PATCH 3/3] Remove FIXME
---
clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp | 3 ---
1 file changed, 3 deletions(-)
diff --git a/clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp b/clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp
index 83d1c0548bfbb..ba9e482c56f30 100644
--- a/clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp
+++ b/clang/test/DebugInfo/CXX/ctor-homing-local-type.cpp
@@ -13,9 +13,6 @@
// 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).
-// FIXME: Should we ensure that DICompositeType is emitted in the same scope in both limited and
-// standalone modes?
-
// 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",
More information about the cfe-commits
mailing list