Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Reland: [clang] preserve class type sugar when taking pointer to member #132401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 21, 2025

Conversation

mizvekov
Copy link
Contributor

Original PR: #130537
Originally reverted due to revert of dependent commit. Relanding with no changes.

This changes the MemberPointerType representation to use a NestedNameSpecifier instead of a Type to represent the base class.

Since the qualifiers are always parsed as nested names, there was an impedance mismatch when converting these back and forth into types, and this led to issues in preserving sugar.

The nested names are indeed a better match for these, as the differences which a QualType can represent cannot be expressed syntatically, and they represent the use case more exactly, being either dependent or referring to a CXXRecord, unqualified.

This patch also makes the MemberPointerType able to represent sugar for a {up/downcast}cast conversion of the base class, although for now the underlying type is canonical, as preserving the sugar up to that point requires further work.

As usual, includes a few drive-by fixes in order to make use of the improvements.

Original PR: #130537
Originally reverted due to revert of dependent commit.
Relanding with no changes.

This changes the MemberPointerType representation to use
a NestedNameSpecifier instead of a Type to represent the base
class.

Since the qualifiers are always parsed as nested names, there
was an impedance mismatch when converting these back and
forth into types, and this led to issues in preserving
sugar.

The nested names are indeed a better match for these,
as the differences which a QualType can represent cannot be
expressed syntatically, and they represent the use case more
exactly, being either dependent or referring to a CXXRecord,
unqualified.

This patch also makes the MemberPointerType able to represent
sugar for a {up/downcast}cast conversion of the base class,
although for now the underlying type is canonical, as preserving
the sugar up to that point requires further work.

As usual, includes a few drive-by fixes in order to make use
of the improvements.
@mizvekov mizvekov self-assigned this Mar 21, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang-tools-extra lldb clangd clang-tidy clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:codegen IR generation bugs: mangling, exceptions, etc. clang:as-a-library libclang and C++ API clang:static analyzer clang:openmp OpenMP related changes to Clang labels Mar 21, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 21, 2025

@llvm/pr-subscribers-clang-static-analyzer-1
@llvm/pr-subscribers-clangd
@llvm/pr-subscribers-clang
@llvm/pr-subscribers-clang-modules

@llvm/pr-subscribers-lldb

Author: Matheus Izvekov (mizvekov)

Changes

Original PR: #130537
Originally reverted due to revert of dependent commit. Relanding with no changes.

This changes the MemberPointerType representation to use a NestedNameSpecifier instead of a Type to represent the base class.

Since the qualifiers are always parsed as nested names, there was an impedance mismatch when converting these back and forth into types, and this led to issues in preserving sugar.

The nested names are indeed a better match for these, as the differences which a QualType can represent cannot be expressed syntatically, and they represent the use case more exactly, being either dependent or referring to a CXXRecord, unqualified.

This patch also makes the MemberPointerType able to represent sugar for a {up/downcast}cast conversion of the base class, although for now the underlying type is canonical, as preserving the sugar up to that point requires further work.

As usual, includes a few drive-by fixes in order to make use of the improvements.


Patch is 143.32 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/132401.diff

71 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp (+1-2)
  • (modified) clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp (+3-1)
  • (modified) clang-tools-extra/clangd/unittests/FindTargetTests.cpp (+1-1)
  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/include/clang/AST/ASTContext.h (+3-4)
  • (modified) clang/include/clang/AST/ASTNodeTraverser.h (+5-2)
  • (modified) clang/include/clang/AST/CanonicalType.h (+1-1)
  • (modified) clang/include/clang/AST/RecursiveASTVisitor.h (+5-4)
  • (modified) clang/include/clang/AST/Type.h (+15-13)
  • (modified) clang/include/clang/AST/TypeLoc.h (+19-14)
  • (modified) clang/include/clang/AST/TypeProperties.td (+6-3)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2-4)
  • (modified) clang/include/clang/Sema/Sema.h (+9-2)
  • (modified) clang/lib/AST/ASTContext.cpp (+47-21)
  • (modified) clang/lib/AST/ASTImporter.cpp (+9-5)
  • (modified) clang/lib/AST/ASTStructuralEquivalence.cpp (+6-2)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+10-1)
  • (modified) clang/lib/AST/NestedNameSpecifier.cpp (+1)
  • (modified) clang/lib/AST/ODRHash.cpp (+1-1)
  • (modified) clang/lib/AST/QualTypeNames.cpp (+4-3)
  • (modified) clang/lib/AST/Type.cpp (+30-4)
  • (modified) clang/lib/AST/TypePrinter.cpp (+2-2)
  • (modified) clang/lib/CodeGen/CGCXXABI.cpp (+1-1)
  • (modified) clang/lib/CodeGen/CGPointerAuth.cpp (+2-2)
  • (modified) clang/lib/CodeGen/CGVTables.cpp (+2-3)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+1-1)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+5-5)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+4-3)
  • (modified) clang/lib/Sema/SemaAccess.cpp (+18-8)
  • (modified) clang/lib/Sema/SemaCast.cpp (+2-2)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+4-6)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+3-1)
  • (modified) clang/lib/Sema/SemaOpenMP.cpp (+2-3)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+60-27)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+6-2)
  • (modified) clang/lib/Sema/SemaTemplateDeduction.cpp (+16-5)
  • (modified) clang/lib/Sema/SemaType.cpp (+22-100)
  • (modified) clang/lib/Sema/TreeTransform.h (+29-30)
  • (modified) clang/lib/Serialization/ASTReader.cpp (+1-1)
  • (modified) clang/lib/Serialization/ASTWriter.cpp (+1-1)
  • (modified) clang/lib/Serialization/TemplateArgumentHasher.cpp (+3-1)
  • (modified) clang/test/AST/ast-dump-template-json-win32-mangler-crash.cpp (+3-1)
  • (modified) clang/test/AST/ast-dump-templates.cpp (+238)
  • (modified) clang/test/AST/ast-dump-types-json.cpp (+338-44)
  • (modified) clang/test/AST/attr-print-emit.cpp (+1-1)
  • (modified) clang/test/Analysis/cxx-uninitialized-object-ptr-ref.cpp (+5-5)
  • (modified) clang/test/CXX/class.access/p6.cpp (+2-2)
  • (modified) clang/test/CXX/drs/cwg0xx.cpp (+6-6)
  • (modified) clang/test/CXX/drs/cwg13xx.cpp (+2-2)
  • (modified) clang/test/CXX/drs/cwg26xx.cpp (+3-3)
  • (modified) clang/test/CXX/drs/cwg2xx.cpp (+2-2)
  • (modified) clang/test/CXX/drs/cwg4xx.cpp (+1-1)
  • (modified) clang/test/CXX/drs/cwg7xx.cpp (+1-2)
  • (modified) clang/test/CXX/temp/temp.arg/temp.arg.nontype/p1.cpp (+1-1)
  • (modified) clang/test/CXX/temp/temp.arg/temp.arg.nontype/p5.cpp (+3-3)
  • (modified) clang/test/Index/print-type.cpp (+1-1)
  • (modified) clang/test/SemaCXX/addr-of-overloaded-function.cpp (+13-13)
  • (modified) clang/test/SemaCXX/builtin-ptrtomember-ambig.cpp (+2-2)
  • (modified) clang/test/SemaCXX/calling-conv-compat.cpp (+21-21)
  • (modified) clang/test/SemaCXX/err_init_conversion_failed.cpp (+1-1)
  • (modified) clang/test/SemaCXX/member-pointer.cpp (+13-4)
  • (modified) clang/test/SemaOpenACC/combined-construct-if-ast.cpp (+2-2)
  • (modified) clang/test/SemaOpenACC/combined-construct-num_workers-ast.cpp (+2-2)
  • (modified) clang/test/SemaOpenACC/compute-construct-clause-ast.cpp (+2-2)
  • (modified) clang/test/SemaOpenACC/compute-construct-intexpr-clause-ast.cpp (+2-2)
  • (modified) clang/test/SemaOpenACC/data-construct-if-ast.cpp (+2-2)
  • (modified) clang/test/SemaTemplate/instantiate-member-pointers.cpp (+2-1)
  • (modified) clang/tools/libclang/CXType.cpp (+3-1)
  • (modified) clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp (+4-4)
  • (modified) lldb/source/Plugins/SymbolFile/NativePDB/PdbAstBuilder.cpp (+1-1)
  • (modified) lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp (+4-3)
