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

Merged
merged 1 commit into from
Aug 6, 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
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ Bug Fixes to C++ Support
- Fix a crash when deleting a pointer to an incomplete array (#GH150359).
- Fix an assertion failure when expression in assumption attribute
(``[[assume(expr)]]``) creates temporary objects.
- Fix the dynamic_cast to final class optimization to correctly handle
casts that are guaranteed to fail (#GH137518).

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
20 changes: 14 additions & 6 deletions clang/lib/CodeGen/CGCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,14 +294,22 @@ class CGCXXABI {
Address Value,
QualType SrcRecordTy) = 0;

struct ExactDynamicCastInfo {
bool RequiresCastToPrimaryBase;
CharUnits Offset;
};

virtual std::optional<ExactDynamicCastInfo>
getExactDynamicCastInfo(QualType SrcRecordTy, QualType DestTy,
QualType DestRecordTy) = 0;

/// 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 llvm::Value *emitExactDynamicCast(
CodeGenFunction &CGF, Address Value, QualType SrcRecordTy,
QualType DestTy, QualType DestRecordTy,
const ExactDynamicCastInfo &CastInfo, llvm::BasicBlock *CastSuccess,
llvm::BasicBlock *CastFail) = 0;

virtual bool EmitBadCastCall(CodeGenFunction &CGF) = 0;

Expand Down
15 changes: 14 additions & 1 deletion clang/lib/CodeGen/CGExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2295,6 +2295,18 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
CGM.getCXXABI().shouldEmitExactDynamicCast(DestRecordTy) &&
!getLangOpts().PointerAuthCalls;

std::optional<CGCXXABI::ExactDynamicCastInfo> ExactCastInfo;
if (IsExact) {
ExactCastInfo = CGM.getCXXABI().getExactDynamicCastInfo(SrcRecordTy, DestTy,
DestRecordTy);
if (!ExactCastInfo) {
llvm::Value *NullValue = EmitDynamicCastToNull(*this, DestTy);
if (!Builder.GetInsertBlock())
EmitBlock(createBasicBlock("dynamic_cast.unreachable"));
return NullValue;
}
}

// C++ [expr.dynamic.cast]p4:
// If the value of v is a null pointer value in the pointer case, the result
// is the null pointer value of type T.
Expand Down Expand Up @@ -2322,7 +2334,8 @@ llvm::Value *CodeGenFunction::EmitDynamicCast(Address ThisAddr,
// 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);
*this, ThisAddr, SrcRecordTy, DestTy, DestRecordTy, *ExactCastInfo,
CastEnd, CastNull);
} else {
assert(DestRecordTy->isRecordType() &&
"destination type must be a record type!");
Expand Down
87 changes: 54 additions & 33 deletions clang/lib/CodeGen/ItaniumCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
return hasUniqueVTablePointer(DestRecordTy);
}

std::optional<ExactDynamicCastInfo>
getExactDynamicCastInfo(QualType SrcRecordTy, QualType DestTy,
QualType DestRecordTy) override;

