Skip to content

[clang] Fix a crash when dynamic_cast-ing to a final class #148088

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 Jul 10, 2025

The verification pass correctly identifies that the phi node produced for an exact dynamic_cast that always fails. Rather than trying to construct pretend that we have pass and fail paths through an invalid exact dynamic_cast we handle the failure path explicitly.

Fixes #137518

The verification pass correctly identifies that the phi node produced
for an exact dynamic_cast that always fails. Rather than trying to
construct pretend that we have pass and fail paths through an invalid
exact dynamic_cast we handle the failure path explicitly.
@ojhunt ojhunt requested review from zygoloid and cor3ntin July 10, 2025 23:54
@ojhunt ojhunt self-assigned this Jul 10, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Jul 10, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 10, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-codegen

Author: Oliver Hunt (ojhunt)

Changes

The verification pass correctly identifies that the phi node produced for an exact dynamic_cast that always fails. Rather than trying to construct pretend that we have pass and fail paths through an invalid exact dynamic_cast we handle the failure path explicitly.


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

6 Files Affected:

  • (modified) clang/lib/CodeGen/CGCXXABI.h (+5-6)
  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+10-3)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+7-8)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+5-5)
  • (modified) clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp (+16)
  • (modified) clang/test/CodeGenCXX/dynamic-cast-exact.cpp (+18)
diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h
index 96fe04661944d..80aaacb56d436 100644
--- a/clang/lib/CodeGen/CGCXXABI.h
+++ b/clang/lib/CodeGen/CGCXXABI.h
@@ -296,12 +296,11 @@ class CGCXXABI {
 
   /// Emit a dynamic_cast from SrcRecordTy to DestRecordTy. The cast fails if
   /// the dynamic type of Value is not exactly DestRecordTy.
-  virtual llvm::Value *emitExactDynamicCast(CodeGenFunction &CGF, Address Value,
-                                            QualType SrcRecordTy,
-                                            QualType DestTy,
-                                            QualType DestRecordTy,
-                                            llvm::BasicBlock *CastSuccess,
-                                            llvm::BasicBlock *CastFail) = 0;
+  virtual std::optional<llvm::Value *>
+  emitExactDynamicCast(CodeGenFunction &CGF, Address Value,
+                       QualType SrcRecordTy, QualType DestTy,
+                       QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
+                       llvm::BasicBlock *CastFail) = 0;
 
   virtual bool EmitBadCastCall(CodeGenFunction &CGF) = 0;
 
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 359e30cb8f5cd..6ac3e27299e51 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -2341,8 +2341,15 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
   } else if (IsExact) {
     // If the destination type is effectively final, this pointer points to the
     // right type if and only if its vptr has the right value.
-    Value = CGM.getCXXABI().emitExactDynamicCast(
-        *this, ThisAddr, SrcRecordTy, DestTy, DestRecordTy, CastEnd, CastNull);
+    std::optional<llvm::Value *> ExactCast =
+        CGM.getCXXABI().emitExactDynamicCast(*this, ThisAddr, SrcRecordTy,
+                                             DestTy, DestRecordTy, CastEnd,
+                                             CastNull);
+    if (!ExactCast) {
+      Value = llvm::Constant::getNullValue(VoidPtrTy);
+      EmitBranch(CastNull);
+    } else
+      Value = *ExactCast;
   } else {
     assert(DestRecordTy->isRecordType() &&
            "destination type must be a record type!");
@@ -2364,7 +2371,7 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
 
   EmitBlock(CastEnd);
 
-  if (CastNull) {
+  if (CastNull && CastNotNull) {
     llvm::PHINode *PHI = Builder.CreatePHI(Value->getType(), 2);
     PHI->addIncoming(Value, CastNotNull);
     PHI->addIncoming(NullValue, CastNull);
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index f4a99467010af..73d188f6ab998 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -231,11 +231,11 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
                                    QualType DestRecordTy,
                                    llvm::BasicBlock *CastEnd) override;
 
-  llvm::Value *emitExactDynamicCast(CodeGenFunction &CGF, Address ThisAddr,
-                                    QualType SrcRecordTy, QualType DestTy,
-                                    QualType DestRecordTy,
-                                    llvm::BasicBlock *CastSuccess,
-                                    llvm::BasicBlock *CastFail) override;
+  std::optional<llvm::Value *>
+  emitExactDynamicCast(CodeGenFunction &CGF, Address ThisAddr,
+                       QualType SrcRecordTy, QualType DestTy,
+                       QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
+                       llvm::BasicBlock *CastFail) override;
 
   llvm::Value *emitDynamicCastToVoid(CodeGenFunction &CGF, Address Value,
                                      QualType SrcRecordTy) override;
@@ -1681,7 +1681,7 @@ llvm::Value *ItaniumCXXABI::emitDynamicCastCall(
   return Value;
 }
 
-llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
+std::optional<llvm::Value *> ItaniumCXXABI::emitExactDynamicCast(
     CodeGenFunction &CGF, Address ThisAddr, QualType SrcRecordTy,
     QualType DestTy, QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
     llvm::BasicBlock *CastFail) {
@@ -1736,8 +1736,7 @@ llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
 
   if (!Offset) {
     // If there are no public inheritance paths, the cast always fails.
-    CGF.EmitBranch(CastFail);
-    return llvm::PoisonValue::get(CGF.VoidPtrTy);
+    return std::nullopt;
   }
 
   // Compare the vptr against the expected vptr for the destination type at
diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index a181559834296..133a2ee1e2433 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -158,11 +158,11 @@ class MicrosoftCXXABI : public CGCXXABI {
     // TODO: Add support for exact dynamic_casts.
     return false;
   }
-  llvm::Value *emitExactDynamicCast(CodeGenFunction &CGF, Address Value,
-                                    QualType SrcRecordTy, QualType DestTy,
-                                    QualType DestRecordTy,
-                                    llvm::BasicBlock *CastSuccess,
-                                    llvm::BasicBlock *CastFail) override {
+  std::optional<llvm::Value *>
+  emitExactDynamicCast(CodeGenFunction &CGF, Address Value,
+                       QualType SrcRecordTy, QualType DestTy,
+                       QualType DestRecordTy, llvm::BasicBlock *CastSuccess,
+                       llvm::BasicBlock *CastFail) override {
     llvm_unreachable("unsupported");
   }
 
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..5fdb2f7eb3d75 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,6 +78,23 @@ 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 GH64088 {
   // Ensure we mark the B vtable as used here, because we're going to emit a
   // reference to it.

@cor3ntin cor3ntin changed the title [clang] Fix #137518 [clang] Jul 11, 2025
@cor3ntin
Copy link
Contributor

/
This change needs a release note.
Please add an entry to clang/docs/ReleaseNotes.rst in the section the most adapted to the change, and referencing any Github issue this change fixes. Thanks!

@cor3ntin cor3ntin changed the title [clang] [clang] Fix a crash when dynamic_cast-ing to a final class Jul 11, 2025
@cor3ntin cor3ntin requested a review from erichkeane July 11, 2025 06:16
Value = CGM.getCXXABI().emitExactDynamicCast(
*this, ThisAddr, SrcRecordTy, DestTy, DestRecordTy, CastEnd, CastNull);
std::optional<llvm::Value *> ExactCast =
CGM.getCXXABI().emitExactDynamicCast(*this, ThisAddr, SrcRecordTy,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we restructure this code in a way that's easier to read?

Like, maybe split emitExactDynamicCast into two functions: one to compute the offset, and one to actually do the cast. If we fail to compute the offset, we can skip all the work constructing the control flow, because the result is always just null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@efriedma-quic yeah, I was convinced that there must have been a reason this was structured in this way, but having looked at it again I can't work out how/why that would be the case.

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