Skip to content
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

[Clang] Fix Microsoft ABI inheritance model when member pointer is used in a base specifier #91990

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

MitalAshok
Copy link
Contributor

@MitalAshok MitalAshok commented May 13, 2024

Fix CXXRecordDecl::isParsingBaseSpecifiers so that it is true while parsing base specifiers instead of directly after they have been parsed.

-fcomplete-member-pointers now issues a diagnostic when a member pointer is used in a base specifier.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels May 13, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented May 13, 2024

@llvm/pr-subscribers-clang

Author: Mital Ashok (MitalAshok)

Changes

Fix CXXRecordDecl::isParsingBaseSpecifiers so that it is true while parsing base specifiers instead of directly after they have been parsed.

-fcomplete-member-pointers now issues a diagnostic when a member pointer is used in a base specifier.

-fcomplete-member-pointers has also been relaxed to not issue a diagnostic for incomplete classes with an explicit __{single|multiple|virtual}_inheritance attribute, whose completeness would not affect the representation of pointer-to-member objects.


Full diff: https://github.com/llvm/llvm-project/pull/91990.diff

9 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+4)
  • (modified) clang/include/clang/AST/DeclCXX.h (+3-2)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/lib/AST/DeclCXX.cpp (+1-5)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+22)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (-3)
  • (modified) clang/lib/Sema/SemaType.cpp (+14-9)
  • (modified) clang/test/SemaCXX/complete-member-pointers.cpp (+23-1)
  • (modified) clang/test/SemaCXX/member-pointer-ms.cpp (+8)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4702b8c10cdbb..054332c2cee4e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -79,6 +79,10 @@ ABI Changes in This Version
 - Fixed Microsoft calling convention when returning classes that have a deleted
   copy assignment operator. Such a class should be returned indirectly.
 
+- Fixed Microsoft layout of pointer-to-members of classes when the layout is needed
+  directly or indirectly by the base classes of a class. These should use the most
+  general unspecified inheritance layout. Also affects -fcomplete-member-pointers.
+
 AST Dumping Potentially Breaking Changes
 ----------------------------------------
 
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index fb52ac804849d..45b3b47ed51c5 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -297,7 +297,8 @@ class CXXRecordDecl : public RecordDecl {
     LLVM_PREFERRED_TYPE(bool)
     unsigned IsLambda : 1;
 
-    /// Whether we are currently parsing base specifiers.
+    /// Whether we are currently parsing base specifiers; the
+    /// colon has been consumed but the beginning left brace hasn't.
     LLVM_PREFERRED_TYPE(bool)
     unsigned IsParsingBaseSpecifiers : 1;
 
@@ -598,7 +599,7 @@ class CXXRecordDecl : public RecordDecl {
     return !hasDefinition() || !isDynamicClass() || hasAnyDependentBases();
   }
 
-  void setIsParsingBaseSpecifiers() { data().IsParsingBaseSpecifiers = true; }
+  void setIsParsingBaseSpecifiers(bool to = true) { data().IsParsingBaseSpecifiers = to; }
 
   bool isParsingBaseSpecifiers() const {
     return data().IsParsingBaseSpecifiers;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9e82130c93609..a9f9f02651cff 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8003,6 +8003,8 @@ def err_bad_memptr_rhs : Error<
 def err_bad_memptr_lhs : Error<
   "left hand operand to %0 must be a %select{|pointer to }1class "
   "compatible with the right hand operand, but is %2">;
+def note_memptr_incomplete_until_bases : Note<
+  "this will affect the ABI of the member pointer until the bases have been specified">;
 def err_memptr_incomplete : Error<
   "member pointer has incomplete base type %0">;
 def warn_exception_caught_by_earlier_handler : Warning<
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 75c441293d62e..7f20a47e6a054 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -474,10 +474,8 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
   if (data().IsStandardLayout && NumBases > 1 && hasRepeatedBaseClass(this))
     data().IsStandardLayout = false;
 
-  if (VBases.empty()) {
-    data().IsParsingBaseSpecifiers = false;
+  if (VBases.empty())
     return;
-  }
 
   // Create base specifier for any direct or indirect virtual bases.
   data().VBases = new (C) CXXBaseSpecifier[VBases.size()];
@@ -488,8 +486,6 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
       addedClassSubobject(Type->getAsCXXRecordDecl());
     data().getVBases()[I] = *VBases[I];
   }
-
-  data().IsParsingBaseSpecifiers = false;
 }
 
 unsigned CXXRecordDecl::getODRHash() const {
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 65ddebca49bc6..681b9803142c2 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2334,6 +2334,26 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
   }
 }
 