llvm::Value *emitDynamicCastCall(CodeGenFunction &CGF, Address Value,
QualType SrcRecordTy, QualType DestTy,
QualType DestRecordTy,
Expand All @@ -234,6 +238,7 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
llvm::Value *emitExactDynamicCast(CodeGenFunction &CGF, Address ThisAddr,
QualType SrcRecordTy, QualType DestTy,
QualType DestRecordTy,
const ExactDynamicCastInfo &CastInfo,
llvm::BasicBlock *CastSuccess,
llvm::BasicBlock *CastFail) override;

Expand Down Expand Up @@ -1681,10 +1686,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) {
std::optional<CGCXXABI::ExactDynamicCastInfo>
ItaniumCXXABI::getExactDynamicCastInfo(QualType SrcRecordTy, QualType DestTy,
QualType DestRecordTy) {
assert(shouldEmitExactDynamicCast(DestRecordTy));

ASTContext &Context = getContext();

// Find all the inheritance paths.
Expand Down Expand Up @@ -1722,41 +1728,56 @@ 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 std::nullopt;
return ExactDynamicCastInfo{/*RequiresCastToPrimaryBase=*/false, *Offset};
}

if (!Offset) {
// If there are no public inheritance paths, the cast always fails.
CGF.EmitBranch(CastFail);
return llvm::PoisonValue::get(CGF.VoidPtrTy);
}
llvm::Value *ItaniumCXXABI::emitExactDynamicCast(
CodeGenFunction &CGF, Address ThisAddr, QualType SrcRecordTy,
QualType DestTy, QualType DestRecordTy,
const ExactDynamicCastInfo &ExactCastInfo, llvm::BasicBlock *CastSuccess,
llvm::BasicBlock *CastFail) {
const CXXRecordDecl *SrcDecl = SrcRecordTy->getAsCXXRecordDecl();
const CXXRecordDecl *DestDecl = DestRecordTy->getAsCXXRecordDecl();

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;
Address VTablePtrPtr = ThisAddr.withElementType(CGF.VoidPtrPtrTy);
VTable = CGF.Builder.CreateLoad(VTablePtrPtr, "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);
}

CGF.Builder.CreateCondBr(Success, CastSuccess, CastFail);
return Result;
return AdjustedThisPtr;
}

llvm::Value *ItaniumCXXABI::emitDynamicCastToVoid(CodeGenFunction &CGF,
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/CodeGen/MicrosoftCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,15 @@ class MicrosoftCXXABI : public CGCXXABI {
// TODO: Add support for exact dynamic_casts.
return false;
}
std::optional<ExactDynamicCastInfo>
getExactDynamicCastInfo(QualType SrcRecordTy, QualType DestTy,
QualType DestRecordTy) override {
llvm_unreachable("unsupported");
}
llvm::Value *emitExactDynamicCast(CodeGenFunction &CGF, Address Value,
QualType SrcRecordTy, QualType DestTy,
QualType DestRecordTy,
const ExactDynamicCastInfo &CastInfo,
llvm::BasicBlock *CastSuccess,
llvm::BasicBlock *CastFail) override {
llvm_unreachable("unsupported");
Expand Down
16 changes: 16 additions & 0 deletions clang/test/CodeGenCXX/dynamic-cast-exact-disabled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,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);
}
42 changes: 41 additions & 1 deletion clang/test/CodeGenCXX/dynamic-cast-exact.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -77,10 +78,49 @@ H *exact_multi(A *a) {
return dynamic_cast<H*>(a);
}

// CHECK-LABEL: @_Z19exact_invalid_multiP1D
H1 *exact_invalid_multi(D* d) {
// CHECK: entry:
// CHECK-NEXT: %d.addr = alloca ptr
// CHECK-NEXT: store ptr %d, ptr %d.addr
// CHECK-NEXT: load ptr, ptr %d.addr
// CHECK-NEXT: ret ptr null
return dynamic_cast<H1*>(d);
}

// CHECK-LABEL: @_Z19exact_invalid_multiR1D
H1 &exact_invalid_multi(D& d) {
// CHECK: entry:
// CHECK-NEXT: %d.addr = alloca ptr
// CHECK-NEXT: store ptr %d, ptr %d.addr
// CHECK-NEXT: load ptr, ptr %d.addr
// CHECK-NEXT: call void @__cxa_bad_cast()
// CHECK-NEXT: unreachable
// CHECK: dynamic_cast.unreachable:
// CHECK-NEXT: ret ptr poison
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: entry:
// CHECK-NEXT: %b.addr = alloca ptr
// CHECK-NEXT: store ptr %b, ptr %b.addr
// CHECK-NEXT: load ptr, ptr %b.addr
// CHECK-NEXT: 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); }
Expand Down