Skip to content

[clang][PAC] Enable the PAC dynamic_cast to final class optimization #152601

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ Bug Fixes to C++ Support
(``[[assume(expr)]]``) creates temporary objects.
- Fix the dynamic_cast to final class optimization to correctly handle
casts that are guaranteed to fail (#GH137518).
- Support the dynamic_cast to final class optimization with pointer
authentication enabled.

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
3 changes: 1 addition & 2 deletions clang/lib/CodeGen/CGExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2292,8 +2292,7 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
bool IsExact = !IsDynamicCastToVoid &&
CGM.getCodeGenOpts().OptimizationLevel > 0 &&
DestRecordTy->getAsCXXRecordDecl()->isEffectivelyFinal() &&
CGM.getCXXABI().shouldEmitExactDynamicCast(DestRecordTy) &&
!getLangOpts().PointerAuthCalls;
CGM.getCXXABI().shouldEmitExactDynamicCast(DestRecordTy);

std::optional<CGCXXABI::ExactDynamicCastInfo> ExactCastInfo;
if (IsExact) {
Expand Down
39 changes: 39 additions & 0 deletions clang/lib/CodeGen/ItaniumCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1745,7 +1745,14 @@ llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
llvm::BasicBlock *CastFail) {
const CXXRecordDecl *SrcDecl = SrcRecordTy->getAsCXXRecordDecl();
const CXXRecordDecl *DestDecl = DestRecordTy->getAsCXXRecordDecl();
auto AuthenticateVTable = [&](Address ThisAddr, const CXXRecordDecl *Decl) {
if (!CGF.getLangOpts().PointerAuthCalls)
return;
(void)CGF.GetVTablePtr(ThisAddr, CGF.UnqualPtrTy, Decl,
CodeGenFunction::VTableAuthMode::MustTrap);
};

bool PerformPostCastAuthentication = false;
llvm::Value *VTable = nullptr;
if (ExactCastInfo.RequiresCastToPrimaryBase) {
// Base appears in at least two different places. Find the most-derived
Expand All @@ -1756,8 +1763,16 @@ llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
emitDynamicCastToVoid(CGF, ThisAddr, SrcRecordTy);
ThisAddr = Address(PrimaryBase, CGF.VoidPtrTy, ThisAddr.getAlignment());
SrcDecl = DestDecl;
// This unauthenticated load is unavoidable, so we're relying on the
// authenticated load in the dynamic cast to void, and we'll manually
// authenticate the resulting v-table at the end of the cast check.
PerformPostCastAuthentication = CGF.getLangOpts().PointerAuthCalls;
CGPointerAuthInfo StrippingAuthInfo(0, PointerAuthenticationMode::Strip,
false, false, nullptr);
Address VTablePtrPtr = ThisAddr.withElementType(CGF.VoidPtrPtrTy);
VTable = CGF.Builder.CreateLoad(VTablePtrPtr, "vtable");
if (PerformPostCastAuthentication)
VTable = CGF.EmitPointerAuthAuth(StrippingAuthInfo, VTable);
} else
VTable = CGF.GetVTablePtr(ThisAddr, CGF.UnqualPtrTy, SrcDecl);

Expand All @@ -1774,8 +1789,32 @@ llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
llvm::ConstantInt::get(CGF.PtrDiffTy, -Offset);
AdjustedThisPtr = CGF.Builder.CreateInBoundsGEP(CGF.CharTy, AdjustedThisPtr,
OffsetConstant);
PerformPostCastAuthentication = CGF.getLangOpts().PointerAuthCalls;
}

if (PerformPostCastAuthentication) {
// If we've changed the object pointer we authenticate the vtable pointer
// of the resulting object.
llvm::BasicBlock *NonNullBlock = CGF.Builder.GetInsertBlock();
llvm::BasicBlock *PostCastAuthSuccess =
CGF.createBasicBlock("dynamic_cast.postauth.success");
llvm::BasicBlock *PostCastAuthComplete =
CGF.createBasicBlock("dynamic_cast.postauth.complete");
CGF.Builder.CreateCondBr(Success, PostCastAuthSuccess,
PostCastAuthComplete);
CGF.EmitBlock(PostCastAuthSuccess);
Address AdjustedThisAddr =
Address(AdjustedThisPtr, CGF.IntPtrTy, CGF.getPointerAlign());
AuthenticateVTable(AdjustedThisAddr, DestDecl);
CGF.EmitBranch(PostCastAuthComplete);
CGF.EmitBlock(PostCastAuthComplete);
llvm::PHINode *PHI = CGF.Builder.CreatePHI(AdjustedThisPtr->getType(), 2);
PHI->addIncoming(AdjustedThisPtr, PostCastAuthSuccess);
llvm::Value *NullValue =
llvm::Constant::getNullValue(AdjustedThisPtr->getType());
PHI->addIncoming(NullValue, NonNullBlock);
AdjustedThisPtr = PHI;
}
CGF.Builder.CreateCondBr(Success, CastSuccess, CastFail);
return AdjustedThisPtr;
}
Expand Down
1 change: 0 additions & 1 deletion clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// RUN: %clang_cc1 -I%S %s -triple x86_64-apple-darwin10 -O1 -fvisibility=hidden -emit-llvm -std=c++11 -o - | FileCheck %s --check-prefixes=CHECK,INEXACT
// RUN: %clang_cc1 -I%S %s -triple x86_64-apple-darwin10 -O1 -fapple-kext -emit-llvm -std=c++11 -o - | FileCheck %s --check-prefixes=CHECK,INEXACT
// RUN: %clang_cc1 -I%S %s -triple x86_64-apple-darwin10 -O1 -fno-assume-unique-vtables -emit-llvm -std=c++11 -o - | FileCheck %s --check-prefixes=CHECK,INEXACT
// RUN: %clang_cc1 -I%S %s -triple arm64e-apple-darwin10 -O1 -fptrauth-calls -emit-llvm -std=c++11 -o - | FileCheck %s --check-prefixes=CHECK,INEXACT

