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);
+}


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

Reply via email to