[c++20] For P1327R1: support dynamic_cast in constant expression

evaluation.

llvm-svn: 360806
This commit is contained in:
Richard Smith 2019-05-15 20:22:21 +00:00
parent 750a45fe25
commit 7bd54ab586
3 changed files with 277 additions and 66 deletions

View File

@ -34,8 +34,15 @@ def note_constexpr_virtual_call : Note<
"cannot evaluate call to virtual function in a constant expression">;
def note_constexpr_pure_virtual_call : Note<
"pure virtual function %q0 called">;
def note_constexpr_virtual_out_of_lifetime : Note<
"virtual function called on object '%0' whose dynamic type is not constant">;
def note_constexpr_polymorphic_unknown_dynamic_type : Note<
"%select{||||virtual function called on|dynamic_cast applied to}0 "
"object '%1' whose dynamic type is not constant">;
def note_constexpr_dynamic_cast_to_reference_failed : Note<
"reference dynamic_cast failed: %select{"
"static type %1 of operand is a non-public base class of dynamic type %2|"
"dynamic type %2 of operand does not have a base class of type %3|"
"%3 is an ambiguous base class of dynamic type %2 of operand|"
"%3 is a non-public base class of dynamic type %2 of operand}0">;
def note_constexpr_virtual_base : Note<
"cannot construct object of type %0 with virtual base class "
"in a constant expression">;
@ -100,10 +107,12 @@ def note_constexpr_this : Note<
"%select{|implicit }0use of 'this' pointer is only allowed within the "
"evaluation of a call to a 'constexpr' member function">;
def note_constexpr_lifetime_ended : Note<
"%select{read of|assignment to|increment of|decrement of|member call on}0 "
"%select{read of|assignment to|increment of|decrement of|member call on|"
"dynamic_cast of}0 "
"%select{temporary|variable}1 whose lifetime has ended">;
def note_constexpr_access_uninit : Note<
"%select{read of|assignment to|increment of|decrement of|member call on}0 "
"%select{read of|assignment to|increment of|decrement of|member call on|"
"dynamic_cast of}0 "
"object outside its lifetime is not allowed in a constant expression">;
def note_constexpr_use_uninit_reference : Note<
"use of reference outside its lifetime "
@ -112,11 +121,11 @@ def note_constexpr_modify_const_type : Note<
"modification of object of const-qualified type %0 is not allowed "
"in a constant expression">;
def note_constexpr_access_volatile_type : Note<
"%select{read of|assignment to|increment of|decrement of|<ERROR>}0 "
"%select{read of|assignment to|increment of|decrement of|<ERROR>|<ERROR>}0 "
"volatile-qualified type %1 is not allowed in a constant expression">;
def note_constexpr_access_volatile_obj : Note<
"%select{read of|assignment to|increment of|decrement of|<ERROR>}0 volatile "
"%select{temporary|object %2|member %2}1 is not allowed in "
"%select{read of|assignment to|increment of|decrement of|<ERROR>|<ERROR>}0 "
"volatile %select{temporary|object %2|member %2}1 is not allowed in "
"a constant expression">;
def note_constexpr_volatile_here : Note<
"volatile %select{temporary created|object declared|member declared}0 here">;
@ -129,21 +138,26 @@ def note_constexpr_ltor_non_constexpr : Note<
def note_constexpr_ltor_incomplete_type : Note<
"read of incomplete type %0 is not allowed in a constant expression">;
def note_constexpr_access_null : Note<
"%select{read of|assignment to|increment of|decrement of|member call on}0 "
"%select{read of|assignment to|increment of|decrement of|member call on|"
"dynamic_cast of}0 "
"dereferenced null pointer is not allowed in a constant expression">;
def note_constexpr_access_past_end : Note<
"%select{read of|assignment to|increment of|decrement of|member call on}0 "
"%select{read of|assignment to|increment of|decrement of|member call on|"
"dynamic_cast of}0 "
"dereferenced one-past-the-end pointer is not allowed in a constant expression">;
def note_constexpr_access_unsized_array : Note<
"%select{read of|assignment to|increment of|decrement of|member call on}0 "
"%select{read of|assignment to|increment of|decrement of|member call on|"
"dynamic_cast of}0 "
"element of array without known bound "
"is not allowed in a constant expression">;
def note_constexpr_access_inactive_union_member : Note<
"%select{read of|assignment to|increment of|decrement of|member call on}0 "
"%select{read of|assignment to|increment of|decrement of|member call on|"
"dynamic_cast of}0 "
"member %1 of union with %select{active member %3|no active member}2 "
"is not allowed in a constant expression">;
def note_constexpr_access_static_temporary : Note<
"%select{read of|assignment to|increment of|decrement of|<ERROR>}0 temporary "
"%select{read of|assignment to|increment of|decrement of|member call on|"
"dynamic_cast of}0 temporary "
"is not allowed in a constant expression outside the expression that "
"created the temporary">;
def note_constexpr_modify_global : Note<

