Skip to content

[clang] Fix PointerAuth semantics of cpp_trivially_relocatable #143969

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 7 commits into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -629,10 +629,48 @@ class ASTContext : public RefCountedBase<ASTContext> {
void setRelocationInfoForCXXRecord(const CXXRecordDecl *,
CXXRecordDeclRelocationInfo);

/// Examines a given type, and returns whether the type itself
/// is address discriminated, or any transitively embedded types
/// contain data that is address discriminated. This includes
/// implicitly authenticated values like vtable pointers, as well as
/// explicitly qualified fields.
bool containsAddressDiscriminatedPointerAuth(QualType T) {
if (!isPointerAuthenticationAvailable())
return false;
return findPointerAuthContent(T) != PointerAuthContent::None;
}
Comment on lines +637 to +641
Copy link
Contributor

Choose a reason for hiding this comment

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

Is that still used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question, it's possible I updated it without verifying it was needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah right, this is used for the union check - in a union it does not matter what the source of the address discrimination is, a union containing anything address discriminated isn't valid. In principle with this change the only thing that would get here is a vtable pointer but it seems reasonable to be safe.


/// Examines a given type, and returns whether the type itself
/// or any data it transitively contains has a pointer authentication
/// schema that is not safely relocatable. e.g. any data or fields
/// with address discrimination other than any otherwise similar
/// vtable pointers.
bool containsNonRelocatablePointerAuth(QualType T) {
if (!isPointerAuthenticationAvailable())
return false;
return findPointerAuthContent(T) ==
PointerAuthContent::AddressDiscriminatedData;
}

private:
llvm::DenseMap<const CXXRecordDecl *, CXXRecordDeclRelocationInfo>
RelocatableClasses;

// FIXME: store in RecordDeclBitfields in future?
enum class PointerAuthContent : uint8_t {
None,
AddressDiscriminatedVTable,
AddressDiscriminatedData
};

// A simple helper function to short circuit pointer auth checks.
bool isPointerAuthenticationAvailable() const {
return LangOpts.PointerAuthCalls || LangOpts.PointerAuthIntrinsics;
}
PointerAuthContent findPointerAuthContent(QualType T);
llvm::DenseMap<const RecordDecl *, PointerAuthContent>
RecordContainsAddressDiscriminatedPointerAuth;

ImportDecl *FirstLocalImport = nullptr;
ImportDecl *LastLocalImport = nullptr;

Expand Down Expand Up @@ -3668,6 +3706,7 @@ OPT_LIST(V)
/// authentication policy for the specified record.
const CXXRecordDecl *
baseForVTableAuthentication(const CXXRecordDecl *ThisClass);

bool useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
StringRef MangledName);

Expand Down
67 changes: 67 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1705,6 +1705,73 @@ void ASTContext::setRelocationInfoForCXXRecord(
RelocatableClasses.insert({D, Info});
}

static bool primaryBaseHaseAddressDiscriminatedVTableAuthentication(
ASTContext &Context, const CXXRecordDecl *Class) {
if (!Class->isPolymorphic())
return false;
const CXXRecordDecl *BaseType = Context.baseForVTableAuthentication(Class);
using AuthAttr = VTablePointerAuthenticationAttr;
const AuthAttr *ExplicitAuth = BaseType->getAttr<AuthAttr>();
if (!ExplicitAuth)
return Context.getLangOpts().PointerAuthVTPtrAddressDiscrimination;
AuthAttr::AddressDiscriminationMode AddressDiscrimination =
ExplicitAuth->getAddressDiscrimination();
if (AddressDiscrimination == AuthAttr::DefaultAddressDiscrimination)
return Context.getLangOpts().PointerAuthVTPtrAddressDiscrimination;
return AddressDiscrimination == AuthAttr::AddressDiscrimination;
}

