In C++14 onwards, it is permitted to read mutable members in constant

expressions, if their lifetime began during the evaluation of the expression.

This is technically not allowed in C++11, though we could consider permitting
it there too, as an extension.

llvm-svn: 325663
This commit is contained in:
Richard Smith 2018-02-21 03:38:30 +00:00
parent 63a1098d73
commit 9defb7d6b1
2 changed files with 45 additions and 10 deletions

View File

@ -2646,10 +2646,13 @@ struct CompleteObject {
APValue *Value; APValue *Value;
/// The type of the complete object. /// The type of the complete object.
QualType Type; QualType Type;
bool LifetimeStartedInEvaluation;
CompleteObject() : Value(nullptr) {} CompleteObject() : Value(nullptr) {}
CompleteObject(APValue *Value, QualType Type) CompleteObject(APValue *Value, QualType Type,
: Value(Value), Type(Type) { bool LifetimeStartedInEvaluation)
: Value(Value), Type(Type),
LifetimeStartedInEvaluation(LifetimeStartedInEvaluation) {
assert(Value && "missing value for complete object"); assert(Value && "missing value for complete object");
} }
@ -2679,6 +2682,8 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
APValue *O = Obj.Value; APValue *O = Obj.Value;
QualType ObjType = Obj.Type; QualType ObjType = Obj.Type;
const FieldDecl *LastField = nullptr; const FieldDecl *LastField = nullptr;
const bool MayReadMutableMembers =
Obj.LifetimeStartedInEvaluation && Info.getLangOpts().CPlusPlus14;
// Walk the designator's path to find the subobject. // Walk the designator's path to find the subobject.
for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) { for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) {
@ -2694,7 +2699,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
// cannot perform this read. (This only happens when performing a trivial // cannot perform this read. (This only happens when performing a trivial
// copy or assignment.) // copy or assignment.)
if (ObjType->isRecordType() && handler.AccessKind == AK_Read && if (ObjType->isRecordType() && handler.AccessKind == AK_Read &&
diagnoseUnreadableFields(Info, E, ObjType)) !MayReadMutableMembers && diagnoseUnreadableFields(Info, E, ObjType))
return handler.failed(); return handler.failed();
if (!handler.found(*O, ObjType)) if (!handler.found(*O, ObjType))
@ -2774,7 +2779,11 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
: O->getComplexFloatReal(), ObjType); : O->getComplexFloatReal(), ObjType);
} }
} else if (const FieldDecl *Field = getAsField(Sub.Entries[I])) { } else if (const FieldDecl *Field = getAsField(Sub.Entries[I])) {
if (Field->isMutable() && handler.AccessKind == AK_Read) { // 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) {
Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1) Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1)
<< Field; << Field;
Info.Note(Field->getLocation(), diag::note_declared_at); Info.Note(Field->getLocation(), diag::note_declared_at);
@ -3020,6 +3029,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
// Compute value storage location and type of base object. // Compute value storage location and type of base object.
APValue *BaseVal = nullptr; APValue *BaseVal = nullptr;
QualType BaseType = getType(LVal.Base); QualType BaseType = getType(LVal.Base);
bool LifetimeStartedInEvaluation = Frame;
if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl*>()) { if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl*>()) {
// In C++98, const, non-volatile integers initialized with ICEs are ICEs. // In C++98, const, non-volatile integers initialized with ICEs are ICEs.
@ -3131,7 +3141,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
// int &&r = 1; // int &&r = 1;
// int x = ++r; // int x = ++r;
// constexpr int k = r; // constexpr int k = r;
// Therefore we use the C++1y rules in C++11 too. // Therefore we use the C++14 rules in C++11 too.
const ValueDecl *VD = Info.EvaluatingDecl.dyn_cast<const ValueDecl*>(); const ValueDecl *VD = Info.EvaluatingDecl.dyn_cast<const ValueDecl*>();
const ValueDecl *ED = MTE->getExtendingDecl(); const ValueDecl *ED = MTE->getExtendingDecl();
if (!(BaseType.isConstQualified() && if (!(BaseType.isConstQualified() &&
@ -3144,6 +3154,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
BaseVal = Info.Ctx.getMaterializedTemporaryValue(MTE, false); BaseVal = Info.Ctx.getMaterializedTemporaryValue(MTE, false);
assert(BaseVal && "got reference to unevaluated temporary"); assert(BaseVal && "got reference to unevaluated temporary");
LifetimeStartedInEvaluation = true;
} else { } else {
Info.FFDiag(E); Info.FFDiag(E);
return CompleteObject(); return CompleteObject();
@ -3172,9 +3183,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
if (Info.isEvaluatingConstructor(LVal.getLValueBase(), LVal.CallIndex)) { if (Info.isEvaluatingConstructor(LVal.getLValueBase(), LVal.CallIndex)) {
BaseType = Info.Ctx.getCanonicalType(BaseType); BaseType = Info.Ctx.getCanonicalType(BaseType);
BaseType.removeLocalConst(); BaseType.removeLocalConst();
LifetimeStartedInEvaluation = true;
} }
// In C++1y, we can't safely access any mutable state when we might be // In C++14, we can't safely access any mutable state when we might be
// evaluating after an unmodeled side effect. // evaluating after an unmodeled side effect.
// //
// FIXME: Not all local state is mutable. Allow local constant subobjects // FIXME: Not all local state is mutable. Allow local constant subobjects
@ -3184,7 +3196,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
(AK != AK_Read && Info.IsSpeculativelyEvaluating)) (AK != AK_Read && Info.IsSpeculativelyEvaluating))
return CompleteObject(); return CompleteObject();
return CompleteObject(BaseVal, BaseType); return CompleteObject(BaseVal, BaseType, LifetimeStartedInEvaluation);
} }
/// \brief Perform an lvalue-to-rvalue conversion on the given glvalue. This /// \brief Perform an lvalue-to-rvalue conversion on the given glvalue. This
@ -3218,14 +3230,14 @@ static bool handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv,
APValue Lit; APValue Lit;
if (!Evaluate(Lit, Info, CLE->getInitializer())) if (!Evaluate(Lit, Info, CLE->getInitializer()))
return false; return false;
CompleteObject LitObj(&Lit, Base->getType()); CompleteObject LitObj(&Lit, Base->getType(), false);
return extractSubobject(Info, Conv, LitObj, LVal.Designator, RVal); return extractSubobject(Info, Conv, LitObj, LVal.Designator, RVal);
} else if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) { } else if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) {
// We represent a string literal array as an lvalue pointing at the // We represent a string literal array as an lvalue pointing at the
// corresponding expression, rather than building an array of chars. // corresponding expression, rather than building an array of chars.
// FIXME: Support ObjCEncodeExpr, MakeStringConstant // FIXME: Support ObjCEncodeExpr, MakeStringConstant
APValue Str(Base, CharUnits::Zero(), APValue::NoLValuePath(), 0); APValue Str(Base, CharUnits::Zero(), APValue::NoLValuePath(), 0);
CompleteObject StrObj(&Str, Base->getType()); CompleteObject StrObj(&Str, Base->getType(), false);
return extractSubobject(Info, Conv, StrObj, LVal.Designator, RVal); return extractSubobject(Info, Conv, StrObj, LVal.Designator, RVal);
} }
} }
@ -4823,7 +4835,7 @@ public:
assert(BaseTy->castAs<RecordType>()->getDecl()->getCanonicalDecl() == assert(BaseTy->castAs<RecordType>()->getDecl()->getCanonicalDecl() ==
FD->getParent()->getCanonicalDecl() && "record / field mismatch"); FD->getParent()->getCanonicalDecl() && "record / field mismatch");
CompleteObject Obj(&Val, BaseTy); CompleteObject Obj(&Val, BaseTy, true);
SubobjectDesignator Designator(BaseTy); SubobjectDesignator Designator(BaseTy);
Designator.addDeclUnchecked(FD); Designator.addDeclUnchecked(FD);

View File

@ -1021,3 +1021,26 @@ constexpr bool evalNested() {
} }
static_assert(evalNested(), ""); static_assert(evalNested(), "");
} // namespace PR19741 } // namespace PR19741
namespace Mutable {
struct A { mutable int n; }; // expected-note 2{{here}}
constexpr int k = A{123}.n; // ok
static_assert(k == 123, "");
struct Q { A &&a; int b = a.n; };
constexpr Q q = { A{456} }; // ok
static_assert(q.b == 456, "");
constexpr A a = {123};
constexpr int m = a.n; // expected-error {{constant expression}} expected-note {{mutable}}
constexpr Q r = { static_cast<A&&>(const_cast<A&>(a)) }; // expected-error {{constant expression}} expected-note@-7 {{mutable}}
struct B {
mutable int n; // expected-note {{here}}
int m;
constexpr B() : n(1), m(n) {} // ok
};
constexpr B b;
constexpr int p = b.n; // expected-error {{constant expression}} expected-note {{mutable}}
}