Skip to content

[clang] Fix crash in dynamic_cast final class optimization #152076

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 5, 2025

This corrects the codegen for the final class optimization to correct handle the case where there is no path to perform the cast, and also corrects the codegen to handle ptrauth protected vtable pointers.

As part of this fix we separate out the path computation as that makes it easier to reason about the failure code paths and more importantly means we can know what the type of the this object is during the cast.

The allows us to use the GetVTablePointer interface which correctly performs the authentication operations required when pointer authentication is enabled.

There is one place where we still lose a fully authenticated path, and that is if there multiple paths from the source type to the destination type. In that case we're forced to perform a dynamic_cast to void* to find the primary base. As we do not know the primary base at this point we do not yet know the dynamic type of the adjusted this object and so cannot authenticate the vtable load. The approach this PR takes to mitigate this gap is to authenticate the vtable of the original object, and then if the stripped vtable pointer matches the expected type we then know the type of the object and so perform a fully authenticated load of the vtable from the resulting object.

Fixes #137518

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Aug 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 5, 2025

@llvm/pr-subscribers-clang

Author: Oliver Hunt (ojhunt)

Changes

This corrects the codegen for the final class optimization to correct handle the case where there is no path to perform the cast, and also corrects the codegen to handle ptrauth protected vtable pointers.

As part of this fix we separate out the path computation as that makes it easier to reason about the failure code paths and more importantly means we can know what the type of the this object is during the cast.

The allows us to use the GetVTablePointer interface which correctly performs the authentication operations required when pointer authentication is enabled.

There is one place where we still lose a fully authenticated path, and that is if there multiple paths from the source type to the destination type. In that case we're forced to perform a dynamic_cast to void* to find the primary base. As we do not know the primary base at this point we do not yet know the dynamic type of the adjusted this object and so cannot authenticate the vtable load. The approach this PR takes to mitigate this gap is to authenticate the vtable of the original object, and then if the stripped vtable pointer matches the expected type we then know the type of the object and so perform a fully authenticated load of the vtable from the resulting object.

Fixes #137518


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

5 Files Affected:

  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+8-5)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+100-29)
  • (modified) clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp (+16)
  • (modified) clang/test/CodeGenCXX/dynamic-cast-exact.cpp (+36-1)
  • (added) clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp (+135)
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index b8238a4702c4d..209678907138e 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -2344,11 +2344,14 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
   EmitBlock(CastEnd);
 
   if (CastNull) {
-    llvm::PHINode *PHI = Builder.CreatePHI(Value->getType(), 2);
-    PHI->addIncoming(Value, CastNotNull);
-    PHI->addIncoming(NullValue, CastNull);
-
-    Value = PHI;
+    if (CastNotNull) {
+      llvm::PHINode *PHI = Builder.CreatePHI(Value->getType(), 2);
+      PHI->addIncoming(Value, CastNotNull);
+      PHI->addIncoming(NullValue, CastNull);
+
+      Value = PHI;
+    } else
+      Value = NullValue;
   }
 
   return Value;
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index f4a99467010af..96305b1b0c1b0 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -226,6 +226,15 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
     return hasUniqueVTablePointer(DestRecordTy);
   }
 