ASTContext::PointerAuthContent ASTContext::findPointerAuthContent(QualType T) {
assert(isPointerAuthenticationAvailable());

T = T.getCanonicalType();
if (T.hasAddressDiscriminatedPointerAuth())
return PointerAuthContent::AddressDiscriminatedData;
const RecordDecl *RD = T->getAsRecordDecl();
if (!RD)
return PointerAuthContent::None;

if (auto Existing = RecordContainsAddressDiscriminatedPointerAuth.find(RD);
Existing != RecordContainsAddressDiscriminatedPointerAuth.end())
return Existing->second;

PointerAuthContent Result = PointerAuthContent::None;

auto SaveResultAndReturn = [&]() -> PointerAuthContent {
auto [ResultIter, DidAdd] =
RecordContainsAddressDiscriminatedPointerAuth.try_emplace(RD, Result);
(void)ResultIter;
(void)DidAdd;
assert(DidAdd);
return Result;
};
auto ShouldContinueAfterUpdate = [&](PointerAuthContent NewResult) {
static_assert(PointerAuthContent::None <
PointerAuthContent::AddressDiscriminatedVTable);
static_assert(PointerAuthContent::AddressDiscriminatedVTable <
PointerAuthContent::AddressDiscriminatedData);
if (NewResult > Result)
Result = NewResult;
return Result != PointerAuthContent::AddressDiscriminatedData;
};
if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
if (primaryBaseHaseAddressDiscriminatedVTableAuthentication(*this, CXXRD) &&
!ShouldContinueAfterUpdate(
PointerAuthContent::AddressDiscriminatedVTable))
return SaveResultAndReturn();
for (auto Base : CXXRD->bases()) {
if (!ShouldContinueAfterUpdate(findPointerAuthContent(Base.getType())))
return SaveResultAndReturn();
}
}
for (auto *FieldDecl : RD->fields()) {
if (!ShouldContinueAfterUpdate(
findPointerAuthContent(FieldDecl->getType())))
return SaveResultAndReturn();
}
return SaveResultAndReturn();
}