View File

@ -1337,10 +1337,26 @@ enum AccessKinds {
AK_Increment,
AK_Decrement,
AK_MemberCall,
AK_DynamicCast,
};
static bool isModification(AccessKinds AK) {
return AK != AK_Read && AK != AK_MemberCall;
switch (AK) {
case AK_Read:
case AK_MemberCall:
case AK_DynamicCast:
return false;
case AK_Assign:
case AK_Increment:
case AK_Decrement:
return true;
}
llvm_unreachable("unknown access kind");
}
/// Is this an access per the C++ definition?
static bool isFormalAccess(AccessKinds AK) {
return AK == AK_Read || isModification(AK);
}
namespace {
@ -2961,8 +2977,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
// If this is our last pass, check that the final object type is OK.
if (I == N || (I == N - 1 && ObjType->isAnyComplexType())) {
// Accesses to volatile objects are prohibited.
if (ObjType.isVolatileQualified() &&
handler.AccessKind != AK_MemberCall) {
if (ObjType.isVolatileQualified() && isFormalAccess(handler.AccessKind)) {
if (Info.getLangOpts().CPlusPlus) {
int DiagKind;
SourceLocation Loc;
@ -3272,11 +3287,13 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
}
}
bool IsAccess = isFormalAccess(AK);
// C++11 DR1311: An lvalue-to-rvalue conversion on a volatile-qualified type
// is not a constant expression (even if the object is non-volatile). We also
// apply this rule to C++98, in order to conform to the expected 'volatile'
// semantics.
if (AK != AK_MemberCall && LValType.isVolatileQualified()) {
if (IsAccess && LValType.isVolatileQualified()) {
if (Info.getLangOpts().CPlusPlus)
Info.FFDiag(E, diag::note_constexpr_access_volatile_type)
<< AK << LValType;
@ -3285,13 +3302,6 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
return CompleteObject();
}
// The wording is unclear on this, but for the purpose of determining the
// validity of a member function call, we assume that all objects whose
// lifetimes did not start within the constant evaluation are in fact within
// their lifetimes, so member calls on them are valid. (This simultaneously
// includes all members of a union!)
bool NeedValue = AK != AK_MemberCall;
// Compute value storage location and type of base object.
APValue *BaseVal = nullptr;
QualType BaseType = getType(LVal.Base);
@ -3335,7 +3345,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
if (!(BaseType.isConstQualified() ||
(Info.getLangOpts().OpenCL &&
BaseType.getAddressSpace() == LangAS::opencl_constant))) {
if (!NeedValue)
if (!IsAccess)
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
if (Info.getLangOpts().CPlusPlus) {
Info.FFDiag(E, diag::note_constexpr_ltor_non_const_int, 1) << VD;
@ -3345,7 +3355,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
}
return CompleteObject();
}
} else if (!NeedValue) {
} else if (!IsAccess) {
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
} else if (BaseType->isFloatingType() && BaseType.isConstQualified()) {
// We support folding of const floating-point types, in order to make
@ -3406,7 +3416,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
if (!(BaseType.isConstQualified() &&
BaseType->isIntegralOrEnumerationType()) &&
!(VD && VD->getCanonicalDecl() == ED->getCanonicalDecl())) {
if (!NeedValue)
if (!IsAccess)
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
Info.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK;
Info.Note(MTE->getExprLoc(), diag::note_constexpr_temporary_here);
@ -3416,7 +3426,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
BaseVal = Info.Ctx.getMaterializedTemporaryValue(MTE, false);
assert(BaseVal && "got reference to unevaluated temporary");
} else {
if (!NeedValue)
if (!IsAccess)
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
Info.FFDiag(E);
return CompleteObject();
@ -4521,8 +4531,8 @@ static bool CheckConstexprFunction(EvalInfo &Info, SourceLocation CallLoc,
}
namespace {
struct CheckMemberCallThisPointerHandler {
static const AccessKinds AccessKind = AK_MemberCall;
struct CheckDynamicTypeHandler {
AccessKinds AccessKind;
typedef bool result_type;
bool failed() { return false; }
bool found(APValue &Subobj, QualType SubobjType) { return true; }
@ -4531,17 +4541,14 @@ struct CheckMemberCallThisPointerHandler {
};
} // end anonymous namespace
const AccessKinds CheckMemberCallThisPointerHandler::AccessKind;
/// Check that the pointee of the 'this' pointer in a member function call is
/// either within its lifetime or in its period of construction or destruction.
static bool checkMemberCallThisPointer(EvalInfo &Info, const Expr *E,
const LValue &This, bool IsVirtual) {
/// Check that we can access the notional vptr of an object / determine its
/// dynamic type.
static bool checkDynamicType(EvalInfo &Info, const Expr *E, const LValue &This,
AccessKinds AK, bool Polymorphic) {
if (This.Designator.Invalid)
return false;
CompleteObject Obj =
findCompleteObject(Info, E, AK_MemberCall, This, QualType());
CompleteObject Obj = findCompleteObject(Info, E, AK, This, QualType());
if (!Obj)
return false;
@ -4555,26 +4562,33 @@ static bool checkMemberCallThisPointer(EvalInfo &Info, const Expr *E,
Info.FFDiag(E, This.Designator.isOnePastTheEnd()
? diag::note_constexpr_access_past_end
: diag::note_constexpr_access_unsized_array)
<< AK_MemberCall;
<< AK;
return false;
} else if (IsVirtual) {
// Conservatively refuse to perform a virtual function call if we would
} else if (Polymorphic) {
// Conservatively refuse to perform a polymorphic operation if we would
// not be able to read a notional 'vptr' value.
APValue Val;
This.moveInto(Val);
QualType StarThisType =
Info.Ctx.getLValueReferenceType(This.Designator.getType(Info.Ctx));
Info.FFDiag(E, diag::note_constexpr_virtual_out_of_lifetime)
<< Val.getAsString(Info.Ctx, StarThisType);
Info.FFDiag(E, diag::note_constexpr_polymorphic_unknown_dynamic_type)
<< AK << Val.getAsString(Info.Ctx, StarThisType);
return false;
}
return true;
}
CheckMemberCallThisPointerHandler Handler;
CheckDynamicTypeHandler Handler{AK};
return Obj && findSubobject(Info, E, Obj, This.Designator, Handler);
}
/// Check that the pointee of the 'this' pointer in a member function call is
/// either within its lifetime or in its period of construction or destruction.
static bool checkNonVirtualMemberCallThisPointer(EvalInfo &Info, const Expr *E,
const LValue &This) {
return checkDynamicType(Info, E, This, AK_MemberCall, false);
}
struct DynamicType {
/// The dynamic class type of the object.
const CXXRecordDecl *Type;
@ -4592,14 +4606,27 @@ static const CXXRecordDecl *getBaseClassType(SubobjectDesignator &Designator,
}
/// Determine the dynamic type of an object.
static Optional<DynamicType> ComputeDynamicType(EvalInfo &Info, LValue &This) {
static Optional<DynamicType> ComputeDynamicType(EvalInfo &Info, const Expr *E,
LValue &This, AccessKinds AK) {
// If we don't have an lvalue denoting an object of class type, there is no
// meaningful dynamic type. (We consider objects of non-class type to have no
// dynamic type.)
if (This.Designator.IsOnePastTheEnd || This.Designator.Invalid ||
!This.Designator.MostDerivedType->getAsCXXRecordDecl())
if (!checkDynamicType(Info, E, This, AK, true))
return None;
// Refuse to compute a dynamic type in the presence of virtual bases. This
// shouldn't happen other than in constant-folding situations, since literal
// types can't have virtual bases.
//
// Note that consumers of DynamicType assume that the type has no virtual
// bases, and will need modifications if this restriction is relaxed.
const CXXRecordDecl *Class =
This.Designator.MostDerivedType->getAsCXXRecordDecl();
if (!Class || Class->getNumVBases()) {
Info.FFDiag(E);
return None;
}
// FIXME: For very deep class hierarchies, it might be beneficial to use a
// binary search here instead. But the overwhelmingly common case is that
// we're not in the middle of a constructor, so it probably doesn't matter
@ -4625,6 +4652,7 @@ static Optional<DynamicType> ComputeDynamicType(EvalInfo &Info, LValue &This) {
// CWG issue 1517: we're constructing a base class of the object described by
// 'This', so that object has not yet begun its period of construction and
// any polymorphic operation on it results in undefined behavior.
Info.FFDiag(E);
return None;
}
@ -4632,11 +4660,10 @@ static Optional<DynamicType> ComputeDynamicType(EvalInfo &Info, LValue &This) {
static const CXXMethodDecl *HandleVirtualDispatch(
EvalInfo &Info, const Expr *E, LValue &This, const CXXMethodDecl *Found,
llvm::SmallVectorImpl<QualType> &CovariantAdjustmentPath) {
Optional<DynamicType> DynType = ComputeDynamicType(Info, This);
if (!DynType) {
Info.FFDiag(E);
Optional<DynamicType> DynType =
ComputeDynamicType(Info, E, This, AK_MemberCall);
if (!DynType)
return nullptr;
}
// Find the final overrider. It must be declared in one of the classes on the
// path from the dynamic type to the static type.
@ -4646,11 +4673,6 @@ static const CXXMethodDecl *HandleVirtualDispatch(
unsigned PathLength = DynType->PathLength;
for (/**/; PathLength <= This.Designator.Entries.size(); ++PathLength) {
const CXXRecordDecl *Class = getBaseClassType(This.Designator, PathLength);
if (Class->getNumVBases()) {
Info.FFDiag(E);
return nullptr;
}
const CXXMethodDecl *Overrider =
Found->getCorrespondingMethodDeclaredInClass(Class, false);
if (Overrider) {
@ -4724,6 +4746,117 @@ static bool HandleCovariantReturnAdjustment(EvalInfo &Info, const Expr *E,
return true;
}
/// Determine whether \p Base, which is known to be a direct base class of
/// \p Derived, is a public base class.
static bool isBaseClassPublic(const CXXRecordDecl *Derived,
const CXXRecordDecl *Base) {
for (const CXXBaseSpecifier &BaseSpec : Derived->bases()) {
auto *BaseClass = BaseSpec.getType()->getAsCXXRecordDecl();
if (BaseClass && declaresSameEntity(BaseClass, Base))
return BaseSpec.getAccessSpecifier() == AS_public;
}
llvm_unreachable("Base is not a direct base of Derived");
}
/// Apply the given dynamic cast operation on the provided lvalue.
///
/// This implements the hard case of dynamic_cast, requiring a "runtime check"
/// to find a suitable target subobject.
static bool HandleDynamicCast(EvalInfo &Info, const ExplicitCastExpr *E,
LValue &Ptr) {
// We can't do anything with a non-symbolic pointer value.
SubobjectDesignator &D = Ptr.Designator;
if (D.Invalid)
return false;
// C++ [expr.dynamic.cast]p6:
// If v is a null pointer value, the result is a null pointer value.
if (Ptr.isNullPointer() && !E->isGLValue())
return true;
// For all the other cases, we need the pointer to point to an object within
// its lifetime / period of construction / destruction, and we need to know
// its dynamic type.
Optional<DynamicType> DynType =
ComputeDynamicType(Info, E, Ptr, AK_DynamicCast);
if (!DynType)
return false;
// C++ [expr.dynamic.cast]p7:
// If T is "pointer to cv void", then the result is a pointer to the most
// derived object
if (E->getType()->isVoidPointerType())
return CastToDerivedClass(Info, E, Ptr, DynType->Type, DynType->PathLength);
const CXXRecordDecl *C = E->getTypeAsWritten()->getPointeeCXXRecordDecl();
assert(C && "dynamic_cast target is not void pointer nor class");
CanQualType CQT = Info.Ctx.getCanonicalType(Info.Ctx.getRecordType(C));
auto RuntimeCheckFailed = [&] (CXXBasePaths *Paths) {
// C++ [expr.dynamic.cast]p9:
if (!E->isGLValue()) {
// The value of a failed cast to pointer type is the null pointer value
// of the required result type.
auto TargetVal = Info.Ctx.getTargetNullPointerValue(E->getType());
Ptr.setNull(E->getType(), TargetVal);
return true;
}
// A failed cast to reference type throws [...] std::bad_cast.
unsigned DiagKind;
if (!Paths && (declaresSameEntity(DynType->Type, C) ||
DynType->Type->isDerivedFrom(C)))
DiagKind = 0;
else if (!Paths || Paths->begin() == Paths->end())
DiagKind = 1;
else if (Paths->isAmbiguous(CQT))
DiagKind = 2;
else {
assert(Paths->front().Access != AS_public && "why did the cast fail?");
DiagKind = 3;
}
Info.FFDiag(E, diag::note_constexpr_dynamic_cast_to_reference_failed)
<< DiagKind << Ptr.Designator.getType(Info.Ctx)
<< Info.Ctx.getRecordType(DynType->Type)
<< E->getType().getUnqualifiedType();
return false;
};
// Runtime check, phase 1:
// Walk from the base subobject towards the derived object looking for the
// target type.
for (int PathLength = Ptr.Designator.Entries.size();
PathLength >= (int)DynType->PathLength; --PathLength) {
const CXXRecordDecl *Class = getBaseClassType(Ptr.Designator, PathLength);
if (declaresSameEntity(Class, C))
return CastToDerivedClass(Info, E, Ptr, Class, PathLength);
// We can only walk across public inheritance edges.
if (PathLength > (int)DynType->PathLength &&
!isBaseClassPublic(getBaseClassType(Ptr.Designator, PathLength - 1),
Class))
return RuntimeCheckFailed(nullptr);
}
// Runtime check, phase 2:
// Search the dynamic type for an unambiguous public base of type C.
CXXBasePaths Paths(/*FindAmbiguities=*/true,
/*RecordPaths=*/true, /*DetectVirtual=*/false);
if (DynType->Type->isDerivedFrom(C, Paths) && !Paths.isAmbiguous(CQT) &&
Paths.front().Access == AS_public) {
// Downcast to the dynamic type...
if (!CastToDerivedClass(Info, E, Ptr, DynType->Type, DynType->PathLength))
return false;
// ... then upcast to the chosen base class subobject.
for (CXXBasePathElement &Elem : Paths.front())
if (!HandleLValueBase(Info, E, Ptr, Elem.Class, Elem.Base))
return false;
return true;
}
// Otherwise, the runtime check fails.
return RuntimeCheckFailed(&Paths);
}
/// Determine if a class has any fields that might need to be copied by a
/// trivial copy or move operation.
static bool hasFields(const CXXRecordDecl *RD) {
@ -5128,7 +5261,8 @@ public:
return static_cast<Derived*>(this)->VisitCastExpr(E);
}
bool VisitCXXDynamicCastExpr(const CXXDynamicCastExpr *E) {
CCEDiag(E, diag::note_constexpr_invalid_cast) << 1;
if (!Info.Ctx.getLangOpts().CPlusPlus2a)
CCEDiag(E, diag::note_constexpr_invalid_cast) << 1;
return static_cast<Derived*>(this)->VisitCastExpr(E);
}
@ -5314,16 +5448,17 @@ public:
SmallVector<QualType, 4> CovariantAdjustmentPath;
if (This) {
auto *NamedMember = dyn_cast<CXXMethodDecl>(FD);
bool IsVirtual = NamedMember && NamedMember->isVirtual() && !HasQualifier;
// Check that the 'this' pointer points to an object of the right type.
if (!checkMemberCallThisPointer(Info, E, *This, IsVirtual))
return false;
// Perform virtual dispatch, if necessary.
if (IsVirtual && !(FD = HandleVirtualDispatch(Info, E, *This, NamedMember,
CovariantAdjustmentPath)))
return true;
if (NamedMember && NamedMember->isVirtual() && !HasQualifier) {
// Perform virtual dispatch, if necessary.
FD = HandleVirtualDispatch(Info, E, *This, NamedMember,
CovariantAdjustmentPath);
if (!FD)
return false;
} else {
// Check that the 'this' pointer points to an object of the right type.
if (!checkNonVirtualMemberCallThisPointer(Info, E, *This))
return false;
}
}
const FunctionDecl *Definition = nullptr;
@ -5691,6 +5826,11 @@ public:
if (!Visit(E->getSubExpr()))
return false;
return HandleBaseToDerivedCast(Info, E, Result);
case CK_Dynamic:
if (!Visit(E->getSubExpr()))
return false;
return HandleDynamicCast(Info, cast<ExplicitCastExpr>(E), Result);
}
}
};
@ -6275,6 +6415,11 @@ bool PointerExprEvaluator::VisitCastExpr(const CastExpr *E) {
return true;
return HandleBaseToDerivedCast(Info, E, Result);
case CK_Dynamic:
if (!Visit(E->getSubExpr()))
return false;
return HandleDynamicCast(Info, cast<ExplicitCastExpr>(E), Result);
case CK_NullToPointer:
VisitIgnoredValue(E->getSubExpr());
return ZeroInitialization(E);

View File

@ -302,3 +302,55 @@ namespace Virtual {
struct PureVirtualCall : Abstract { void f(); }; // expected-note {{in call to 'Abstract}}
constexpr PureVirtualCall pure_virtual_call; // expected-error {{constant expression}} expected-note {{in call to 'PureVirtualCall}}
}
namespace DynamicCast {
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 {};
constexpr G g;
// During construction of C, A is unambiguous subobject of dynamic type C.
static_assert(g.c == (C*)&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*>(&g)) == nullptr);
// dynamic_cast<void*> produces a pointer to the object of the dynamic type.
static_assert(g.f == (void*)(F*)&g);
static_assert(dynamic_cast<const void*>(static_cast<const D*>(&g)) == &g);
// expected-note@+1 {{reference dynamic_cast failed: 'DynamicCast::A' is an ambiguous base class of dynamic type 'DynamicCast::G' of operand}}
constexpr int d_a = (dynamic_cast<const A&>(static_cast<const D&>(g)), 0); // expected-error {{}}
// Can navigate from A2 to its A...
static_assert(&dynamic_cast<A&>((A2&)(B&)g) == &(A&)(B&)g);
// ... and from B to its A ...
static_assert(&dynamic_cast<A&>((B&)g) == &(A&)(B&)g);
// ... but not from D.
// expected-note@+1 {{reference dynamic_cast failed: 'DynamicCast::A' is an ambiguous base class of dynamic type 'DynamicCast::G' of operand}}
static_assert(&dynamic_cast<A&>((D&)g) == &(A&)(B&)g); // expected-error {{}}
// Can cast from A2 to sibling class D.
static_assert(&dynamic_cast<D&>((A2&)(B&)g) == &(D&)g);
// Cannot cast from private base E to derived class F.
// expected-note@+1 {{reference dynamic_cast failed: static type 'DynamicCast::E' of operand is a non-public base class of dynamic type 'DynamicCast::G'}}
constexpr int e_f = (dynamic_cast<F&>((E&)g), 0); // expected-error {{}}
// Cannot cast from B to private sibling E.
// expected-note@+1 {{reference dynamic_cast failed: 'DynamicCast::E' is a non-public base class of dynamic type 'DynamicCast::G' of operand}}
constexpr int b_e = (dynamic_cast<E&>((B&)g), 0); // expected-error {{}}
struct Unrelated { virtual void unrelated(); };
// expected-note@+1 {{reference dynamic_cast failed: dynamic type 'DynamicCast::G' of operand does not have a base class of type 'DynamicCast::Unrelated'}}
constexpr int b_unrelated = (dynamic_cast<Unrelated&>((B&)g), 0); // expected-error {{}}
// expected-note@+1 {{reference dynamic_cast failed: dynamic type 'DynamicCast::G' of operand does not have a base class of type 'DynamicCast::Unrelated'}}
constexpr int e_unrelated = (dynamic_cast<Unrelated&>((E&)g), 0); // expected-error {{}}
}