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 |