"%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">;
"%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 "
"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<
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<
// 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.
}
}
-/// 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:
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).
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
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.
// 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;
// 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();
}
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;
: 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);
// 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;
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.
// 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);
}
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)
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();
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();
}
// 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
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
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) {
} else
return Error(E);
- if (This && !This->checkSubobject(Info, E, CSK_This))
+ if (This && !checkMemberCallThisPointer(Info, E, *This))
return false;
const FunctionDecl *Definition = nullptr;
// 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);
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 {
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())});
}
};
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 {