struct A { virtual ~A(); };
struct B final : A { };
Expand Down
128 changes: 128 additions & 0 deletions clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// RUN: %clang_cc1 -I%S %s -triple arm64e-apple-darwin10 -O1 -fptrauth-calls -fptrauth-vtable-pointer-address-discrimination -fptrauth-vtable-pointer-type-discrimination -emit-llvm -std=c++11 -o - | FileCheck %s --check-prefixes=CHECK

struct A {
virtual ~A();
};
struct B {
int foo;
virtual ~B();
};
struct C final : A, B {
virtual void f(){};
};
struct D final : B, A {
virtual void f(){};
};

struct Offset {
virtual ~Offset();
};
struct E {
virtual ~E();
};
struct F final : Offset, E {
};
struct G {
virtual ~G();
int g;
};
struct H : E {
int h;
};
struct I : E {
int i;
};
struct J : virtual E {
int j;
};
struct K : virtual E {
int k;
};
struct L final : G, H, I, J, K {
int l;
};
struct M final: G, private H { int m; };

// CHECK-LABEL: @_Z10exact_to_CP1A
C *exact_to_C(A *a) {
// CHECK: [[UNAUTHED_VPTR:%.*]] = load ptr, ptr %a, align 8
// CHECK: [[VPTR_ADDRI:%.*]] = ptrtoint ptr %a to i64
// CHECK: [[VPTR_ADDR_DISC:%.*]] = tail call i64 @llvm.ptrauth.blend(i64 [[VPTR_ADDRI]], i64 62866)
// CHECK: [[UNAUTHED_VPTRI:%.*]] = ptrtoint ptr [[UNAUTHED_VPTR]] to i64
// CHECK: [[AUTHED_VPTRI:%.*]] = tail call i64 @llvm.ptrauth.auth(i64 [[UNAUTHED_VPTRI]], i32 2, i64 [[VPTR_ADDR_DISC]])
// CHECK: [[IS_EXPECTED:%.*]] = icmp eq i64 [[AUTHED_VPTRI]], ptrtoint (ptr getelementptr inbounds nuw inrange(-16, 24) (i8, ptr @_ZTV1C, i64 16) to i64)
// CHECK: br i1 [[IS_EXPECTED]], label %dynamic_cast.end, label %dynamic_cast.null
// CHECK: [[NULL_CHECKED_RESULT:%.*]] = phi ptr [ %a, %dynamic_cast.notnull ], [ null, %dynamic_cast.null ]
// CHECK: ret ptr [[NULL_CHECKED_RESULT]]
return dynamic_cast<C*>(a);
}