void ASTContext::addedLocalImportDecl(ImportDecl *Import) {
assert(!Import->getNextLocalImport() &&
"Import declaration already in the chain");
Expand Down
17 changes: 13 additions & 4 deletions clang/lib/Sema/SemaTypeTraits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ static bool IsEligibleForTrivialRelocation(Sema &SemaRef,
return false;
}

bool IsUnion = D->isUnion();
for (const FieldDecl *Field : D->fields()) {
if (Field->getType()->isDependentType())
continue;
Expand All @@ -197,6 +198,12 @@ static bool IsEligibleForTrivialRelocation(Sema &SemaRef,
// of a trivially relocatable type
if (!SemaRef.IsCXXTriviallyRelocatableType(Field->getType()))
return false;

// A union contains values with address discriminated pointer auth
// cannot be relocated.
if (IsUnion && SemaRef.Context.containsAddressDiscriminatedPointerAuth(
Field->getType()))
return false;
}
return !D->hasDeletedDestructor();
}
Expand Down Expand Up @@ -313,7 +320,6 @@ bool Sema::IsCXXTriviallyRelocatableType(const CXXRecordDecl &RD) {
}

bool Sema::IsCXXTriviallyRelocatableType(QualType Type) {

QualType BaseElementType = getASTContext().getBaseElementType(Type);

if (Type->isVariableArrayType())
Expand All @@ -322,10 +328,10 @@ bool Sema::IsCXXTriviallyRelocatableType(QualType Type) {
if (BaseElementType.hasNonTrivialObjCLifetime())
return false;

if (BaseElementType.hasAddressDiscriminatedPointerAuth())
if (BaseElementType->isIncompleteType())
return false;

if (BaseElementType->isIncompleteType())
if (Context.containsNonRelocatablePointerAuth(Type))
return false;

if (BaseElementType->isScalarType() || BaseElementType->isVectorType())
Expand Down Expand Up @@ -670,7 +676,10 @@ static bool IsTriviallyRelocatableType(Sema &SemaRef, QualType T) {
if (!BaseElementType->isObjectType())
return false;

if (T.hasAddressDiscriminatedPointerAuth())
// The deprecated __builtin_is_trivially_relocatable does not have
// an equivalent to __builtin_trivially_relocate, so there is no
// safe way to use it if there are any address discriminated values.
if (SemaRef.getASTContext().containsAddressDiscriminatedPointerAuth(T))
return false;

if (const auto *RD = BaseElementType->getAsCXXRecordDecl();
Expand Down
1 change: 1 addition & 0 deletions clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
Original file line number Diff line number Diff line change
@@ -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));
Expand Down
44 changes: 39 additions & 5 deletions clang/test/SemaCXX/ptrauth-triviality.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// RUN: %clang_cc1 -triple arm64-apple-ios -std=c++20 -fptrauth-calls -fptrauth-intrinsics -verify -fsyntax-only %s
// RUN: %clang_cc1 -triple aarch64-linux-gnu -std=c++20 -fptrauth-calls -fptrauth-intrinsics -verify -fsyntax-only %s
// RUN: %clang_cc1 -triple arm64-apple-ios -std=c++26 -fptrauth-calls -fptrauth-intrinsics -verify -fsyntax-only %s
// RUN: %clang_cc1 -triple aarch64-linux-gnu -std=c++26 -fptrauth-calls -fptrauth-intrinsics -verify -fsyntax-only %s

#define AQ __ptrauth(1,1,50)
#define IQ __ptrauth(1,0,50)
Expand Down Expand Up @@ -83,7 +83,7 @@ static_assert(!__is_trivially_constructible(Holder<S3>, const Holder<S3>&));
static_assert(!__is_trivially_assignable(Holder<S3>, const Holder<S3>&));
static_assert(__is_trivially_destructible(Holder<S3>));
static_assert(!__is_trivially_copyable(Holder<S3>));
static_assert(__is_trivially_relocatable(Holder<S3>)); // expected-warning{{deprecated}}
static_assert(!__is_trivially_relocatable(Holder<S3>)); // expected-warning{{deprecated}}
static_assert(__builtin_is_cpp_trivially_relocatable(Holder<S3>));
static_assert(!__is_trivially_equality_comparable(Holder<S3>));

Expand All @@ -99,7 +99,6 @@ static_assert(!__is_trivially_assignable(S4, const S4&));
static_assert(__is_trivially_destructible(S4));
static_assert(!__is_trivially_copyable(S4));
static_assert(!__is_trivially_relocatable(S4)); // expected-warning{{deprecated}}
//FIXME
static_assert(__builtin_is_cpp_trivially_relocatable(S4));
static_assert(!__is_trivially_equality_comparable(S4));

Expand All @@ -124,7 +123,6 @@ static_assert(!__is_trivially_assignable(S5, const S5&));
static_assert(__is_trivially_destructible(S5));
static_assert(!__is_trivially_copyable(S5));
static_assert(!__is_trivially_relocatable(S5)); // expected-warning{{deprecated}}
//FIXME
static_assert(__builtin_is_cpp_trivially_relocatable(S5));
static_assert(!__is_trivially_equality_comparable(S5));

Expand Down Expand Up @@ -182,3 +180,39 @@ static_assert(__is_trivially_copyable(Holder<S7>));
static_assert(__is_trivially_relocatable(Holder<S7>)); // expected-warning{{deprecated}}
static_assert(__builtin_is_cpp_trivially_relocatable(Holder<S7>));
static_assert(__is_trivially_equality_comparable(Holder<S7>));

template <class... Bases> struct MultipleInheriter : Bases... {
};

template <class T> static const bool test_is_trivially_relocatable_v = __builtin_is_cpp_trivially_relocatable(T);
template <class... Types> static const bool multiple_inheritance_is_relocatable = test_is_trivially_relocatable_v<MultipleInheriter<Types...>>;
template <class... Types> static const bool inheritance_relocatability_matches_bases_v =
(test_is_trivially_relocatable_v<Types> && ...) == multiple_inheritance_is_relocatable<Types...>;

static_assert(multiple_inheritance_is_relocatable<S4, S5> == multiple_inheritance_is_relocatable<S5, S4>);
static_assert(inheritance_relocatability_matches_bases_v<S4, S5>);
static_assert(inheritance_relocatability_matches_bases_v<S5, S4>);

struct AA AddressDiscriminatedPolymorphicBase trivially_relocatable_if_eligible {
virtual void foo();
};

struct IA NoAddressDiscriminatedPolymorphicBase trivially_relocatable_if_eligible {
virtual void bar();
};

template <class T> struct UnionWrapper trivially_relocatable_if_eligible {
union U {
T field1;
} u;
};

static_assert(test_is_trivially_relocatable_v<AddressDiscriminatedPolymorphicBase>);
static_assert(test_is_trivially_relocatable_v<NoAddressDiscriminatedPolymorphicBase>);
static_assert(inheritance_relocatability_matches_bases_v<AddressDiscriminatedPolymorphicBase, NoAddressDiscriminatedPolymorphicBase>);
static_assert(inheritance_relocatability_matches_bases_v<NoAddressDiscriminatedPolymorphicBase, AddressDiscriminatedPolymorphicBase>);

static_assert(!test_is_trivially_relocatable_v<UnionWrapper<AddressDiscriminatedPolymorphicBase>>);
static_assert(test_is_trivially_relocatable_v<UnionWrapper<NoAddressDiscriminatedPolymorphicBase>>);
static_assert(!test_is_trivially_relocatable_v<UnionWrapper<MultipleInheriter<NoAddressDiscriminatedPolymorphicBase, AddressDiscriminatedPolymorphicBase>>>);
static_assert(!test_is_trivially_relocatable_v<UnionWrapper<MultipleInheriter<AddressDiscriminatedPolymorphicBase, NoAddressDiscriminatedPolymorphicBase>>>);
109 changes: 109 additions & 0 deletions clang/test/SemaCXX/trivially-relocatable-ptrauth.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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 MultipleMembers1 {
NonAddressDiscPtrauth field0;
AddressDiscPtrauth field1;
};

static_assert(!__builtin_is_cpp_trivially_relocatable(MultipleMembers1));

struct MultipleMembers2 {
NonAddressDiscPtrauth field0;
NonAddressDiscPtrauth field1;
};

static_assert(__builtin_is_cpp_trivially_relocatable(MultipleMembers2));

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