lldb: GCC C++20 std::tuple backing std::unique_ptr fails to parse (DW_TAG_member '_M_t' ... unable to be parsed)
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
_DISCLAIMER: I have next to none experience with compiler intrinsics, which is why this bug report is heavily AI assisted. It was created in good faith to address an actual reproduced (by me) regression bug in the 22.x release._
# lldb: GCC C++20 `std::tuple` backing `std::unique_ptr` fails to parse (`DW_TAG_member '_M_t' ... unable to be parsed`)
## LLVM Component
`lldb` - DWARF symbol file / Clang TypeSystem
(`DWARFASTParserClang::ParseStructureLikeDIE`,
`TypeSystemClang::CreateClassTemplateSpecializationDecl`), surfaced through the
libstdc++ `std::unique_ptr` data formatter.
## Summary
When lldb (>= 22) debugs a binary compiled by **GCC** with **`-std=c++20`** (or
later), it fails to parse the `std::tuple>` type that
backs `std::unique_ptr`. `frame variable` (and any Variables-view refresh)
prints:
```
error: mytest 0x00007c94: DW_TAG_member '_M_t' refers to type 0x000000000000815a which was unable to be parsed
```
Until recently this parse failure also caused a **SIGSEGV** in the libstdc++
`unique_ptr` synthetic child provider (it dereferenced the null pointer child
produced by the failed parse). That crash was fixed defensively in
[#175737](https://github.com/llvm/llvm-project/pull/175737) (the formatter now
bails out with `eRefetch` when the pointer child is missing).
**The underlying root cause is still present:** the tuple type still fails to
parse, so `std::unique_ptr` members in GCC/C++20 binaries cannot be displayed
(they show the "unable to be parsed" error instead of the pointer value). Since
`std::unique_ptr` is ubiquitous, this makes lldb 22 substantially less usable on
GCC/C++20 binaries even after the crash fix.
- **Regression** vs lldb 21.x (same binary parses/prints fine under 21.1.7).
- **Clang**-compiled binaries are **not** affected (Clang binds the tuple's
template parameters via `DW_TAG_template_type_parameter`; see Compiler
Explorer link).
### Root cause
`std::unique_ptr` stores `std::tuple> _M_t;`.
GCC emits **multiple copies** of that `std::tuple<...>` instantiation whose only
template child is an **empty** `DW_TAG_GNU_template_parameter_pack` - there is
**no** `DW_TAG_template_type_parameter` binding the pack:
```
0x815a: DW_TAG_class_type "tuple>"
0x8168: DW_TAG_inheritance -> std::_Tuple_impl<0, std::string*, std::default_delete>
0x816e: DW_TAG_subprogram "tuple" linkage=_ZNSt5tuple...C4EvQfraa26is_default_constructible_vIT_E
... (more constrained ctors / operator= / swap declarations) ...
0x8259: DW_TAG_GNU_template_parameter_pack <-- EMPTY, no children
<-- NOTE: no DW_TAG_template_type_parameter for the pack
```
Because the pack is empty, every copy of this tuple collapses to the **same
(empty) template-argument list**. When lldb parses the second copy:
1. `DWARFASTParserClang::ParseStructureLikeDIE` reuses the existing
`ClassTemplateDecl` and calls
`TypeSystemClang::CreateClassTemplateSpecializationDecl(...)`.
2. That helper was changed by commit
[`4dfe212dade7`](https://github.com/llvm/llvm-project/commit/4dfe212dade7)
("[lldb][DWARFASTParserClang] Added a check for the specialization existence",
[#154123](https://github.com/llvm/llvm-project/pull/154123)) to return
`nullptr` when `class_template_decl->findSpecialization(args, insert_pos)`
finds an already-existing specialization with identical args.
3. `ParseStructureLikeDIE` treats that `nullptr` as a hard failure and does
`return TypeSP()`, leaving the `_M_t` member with a **null type** - hence
"unable to be parsed".
#154123 was intended to break an infinite-recursion / self-inheritance case
caused by malformed DWARF with lost `DW_TAG_template_value_parameter`. But the
same `nullptr` path now also rejects GCC's duplicate tuple copies (whose reduced
form drops the template-parameter binding - arguably a separate GCC debug-info
bug; see "A note on the GCC DWARF" below), breaking a very common type.
Regardless of whether GCC should emit that form, lldb must not fail to parse an
otherwise-usable type - lldb 21.x consumed these same DIEs without error.
Note: the presence of constrained `Q` ctors alone is **not** a reliable
discriminator between affected and unaffected builds - Godbolt's Clang using
libstdc++ also emits the `...Q...` constrained ctors. The decisive difference is
the **empty vs. populated `DW_TAG_GNU_template_parameter_pack`** (see the
Compiler Explorer link).
### A note on the GCC DWARF (is this a GCC bug?)
Two distinct things are happening, and they warrant different verdicts:
- **The duplication across CUs is legitimate.** Debug info is emitted per
translation unit, so multiple `DW_TAG_class_type` copies of the same
`std::tuple<...>` are expected and ODR-consistent; consumers are responsible
for uniquing them (lldb has `UniqueDWARFASTTypeMap`). This is not a GCC bug.
- **The empty `DW_TAG_GNU_template_parameter_pack` on a full definition is
arguably a GCC debug-info bug.** The reduced tuple DIE is a *definition*
(`DW_AT_byte_size`, base class, member declarations; no `DW_AT_declaration`),
yet its only template child is an empty pack. GCC clearly can and normally
does emit the populated form: in the reproducer object there are 228
`DW_TAG_template_type_parameter` DIEs and 32 populated packs, versus only 3
empty packs (the constrained `std::tuple` copies). It is also internally
inconsistent - the constrained member linkage names reference template
parameter `T` (`..._C4EvQ...IT_E`) while the `DW_TAG_template_type_parameter`
that would define `T` is omitted. This is worth reporting to GCC separately.
Strictly, DWARF does not mandate `DW_TAG_template_type_parameter` DIEs and
`DW_TAG_GNU_template_parameter_pack` is a GNU extension, so the empty pack is
"valid but lossy" rather than a hard standard violation. Either way, this issue
is about the lldb-side regression: a debugger must not fail on incomplete but
parseable debug info, and lldb 21.x did not.
### Suggested fix direction
In `ParseStructureLikeDIE`, when `CreateClassTemplateSpecializationDecl` returns
`nullptr` (a specialization with identical args already exists), fall through to
building a plain, non-template record type instead of failing the whole type.
This produces a valid type for the duplicate copy (so `_M_t` and the
`unique_ptr` formatter work), creates a distinct clang record (so it does not
re-introduce the self-inheritance recursion that #154123 guarded against), and
avoids the forward-declaration-map assertion. A unit test covering both the
GCC duplicate-tuple case and the original #154123 self-inheritance case should
accompany the fix.
## Reproducer Code
`mytest.cpp` (an 11-line googletest test; `std::unique_ptr` reaches
the stack frame through `testing::AssertionResult::message_`):
```cpp
#include
TEST(Repro, body) {
int local = 42;
EXPECT_EQ(42, local); // line 4: BREAKPOINT HERE
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
```
Build with GCC and debug with lldb:
```bash
GT=/path/to/googletest-1.15.2
g++ -std=c++20 -g -O0 -I"$GT/googletest/include" -I"$GT/googletest" \
-c "$GT/googletest/src/gtest-all.cc" -o gtest-all.o
g++ -std=c++20 -g -O0 -I"$GT/googletest/include" \
mytest.cpp gtest-all.o -pthread -o mytest
lldb -b -o "b mytest.cpp:4" -o run -o "frame variable" mytest
```
Confirm the necessary DWARF ingredient is present (the empty template pack on a
duplicated tuple copy is layout-sensitive; the constrained-ctor count is a quick
proxy):
```bash
llvm-dwarfdump --debug-info mytest | grep -cE 'linkage_name.*_ZNSt5tuple.*Q'
# GCC build -> non-zero (constrained tuple ctors present)
# Clang build isolates the difference via the template-parameter pack; see Godbolt
```
Observed (GCC build): `frame variable` prints the "unable to be parsed" error
for `_M_t`; the enclosing `std::unique_ptr` cannot be displayed. The Clang build
of the identical source prints the value with no error.
## Compiler Explorer
https://godbolt.org/z/eEqhdGjje
The link shows `llvm-dwarfdump` of the same `std::tuple<...>` type for GCC 15.2
vs Clang 22.1.0:
- **GCC**: `DW_TAG_GNU_template_parameter_pack` is **empty** (immediately
followed by `NULL`), with no `DW_TAG_template_type_parameter` - the defective
form that lldb mishandles.
- **Clang**: `DW_TAG_GNU_template_parameter_pack ("_Elements")` is **populated**
with two `DW_TAG_template_type_parameter` children binding the arguments - the
well-formed form that parses fine.
## Status
- Affects the **22.x** release (and current `main`) when debugging binaries
compiled by **GCC** with **`-std=c++20`** or later.
- **Regression** from 21.x (bisects to `4dfe212dade7` / #154123).
- The **SIGSEGV** is fixed on `main` by
[#175737](https://github.com/llvm/llvm-project/pull/175737).
- The **root-cause parse failure** (this issue) is **still open**:
`std::unique_ptr` values from GCC/C++20 binaries remain undisplayable.
- **Clang**-compiled binaries are unaffected.
## Crash backtrace (prior to #175737)
The backtrace below is from before the defensive fix and is included for
context; with #175737 the process no longer crashes but still emits the
"unable to be parsed" error. Captured from a `RelWithDebInfo` +
`LLVM_ENABLE_ASSERTIONS=ON` build of the 22.1.1 tag.
SIGSEGV backtrace (frame variable)
```
error: mytest 0x00007c94: DW_TAG_member '_M_t' refers to type 0x000000000000815a which was unable to be parsed
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
Stack dump:
0. Program arguments: build/bin/lldb -b -o "b mytest.cpp:4" -o run -o "frame variable" mytest
#7 (anonymous namespace)::LibStdcppUniquePtrSyntheticFrontEnd::Update()
lldb/source/Plugins/Language/CPlusPlus/LibStdcppUniquePointer.cpp:104:52 <-- null deref (now guarded by #175737)
#11 lldb_private::formatters::LibStdcppUniquePtrSyntheticFrontEndCreator(CXXSyntheticChildren*, shared_ptr)
lldb/source/Plugins/Language/CPlusPlus/LibStdcppUniquePointer.cpp:163
#15 GenericUniquePtrSyntheticFrontEndCreator(CXXSyntheticChildren*, shared_ptr)
lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp:1555
#21 std::_Function_handler::_M_invoke(...)
#25 lldb_private::CXXSyntheticChildren::GetFrontEnd(ValueObject&)
lldb/include/lldb/DataFormatters/TypeSynthetic.h:386
#30 lldb_private::ValueObjectSynthetic::CreateSynthFilter()
lldb/source/ValueObject/ValueObjectSynthetic.cpp:155
#34 lldb_private::ValueObject::CalculateSyntheticValue()
lldb/source/ValueObject/ValueObject.cpp:2018
#35 lldb_private::ValueObject::GetSyntheticValue()
lldb/source/ValueObject/ValueObject.cpp:2051
#37 lldb_private::FormatManager::ShouldPrintAsOneLiner(ValueObject&)
lldb/source/DataFormatters/FormatManager.cpp:523
#38 lldb_private::ValueObjectPrinter::PrintChildrenIfNeeded(bool, bool)
lldb/source/DataFormatters/ValueObjectPrinter.cpp:824
#43 lldb_private::ValueObjectPrinter::PrintValueObject()
lldb/source/DataFormatters/ValueObjectPrinter.cpp:133
#49 lldb_private::ValueObject::Dump(Stream&, DumpValueObjectOptions const&)
lldb/source/ValueObject/ValueObject.cpp:2727
#52 CommandObjectFrameVariable::DoExecute(Args&, CommandReturnObject&)
lldb/source/Commands/CommandObjectFrame.cpp:695
#56 lldb_private::CommandInterpreter::HandleCommand(char const*, LazyBool, CommandReturnObject&, bool)
lldb/source/Interpreter/CommandInterpreter.cpp:2299
#64 lldb_private::Debugger::RunIOHandlers()
lldb/source/Core/Debugger.cpp:1280
#65 lldb_private::CommandInterpreter::RunCommandInterpreter(CommandInterpreterRunOptions&)
lldb/source/Interpreter/CommandInterpreter.cpp:3692
#66 lldb::SBDebugger::RunCommandInterpreter(SBCommandInterpreterRunOptions const&)
lldb/source/API/SBDebugger.cpp:1227
#67 Driver::MainLoop() lldb/tools/driver/Driver.cpp:660
#68 main lldb/tools/driver/Driver.cpp:930
```
## Environment
| Component | Version |
| --- | --- |
| Debugger | lldb 22.1.1 (also reproduces on `main`), CodeLLDB `22.1.4-codelldb` |
| Last-good debugger | lldb 21.1.7 (no crash / no error on the same binary) |
| Compiler (triggers) | `g++ (GCC) 15.2.0`, `-std=c++20`, DWARF v5 (default) |
| Compiler (unaffected) | `clang++` (binds tuple template params via `DW_TAG_template_type_parameter`) |
| Target | `x86_64-unknown-linux-gnu`, libstdc++ from GCC 15.2.0 |
| Third party in repro | googletest 1.15.2 |
Contributor guide
Research direction
Start by running the provided GCC C++20 reproducer and inspect DWARF with llvm-dwarfdump. Read DWARFASTParserClang::ParseStructureLikeDIE and TypeSystemClang::CreateClassTemplateSpecializationDecl, using the _M_t parse error as the failure point. Done means GCC-built std::unique_ptr values display in lldb, while the #154123 self-inheritance case remains protected by tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100