Reject attempts to call non-static member functions on objects outside

their lifetime in constant expressions.

This is undefined behavior per [class.cdtor]p2.

We continue to allow this for objects whose values are not visible
within the constant evaluation, because there's no way we can tell
whether the access is defined or not, existing code relies on the
ability to make such calls, and every other compiler allows such
calls.

This reinstates r360499, reverted in r360531.

llvm-svn: 360538
This commit is contained in:
Richard Smith 2019-05-12 09:39:08 +00:00
parent d3d6f4f65c
commit debad6460b
4 changed files with 202 additions and 57 deletions

View File

@ -67,13 +67,13 @@ def note_constexpr_past_end : Note<
"%select{temporary|%2}1 is not a constant expression">; "%select{temporary|%2}1 is not a constant expression">;
def note_constexpr_past_end_subobject : Note< def note_constexpr_past_end_subobject : Note<
"cannot %select{access base class of|access derived class of|access field of|" "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 " "access real component of|access imaginary component of}0 "
"pointer past the end of object">; "pointer past the end of object">;
def note_constexpr_null_subobject : Note< def note_constexpr_null_subobject : Note<
"cannot %select{access base class of|access derived class of|access field of|" "cannot %select{access base class of|access derived class of|access field of|"
"access array element of|perform pointer arithmetic on|" "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">; "access imaginary component of}0 null pointer">;
def note_constexpr_var_init_non_constant : Note< def note_constexpr_var_init_non_constant : Note<
"initializer of %0 is not a constant expression">; "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 " "%select{|implicit }0use of 'this' pointer is only allowed within the "
"evaluation of a call to a 'constexpr' member function">; "evaluation of a call to a 'constexpr' member function">;
def note_constexpr_lifetime_ended : Note< 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">; "%select{temporary|variable}1 whose lifetime has ended">;
def note_constexpr_access_uninit : Note< 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">; "object outside its lifetime is not allowed in a constant expression">;
def note_constexpr_use_uninit_reference : Note< def note_constexpr_use_uninit_reference : Note<
"use of reference outside its lifetime " "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 " "modification of object of const-qualified type %0 is not allowed "
"in a constant expression">; "in a constant expression">;
def note_constexpr_access_volatile_type : Note< 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">; "volatile-qualified type %1 is not allowed in a constant expression">;
def note_constexpr_access_volatile_obj : Note< 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 " "%select{temporary|object %2|member %2}1 is not allowed in "
"a constant expression">; "a constant expression">;
def note_constexpr_volatile_here : Note< def note_constexpr_volatile_here : Note<
@ -125,21 +125,21 @@ def note_constexpr_ltor_non_constexpr : Note<
def note_constexpr_ltor_incomplete_type : Note< def note_constexpr_ltor_incomplete_type : Note<
"read of incomplete type %0 is not allowed in a constant expression">; "read of incomplete type %0 is not allowed in a constant expression">;
def note_constexpr_access_null : Note< 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">; "dereferenced null pointer is not allowed in a constant expression">;
def note_constexpr_access_past_end : Note< 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">; "dereferenced one-past-the-end pointer is not allowed in a constant expression">;
def note_constexpr_access_unsized_array : Note< def note_constexpr_access_unsized_array : Note<
"%select{read of|assignment to|increment of|decrement of}0 " "%select{read of|assignment to|increment of|decrement of|member call on}0 "
"pointer to element of array without known bound " "element of array without known bound "
"is not allowed in a constant expression">; "is not allowed in a constant expression">;
def note_constexpr_access_inactive_union_member : Note< 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 " "member %1 of union with %select{active member %3|no active member}2 "
"is not allowed in a constant expression">; "is not allowed in a constant expression">;
def note_constexpr_access_static_temporary : Note< 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 " "is not allowed in a constant expression outside the expression that "
"created the temporary">; "created the temporary">;
def note_constexpr_modify_global : Note< def note_constexpr_modify_global : Note<

View File