// CHECK-LABEL: @_Z9exact_t_DP1A
D *exact_t_D(A *a) {
// CHECK: dynamic_cast.notnull:
// CHECK: [[SRC_UNAUTHED_VPTR:%.*]] = load ptr, ptr %a
// CHECK: [[SRC_VPTR_ADDRI:%.*]] = ptrtoint ptr %a to i64
// CHECK: [[SRC_VPTR_DISC:%.*]] = tail call i64 @llvm.ptrauth.blend(i64 [[SRC_VPTR_ADDRI]], i64 62866)
// CHECK: [[SRC_UNAUTHED_VPTRI:%.*]] = ptrtoint ptr [[SRC_UNAUTHED_VPTR]] to i64
// CHECK: [[SRC_AUTHED_VPTRI:%.*]] = tail call i64 @llvm.ptrauth.auth(i64 [[SRC_UNAUTHED_VPTRI]], i32 2, i64 [[SRC_VPTR_DISC]])
// CHECK: [[SUCCESS:%.*]] = icmp eq i64 [[SRC_AUTHED_VPTRI]], ptrtoint (ptr getelementptr inbounds nuw inrange(-16, 16) (i8, ptr @_ZTV1D, i64 56) to i64)
// CHECK: br i1 [[SUCCESS]], label %dynamic_cast.postauth.success, label %dynamic_cast.postauth.complete
// CHECK: dynamic_cast.postauth.success:
// CHECK: [[ADJUSTED_THIS:%.*]] = getelementptr inbounds i8, ptr %a, i64 -16
// CHECK: [[ADJUSTED_UNAUTHED_VPTR:%.*]] = load ptr, ptr [[ADJUSTED_THIS]]
// CHECK: [[ADJUSTED_VPTR_ADDRI:%.*]] = ptrtoint ptr [[ADJUSTED_THIS]] to i64
// CHECK: [[ADJUSTED_VPTR_DISC:%.*]] = tail call i64 @llvm.ptrauth.blend(i64 [[ADJUSTED_VPTR_ADDRI]], i64 28965)
// CHECK: [[ADJUSTED_UNAUTHED_VPTRI:%.*]] = ptrtoint ptr [[ADJUSTED_UNAUTHED_VPTR]] to i64
// CHECK: [[ADJUSTED_AUTHED_VPTRI:%.*]] = tail call i64 @llvm.ptrauth.auth(i64 [[ADJUSTED_UNAUTHED_VPTRI]], i32 2, i64 [[ADJUSTED_VPTR_DISC]])
// CHECK: [[ADJUSTED_AUTHED_VPTR:%.*]] = inttoptr i64 [[ADJUSTED_AUTHED_VPTRI]] to ptr
// CHECK: br label %dynamic_cast.postauth.complete
// CHECK: dynamic_cast.postauth.complete:
// CHECK: [[AUTHED_ADJUSTED_THIS:%.*]] = phi ptr [ [[ADJUSTED_THIS]], %dynamic_cast.postauth.success ], [ null, %dynamic_cast.notnull ]
// CHECK: br i1 [[SUCCESS]], label %dynamic_cast.end, label %dynamic_cast.null
// CHECK: dynamic_cast.null:
// CHECK: br label %dynamic_cast.end
// CHECK: dynamic_cast.end:
// CHECK: [[RESULT:%.*]] = phi ptr [ [[AUTHED_ADJUSTED_THIS]], %dynamic_cast.postauth.complete ], [ null, %dynamic_cast.null ]
// CHECK: ret ptr [[RESULT]]
return dynamic_cast<D*>(a);
}