+  struct ExactDynamicCastInfo {
+    bool RequiresCastToPrimaryBase;
+    std::optional<CharUnits> Offset;
+  };
+
+  ExactDynamicCastInfo getExactDynamicCastInfo(QualType SrcRecordTy,
+                                               QualType DestTy,
+                                               QualType DestRecordTy);
+
   llvm::Value *emitDynamicCastCall(CodeGenFunction &CGF, Address Value,
                                    QualType SrcRecordTy, QualType DestTy,
                                    QualType DestRecordTy,
@@ -1681,10 +1690,11 @@ llvm::Value *ItaniumCXXABI::emitDynamicCastCall(
   return Value;
 }
 
-llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
-    CodeGenFunction &CGF, Address ThisAddr, QualType SrcRecordTy,
-    QualType DestTy, QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
-    llvm::BasicBlock *CastFail) {
+ItaniumCXXABI::ExactDynamicCastInfo
+ItaniumCXXABI::getExactDynamicCastInfo(QualType SrcRecordTy, QualType DestTy,
+                                       QualType DestRecordTy) {
+  assert(shouldEmitExactDynamicCast(DestRecordTy));
+
   ASTContext &Context = getContext();
 
   // Find all the inheritance paths.
@@ -1722,41 +1732,102 @@ llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
     if (!Offset)
       Offset = PathOffset;
     else if (Offset != PathOffset) {
-      // Base appears in at least two different places. Find the most-derived
-      // object and see if it's a DestDecl. Note that the most-derived object
-      // must be at least as aligned as this base class subobject, and must
-      // have a vptr at offset 0.
-      ThisAddr = Address(emitDynamicCastToVoid(CGF, ThisAddr, SrcRecordTy),
-                         CGF.VoidPtrTy, ThisAddr.getAlignment());
-      SrcDecl = DestDecl;
-      Offset = CharUnits::Zero();
-      break;
+      // Base appears in at least two different places.
+      return ExactDynamicCastInfo{/*RequiresCastToPrimaryBase=*/true,
+                                  CharUnits::Zero()};
     }
   }
 
-  if (!Offset) {
+  return ExactDynamicCastInfo{/*RequiresCastToPrimaryBase=*/false, Offset};
+}
+
+llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
+    CodeGenFunction &CGF, Address ThisAddr, QualType SrcRecordTy,
+    QualType DestTy, QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
+    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);
+  };
+
+  ExactDynamicCastInfo ExactCastInfo =
+      getExactDynamicCastInfo(SrcRecordTy, DestTy, DestRecordTy);
+  if (!ExactCastInfo.Offset) {
     // If there are no public inheritance paths, the cast always fails.
+    AuthenticateVTable(ThisAddr, SrcDecl);
     CGF.EmitBranch(CastFail);
     return llvm::PoisonValue::get(CGF.VoidPtrTy);
   }
 
+  bool PerformPostCastAuthentication = false;
+  llvm::Value *VTable = nullptr;
+  if (ExactCastInfo.RequiresCastToPrimaryBase) {
+    // Base appears in at least two different places. Find the most-derived
+    // object and see if it's a DestDecl. Note that the most-derived object
+    // must be at least as aligned as this base class subobject, and must
+    // have a vptr at offset 0.
+    llvm::Value *PrimaryBase =
+        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);
+
   // Compare the vptr against the expected vptr for the destination type at
