Lifetime: Support analysis with functions that do not return a reference (#1632)
* Initial support for function return * Add test case * Add support for reference parameters * Format
This commit is contained in:
parent
d6d8a871ca
commit
165a22ed0f
|
@ -622,7 +622,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const ValueFlow::Value& val:tok->values()) {
|
for (const ValueFlow::Value& val:tok->values()) {
|
||||||
if (!val.isLifetimeValue())
|
if (!val.isLocalLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
// Skip temporaries for now
|
// Skip temporaries for now
|
||||||
if (val.tokvalue == tok)
|
if (val.tokvalue == tok)
|
||||||
|
@ -677,28 +677,6 @@ void CheckAutoVariables::checkVarLifetime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string lifetimeType(const Token *tok, const ValueFlow::Value* val)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
if (!val)
|
|
||||||
return "object";
|
|
||||||
switch (val->lifetimeKind) {
|
|
||||||
case ValueFlow::Value::Lambda:
|
|
||||||
result = "lambda";
|
|
||||||
break;
|
|
||||||
case ValueFlow::Value::Iterator:
|
|
||||||
result = "iterator";
|
|
||||||
break;
|
|
||||||
case ValueFlow::Value::Object:
|
|
||||||
if (astIsPointer(tok))
|
|
||||||
result = "pointer";
|
|
||||||
else
|
|
||||||
result = "object";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string lifetimeMessage(const Token *tok, const ValueFlow::Value *val, ErrorPath &errorPath)
|
static std::string lifetimeMessage(const Token *tok, const ValueFlow::Value *val, ErrorPath &errorPath)
|
||||||
{
|
{
|
||||||
const Token *vartok = val ? val->tokvalue : nullptr;
|
const Token *vartok = val ? val->tokvalue : nullptr;
|
||||||
|
|
|
@ -2609,22 +2609,59 @@ static const Token *findSimpleReturn(const Function *f)
|
||||||
if (!scope)
|
if (!scope)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
const Token *returnTok = nullptr;
|
const Token *returnTok = nullptr;
|
||||||
for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) {
|
for (const Token *tok = scope->bodyStart->next(); tok && tok != scope->bodyEnd; tok = tok->next()) {
|
||||||
if (tok->str() == "{" && tok->scope() &&
|
if (tok->str() == "{" && tok->scope() &&
|
||||||
(tok->scope()->type == Scope::eLambda || tok->scope()->type == Scope::eClass)) {
|
(tok->scope()->type == Scope::eLambda || tok->scope()->type == Scope::eClass)) {
|
||||||
tok = tok->link();
|
tok = tok->link();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!Token::simpleMatch(tok->astParent(), "return"))
|
if (Token::simpleMatch(tok->astParent(), "return")) {
|
||||||
continue;
|
|
||||||
// Multiple returns
|
// Multiple returns
|
||||||
if (returnTok)
|
if (returnTok)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
returnTok = tok;
|
returnTok = tok;
|
||||||
}
|
}
|
||||||
|
// Skip lambda functions since the scope may not be set correctly
|
||||||
|
const Token *lambdaEndToken = findLambdaEndToken(tok);
|
||||||
|
if (lambdaEndToken) {
|
||||||
|
tok = lambdaEndToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
return returnTok;
|
return returnTok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int getArgumentPos(const Variable *var, const Function *f)
|
||||||
|
{
|
||||||
|
auto arg_it = std::find_if(f->argumentList.begin(), f->argumentList.end(), [&](const Variable &v) {
|
||||||
|
return v.nameToken() == var->nameToken();
|
||||||
|
});
|
||||||
|
if (arg_it == f->argumentList.end())
|
||||||
|
return -1;
|
||||||
|
return std::distance(f->argumentList.begin(), arg_it);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string lifetimeType(const Token *tok, const ValueFlow::Value *val)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
if (!val)
|
||||||
|
return "object";
|
||||||
|
switch (val->lifetimeKind) {
|
||||||
|
case ValueFlow::Value::Lambda:
|
||||||
|
result = "lambda";
|
||||||
|
break;
|
||||||
|
case ValueFlow::Value::Iterator:
|
||||||
|
result = "iterator";
|
||||||
|
break;
|
||||||
|
case ValueFlow::Value::Object:
|
||||||
|
if (astIsPointer(tok))
|
||||||
|
result = "pointer";
|
||||||
|
else
|
||||||
|
result = "object";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPath &errorPath)
|
const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPath &errorPath)
|
||||||
{
|
{
|
||||||
if (!tok)
|
if (!tok)
|
||||||
|
@ -2661,12 +2698,9 @@ const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPat
|
||||||
if (!argvar)
|
if (!argvar)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
if (argvar->isArgument() && (argvar->isReference() || argvar->isRValueReference())) {
|
if (argvar->isArgument() && (argvar->isReference() || argvar->isRValueReference())) {
|
||||||
auto arg_it = std::find_if(f->argumentList.begin(), f->argumentList.end(), [&](const Variable &v) {
|
int n = getArgumentPos(argvar, f);
|
||||||
return v.nameToken() == argvar->nameToken();
|
if (n < 0)
|
||||||
});
|
|
||||||
if (arg_it == f->argumentList.end())
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
std::size_t n = std::distance(f->argumentList.begin(), arg_it);
|
|
||||||
const Token *argTok = getArguments(tok->previous()).at(n);
|
const Token *argTok = getArguments(tok->previous()).at(n);
|
||||||
errorPath.emplace_back(returnTok, "Return reference.");
|
errorPath.emplace_back(returnTok, "Return reference.");
|
||||||
errorPath.emplace_back(tok->previous(), "Called function passing '" + argTok->str() + "'.");
|
errorPath.emplace_back(tok->previous(), "Called function passing '" + argTok->str() + "'.");
|
||||||
|
@ -2761,21 +2795,29 @@ struct LifetimeStore {
|
||||||
const Token *argtok;
|
const Token *argtok;
|
||||||
std::string message;
|
std::string message;
|
||||||
ValueFlow::Value::LifetimeKind type;
|
ValueFlow::Value::LifetimeKind type;
|
||||||
|
ErrorPath errorPath;
|
||||||
|
|
||||||
|
LifetimeStore(const Token *argtok,
|
||||||
|
const std::string &message,
|
||||||
|
ValueFlow::Value::LifetimeKind type = ValueFlow::Value::Object)
|
||||||
|
: argtok(argtok), message(message), type(type), errorPath()
|
||||||
|
{}
|
||||||
|
|
||||||
template <class Predicate>
|
template <class Predicate>
|
||||||
void byRef(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings, Predicate pred) const {
|
void byRef(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings, Predicate pred) const {
|
||||||
ErrorPath errorPath;
|
ErrorPath er = errorPath;
|
||||||
const Variable *var = getLifetimeVariable(argtok, errorPath);
|
const Variable *var = getLifetimeVariable(argtok, er);
|
||||||
if (!var)
|
if (!var)
|
||||||
return;
|
return;
|
||||||
if (!pred(var))
|
if (!pred(var))
|
||||||
return;
|
return;
|
||||||
errorPath.emplace_back(argtok, message);
|
er.emplace_back(argtok, message);
|
||||||
|
|
||||||
ValueFlow::Value value;
|
ValueFlow::Value value;
|
||||||
value.valueType = ValueFlow::Value::LIFETIME;
|
value.valueType = ValueFlow::Value::LIFETIME;
|
||||||
|
value.lifetimeScope = ValueFlow::Value::Local;
|
||||||
value.tokvalue = var->nameToken();
|
value.tokvalue = var->nameToken();
|
||||||
value.errorPath = errorPath;
|
value.errorPath = er;
|
||||||
value.lifetimeKind = type;
|
value.lifetimeKind = type;
|
||||||
// Don't add the value a second time
|
// Don't add the value a second time
|
||||||
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
||||||
|
@ -2792,22 +2834,42 @@ struct LifetimeStore {
|
||||||
|
|
||||||
template <class Predicate>
|
template <class Predicate>
|
||||||
void byVal(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings, Predicate pred) const {
|
void byVal(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings, Predicate pred) const {
|
||||||
|
if (argtok->values().empty()) {
|
||||||
|
ErrorPath er;
|
||||||
|
er.emplace_back(argtok, message);
|
||||||
|
const Variable *var = getLifetimeVariable(argtok, er);
|
||||||
|
if (var && var->isArgument()) {
|
||||||
|
ValueFlow::Value value;
|
||||||
|
value.valueType = ValueFlow::Value::LIFETIME;
|
||||||
|
value.lifetimeScope = ValueFlow::Value::Argument;
|
||||||
|
value.tokvalue = var->nameToken();
|
||||||
|
value.errorPath = er;
|
||||||
|
value.lifetimeKind = type;
|
||||||
|
// Don't add the value a second time
|
||||||
|
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
||||||
|
return;
|
||||||
|
setTokenValue(tok, value, tokenlist->getSettings());
|
||||||
|
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const ValueFlow::Value &v : argtok->values()) {
|
for (const ValueFlow::Value &v : argtok->values()) {
|
||||||
if (!v.isLifetimeValue())
|
if (!v.isLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
const Token *tok3 = v.tokvalue;
|
const Token *tok3 = v.tokvalue;
|
||||||
ErrorPath errorPath = v.errorPath;
|
ErrorPath er = v.errorPath;
|
||||||
const Variable *var = getLifetimeVariable(tok3, errorPath);
|
const Variable *var = getLifetimeVariable(tok3, er);
|
||||||
if (!var)
|
if (!var)
|
||||||
continue;
|
continue;
|
||||||
if (!pred(var))
|
if (!pred(var))
|
||||||
return;
|
return;
|
||||||
errorPath.emplace_back(argtok, message);
|
er.emplace_back(argtok, message);
|
||||||
|
er.insert(er.end(), errorPath.begin(), errorPath.end());
|
||||||
|
|
||||||
ValueFlow::Value value;
|
ValueFlow::Value value;
|
||||||
value.valueType = ValueFlow::Value::LIFETIME;
|
value.valueType = ValueFlow::Value::LIFETIME;
|
||||||
|
value.lifetimeScope = v.lifetimeScope;
|
||||||
value.tokvalue = var->nameToken();
|
value.tokvalue = var->nameToken();
|
||||||
value.errorPath = errorPath;
|
value.errorPath = er;
|
||||||
value.lifetimeKind = type;
|
value.lifetimeKind = type;
|
||||||
// Don't add the value a second time
|
// Don't add the value a second time
|
||||||
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
||||||
|
@ -2829,8 +2891,9 @@ struct LifetimeStore {
|
||||||
if (!v.isLifetimeValue())
|
if (!v.isLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
const Token *tok2 = v.tokvalue;
|
const Token *tok2 = v.tokvalue;
|
||||||
ErrorPath errorPath = v.errorPath;
|
ErrorPath er = v.errorPath;
|
||||||
const Variable *var = getLifetimeVariable(tok2, errorPath);
|
const Variable *var = getLifetimeVariable(tok2, er);
|
||||||
|
er.insert(er.end(), errorPath.begin(), errorPath.end());
|
||||||
if (!var)
|
if (!var)
|
||||||
continue;
|
continue;
|
||||||
for (const Token *tok3 = tok; tok3 && tok3 != var->declEndToken(); tok3 = tok3->previous()) {
|
for (const Token *tok3 = tok; tok3 && tok3 != var->declEndToken(); tok3 = tok3->previous()) {
|
||||||
|
@ -2893,6 +2956,36 @@ static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLog
|
||||||
LifetimeStore{args.back(), "Added to container '" + vartok->str() + "'.", ValueFlow::Value::Object} .byVal(
|
LifetimeStore{args.back(), "Added to container '" + vartok->str() + "'.", ValueFlow::Value::Object} .byVal(
|
||||||
vartok, tokenlist, errorLogger, settings);
|
vartok, tokenlist, errorLogger, settings);
|
||||||
}
|
}
|
||||||
|
} else if (tok->function()) {
|
||||||
|
const Function *f = tok->function();
|
||||||
|
if (Function::returnsReference(f))
|
||||||
|
return;
|
||||||
|
const Token *returnTok = findSimpleReturn(f);
|
||||||
|
if (!returnTok)
|
||||||
|
return;
|
||||||
|
for (const ValueFlow::Value &v : returnTok->values()) {
|
||||||
|
if (!v.isLifetimeValue())
|
||||||
|
continue;
|
||||||
|
if (!v.tokvalue)
|
||||||
|
continue;
|
||||||
|
const Variable *var = v.tokvalue->variable();
|
||||||
|
if (!var)
|
||||||
|
continue;
|
||||||
|
if (!var->isArgument())
|
||||||
|
continue;
|
||||||
|
int n = getArgumentPos(var, f);
|
||||||
|
if (n < 0)
|
||||||
|
continue;
|
||||||
|
const Token *argtok = getArguments(tok).at(n);
|
||||||
|
LifetimeStore ls{argtok, "Passed to '" + tok->str() + "'.", ValueFlow::Value::Object};
|
||||||
|
ls.errorPath = v.errorPath;
|
||||||
|
ls.errorPath.emplace_front(returnTok, "Return " + lifetimeType(returnTok, &v) + ".");
|
||||||
|
if (var->isReference() || var->isRValueReference()) {
|
||||||
|
ls.byRef(tok->next(), tokenlist, errorLogger, settings);
|
||||||
|
} else if (v.isArgumentLifetimeValue()) {
|
||||||
|
ls.byVal(tok->next(), tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2949,6 +3042,10 @@ static bool isDecayedPointer(const Token *tok, const Settings *settings)
|
||||||
static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger *errorLogger, const Settings *settings)
|
static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger *errorLogger, const Settings *settings)
|
||||||
{
|
{
|
||||||
for (Token *tok = tokenlist->front(); tok; tok = tok->next()) {
|
for (Token *tok = tokenlist->front(); tok; tok = tok->next()) {
|
||||||
|
if (!tok->scope())
|
||||||
|
continue;
|
||||||
|
if (tok->scope()->type == Scope::eGlobal)
|
||||||
|
continue;
|
||||||
Lambda lam(tok);
|
Lambda lam(tok);
|
||||||
// Lamdas
|
// Lamdas
|
||||||
if (lam.isLambda()) {
|
if (lam.isLambda()) {
|
||||||
|
@ -3009,6 +3106,7 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
|
|
||||||
ValueFlow::Value value;
|
ValueFlow::Value value;
|
||||||
value.valueType = ValueFlow::Value::LIFETIME;
|
value.valueType = ValueFlow::Value::LIFETIME;
|
||||||
|
value.lifetimeScope = ValueFlow::Value::Local;
|
||||||
value.tokvalue = var->nameToken();
|
value.tokvalue = var->nameToken();
|
||||||
value.errorPath = errorPath;
|
value.errorPath = errorPath;
|
||||||
setTokenValue(tok, value, tokenlist->getSettings());
|
setTokenValue(tok, value, tokenlist->getSettings());
|
||||||
|
@ -3031,6 +3129,7 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
|
|
||||||
ValueFlow::Value value;
|
ValueFlow::Value value;
|
||||||
value.valueType = ValueFlow::Value::LIFETIME;
|
value.valueType = ValueFlow::Value::LIFETIME;
|
||||||
|
value.lifetimeScope = ValueFlow::Value::Local;
|
||||||
value.tokvalue = var->nameToken();
|
value.tokvalue = var->nameToken();
|
||||||
value.errorPath = errorPath;
|
value.errorPath = errorPath;
|
||||||
value.lifetimeKind = isIterator ? ValueFlow::Value::Iterator : ValueFlow::Value::Object;
|
value.lifetimeKind = isIterator ? ValueFlow::Value::Iterator : ValueFlow::Value::Object;
|
||||||
|
@ -3056,6 +3155,7 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
|
|
||||||
ValueFlow::Value value;
|
ValueFlow::Value value;
|
||||||
value.valueType = ValueFlow::Value::LIFETIME;
|
value.valueType = ValueFlow::Value::LIFETIME;
|
||||||
|
value.lifetimeScope = ValueFlow::Value::Local;
|
||||||
value.tokvalue = var->nameToken();
|
value.tokvalue = var->nameToken();
|
||||||
value.errorPath = errorPath;
|
value.errorPath = errorPath;
|
||||||
setTokenValue(tok, value, tokenlist->getSettings());
|
setTokenValue(tok, value, tokenlist->getSettings());
|
||||||
|
@ -4822,6 +4922,7 @@ ValueFlow::Value::Value(const Token *c, long long val)
|
||||||
conditional(false),
|
conditional(false),
|
||||||
defaultArg(false),
|
defaultArg(false),
|
||||||
lifetimeKind(Object),
|
lifetimeKind(Object),
|
||||||
|
lifetimeScope(Local),
|
||||||
valueKind(ValueKind::Possible)
|
valueKind(ValueKind::Possible)
|
||||||
{
|
{
|
||||||
errorPath.emplace_back(c, "Assuming that condition '" + c->expressionString() + "' is not redundant");
|
errorPath.emplace_back(c, "Assuming that condition '" + c->expressionString() + "' is not redundant");
|
||||||
|
|
|
@ -40,7 +40,21 @@ namespace ValueFlow {
|
||||||
typedef std::pair<const Token *, std::string> ErrorPathItem;
|
typedef std::pair<const Token *, std::string> ErrorPathItem;
|
||||||
typedef std::list<ErrorPathItem> ErrorPath;
|
typedef std::list<ErrorPathItem> ErrorPath;
|
||||||
|
|
||||||
explicit Value(long long val = 0) : valueType(INT), intvalue(val), tokvalue(nullptr), floatValue(0.0), moveKind(NonMovedVariable), varvalue(val), condition(nullptr), varId(0U), conditional(false), defaultArg(false), lifetimeKind(Object), valueKind(ValueKind::Possible) {}
|
explicit Value(long long val = 0)
|
||||||
|
: valueType(INT),
|
||||||
|
intvalue(val),
|
||||||
|
tokvalue(nullptr),
|
||||||
|
floatValue(0.0),
|
||||||
|
moveKind(NonMovedVariable),
|
||||||
|
varvalue(val),
|
||||||
|
condition(nullptr),
|
||||||
|
varId(0U),
|
||||||
|
conditional(false),
|
||||||
|
defaultArg(false),
|
||||||
|
lifetimeKind(Object),
|
||||||
|
lifetimeScope(Local),
|
||||||
|
valueKind(ValueKind::Possible)
|
||||||
|
{}
|
||||||
Value(const Token *c, long long val);
|
Value(const Token *c, long long val);
|
||||||
|
|
||||||
bool operator==(const Value &rhs) const {
|
bool operator==(const Value &rhs) const {
|
||||||
|
@ -108,6 +122,10 @@ namespace ValueFlow {
|
||||||
return valueType == LIFETIME;
|
return valueType == LIFETIME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isLocalLifetimeValue() const { return valueType == LIFETIME && lifetimeScope == Local; }
|
||||||
|
|
||||||
|
bool isArgumentLifetimeValue() const { return valueType == LIFETIME && lifetimeScope == Argument; }
|
||||||
|
|
||||||
/** int value */
|
/** int value */
|
||||||
long long intvalue;
|
long long intvalue;
|
||||||
|
|
||||||
|
@ -139,6 +157,8 @@ namespace ValueFlow {
|
||||||
|
|
||||||
enum LifetimeKind {Object, Lambda, Iterator} lifetimeKind;
|
enum LifetimeKind {Object, Lambda, Iterator} lifetimeKind;
|
||||||
|
|
||||||
|
enum LifetimeScope { Local, Argument } lifetimeScope;
|
||||||
|
|
||||||
static const char * toString(MoveKind moveKind) {
|
static const char * toString(MoveKind moveKind) {
|
||||||
switch (moveKind) {
|
switch (moveKind) {
|
||||||
case NonMovedVariable:
|
case NonMovedVariable:
|
||||||
|
@ -207,4 +227,6 @@ namespace ValueFlow {
|
||||||
|
|
||||||
const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPath &errorPath);
|
const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPath &errorPath);
|
||||||
|
|
||||||
|
std::string lifetimeType(const Token *tok, const ValueFlow::Value *val);
|
||||||
|
|
||||||
#endif // valueflowH
|
#endif // valueflowH
|
||||||
|
|
|
@ -1752,6 +1752,29 @@ private:
|
||||||
"[test.cpp:3] -> [test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning object that points to local variable 'a' that will be invalid when returning.\n",
|
"[test.cpp:3] -> [test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning object that points to local variable 'a' that will be invalid when returning.\n",
|
||||||
errout.str());
|
errout.str());
|
||||||
|
|
||||||
|
check("template<class T>\n"
|
||||||
|
"auto by_value(T x) {\n"
|
||||||
|
" return [=] { return x; };\n"
|
||||||
|
"}\n"
|
||||||
|
"auto g() {\n"
|
||||||
|
" std::vector<int> v;\n"
|
||||||
|
" return by_value(v.begin());\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:7] -> [test.cpp:7] -> [test.cpp:3] -> [test.cpp:3] -> [test.cpp:6] -> [test.cpp:7]: (error) Returning object that points to local variable 'v' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("auto by_ref(int& x) {\n"
|
||||||
|
" return [&] { return x; };\n"
|
||||||
|
"}\n"
|
||||||
|
"auto f() {\n"
|
||||||
|
" int i = 0;\n"
|
||||||
|
" return by_ref(i);\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:2] -> [test.cpp:1] -> [test.cpp:2] -> [test.cpp:6] -> [test.cpp:5] -> [test.cpp:6]: (error) Returning object that points to local variable 'i' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
check("auto f(int x) {\n"
|
check("auto f(int x) {\n"
|
||||||
" int a;\n"
|
" int a;\n"
|
||||||
" std::tie(a) = x;\n"
|
" std::tie(a) = x;\n"
|
||||||
|
|
Loading…
Reference in New Issue