llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: Zyn (zyn-li) <details> <summary>Changes</summary> ## 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 ``` --- Full diff: https://github.com/llvm/llvm-project/pull/187598.diff 5 Files Affected: - (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp (+59-5) - (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h (+7) - (added) lldb/test/API/lang/cpp/non-type-template-param-member-ptr/Makefile (+3) - (added) lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py (+15) - (added) lldb/test/API/lang/cpp/non-type-template-param-member-ptr/main.cpp (+16) ``````````diff 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); +} `````````` </details> https://github.com/llvm/llvm-project/pull/187598 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
