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 @@ -95,6 +95,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
----------------------------------------

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 = 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 :).

data().IsParsingBaseSpecifiers = to;
}

bool isParsingBaseSpecifiers() const {
return data().IsParsingBaseSpecifiers;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -8052,6 +8052,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<
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
24 changes: 24 additions & 0 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2332,6 +2332,28 @@ 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);
}
}
};
} // namespace

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

Expand Down
6 changes: 4 additions & 2 deletions clang/lib/Sema/SemaCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1795,7 +1795,8 @@ TryStaticMemberPointerUpcast(Sema &Self, ExprResult &SrcExpr, QualType SrcType,

// Lock down the inheritance model right now in MS ABI, whether or not the
// pointee types are the same.
if (Self.Context.getTargetInfo().getCXXABI().isMicrosoft()) {
if (Self.Context.getTargetInfo().getCXXABI().isMicrosoft() ||
Self.getLangOpts().CompleteMemberPointers) {
(void)Self.isCompleteType(OpRange.getBegin(), SrcType);
(void)Self.isCompleteType(OpRange.getBegin(), DestType);
}
Expand Down Expand Up @@ -2337,7 +2338,8 @@ static TryCastResult TryReinterpretCast(Sema &Self, ExprResult &SrcExpr,
SrcMemPtr->isMemberFunctionPointer())
return TC_NotApplicable;

if (Self.Context.getTargetInfo().getCXXABI().isMicrosoft()) {
if (Self.Context.getTargetInfo().getCXXABI().isMicrosoft() ||
Self.getLangOpts().CompleteMemberPointers) {
// We need to determine the inheritance model that the class will use if
// haven't yet.
(void)Self.isCompleteType(OpRange.getBegin(), SrcType);
Expand Down
3 changes: 0 additions & 3 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2816,9 +2816,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
9 changes: 6 additions & 3 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,8 @@ ExprResult Sema::DefaultLvalueConversion(Expr *E) {

// Under the MS ABI, lock down the inheritance model now.
if (T->isMemberPointerType() &&
Context.getTargetInfo().getCXXABI().isMicrosoft())
(Context.getTargetInfo().getCXXABI().isMicrosoft() ||
getLangOpts().CompleteMemberPointers))
(void)isCompleteType(E->getExprLoc(), T);

ExprResult Res = CheckLValueToRValueConversionOperand(E);
Expand Down Expand Up @@ -14208,7 +14209,8 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) {
QualType MPTy = Context.getMemberPointerType(
op->getType(), Context.getTypeDeclType(MD->getParent()).getTypePtr());
// Under the MS ABI, lock down the inheritance model now.
if (Context.getTargetInfo().getCXXABI().isMicrosoft())
if (Context.getTargetInfo().getCXXABI().isMicrosoft() ||
getLangOpts().CompleteMemberPointers)
(void)isCompleteType(OpLoc, MPTy);
return MPTy;
} else if (lval != Expr::LV_Valid && lval != Expr::LV_IncompleteVoidType) {
Expand Down Expand Up @@ -14288,7 +14290,8 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) {
op->getType(),
Context.getTypeDeclType(cast<RecordDecl>(Ctx)).getTypePtr());
// Under the MS ABI, lock down the inheritance model now.
if (Context.getTargetInfo().getCXXABI().isMicrosoft())
if (Context.getTargetInfo().getCXXABI().isMicrosoft() ||
getLangOpts().CompleteMemberPointers)
(void)isCompleteType(OpLoc, MPTy);
return MPTy;
}
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4679,7 +4679,8 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,

// We may not have been able to figure out what this member pointer resolved
// to up until this exact point. Attempt to lock-in it's inheritance model.
if (Context.getTargetInfo().getCXXABI().isMicrosoft()) {
if (Context.getTargetInfo().getCXXABI().isMicrosoft() ||
getLangOpts().CompleteMemberPointers) {
(void)isCompleteType(From->getExprLoc(), From->getType());
(void)isCompleteType(From->getExprLoc(), ToType);
}
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16442,7 +16442,8 @@ ExprResult Sema::FixOverloadedFunctionReference(Expr *E, DeclAccessPair Found,
QualType MemPtrType
= Context.getMemberPointerType(Fn->getType(), ClassType.getTypePtr());
// Under the MS ABI, lock down the inheritance model now.
if (Context.getTargetInfo().getCXXABI().isMicrosoft())
if (Context.getTargetInfo().getCXXABI().isMicrosoft() ||
getLangOpts().CompleteMemberPointers)
(void)isCompleteType(UnOp->getOperatorLoc(), MemPtrType);

return UnaryOperator::Create(Context, SubExpr.get(), UO_AddrOf,
Expand Down
30 changes: 20 additions & 10 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2117,7 +2117,8 @@ QualType Sema::BuildArrayType(QualType T, ArraySizeModifier ASM,

// Mentioning a member pointer type for an array type causes us to lock in
// an inheritance model, even if it's inside an unused typedef.
if (Context.getTargetInfo().getCXXABI().isMicrosoft())
if (Context.getTargetInfo().getCXXABI().isMicrosoft() ||
getLangOpts().CompleteMemberPointers)
if (const MemberPointerType *MPTy = T->getAs<MemberPointerType>())
if (!MPTy->getClass()->isDependentType())
(void)isCompleteType(Loc, T);
Expand Down Expand Up @@ -9022,17 +9023,26 @@ 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;
}
}
}
}
Expand Down
24 changes: 23 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,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}}
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