From 08d6a2cc7a58cfc0d04ed7215ab0b7b9d3193494 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 24 Jul 2013 07:11:57 +0000 Subject: [PATCH] C++1y: track object lifetime during constexpr evaluation, and don't allow objects to be used once their lifetimes end. This completes the C++1y constexpr extensions. llvm-svn: 187025 --- .../include/clang/Basic/DiagnosticASTKinds.td | 3 + clang/lib/AST/ExprConstant.cpp | 229 +++++++++++++----- .../SemaCXX/constant-expression-cxx11.cpp | 18 ++ .../SemaCXX/constant-expression-cxx1y.cpp | 56 +++++ clang/test/SemaCXX/i-c-e-cxx.cpp | 2 +- clang/www/cxx_status.html | 2 +- 6 files changed, 249 insertions(+), 61 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 919245eccc50..86c751459393 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -93,6 +93,9 @@ def note_constexpr_lifetime_ended : Note< def note_constexpr_access_uninit : Note< "%select{read of|assignment to|increment of|decrement of}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 " + "is not allowed in a constant expression">; def note_constexpr_modify_const_type : Note< "modification of object of const-qualified type %0 is not allowed " "in a constant expression">; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index ddfb64c44d54..47b0a6690015 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -317,6 +317,12 @@ namespace { const FunctionDecl *Callee, const LValue *This, APValue *Arguments); ~CallStackFrame(); + + APValue *getTemporary(const void *Key) { + MapTy::iterator I = Temporaries.find(Key); + return I == Temporaries.end() ? 0 : &I->second; + } + APValue &createTemporary(const void *Key, bool IsLifetimeExtended); }; /// Temporarily override 'this'. @@ -369,6 +375,20 @@ namespace { } }; + /// A cleanup, and a flag indicating whether it is lifetime-extended. + class Cleanup { + llvm::PointerIntPair Value; + + public: + Cleanup(APValue *Val, bool IsLifetimeExtended) + : Value(Val, IsLifetimeExtended) {} + + bool isLifetimeExtended() const { return Value.getInt(); } + void endLifetime() { + *Value.getPointer() = APValue(); + } + }; + /// EvalInfo - This is a private struct used by the evaluator to capture /// information about a subexpression as it is folded. It retains information /// about the AST context, but also maintains information about the folded @@ -407,6 +427,10 @@ namespace { /// initialized after CurrentCall and CallStackDepth. CallStackFrame BottomFrame; + /// A stack of values whose lifetimes end at the end of some surrounding + /// evaluation frame. + llvm::SmallVector CleanupStack; + /// EvaluatingDecl - This is the declaration whose initializer is being /// evaluated, if any. APValue::LValueBase EvaluatingDecl; @@ -423,7 +447,7 @@ namespace { /// expression is a potential constant expression? If so, some diagnostics /// are suppressed. bool CheckingPotentialConstantExpression; - + bool IntOverflowCheckMode; EvalInfo(const ASTContext &C, Expr::EvalStatus &S, @@ -601,6 +625,42 @@ namespace { Info.EvalStatus = Old; } }; + + /// RAII object wrapping a full-expression or block scope, and handling + /// the ending of the lifetime of temporaries created within it. + template + class ScopeRAII { + EvalInfo &Info; + unsigned OldStackSize; + public: + ScopeRAII(EvalInfo &Info) + : Info(Info), OldStackSize(Info.CleanupStack.size()) {} + ~ScopeRAII() { + // Body moved to a static method to encourage the compiler to inline away + // instances of this class. + cleanup(Info, OldStackSize); + } + private: + static void cleanup(EvalInfo &Info, unsigned OldStackSize) { + unsigned NewEnd = OldStackSize; + for (unsigned I = OldStackSize, N = Info.CleanupStack.size(); + I != N; ++I) { + if (IsFullExpression && Info.CleanupStack[I].isLifetimeExtended()) { + // Full-expression cleanup of a lifetime-extended temporary: nothing + // to do, just move this cleanup to the right place in the stack. + std::swap(Info.CleanupStack[I], Info.CleanupStack[NewEnd]); + ++NewEnd; + } else { + // End the lifetime of the object. + Info.CleanupStack[I].endLifetime(); + } + } + Info.CleanupStack.erase(Info.CleanupStack.begin() + NewEnd, + Info.CleanupStack.end()); + } + }; + typedef ScopeRAII BlockScopeRAII; + typedef ScopeRAII FullExpressionRAII; } bool SubobjectDesignator::checkSubobject(EvalInfo &Info, const Expr *E, @@ -643,6 +703,14 @@ CallStackFrame::~CallStackFrame() { Info.CurrentCall = Caller; } +APValue &CallStackFrame::createTemporary(const void *Key, + bool IsLifetimeExtended) { + APValue &Result = Temporaries[Key]; + assert(Result.isUninit() && "temporary created multiple times"); + Info.CleanupStack.push_back(Cleanup(&Result, IsLifetimeExtended)); + return Result; +} + static void describeCall(CallStackFrame *Frame, raw_ostream &Out); void EvalInfo::addCallStack(unsigned Limit) { @@ -1710,11 +1778,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // If this is a local variable, dig out its value. if (Frame) { - Result = &Frame->Temporaries[VD]; - // If we've carried on past an unevaluatable local variable initializer, - // we can't go any further. This can happen during potential constant - // expression checking. - return !Result->isUninit(); + Result = Frame->getTemporary(VD); + assert(Result && "missing value for local variable"); + return true; } // Dig out the initializer, and use the declaration which it's attached to. @@ -1731,7 +1797,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // in-flight value. if (Info.EvaluatingDecl.dyn_cast() == VD) { Result = Info.EvaluatingDeclValue; - return !Result->isUninit(); + return true; } // Never evaluate the initializer of a weak variable. We can't be sure that @@ -1884,16 +1950,20 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, Info.Diag(E); return handler.failed(); } - if (Sub.Entries.empty()) - return handler.found(*Obj.Value, Obj.Type); - if (Info.CheckingPotentialConstantExpression && Obj.Value->isUninit()) - // This object might be initialized later. - return handler.failed(); APValue *O = Obj.Value; QualType ObjType = Obj.Type; // Walk the designator's path to find the subobject. - for (unsigned I = 0, N = Sub.Entries.size(); I != N; ++I) { + for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) { + if (O->isUninit()) { + if (!Info.CheckingPotentialConstantExpression) + Info.Diag(E, diag::note_constexpr_access_uninit) << handler.AccessKind; + return handler.failed(); + } + + if (I == N) + return handler.found(*O, ObjType); + if (ObjType->isArrayType()) { // Next subobject is an array element. const ConstantArrayType *CAT = Info.Ctx.getAsConstantArrayType(ObjType); @@ -2006,15 +2076,7 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, if (WasConstQualified) ObjType.addConst(); } - - if (O->isUninit()) { - if (!Info.CheckingPotentialConstantExpression) - Info.Diag(E, diag::note_constexpr_access_uninit) << handler.AccessKind; - return handler.failed(); - } } - - return handler.found(*O, ObjType); } namespace { @@ -2328,7 +2390,8 @@ CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, AccessKinds AK, return CompleteObject(); } } else { - BaseVal = &Frame->Temporaries[Base]; + BaseVal = Frame->getTemporary(Base); + assert(BaseVal && "missing value for temporary"); } // Volatile temporary objects cannot be accessed in constant expressions. @@ -2887,7 +2950,7 @@ static bool EvaluateDecl(EvalInfo &Info, const Decl *D) { LValue Result; Result.set(VD, Info.CurrentCall->Index); - APValue &Val = Info.CurrentCall->Temporaries[VD]; + APValue &Val = Info.CurrentCall->createTemporary(VD, true); if (!VD->getInit()) { Info.Diag(D->getLocStart(), diag::note_constexpr_uninitialized) @@ -2910,6 +2973,7 @@ static bool EvaluateDecl(EvalInfo &Info, const Decl *D) { /// Evaluate a condition (either a variable declaration or an expression). static bool EvaluateCond(EvalInfo &Info, const VarDecl *CondDecl, const Expr *Cond, bool &Result) { + FullExpressionRAII Scope(Info); if (CondDecl && !EvaluateDecl(Info, CondDecl)) return false; return EvaluateAsBooleanCondition(Cond, Result, Info); @@ -2922,6 +2986,7 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, static EvalStmtResult EvaluateLoopBody(APValue &Result, EvalInfo &Info, const Stmt *Body, const SwitchCase *Case = 0) { + BlockScopeRAII Scope(Info); switch (EvalStmtResult ESR = EvaluateStmt(Result, Info, Body, Case)) { case ESR_Break: return ESR_Succeeded; @@ -2939,13 +3004,18 @@ static EvalStmtResult EvaluateLoopBody(APValue &Result, EvalInfo &Info, /// Evaluate a switch statement. static EvalStmtResult EvaluateSwitch(APValue &Result, EvalInfo &Info, const SwitchStmt *SS) { + BlockScopeRAII Scope(Info); + // Evaluate the switch condition. - if (SS->getConditionVariable() && - !EvaluateDecl(Info, SS->getConditionVariable())) - return ESR_Failed; APSInt Value; - if (!EvaluateInteger(SS->getCond(), Value, Info)) - return ESR_Failed; + { + FullExpressionRAII Scope(Info); + if (SS->getConditionVariable() && + !EvaluateDecl(Info, SS->getConditionVariable())) + return ESR_Failed; + if (!EvaluateInteger(SS->getCond(), Value, Info)) + return ESR_Failed; + } // Find the switch case corresponding to the value of the condition. // FIXME: Cache this lookup. @@ -3021,6 +3091,11 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, // FIXME: Precompute which side of an 'if' we would jump to, and go // straight there rather than scanning both sides. const IfStmt *IS = cast(S); + + // Wrap the evaluation in a block scope, in case it's a DeclStmt + // preceded by our switch label. + BlockScopeRAII Scope(Info); + EvalStmtResult ESR = EvaluateStmt(Result, Info, IS->getThen(), Case); if (ESR != ESR_CaseNotFound || !IS->getElse()) return ESR; @@ -3041,8 +3116,11 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, EvaluateLoopBody(Result, Info, FS->getBody(), Case); if (ESR != ESR_Continue) return ESR; - if (FS->getInc() && !EvaluateIgnoredValue(Info, FS->getInc())) - return ESR_Failed; + if (FS->getInc()) { + FullExpressionRAII IncScope(Info); + if (!EvaluateIgnoredValue(Info, FS->getInc())) + return ESR_Failed; + } break; } @@ -3054,13 +3132,12 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, } } - // FIXME: Mark all temporaries in the current frame as destroyed at - // the end of each full-expression. switch (S->getStmtClass()) { default: if (const Expr *E = dyn_cast(S)) { // Don't bother evaluating beyond an expression-statement which couldn't // be evaluated. + FullExpressionRAII Scope(Info); if (!EvaluateIgnoredValue(Info, E)) return ESR_Failed; return ESR_Succeeded; @@ -3075,20 +3152,28 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, case Stmt::DeclStmtClass: { const DeclStmt *DS = cast(S); for (DeclStmt::const_decl_iterator DclIt = DS->decl_begin(), - DclEnd = DS->decl_end(); DclIt != DclEnd; ++DclIt) + DclEnd = DS->decl_end(); DclIt != DclEnd; ++DclIt) { + // Each declaration initialization is its own full-expression. + // FIXME: This isn't quite right; if we're performing aggregate + // initialization, each braced subexpression is its own full-expression. + FullExpressionRAII Scope(Info); if (!EvaluateDecl(Info, *DclIt) && !Info.keepEvaluatingAfterFailure()) return ESR_Failed; + } return ESR_Succeeded; } case Stmt::ReturnStmtClass: { const Expr *RetExpr = cast(S)->getRetValue(); + FullExpressionRAII Scope(Info); if (RetExpr && !Evaluate(Result, Info, RetExpr)) return ESR_Failed; return ESR_Returned; } case Stmt::CompoundStmtClass: { + BlockScopeRAII Scope(Info); + const CompoundStmt *CS = cast(S); for (CompoundStmt::const_body_iterator BI = CS->body_begin(), BE = CS->body_end(); BI != BE; ++BI) { @@ -3105,6 +3190,7 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, const IfStmt *IS = cast(S); // Evaluate the condition, as either a var decl or as an expression. + BlockScopeRAII Scope(Info); bool Cond; if (!EvaluateCond(Info, IS->getConditionVariable(), IS->getCond(), Cond)) return ESR_Failed; @@ -3120,6 +3206,7 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, case Stmt::WhileStmtClass: { const WhileStmt *WS = cast(S); while (true) { + BlockScopeRAII Scope(Info); bool Continue; if (!EvaluateCond(Info, WS->getConditionVariable(), WS->getCond(), Continue)) @@ -3143,6 +3230,7 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, return ESR; Case = 0; + FullExpressionRAII CondScope(Info); if (!EvaluateAsBooleanCondition(DS->getCond(), Continue, Info)) return ESR_Failed; } while (Continue); @@ -3151,12 +3239,14 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, case Stmt::ForStmtClass: { const ForStmt *FS = cast(S); + BlockScopeRAII Scope(Info); if (FS->getInit()) { EvalStmtResult ESR = EvaluateStmt(Result, Info, FS->getInit()); if (ESR != ESR_Succeeded) return ESR; } while (true) { + BlockScopeRAII Scope(Info); bool Continue = true; if (FS->getCond() && !EvaluateCond(Info, FS->getConditionVariable(), FS->getCond(), Continue)) @@ -3168,14 +3258,18 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, if (ESR != ESR_Continue) return ESR; - if (FS->getInc() && !EvaluateIgnoredValue(Info, FS->getInc())) - return ESR_Failed; + if (FS->getInc()) { + FullExpressionRAII IncScope(Info); + if (!EvaluateIgnoredValue(Info, FS->getInc())) + return ESR_Failed; + } } return ESR_Succeeded; } case Stmt::CXXForRangeStmtClass: { const CXXForRangeStmt *FS = cast(S); + BlockScopeRAII Scope(Info); // Initialize the __range variable. EvalStmtResult ESR = EvaluateStmt(Result, Info, FS->getRangeStmt()); @@ -3189,13 +3283,17 @@ static EvalStmtResult EvaluateStmt(APValue &Result, EvalInfo &Info, while (true) { // Condition: __begin != __end. - bool Continue = true; - if (!EvaluateAsBooleanCondition(FS->getCond(), Continue, Info)) - return ESR_Failed; - if (!Continue) - break; + { + bool Continue = true; + FullExpressionRAII CondExpr(Info); + if (!EvaluateAsBooleanCondition(FS->getCond(), Continue, Info)) + return ESR_Failed; + if (!Continue) + break; + } // User's variable declaration, initialized by *__begin. + BlockScopeRAII InnerScope(Info); ESR = EvaluateStmt(Result, Info, FS->getLoopVarStmt()); if (ESR != ESR_Succeeded) return ESR; @@ -3410,6 +3508,9 @@ static bool HandleConstructorCall(SourceLocation CallLoc, const LValue &This, if (RD->isInvalidDecl()) return false; const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD); + // A scope for temporaries lifetime-extended by reference members. + BlockScopeRAII LifetimeExtendedScope(Info); + bool Success = true; unsigned BasesSeen = 0; #ifndef NDEBUG @@ -3476,6 +3577,7 @@ static bool HandleConstructorCall(SourceLocation CallLoc, const LValue &This, llvm_unreachable("unknown base initializer kind"); } + FullExpressionRAII InitScope(Info); if (!EvaluateInPlace(*Value, Info, Subobject, (*I)->getInit())) { // If we're checking for a potential constant expression, evaluate all // initializers even if some of them fail. @@ -3633,7 +3735,7 @@ public: RetTy VisitBinaryConditionalOperator(const BinaryConditionalOperator *E) { // Evaluate and cache the common expression. We treat it as a temporary, // even though it's not quite the same thing. - if (!Evaluate(Info.CurrentCall->Temporaries[E->getOpaqueValue()], + if (!Evaluate(Info.CurrentCall->createTemporary(E->getOpaqueValue(), false), Info, E->getCommon())) return false; @@ -3668,18 +3770,17 @@ public: } RetTy VisitOpaqueValueExpr(const OpaqueValueExpr *E) { - APValue &Value = Info.CurrentCall->Temporaries[E]; - if (Value.isUninit()) { - const Expr *Source = E->getSourceExpr(); - if (!Source) - return Error(E); - if (Source == E) { // sanity checking. - assert(0 && "OpaqueValueExpr recursively refers to itself"); - return Error(E); - } - return StmtVisitorTy::Visit(Source); + if (APValue *Value = Info.CurrentCall->getTemporary(E)) + return DerivedSuccess(*Value, E); + + const Expr *Source = E->getSourceExpr(); + if (!Source) + return Error(E); + if (Source == E) { // sanity checking. + assert(0 && "OpaqueValueExpr recursively refers to itself"); + return Error(E); } - return DerivedSuccess(Value, E); + return StmtVisitorTy::Visit(Source); } RetTy VisitCallExpr(const CallExpr *E) { @@ -3870,6 +3971,7 @@ public: if (Info.getIntOverflowCheckMode()) return Error(E); + BlockScopeRAII Scope(Info); const CompoundStmt *CS = E->getSubStmt(); for (CompoundStmt::const_body_iterator BI = CS->body_begin(), BE = CS->body_end(); @@ -4121,6 +4223,11 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) { APValue *V; if (!evaluateVarDeclInit(Info, E, VD, Frame, V)) return false; + if (V->isUninit()) { + if (!Info.CheckingPotentialConstantExpression) + Info.Diag(E, diag::note_constexpr_use_uninit_reference); + return false; + } return Success(*V, E); } @@ -4146,7 +4253,8 @@ bool LValueExprEvaluator::VisitMaterializeTemporaryExpr( *Value = APValue(); Result.set(E); } else { - Value = &Info.CurrentCall->Temporaries[E]; + Value = &Info.CurrentCall-> + createTemporary(E, E->getStorageDuration() == SD_Automatic); Result.set(E, Info.CurrentCall->Index); } @@ -4490,7 +4598,7 @@ bool PointerExprEvaluator::VisitCastExpr(const CastExpr* E) { return false; } else { Result.set(SubExpr, Info.CurrentCall->Index); - if (!EvaluateInPlace(Info.CurrentCall->Temporaries[SubExpr], + if (!EvaluateInPlace(Info.CurrentCall->createTemporary(SubExpr, false), Info, Result, SubExpr)) return false; } @@ -4940,7 +5048,8 @@ public: /// Visit an expression which constructs the value of this temporary. bool VisitConstructExpr(const Expr *E) { Result.set(E, Info.CurrentCall->Index); - return EvaluateInPlace(Info.CurrentCall->Temporaries[E], Info, Result, E); + return EvaluateInPlace(Info.CurrentCall->createTemporary(E, false), + Info, Result, E); } bool VisitCastExpr(const CastExpr *E) { @@ -7644,15 +7753,17 @@ static bool Evaluate(APValue &Result, EvalInfo &Info, const Expr *E) { } else if (T->isArrayType()) { LValue LV; LV.set(E, Info.CurrentCall->Index); - if (!EvaluateArray(E, LV, Info.CurrentCall->Temporaries[E], Info)) + APValue &Value = Info.CurrentCall->createTemporary(E, false); + if (!EvaluateArray(E, LV, Value, Info)) return false; - Result = Info.CurrentCall->Temporaries[E]; + Result = Value; } else if (T->isRecordType()) { LValue LV; LV.set(E, Info.CurrentCall->Index); - if (!EvaluateRecord(E, LV, Info.CurrentCall->Temporaries[E], Info)) + APValue &Value = Info.CurrentCall->createTemporary(E, false); + if (!EvaluateRecord(E, LV, Value, Info)) return false; - Result = Info.CurrentCall->Temporaries[E]; + Result = Value; } else if (T->isVoidType()) { if (!Info.getLangOpts().CPlusPlus11) Info.CCEDiag(E, diag::note_constexpr_nonliteral) diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index dc8b6346fe73..8d16962387e1 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -1709,3 +1709,21 @@ namespace ConstexprConstructorRecovery { }; constexpr X x{}; } + +namespace Lifetime { + void f() { + constexpr int &n = n; // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime}} expected-warning {{not yet bound to a value}} + constexpr int m = m; // expected-error {{constant expression}} expected-note {{read of object outside its lifetime}} + } + + constexpr int &get(int &&n) { return n; } + struct S { + int &&r; // expected-note 2{{declared here}} + int &s; + int t; + constexpr S() : r(0), s(get(0)), t(r) {} // expected-warning {{temporary}} + constexpr S(int) : r(0), s(get(0)), t(s) {} // expected-warning {{temporary}} expected-note {{read of object outside its lifetime}} + }; + constexpr int k1 = S().t; // ok, int is lifetime-extended to end of constructor + constexpr int k2 = S(0).t; // expected-error {{constant expression}} expected-note {{in call}} +} diff --git a/clang/test/SemaCXX/constant-expression-cxx1y.cpp b/clang/test/SemaCXX/constant-expression-cxx1y.cpp index 0d099c2120cf..ee88a3b959cc 100644 --- a/clang/test/SemaCXX/constant-expression-cxx1y.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx1y.cpp @@ -814,3 +814,59 @@ namespace VirtualFromBase { constexpr X *q = const_cast>*>(&xxs2); static_assert(q->f() == sizeof(X), ""); // expected-error {{constant expression}} expected-note {{virtual function call}} } + +namespace Lifetime { + constexpr int &get(int &&r) { return r; } + constexpr int f() { + int &r = get(123); + return r; // expected-note {{read of object outside its lifetime}} + } + static_assert(f() == 123, ""); // expected-error {{constant expression}} expected-note {{in call}} + + constexpr int g() { + int *p = 0; + { + int n = 0; + p = &n; + n = 42; + } + *p = 123; // expected-note {{assignment to object outside its lifetime}} + return *p; + } + static_assert(g() == 42, ""); // expected-error {{constant expression}} expected-note {{in call}} + + constexpr int h(int n) { + int *p[4] = {}; + int &&r = 1; + p[0] = &r; + while (int a = 1) { + p[1] = &a; + for (int b = 1; int c = 1; ) { + p[2] = &b, p[3] = &c; + break; + } + break; + } + *p[n] = 0; // expected-note 3{{assignment to object outside its lifetime}} + return *p[n]; + } + static_assert(h(0) == 0, ""); // ok, lifetime-extended + static_assert(h(1) == 0, ""); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(h(2) == 0, ""); // expected-error {{constant expression}} expected-note {{in call}} + static_assert(h(3) == 0, ""); // expected-error {{constant expression}} expected-note {{in call}} + + // FIXME: This function should be treated as non-constant. + constexpr void lifetime_versus_loops() { + int *p = 0; + for (int i = 0; i != 2; ++i) { + int *q = p; + int n = 0; + p = &n; + if (i) + // This modifies the 'n' from the previous iteration of the loop outside + // its lifetime. + ++*q; + } + } + static_assert((lifetime_versus_loops(), true), ""); +} diff --git a/clang/test/SemaCXX/i-c-e-cxx.cpp b/clang/test/SemaCXX/i-c-e-cxx.cpp index c80323ccd78a..39c6b1fc1320 100644 --- a/clang/test/SemaCXX/i-c-e-cxx.cpp +++ b/clang/test/SemaCXX/i-c-e-cxx.cpp @@ -16,7 +16,7 @@ void f() { } int a() { - const int t=t; // expected-note {{declared here}} + const int t=t; // expected-note {{declared here}} expected-note {{read of object outside its lifetime}} switch(1) { // expected-warning {{no case matching constant switch condition '1'}} case t:; // expected-error {{not an integral constant expression}} expected-note {{initializer of 't' is not a constant expression}} } diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 4b88697175fd..6cbc769769d8 100644 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -457,7 +457,7 @@ available.

Relaxing requirements on constexpr functions N3652 - Partial + SVN Member initializers and aggregates