[Lldb-commits] [lldb] [lldb][DWARFASTParserClang] Fix handle non-type template parameters in MakeAPValue (PR #186629)
via lldb-commits
lldb-commits at lists.llvm.org
Sun Mar 15 16:47:03 PDT 2026
https://github.com/zyn-li updated https://github.com/llvm/llvm-project/pull/186629
>From 61e17a3af68f36536e98bb676a5fda541532296d Mon Sep 17 00:00:00 2001
From: Zhiyuan Li <zynli at meta.com>
Date: Sun, 15 Mar 2026 16:02:35 -0700
Subject: [PATCH 1/3] [lldb][TypeSystem] Add
CompilerType::IsMemberDataPointerType
---
lldb/include/lldb/Symbol/CompilerType.h | 2 ++
lldb/include/lldb/Symbol/TypeSystem.h | 2 ++
.../TypeSystem/Clang/TypeSystemClang.cpp | 9 ++++++
.../TypeSystem/Clang/TypeSystemClang.h | 2 ++
lldb/source/Symbol/CompilerType.cpp | 7 +++++
lldb/unittests/Symbol/TestTypeSystemClang.cpp | 29 +++++++++++++++++++
6 files changed, 51 insertions(+)
diff --git a/lldb/include/lldb/Symbol/CompilerType.h b/lldb/include/lldb/Symbol/CompilerType.h
index 5f152b6f26a89..ca310c9098841 100644
--- a/lldb/include/lldb/Symbol/CompilerType.h
+++ b/lldb/include/lldb/Symbol/CompilerType.h
@@ -166,6 +166,8 @@ class CompilerType {
bool IsMemberFunctionPointerType() const;
+ bool IsMemberDataPointerType() const;
+
bool
IsBlockPointerType(CompilerType *function_pointer_type_ptr = nullptr) const;
diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h
index 8ee7df40051bc..d68abba0b384d 100644
--- a/lldb/include/lldb/Symbol/TypeSystem.h
+++ b/lldb/include/lldb/Symbol/TypeSystem.h
@@ -178,6 +178,8 @@ class TypeSystem : public PluginInterface,
virtual bool
IsMemberFunctionPointerType(lldb::opaque_compiler_type_t type) = 0;
+ virtual bool IsMemberDataPointerType(lldb::opaque_compiler_type_t type) = 0;
+
virtual bool IsBlockPointerType(lldb::opaque_compiler_type_t type,
CompilerType *function_pointer_type_ptr) = 0;
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index 211630f24c7f4..bf7666cd9d5e0 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -3169,6 +3169,15 @@ bool TypeSystemClang::IsMemberFunctionPointerType(
return IsTypeImpl(type, isMemberFunctionPointerType);
}
+bool TypeSystemClang::IsMemberDataPointerType(
+ lldb::opaque_compiler_type_t type) {
+ auto isMemberDataPointerType = [](clang::QualType qual_type) {
+ return qual_type->isMemberDataPointerType();
+ };
+
+ return IsTypeImpl(type, isMemberDataPointerType);
+}
+
bool TypeSystemClang::IsFunctionPointerType(lldb::opaque_compiler_type_t type) {
auto isFunctionPointerType = [](clang::QualType qual_type) {
return qual_type->isFunctionPointerType();
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
index e2c63741c82f8..d6c221906a997 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
@@ -672,6 +672,8 @@ class TypeSystemClang : public TypeSystem {
bool IsMemberFunctionPointerType(lldb::opaque_compiler_type_t type) override;
+ bool IsMemberDataPointerType(lldb::opaque_compiler_type_t type) override;
+
bool IsBlockPointerType(lldb::opaque_compiler_type_t type,
CompilerType *function_pointer_type_ptr) override;
diff --git a/lldb/source/Symbol/CompilerType.cpp b/lldb/source/Symbol/CompilerType.cpp
index a568923e83201..20a69becdd56f 100644
--- a/lldb/source/Symbol/CompilerType.cpp
+++ b/lldb/source/Symbol/CompilerType.cpp
@@ -177,6 +177,13 @@ bool CompilerType::IsMemberFunctionPointerType() const {
return false;
}
+bool CompilerType::IsMemberDataPointerType() const {
+ if (IsValid())
+ if (auto type_system_sp = GetTypeSystem())
+ return type_system_sp->IsMemberDataPointerType(m_type);
+ return false;
+}
+
bool CompilerType::IsBlockPointerType(
CompilerType *function_pointer_type_ptr) const {
if (IsValid())
diff --git a/lldb/unittests/Symbol/TestTypeSystemClang.cpp b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
index d8d95f17e5104..3946c8c737870 100644
--- a/lldb/unittests/Symbol/TestTypeSystemClang.cpp
+++ b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
@@ -1518,3 +1518,32 @@ TEST_P(TestTypeSystemClangAsmLabel, DeclGetMangledName) {
INSTANTIATE_TEST_SUITE_P(AsmLabelTests, TestTypeSystemClangAsmLabel,
testing::ValuesIn(g_asm_label_test_cases));
+
+TEST_F(TestTypeSystemClang, TestIsMemberDataPointerType) {
+ // Create struct S { int x; void foo(); };
+ CompilerType int_type = m_ast->GetBasicType(lldb::eBasicTypeInt);
+ CompilerType record_type = clang_utils::createRecord(*m_ast, "S");
+
+ // int S::* — member data pointer
+ CompilerType member_data_ptr =
+ TypeSystemClang::CreateMemberPointerType(record_type, int_type);
+ EXPECT_TRUE(member_data_ptr.IsMemberDataPointerType());
+ EXPECT_FALSE(member_data_ptr.IsMemberFunctionPointerType());
+
+ // void (S::*)() — member function pointer
+ CompilerType void_type = m_ast->GetBasicType(lldb::eBasicTypeVoid);
+ CompilerType func_type = m_ast->CreateFunctionType(void_type, {}, false, 0U);
+ CompilerType member_func_ptr =
+ TypeSystemClang::CreateMemberPointerType(record_type, func_type);
+ EXPECT_FALSE(member_func_ptr.IsMemberDataPointerType());
+ EXPECT_TRUE(member_func_ptr.IsMemberFunctionPointerType());
+
+ // int* — regular pointer, neither member data nor member function
+ CompilerType regular_ptr = int_type.GetPointerType();
+ EXPECT_FALSE(regular_ptr.IsMemberDataPointerType());
+ EXPECT_FALSE(regular_ptr.IsMemberFunctionPointerType());
+
+ // int — not a pointer at all
+ EXPECT_FALSE(int_type.IsMemberDataPointerType());
+ EXPECT_FALSE(int_type.IsMemberFunctionPointerType());
+}
>From a406162bd33e687c93605075a4d964c8c3b8e4ff 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 2/3] [lldb][DWARFASTParserClang] Handle pointer-to-member
non-type template parameters in MakeAPValue
---
.../SymbolFile/DWARF/DWARFASTParserClang.cpp | 22 ++++++++++----
.../Makefile | 3 ++
.../TestCppNonTypeTemplateParamPtrToMember.py | 23 ++++++++++++++
.../main.cpp | 30 +++++++++++++++++++
4 files changed, 73 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..7f4b2ad177efb 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -2015,7 +2015,11 @@ 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.IsPointerOrReferenceType() ||
+ clang_type.IsMemberFunctionPointerType() ||
+ clang_type.IsMemberDataPointerType() ||
+ clang_type.IsNullPtrType();
llvm::APSInt apint(*bit_width, !is_signed);
apint = value;
@@ -2023,13 +2027,21 @@ 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;
}
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..0a236ce3604bb
--- /dev/null
+++ b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/main.cpp
@@ -0,0 +1,30 @@
+// 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);
+}
>From efadeabbd3d6ec9ac866c6f087f3b28a3ad1be5e Mon Sep 17 00:00:00 2001
From: Zhiyuan Li <zynli at meta.com>
Date: Sun, 15 Mar 2026 15:35:41 -0700
Subject: [PATCH 3/3] [lldb][DWARFASTParserClang] Resolve pointer-to-member
NTTP values to FieldDecls
---
.../SymbolFile/DWARF/DWARFASTParserClang.cpp | 46 +++++++++++++++++++
.../SymbolFile/DWARF/DWARFASTParserClang.h | 7 +++
.../TestCppNonTypeTemplateParamPtrToMember.py | 4 +-
3 files changed, 55 insertions(+), 2 deletions(-)
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index 7f4b2ad177efb..2a6acf32e3ebf 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -2044,6 +2044,39 @@ static std::optional<clang::APValue> MakeAPValue(const clang::ASTContext &ast,
return std::nullopt;
}
+clang::FieldDecl *DWARFASTParserClang::ResolveFieldFromPtrToMemberValue(
+ 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(
const DWARFDIE &die,
TypeSystemClang::TemplateParameterInfos &template_param_infos) {
@@ -2127,6 +2160,19 @@ 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 so it displays as "&S::x" instead of "0".
+ if (clang_type.IsMemberDataPointerType()) {
+ if (auto *field = ResolveFieldFromPtrToMemberValue(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..f43638b005459 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-member, follow the DWARF chain to find the FieldDecl
+ /// at the given byte offset within the containing class.
+ clang::FieldDecl *ResolveFieldFromPtrToMemberValue(
+ 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/TestCppNonTypeTemplateParamPtrToMember.py b/lldb/test/API/lang/cpp/non-type-template-param-member-ptr/TestCppNonTypeTemplateParamPtrToMember.py
index 7af6d9014b26d..5d98ca21fb17b 100644
--- 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
@@ -19,5 +19,5 @@ def test(self):
# 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>")
+ self.expect_expr("dx", result_type="Data<&S::x>")
+ self.expect_expr("dy", result_type="Data<&S::y>")
More information about the lldb-commits
mailing list