diff --git a/clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp
index 108717e151b57..a6b00be75abf8 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp
@@ -493,8 +493,7 @@ UseNullptrCheck::UseNullptrCheck(StringRef Name, ClangTidyContext *Context)
     : ClangTidyCheck(Name, Context),
       NullMacrosStr(Options.get("NullMacros", "NULL")),
       IgnoredTypes(utils::options::parseStringList(Options.get(
-          "IgnoredTypes",
-          "std::_CmpUnspecifiedParam::;^std::__cmp_cat::__unspec"))) {
+          "IgnoredTypes", "_CmpUnspecifiedParam;^std::__cmp_cat::__unspec"))) {
   StringRef(NullMacrosStr).split(NullMacros, ",");
 }
 
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
index b66cc8512fad6..bc49fa856bafc 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -178,7 +178,9 @@ bool isFunctionPointerConvertible(QualType From, QualType To) {
 
     // Note: converting Derived::* to Base::* is a different kind of conversion,
     // called Pointer-to-member conversion.
-    return FromMember->getClass() == ToMember->getClass() &&
+    return FromMember->getQualifier() == ToMember->getQualifier() &&
+           FromMember->getMostRecentCXXRecordDecl() ==
+               ToMember->getMostRecentCXXRecordDecl() &&
            FromMember->getPointeeType() == ToMember->getPointeeType();
   }
 
diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
index fc54f89f4941e..602f61d9ecb41 100644
--- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
@@ -1489,7 +1489,7 @@ TEST_F(FindExplicitReferencesTest, AllRefsInFoo) {
         "4: targets = {a}\n"
         "5: targets = {a::b}, qualifier = 'a::'\n"
         "6: targets = {a::b::S}\n"
-        "7: targets = {a::b::S::type}, qualifier = 'struct S::'\n"
+        "7: targets = {a::b::S::type}, qualifier = 'S::'\n"
         "8: targets = {y}, decl\n"},
        {R"cpp(
          void foo() {
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 159991e8db981..724cb62f1b0e6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -267,6 +267,7 @@ Improvements to Clang's diagnostics
   under the subgroup ``-Wunsafe-buffer-usage-in-libc-call``.
 - Diagnostics on chained comparisons (``a < b < c``) are now an error by default. This can be disabled with
   ``-Wno-error=parentheses``.
+- Clang now better preserves the sugared types of pointers to member.
 - The ``-Wshift-bool`` warning has been added to warn about shifting a boolean. (#GH28334)
 - Fixed diagnostics adding a trailing ``::`` when printing some source code
   constructs, like base classes.
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index f9a12260a6590..af8c49e99a7ce 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1558,10 +1558,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
   QualType getRValueReferenceType(QualType T) const;
 
   /// Return the uniqued reference to the type for a member pointer to
-  /// the specified type in the specified class.
-  ///
-  /// The class \p Cls is a \c Type because it could be a dependent name.
-  QualType getMemberPointerType(QualType T, const Type *Cls) const;
+  /// the specified type in the specified nested name.
+  QualType getMemberPointerType(QualType T, NestedNameSpecifier *Qualifier,
+                                const CXXRecordDecl *Cls) const;
 
   /// Return a non-unique reference to the type for a variable array of
   /// the specified element type.
diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 3bc0bdff2bdd1..f557555e96e59 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -393,7 +393,9 @@ class ASTNodeTraverser
     Visit(T->getPointeeType());
   }
   void VisitMemberPointerType(const MemberPointerType *T) {
-    Visit(T->getClass());
+    // FIXME: Provide a NestedNameSpecifier visitor.
+    Visit(T->getQualifier()->getAsType());
+    Visit(T->getMostRecentCXXRecordDecl());
     Visit(T->getPointeeType());
   }
   void VisitArrayType(const ArrayType *T) { Visit(T->getElementType()); }
@@ -485,7 +487,8 @@ class ASTNodeTraverser
     }
   }
   void VisitMemberPointerTypeLoc(MemberPointerTypeLoc TL) {
-    Visit(TL.getClassTInfo()->getTypeLoc());
+    // FIXME: Provide NestedNamespecifierLoc visitor.
+    Visit(TL.getQualifierLoc().getTypeLoc());
   }
   void VisitVariableArrayTypeLoc(VariableArrayTypeLoc TL) {
     Visit(TL.getSizeExpr());
diff --git a/clang/include/clang/AST/CanonicalType.h b/clang/include/clang/AST/CanonicalType.h
index 50d1ba1b8f63f..35db68971e029 100644
--- a/clang/include/clang/AST/CanonicalType.h
+++ b/clang/include/clang/AST/CanonicalType.h
@@ -453,7 +453,7 @@ template<>
 struct CanProxyAdaptor<MemberPointerType>
   : public CanProxyBase<MemberPointerType> {
   LLVM_CLANG_CANPROXY_TYPE_ACCESSOR(getPointeeType)
-  LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(const Type *, getClass)
+  LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(NestedNameSpecifier *, getQualifier)
   LLVM_CLANG_CANPROXY_SIMPLE_ACCESSOR(const CXXRecordDecl *,
                                       getMostRecentCXXRecordDecl)
 };
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 87a6c22b35ee8..e93d1d8eab56f 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1004,7 +1004,8 @@ DEF_TRAVERSE_TYPE(RValueReferenceType,
                   { TRY_TO(TraverseType(T->getPointeeType())); })
 
 DEF_TRAVERSE_TYPE(MemberPointerType, {
-  TRY_TO(TraverseType(QualType(T->getClass(), 0)));
+  TRY_TO(TraverseNestedNameSpecifier(T->getQualifier()));
+  TRY_TO(TraverseDecl(T->getMostRecentCXXRecordDecl()));
   TRY_TO(TraverseType(T->getPointeeType()));
 })
 
@@ -1269,10 +1270,10 @@ DEF_TRAVERSE_TYPELOC(RValueReferenceType,
 // We traverse this in the type case as well, but how is it not reached through
 // the pointee type?
 DEF_TRAVERSE_TYPELOC(MemberPointerType, {
-  if (auto *TSI = TL.getClassTInfo())
-    TRY_TO(TraverseTypeLoc(TSI->getTypeLoc()));
+  if (NestedNameSpecifierLoc QL = TL.getQualifierLoc())
+    TRY_TO(TraverseNestedNameSpecifierLoc(QL));
   else
-    TRY_TO(TraverseType(QualType(TL.getTypePtr()->getClass(), 0)));
+    TRY_TO(TraverseNestedNameSpecifier(TL.getTypePtr()->getQualifier()));
   TRY_TO(TraverseTypeLoc(TL.getPointeeLoc()));
 })
 
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 3c942f2ed7486..65756203f2073 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -3527,14 +3527,16 @@ class MemberPointerType : public Type, public llvm::FoldingSetNode {
   QualType PointeeType;
 
   /// The class of which the pointee is a member. Must ultimately be a
-  /// RecordType, but could be a typedef or a template parameter too.
-  const Type *Class;
+  /// CXXRecordType, but could be a typedef or a template parameter too.
+  NestedNameSpecifier *Qualifier;
 
-  MemberPointerType(QualType Pointee, const Type *Cls, QualType CanonicalPtr)
+  MemberPointerType(QualType Pointee, NestedNameSpecifier *Qualifier,
+                    QualType CanonicalPtr)
       : Type(MemberPointer, CanonicalPtr,
-             (Cls->getDependence() & ~TypeDependence::VariablyModified) |
+             (toTypeDependence(Qualifier->getDependence()) &
+              ~TypeDependence::VariablyModified) |
                  Pointee->getDependence()),
-        PointeeType(Pointee), Class(Cls) {}
+        PointeeType(Pointee), Qualifier(Qualifier) {}
 
 public:
   QualType getPointeeType() const { return PointeeType; }
@@ -3551,21 +3553,21 @@ class MemberPointerType : public Type, public llvm::FoldingSetNode {
     return !PointeeType->isFunctionProtoType();
   }
 
-  const Type *getClass() const { return Class; }
+  NestedNameSpecifier *getQualifier() const { return Qualifier; }
   CXXRecordDecl *getMostRecentCXXRecordDecl() const;
 
-  bool isSugared() const { return false; }
-  QualType desugar() const { return QualType(this, 0); }
+  bool isSugared() const;
+  QualType desugar() const {
+    return isSugared() ? getCanonicalTypeInternal() : QualType(this, 0);
+  }
 
   void Profile(llvm::FoldingSetNodeID &ID) {
-    Profile(ID, getPointeeType(), getClass());
+    Profile(ID, getPointeeType(), getQualifier(), getMostRecentCXXRecordDecl());
   }
 
   static void Profile(llvm::FoldingSetNodeID &ID, QualType Pointee,
-                      const Type *Class) {
-    ID.AddPointer(Pointee.getAsOpaquePtr());
-    ID.AddPointer(Class);
-  }
+                      const NestedNameSpecifier *Qualifier,
+                      const CXXRecordDecl *Cls);
 
   static bool classof(const Type *T) {
     return T->getTypeClass() == MemberPointer;
diff --git a/clang/include/clang/AST/TypeLoc.h b/clang/include/clang/AST/TypeLoc.h
index a55a38335ef6a..17ce09fa5da4f 100644
--- a/clang/include/clang/AST/TypeLoc.h
+++ b/clang/include/clang/AST/TypeLoc.h
@@ -139,6 +139,7 @@ class TypeLoc {
   }
 
   /// Get the pointer where source information is stored.
+  // FIXME: This should provide a type-safe interface.
   void *getOpaqueData() const {
     return Data;
   }
@@ -1355,7 +1356,7 @@ class BlockPointerTypeLoc : public PointerLikeTypeLoc<BlockPointerTypeLoc,
 };
 
 struct MemberPointerLocInfo : public PointerLikeLocInfo {
-  TypeSourceInfo *ClassTInfo;
+  void *QualifierData = nullptr;
 };
 
 /// Wrapper for source info for member pointers.
@@ -1371,28 +1372,32 @@ class MemberPointerTypeLoc : public PointerLikeTypeLoc<MemberPointerTypeLoc,
     setSigilLoc(Loc);
   }
 
-  const Type *getClass() const {
-    return getTypePtr()->getClass();
-  }
-
-  TypeSourceInfo *getClassTInfo() const {
-    return getLocalData()->ClassTInfo;
+  NestedNameSpecifierLoc getQualifierLoc() const {
+    return NestedNameSpecifierLoc(getTypePtr()->getQualifier(),
+                                  getLocalData()->QualifierData);
   }
 
-  void setClassTInfo(TypeSourceInfo* TI) {
-    getLocalData()->ClassTInfo = TI;
+  void setQualifierLoc(NestedNameSpecifierLoc QualifierLoc) {
+    assert(QualifierLoc.getNestedNameSpecifier() ==
+               getTypePtr()->getQualifier() &&
+           "Inconsistent nested-name-specifier pointer");
+    getLocalData()->QualifierData = QualifierLoc.getOpaqueData();
   }
 
   void initializeLocal(ASTContext &Context, SourceLocation Loc) {
     setSigilLoc(Loc);
-    setClassTInfo(nullptr);
+    if (auto *Qualifier = getTypePtr()->getQualifier()) {
+      NestedNameSpecifierLocBuilder Builder;
+      Builder.MakeTrivial(Context, Qualifier, Loc);
+      setQualifierLoc(Builder.getWithLocInContext(Context));
+    } else
+      getLocalData()->QualifierData = nullptr;
   }
 
   SourceRange getLocalSourceRange() const {
-    if (TypeSourceInfo *TI = getClassTInfo())
-      return SourceRange(TI->getTypeLoc().getBeginLoc(), getStarLoc());
-    else
-      return SourceRange(getStarLoc());
+    if (NestedNameSpecifierLoc QL = getQualifierLoc())
+      return SourceRange(QL.getBeginLoc(), getStarLoc());
+    return SourceRange(getStarLoc());
   }
 };
 
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 6f1a76bd18fb5..27f71bf5cc62f 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -100,12 +100,15 @@ let Class = MemberPointerType in {
   def : Property<"pointeeType", QualType> {
     let Read = [{ node->getPointeeType() }];
   }
-  def : Property<"baseType", QualType> {
-    let Read = [{ QualType(node->getClass(), 0) }];
+  def : Property<"Qualifier", NestedNameSpecifier> {
+    let Read = [{ node->getQualifier() }];
+  }
+  def : Property<"Cls", DeclRef> {
+    let Read = [{ node->getMostRecentCXXRecordDecl() }];
   }
 
   def : Creator<[{
-    return ctx.getMemberPointerType(pointeeType, baseType.getTypePtr());
+    return ctx.getMemberPointerType(pointeeType, Qualifier, cast_or_null<CXXRecordDecl>(Cls));
   }]>;
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index eed06657536f4..5fc5b0f57c8b5 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6975,10 +6975,8 @@ def err_illegal_decl_mempointer_to_reference : Error<
   "'%0' declared as a member pointer to a reference of type %1">;
 def err_illegal_decl_mempointer_to_void : Error<
   "'%0' declared as a member pointer to void">;
-def err_illegal_decl_mempointer_in_nonclass : Error<
-  "'%0' does not point into a class">;
-def err_mempointer_in_nonclass_type : Error<
-  "member pointer refers into non-class type %0">;
+def err_illegal_decl_mempointer_in_nonclass
+    : Error<"'%0' does not point into a class">;
 def err_reference_to_void : Error<"cannot form a reference to 'void'">;
 def err_nonfunction_block_type : Error<
   "block pointer to non-function type is invalid">;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9724f0def743a..e215f07e2bf0a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1348,6 +1348,12 @@ class Sema final : public SemaBase {
                                     unsigned DiagID, bool ForceCheck = false,
                                     bool ForceUnprivileged = false);
 
+  AccessResult CheckBaseClassAccess(
+      SourceLocation AccessLoc, CXXRecordDecl *Base, CXXRecordDecl *Derived,
+      const CXXBasePath &Path, unsigned DiagID,
+      llvm::function_ref<void(PartialDiagnostic &PD)> SetupPDiag,
+      bool ForceCheck = false, bool ForceUnprivileged = false);
+
   /// Checks access to all the declarations in the given result set.
   void CheckLookupAccess(const LookupResult &R);
 
@@ -14879,8 +14885,9 @@ class Sema final : public SemaBase {
   ///
   /// \returns a member pointer type, if successful, or a NULL type if there was
   /// an error.
-  QualType BuildMemberPointerType(QualType T, QualType Class,
-                                  SourceLocation Loc, DeclarationName Entity);
+  QualType BuildMemberPointerType(QualType T, NestedNameSpecifier *Qualifier,
+                                  CXXRecordDecl *Cls, SourceLocation Loc,
+                                  DeclarationName Entity);
 
   /// Build a block pointer type.
   ///
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 68a02f3bbe1ec..de868ac821745 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3322,7 +3322,8 @@ static void encodeTypeForFunctionPointerAuth(const ASTContext &Ctx,
   case Type::MemberPointer: {
     OS << "M";
     const auto *MPT = T->castAs<MemberPointerType>();
-    encodeTypeForFunctionPointerAuth(Ctx, OS, QualType(MPT->getClass(), 0));
+    encodeTypeForFunctionPointerAuth(
+        Ctx, OS, QualType(MPT->getQualifier()->getAsType(), 0));
     encodeTypeForFunctionPointerAuth(Ctx, OS, MPT->getPointeeType());
     return;
   }
@@ -3511,7 +3512,8 @@ uint16_t ASTContext::getPointerAuthTypeDiscriminator(QualType T) {
         if (PointeeType->castAs<FunctionProtoType>()->getExceptionSpecType() !=
             EST_None) {
           QualType FT = getFunctionTypeWithExceptionSpec(PointeeType, EST_None);
-          T = getMemberPointerType(FT, MPT->getClass());
+          T = getMemberPointerType(FT, MPT->getQualifier(),
+                                   MPT->getMostRecentCXXRecordDecl());
         }
       }
     std::unique_ptr<MangleContext> MC(createMangleContext());
@@ -4025,32 +4027,50 @@ QualType ASTContext::getRValueReferenceType(QualType T) const {
   return QualType(New, 0);
 }
 
-/// getMemberPointerType - Return the uniqued reference to the type for a
-/// member pointer to the specified type, in the specified class.
-QualType ASTContext::getMemberPointerType(QualType T, const Type *Cls) const {
+QualType ASTContext::getMemberPointerType(QualType T,
+                                          NestedNameSpecifier *Qualifier,
+                                          const CXXRecordDecl *Cls) const {
+  if (!Qualifier) {
+    assert(Cls && "At least one of Qualifier or Cls must be provided");
+    Qualifier = NestedNameSpecifier::Create(*this, /*Prefix=*/nullptr,
+                                            /*Template=*/false,
+                                            getTypeDeclType(Cls).getTypePtr());
+  } else if (!Cls) {
+    Cls = Qualifier->getAsRecordDecl();
+  }
   // Unique pointers, to guarantee there is only one pointer of a particular
   // structure.
   llvm::FoldingSetNodeID ID;
-  MemberPointerType::Profile(ID, T, Cls);
+  MemberPointerType::Profile(ID, T, Qualifier, Cls);
 
   void *InsertPos = nullptr;
   if (MemberPointerType *PT =
       MemberPointerTypes.FindNodeOrInsertPos(ID, InsertPos))
     return QualType(PT, 0);
 
+  NestedNameSpecifier *CanonicalQualifier = [&] {
+    if (!Cls)
+      return getCanonicalNestedNameSpecifier(Qualifier);
+    NestedNameSpecifier *R = NestedNameSpecifier::Create(
+        *this, /*Prefix=*/nullptr, /*Template=*/false,
+        Cls->getCanonicalDecl()->getTypeForDecl());
+    assert(R == getCanonicalNestedNameSpecifier(R));
+    return R;
+  }();
   // If the pointee or class type isn't canonical, this won't be a canonical
   // type either, so fill in the canonical type field.
   QualType Canonical;
-  if (!T.isCanonical() || !Cls->isCanonicalUnqualified()) {
-    Canonical = getMemberPointerType(getCanonicalType(T),getCanonicalType(Cls));
-
+  if (!T.isCanonical() || Qualifier != CanonicalQualifier) {
+    Canonical =
+        getMemberPointerType(getCanonicalType(T), CanonicalQualifier, Cls);
+    assert(!cast<MemberPointerType>(Canonical)->isSugared());
     // Get the new insert position for the node we care about.
-    MemberPointerType *NewIP =
-      MemberPointerTypes.FindNodeOrInsertPos(ID, InsertPos);
-    assert(!NewIP && "Shouldn't be in the map!"); (void)NewIP;
+    [[maybe_unused]] MemberPointerType *NewIP =
+        MemberPointerTypes.FindNodeOrInsertPos(ID, InsertPos);
+    assert(!NewIP && "Shouldn't be in the map!");
   }
   auto *New = new (*this, alignof(MemberPointerType))
-      MemberPointerType(T, Cls, Canonical);
+      MemberPointerType(T, Qualifier, Canonical);
   Types.push_back(New);
   MemberPointerTypes.InsertNode(New, InsertPos);
   return QualType(New, 0);
@@ -6812,11 +6832,16 @@ bool ASTContext::UnwrapSimilarTypes(QualType &T1, QualType &T2,
     return true;
   }
 
-  const auto *T1MPType = T1->getAs<MemberPointerType>();
-  const auto *T2MPType = T2->getAs<MemberPointerType>();
-  if (T1MPType && T2MPType &&
-      hasSameUnqualifiedType(QualType(T1MPType->getClass(), 0),
-                             QualType(T2MPType->getClass(), 0))) {
+  if (const auto *T1MPType = T1->getAs<MemberPointerType>(),
+      *T2MPType = T2->getAs<MemberPointerType>();
+      T1MPType && T2MPType) {
+    if (auto *RD1 = T1MPType->getMostRecentCXXRecordDecl(),
+        *RD2 = T2MPType->getMostRecentCXXRecordDecl();
+        RD1 != RD2 && RD1->getCanonicalDecl() != RD2->getCanonicalDecl())
+      return false;
+    if (getCanonicalNestedNameSpecifier(T1MPType->getQualifier()) !=
+        getCanonicalNestedNameSpecifier(T2MPType->getQualifier()))
+      return false;
     T1 = T1MPType->getPointeeType();
     T2 = T2MPType->getPointeeType();
     return true;
@@ -13857,11 +13882,12 @@ static QualType getCommonNonSugarTypeNode(ASTContext &Ctx, const Type *X,
   case Type::MemberPointer: {
     const auto *PX = cast<MemberPointerType>(X),
                *PY = cast<MemberPointerType>(Y);
+    assert(declaresSameEntity(PX->getMostRecentCXXRecordDecl(),
+                              PY->getMostRecentCXXRecordDecl()));
     return Ctx.getMemberPointerType(
         getCommonPointeeType(Ctx, PX, PY),
-        Ctx.getCommonSugaredType(QualType(PX->getClass(), 0),
-                                 ...
[truncated]

Copy link
Member

@Michael137 Michael137 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLDB changes LGTM

@mizvekov mizvekov merged commit 14f7bd6 into main Mar 21, 2025
26 checks passed
@mizvekov mizvekov deleted the users/mizvekov/clang-member-pointer-qualifier branch March 21, 2025 16:20
@mstorsjo
Copy link
Member

This change broke building Qt (tested with 6.8), ending up with errors like this:

qtbase/src/corelib/kernel/qcoreapplication.cpp:2946:78: error: 'this' cannot be used in a static member function declaration
 2946 |             : slotObject(std::move(slotObject)), context(context ? context : this)
      |                                                                              ^
qtbase/src/corelib/kernel/qobjectdefs_impl.h:405:23: note: in instantiation of template class 'QtPrivate::CallableHelper<void (PermissionReceiver::*)(const QPermission &)>' requested here
  405 |     struct Callable : CallableHelper<Func, Args...>::Type
      |                       ^
qtbase/src/corelib/kernel/qobjectdefs.h:437:38: note: in instantiation of template class 'QtPrivate::Callable<void (PermissionReceiver::*)(const QPermission &)>' requested here
  437 |                  typename QtPrivate::Callable<Func>::ReturnType *ret)
      |                                      ^
qtbase/src/corelib/kernel/qcoreapplication.cpp:2980:13: note: while substituting deduced template arguments into function template 'invokeMethod' [with Func = void (PermissionReceiver::*)(const QPermission &)]
 2980 |             QMetaObject::invokeMethod(receiver,
      |             ^

This issue also persists on the latest git main as of right now.

I've reduced the issue down to the following small reproducer:

template <typename Func> struct CallableHelper {
  static auto Resolve() -> Func;
};
struct QIODevice {
  void d_func() { d_ptr; }
  int d_ptr;
};
struct Callable : CallableHelper<void (QIODevice::*)()> {};

Reproducible with e.g. clang -target x86_64-linux-gnu -c repro.cpp (presumably the same way for any target).

mizvekov added a commit that referenced this pull request Mar 22, 2025
For that visitor, it is not expected that a type can traverse into
a declaration. This makes the MemberPointer visitor conform to
that rule.

This turns the base class visitor into a CXXRecordType visitor,
and only performs that visit in case it points to something different
than the qualifier does.

Fixes a regression introduced in #132401
As this fixes a regression which has not been released, there are no release notes.
mizvekov added a commit that referenced this pull request Mar 22, 2025
For that visitor, it is not expected that a type can traverse into
a declaration. This makes the MemberPointer visitor conform to
that rule.

This turns the base class visitor into a CXXRecordType visitor,
and only performs that visit in case it points to something different
than the qualifier does.

Fixes a regression reported here: #132401 (comment)
As this fixes a regression which has not been released, there are no release notes.
@mizvekov
Copy link
Contributor Author

Thanks, for the report, will be fixed by #132551

@mstorsjo
Copy link
Member

Thanks, for the report, will be fixed by #132551

Thanks for the quick fix, I can confirm that Qt builds fine for me again!

@rupprecht
Copy link
Collaborator

Still seeing a crash even with the latest fix applied

assertion failed at clang/include/clang/AST/Type.h:945 in const ExtQualsTypeCommonBase *clang::QualType::getCommonPtr() const: !isNull() && "Cannot retrieve a NULL type pointer"
*** Check failure stack trace: ***
    @     0x55e0dffed1f5  DeduceTemplateArgumentsByTypeMatch()
    @     0x55e0dffecc3e  DeduceTemplateArgumentsByTypeMatch()
    @     0x55e0dfff79f1  DeduceTemplateArguments()
    @     0x55e0dffe3ce9  DeduceTemplateArguments()
    @     0x55e0dffe47c0  clang::Sema::DeduceTemplateArguments()
    @     0x55e0e008943b  clang::Sema::InstantiateClassTemplateSpecialization()
    @     0x55e0e018b8a3  llvm::function_ref<>::callback_fn<>()
    @     0x55e0e0b04caf  clang::StackExhaustionHandler::runWithSufficientStackSpace()
    @     0x55e0e01741a8  clang::Sema::RequireCompleteTypeImpl()
    @     0x55e0e01739b5  clang::Sema::RequireCompleteType()
    @     0x55e0df8e6914  clang::Sema::RequireCompleteDeclContext()
    @     0x55e0dfd44fa9  clang::Sema::LookupParsedName()
    @     0x55e0dfb3b113  clang::Sema::BuildQualifiedDeclarationNameExpr()
    @     0x55e0e00d83e8  clang::TreeTransform<>::TransformDependentScopeDeclRefExpr()
    @     0x55e0e00d2529  clang::TreeTransform<>::TransformCallExpr()
    @     0x55e0e00ef218  clang::TreeTransform<>::TransformReturnStmt()
    @     0x55e0e00d9018  clang::TreeTransform<>::TransformCompoundStmt()
    @     0x55e0e008aa0a  clang::Sema::SubstStmt()
    @     0x55e0e011f3ce  clang::Sema::InstantiateFunctionDefinition()
    @     0x55e0e0b04caf  clang::StackExhaustionHandler::runWithSufficientStackSpace()
    @     0x55e0dffed9d6  clang::Sema::DeduceReturnType()
    @     0x55e0dfb2bac5  clang::Sema::DiagnoseUseOfDecl()
    @     0x55e0dfeb8361  FinishOverloadedCallExpr()
    @     0x55e0dfeb8274  clang::Sema::BuildOverloadedCallExpr()
    @     0x55e0dfb322ba  clang::Sema::BuildCallExpr()
    @     0x55e0dfb492b8  clang::Sema::ActOnCallExpr()
    @     0x55e0e00d2704  clang::TreeTransform<>::TransformCallExpr()
    @     0x55e0e008d052  clang::TreeTransform<>::TransformExprs()
    @     0x55e0e00d258c  clang::TreeTransform<>::TransformCallExpr()
    @     0x55e0e008d052  clang::TreeTransform<>::TransformExprs()
    @     0x55e0e00d258c  clang::TreeTransform<>::TransformCallExpr()
    @     0x55e0e00ef218  clang::TreeTransform<>::TransformReturnStmt()
    @     0x55e0e00d9018  clang::TreeTransform<>::TransformCompoundStmt()
    @     0x55e0e008aa0a  clang::Sema::SubstStmt()
    @     0x55e0e011f3ce  clang::Sema::InstantiateFunctionDefinition()
    @     0x55e0e01227ba  clang::Sema::PerformPendingInstantiations()
    @     0x55e0df88aaac  clang::Sema::ActOnEndOfTranslationUnitFragment()
    @     0x55e0df88b22b  clang::Sema::ActOnEndOfTranslationUnit()
    @     0x55e0df5a1e5a  clang::Parser::ParseTopLevelDecl()
    @     0x55e0df59e05e  clang::ParseAST()
...

In the meantime, I'll try reducing this

@mizvekov
Copy link
Contributor Author

Thanks, that would be appreciated.

@rupprecht
Copy link
Collaborator

Sorry for not getting back sooner; had to let cvise run overnight. Repro w/ clang++ -std=c++20:

template <typename>
struct RunCallImpl;

template <typename Derived>
struct RunCallImpl<int (Derived::Info::*)(Derived *)> {};

template <typename d>
void RunCall(d) {
    RunCallImpl<d>();
}

struct Filter {
    virtual void MakeCall();
    virtual ~Filter() = default;
};

template <typename Derived>
struct ImplementFilter : Filter {
    void MakeCall() { RunCall(&Derived::Info::OnStuffHandler); }
};

struct FoobarFilter : ImplementFilter<FoobarFilter> {
    struct Info {
        int OnStuffHandler(FoobarFilter *);
    };
};

Live link: https://godbolt.org/z/oboYrKM8G

@mizvekov
Copy link
Contributor Author

We are now seeing non-determinism in .pcm files that root-causes to this commit.

@mizvekov - might it be something obvious, like pointer-keyed containers or similar?

I don't see anything obvious. The only parts of the patch which touch anything similar to pointer-keyed containers are the type properties cache changes, like the linkage computation.

mizvekov added a commit that referenced this pull request Apr 11, 2025
This fixes a problem originally reported here:
#132401 (comment)

This makes sure we only serialize the class declaration when its strictly
needed, and canonicalizes it, so it doesn't change in ways that don't round
trip.

There are no release notes, since this regression was never released.
@mizvekov
Copy link
Contributor Author

@eaeltsin speculative fix here, but can you try with this patch? #135434

@eaeltsin
Copy link
Contributor

eaeltsin commented Apr 11, 2025

(EDITED!)

Memory Sanitizer complains about initialized value here - IsInvalid in TranslateSourceLocation

Will try the patch now.

@eaeltsin
Copy link
Contributor

@mizvekov - no, the patch doesn't help, or I did something wrong.

@mizvekov
Copy link
Contributor Author

Okay, if the problem is an uninitialized source location somewhere, then that patch doesn't help at all.

@mizvekov
Copy link
Contributor Author

Do you have a backtrace of that uninitialized read?

@eaeltsin
Copy link
Contributor

Well, to be honest, I'm not completely sure these are the same problems, these are just sanitizer finding for the same compilation.

Here is the MSan finding. Please note my source is somewhat behind the head, so locations might be off a bit

@mizvekov
Copy link
Contributor Author

mizvekov commented Apr 11, 2025

Thanks for that stack trace, could be unrelated to this, but that still helped find an issue: #135450

@eaeltsin
Copy link
Contributor

Patched #135450 but still see the same MSan finding on my compilation - uninitialized value while checking location validity in TranslateSourceLocation called from VisitSubstNonTypeTemplateParmExpr.

Sorry but I'm not yet sure how to strip the reproducer from internal code so cannot provide it :|

@eaeltsin
Copy link
Contributor

Using library with assertions, I'm seeing out-of-bounds source location read called from ASTStmtReader::VisitSubstNonTypeTemplateParmExpr - trace

@mizvekov
Copy link
Contributor Author

mizvekov commented Apr 14, 2025

One thing that looks fishy, but that is even a different node, is this early return on VisitSubstNonTypeTemplateParmPackExpr in ASTReaderStmt.

  if (ArgPack.getKind() != TemplateArgument::Pack)
    return;

This looks impossible to hit, because getArgumentPack calls a constructor which can only return Packs.

Can you turn that into an assert and try again?

assert(ArgPack.getKind() == TemplateArgument::Pack);

@eaeltsin
Copy link
Contributor

Didn't fire so far.

Though this is non-deterministic, I might be (un)lucky.

@eaeltsin
Copy link
Contributor

Comparing bcanalyzer --dump outputs for non-deterministic pcms, I see a lot of op values that differ by 1.

I wonder if this might be something like the mismatch of Read Write UnsignedOrNone vs unsigned ...

@mizvekov
Copy link
Contributor Author

Could it be you are hitting an overflow/wrap around perhaps?

Some of these nodes store the unsignedOrNone representation in a bitfield, but that's still 15 bits.

@mizvekov
Copy link
Contributor Author

In one of these changes we did bump one of these bit fields down to 15 bits, starting from 16.

@eaeltsin
Copy link
Contributor

No, the problem seems to be in serialization/deserialization, there must be some mismatch between ASTStmtWriter::VisitSubstNonTypeTemplateParmExpr and ASTStmtReader::VisitSubstNonTypeTemplateParmExpr.

More precisely, I'm seeing that ASTStmtWriter::VisitSubstNonTypeTemplateParmExpr outputs a record of 5 elements, while ASTStmtReader::VisitSubstNonTypeTemplateParmExpr gets a record of 4 elements as input, and thus triggers assertion when reading the source location.

@emaxx-google
Copy link
Contributor

Hello, the minimized reproducer for the determinism issue is there: https://pastebin.com/6aL6rmBe . To build it, unpack it into separate files (via split-file), then run CLANG=path/to/clang make. The nondeterminism can be checked by looping make clean + make + md5sum problem.pcm - typically a different hash pops up after a few dozens of iterations.

@mizvekov
Copy link
Contributor Author

@emaxx-google thanks for the reproducer.

I will be off to C++Now soon, so it's unlikely I will have time to take a look at
that in the next two weeks, sorry about that.

@gulfemsavrun
Copy link
Contributor

gulfemsavrun commented Apr 29, 2025

We started seeing the following issue, and bisected to this commit.

$ clang++ -c test.cpp
test.cpp:8:12: error: no matching function for call to 'GetFieldChecked'
    8 |     return GetFieldChecked(b, &std::remove_reference<decltype(b)>::type::has_total);
      |            ^~~~~~~~~~~~~~~
test.cpp:6:5: note: candidate template ignored: deduced conflicting types for parameter 'Table' ('Bar' vs. 'const Bar')
    6 | int GetFieldChecked(const Table&, bool (Table::*)() const);
      |     ^
1 error generate

Here's a small test case:

#include <type_traits>
struct Bar {
  bool has_total() const;
};
template <typename Table>
int GetFieldChecked(const Table&, bool (Table::*)() const);
int foo(const Bar& b) { 
    return GetFieldChecked(b, &std::remove_reference<decltype(b)>::type::has_total);
}

If we just compile with this commit, we ran into this issue and it is blocking us to roll Clang in our project.

emaxx-google added a commit that referenced this pull request May 5, 2025
This commit fixes the nondeterminism issue in C++ header module enabled builds which were observed after
#132401.

The issue was related to the fact that the hash set operation in MemberPointerType::Profile() was triggering getMostRecentDecl(). As the latter may trigger the loading of new entities from the external AST source, this
was presumably causing reentrant modification of data structure or some other issue that affects
compiler's output in a nondeterministic way (likely depending on specific values hashes/pointers have).

The change should otherwise be a no-op, because whether we take a "most recent" or "any" Decl shouldn't
matter since `getCanonicalDecl()` is called on it anyway inside `MemberPointerType::Profile()`.

We haven't been able to come up with a deterministic regression test for this fix.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This commit fixes the nondeterminism issue in C++ header module enabled builds which were observed after
llvm#132401.

The issue was related to the fact that the hash set operation in MemberPointerType::Profile() was triggering getMostRecentDecl(). As the latter may trigger the loading of new entities from the external AST source, this
was presumably causing reentrant modification of data structure or some other issue that affects
compiler's output in a nondeterministic way (likely depending on specific values hashes/pointers have).

The change should otherwise be a no-op, because whether we take a "most recent" or "any" Decl shouldn't
matter since `getCanonicalDecl()` is called on it anyway inside `MemberPointerType::Profile()`.

We haven't been able to come up with a deterministic regression test for this fix.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This commit fixes the nondeterminism issue in C++ header module enabled builds which were observed after
llvm#132401.

The issue was related to the fact that the hash set operation in MemberPointerType::Profile() was triggering getMostRecentDecl(). As the latter may trigger the loading of new entities from the external AST source, this
was presumably causing reentrant modification of data structure or some other issue that affects
compiler's output in a nondeterministic way (likely depending on specific values hashes/pointers have).

The change should otherwise be a no-op, because whether we take a "most recent" or "any" Decl shouldn't
matter since `getCanonicalDecl()` is called on it anyway inside `MemberPointerType::Profile()`.

We haven't been able to come up with a deterministic regression test for this fix.
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
This commit fixes the nondeterminism issue in C++ header module enabled builds which were observed after
llvm#132401.

The issue was related to the fact that the hash set operation in MemberPointerType::Profile() was triggering getMostRecentDecl(). As the latter may trigger the loading of new entities from the external AST source, this
was presumably causing reentrant modification of data structure or some other issue that affects
compiler's output in a nondeterministic way (likely depending on specific values hashes/pointers have).

The change should otherwise be a no-op, because whether we take a "most recent" or "any" Decl shouldn't
matter since `getCanonicalDecl()` is called on it anyway inside `MemberPointerType::Profile()`.

We haven't been able to come up with a deterministic regression test for this fix.
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request May 6, 2025
This commit fixes the nondeterminism issue in C++ header module enabled builds which were observed after
llvm/llvm-project#132401.

The issue was related to the fact that the hash set operation in MemberPointerType::Profile() was triggering getMostRecentDecl(). As the latter may trigger the loading of new entities from the external AST source, this
was presumably causing reentrant modification of data structure or some other issue that affects
compiler's output in a nondeterministic way (likely depending on specific values hashes/pointers have).

The change should otherwise be a no-op, because whether we take a "most recent" or "any" Decl shouldn't
matter since `getCanonicalDecl()` is called on it anyway inside `MemberPointerType::Profile()`.

We haven't been able to come up with a deterministic regression test for this fix.
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
This commit fixes the nondeterminism issue in C++ header module enabled builds which were observed after
llvm#132401.

The issue was related to the fact that the hash set operation in MemberPointerType::Profile() was triggering getMostRecentDecl(). As the latter may trigger the loading of new entities from the external AST source, this
was presumably causing reentrant modification of data structure or some other issue that affects
compiler's output in a nondeterministic way (likely depending on specific values hashes/pointers have).

The change should otherwise be a no-op, because whether we take a "most recent" or "any" Decl shouldn't
matter since `getCanonicalDecl()` is called on it anyway inside `MemberPointerType::Profile()`.

We haven't been able to come up with a deterministic regression test for this fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:as-a-library libclang and C++ API clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:openmp OpenMP related changes to Clang clang:static analyzer clang Clang issues not falling into any other category clang-tidy clang-tools-extra clangd lldb
Projects
None yet
Development

Successfully merging this pull request may close these issues.