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

Reply via email to