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
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ ABI Changes in This Version
`-fms-compatibility-version=19.14` to imitate the MSVC 1914 mangling behavior.
(GH#70899).

- 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
----------------------------------------

Expand Down
7 changes: 5 additions & 2 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -598,7 +599,9 @@ class CXXRecordDecl : public RecordDecl {
return !hasDefinition() || !isDynamicClass() || hasAnyDependentBases();
}

void setIsParsingBaseSpecifiers() { data().IsParsingBaseSpecifiers = true; }
void setIsParsingBaseSpecifiers(bool To) {
data().IsParsingBaseSpecifiers = To;
}

bool isParsingBaseSpecifiers() const {
return data().IsParsingBaseSpecifiers;
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -5651,6 +5651,10 @@ class Sema final : public SemaBase {
SourceLocation BaseLoc,
SourceLocation EllipsisLoc);

/// Sets the value of "isParsingBaseSpecifiers" when parsing a
/// C++ class definition.
void SetParsingBaseSpecifiers(Decl *ClassDecl, bool To);

/// Performs the actual work of attaching the given base class
/// specifiers to a C++ class.
bool AttachBaseSpecifiers(CXXRecordDecl *Class,
Expand Down
6 changes: 1 addition & 5 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()];
Expand All @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2375,6 +2375,17 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
}
}

namespace {
struct ParsingBaseSpecifiersGuard {
Sema &Actions;
Decl *D = nullptr;
ParsingBaseSpecifiersGuard(Sema &Actions, Decl *D) : Actions(Actions), D(D) {
Actions.SetParsingBaseSpecifiers(D, true);
}
~ParsingBaseSpecifiersGuard() { Actions.SetParsingBaseSpecifiers(D, false); }
};
} // namespace

/// ParseBaseClause - Parse the base-clause of a C++ class [C++ class.derived].
///
/// base-clause : [C++ class.derived]
Expand All @@ -2386,6 +2397,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;

Expand Down
13 changes: 10 additions & 3 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2763,9 +2763,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) {
Expand Down Expand Up @@ -2829,6 +2826,16 @@ NoteIndirectBases(ASTContext &Context, IndirectBaseSet &Set,
}
}

void Sema::SetParsingBaseSpecifiers(Decl *ClassDecl, bool To) {
if (ClassDecl) {
AdjustDeclIfTemplate(ClassDecl);
auto *RD = cast<CXXRecordDecl>(ClassDecl);
assert(RD->isParsingBaseSpecifiers() != To &&
"Unexpected state for isParsingBaseSpecifiers()");
RD->setIsParsingBaseSpecifiers(To);
}
}

bool Sema::AttachBaseSpecifiers(CXXRecordDecl *Class,
MutableArrayRef<CXXBaseSpecifier *> Bases) {
if (Bases.empty())
Expand Down
19 changes: 14 additions & 5 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9015,11 +9015,20 @@ 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;
if (getLangOpts().CompleteMemberPointers) {
const CXXRecordDecl *RD = MPTy->getClass()->getAsCXXRecordDecl();
if (RD->isBeingDefined()) {
if (RD->isParsingBaseSpecifiers()) {
Comment on lines +9020 to +9021
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.

Diag(Loc, diag::err_memptr_incomplete)
<< QualType(MPTy->getClass(), 0);
return true;
}
// Otherwise, we know the bases and can assign a proper inheritance
// model even though the class is incomplete.
} else if (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.
Expand Down
19 changes: 18 additions & 1 deletion clang/test/SemaCXX/complete-member-pointers.cpp
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,3 +15,18 @@ template <typename T>
struct S3 {
int T::*foo;
};

template<int I> struct Base {};
struct
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

};

template<typename T> struct S6 {};
int S6<void>::* qux; // #qux
template<> struct S6<void> {};
// expected-error@-1 {{explicit specialization of 'S6<void>' after instantiation}}
// expected-note@#qux {{implicit instantiation first required here}}
8 changes: 8 additions & 0 deletions clang/test/SemaCXX/member-pointer-ms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.

#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, "");
Expand Down
Loading