diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 4b5679cccfb6..17256fbd80b0 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -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|}0 " + "%select{read of|assignment to|increment of|decrement of||}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|}0 volatile " - "%select{temporary|object %2|member %2}1 is not allowed in " + "%select{read of|assignment to|increment of|decrement of||}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|}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< diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 2c4cad1fff44..5bedddda57a6 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -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 ComputeDynamicType(EvalInfo &Info, LValue &This) { +static Optional 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 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 ComputeDynamicType(EvalInfo &Info, LValue &This) { static const CXXMethodDecl *HandleVirtualDispatch( EvalInfo &Info, const Expr *E, LValue &This, const CXXMethodDecl *Found, llvm::SmallVectorImpl &CovariantAdjustmentPath) { - Optional DynType = ComputeDynamicType(Info, This); - if (!DynType) { - Info.FFDiag(E); + Optional 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 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(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(this)->VisitCastExpr(E); } @@ -5314,16 +5448,17 @@ public: SmallVector CovariantAdjustmentPath; if (This) { auto *NamedMember = dyn_cast(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(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(E), Result); + case CK_NullToPointer: VisitIgnoredValue(E->getSubExpr()); return ZeroInitialization(E); diff --git a/clang/test/SemaCXX/constant-expression-cxx2a.cpp b/clang/test/SemaCXX/constant-expression-cxx2a.cpp index e8819edd4ee5..c49d670db3c8 100644 --- a/clang/test/SemaCXX/constant-expression-cxx2a.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx2a.cpp @@ -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(static_cast(this)); }; + struct D { virtual void d(); }; + struct E { virtual void e(); }; + struct F : B, C, D, private E { void *f = dynamic_cast(static_cast(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(static_cast(&g)) == nullptr); + + // dynamic_cast produces a pointer to the object of the dynamic type. + static_assert(g.f == (void*)(F*)&g); + static_assert(dynamic_cast(static_cast(&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(static_cast(g)), 0); // expected-error {{}} + + // Can navigate from A2 to its A... + static_assert(&dynamic_cast((A2&)(B&)g) == &(A&)(B&)g); + // ... and from B to its A ... + static_assert(&dynamic_cast((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((D&)g) == &(A&)(B&)g); // expected-error {{}} + + // Can cast from A2 to sibling class D. + static_assert(&dynamic_cast((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((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((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((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((E&)g), 0); // expected-error {{}} +}