[Lldb-commits] [lldb] 103f821 - [lldb][DWARFASTParserClang] Handle pointer-to-member-data non-type (#189510)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Apr 7 14:26:47 PDT 2026
Author: Zyn
Date: 2026-04-08T01:26:41+04:00
New Revision: 103f821cb0977612fbff270df0e81801e8b93ed4
URL: https://github.com/llvm/llvm-project/commit/103f821cb0977612fbff270df0e81801e8b93ed4
DIFF: https://github.com/llvm/llvm-project/commit/103f821cb0977612fbff270df0e81801e8b93ed4.diff
LOG: [lldb][DWARFASTParserClang] Handle pointer-to-member-data non-type (#189510)
## Reland Notes
Re applying [187598](https://github.com/llvm/llvm-project/pull/187598)
This is a reland of the original commit which was reverted due to a
failure on the Windows buildbot.
Root cause of the Windows failure:
* The fix introduces TemplateArgument::Declaration (pointing to a
FieldDecl)
* GetValueParamType() in TypeSystemClang.cpp did not handle this kind,
so CreateTemplateParameterList() created a
TemplateTypeParmDecl instead of a NonTypeTemplateParmDecl for the
corresponding template parameter.
* On Windows, the Microsoft name mangler calls
cast<NonTypeTemplateParmDecl>(Parm) when mangling member data pointer
NTTPs, which crashed because Parm was a TemplateTypeParmDecl.
* The Itanium mangler (Linux/Mac) does not inspect the parameter
declaration, so the bug was latent there.
Fix:
* Added TemplateArgument::Declaration to GetValueParamType() so it
returns argument.getParamTypeForDecl(), causing
CreateTemplateParameterList() to create the correct
NonTypeTemplateParmDecl.
* Removed the @skipIfWindows decorator from the test.
## 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.
Added:
lldb/test/API/lang/cpp/non-type-template-param-member-ptr/Makefile
lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py
lldb/test/API/lang/cpp/non-type-template-param-member-ptr/main.cpp
Modified:
lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
Removed:
################################################################################
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index 21288cad72ee4..fa76260dca63d 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -2008,28 +2008,94 @@ class DWARFASTParserClang::DelayedAddObjCClassProperty {
static std::optional<clang::APValue> MakeAPValue(const clang::ASTContext &ast,
CompilerType clang_type,
- uint64_t value) {
+ uint64_t value,
+ const DWARFDIE &die) {
std::optional<uint64_t> bit_width =
llvm::expectedToOptional(clang_type.GetBitSize(nullptr));
if (!bit_width)
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.IsMemberDataPointerType()) {
+ llvm::APSInt apint(*bit_width, !is_signed);
+ apint = value;
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()) {
+ llvm::APInt apint(*bit_width, value);
+ return clang::APValue(llvm::APFloat(
+ ast.getFloatTypeSemantics(ClangUtil::GetQualType(clang_type)), apint));
+ }
+
+ die.GetDWARF()->GetObjectFile()->GetModule()->ReportError(
+ "error: unsupported template value type in die {0:x16}, "
+ "please file a bug",
+ die.GetOffset());
+ lldbassert(false && "Unsupported type for non-type template parameter");
+
+ return std::nullopt;
+}
+
+clang::FieldDecl *DWARFASTParserClang::ResolveMemberDataPointerToFieldDecl(
+ const DWARFDIE &die, uint64_t member_byte_offset) {
+ Log *log = GetLog(DWARFLog::TypeCompletion);
+
+ DWARFDIE type_die = die.GetReferencedDIE(DW_AT_type);
+ assert(type_die && type_die.Tag() == DW_TAG_ptr_to_member_type &&
+ "DW_AT_type of a member data pointer must be "
+ "DW_TAG_ptr_to_member_type");
+
+ DWARFDIE containing_die = type_die.GetReferencedDIE(DW_AT_containing_type);
+ if (!containing_die) {
+ LLDB_LOG(log,
+ "ResolveMemberDataPointerToFieldDecl: DIE {0:x16} — "
+ "DW_TAG_ptr_to_member_type {1:x16} has no DW_AT_containing_type",
+ die.GetOffset(), type_die.GetOffset());
+ return nullptr;
+ }
+
+ Type *containing_type = die.ResolveTypeUID(containing_die);
+ if (!containing_type) {
+ LLDB_LOG(log,
+ "ResolveMemberDataPointerToFieldDecl: DIE {0:x16} — "
+ "failed to resolve containing type {1:x16}",
+ die.GetOffset(), containing_die.GetOffset());
+ return nullptr;
+ }
+
+ CompilerType containing_ct = containing_type->GetFullCompilerType();
+ auto *record_decl =
+ m_ast.GetAsCXXRecordDecl(containing_ct.GetOpaqueQualType());
+ if (!record_decl) {
+ LLDB_LOG(log,
+ "ResolveMemberDataPointerToFieldDecl: DIE {0:x16} — "
+ "containing type {1:x16} is not a CXXRecordDecl",
+ die.GetOffset(), containing_die.GetOffset());
+ return nullptr;
+ }
- return clang::APValue(llvm::APFloat(
- ast.getFloatTypeSemantics(ClangUtil::GetQualType(clang_type)), apint));
+ clang::ASTContext &ast = m_ast.getASTContext();
+ for (auto *field : record_decl->fields()) {
+ if (ast.getFieldOffset(field) / 8 == member_byte_offset) {
+ LLDB_LOG(log,
+ "ResolveMemberDataPointerToFieldDecl: DIE {0:x16} — "
+ "resolved to field '{1}' at byte offset {2} in {3}",
+ die.GetOffset(), field->getName(), member_byte_offset,
+ containing_die.GetName());
+ return field;
+ }
+ }
+
+ LLDB_LOG(log,
+ "ResolveMemberDataPointerToFieldDecl: DIE {0:x16} — "
+ "no field found at byte offset {1} in {2}",
+ die.GetOffset(), member_byte_offset, containing_die.GetName());
+ return nullptr;
}
bool DWARFASTParserClang::ParseTemplateDIE(
@@ -2114,7 +2180,24 @@ bool DWARFASTParserClang::ParseTemplateDIE(
name = nullptr;
if (tag == DW_TAG_template_value_parameter && uval64_valid) {
- if (auto value = MakeAPValue(ast, clang_type, uval64)) {
+ if (auto value = MakeAPValue(ast, clang_type, uval64, die)) {
+ // 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
+ die.GetDWARF()->GetObjectFile()->GetModule()->ReportError(
+ "error: failed to resolve member data pointer to FieldDecl "
+ "in die {0:x16}, please file a bug",
+ die.GetOffset());
+ }
+
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/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index fba6aec296324..86d39b51e9b4c 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -1345,6 +1345,8 @@ QualType GetValueParamType(const clang::TemplateArgument &argument) {
return argument.getIntegralType();
case TemplateArgument::StructuralValue:
return argument.getStructuralValueType();
+ case TemplateArgument::Declaration:
+ return argument.getParamTypeForDecl();
default:
return {};
}
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..2133c58aeb8a5
--- /dev/null
+++ b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py
@@ -0,0 +1,14 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+ 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);
+}
More information about the lldb-commits
mailing list