[llvm][DebugInfo] Avoid attaching retained nodes to unrelated subprograms in DIBuilder#180294
Conversation
…s in DIBuilder Fix a regression introduced by llvm#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.
|
@llvm/pr-subscribers-debuginfo @llvm/pr-subscribers-llvm-ir Author: Vladislav Dzhidzhoev (dzhidzhoev) ChangesFix a regression introduced by #165032, where DIBuilder could attach local metadata nodes to the wrong subprogram during finalization. DIBuilder tracks local variables, labels, and types in However, temporary local types created via 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 This patch updates 3 Files Affected:
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());
+}
}
|
jmorse
left a comment
There was a problem hiding this comment.
LGTM; this feels like the kind of assumption/situation that we'd expect to be flushed out by fixing locally-scoped types. Great!
| // FIXME: Should we ensure that DICompositeType is emitted in the same scope in both limited and | ||
| // standalone modes? |
There was a problem hiding this comment.
It does feel wrong that there's a difference; I think in the grand scheme of things though, this is an acceptable variation as part of the greater aim of fixing locally-scoped types. Presumably if this created difficulties for consumers, it would present itself when someone consumed compilation-units that were compiled with different flags?
There was a problem hiding this comment.
I've noticed this quirk when building clang on clang in limited debug info mode on my own (and got a crash bc of DIBuilder attaching a type to two subprograms).
I haven't received feedback from consumers about the mismatch in where (in which constructor version) local types are emitted.
I will remove this FIXME for now, check a bit later how this affects debugging experience, and create an issue for that if there is something (not sure how much time I'll have this week as I'm out of office).
|
Thank you! |
Fix a regression introduced by #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 infinalizeSubprogram().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.