@ -213,7 +213,7 @@ namespace {
// The order of this enum is important for diagnostics. // The order of this enum is important for diagnostics.
enum CheckSubobjectKind { enum CheckSubobjectKind {
CSK_Base, CSK_Derived, CSK_Field, CSK_ArrayToPointer, CSK_ArrayIndex, 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. /// 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 { enum AccessKinds {
AK_Read, AK_Read,
AK_Assign, AK_Assign,
AK_Increment, AK_Increment,
AK_Decrement AK_Decrement,
AK_MemberCall,
}; };
static bool isModification(AccessKinds AK) {
return AK != AK_Read && AK != AK_MemberCall;
}
namespace { namespace {
struct ComplexValue { struct ComplexValue {
private: private:
@ -2820,6 +2828,31 @@ static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E,
return false; 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 { namespace {
/// A handle to a complete object (an object that is not a subobject of /// A handle to a complete object (an object that is not a subobject of
/// another object). /// another object).
@ -2830,17 +2863,21 @@ 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::LValueBase Base, APValue *Value, QualType Type, CompleteObject(APValue::LValueBase Base, APValue *Value, QualType Type)
bool LifetimeStartedInEvaluation) : Base(Base), Value(Value), Type(Type) {}
: Base(Base), Value(Value), Type(Type),
LifetimeStartedInEvaluation(LifetimeStartedInEvaluation) { bool mayReadMutableMembers(EvalInfo &Info) const {
assert(Value && "missing value for complete object"); // 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 } // end anonymous namespace
@ -2880,8 +2917,6 @@ 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;
const FieldDecl *VolatileField = nullptr; const FieldDecl *VolatileField = nullptr;
// Walk the designator's path to find the subobject. // 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 this is our last pass, check that the final object type is OK.
if (I == N || (I == N - 1 && ObjType->isAnyComplexType())) { if (I == N || (I == N - 1 && ObjType->isAnyComplexType())) {
// Accesses to volatile objects are prohibited. // Accesses to volatile objects are prohibited.
if (ObjType.isVolatileQualified()) { if (ObjType.isVolatileQualified() &&
handler.AccessKind != AK_MemberCall) {
if (Info.getLangOpts().CPlusPlus) { if (Info.getLangOpts().CPlusPlus) {
int DiagKind; int DiagKind;
SourceLocation Loc; 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 // 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 &&
!MayReadMutableMembers && diagnoseUnreadableFields(Info, E, ObjType)) !Obj.mayReadMutableMembers(Info) &&
diagnoseUnreadableFields(Info, E, ObjType))
return handler.failed(); return handler.failed();
} }
@ -2951,7 +2988,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
return false; return false;
// If we modified a bit-field, truncate it to the right width. // If we modified a bit-field, truncate it to the right width.
if (handler.AccessKind != AK_Read && if (isModification(handler.AccessKind) &&
LastField && LastField->isBitField() && LastField && LastField->isBitField() &&
!truncateBitfieldValue(Info, E, *O, LastField)) !truncateBitfieldValue(Info, E, *O, LastField))
return false; return false;
@ -3010,11 +3047,8 @@ 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])) {
// 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 && if (Field->isMutable() && handler.AccessKind == AK_Read &&
!MayReadMutableMembers) { !Obj.mayReadMutableMembers(Info)) {
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);
@ -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 // 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' // apply this rule to C++98, in order to conform to the expected 'volatile'
// semantics. // semantics.
if (LValType.isVolatileQualified()) { if (AK != AK_MemberCall && LValType.isVolatileQualified()) {
if (Info.getLangOpts().CPlusPlus) if (Info.getLangOpts().CPlusPlus)
Info.FFDiag(E, diag::note_constexpr_access_volatile_type) Info.FFDiag(E, diag::note_constexpr_access_volatile_type)
<< AK << LValType; << AK << LValType;
@ -3235,10 +3269,16 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
return CompleteObject(); 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. // 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.
@ -3262,22 +3302,25 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
// the variable we're reading must be const. // the variable we're reading must be const.
if (!Frame) { if (!Frame) {
if (Info.getLangOpts().CPlusPlus14 && 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 // OK, we can read and modify an object if we're in the process of
// evaluating its initializer, because its lifetime began in this // evaluating its initializer, because its lifetime began in this
// evaluation. // evaluation.
LifetimeStartedInEvaluation = true; } else if (isModification(AK)) {
} else if (AK != AK_Read) { // All the remaining cases do not permit modification of the object.
// All the remaining cases only permit reading.
Info.FFDiag(E, diag::note_constexpr_modify_global); Info.FFDiag(E, diag::note_constexpr_modify_global);
return CompleteObject(); return CompleteObject();
} else if (VD->isConstexpr()) { } else if (VD->isConstexpr()) {
// OK, we can read this variable. // OK, we can read this variable.
} else if (BaseType->isIntegralOrEnumerationType()) { } 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() || if (!(BaseType.isConstQualified() ||
(Info.getLangOpts().OpenCL && (Info.getLangOpts().OpenCL &&
BaseType.getAddressSpace() == LangAS::opencl_constant))) { BaseType.getAddressSpace() == LangAS::opencl_constant))) {
if (!NeedValue)
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
if (Info.getLangOpts().CPlusPlus) { if (Info.getLangOpts().CPlusPlus) {
Info.FFDiag(E, diag::note_constexpr_ltor_non_const_int, 1) << VD; Info.FFDiag(E, diag::note_constexpr_ltor_non_const_int, 1) << VD;
Info.Note(VD->getLocation(), diag::note_declared_at); Info.Note(VD->getLocation(), diag::note_declared_at);
@ -3286,6 +3329,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
} }
return CompleteObject(); return CompleteObject();
} }
} else if (!NeedValue) {
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
} else if (BaseType->isFloatingType() && BaseType.isConstQualified()) { } else if (BaseType->isFloatingType() && BaseType.isConstQualified()) {
// We support folding of const floating-point types, in order to make // We support folding of const floating-point types, in order to make
// static const data members of such types (supported as an extension) // 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() && if (!(BaseType.isConstQualified() &&
BaseType->isIntegralOrEnumerationType()) && BaseType->isIntegralOrEnumerationType()) &&
!(VD && VD->getCanonicalDecl() == ED->getCanonicalDecl())) { !(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.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK;
Info.Note(MTE->getExprLoc(), diag::note_constexpr_temporary_here); Info.Note(MTE->getExprLoc(), diag::note_constexpr_temporary_here);
return CompleteObject(); return CompleteObject();
@ -3352,8 +3399,9 @@ 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 {
if (!NeedValue)
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
Info.FFDiag(E); Info.FFDiag(E);
return CompleteObject(); return CompleteObject();
} }
@ -3370,11 +3418,10 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
// to be read here (but take care with 'mutable' fields). // to be read here (but take care with 'mutable' fields).
if ((Frame && Info.getLangOpts().CPlusPlus14 && if ((Frame && Info.getLangOpts().CPlusPlus14 &&
Info.EvalStatus.HasSideEffects) || Info.EvalStatus.HasSideEffects) ||
(AK != AK_Read && Depth < Info.SpeculativeEvaluationDepth)) (isModification(AK) && Depth < Info.SpeculativeEvaluationDepth))
return CompleteObject(); return CompleteObject();
return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType, return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType);
LifetimeStartedInEvaluation);
} }
/// Perform an lvalue-to-rvalue conversion on the given glvalue. This /// 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; APValue Lit;
if (!Evaluate(Lit, Info, CLE->getInitializer())) if (!Evaluate(Lit, Info, CLE->getInitializer()))
return false; 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); return extractSubobject(Info, Conv, LitObj, LVal.Designator, RVal);
} else if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) { } else if (isa<StringLiteral>(Base) || isa<PredefinedExpr>(Base)) {
// Special-case character extraction so we don't have to construct an // 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; 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 /// Determine if a class has any fields that might need to be copied by a
/// trivial copy or move operation. /// trivial copy or move operation.
static bool hasFields(const CXXRecordDecl *RD) { static bool hasFields(const CXXRecordDecl *RD) {
@ -5038,7 +5127,7 @@ public:
} else } else
return Error(E); return Error(E);
if (This && !This->checkSubobject(Info, E, CSK_This)) if (This && !checkMemberCallThisPointer(Info, E, *This))
return false; return false;
const FunctionDecl *Definition = nullptr; const FunctionDecl *Definition = nullptr;
@ -5093,7 +5182,7 @@ public:
// Note: there is no lvalue base here. But this case should only ever // 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 // happen in C or in C++98, where we cannot be evaluating a constexpr
// constructor, which is the only case the base matters. // 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); SubobjectDesignator Designator(BaseTy);
Designator.addDeclUnchecked(FD); Designator.addDeclUnchecked(FD);

View File

@ -210,8 +210,8 @@ namespace UndefinedBehavior {
constexpr int f() const { return 0; } constexpr int f() const { return 0; }
} constexpr c = C(); } constexpr c = C();
constexpr int k1 = c.f(); // ok 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 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 {{cannot call member function on pointer past the end of object}} constexpr int k3 = (&c)[1].f(); // expected-error {{constant expression}} expected-note {{member call on dereferenced one-past-the-end pointer}}
C c2; C c2;
constexpr int k4 = c2.f(); // ok! constexpr int k4 = c2.f(); // ok!

View File

@ -192,6 +192,25 @@ namespace StaticMemberFunction {
constexpr int (*sf1)(int) = &S::f; constexpr int (*sf1)(int) = &S::f;
constexpr int (*sf2)(int) = &s.f; constexpr int (*sf2)(int) = &s.f;
constexpr const int *sk = &s.k; 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 { namespace ParameterScopes {
@ -1729,19 +1748,10 @@ namespace PR14203 {
constexpr duration() {} constexpr duration() {}
constexpr operator int() const { return 0; } constexpr operator int() const { return 0; }
}; };
// These are valid per P0859R0 (moved as DR).
template<typename T> void f() { 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(); 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())}); 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 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}} 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 { namespace Bitfields {