Reject a slightly-sneaky way to perform a read of mutable state from within a

constexpr function. Part of this fix is a tentative fix for an as-yet-unfiled
core issue (we're missing a prohibition against reading mutable members from
unions via a trivial constructor/assignment, since that doesn't perform an
lvalue-to-rvalue conversion on the members).

llvm-svn: 217852
This commit is contained in:
Richard Smith 2014-09-16 01:24:02 +00:00
parent 74acb46d26
commit b01fe40c07
2 changed files with 109 additions and 0 deletions

View File

@ -2080,6 +2080,64 @@ static void expandArray(APValue &Array, unsigned Index) {
Array.swap(NewValue);
}
/// Determine whether a type would actually be read by an lvalue-to-rvalue
/// conversion. If it's of class type, we may assume that the copy operation
/// is trivial. Note that this is never true for a union type with fields
/// (because the copy always "reads" the active member) and always true for
/// a non-class type.
static bool isReadByLvalueToRvalueConversion(QualType T) {
CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
if (!RD || (RD->isUnion() && !RD->field_empty()))
return true;
if (RD->isEmpty())
return false;
for (auto *Field : RD->fields())
if (isReadByLvalueToRvalueConversion(Field->getType()))
return true;
for (auto &BaseSpec : RD->bases())
if (isReadByLvalueToRvalueConversion(BaseSpec.getType()))
return true;
return false;
}
/// Diagnose an attempt to read from any unreadable field within the specified
/// type, which might be a class type.
static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E,
QualType T) {
CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
if (!RD)
return false;
if (!RD->hasMutableFields())
return false;
for (auto *Field : RD->fields()) {
// If we're actually going to read this field in some way, then it can't
// be mutable. If we're in a union, then assigning to a mutable field
// (even an empty one) can change the active member, so that's not OK.
// FIXME: Add core issue number for the union case.
if (Field->isMutable() &&
(RD->isUnion() || isReadByLvalueToRvalueConversion(Field->getType()))) {
Info.Diag(E, diag::note_constexpr_ltor_mutable, 1) << Field;
Info.Note(Field->getLocation(), diag::note_declared_at);
return true;
}
if (diagnoseUnreadableFields(Info, E, Field->getType()))
return true;
}
for (auto &BaseSpec : RD->bases())
if (diagnoseUnreadableFields(Info, E, BaseSpec.getType()))
return true;
// All mutable fields were empty, and thus not actually read.
return false;
}
/// Kinds of access we can perform on an object, for diagnostics.
enum AccessKinds {
AK_Read,
@ -2135,6 +2193,14 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
}
if (I == N) {
// If we are reading an object of class type, there may still be more
// things we need to check: if there are any mutable subobjects, we
// cannot perform this read. (This only happens when performing a trivial
// copy or assignment.)
if (ObjType->isRecordType() && handler.AccessKind == AK_Read &&
diagnoseUnreadableFields(Info, E, ObjType))
return handler.failed();
if (!handler.found(*O, ObjType))
return false;

View File

@ -1326,6 +1326,49 @@ namespace MutableMembers {
struct C { B b; };
constexpr C c[3] = {};
constexpr int k = c[1].b.a.n; // expected-error {{constant expression}} expected-note {{mutable}}
struct D { int x; mutable int y; }; // expected-note {{here}}
constexpr D d1 = { 1, 2 };
int l = ++d1.y;
constexpr D d2 = d1; // expected-error {{constant}} expected-note {{mutable}} expected-note {{in call}}
struct E {
union {
int a;
mutable int b; // expected-note {{here}}
};
};
constexpr E e1 = {{1}};
constexpr E e2 = e1; // expected-error {{constant}} expected-note {{mutable}} expected-note {{in call}}
struct F {
union U { };
mutable U u;
struct X { };
mutable X x;
struct Y : X { X x; U u; };
mutable Y y;
int n;
};
// This is OK; we don't actually read any mutable state here.
constexpr F f1 = {};
constexpr F f2 = f1;
struct G {
struct X {};
union U { X a; };
mutable U u; // expected-note {{here}}
};
constexpr G g1 = {};
constexpr G g2 = g1; // expected-error {{constant}} expected-note {{mutable}} expected-note {{in call}}
constexpr G::U gu1 = {};
constexpr G::U gu2 = gu1;
union H {
mutable G::X gx; // expected-note {{here}}
};
constexpr H h1 = {};
constexpr H h2 = h1; // expected-error {{constant}} expected-note {{mutable}} expected-note {{in call}}
}
namespace Fold {