https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/203489
>From 8fd11880c12b38452adbca1d8f249d98d9cb8b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Thu, 11 Jun 2026 09:49:34 +0200 Subject: [PATCH] asdf --- clang/lib/AST/ByteCode/Compiler.cpp | 21 +- clang/lib/AST/ByteCode/Interp.cpp | 214 +++++++++++++++- clang/lib/AST/ByteCode/Interp.h | 3 +- clang/lib/AST/ByteCode/InterpState.h | 2 + clang/lib/AST/ByteCode/Opcodes.td | 4 +- clang/lib/AST/ByteCode/Pointer.h | 1 + clang/test/AST/ByteCode/dynamic-cast.cpp | 296 +++++++++++++++++++++++ 7 files changed, 522 insertions(+), 19 deletions(-) create mode 100644 clang/test/AST/ByteCode/dynamic-cast.cpp diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 8ea42fea03bee..638e6ecafb295 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -823,9 +823,7 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *E) { return discard(SubExpr); case CK_Dynamic: - // This initially goes through VisitCXXDynamicCastExpr, where we emit - // a diagnostic if appropriate. - return this->delegate(SubExpr); + llvm_unreachable("CXXDynamicCastExpr has its own function"); case CK_LValueBitCast: if (!this->emitInvalidCast(CastKind::ReinterpretLike, /*Fatal=*/false, E)) @@ -3543,16 +3541,29 @@ bool Compiler<Emitter>::VisitCXXReinterpretCastExpr( template <class Emitter> bool Compiler<Emitter>::VisitCXXDynamicCastExpr(const CXXDynamicCastExpr *E) { - if (!Ctx.getLangOpts().CPlusPlus20) { if (!this->emitInvalidCast(CastKind::Dynamic, /*Fatal=*/false, E)) return false; + } + + if (E->getCastKind() != CK_Dynamic) return this->VisitCastExpr(E); + + QualType DestType = E->getType(); + // "target type must be a reference or pointer type to a defined class" + if (DestType->isRecordType()) { + assert(E->isGLValue()); + } else { + assert(DestType->isPointerOrReferenceType()); + assert(DestType->isVoidPointerType() || + DestType->getPointeeType()->isRecordType()); + DestType = DestType->getPointeeType(); } if (!this->visit(E->getSubExpr())) return false; - if (!this->emitCheckDynamicCast(E)) + if (!this->emitDynamicCast(DestType.getTypePtr(), + /*IsReferenceCast=*/E->isGLValue(), E)) return false; if (DiscardResult) diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 0a4a84edd62a4..6b831cc69d819 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1843,8 +1843,10 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func, if (Func->isDestructor() && !CheckDestructor(S, OpPC, ThisPtr)) return false; - if (Func->isConstructor() || Func->isDestructor()) + if (Func->isConstructor() || Func->isDestructor()) { + S.InitializingPtrs.push_back(ThisPtr.view()); S.InitializingBlocks.push_back(ThisPtr.block()); + } } if (!Func->isFullyCompiled()) @@ -1873,8 +1875,10 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func, // have a caller set. bool Success = Interpret(S); // Remove initializing block again. - if (Func->isConstructor() || Func->isDestructor()) + if (Func->isConstructor() || Func->isDestructor()) { S.InitializingBlocks.pop_back(); + S.InitializingPtrs.pop_back(); + } if (!Success) { InterpFrame::free(NewFrame); @@ -1917,19 +1921,205 @@ static bool getDynamicDecl(InterpState &S, CodePtr OpPC, Pointer TypePtr, return DynamicDecl != nullptr; } -bool CheckDynamicCast(InterpState &S, CodePtr OpPC) { - const auto &Ptr = S.Stk.peek<Pointer>(); +struct DynamicCastResult { + UnsignedOrNone Offset = std::nullopt; + bool Ambiguous = false; + + bool valid() const { return !Ambiguous && Offset; } + + void setOffset(unsigned O) { + if (!Offset) + Offset = O; + else { + Ambiguous = true; + } + } + + void merge(DynamicCastResult C) { + Ambiguous |= C.Ambiguous; + if (C.Offset) { + if (!Offset) + Offset = C.Offset; + else + Ambiguous = true; + } + } +}; + +// Walk UP the type hierarchy, starting at the decl of R to find Needle. +static DynamicCastResult findRecordBase(const ASTContext &Ctx, const Record *R, + QualType Needle) { + DynamicCastResult Res; + + if (Ctx.hasSimilarType(Needle, Ctx.getCanonicalTagType(R->getDecl()))) + Res.setOffset(0); + + for (const Record::Base &B : R->bases()) { + auto N = findRecordBase(Ctx, B.R, Needle); + if (N.Offset) + N.Offset = *N.Offset + B.Offset; + Res.merge(N); + } + + return Res; +} + +bool DynamicCast(InterpState &S, CodePtr OpPC, const Type *DestTypePtr, + bool IsReferenceCast) { + const auto &Ptr = S.Stk.pop<Pointer>(); + QualType TargetType = QualType(DestTypePtr, 0); + + if (Ptr.isConstexprUnknown()) { + QualType T = Ptr.getType(); + const Expr *E = S.Current->getExpr(OpPC); + APValue V = Ptr.toAPValue(S.getASTContext()); + QualType TT = S.getASTContext().getLValueReferenceType(T); + S.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type) + << AK_DynamicCast << V.getAsString(S.getASTContext(), TT); + return false; + } - if (!Ptr.isConstexprUnknown()) + // TODO: Other checks? + if (!Ptr.isBlockPointer()) + return false; + + // Our given pointer, limited by the base that's currently being initialized, + // if any. + PtrView LimitedPtr; + if (S.InitializingPtrs.empty()) { + LimitedPtr = Ptr.stripBaseCasts().view(); + } else { + // FIXME: Is this always the correct block? + LimitedPtr = S.InitializingPtrs.back(); + assert(LimitedPtr.block() == Ptr.block()); + } + assert(LimitedPtr.getRecord()); + + // C++ [expr.dynamic.cast]p7: + // If T is "pointer to cv void", then the result is a pointer to the most + // derived object + if (TargetType->isVoidType()) { + S.Stk.push<Pointer>(LimitedPtr); return true; + } - QualType T = Ptr.getType(); - const Expr *E = S.Current->getExpr(OpPC); - APValue V = Ptr.toAPValue(S.getASTContext()); - QualType TT = S.getASTContext().getLValueReferenceType(T); - S.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type) - << AK_DynamicCast << V.getAsString(S.getASTContext(), TT); - return false; + assert(!TargetType.isNull()); + assert(!TargetType->isVoidType()); + assert(TargetType->isRecordType()); + + // Helper lambdas. + auto typesMatch = [&](QualType A, QualType B) -> bool { + return S.getASTContext().hasSimilarType(A, B); + }; + auto getRecord = [](PtrView P) -> const CXXRecordDecl * { + assert(P.getRecord()); + return cast<CXXRecordDecl>(P.getRecord()->getDecl()); + }; + + auto baseIsPrivate = [&](PtrView P) -> bool { + if (P.isRoot() || !P.isBaseClass()) + return false; + + CXXBasePaths Paths; + getRecord(P.getBase())->isDerivedFrom(getRecord(P), Paths); + assert(std::distance(Paths.begin(), Paths.end()) == 1); + + return Paths.front().Access == AS_private; + }; + + enum { + DiagPrivateBase = 0, + DiagNoBase = 1, + DiagAmbiguous = 2, + DiagPrivateSibling = 3 + }; + + auto diag = [&](int DiagKind, QualType ResultType) -> bool { + // Pointer casts return nullptr on failure. + if (!IsReferenceCast) { + S.Stk.push<Pointer>(0, DestTypePtr); + return true; + } + QualType DynamicType = LimitedPtr.getType()->getCanonicalTypeUnqualified(); + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_dynamic_cast_to_reference_failed) + << DiagKind << ResultType << DynamicType << TargetType; + return false; + }; + + // Check if Ptr's dynamic type is derived from our target type at all. + // If it isn't, diagnose this as "operand does not have base class of type + // [...]". + { + CXXBasePaths Paths; + getRecord(LimitedPtr) + ->isDerivedFrom(TargetType->getAsCXXRecordDecl(), Paths); + if (std::distance(Paths.begin(), Paths.end()) == 0 && + !typesMatch(LimitedPtr.getType(), TargetType)) { + return diag(DiagNoBase, TargetType); + } + } + + // Current base is already private. + if (baseIsPrivate(Ptr.view())) + return diag(DiagPrivateBase, Ptr.getType()); + + std::optional<PtrView> Result; + // First, check simple downcasts without ambiguities. + for (PtrView Iter = Ptr.view();;) { + if (typesMatch(TargetType, Iter.getType())) { + Result = Iter; + break; + } + // Moving DOWN the type hierarchy. + Iter = Iter.getBase(); + if (Iter.isRoot() || !Iter.isBaseClass()) + break; + } + + // Simply walking down the type hierarchy has produced a valid result, use + // that. + if (Result) { + if (baseIsPrivate(*Result)) + return diag(DiagPrivateBase, Result->getType()); + S.Stk.push<Pointer>(*Result); + return true; + } + + // Otherwise, we need to do a deep hierarchy check. + bool Ambiguous = false; + for (PtrView Iter = LimitedPtr;;) { + // If we can move up the hierarchy from this level and reach the target type + // unambiguously, we're fine. + auto R = findRecordBase(S.getASTContext(), Iter.getRecord(), TargetType); + + if (R.valid()) { + Result = Iter.atField(*R.Offset); + break; + } else if (R.Ambiguous) { + Ambiguous = true; + break; + } + + // This moves us DOWN the type hierarchy. + Iter = Iter.getBase(); + if (Iter.isRoot() || !Iter.isBaseClass()) + break; + } + + if (Ambiguous) + return diag(DiagAmbiguous, TargetType); + + if (Result) { + // Might still be invalid due to resulting in a private base though. + if (baseIsPrivate(*Result)) + return diag(DiagPrivateSibling, TargetType); + S.Stk.push<Pointer>(*Result); + return true; + } + + // We couldn't find the requested base. + return diag(DiagNoBase, TargetType); } bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func, diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index b6377e22f0db6..a062d43e23906 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -143,7 +143,8 @@ bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC, bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I); bool isConstexprUnknown(const Pointer &P); bool isConstexprUnknown(const Block *B); -bool CheckDynamicCast(InterpState &S, CodePtr OpPC); +bool DynamicCast(InterpState &S, CodePtr OpPC, const Type *DestType, + bool IsReferenceCast); enum class ShiftDir { Left, Right }; diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h index b6ef985095907..345a12b49c867 100644 --- a/clang/lib/AST/ByteCode/InterpState.h +++ b/clang/lib/AST/ByteCode/InterpState.h @@ -175,6 +175,8 @@ class InterpState final : public State, public SourceMapper { /// List of blocks we're currently running either constructors or destructors /// for. llvm::SmallVector<const Block *> InitializingBlocks; + // FIXME: We clearly dont' need two lists here, just one is enough. + llvm::SmallVector<PtrView> InitializingPtrs; }; class InterpStateCCOverride final { diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 09c616aa2ff1d..d944c964cc919 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -849,7 +849,9 @@ def SideEffect : Opcode {} def InvalidCast : Opcode { let Args = [ArgCastKind, ArgBool]; } -def CheckDynamicCast : Opcode {} +def DynamicCast : Opcode { + let Args = [ArgTypePtr, ArgBool]; +} def InvalidStore : Opcode { let Args = [ArgTypePtr]; } def CheckPseudoDtor : Opcode {} diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h index 4c7951979630a..0fd8082c73d69 100644 --- a/clang/lib/AST/ByteCode/Pointer.h +++ b/clang/lib/AST/ByteCode/Pointer.h @@ -53,6 +53,7 @@ struct PtrView { bool inUnion() const { return getInlineDesc()->InUnion; }; bool inArray() const { return getFieldDesc()->IsArray; } bool inPrimitiveArray() const { return getFieldDesc()->isPrimitiveArray(); } + const Block *block() const { return Pointee; } unsigned getEvalID() { return Pointee->getEvalID(); } diff --git a/clang/test/AST/ByteCode/dynamic-cast.cpp b/clang/test/AST/ByteCode/dynamic-cast.cpp new file mode 100644 index 0000000000000..b782920eb8763 --- /dev/null +++ b/clang/test/AST/ByteCode/dynamic-cast.cpp @@ -0,0 +1,296 @@ +// RUN: %clang_cc1 -verify=ref,both -std=c++26 %s +// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both -std=c++26 %s + +namespace Simple { + + struct S {}; + constexpr S ss; + constexpr int foo = (dynamic_cast<const S &>(ss), 0); + + struct S1 { virtual void a(); }; + struct S2 : S1 {}; + constexpr S2 s{}; + static_assert(dynamic_cast<const S2*>(static_cast<const S1*>(&s)) == &s); +} + +namespace Failing { +} + +namespace NotSoSimple { + struct A2 { virtual void a2(); }; + struct D { + virtual void d(); + }; + struct F : A2, D { + void *f = dynamic_cast<void*>( static_cast<D*>(this) ); + }; + constexpr F g; + static_assert(g.f == (void*)(F*)&g); + + constexpr D* d = (D*)&g; + constexpr void* f = dynamic_cast<void*>(d); + static_assert(f == &g); +} + + +namespace Again { + struct A3 {}; + struct A4 {}; + + struct A2 : A3, A4 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct C2 { virtual void c2(); }; + struct C : A, C2 { A4 *c = dynamic_cast<A4*>(static_cast<C2*>(this)); }; + + struct D { virtual void d(); }; + + struct F : C, D{}; + struct G : F {}; + constexpr G g; + static_assert(g.c == (C*)&g); +} + +namespace FailedReference { + struct A {}; + struct K : A {}; + struct P : A {}; + + struct L{virtual void p();}; + + struct S : P, K, L { + virtual void p(); + }; + constexpr S s{}; + static_assert(&dynamic_cast<const A&>((L&)s) == nullptr); // both-error {{not an integral constant expression}} \ + // both-note {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'FailedReference::S' of operand}} +} + +namespace FailedReference2 { + struct A2 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct B : A {}; + struct C2 { virtual void c2(); }; + struct C : A, C2 { A *c = dynamic_cast<A*>(static_cast<C2*>(this)); }; + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D, private E { void *f = dynamic_cast<void*>(static_cast<D*>(this)); }; + struct G : F {}; + + constexpr G g; + static_assert(&dynamic_cast<A&>((D&)g) == nullptr); // both-error {{not an integral constant expression}} \ + // both-note {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'FailedReference2::G' of operand}} +} + +namespace FailedPtr { + struct A {}; + + struct B : A {}; + + struct C2 { virtual void c2(); }; + + struct C : A, C2 {}; + struct F : B, C {}; + constexpr F g; + static_assert(dynamic_cast<const A*>(static_cast<const C2*>(&g)) == nullptr); + static_assert(dynamic_cast<const B*>(static_cast<const C2*>(&g)) != nullptr); +} + +namespace Initializing { + struct A2 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct B : A {}; + struct C2 { virtual void c2(); }; + struct C : A, C2 { A *c = dynamic_cast<A*>(static_cast<C2*>(this)); }; + + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D {}; + struct Padding { virtual void padding(); }; + struct G : Padding, F {}; + + constexpr G g; + + // During construction of C, A is unambiguous subobject of dynamic type C. + static_assert(g.c == (C*)&g); +} + +namespace SimpleDowncast { + struct A2 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct B : A {}; + struct C2 { virtual void c2(); }; + struct C : A, C2 {}; + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D {}; + struct Padding { virtual void padding(); }; + struct G : Padding, F {}; + + constexpr G g; + // Can navigate from A2 to its A... + static_assert(&dynamic_cast<A&>((A2&)(B&)g) == &(A&)(B&)g); +} + +namespace ActuallyADerived2BaseCast { + struct A2 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct B : A {}; + struct C2 { virtual void c2(); }; + struct C : A, C2 {}; + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D {}; + struct Padding { virtual void padding(); }; + struct G : Padding, F {}; + constexpr G g; + // ... and from B to its A ... + static_assert(&dynamic_cast<A2&>((B&)g) == &(A2&)(B&)g); +} + +namespace ProperLimitedPtrInVoidCast { + struct A2 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct B : A {}; + struct C2 { virtual void c2(); }; + struct C : A, C2 {}; + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D, private E { void *f = dynamic_cast<void*>(static_cast<D*>(this)); }; + struct Padding { virtual void padding(); }; + struct G : Padding, F {}; + + constexpr G g; + static_assert(g.f == (void*)(F*)&g); +} + +namespace Unrelated { + struct A2 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct B : A {}; + struct C2 { virtual void c2(); }; + struct C : A, C2 {}; + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D, private E {}; + struct Padding { virtual void padding(); }; + struct G : Padding, F {}; + + constexpr G g; + struct Unrelated { virtual void unrelated(); }; + constexpr int b_unrelated = (dynamic_cast<Unrelated&>((B&)g), 0); // both-error {{must be initialized by a constant expression}} \ + // both-note {{reference dynamic_cast failed: dynamic type 'Unrelated::G' of operand does not have a base class of type 'Unrelated'}} + constexpr int e_unrelated = (dynamic_cast<Unrelated&>((E&)g), 0); // both-error {{must be initialized by a constant expression}} \ + // both-note {{reference dynamic_cast failed: dynamic type 'Unrelated::G' of operand does not have a base class of type 'Unrelated'}} + static_assert(dynamic_cast<Unrelated*>((B*)&g) == nullptr); + static_assert(dynamic_cast<Unrelated*>((E*)&g) == nullptr); +} + +namespace PrivateSibling { + struct A2 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct B : A {}; + struct C2 { virtual void c2(); }; + struct C : A, C2 {}; + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D, private E {}; + struct Padding { virtual void padding(); }; + struct G : Padding, F {}; + + constexpr G g; + // Cannot cast from B to private sibling E. + constexpr int b_e = (dynamic_cast<E&>((B&)g), 0); // both-error {{must be initialized by a constant expression}} \ + // both-note {{reference dynamic_cast failed: 'E' is a non-public base class of dynamic type 'PrivateSibling::G' of operand}} + static_assert(dynamic_cast<E*>((B*)&g) == nullptr); +} + +namespace Field { + struct X { + mutable int n = 0; + virtual constexpr ~X() {} + }; + struct Y : X { + }; + struct Z { + mutable Y y; + }; + constexpr Z z; + constexpr const X *pz = &z.y; + constexpr const Y *qz = dynamic_cast<const Y*>(pz); + static_assert(qz != nullptr); +} + +/// The entire DynamicCast test from constant-expression-cxx2a.cpp but the g variable is a field. +namespace Field2 { + struct A2 { virtual void a2(); }; + struct A : A2 { virtual void a(); }; + struct B : A {}; + struct C2 { virtual void c2(); }; + struct C : A, C2 { A *c = dynamic_cast<A*>(static_cast<C2*>(this)); }; + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D, private E { void *f = dynamic_cast<void*>(static_cast<D*>(this)); }; + struct Padding { virtual void padding(); }; + struct G : Padding, F {}; + + + struct SomeStruct { + int a; + int b; + G g; + }; + + constexpr SomeStruct ss{}; + + // During construction of C, A is unambiguous subobject of dynamic type C. + static_assert(ss.g.c == (C*)&ss.g); + // ... but in the complete object, the same is not true, so the runtime fails. + static_assert(dynamic_cast<const A*>(static_cast<const C2*>(&ss.g)) == nullptr); + + // dynamic_cast<void*> produces a pointer to the object of the dynamic type. + static_assert(ss.g.f == (void*)(F*)&ss.g); + static_assert(dynamic_cast<const void*>(static_cast<const D*>(&ss.g)) == &ss.g); + + // both-note@+1 {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'Field2::G' of operand}} + constexpr int d_a = (dynamic_cast<const A&>(static_cast<const D&>(ss.g)), 0); // both-error {{}} + + // Can navigate from A2 to its A... + static_assert(&dynamic_cast<A&>((A2&)(B&)ss.g) == &(A&)(B&)ss.g); + // ... and from B to its A ... + static_assert(&dynamic_cast<A&>((B&)ss.g) == &(A&)(B&)ss.g); + // ... but not from D. + // both-note@+1 {{reference dynamic_cast failed: 'A' is an ambiguous base class of dynamic type 'Field2::G' of operand}} + static_assert(&dynamic_cast<A&>((D&)ss.g) == &(A&)(B&)ss.g); // both-error {{}} + + // Can cast from A2 to sibling class D. + static_assert(&dynamic_cast<D&>((A2&)(B&)ss.g) == &(D&)ss.g); + + // Cannot cast from private base E to derived class F. + // both-note@+1 {{reference dynamic_cast failed: static type 'Field2::E' of operand is a non-public base class of dynamic type 'Field2::G'}} + constexpr int e_f = (dynamic_cast<F&>((E&)ss.g), 0); // both-error {{}} + + // Cannot cast from B to private sibling E. + // both-note@+1 {{reference dynamic_cast failed: 'E' is a non-public base class of dynamic type 'Field2::G' of operand}} + constexpr int b_e = (dynamic_cast<E&>((B&)ss.g), 0); // both-error {{}} + + struct Unrelated { virtual void unrelated(); }; + // both-note@+1 {{reference dynamic_cast failed: dynamic type 'Field2::G' of operand does not have a base class of type 'Unrelated'}} + constexpr int b_unrelated = (dynamic_cast<Unrelated&>((B&)ss.g), 0); // both-error {{}} + // both-note@+1 {{reference dynamic_cast failed: dynamic type 'Field2::G' of operand does not have a base class of type 'Unrelated'}} + constexpr int e_unrelated = (dynamic_cast<Unrelated&>((E&)ss.g), 0); // both-error {{}} +} + +namespace UnrelatedAndRootPtr{ + struct A { + virtual void d() {} + }; + struct B final : A { }; + struct C { }; + + constexpr bool f() { + B b; + C *bb = dynamic_cast<C*>(&b); + return !bb; + } + static_assert(f()); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
