[Lldb-commits] [lldb] [lldb][DWARFASTParserClang] Fix handle non-type template parameters in MakeAPValue (PR #186629)
via lldb-commits
lldb-commits at lists.llvm.org
Sat Mar 14 16:54:51 PDT 2026
https://github.com/zyn-li created https://github.com/llvm/llvm-project/pull/186629
## **Summary**
MakeAPValue in `DWARFASTParserClang.cpp` did not handle pointer-to-member non-type template parameters (e.g., `template <int S::*P>`), causing LLDB to silently produce incorrect results or crash.
When DWARF encodes a pointer-to-member non-type template parameter, it uses `DW_TAG_template_value_parameter` with a `DW_AT_const_value` representing the byte offset of the member within the 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., `Data<&S::x>` vs `Data<&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<Ty>()](https://github.com/llvm/llvm-project/blob/3bc216c29cb42c7d94b617943b1d44afce605588/clang/lib/AST/DeclCXX.cpp#L219) argument of incompatible type.
## **Fix**
Refactored MakeAPValue to comprehensively handle the Non-Type Template Parameters (NTTPs). Instead of a narrow check for integers and enums, the function now leverages CompilerType specialized queries to treat the following categories as integral values:
* Pointer-to-Member Types: Specifically resolves the reported issue where member offsets (e.g., 8 vs. 24) were being dropped.
* Pointer Types: Generalizes support for NTTPs that represent addresses of objects or functions.
* Nullptr Type: Properly handles std::nullptr_t constants.
* Reference Types: lvalue references are correctly captured as value arguments.
By converting these types into distinct clang::APValue objects, we ensure that `ParseTemplateDIE` no longer falls back to type-only arguments. This allows the Clang AST to uniquely identify specializations that differ only by constant offsets or addresses.
Additionally, the fix replaces the silent failure path with:
* Unsupported TypeClass entries are now logged,
* Added an lldbassert to ensure that problems are caught during development rather than silently "ruining" the template AST.
## **Known Limitation**
The fix displays pointer-to-member template arguments as integer offsets (e.g., Data<0>, Data<4>) rather than symbolic names (e.g., Data<&S::x>). A follow-up patch will improve the display.
## **Test Plan**
Added lldb/test/API/lang/cpp/non-type-template-param-member-ptr/ with a test that verifies both Data<&S::x> and Data<&S::y> are resolvable as distinct specializations.
## **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 4b011ccff244f16aaf1975f61b2afd2dac6c1c54 Mon Sep 17 00:00:00 2001
From: Zhiyuan Li <zynli at meta.com>
Date: Fri, 13 Mar 2026 18:05:10 -0700
Subject: [PATCH] [lldb][DWARFASTParserClang] Handle pointer-to-member non-type
template parameters in MakeAPValue
---
.../SymbolFile/DWARF/DWARFASTParserClang.cpp | 24 ++++++++------
.../Makefile | 3 ++
.../TestCppNonTypeTemplateParamPtrToMember.py | 23 ++++++++++++++
.../main.cpp | 31 +++++++++++++++++++
4 files changed, 72 insertions(+), 9 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..509ae46d53c4d 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -2015,21 +2015,27 @@ 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);
-
llvm::APSInt apint(*bit_width, !is_signed);
apint = value;
- if (is_integral)
+ if (clang_type.IsIntegerOrEnumerationType(is_signed) ||
+ clang_type.IsPointerType() || clang_type.IsMemberFunctionPointerType() ||
+ clang_type.IsNullPtrType() || clang_type.IsReferenceType()) {
return clang::APValue(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;
+ if (clang_type.IsRealFloatingPointType()) {
+ 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 clang::APValue(llvm::APFloat(
- ast.getFloatTypeSemantics(ClangUtil::GetQualType(clang_type)), apint));
+ return std::nullopt;
}
bool DWARFASTParserClang::ParseTemplateDIE(
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..7af6d9014b26d
--- /dev/null
+++ b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py
@@ -0,0 +1,23 @@
+"""
+Test that LLDB correctly distinguishes template specializations
+with different pointer-to-member non-type template parameter values.
+"""
+
+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(self):
+ self.build()
+ self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+
+ # Both Data<&S::x> and Data<&S::y> should be resolvable as
+ # distinct specializations. Without the fix, the second
+ # specialization is rejected as a duplicate and dy is
+ # unresolvable.
+ self.expect_expr("dx", result_type="Data<0>")
+ self.expect_expr("dy", result_type="Data<4>")
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..ca3ba3e8c4524
--- /dev/null
+++ b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/main.cpp
@@ -0,0 +1,31 @@
+// Test that LLDB correctly distinguishes template specializations with
+// different pointer-to-member non-type template parameter values.
+//
+// When a template takes a pointer-to-member as a non-type parameter
+// (e.g., template <int S::*P>), DWARF encodes each instantiation's
+// member as a DW_AT_const_value (the byte offset of the member).
+// LLDB must treat these as distinct values so that each specialization
+// (e.g., Data<&S::x> vs Data<&S::y>) gets its own type in the AST.
+//
+// Without the fix, MakeAPValue rejects pointer-to-member types,
+// causing both specializations to produce identical TemplateArguments.
+// findSpecialization then treats the second as a duplicate, so only
+// one specialization exists and the other variable becomes unresolvable.
+
+struct S {
+ int x;
+ int y;
+};
+
+template <int S::*P>
+struct Data {
+ int get(S &s) { return s.*P; }
+};
+
+Data<&S::x> dx;
+Data<&S::y> dy;
+
+int main() {
+ S s{1, 2};
+ return dx.get(s) + dy.get(s);
+}
More information about the lldb-commits
mailing list