-  // this offset. Note that we do not know what type ThisAddr points to in
-  // the case where the derived class multiply inherits from the base class
-  // so we can't use GetVTablePtr, so we load the vptr directly instead.
-  llvm::Instruction *VPtr = CGF.Builder.CreateLoad(
-      ThisAddr.withElementType(CGF.VoidPtrPtrTy), "vtable");
-  CGM.DecorateInstructionWithTBAA(
-      VPtr, CGM.getTBAAVTablePtrAccessInfo(CGF.VoidPtrPtrTy));
-  llvm::Value *Success = CGF.Builder.CreateICmpEQ(
-      VPtr, getVTableAddressPoint(BaseSubobject(SrcDecl, *Offset), DestDecl));
-  llvm::Value *Result = ThisAddr.emitRawPointer(CGF);
-  if (!Offset->isZero())
-    Result = CGF.Builder.CreateInBoundsGEP(
-        CGF.CharTy, Result,
-        {llvm::ConstantInt::get(CGF.PtrDiffTy, -Offset->getQuantity())});
+  // this offset.
+  llvm::Constant *ExpectedVTable = getVTableAddressPoint(
+      BaseSubobject(SrcDecl, *ExactCastInfo.Offset), DestDecl);
+  llvm::Value *Success = CGF.Builder.CreateICmpEQ(VTable, ExpectedVTable);
+  llvm::Value *AdjustedThisPtr = ThisAddr.emitRawPointer(CGF);
+
+  if (!ExactCastInfo.Offset->isZero()) {
+    CharUnits::QuantityType Offset = ExactCastInfo.Offset->getQuantity();
+    llvm::Constant *OffsetConstant =
+        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 Result;
+  return AdjustedThisPtr;
 }
 
 llvm::Value *ItaniumCXXABI::emitDynamicCastToVoid(CodeGenFunction &CGF,
diff --git a/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp b/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
index 9a8ce1997a7f9..bf202d14c3398 100644
--- a/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
+++ b/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
@@ -13,3 +13,19 @@ B *exact(A *a) {
   // EXACT-NOT: call {{.*}} @__dynamic_cast
   return dynamic_cast<B*>(a);
 }
+
+struct C {
+  virtual ~C();
+};
+
+struct D final : private C {
+
+};
+
+// CHECK-LABEL: @_Z5exactP1C
+D *exact(C *a) {
+  // INEXACT: call {{.*}} @__dynamic_cast
+  // EXACT: entry:
+  // EXACT-NEXT: ret ptr null
+  return dynamic_cast<D*>(a);
+}
diff --git a/clang/test/CodeGenCXX/dynamic-cast-exact.cpp b/clang/test/CodeGenCXX/dynamic-cast-exact.cpp
index 86e1965b4ba68..adb9d6a6f9cf4 100644
--- a/clang/test/CodeGenCXX/dynamic-cast-exact.cpp
+++ b/clang/test/CodeGenCXX/dynamic-cast-exact.cpp
@@ -9,6 +9,7 @@ struct E : A { int e; };
 struct F : virtual A { int f; };
 struct G : virtual A { int g; };
 struct H final : C, D, E, F, G { int h; };
+struct H1 final: C, private D { int h1; };
 
 // CHECK-LABEL: @_Z7inexactP1A
 C *inexact(A *a) {
@@ -77,10 +78,44 @@ H *exact_multi(A *a) {
   return dynamic_cast<H*>(a);
 }
 
+// CHECK-LABEL: @_Z19exact_invalid_multiP1D
+H1 *exact_invalid_multi(D* d) {
+  // CHECK: dynamic_cast.end:
+  // CHECK-NEXT: ret ptr null
+  return dynamic_cast<H1*>(d);
+}
+
+// CHECK-LABEL: @_Z19exact_invalid_multiR1D
+H1 &exact_invalid_multi(D& d) {
+  // CHECK: dynamic_cast.notnull:
+  // CHECK-NEXT: br label %dynamic_cast.null
+  // CHECK: dynamic_cast.null:
+  // CHECK-NEXT: call void @__cxa_bad_cast()
+  // CHECK-NEXT: unreachable
+  return dynamic_cast<H1&>(d);
+}
+
+namespace GH137518 {
+  class base { virtual void fn() = 0; };
+  class test final : base { virtual void fn() { } };
+  test* new_test() { return new test(); }
+
+  // CHECK-LABEL: @_ZN8GH1375184castEPNS_4baseE(
+  test* cast(base* b) {
+  // CHECK: dynamic_cast.notnull:
+  // CHECK:   br label %dynamic_cast.null
+  // CHECK: dynamic_cast.null:
+  // CHECK:   br label %dynamic_cast.end
+  // CHECK: dynamic_cast.end:
+  // CHECK:   ret ptr null
+    return dynamic_cast<test*>(b);
+  }
+}
+
 namespace GH64088 {
   // Ensure we mark the B vtable as used here, because we're going to emit a
   // reference to it.
-  // CHECK: define {{.*}} @_ZN7GH640881BD0
+  // CHECK: define {{.*}} void @_ZN7GH640881BD0Ev(
   struct A { virtual ~A(); };
   struct B final : A { virtual ~B() = default; };
   B *cast(A *p) { return dynamic_cast<B*>(p); }
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..7ce5071aa5c4d
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp
@@ -0,0 +1,135 @@
+// 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: dynamic_cast.notnull:                             ; preds = %entry
+  // CHECK:   [[VPTR:%.*]] = load ptr, ptr %d, align 8, !tbaa !2
+  // CHECK:   [[VPTR_ADDRI:%.*]] = ptrtoint ptr %d to i64
+  // CHECK:   [[VPTR_DISC:%.*]] = tail call i64 @llvm.ptrauth.blend(i64 [[SRC_VPTR_ADDRI]], i64 12810)
+  // CHECK:   [[VPTRI:%.*]] = ptrtoint ptr [[VPTR]] to i64
+  // CHECK:   tail call i64 @llvm.ptrauth.auth(i64 [[VPTRI]], i32 2, i64 [[VPTR_DISC]])
+  // CHECK:   br label %dynamic_cast.end
+  // CHECK: dynamic_cast.end:
+  // CHECK:   ret ptr null
+  return dynamic_cast<M*>(d);
+}

@llvmbot
Copy link
Member

llvmbot commented Aug 5, 2025

@llvm/pr-subscribers-clang-codegen

Author: Oliver Hunt (ojhunt)

Changes

This corrects the codegen for the final class optimization to correct handle the case where there is no path to perform the cast, and also corrects the codegen to handle ptrauth protected vtable pointers.

As part of this fix we separate out the path computation as that makes it easier to reason about the failure code paths and more importantly means we can know what the type of the this object is during the cast.

The allows us to use the GetVTablePointer interface which correctly performs the authentication operations required when pointer authentication is enabled.

There is one place where we still lose a fully authenticated path, and that is if there multiple paths from the source type to the destination type. In that case we're forced to perform a dynamic_cast to void* to find the primary base. As we do not know the primary base at this point we do not yet know the dynamic type of the adjusted this object and so cannot authenticate the vtable load. The approach this PR takes to mitigate this gap is to authenticate the vtable of the original object, and then if the stripped vtable pointer matches the expected type we then know the type of the object and so perform a fully authenticated load of the vtable from the resulting object.

Fixes #137518


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

5 Files Affected:

  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+8-5)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+100-29)
  • (modified) clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp (+16)
  • (modified) clang/test/CodeGenCXX/dynamic-cast-exact.cpp (+36-1)
  • (added) clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp (+135)
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index b8238a4702c4d..209678907138e 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -2344,11 +2344,14 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
   EmitBlock(CastEnd);
 
   if (CastNull) {
-    llvm::PHINode *PHI = Builder.CreatePHI(Value->getType(), 2);
-    PHI->addIncoming(Value, CastNotNull);
-    PHI->addIncoming(NullValue, CastNull);
-
-    Value = PHI;
+    if (CastNotNull) {
+      llvm::PHINode *PHI = Builder.CreatePHI(Value->getType(), 2);
+      PHI->addIncoming(Value, CastNotNull);
+      PHI->addIncoming(NullValue, CastNull);
+
+      Value = PHI;
+    } else
+      Value = NullValue;
   }
 
   return Value;
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index f4a99467010af..96305b1b0c1b0 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -226,6 +226,15 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
     return hasUniqueVTablePointer(DestRecordTy);
   }
 