+namespace {
+  struct ParsingBaseSpecifiersGuard {
+    CXXRecordDecl *RD = nullptr;
+    ParsingBaseSpecifiersGuard(Sema& S, Decl *D) {
+      if (D) {
+        S.AdjustDeclIfTemplate(D);
+        RD = cast<CXXRecordDecl>(D);
+        assert(!RD->isParsingBaseSpecifiers() && "Recursively parsing base specifiers of the same class?");
+        RD->setIsParsingBaseSpecifiers(true);
+      }
+    }
+    ~ParsingBaseSpecifiersGuard() {
+      if (RD) {
+        assert(RD->isParsingBaseSpecifiers() && "Stopped parsing base specifiers before exiting ParseBaseClause?");
+        RD->setIsParsingBaseSpecifiers(false);
+      }
+    }
+  };
+}
+
 /// ParseBaseClause - Parse the base-clause of a C++ class [C++ class.derived].
 ///
 ///       base-clause : [C++ class.derived]
@@ -2345,6 +2365,8 @@ void Parser::ParseBaseClause(Decl *ClassDecl) {
   assert(Tok.is(tok::colon) && "Not a base clause");
   ConsumeToken();
 
+  ParsingBaseSpecifiersGuard Guard(Actions, ClassDecl);
+
   // Build up an array of parsed base specifiers.
   SmallVector<CXXBaseSpecifier *, 8> BaseInfo;
 
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 53238d355ea09..0bf7208605213 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -2862,9 +2862,6 @@ BaseResult Sema::ActOnBaseSpecifier(Decl *classdecl, SourceRange SpecifierRange,
   if (!Class)
     return true;
 
-  // We haven't yet attached the base specifiers.
-  Class->setIsParsingBaseSpecifiers();
-
   // We do not support any C++11 attributes on base-specifiers yet.
   // Diagnose any attributes we see.
   for (const ParsedAttr &AL : Attributes) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index fddc3545ecb61..0a801f1797d23 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9463,17 +9463,22 @@ bool Sema::RequireCompleteTypeImpl(SourceLocation Loc, QualType T,
 
   if (const MemberPointerType *MPTy = T->getAs<MemberPointerType>()) {
     if (!MPTy->getClass()->isDependentType()) {
-      if (getLangOpts().CompleteMemberPointers &&
-          !MPTy->getClass()->getAsCXXRecordDecl()->isBeingDefined() &&
-          RequireCompleteType(Loc, QualType(MPTy->getClass(), 0), Kind,
-                              diag::err_memptr_incomplete))
-        return true;
-
       // We lock in the inheritance model once somebody has asked us to ensure
       // that a pointer-to-member type is complete.
-      if (Context.getTargetInfo().getCXXABI().isMicrosoft()) {
-        (void)isCompleteType(Loc, QualType(MPTy->getClass(), 0));
-        assignInheritanceModel(*this, MPTy->getMostRecentCXXRecordDecl());
+      if (Context.getTargetInfo().getCXXABI().isMicrosoft() || getLangOpts().CompleteMemberPointers) {
+        QualType Class = QualType(MPTy->getClass(), 0);
+        (void)isCompleteType(Loc, Class);
+        CXXRecordDecl *RD = MPTy->getMostRecentCXXRecordDecl();
+        assignInheritanceModel(*this, RD);
+
+        if (getLangOpts().CompleteMemberPointers && RD->getMSInheritanceModel() == MSInheritanceModel::Unspecified) {
+          if (RD->hasDefinition() && RD->isParsingBaseSpecifiers()) {
+            Diag(Loc, diag::err_memptr_incomplete) << Class;
+            Diag(RD->getDefinition()->getLocation(), diag::note_memptr_incomplete_until_bases);
+          } else if (!RequireCompleteType(Loc, Class, diag::err_memptr_incomplete))
+            Diag(Loc, diag::err_memptr_incomplete) << Class;
+          return true;
+        }
       }
     }
   }
diff --git a/clang/test/SemaCXX/complete-member-pointers.cpp b/clang/test/SemaCXX/complete-member-pointers.cpp
index 942bb703a9223..41c9b98438c1a 100644
--- a/clang/test/SemaCXX/complete-member-pointers.cpp
+++ b/clang/test/SemaCXX/complete-member-pointers.cpp
@@ -1,4 +1,6 @@
-// RUN: %clang_cc1 -verify -fsyntax-only -fcomplete-member-pointers %s
+// RUN: %clang_cc1 -verify -fsyntax-only -fc++-abi=itanium -fms-extensions -fcomplete-member-pointers %s
+// RUN: %clang_cc1 -verify -fsyntax-only -triple=x86_64-unknown-win32 -fc++-abi=microsoft -fms-compatibility -fcomplete-member-pointers %s
+// RUN: %clang_cc1 -verify -fsyntax-only -triple=x86_64-unknown-win32 -fc++-abi=itanium -fms-compatibility -fcomplete-member-pointers %s
 
 struct S; // expected-note {{forward declaration of 'S'}}
 typedef int S::*t;
@@ -13,3 +15,23 @@ template <typename T>
 struct S3 {
   int T::*foo;
 };
+
+struct __single_inheritance S4;
+int S4::* baz;
+
+template<int I> struct Base {};
+struct __single_inheritance S5 : Base<sizeof(int S5::*)> {};
+struct
+S6  // #S6
+:
+Base<sizeof(int S6::*)>
+// expected-error@-1 {{member pointer has incomplete base type 'S6'}}
+//   expected-note@#S6 {{this will affect the ABI of the member pointer until the bases have been specified}}
+{
+};
+
+template<typename T> struct S7 {};
+int S7<void>::* qux;  // #qux
+template<> struct S7<void> {};
+// expected-error@-1 {{explicit specialization of 'S7<void>' after instantiation}}
+//   expected-note@#qux {{implicit instantiation first required here}}
diff --git a/clang/test/SemaCXX/member-pointer-ms.cpp b/clang/test/SemaCXX/member-pointer-ms.cpp
index 3271ff0c623a2..efbab8b28498f 100644
--- a/clang/test/SemaCXX/member-pointer-ms.cpp
+++ b/clang/test/SemaCXX/member-pointer-ms.cpp
@@ -215,6 +215,14 @@ struct NewUnspecified;
 SingleTemplate<void (IncSingle::*)()> tmpl_single;
 UnspecTemplate<void (NewUnspecified::*)()> tmpl_unspec;
 
+// Member pointers used in base specifiers force an unspecified inheritance model
+struct MemPtrInBase : UnspecTemplate<void (MemPtrInBase::*)()> {};
+#ifdef VMB
+struct __single_inheritance SpecifiedMemPtrInBase;
+struct SpecifiedMemPtrInBase : SingleTemplate<void (SpecifiedMemPtrInBase::*)()> {};
+struct __single_inheritance SpecifiedMemPtrInBase2 : SingleTemplate<void (SpecifiedMemPtrInBase2::*)()> {};
+#endif
+
 struct NewUnspecified { };
 
 static_assert(sizeof(void (NewUnspecified::*)()) == kUnspecifiedFunctionSize, "");

Copy link

github-actions bot commented May 13, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@MitalAshok
Copy link
Contributor Author

Example of the incompatibility: https://godbolt.org/z/Mn1T57WGb

struct unspecified_inheritance;
template<int I>
struct X {
    static_assert(I == sizeof(int unspecified_inheritance::*), "");
};

struct Y : X<sizeof(int Y::*)> {};

Currently, the sizeof(int Y::*) locks down the inheritance model of Y as single, when MSVC marks it as unspecified.

It was the intent that it should have been unspecified:

if (!hasDefinition() || isParsingBaseSpecifiers())
return MSInheritanceModel::Unspecified;

But isParsingBaseSpecifiers() was set after parsing instead of before.

…ed in a base specifier

Fix CXXRecordDecl::isParsingBaseSpecifiers so that it is true while parsing base specifiers instead of directly after they have been parsed.

-fcomplete-member-pointers now issues a diagnostic when a member pointer is used in a base specifier.

-fcomplete-member-pointers has also been relaxed to not issue a diagnostic for incomplete classes with an explicit __{single|multiple|virtual}_inheritance attribute, whose completeness would not affect the representation of pointer-to-member objects.
@@ -598,7 +599,9 @@ class CXXRecordDecl : public RecordDecl {
return !hasDefinition() || !isDynamicClass() || hasAnyDependentBases();
}

void setIsParsingBaseSpecifiers() { data().IsParsingBaseSpecifiers = true; }
void setIsParsingBaseSpecifiers(bool to = true) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
void setIsParsingBaseSpecifiers(bool to = true) {
void setIsParsingBaseSpecifiers(bool To = true) {

I am surprised clang-format didn't complain about starting with a lowercase for variables.
I can't think of a better name. I respond here if I do :).

… the Microsoft ABI"

This reverts commit f343670.

I'm planning on moving this to a separate pr which substantially changes -fcomplete-member-pointers
I'm planning on moving this to a separate pr which substantially changes -fcomplete-member-pointers
@MitalAshok MitalAshok requested a review from Endilll as a code owner July 6, 2024 09:59
Copy link
Contributor

@Endilll Endilll left a comment

Choose a reason for hiding this comment

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

Sema.h changes look good.

Copy link
Contributor

@MaxEW707 MaxEW707 left a comment

Choose a reason for hiding this comment

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

LGTM.

@MitalAshok I noticed in your commit messages you mentioned reworking -fcomplete-member-pointers. Will that include fixing clang incorrectly warning when an inheritance model is explicitly specified? Godbolt for reference: https://godbolt.org/z/rYaWz6sra.

@MitalAshok
Copy link
Contributor Author

Will that include fixing clang incorrectly warning when an inheritance model is explicitly specified?

Yes, I have a WIP of it here #98010 where that doesn't get a warning (https://github.com/llvm/llvm-project/pull/98010/files#diff-bec1ae40e5793adbd61e7a9dc41a4715a06e27888a384c0752bb6b4b09a1075fR20-R21)

@MaxEW707 MaxEW707 requested a review from Sirraide July 9, 2024 02:17
@MaxEW707
Copy link
Contributor

@Endilll Wondering if this PR gets the green check from you.

I would like to get this merged before the clang-19 branch is created and just want to get the green light from a more senior member of the community before merging. Thanks :).

@cor3ntin
Copy link
Contributor

@AaronBallman

@AaronBallman AaronBallman requested a review from rnk July 17, 2024 16:54
Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

This generally LGTM but I'd love to hear from @rnk or @zmodem as they are quite knowledgeable about the MS ABI in general.

Comment on lines +9020 to +9021
if (RD->isBeingDefined()) {
if (RD->isParsingBaseSpecifiers()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Combine these into a single statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really only want to call RequireCompleteTypein the else branch when !RD->isBeingDefined() (when called while RD->isParsingBaseSpecifiers(), there's an extra note 'definition of "[class name]" is incomplete until the closing "}"', which is misleading since for CompleteMemberPointers purposes it's complete after the bases are parsed)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm struggling to understand why this error diagnostic doesn't fire in your MS ABI test case example that forms a member pointer type in the base specifier list.

S5 // #S5
:
Base<sizeof(int S5::*)>
// expected-error@-1 {{member pointer has incomplete base type 'S5'}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this be an error in Microsoft mode? Shouldn't we silently use the unspecified model in that mode?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's an error for -fcomplete-member-pointers which is supposed to tell you when you use a member pointer in a way that the MS ABI would have it be unspecified, regardless of if you are compiling with MS member pointers or Itanium member pointers

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree on the desired behavior, but doesn't this test show that, in the MS ABI mode, clang emits a hard error about an incomplete type? That's my reading of what the code does, and what the test shows.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's just how -fcomplete-member-pointers works currently. It's more like a -Werror=... diagnostic

@@ -215,6 +215,14 @@ struct NewUnspecified;
SingleTemplate<void (IncSingle::*)()> tmpl_single;
UnspecTemplate<void (NewUnspecified::*)()> tmpl_unspec;

// Member pointers used in base specifiers force an unspecified inheritance model
struct MemPtrInBase : UnspecTemplate<void (MemPtrInBase::*)()> {};
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this memptr type is not required to be complete during the parsing of the base specifiers here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not for simply naming the type for the template argument but UnspecTemplate's definition does complete it by taking its sizeof, forcing the inheritance model to be assigned

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, but it's not clear to me if that happens before or after we've finished parsing the base specifiers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see what you mean.
If there were two bases struct MemPtrInBase : UnspecTemplate<void (MemPtrInBase::*)()>, SomeOtherClass {, UnspecTemplate<void (MemPtrInBase::*)()> would be instantiated and completed before the token for SomeOtherClass is seen. So this is the case for Clang, but I don't see why a different compiler couldn't parse all the bases at once then instantiated them.

However, they need to be instantiated at least before the body of the struct is parsed (because the bases affect name lookup), and this is essentially the same time as when you are "parsing the base specifiers".
And of course it needs to match Microsoft behaviour and assign the unspecified inheritance model during this time.

Comment on lines +9020 to +9021
if (RD->isBeingDefined()) {
if (RD->isParsingBaseSpecifiers()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm struggling to understand why this error diagnostic doesn't fire in your MS ABI test case example that forms a member pointer type in the base specifier list.

@MaxEW707
Copy link
Contributor

MaxEW707 commented Sep 6, 2024

@rnk @zmodem Sorry for the ping. Its been a bit since this got reviewed and I recently hit this ABI case when moving some parts of a larger project to clang.

Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

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

Sorry, looks like I wrote pending comments and didn't publish them

S5 // #S5
:
Base<sizeof(int S5::*)>
// expected-error@-1 {{member pointer has incomplete base type 'S5'}}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree on the desired behavior, but doesn't this test show that, in the MS ABI mode, clang emits a hard error about an incomplete type? That's my reading of what the code does, and what the test shows.

@@ -215,6 +215,14 @@ struct NewUnspecified;
SingleTemplate<void (IncSingle::*)()> tmpl_single;
UnspecTemplate<void (NewUnspecified::*)()> tmpl_unspec;

// Member pointers used in base specifiers force an unspecified inheritance model
struct MemPtrInBase : UnspecTemplate<void (MemPtrInBase::*)()> {};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, but it's not clear to me if that happens before or after we've finished parsing the base specifiers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants