Zyn 303afa0899
[lldb][DWARFASTParserClang] Handle pointer-to-member-data non-type template (#187598)
## Description

### Problem
MakeAPValue in DWARFASTParserClang.cpp did not handle
pointer-to-member-data non-type template parameters (e.g., template <int
S::*P>), causing LLDB to produce incorrect results or crash.

DWARF encodes pointer-to-member-data NTTPs as
`DW_TAG_template_value_parameter` with a `DW_AT_const_value`
representing the byte offset of the member within the containing struct.
MakeAPValue is responsible for converting this value into a clang
APValue, but it only handled integer/enum and floating-point types. For
pointer-to-member types, it returned `std::nullopt`.

This caused the caller (ParseTemplateDIE) to fall back to creating a
type-only TemplateArgument (kind=Type) instead of a value-carrying one.
When two specializations differ only by which member they point to
(e.g., MemberData<&S::x> / MemberData<&S::y>), both produce identical
TemplateArguments. Clang's
[findSpecialization](3bc216c29c/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp (L1674-L1677))
then treats the second as a duplicate, so only one specialization exists
in the AST. The second variable becomes unresolvable.

(See Debugger Evidence section below)

In more complex cases, this triggers an assertion failure in
[clang::CXXRecordDecl::setBases():
cast()](3bc216c29c/clang/lib/AST/DeclCXX.cpp (L219))
argument of incompatible type.

## Fix
MakeAPValue: Added `IsMemberDataPointerType()` to the integral type
check so that pointer-to-member byte offsets produce distinct APValues.
Also replaced the silent return `std::nullopt` for unsupported types
with `lldbassert` so unknown type classes are caught during development.

`ResolveMemberDataPointerToFieldDecl`: New method that follows the DWARF
chain to resolve the byte offset to the actual FieldDecl, creating
TemplateArgument(Declaration) matching clang's own AST:

DW_TAG_template_value_parameter (DW_AT_type)
  → DW_TAG_ptr_to_member_type (DW_AT_containing_type)
    → DW_TAG_structure_type → match DW_TAG_member by byte offset

If resolution fails at any step, falls through to the integer APValue
path as a safe fallback.

Verified by comparing clang's AST (clang -Xclang -ast-dump) with LLDB's
reconstructed AST (image dump ast) — both now produce TemplateArgument
decl '&S::x' referencing the correct FieldDecl.


## Test Plan
Added `lldb/test/API/lang/cpp/non-type-template-param-member-ptr/` with
a test that creates two specializations (MemberData<&S::x> and
MemberData<&S::y>) and verifies both are resolvable with correct type
names.

```
| |-ClassTemplateSpecializationDecl 0x234e2314800 <line:18:1, line:21:1> line:19:8 struct MemberData definition instantiated_from 0x234e2314110 implicit_instantiation
| | |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
| | | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
--
| | |-TemplateArgument decl '&S::x'
| | | `-Field 0x234e2313ed0 'x' 'int'
| | |-CXXRecordDecl 0x234e2314ac0 <col:1, col:8> col:8 implicit struct MemberData
--
| `-ClassTemplateSpecializationDecl 0x234e25b5968 <line:18:1, line:21:1> line:19:8 struct MemberData definition instantiated_from 0x234e2314110 implicit_instantiation
|   |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
|   | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
--
|   |-TemplateArgument decl '&S::y'
|   | `-Field 0x234e2313f38 'y' 'int'
|   |-CXXRecordDecl 0x234e25b5bd8 <col:1, col:8> col:8 implicit struct MemberData
--
| |-ClassTemplateSpecializationDecl 0x234e25b7080 <line:27:1, line:30:1> line:28:8 struct MaybeNull definition instantiated_from 0x234e25b6b50 implicit_instantiation
| | |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
| | | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
--
| `-ClassTemplateSpecializationDecl 0x234e25b80c0 <line:27:1, line:30:1> line:28:8 struct MaybeNull definition instantiated_from 0x234e25b6b50 implicit_instantiation
|   |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
|   | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
--
|   |-TemplateArgument decl '&g1'
|   | `-Var 0x234e25b69b8 'g1' 'int'
|   |-CXXRecordDecl 0x234e25b8338 <col:1, col:8> col:8 implicit struct MaybeNull
```

```
lldb a.out -o "type lookup MemberData<&S::x>" -o "type lookup MemberData<&S::y>" -o quit
(lldb) target create "a.out"
Current executable set to 'a.out' (x86_64).
(lldb) type lookup MemberData<&S::x>
template<> struct MemberData<&S::x> {
    int get(S &);
}
(lldb) type lookup MemberData<&S::y>
template<> struct MemberData<&S::y> {
    int get(S &);
}
```



## **Debugger Evidence**
Collected at two `DW_TAG_template_value_parameter` DIEs during
ParseTemplateDIE, both with name="PtrToMember" and `type_class=256`
(eTypeClassMemberPointer): DIE `0xcb: uval64=8`; DIE `0x1e9 : uval64=24`

These correspond to two members of Fiber:
```
DW_TAG_member "listHook_"        DW_AT_data_member_location(0x08)  ← uval64=8
DW_TAG_member "globalListHook_"  DW_AT_data_member_location(0x18)  ← uval64=24
```
Clang's ground truth AST correctly produces TemplateArgument decl
(kind=Declaration) with distinct FieldDecl references for each
specialization.
```
22:| | |-ClassTemplateSpecializationDecl 0x36d785b7cc0 <line:43:1, line:56:1> line:44:8 struct mhtraits definition instantiated_from 0x36d785b4238 implicit_instantiation
23-| | | |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
24-| | | | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
25-| | | | |-CopyConstructor simple trivial has_const_param implicit_has_const_param
26-| | | | |-MoveConstructor exists simple trivial
27-| | | | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
28-| | | | |-MoveAssignment exists simple trivial needs_implicit
29-| | | | `-Destructor simple irrelevant trivial
30-| | | |-TemplateArgument type 'intrusive::Fiber'
31-| | | | `-RecordType 0x36d785b3fb0 'intrusive::Fiber' canonical
32-| | | |   `-CXXRecord 0x36d78344850 'Fiber'
33-| | | |-TemplateArgument type 'intrusive::list_member_hook<>'
34-| | | | `-RecordType 0x36d785b3c20 'intrusive::list_member_hook<>' canonical
35-| | | |   `-ClassTemplateSpecialization 0x36d78344b48 'list_member_hook'
36-| | | |-TemplateArgument decl '&intrusive::Fiber::listHook_'
37-| | | | `-Field 0x36d785b3e70 'listHook_' 'list_member_hook<>':'intrusive::list_member_hook<>'
38-| | | |-CXXRecordDecl 0x36d785b93a0 <col:1, col:8> col:8 implicit struct mhtraits
39-| | | |-TypeAliasDecl 0x36d785b9470 <line:45:3, col:22> col:9 referenced value_type 'intrusive::Fiber'
40-| | | | `-SubstTemplateTypeParmType 0x36d785b9430 'intrusive::Fiber' sugar typename depth 0 index 0 T
41-| | | |   |-ClassTemplateSpecialization 0x36d785b7cc0 'mhtraits'
42-| | | |   `-RecordType 0x36d785b3fb0 'intrusive::Fiber' canonical
--
78:| | `-ClassTemplateSpecializationDecl 0x36d785b8210 <line:43:1, line:56:1> line:44:8 struct mhtraits definition instantiated_from 0x36d785b4238 implicit_instantiation
79-| |   |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
80-| |   | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
81-| |   | |-CopyConstructor simple trivial has_const_param implicit_has_const_param
82-| |   | |-MoveConstructor exists simple trivial
83-| |   | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
84-| |   | |-MoveAssignment exists simple trivial needs_implicit
85-| |   | `-Destructor simple irrelevant trivial
86-| |   |-TemplateArgument type 'intrusive::Fiber'
87-| |   | `-RecordType 0x36d785b3fb0 'intrusive::Fiber' canonical
88-| |   |   `-CXXRecord 0x36d78344850 'Fiber'
89-| |   |-TemplateArgument type 'intrusive::list_member_hook<>'
90-| |   | `-RecordType 0x36d785b3c20 'intrusive::list_member_hook<>' canonical
91-| |   |   `-ClassTemplateSpecialization 0x36d78344b48 'list_member_hook'
92-| |   |-TemplateArgument decl '&intrusive::Fiber::globalListHook_'
93-| |   | `-Field 0x36d785b3f60 'globalListHook_' 'list_member_hook<>':'intrusive::list_member_hook<>'
94-| |   |-CXXRecordDecl 0x36d785be080 <col:1, col:8> col:8 implicit struct mhtraits
95-| |   |-TypeAliasDecl 0x36d785be150 <line:45:3, col:22> col:9 referenced value_type 'intrusive::Fiber'
96-| |   | `-SubstTemplateTypeParmType 0x36d785be110 'intrusive::Fiber' sugar typename depth 0 index 0 T
97-| |   |   |-ClassTemplateSpecialization 0x36d785b8210 'mhtraits'
98-| |   |   `-RecordType 0x36d785b3fb0 'intrusive::Fiber' canonical
```


### **Full Paste**
```
$ llvm-dwarfdump --debug-info=0x000000cb repro.dwp -c -p
repro.dwp:	file format elf64-x86-64
.debug_info.dwo contents:
0x00000014: DW_TAG_compile_unit
              DW_AT_producer	("clang version 22.1.20 ...)
              DW_AT_language	(DW_LANG_C_plus_plus_14)
              DW_AT_name	("repro.cpp")
              DW_AT_dwo_name	("repro-repro.dwo")
0x0000001a:   DW_TAG_namespace
                DW_AT_name	("intrusive")
0x000000b9:     DW_TAG_structure_type
                  DW_AT_calling_convention	(DW_CC_pass_by_value)
                  DW_AT_name	("mhtraits<intrusive::Fiber, intrusive::list_member_hook<(intrusive::link_mode_type)2>, &intrusive::Fiber::listHook_>")
                  DW_AT_byte_size	(0x01)
                  DW_AT_decl_file	(0x00)
                  DW_AT_decl_line	(44)
0x000000cb:       DW_TAG_template_value_parameter
                    DW_AT_type	(0x0000022d "intrusive::list_member_hook<(intrusive::link_mode_type)2> intrusive::Fiber::*")
                    DW_AT_name	("PtrToMember")
                    DW_AT_const_value	(8)
```
lldb prints: 
```
p name
(const char *) 0x000055f44554c4ca "PtrToMember"
p uval64
(uint64_t) 8
p (unsigned)clang_type.GetTypeClass()
(unsigned int) 256
```
```
$ llvm-dwarfdump --debug-info=0x000001e9 repro.dwp -c -p
repro.dwp:	file format elf64-x86-64
.debug_info.dwo contents:
0x00000014: DW_TAG_compile_unit
              DW_AT_producer	("clang version 22.1.20")
              DW_AT_language	(DW_LANG_C_plus_plus_14)
              DW_AT_name	("repro.cpp")
              DW_AT_dwo_name	("repro-repro.dwo")
0x0000001a:   DW_TAG_namespace
                DW_AT_name	("intrusive")
0x000001d7:     DW_TAG_structure_type
                  DW_AT_calling_convention	(DW_CC_pass_by_value)
                  DW_AT_name	("mhtraits<intrusive::Fiber, intrusive::list_member_hook<(intrusive::link_mode_type)2>, &intrusive::Fiber::globalListHook_>")
                  DW_AT_byte_size	(0x01)
                  DW_AT_decl_file	(0x00)
                  DW_AT_decl_line	(44)
0x000001e9:       DW_TAG_template_value_parameter
                    DW_AT_type	(0x0000022d "intrusive::list_member_hook<(intrusive::link_mode_type)2> intrusive::Fiber::*")
                    DW_AT_name	("PtrToMember")
                    DW_AT_const_value	(24)
```
lldb prints: 
```
 p name
(const char *) 0x000055f44554c4ca "PtrToMember"
p uval64
(uint64_t) 24
p (unsigned)clang_type.GetTypeClass()
(unsigned int) 256
```
2026-03-24 15:26:41 -07:00
..