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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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", _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