+  struct ExactDynamicCastInfo {
+    bool RequiresCastToPrimaryBase;
+    std::optional<CharUnits> Offset;
+  };
+
+  ExactDynamicCastInfo getExactDynamicCastInfo(QualType SrcRecordTy,
+                                               QualType DestTy,
+                                               QualType DestRecordTy);
+
   llvm::Value *emitDynamicCastCall(CodeGenFunction &CGF, Address Value,
                                    QualType SrcRecordTy, QualType DestTy,
                                    QualType DestRecordTy,
@@ -1681,10 +1690,11 @@ llvm::Value *ItaniumCXXABI::emitDynamicCastCall(
   return Value;
 }
 
-llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
-    CodeGenFunction &CGF, Address ThisAddr, QualType SrcRecordTy,
-    QualType DestTy, QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
-    llvm::BasicBlock *CastFail) {
+ItaniumCXXABI::ExactDynamicCastInfo
+ItaniumCXXABI::getExactDynamicCastInfo(QualType SrcRecordTy, QualType DestTy,
+                                       QualType DestRecordTy) {
+  assert(shouldEmitExactDynamicCast(DestRecordTy));
+
   ASTContext &Context = getContext();
 
   // Find all the inheritance paths.
@@ -1722,41 +1732,102 @@ llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
     if (!Offset)
       Offset = PathOffset;
     else if (Offset != PathOffset) {
-      // Base appears in at least two different places. Find the most-derived
-      // object and see if it's a DestDecl. Note that the most-derived object
-      // must be at least as aligned as this base class subobject, and must
-      // have a vptr at offset 0.
-      ThisAddr = Address(emitDynamicCastToVoid(CGF, ThisAddr, SrcRecordTy),
-                         CGF.VoidPtrTy, ThisAddr.getAlignment());
-      SrcDecl = DestDecl;
-      Offset = CharUnits::Zero();
-      break;
+      // Base appears in at least two different places.
+      return ExactDynamicCastInfo{/*RequiresCastToPrimaryBase=*/true,
+                                  CharUnits::Zero()};
     }
   }
 
-  if (!Offset) {
+  return ExactDynamicCastInfo{/*RequiresCastToPrimaryBase=*/false, Offset};
+}
+
+llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
+    CodeGenFunction &CGF, Address ThisAddr, QualType SrcRecordTy,
+    QualType DestTy, QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
+    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);
+  };
+
+  ExactDynamicCastInfo ExactCastInfo =
+      getExactDynamicCastInfo(SrcRecordTy, DestTy, DestRecordTy);
+  if (!ExactCastInfo.Offset) {
     // If there are no public inheritance paths, the cast always fails.
+    AuthenticateVTable(ThisAddr, SrcDecl);
     CGF.EmitBranch(CastFail);
     return llvm::PoisonValue::get(CGF.VoidPtrTy);
   }
 
