diff options
| -rw-r--r-- | clang/include/clang/Basic/DiagnosticASTKinds.td | 24 | ||||
| -rw-r--r-- | clang/lib/AST/ExprConstant.cpp | 155 | ||||
| -rw-r--r-- | clang/test/CXX/expr/expr.const/p2-0x.cpp | 4 | ||||
| -rw-r--r-- | clang/test/SemaCXX/constant-expression-cxx11.cpp | 76 |
4 files changed, 202 insertions, 57 deletions
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 7984e22fa67..8801461a7e5 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -67,13 +67,13 @@ def note_constexpr_past_end : Note< "%select{temporary|%2}1 is not a constant expression">; def note_constexpr_past_end_subobject : Note< "cannot %select{access base class of|access derived class of|access field of|" - "access array element of|ERROR|call member function on|" + "access array element of|ERROR|" "access real component of|access imaginary component of}0 " "pointer past the end of object">; def note_constexpr_null_subobject : Note< "cannot %select{access base class of|access derived class of|access field of|" "access array element of|perform pointer arithmetic on|" - "call member function on|access real component of|" + "access real component of|" "access imaginary component of}0 null pointer">; def note_constexpr_var_init_non_constant : Note< "initializer of %0 is not a constant expression">; @@ -96,10 +96,10 @@ 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}0 " + "%select{read of|assignment to|increment of|decrement of|member call on}0 " "%select{temporary|variable}1 whose lifetime has ended">; def note_constexpr_access_uninit : Note< - "%select{read of|assignment to|increment of|decrement of}0 " + "%select{read of|assignment to|increment of|decrement of|member call on}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 " @@ -108,10 +108,10 @@ 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|<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}0 volatile " + "%select{read of|assignment to|increment of|decrement of|<ERROR>}0 volatile " "%select{temporary|object %2|member %2}1 is not allowed in " "a constant expression">; def note_constexpr_volatile_here : Note< @@ -125,21 +125,21 @@ 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}0 " + "%select{read of|assignment to|increment of|decrement of|member call on}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}0 " + "%select{read of|assignment to|increment of|decrement of|member call on}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}0 " - "pointer to element of array without known bound " + "%select{read of|assignment to|increment of|decrement of|member call on}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}0 " + "%select{read of|assignment to|increment of|decrement of|member call on}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|<ERROR>}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 fa0fbbbf8dc..77f65337dde 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -213,7 +213,7 @@ namespace { // The order of this enum is important for diagnostics. enum CheckSubobjectKind { CSK_Base, CSK_Derived, CSK_Field, CSK_ArrayToPointer, CSK_ArrayIndex, - CSK_This, CSK_Real, CSK_Imag + CSK_Real, CSK_Imag }; /// A path from a glvalue to a subobject of that glvalue. @@ -1326,14 +1326,22 @@ void EvalInfo::addCallStack(unsigned Limit) { } } -/// Kinds of access we can perform on an object, for diagnostics. +/// Kinds of access we can perform on an object, for diagnostics. Note that +/// we consider a member function call to be a kind of access, even though +/// it is not formally an access of the object, because it has (largely) the +/// same set of semantic restrictions. enum AccessKinds { AK_Read, AK_Assign, AK_Increment, - AK_Decrement + AK_Decrement, + AK_MemberCall, }; +static bool isModification(AccessKinds AK) { + return AK != AK_Read && AK != AK_MemberCall; +} + namespace { struct ComplexValue { private: @@ -2820,6 +2828,31 @@ static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E, return false; } +static bool lifetimeStartedInEvaluation(EvalInfo &Info, + APValue::LValueBase Base) { + // A temporary we created. + if (Base.getCallIndex()) + return true; + + auto *Evaluating = Info.EvaluatingDecl.dyn_cast<const ValueDecl*>(); + if (!Evaluating) + return false; + + // The variable whose initializer we're evaluating. + if (auto *BaseD = Base.dyn_cast<const ValueDecl*>()) + if (declaresSameEntity(Evaluating, BaseD)) + return true; + + // A temporary lifetime-extended by the variable whose initializer we're + // evaluating. + if (auto *BaseE = Base.dyn_cast<const Expr *>()) + if (auto *BaseMTE = dyn_cast<MaterializeTemporaryExpr>(BaseE)) + if (declaresSameEntity(BaseMTE->getExtendingDecl(), Evaluating)) + return true; + + return false; +} + namespace { /// A handle to a complete object (an object that is not a subobject of /// another object). @@ -2830,17 +2863,21 @@ struct CompleteObject { APValue *Value; /// The type of the complete object. QualType Type; - bool LifetimeStartedInEvaluation; CompleteObject() : Value(nullptr) {} - CompleteObject(APValue::LValueBase Base, APValue *Value, QualType Type, - bool LifetimeStartedInEvaluation) - : Base(Base), Value(Value), Type(Type), - LifetimeStartedInEvaluation(LifetimeStartedInEvaluation) { - assert(Value && "missing value for complete object"); + CompleteObject(APValue::LValueBase Base, APValue *Value, QualType Type) + : Base(Base), Value(Value), Type(Type) {} + + bool mayReadMutableMembers(EvalInfo &Info) const { + // In C++14 onwards, it is permitted to read a mutable member whose + // lifetime began within the evaluation. + // FIXME: Should we also allow this in C++11? + if (!Info.getLangOpts().CPlusPlus14) + return false; + return lifetimeStartedInEvaluation(Info, Base); } - explicit operator bool() const { return Value; } + explicit operator bool() const { return !Type.isNull(); } }; } // end anonymous namespace @@ -2880,8 +2917,6 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, APValue *O = Obj.Value; QualType ObjType = Obj.Type; const FieldDecl *LastField = nullptr; - const bool MayReadMutableMembers = - Obj.LifetimeStartedInEvaluation && Info.getLangOpts().CPlusPlus14; const FieldDecl *VolatileField = nullptr; // Walk the designator's path to find the subobject. @@ -2910,7 +2945,8 @@ 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()) { + if (ObjType.isVolatileQualified() && + handler.AccessKind != AK_MemberCall) { if (Info.getLangOpts().CPlusPlus) { int DiagKind; SourceLocation Loc; @@ -2942,7 +2978,8 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, // cannot perform this read. (This only happens when performing a trivial // copy or assignment.) if (ObjType->isRecordType() && handler.AccessKind == AK_Read && - !MayReadMutableMembers && diagnoseUnreadableFields(Info, E, ObjType)) + !Obj.mayReadMutableMembers(Info) && + diagnoseUnreadableFields(Info, E, ObjType)) return handler.failed(); } @@ -2951,7 +2988,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, return false; // If we modified a bit-field, truncate it to the right width. - if (handler.AccessKind != AK_Read && + if (isModification(handler.AccessKind) && LastField && LastField->isBitField() && !truncateBitfieldValue(Info, E, *O, LastField)) return false; @@ -3010,11 +3047,8 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, : O->getComplexFloatReal(), ObjType); } } else if (const FieldDecl *Field = getAsField(Sub.Entries[I])) { - // In C++14 onwards, it is permitted to read a mutable member whose - // lifetime began within the evaluation. - // FIXME: Should we also allow this in C++11? if (Field->isMutable() && handler.AccessKind == AK_Read && - !MayReadMutableMembers) { + !Obj.mayReadMutableMembers(Info)) { Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1) << Field; Info.Note(Field->getLocation(), diag::note_declared_at); @@ -3226,7 +3260,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // 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 (LValType.isVolatileQualified()) { + if (AK != AK_MemberCall && LValType.isVolatileQualified()) { if (Info.getLangOpts().CPlusPlus) Info.FFDiag(E, diag::note_constexpr_access_volatile_type) << AK << LValType; @@ -3235,10 +3269,16 @@ 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); - bool LifetimeStartedInEvaluation = Frame; if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl*>()) { // In C++98, const, non-volatile integers initialized with ICEs are ICEs. @@ -3262,22 +3302,25 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // the variable we're reading must be const. if (!Frame) { if (Info.getLangOpts().CPlusPlus14 && - VD == Info.EvaluatingDecl.dyn_cast<const ValueDecl *>()) { + declaresSameEntity( + VD, Info.EvaluatingDecl.dyn_cast<const ValueDecl *>())) { // OK, we can read and modify an object if we're in the process of // evaluating its initializer, because its lifetime began in this // evaluation. - LifetimeStartedInEvaluation = true; - } else if (AK != AK_Read) { - // All the remaining cases only permit reading. + } else if (isModification(AK)) { + // All the remaining cases do not permit modification of the object. Info.FFDiag(E, diag::note_constexpr_modify_global); return CompleteObject(); } else if (VD->isConstexpr()) { // OK, we can read this variable. } else if (BaseType->isIntegralOrEnumerationType()) { - // In OpenCL if a variable is in constant address space it is a const value. + // In OpenCL if a variable is in constant address space it is a const + // value. if (!(BaseType.isConstQualified() || (Info.getLangOpts().OpenCL && BaseType.getAddressSpace() == LangAS::opencl_constant))) { + if (!NeedValue) + return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); if (Info.getLangOpts().CPlusPlus) { Info.FFDiag(E, diag::note_constexpr_ltor_non_const_int, 1) << VD; Info.Note(VD->getLocation(), diag::note_declared_at); @@ -3286,6 +3329,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } return CompleteObject(); } + } else if (!NeedValue) { + return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); } else if (BaseType->isFloatingType() && BaseType.isConstQualified()) { // We support folding of const floating-point types, in order to make // static const data members of such types (supported as an extension) @@ -3345,6 +3390,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, if (!(BaseType.isConstQualified() && BaseType->isIntegralOrEnumerationType()) && !(VD && VD->getCanonicalDecl() == ED->getCanonicalDecl())) { + if (!NeedValue) + 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); return CompleteObject(); @@ -3352,8 +3399,9 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, BaseVal = Info.Ctx.getMaterializedTemporaryValue(MTE, false); assert(BaseVal && "got reference to unevaluated temporary"); - LifetimeStartedInEvaluation = true; } else { + if (!NeedValue) + return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); Info.FFDiag(E); return CompleteObject(); } @@ -3370,11 +3418,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, // to be read here (but take care with 'mutable' fields). if ((Frame && Info.getLangOpts().CPlusPlus14 && Info.EvalStatus.HasSideEffects) || - (AK != AK_Read && Depth < Info.SpeculativeEvaluationDepth)) + (isModification(AK) && Depth < Info.SpeculativeEvaluationDepth)) return CompleteObject(); - return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType, - LifetimeStartedInEvaluation); + return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType); } /// Perform an lvalue-to-rvalue conversion on the given glvalue. This @@ -3408,7 +3455,7 @@ static bool handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, APValue Lit; if (!Evaluate(Lit, Info, CLE->getInitializer())) return false; - CompleteObject LitObj(LVal.Base, &Lit, Base->getType(), false); + CompleteObject LitObj(LVal.Base, &Lit, Base->getType()); return extractSubobject(Info, Conv, LitObj, LVal.Designator, RVal); } else if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) { // Special-case character extraction so we don't have to construct an @@ -4454,6 +4501,48 @@ static bool CheckConstexprFunction(EvalInfo &Info, SourceLocation CallLoc, return false; } +namespace { +struct CheckMemberCallThisPointerHandler { + static const AccessKinds AccessKind = AK_MemberCall; + typedef bool result_type; + bool failed() { return false; } + bool found(APValue &Subobj, QualType SubobjType) { return true; } + bool found(APSInt &Value, QualType SubobjType) { return true; } + bool found(APFloat &Value, QualType SubobjType) { return true; } +}; +} // 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) { + CompleteObject Obj = + findCompleteObject(Info, E, AK_MemberCall, This, QualType()); + + if (!Obj) + return false; + + if (!Obj.Value) { + // The object is not usable in constant expressions, so we can't inspect + // its value to see if it's in-lifetime or what the active union members + // are. We can still check for a one-past-the-end lvalue. + if (This.Designator.isOnePastTheEnd() || + This.Designator.isMostDerivedAnUnsizedArray()) { + Info.FFDiag(E, This.Designator.isOnePastTheEnd() + ? diag::note_constexpr_access_past_end + : diag::note_constexpr_access_unsized_array) + << AK_MemberCall; + return false; + } + return true; + } + + CheckMemberCallThisPointerHandler Handler; + return Obj && findSubobject(Info, E, Obj, This.Designator, Handler); +} + /// 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) { @@ -5038,7 +5127,7 @@ public: } else return Error(E); - if (This && !This->checkSubobject(Info, E, CSK_This)) + if (This && !checkMemberCallThisPointer(Info, E, *This)) return false; const FunctionDecl *Definition = nullptr; @@ -5093,7 +5182,7 @@ public: // Note: there is no lvalue base here. But this case should only ever // happen in C or in C++98, where we cannot be evaluating a constexpr // constructor, which is the only case the base matters. - CompleteObject Obj(APValue::LValueBase(), &Val, BaseTy, true); + CompleteObject Obj(APValue::LValueBase(), &Val, BaseTy); SubobjectDesignator Designator(BaseTy); Designator.addDeclUnchecked(FD); diff --git a/clang/test/CXX/expr/expr.const/p2-0x.cpp b/clang/test/CXX/expr/expr.const/p2-0x.cpp index 4daed23bf9c..2496845182f 100644 --- a/clang/test/CXX/expr/expr.const/p2-0x.cpp +++ b/clang/test/CXX/expr/expr.const/p2-0x.cpp @@ -210,8 +210,8 @@ namespace UndefinedBehavior { constexpr int f() const { return 0; } } constexpr c = C(); constexpr int k1 = c.f(); // ok - constexpr int k2 = ((C*)nullptr)->f(); // expected-error {{constant expression}} expected-note {{cannot call member function on null pointer}} - constexpr int k3 = (&c)[1].f(); // expected-error {{constant expression}} expected-note {{cannot call member function on pointer past the end of object}} + constexpr int k2 = ((C*)nullptr)->f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced null pointer}} + constexpr int k3 = (&c)[1].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}} C c2; constexpr int k4 = c2.f(); // ok! diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index 6af43854b52..c136b4d2693 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -192,6 +192,25 @@ namespace StaticMemberFunction { constexpr int (*sf1)(int) = &S::f; constexpr int (*sf2)(int) = &s.f; constexpr const int *sk = &s.k; + + // Note, out_of_lifetime returns an invalid pointer value, but we don't do + // anything with it (other than copy it around), so there's no UB there. + constexpr S *out_of_lifetime(S s) { return &s; } // expected-warning {{address of stack}} + static_assert(out_of_lifetime({})->k == 42, ""); + static_assert(out_of_lifetime({})->f(3) == 128, ""); + + // Similarly, using an inactive union member breaks no rules. + union U { + int n; + S s; + }; + constexpr U u = {0}; + static_assert(u.s.k == 42, ""); + static_assert(u.s.f(1) == 44, ""); + + // And likewise for a past-the-end pointer. + static_assert((&s)[1].k == 42, ""); + static_assert((&s)[1].f(1) == 44, ""); } namespace ParameterScopes { @@ -1729,19 +1748,10 @@ namespace PR14203 { constexpr duration() {} constexpr operator int() const { return 0; } }; + // These are valid per P0859R0 (moved as DR). template<typename T> void f() { - // If we want to evaluate this at the point of the template definition, we - // need to trigger the implicit definition of the move constructor at that - // point. - // FIXME: C++ does not permit us to implicitly define it at the appropriate - // times, since it is only allowed to be implicitly defined when it is - // odr-used. constexpr duration d = duration(); } - // FIXME: It's unclear whether this is valid. On the one hand, we're not - // allowed to generate a move constructor. On the other hand, if we did, - // this would be a constant expression. For now, we generate a move - // constructor here. int n = sizeof(short{duration(duration())}); } @@ -1902,6 +1912,52 @@ namespace Lifetime { }; constexpr int k1 = S().t; // expected-error {{constant expression}} expected-note {{in call}} constexpr int k2 = S(0).t; // expected-error {{constant expression}} expected-note {{in call}} + + struct Q { + int n = 0; + constexpr int f() const { return 0; } + }; + constexpr Q *out_of_lifetime(Q q) { return &q; } // expected-warning {{address of stack}} expected-note 2{{declared here}} + constexpr int k3 = out_of_lifetime({})->n; // expected-error {{constant expression}} expected-note {{read of variable whose lifetime has ended}} + constexpr int k4 = out_of_lifetime({})->f(); // expected-error {{constant expression}} expected-note {{member call on variable whose lifetime has ended}} + + constexpr int null = ((Q*)nullptr)->f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced null pointer}} + + Q q; + Q qa[3]; + constexpr int pte0 = (&q)[0].f(); // ok + constexpr int pte1 = (&q)[1].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}} + constexpr int pte2 = qa[2].f(); // ok + constexpr int pte3 = qa[3].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}} + + constexpr Q cq; + constexpr Q cqa[3]; + constexpr int cpte0 = (&cq)[0].f(); // ok + constexpr int cpte1 = (&cq)[1].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}} + constexpr int cpte2 = cqa[2].f(); // ok + constexpr int cpte3 = cqa[3].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}} + + // FIXME: There's no way if we can tell if the first call here is valid; it + // depends on the active union member. Should we reject for that reason? + union U { + int n; + Q q; + }; + U u1 = {0}; + constexpr U u2 = {0}; + constexpr int union_member1 = u1.q.f(); + constexpr int union_member2 = u2.q.f(); // expected-error {{constant expression}} expected-note {{member call on member 'q' of union with active member 'n'}} + + struct R { // expected-note {{field init}} + struct Inner { constexpr int f() const { return 0; } }; + int a = b.f(); // expected-warning {{uninitialized}} expected-note {{member call on object outside its lifetime}} + Inner b; + }; + // FIXME: This should be rejected under DR2026. + constexpr R r; // expected-note {{default constructor}} + void rf() { + constexpr R r; // expected-error {{constant expression}} expected-note {{in call}} + } } namespace Bitfields { |

