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

Conversation

ojhunt
Copy link
Contributor

@ojhunt ojhunt commented Aug 7, 2025

Update the codegen for the the dynamic_cast to a final class optimization when pointer authentication is enabled.

Update the codegen for the the dynamic_cast to a final class
optimization when pointer authentication is enabled.
@ojhunt ojhunt requested review from asl and kovdan01 August 7, 2025 22:01
@ojhunt ojhunt self-assigned this Aug 7, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Aug 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 7, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-codegen

Author: Oliver Hunt (ojhunt)

Changes

Update the codegen for the the dynamic_cast to a final class optimization when pointer authentication is enabled.


Full diff: https://github.com/llvm/llvm-project/pull/152601.diff

5 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+1-2)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+39)
  • (modified) clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp (-1)
  • (added) clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp (+128)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 0e9fcaa5fac6a..94de70329ee34 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -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
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 49d5d8acbe331..afb9ab8a55c56 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -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) {
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 5ffc1edb9986f..f4c84c37ce72b 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -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
@@ -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);
 
@@ -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;
 }
diff --git a/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp b/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
index 3156e1bb21b1e..bf202d14c3398 100644
--- a/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
+++ b/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
@@ -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 { };
diff --git a/clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp b/clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp
new file mode 100644
index 0000000000000..1710ca5563380
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp
@@ -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);
+}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants