https://github.com/zyn-li created 
https://github.com/llvm/llvm-project/pull/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](https://github.com/llvm/llvm-project/blob/3bc216c29cb42c7d94b617943b1d44afce605588/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()](https://github.com/llvm/llvm-project/blob/3bc216c29cb42c7d94b617943b1d44afce605588/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
```

>From 4ed350ab81ff58290eee2bd7957766a82d8c603d Mon Sep 17 00:00:00 2001
From: Zhiyuan Li <[email protected]>
Date: Fri, 13 Mar 2026 18:05:10 -0700
Subject: [PATCH] [lldb][DWARFASTParserClang] Handle pointer-to-member-data
 non-type template parameters

---
 .../SymbolFile/DWARF/DWARFASTParserClang.cpp  | 64 +++++++++++++++++--
 .../SymbolFile/DWARF/DWARFASTParserClang.h    |  7 ++
 .../Makefile                                  |  3 +
 .../TestCppNonTypeTemplateParamPtrToMember.py | 15 +++++
 .../main.cpp                                  | 16 +++++
 5 files changed, 100 insertions(+), 5 deletions(-)
 create mode 100644 
lldb/test/API/lang/cpp/non-type-template-param-member-ptr/Makefile
 create mode 100644 
lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py
 create mode 100644 
lldb/test/API/lang/cpp/non-type-template-param-member-ptr/main.cpp

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp 
b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index cb33fc21bfba9..01666a435eed2 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -2015,7 +2015,8 @@ static std::optional<clang::APValue> MakeAPValue(const 
clang::ASTContext &ast,
     return std::nullopt;
 
   bool is_signed = false;
-  const bool is_integral = clang_type.IsIntegerOrEnumerationType(is_signed);
+  const bool is_integral = clang_type.IsIntegerOrEnumerationType(is_signed) ||
+                           clang_type.IsMemberDataPointerType();
 
   llvm::APSInt apint(*bit_width, !is_signed);
   apint = value;
@@ -2023,13 +2024,54 @@ static std::optional<clang::APValue> MakeAPValue(const 
clang::ASTContext &ast,
   if (is_integral)
     return clang::APValue(apint);
 
+  if (clang_type.IsRealFloatingPointType()) {
+    return clang::APValue(llvm::APFloat(
+        ast.getFloatTypeSemantics(ClangUtil::GetQualType(clang_type)), apint));
+  }
+
   // FIXME: we currently support a limited set of floating point types.
   // E.g., 16-bit floats are not supported.
-  if (!clang_type.IsRealFloatingPointType())
-    return std::nullopt;
 
-  return clang::APValue(llvm::APFloat(
-      ast.getFloatTypeSemantics(ClangUtil::GetQualType(clang_type)), apint));
+  LLDB_LOG(GetLog(LLDBLog::Types),
+           "MakeAPValue: Unsupported NTTP type class: {0}",
+           clang_type.GetTypeClass());
+
+  lldbassert(false && "Unsupported type for non-type template parameter");
+
+  return std::nullopt;
+}
+
+clang::FieldDecl *DWARFASTParserClang::ResolveMemberDataPointerToFieldDecl(
+    const DWARFDIE &die, uint64_t member_byte_offset) {
+  // die (DW_AT_type) → DW_TAG_ptr_to_member_type
+  DWARFDIE type_die = die.GetReferencedDIE(DW_AT_type);
+  if (!type_die || type_die.Tag() != DW_TAG_ptr_to_member_type)
+    return nullptr;
+
+  // → DW_AT_containing_type → struct/class DIE
+  DWARFDIE containing_die = type_die.GetReferencedDIE(DW_AT_containing_type);
+  if (!containing_die)
+    return nullptr;
+
+  // Resolve and complete the containing class
+  Type *containing_type = die.ResolveTypeUID(containing_die);
+  if (!containing_type)
+    return nullptr;
+
+  CompilerType containing_ct = containing_type->GetFullCompilerType();
+  auto *record_decl =
+      m_ast.GetAsCXXRecordDecl(containing_ct.GetOpaqueQualType());
+  if (!record_decl)
+    return nullptr;
+
+  // Walk fields, match by byte offset
+  clang::ASTContext &ast = m_ast.getASTContext();
+  for (auto *field : record_decl->fields()) {
+    if (ast.getFieldOffset(field) / 8 == member_byte_offset)
+      return field;
+  }
+
+  return nullptr;
 }
 
 bool DWARFASTParserClang::ParseTemplateDIE(
@@ -2115,6 +2157,18 @@ bool DWARFASTParserClang::ParseTemplateDIE(
 
       if (tag == DW_TAG_template_value_parameter && uval64_valid) {
         if (auto value = MakeAPValue(ast, clang_type, uval64)) {
+          // For pointer-to-member types, try to resolve to the actual 
FieldDecl
+          if (clang_type.IsMemberDataPointerType()) {
+            if (auto *field = ResolveMemberDataPointerToFieldDecl(die, 
uval64)) {
+              template_param_infos.InsertArg(
+                  name, clang::TemplateArgument(
+                            field, ClangUtil::GetQualType(clang_type),
+                            is_default_template_arg));
+              return true;
+            }
+            // Failed to resolve FieldDecl, fall through to integer path
+          }
+
           template_param_infos.InsertArg(
               name, clang::TemplateArgument(
                         ast, ClangUtil::GetQualType(clang_type),
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h 
b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
index 03c431c73fb6f..ca76bcdc4ace2 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
@@ -185,6 +185,13 @@ class DWARFASTParserClang : public 
lldb_private::plugin::dwarf::DWARFASTParser {
                         lldb_private::TypeSystemClang::TemplateParameterInfos
                             &template_param_infos);
 
+  /// Given a DW_TAG_template_value_parameter DIE whose type is a
+  /// pointer-to-data-member, follow the DWARF chain to find the FieldDecl
+  /// at the given byte offset within the containing class.
+  clang::FieldDecl *ResolveMemberDataPointerToFieldDecl(
+      const lldb_private::plugin::dwarf::DWARFDIE &die,
+      uint64_t member_byte_offset);
+
   bool ParseTemplateParameterInfos(
       const lldb_private::plugin::dwarf::DWARFDIE &parent_die,
       lldb_private::TypeSystemClang::TemplateParameterInfos
diff --git a/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/Makefile 
b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git 
a/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py
 
b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py
new file mode 100644
index 0000000000000..9279ccf7cd358
--- /dev/null
+++ 
b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py
@@ -0,0 +1,15 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+    @no_debug_info_test
+    def test_member_data_pointer(self):
+        """Member data pointer NTTPs: MemberData<&S::x> vs MemberData<&S::y>"""
+        self.build()
+        self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        # Both must be resolvable as distinct specializations.
+        self.expect_expr("md1", result_type="MemberData<&S::x>")
+        self.expect_expr("md2", result_type="MemberData<&S::y>")
diff --git a/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/main.cpp 
b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/main.cpp
new file mode 100644
index 0000000000000..3de9c024c82f9
--- /dev/null
+++ b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/main.cpp
@@ -0,0 +1,16 @@
+struct S {
+  int x;
+  int y;
+};
+
+// --- Member data pointer NTTP ---
+template <int S::*P> struct MemberData {
+  int get(S &s) { return s.*P; }
+};
+MemberData<&S::x> md1;
+MemberData<&S::y> md2;
+
+int main() {
+  S s{1, 2};
+  return md1.get(s) + md2.get(s);
+}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to