[llvm] [DWARFTypePrinter] Stop the scope walk at any unit root (PR #219729)

via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 29 14:54:10 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-debuginfo

Author: Arcadiy Ivanov (arcivanov)

<details>
<summary>Changes</summary>

`DWARFTypePrinter::appendScopes` builds a type's qualified name by walking up its parents and prefixing each enclosing scope. It recognizes a unit root by tag, and it enumerates `DW_TAG_compile_unit`, `DW_TAG_type_unit` and `DW_TAG_skeleton_unit`. `DW_TAG_partial_unit`, the fourth unit root tag and the one `dwz` produces, is absent, so the walk does not stop there. It takes the root for an ordinary enclosing scope and emits the root's `DW_AT_name`, a source file path, as the outermost component of the name:

```console
$ llvm-dwarfdump --debug-info pu.o
0x00000029:   DW_TAG_subprogram
                DW_AT_name	("foo")
                DW_AT_type	(0x0000003e "dwz-common.h::Foo")
```

The control, the same input with only the root tag and its matching unit type changed, names it `Foo`. `dwz` is a standard step in Fedora, RHEL and Debian debuginfo packaging, so this is the ordinary shape of a distribution debuginfo file rather than a corner case. It reproduces on stock Fedora `llvm-dwarfdump` 22.1.8 and involves no linking at all.

**Specification.** DWARFv5 §3.1.1 says that for a partial compilation unit "the containing scope of its owned declarations is indicated by imported unit entries in one or more other compilation unit entries that refer to that partial compilation unit". The scope comes from the importers, not from the partial unit, so its root contributes no scope component of its own. §3.1.1 item 2 says the root's `DW_AT_name` is the path of the primary source file, which is not an identifier under any reading, and it is the same attribute for both root tags — which is why nothing downstream can tell the difference once it has been emitted as a scope. §3.2.2 settles what the name should be instead: the C++ global namespace has no DWARF entry at all, so a type directly under a unit root has no scope component above it.

**The fix.** `dwarf::isUnitType()` already answers exactly this question for all four unit root tags, so it replaces the three comparisons. The `DW_TAG_subprogram` and `DW_TAG_lexical_block` stops below it are unrelated and stay as they are. Both instantiations of the printer pass a `dwarf::Tag` — LLDB's `dw_tag_t` is a typedef for it — so neither picks up the `uint8_t` overload that compares tag numbers against the `DW_UT_*` codes.

**Tests.** A new `DWARFDie.DWARFTypePrinterUnitRootTest` in `DWARFDieTest.cpp`, alongside the existing printer tests, builds one index-free object holding two units that differ only in the root tag and the unit type. Each carries a structure at unit scope and a structure inside a namespace, so the partial unit case is pinned against a compile unit control and against a scope that must still be printed: a fix that stopped the walk unconditionally would name the second one `Foo` rather than `ns::Foo`.

The printer has two other consumers that share this root cause and are not covered here. `makeSimpleTemplateNameWithParams` (`Classic/DWARFLinkerDeclContext.cpp`) puts the qualified name of every template type parameter into the classic DWARFLinker's ODR key, so an instantiation whose argument lives under a partial unit root does not unique against the same instantiation from another object; LLDB computes simple template names the same way in `DWARFASTParserClang.cpp`. Both follow from the same walk and neither has a reproducer here.

Fixes #<!-- -->219635.


---
Full diff: https://github.com/llvm/llvm-project/pull/219729.diff


2 Files Affected:

- (modified) llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h (+4-5) 
- (modified) llvm/unittests/DebugInfo/DWARF/DWARFDieTest.cpp (+130) 


``````````diff
diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
index 6e1b8de48c913..0ea1ba44c47a4 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
@@ -836,11 +836,10 @@ void DWARFTypePrinter<DieType>::appendSubroutineNameAfter(
 
 template <typename DieType>
 void DWARFTypePrinter<DieType>::appendScopes(DieType D) {
-  if (D.getTag() == dwarf::DW_TAG_compile_unit)
-    return;
-  if (D.getTag() == dwarf::DW_TAG_type_unit)
-    return;
-  if (D.getTag() == dwarf::DW_TAG_skeleton_unit)
+  // A unit root is not an enclosing scope: its DW_AT_name is the path of a
+  // source file rather than an identifier. isUnitType covers all four unit root
+  // tags, including DW_TAG_partial_unit, which dwz emits.
+  if (dwarf::isUnitType(D.getTag()))
     return;
   if (D.getTag() == dwarf::DW_TAG_subprogram)
     return;
diff --git a/llvm/unittests/DebugInfo/DWARF/DWARFDieTest.cpp b/llvm/unittests/DebugInfo/DWARF/DWARFDieTest.cpp
index f6e4073e8607a..9f91790d658b5 100644
--- a/llvm/unittests/DebugInfo/DWARF/DWARFDieTest.cpp
+++ b/llvm/unittests/DebugInfo/DWARF/DWARFDieTest.cpp
@@ -840,6 +840,136 @@ TEST(DWARFDie, DWARFTypePrinterTest) {
   testAppendQualifiedName(Ctx->getDIEForOffset(0x28), "t3<int>::my_int");
 }
 
+void testUnitRootIsNotAScope(DWARFUnit *U) {
+  ASSERT_NE(nullptr, U);
+  DWARFDie Root = U->getUnitDIE(/*ExtractUnitDIEOnly=*/false);
+  ASSERT_TRUE(Root.isValid());
+
+  DWARFDie Bar = Root.getFirstChild();
+  ASSERT_TRUE(Bar.isValid());
+  DWARFDie Namespace = Bar.getSibling();
+  ASSERT_TRUE(Namespace.isValid());
+  DWARFDie Foo = Namespace.getFirstChild();
+  ASSERT_TRUE(Foo.isValid());
+
+  testAppendQualifiedName(Bar, "Bar");
+  testAppendQualifiedName(Foo, "ns::Foo");
+}
+
+TEST(DWARFDie, DWARFTypePrinterUnitRootTest) {
+  // A unit root is not an enclosing scope, whatever tag it carries: its
+  // DW_AT_name is the path of a source file rather than an identifier. The two
+  // units below hold the same two declarations, a structure at unit scope and
+  // a structure inside a namespace, and differ only in the root tag and the
+  // matching unit type.
+
+  // 0x0000000c: DW_TAG_partial_unit
+  //               DW_AT_name      ("dwz-common.h")
+  // 0x0000001a:   DW_TAG_structure_type
+  //                 DW_AT_name    ("Bar")
+  // 0x0000001f:   DW_TAG_namespace
+  //                 DW_AT_name    ("ns")
+  // 0x00000023:     DW_TAG_structure_type
+  //                   DW_AT_name  ("Foo")
+  // 0x00000028:     NULL
+  // 0x00000029:   NULL
+  // 0x00000036: DW_TAG_compile_unit
+  //               DW_AT_name      ("main.cpp")
+  // 0x00000040:   DW_TAG_structure_type
+  //                 DW_AT_name    ("Bar")
+  // 0x00000045:   DW_TAG_namespace
+  //                 DW_AT_name    ("ns")
+  // 0x00000049:     DW_TAG_structure_type
+  //                   DW_AT_name  ("Foo")
+  // 0x0000004e:     NULL
+  // 0x0000004f:   NULL
+  const char *yamldata = R"(
+  debug_abbrev:
+    - ID:              0
+      Table:
+        - Code:            0x1
+          Tag:             DW_TAG_partial_unit
+          Children:        DW_CHILDREN_yes
+          Attributes:
+            - Attribute:       DW_AT_name
+              Form:            DW_FORM_string
+        - Code:            0x2
+          Tag:             DW_TAG_compile_unit
+          Children:        DW_CHILDREN_yes
+          Attributes:
+            - Attribute:       DW_AT_name
+              Form:            DW_FORM_string
+        - Code:            0x3
+          Tag:             DW_TAG_namespace
+          Children:        DW_CHILDREN_yes
+          Attributes:
+            - Attribute:       DW_AT_name
+              Form:            DW_FORM_string
+        - Code:            0x4
+          Tag:             DW_TAG_structure_type
+          Children:        DW_CHILDREN_no
+          Attributes:
+            - Attribute:       DW_AT_name
+              Form:            DW_FORM_string
+  debug_info:
+    - Version:         5
+      UnitType:        DW_UT_partial
+      AbbrevTableID:   0
+      Entries:
+        - AbbrCode:        0x1
+          Values:
+            - CStr:            dwz-common.h
+        - AbbrCode:        0x4
+          Values:
+            - CStr:            Bar
+        - AbbrCode:        0x3
+          Values:
+            - CStr:            ns
+        - AbbrCode:        0x4
+          Values:
+            - CStr:            Foo
+        - AbbrCode:        0x0
+        - AbbrCode:        0x0
+    - Version:         5
+      UnitType:        DW_UT_compile
+      AbbrevTableID:   0
+      Entries:
+        - AbbrCode:        0x2
+          Values:
+            - CStr:            main.cpp
+        - AbbrCode:        0x4
+          Values:
+            - CStr:            Bar
+        - AbbrCode:        0x3
+          Values:
+            - CStr:            ns
+        - AbbrCode:        0x4
+          Values:
+            - CStr:            Foo
+        - AbbrCode:        0x0
+        - AbbrCode:        0x0)";
+  Expected<StringMap<std::unique_ptr<MemoryBuffer>>> Sections =
+      DWARFYAML::emitDebugSections(StringRef(yamldata),
+                                   /*IsLittleEndian=*/true,
+                                   /*Is64BitAddrSize=*/true);
+  ASSERT_THAT_EXPECTED(Sections, Succeeded());
+  std::unique_ptr<DWARFContext> Ctx =
+      DWARFContext::create(*Sections, 4, /*isLittleEndian=*/true);
+  ASSERT_EQ(2u, Ctx->getNumCompileUnits());
+
+  DWARFUnit *PartialUnit = Ctx->getUnitAtIndex(0);
+  ASSERT_NE(nullptr, PartialUnit);
+  EXPECT_EQ(dwarf::DW_TAG_partial_unit,
+            PartialUnit->getUnitDIE(/*ExtractUnitDIEOnly=*/false).getTag());
+  testUnitRootIsNotAScope(PartialUnit);
+
+  DWARFUnit *CompileUnit = Ctx->getUnitAtIndex(1);
+  ASSERT_NE(nullptr, CompileUnit);
+  EXPECT_EQ(dwarf::DW_TAG_compile_unit,
+            CompileUnit->getUnitDIE(/*ExtractUnitDIEOnly=*/false).getTag());
+  testUnitRootIsNotAScope(CompileUnit);
+}
+
 TEST(DWARFDie, getLanguage) {
   const char *yamldata = R"(
     debug_abbrev:

``````````

</details>


https://github.com/llvm/llvm-project/pull/219729


More information about the llvm-commits mailing list