diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 8d24d393eab09..3b8a47392c647 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -621,17 +621,24 @@ class ASTContext : public RefCountedBase { public: struct CXXRecordDeclRelocationInfo { + static CXXRecordDeclRelocationInfo NonRelocatable() { + return {false, false}; + } + unsigned IsRelocatable; unsigned IsReplaceable; }; std::optional getRelocationInfoForCXXRecord(const CXXRecordDecl *) const; void setRelocationInfoForCXXRecord(const CXXRecordDecl *, - CXXRecordDeclRelocationInfo); + const CXXRecordDeclRelocationInfo &); + bool containsAddressDiscriminatedPointerAuth(QualType T); private: llvm::DenseMap RelocatableClasses; + llvm::DenseMap + RecordContainsAddressDiscriminatedPointerAuth; ImportDecl *FirstLocalImport = nullptr; ImportDecl *LastLocalImport = nullptr; @@ -3668,6 +3675,7 @@ OPT_LIST(V) /// authentication policy for the specified record. const CXXRecordDecl * baseForVTableAuthentication(const CXXRecordDecl *ThisClass); + bool hasAddressDiscriminatedVTableAuthentication(const CXXRecordDecl *Class); bool useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl, StringRef MangledName); diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 0dad07e55a820..261836e0bde32 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4343,8 +4343,10 @@ class Sema final : public SemaBase { void ActOnTagFinishDefinition(Scope *S, Decl *TagDecl, SourceRange BraceRange); - ASTContext::CXXRecordDeclRelocationInfo + using CXXRecordDeclRelocationInfo = ASTContext::CXXRecordDeclRelocationInfo; + CXXRecordDeclRelocationInfo CheckCXX2CRelocatableAndReplaceable(const clang::CXXRecordDecl *D); + CXXRecordDeclRelocationInfo CheckCXX2CRelocatableAndReplaceable(QualType T); void ActOnTagFinishSkippedDefinition(SkippedDefinitionContext Context); @@ -8680,7 +8682,6 @@ class Sema final : public SemaBase { // FIXME: This is in Sema because it requires // overload resolution, can we move to ASTContext? bool IsCXXTriviallyRelocatableType(QualType T); - bool IsCXXTriviallyRelocatableType(const CXXRecordDecl &RD); //// Determines if a type is replaceable /// according to the C++26 rules. diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index b51f7622288df..32c78f0141d6c 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -1698,13 +1698,47 @@ ASTContext::getRelocationInfoForCXXRecord(const CXXRecordDecl *RD) const { } void ASTContext::setRelocationInfoForCXXRecord( - const CXXRecordDecl *RD, CXXRecordDeclRelocationInfo Info) { + const CXXRecordDecl *RD, const CXXRecordDeclRelocationInfo &Info) { assert(RD); CXXRecordDecl *D = RD->getDefinition(); assert(RelocatableClasses.find(D) == RelocatableClasses.end()); RelocatableClasses.insert({D, Info}); } +bool ASTContext::containsAddressDiscriminatedPointerAuth(QualType T) { + if (!LangOpts.PointerAuthCalls && !LangOpts.PointerAuthIntrinsics) + return false; + + T = T.getCanonicalType(); + if (T.hasAddressDiscriminatedPointerAuth()) + return true; + const RecordDecl *RD = T->getAsRecordDecl(); + if (!RD) + return false; + + auto SaveReturn = [this, RD](bool Result) { + RecordContainsAddressDiscriminatedPointerAuth.insert({RD, Result}); + return Result; + }; + if (auto Existing = RecordContainsAddressDiscriminatedPointerAuth.find(RD); + Existing != RecordContainsAddressDiscriminatedPointerAuth.end()) + return Existing->second; + if (const CXXRecordDecl *CXXRD = dyn_cast(RD)) { + if (CXXRD->isPolymorphic() && + hasAddressDiscriminatedVTableAuthentication(CXXRD)) + return SaveReturn(true); + for (auto Base : CXXRD->bases()) { + if (containsAddressDiscriminatedPointerAuth(Base.getType())) + return SaveReturn(true); + } + } + for (auto *FieldDecl : RD->fields()) { + if (containsAddressDiscriminatedPointerAuth(FieldDecl->getType())) + return SaveReturn(true); + } + return SaveReturn(false); +} + void ASTContext::addedLocalImportDecl(ImportDecl *Import) { assert(!Import->getNextLocalImport() && "Import declaration already in the chain"); @@ -15121,6 +15155,21 @@ ASTContext::baseForVTableAuthentication(const CXXRecordDecl *ThisClass) { return PrimaryBase; } +bool ASTContext::hasAddressDiscriminatedVTableAuthentication( + const CXXRecordDecl *Class) { + assert(Class->isPolymorphic()); + const CXXRecordDecl *BaseType = baseForVTableAuthentication(Class); + using AuthAttr = VTablePointerAuthenticationAttr; + const auto *ExplicitAuth = BaseType->getAttr(); + if (!ExplicitAuth) + return LangOpts.PointerAuthVTPtrAddressDiscrimination; + AuthAttr::AddressDiscriminationMode AddressDiscrimination = + ExplicitAuth->getAddressDiscrimination(); + if (AddressDiscrimination == AuthAttr::DefaultAddressDiscrimination) + return LangOpts.PointerAuthVTPtrAddressDiscrimination; + return AddressDiscrimination == AuthAttr::AddressDiscrimination; +} + bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl, StringRef MangledName) { auto *Method = cast(VirtualMethodDecl.getDecl()); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 39d4d49a0fe79..c7da12b8b4132 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -10615,7 +10615,7 @@ void Sema::checkIllFormedTrivialABIStruct(CXXRecordDecl &RD) { } } - if (IsCXXTriviallyRelocatableType(RD)) + if (CheckCXX2CRelocatableAndReplaceable(&RD).IsRelocatable) return; // Ill-formed if the copy and move constructors are deleted. diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp index 1738ab4466001..9e07bbcb0a218 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -228,10 +228,11 @@ static bool IsEligibleForReplacement(Sema &SemaRef, const CXXRecordDecl *D) { ASTContext::CXXRecordDeclRelocationInfo Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { - ASTContext::CXXRecordDeclRelocationInfo Info{false, false}; + if (auto ExistingInfo = Context.getRelocationInfoForCXXRecord(D)) + return *ExistingInfo; if (!getLangOpts().CPlusPlus || D->isInvalidDecl()) - return Info; + return CXXRecordDeclRelocationInfo::NonRelocatable(); assert(D->hasDefinition()); @@ -244,7 +245,7 @@ Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { *this, D, /*AllowUserDefined=*/true); }; - auto IsUnion = [&, Is = std::optional{}]() mutable { + auto IsTrivialUnion = [&, Is = std::optional{}]() mutable { if (!Is.has_value()) Is = D->isUnion() && !D->hasUserDeclaredCopyConstructor() && !D->hasUserDeclaredCopyAssignment() && @@ -259,6 +260,7 @@ Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { return *Is; }; + ASTContext::CXXRecordDeclRelocationInfo Info; Info.IsRelocatable = [&] { if (D->isDependentType()) return false; @@ -272,7 +274,7 @@ Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { return true; // is a union with no user-declared special member functions, or - if (IsUnion()) + if (IsTrivialUnion()) return true; // is default-movable. @@ -292,77 +294,129 @@ Sema::CheckCXX2CRelocatableAndReplaceable(const CXXRecordDecl *D) { return HasSuitableSMP(); // is a union with no user-declared special member functions, or - if (IsUnion()) + if (IsTrivialUnion()) return HasSuitableSMP(); // is default-movable. return IsDefaultMovable(); }(); + bool PtrauthMatters = LangOpts.PointerAuthIntrinsics || + LangOpts.PointerAuthVTPtrAddressDiscrimination; + if (PtrauthMatters) { + bool IsUnion = D->isUnion(); + auto RecordPointerAuth = [&](bool HasAddressDiscrimination) { + if (HasAddressDiscrimination && IsUnion) { + Info.IsRelocatable = false; + Info.IsReplaceable = false; + } + }; + auto IsBottomRelocationInfo = [](const CXXRecordDeclRelocationInfo &Info) { + return !Info.IsReplaceable && !Info.IsRelocatable; + }; + + if (D->isPolymorphic()) + RecordPointerAuth(Context.hasAddressDiscriminatedVTableAuthentication(D)); + for (auto Base : D->bases()) { + if (IsBottomRelocationInfo(Info)) + break; + bool BaseHasPtrauth = + Context.containsAddressDiscriminatedPointerAuth(Base.getType()); + RecordPointerAuth(BaseHasPtrauth); + } + for (auto *FieldDecl : D->fields()) { + if (IsBottomRelocationInfo(Info)) + break; + bool FieldHasPtrauth = + Context.containsAddressDiscriminatedPointerAuth(FieldDecl->getType()); + RecordPointerAuth(FieldHasPtrauth); + } + } + Context.setRelocationInfoForCXXRecord(D, Info); return Info; } -bool Sema::IsCXXTriviallyRelocatableType(const CXXRecordDecl &RD) { - if (std::optional Info = - getASTContext().getRelocationInfoForCXXRecord(&RD)) - return Info->IsRelocatable; - ASTContext::CXXRecordDeclRelocationInfo Info = - CheckCXX2CRelocatableAndReplaceable(&RD); - getASTContext().setRelocationInfoForCXXRecord(&RD, Info); - return Info.IsRelocatable; -} +ASTContext::CXXRecordDeclRelocationInfo +Sema::CheckCXX2CRelocatableAndReplaceable(QualType T) { + T = T.getCanonicalType(); + enum class DirectRelocationInformation { Yes, No, Unknown }; + DirectRelocationInformation Relocatable = + DirectRelocationInformation::Unknown; + DirectRelocationInformation Replaceable = + DirectRelocationInformation::Unknown; + DirectRelocationInformation ContainsAddressDiscriminatedValues = + DirectRelocationInformation::Unknown; + + auto UpdateRelocatable = [&](DirectRelocationInformation DRI) { + if (Relocatable == DirectRelocationInformation::Unknown || + Relocatable == DirectRelocationInformation::Yes) + Relocatable = DRI; + }; + auto UpdateReplaceable = [&](DirectRelocationInformation DRI) { + if (Replaceable == DirectRelocationInformation::Unknown || + Replaceable == DirectRelocationInformation::Yes) + Replaceable = DRI; + }; + auto UpdateAddressDiscrimination = [&](DirectRelocationInformation DRI) { + if (ContainsAddressDiscriminatedValues == DirectRelocationInformation::Yes) + Replaceable = DirectRelocationInformation::No; + }; -bool Sema::IsCXXTriviallyRelocatableType(QualType Type) { + if (T->isVariableArrayType()) { + UpdateRelocatable(DirectRelocationInformation::No); + UpdateReplaceable(DirectRelocationInformation::No); + } - QualType BaseElementType = getASTContext().getBaseElementType(Type); + if (T.isConstQualified() || T.isVolatileQualified()) + UpdateReplaceable(DirectRelocationInformation::No); - if (Type->isVariableArrayType()) - return false; + if (T.hasAddressDiscriminatedPointerAuth()) + UpdateAddressDiscrimination(DirectRelocationInformation::Yes); + + QualType BaseElementType = + SemaRef.getASTContext().getBaseElementType(T.getUnqualifiedType()); + + if (BaseElementType->isIncompleteType()) { + Relocatable = DirectRelocationInformation::No; + Replaceable = DirectRelocationInformation::No; + } if (BaseElementType.hasNonTrivialObjCLifetime()) - return false; + UpdateRelocatable(DirectRelocationInformation::No); - if (BaseElementType.hasAddressDiscriminatedPointerAuth()) - return false; + if (BaseElementType->isScalarType()) { + UpdateRelocatable(DirectRelocationInformation::Yes); + UpdateReplaceable(DirectRelocationInformation::Yes); + UpdateAddressDiscrimination(DirectRelocationInformation::No); + } - if (BaseElementType->isIncompleteType()) - return false; + if (BaseElementType->isVectorType()) + UpdateRelocatable(DirectRelocationInformation::Yes); - if (BaseElementType->isScalarType() || BaseElementType->isVectorType()) - return true; + auto CreateInfo = [=]() -> CXXRecordDeclRelocationInfo { + return {Relocatable == DirectRelocationInformation::Yes, + Replaceable == DirectRelocationInformation::Yes}; + }; - if (const auto *RD = BaseElementType->getAsCXXRecordDecl()) - return IsCXXTriviallyRelocatableType(*RD); + if (BaseElementType->isIncompleteType()) + return CreateInfo(); - return false; + const CXXRecordDecl *RD = BaseElementType->getAsCXXRecordDecl(); + if (!RD) + return CreateInfo(); + + CXXRecordDeclRelocationInfo Info = CheckCXX2CRelocatableAndReplaceable(RD); + Info.IsRelocatable &= Relocatable != DirectRelocationInformation::No; + Info.IsReplaceable &= Replaceable != DirectRelocationInformation::No; + return Info; } -static bool IsCXXReplaceableType(Sema &S, const CXXRecordDecl *RD) { - if (std::optional Info = - S.getASTContext().getRelocationInfoForCXXRecord(RD)) - return Info->IsReplaceable; - ASTContext::CXXRecordDeclRelocationInfo Info = - S.CheckCXX2CRelocatableAndReplaceable(RD); - S.getASTContext().setRelocationInfoForCXXRecord(RD, Info); - return Info.IsReplaceable; +bool Sema::IsCXXTriviallyRelocatableType(QualType Type) { + return CheckCXX2CRelocatableAndReplaceable(Type).IsRelocatable; } bool Sema::IsCXXReplaceableType(QualType Type) { - if (Type.isConstQualified() || Type.isVolatileQualified()) - return false; - - if (Type->isVariableArrayType()) - return false; - - QualType BaseElementType = - getASTContext().getBaseElementType(Type.getUnqualifiedType()); - if (BaseElementType->isIncompleteType()) - return false; - if (BaseElementType->isScalarType()) - return true; - if (const auto *RD = BaseElementType->getAsCXXRecordDecl()) - return ::IsCXXReplaceableType(*this, RD); - return false; + return CheckCXX2CRelocatableAndReplaceable(Type).IsReplaceable; } /// Checks that type T is not a VLA. @@ -663,7 +717,8 @@ static bool isTriviallyEqualityComparableType(Sema &S, QualType Type, } static bool IsTriviallyRelocatableType(Sema &SemaRef, QualType T) { - QualType BaseElementType = SemaRef.getASTContext().getBaseElementType(T); + ASTContext &Ctx = SemaRef.getASTContext(); + QualType BaseElementType = Ctx.getBaseElementType(T); if (BaseElementType->isIncompleteType()) return false; @@ -673,9 +728,16 @@ static bool IsTriviallyRelocatableType(Sema &SemaRef, QualType T) { if (T.hasAddressDiscriminatedPointerAuth()) return false; + if (Ctx.containsAddressDiscriminatedPointerAuth(BaseElementType)) + return false; + if (const auto *RD = BaseElementType->getAsCXXRecordDecl(); - RD && !RD->isPolymorphic() && SemaRef.IsCXXTriviallyRelocatableType(*RD)) - return true; + RD && !RD->isPolymorphic()) { + ASTContext::CXXRecordDeclRelocationInfo Info = + SemaRef.CheckCXX2CRelocatableAndReplaceable(RD); + if (Info.IsRelocatable) + return true; + } if (const auto *RD = BaseElementType->getAsRecordDecl()) return RD->canPassInRegisters(); diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp index 9d43994ee7661..7152a5937d9b7 100644 --- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp +++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -std=c++2c -verify %s +// RUN: %clang_cc1 -triple aarch64-linux-gnu -fptrauth-intrinsics -fptrauth-calls -std=c++2c -verify %s class Trivial {}; static_assert(__builtin_is_cpp_trivially_relocatable(Trivial)); diff --git a/clang/test/SemaCXX/ptrauth-triviality.cpp b/clang/test/SemaCXX/ptrauth-triviality.cpp index 60d1b57230f18..6f3650f7ac2e3 100644 --- a/clang/test/SemaCXX/ptrauth-triviality.cpp +++ b/clang/test/SemaCXX/ptrauth-triviality.cpp @@ -26,7 +26,7 @@ static_assert(!__is_trivially_assignable(S1, const S1&)); static_assert(__is_trivially_destructible(S1)); static_assert(!__is_trivially_copyable(S1)); static_assert(!__is_trivially_relocatable(S1)); // expected-warning{{deprecated}} -static_assert(!__builtin_is_cpp_trivially_relocatable(S1)); +static_assert(__builtin_is_cpp_trivially_relocatable(S1)); static_assert(!__is_trivially_equality_comparable(S1)); static_assert(__is_trivially_constructible(Holder)); @@ -35,7 +35,7 @@ static_assert(!__is_trivially_assignable(Holder, const Holder&)); static_assert(__is_trivially_destructible(Holder)); static_assert(!__is_trivially_copyable(Holder)); static_assert(!__is_trivially_relocatable(Holder)); // expected-warning{{deprecated}} -static_assert(!__builtin_is_cpp_trivially_relocatable(Holder)); +static_assert(__builtin_is_cpp_trivially_relocatable(Holder)); static_assert(!__is_trivially_equality_comparable(Holder)); struct S2 { @@ -83,7 +83,7 @@ static_assert(!__is_trivially_constructible(Holder, const Holder&)); static_assert(!__is_trivially_assignable(Holder, const Holder&)); static_assert(__is_trivially_destructible(Holder)); static_assert(!__is_trivially_copyable(Holder)); -static_assert(__is_trivially_relocatable(Holder)); // expected-warning{{deprecated}} +static_assert(!__is_trivially_relocatable(Holder)); // expected-warning{{deprecated}} static_assert(__builtin_is_cpp_trivially_relocatable(Holder)); static_assert(!__is_trivially_equality_comparable(Holder)); @@ -148,7 +148,7 @@ static_assert(!__is_trivially_assignable(S6, const S6&)); static_assert(__is_trivially_destructible(S6)); static_assert(!__is_trivially_copyable(S6)); static_assert(!__is_trivially_relocatable(S6)); // expected-warning{{deprecated}} -static_assert(!__builtin_is_cpp_trivially_relocatable(S6)); +static_assert(__builtin_is_cpp_trivially_relocatable(S6)); static_assert(!__is_trivially_equality_comparable(S6)); static_assert(__is_trivially_constructible(Holder)); @@ -157,7 +157,7 @@ static_assert(!__is_trivially_assignable(Holder, const Holder&)); static_assert(__is_trivially_destructible(Holder)); static_assert(!__is_trivially_copyable(Holder)); static_assert(!__is_trivially_relocatable(Holder)); // expected-warning{{deprecated}} -static_assert(!__builtin_is_cpp_trivially_relocatable(Holder)); +static_assert(__builtin_is_cpp_trivially_relocatable(Holder)); static_assert(!__is_trivially_equality_comparable(Holder)); struct S7 { diff --git a/clang/test/SemaCXX/trivially-relocatable-ptrauth.cpp b/clang/test/SemaCXX/trivially-relocatable-ptrauth.cpp new file mode 100644 index 0000000000000..29722fadd4d17 --- /dev/null +++ b/clang/test/SemaCXX/trivially-relocatable-ptrauth.cpp @@ -0,0 +1,102 @@ +// RUN: %clang_cc1 -triple arm64 -fptrauth-calls -fptrauth-intrinsics -std=c++26 -verify %s + +// This test intentionally does not enable the global address discrimination +// of vtable pointers. This lets us configure them with different schemas +// and verify that we're correctly tracking the existence of address discrimination + +// expected-no-diagnostics + +struct NonAddressDiscPtrauth { + void * __ptrauth(1, 0, 1234) p; +}; + +static_assert(__builtin_is_cpp_trivially_relocatable(NonAddressDiscPtrauth)); + +struct AddressDiscPtrauth { + void * __ptrauth(1, 1, 1234) p; +}; + +static_assert(__builtin_is_cpp_trivially_relocatable(AddressDiscPtrauth)); + +struct MultipleBaseClasses : NonAddressDiscPtrauth, AddressDiscPtrauth { + +}; + +static_assert(__builtin_is_cpp_trivially_relocatable(MultipleBaseClasses)); + +struct MultipleMembers { + NonAddressDiscPtrauth field0; + AddressDiscPtrauth field1; +}; + +static_assert(__builtin_is_cpp_trivially_relocatable(MultipleMembers)); + +struct UnionOfPtrauth { + union { + NonAddressDiscPtrauth field0; + AddressDiscPtrauth field1; + } u; +}; + +static_assert(!__builtin_is_cpp_trivially_relocatable(UnionOfPtrauth)); + +struct [[clang::ptrauth_vtable_pointer(process_independent,address_discrimination,no_extra_discrimination)]] Polymorphic trivially_relocatable_if_eligible { + virtual ~Polymorphic(); +}; + +struct Foo : Polymorphic { + Foo(const Foo&); + ~Foo(); +}; + + +static_assert(__builtin_is_cpp_trivially_relocatable(Polymorphic)); + +struct [[clang::ptrauth_vtable_pointer(process_independent,no_address_discrimination,no_extra_discrimination)]] NonAddressDiscriminatedPolymorphic trivially_relocatable_if_eligible { + virtual ~NonAddressDiscriminatedPolymorphic(); +}; + +static_assert(__builtin_is_cpp_trivially_relocatable(NonAddressDiscriminatedPolymorphic)); + + +struct PolymorphicMembers { + Polymorphic field; +}; + +static_assert(__builtin_is_cpp_trivially_relocatable(PolymorphicMembers)); + +struct UnionOfPolymorphic { + union trivially_relocatable_if_eligible { + Polymorphic p; + int i; + } u; +}; + +static_assert(!__builtin_is_cpp_trivially_relocatable(UnionOfPolymorphic)); + + +struct UnionOfNonAddressDiscriminatedPolymorphic { + union trivially_relocatable_if_eligible { + NonAddressDiscriminatedPolymorphic p; + int i; + } u; +}; +static_assert(!__builtin_is_cpp_trivially_relocatable(UnionOfNonAddressDiscriminatedPolymorphic)); + +struct UnionOfNonAddressDiscriminatedPtrauth { + union { + NonAddressDiscPtrauth p; + int i; + } u; +}; + +static_assert(__builtin_is_cpp_trivially_relocatable(UnionOfNonAddressDiscriminatedPtrauth)); + +struct UnionOfAddressDisriminatedPtrauth { + union { + AddressDiscPtrauth p; + int i; + } u; +}; + +static_assert(!__builtin_is_cpp_trivially_relocatable(UnionOfAddressDisriminatedPtrauth));