// CHECK-LABEL: @_Z11exact_multiP1E
L *exact_multi(E *e) {
// CHECK: dynamic_cast.notnull:
// CHECK: [[VTABLE_ADDR:%.*]] = load ptr, ptr %e, align 8
// CHECK: [[THIS_ADDRI:%.*]] = ptrtoint ptr %e to i64
// CHECK: [[VTABLE_DISC:%.*]] = tail call i64 @llvm.ptrauth.blend(i64 [[THIS_ADDRI]], i64 12810)
// CHECK: [[VTABLE_ADDRI:%.*]] = ptrtoint ptr [[VTABLE_ADDR]] to i64
// CHECK: [[AUTHED_VTABLEI:%.*]] = tail call i64 @llvm.ptrauth.auth(i64 [[VTABLE_ADDRI]], i32 2, i64 [[VTABLE_DISC]])
// CHECK: [[AUTHED_VTABLE:%.*]] = inttoptr i64 [[AUTHED_VTABLEI]] to ptr
// CHECK: [[PRIMARY_BASE_OFFSET:%.*]] = getelementptr inbounds i8, ptr [[AUTHED_VTABLE]], i64 -16
// CHECK: %offset.to.top = load i64, ptr [[PRIMARY_BASE_OFFSET]]
// CHECK: [[ADJUSTED_THIS:%.*]] = getelementptr inbounds i8, ptr %e, i64 %offset.to.top
// CHECK: [[ADJUSTED_THIS_VTABLE:%.*]] = load ptr, ptr [[ADJUSTED_THIS]]
// CHECK: [[ADJUSTED_THIS_VTABLEI:%.*]] = ptrtoint ptr [[ADJUSTED_THIS_VTABLE]] to i64
// CHECK: [[ADJUSTED_THIS_STRIPPED_VTABLEI:%.*]] = tail call i64 @llvm.ptrauth.strip(i64 [[ADJUSTED_THIS_VTABLEI]], i32 0)
// CHECK: [[SUCCESS:%.*]] = icmp eq i64 [[ADJUSTED_THIS_STRIPPED_VTABLEI]], ptrtoint (ptr getelementptr inbounds nuw inrange(-24, 16) (i8, ptr @_ZTV1L, i64 24) to i64)
// CHECK: br i1 [[SUCCESS]], label %dynamic_cast.postauth.success, label %dynamic_cast.postauth.complete
// CHECK: dynamic_cast.postauth.success:
// CHECK: [[ADJUSTED_THISI:%.*]] = ptrtoint ptr [[ADJUSTED_THIS]] to i64
// CHECK: [[DEST_DISC:%.*]] = tail call i64 @llvm.ptrauth.blend(i64 [[ADJUSTED_THISI]], i64 41434)
// CHECK: tail call i64 @llvm.ptrauth.auth(i64 [[ADJUSTED_THIS_VTABLEI]], i32 2, i64 [[DEST_DISC]])
// CHECK: br label %dynamic_cast.postauth.complete
// CHECK: dynamic_cast.postauth.complete:
// CHECK: [[AUTHED_ADJUSTED_THIS:%.*]] = phi ptr [ [[ADJUSTED_THIS]], %dynamic_cast.postauth.success ], [ null, %dynamic_cast.notnull ]
// CHECK: br i1 [[SUCCESS]], label %dynamic_cast.end, label %dynamic_cast.null
// CHECK: dynamic_cast.null:
// CHECK: br label %dynamic_cast.end
// CHECK: dynamic_cast.end:
// CHECK: [[RESULT:%.*]] = phi ptr [ [[AUTHED_ADJUSTED_THIS]], %dynamic_cast.postauth.complete ], [ null, %dynamic_cast.null ]
// CHECK: ret ptr [[RESULT]]
return dynamic_cast<L*>(e);
}

// CHECK-LABEL: @_Z19exact_invalid_multiP1H
M *exact_invalid_multi(H* d) {
// CHECK: entry:
// CHECK-NEXT: ret ptr null
return dynamic_cast<M*>(d);
}