+  bool PerformPostCastAuthentication = false;
+  llvm::Value *VTable = nullptr;
+  if (ExactCastInfo.RequiresCastToPrimaryBase) {
+    // Base appears in at least two different places. Find the most-derived
+    // object and see if it's a DestDecl. Note that the most-derived object
+    // must be at least as aligned as this base class subobject, and must
+    // have a vptr at offset 0.
+    llvm::Value *PrimaryBase =
+        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);
+
   // Compare the vptr against the expected vptr for the destination type at
-  // this offset. Note that we do not know what type ThisAddr points to in
-  // the case where the derived class multiply inherits from the base class
-  // so we can't use GetVTablePtr, so we load the vptr directly instead.
-  llvm::Instruction *VPtr = CGF.Builder.CreateLoad(
-      ThisAddr.withElementType(CGF.VoidPtrPtrTy), "vtable");
-  CGM.DecorateInstructionWithTBAA(
-      VPtr, CGM.getTBAAVTablePtrAccessInfo(CGF.VoidPtrPtrTy));
-  llvm::Value *Success = CGF.Builder.CreateICmpEQ(
-      VPtr, getVTableAddressPoint(BaseSubobject(SrcDecl, *Offset), DestDecl));
-  llvm::Value *Result = ThisAddr.emitRawPointer(CGF);
-  if (!Offset->isZero())
-    Result = CGF.Builder.CreateInBoundsGEP(
-        CGF.CharTy, Result,
-        {llvm::ConstantInt::get(CGF.PtrDiffTy, -Offset->getQuantity())});
+  // this offset.
+  llvm::Constant *ExpectedVTable = getVTableAddressPoint(
+      BaseSubobject(SrcDecl, *ExactCastInfo.Offset), DestDecl);
+  llvm::Value *Success = CGF.Builder.CreateICmpEQ(VTable, ExpectedVTable);
+  llvm::Value *AdjustedThisPtr = ThisAddr.emitRawPointer(CGF);
+
+  if (!ExactCastInfo.Offset->isZero()) {
+    CharUnits::QuantityType Offset = ExactCastInfo.Offset->getQuantity();
+    llvm::Constant *OffsetConstant =
+        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 Result;
+  return AdjustedThisPtr;
 }
 
 llvm::Value *ItaniumCXXABI::emitDynamicCastToVoid(CodeGenFunction &CGF,
diff --git a/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp b/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
index 9a8ce1997a7f9..bf202d14c3398 100644
--- a/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
+++ b/clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
@@ -13,3 +13,19 @@ B *exact(A *a) {
   // EXACT-NOT: call {{.*}} @__dynamic_cast
   return dynamic_cast<B*>(a);
 }
+
+struct C {
+  virtual ~C();
+};
+
+struct D final : private C {
+
+};
+
+// CHECK-LABEL: @_Z5exactP1C
+D *exact(C *a) {
+  // INEXACT: call {{.*}} @__dynamic_cast
+  // EXACT: entry:
+  // EXACT-NEXT: ret ptr null
+  return dynamic_cast<D*>(a);
+}
diff --git a/clang/test/CodeGenCXX/dynamic-cast-exact.cpp b/clang/test/CodeGenCXX/dynamic-cast-exact.cpp
index 86e1965b4ba68..adb9d6a6f9cf4 100644
--- a/clang/test/CodeGenCXX/dynamic-cast-exact.cpp
+++ b/clang/test/CodeGenCXX/dynamic-cast-exact.cpp
@@ -9,6 +9,7 @@ struct E : A { int e; };
 struct F : virtual A { int f; };
 struct G : virtual A { int g; };
 struct H final : C, D, E, F, G { int h; };
+struct H1 final: C, private D { int h1; };
 
 // CHECK-LABEL: @_Z7inexactP1A
 C *inexact(A *a) {
@@ -77,10 +78,44 @@ H *exact_multi(A *a) {
   return dynamic_cast<H*>(a);
 }
 
+// CHECK-LABEL: @_Z19exact_invalid_multiP1D
+H1 *exact_invalid_multi(D* d) {
+  // CHECK: dynamic_cast.end:
+  // CHECK-NEXT: ret ptr null
+  return dynamic_cast<H1*>(d);
+}
+
+// CHECK-LABEL: @_Z19exact_invalid_multiR1D
+H1 &exact_invalid_multi(D& d) {
+  // CHECK: dynamic_cast.notnull:
+  // CHECK-NEXT: br label %dynamic_cast.null
+  // CHECK: dynamic_cast.null:
+  // CHECK-NEXT: call void @__cxa_bad_cast()
+  // CHECK-NEXT: unreachable
+  return dynamic_cast<H1&>(d);
+}
+
+namespace GH137518 {
+  class base { virtual void fn() = 0; };
+  class test final : base { virtual void fn() { } };
+  test* new_test() { return new test(); }
+
+  // CHECK-LABEL: @_ZN8GH1375184castEPNS_4baseE(
+  test* cast(base* b) {
+  // CHECK: dynamic_cast.notnull:
+  // CHECK:   br label %dynamic_cast.null
+  // CHECK: dynamic_cast.null:
+  // CHECK:   br label %dynamic_cast.end
+  // CHECK: dynamic_cast.end:
+  // CHECK:   ret ptr null
+    return dynamic_cast<test*>(b);
+  }
+}
+
 namespace GH64088 {
   // Ensure we mark the B vtable as used here, because we're going to emit a
   // reference to it.
-  // CHECK: define {{.*}} @_ZN7GH640881BD0
+  // CHECK: define {{.*}} void @_ZN7GH640881BD0Ev(
   struct A { virtual ~A(); };
   struct B final : A { virtual ~B() = default; };
   B *cast(A *p) { return dynamic_cast<B*>(p); }
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..7ce5071aa5c4d
--- /dev/null
+++ b/clang/test/CodeGenCXX/ptrauth-dynamic-cast-exact.cpp
@@ -0,0 +1,135 @@
+// 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: dynamic_cast.notnull:                             ; preds = %entry
+  // CHECK:   [[VPTR:%.*]] = load ptr, ptr %d, align 8, !tbaa !2
+  // CHECK:   [[VPTR_ADDRI:%.*]] = ptrtoint ptr %d to i64
+  // CHECK:   [[VPTR_DISC:%.*]] = tail call i64 @llvm.ptrauth.blend(i64 [[SRC_VPTR_ADDRI]], i64 12810)
+  // CHECK:   [[VPTRI:%.*]] = ptrtoint ptr [[VPTR]] to i64
+  // CHECK:   tail call i64 @llvm.ptrauth.auth(i64 [[VPTRI]], i32 2, i64 [[VPTR_DISC]])
+  // CHECK:   br label %dynamic_cast.end
+  // CHECK: dynamic_cast.end:
+  // CHECK:   ret ptr null
+  return dynamic_cast<M*>(d);
+}

@ojhunt ojhunt force-pushed the users/ojhunt/pr-137518 branch from e5cc79d to 7af97ae Compare August 5, 2025 04:50
This corrects the codegen for the final class optimization to
correct handle the case where there is no path to perform the
cast, and also corrects the codegen to handle ptrauth protected
vtable pointers.

As part of this fix we separate out the path computation as
that makes it easier to reason about the failure code paths
and more importantly means we can know what the type of the
this object is during the cast.

The allows us to use the GetVTablePointer interface which
correctly performs the authentication operations required
when pointer authentication is enabled.

There is one place where we still lose a fully authenticated
path, and that is if there multiple paths from the source
type to the destination type. In that case we're forced to
perform a dynamic_cast to void* to find the primary base. As
we do not know the primary base at this point we do not yet
know the dynamic type of the adjusted this object and so cannot
authenticate the vtable load. The approach this PR takes to
mitigate this gap is to authenticate the vtable of the original
object, and then if the stripped vtable pointer matches the
expected type we then know the type of the object and so
perform a fully authenticated load of the vtable from the
resulting object.

Fixes #137518
@ojhunt ojhunt force-pushed the users/ojhunt/pr-137518 branch from 7af97ae to f04f6c3 Compare August 5, 2025 06:53
@ojhunt
Copy link
Contributor Author

ojhunt commented Aug 5, 2025

Removed the ugly conditional branch by making the separate path evaluation accessible from CGExpr and allow it to short circuit the casting logic once we know it will always fail. Given we know this path always fails an alternative might be to just remove it from the optimized path and instead emit a warning.

@cor3ntin cor3ntin requested a review from erichkeane August 5, 2025 08:36
Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

I don't have as good of a grasp on a couple of the things this touches, so hopefully @efriedma-quic or @rjmccall can take a look here. But I saw nothing to comment on.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

I think I'd prefer to split the pointer authentication changes into a separate patch; I'm not sure I'm comfortable reviewing that bit.

Other changes look fine.

if (!ExactCastInfo) {
llvm::Value *NullValue = EmitDynamicCastToNull(*this, DestTy);
if (!Builder.GetInsertBlock())
EmitBlock(createBasicBlock("dynamic_cast.always_fails"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe name this dynamic_cast.unreachable, since anything in the basic block is unreachable.

const CXXRecordDecl *SrcDecl = SrcRecordTy->getAsCXXRecordDecl();
const CXXRecordDecl *DestDecl = DestRecordTy->getAsCXXRecordDecl();
auto AuthenticateVTable = [&](Address ThisAddr, const CXXRecordDecl *Decl) {
if (!CGF.getLangOpts().PointerAuthCalls)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This PointerAuthCalls check is redundant?

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.

[clang] Regression: crash compiling dynamic_cast with final class and optimisation level higher than 0
4 participants