Add initial lifetime checker (#1448)

* Inital valueflow lifetime checker

* Forward values

* Add initial tests

* Fix deplicate messages

* Fix traversing nested lambdas

* Turn test case into a todo

* Skip if returning a container

* Fix FP when using references

* Add missing header

* Fix FP from broken scopes

* Fix FP with static variable

* Add test for more FPs

* Parse lambda functions

* Check for capture by value

* Add tests for using a container and lambda together

* Fix cppcheck errors

* Add test for nextAfterAstRightmostLeaf

* Add valueflow tests

* Update error message

* Check for correct lambda token

* Improve error path reporting

* Fix hang when parsing arrays that look almlost like lambdas
This commit is contained in:
Paul Fultz II 2018-11-10 09:40:40 -06:00 committed by Daniel Marjamäki
parent e302e6e7a1
commit 1ffcc6b730
9 changed files with 641 additions and 20 deletions

View File

@ -136,11 +136,31 @@ const Token * astIsVariableComparison(const Token *tok, const std::string &comp,
return ret; return ret;
} }
static bool hasToken(const Token * startTok, const Token * stopTok, const Token * tok)
{
for(const Token * tok2 = startTok;tok2 != stopTok;tok2 = tok2->next()) {
if(tok2 == tok)
return true;
}
return false;
}
const Token * nextAfterAstRightmostLeaf(const Token * tok) const Token * nextAfterAstRightmostLeaf(const Token * tok)
{ {
if (!tok || !tok->astOperand1()) const Token * rightmostLeaf = tok;
if (!rightmostLeaf || !rightmostLeaf->astOperand1())
return nullptr; return nullptr;
return tok->findExpressionStartEndTokens().second->next(); do {
if (rightmostLeaf->astOperand2())
rightmostLeaf = rightmostLeaf->astOperand2();
else
rightmostLeaf = rightmostLeaf->astOperand1();
} while (rightmostLeaf->astOperand1());
while(Token::Match(rightmostLeaf->next(), "]|)") && !hasToken(rightmostLeaf->next()->link(), rightmostLeaf->next(), tok))
rightmostLeaf = rightmostLeaf->next();
if(rightmostLeaf->str() == "{" && rightmostLeaf->link())
rightmostLeaf = rightmostLeaf->link();
return rightmostLeaf->next();
} }
static const Token * getVariableInitExpression(const Variable * var) static const Token * getVariableInitExpression(const Variable * var)
@ -943,6 +963,10 @@ const Token *findLambdaEndToken(const Token *first)
{ {
if (!first || first->str() != "[") if (!first || first->str() != "[")
return nullptr; return nullptr;
if(!Token::Match(first->link(), "] (|{"))
return nullptr;
if(first->astOperand1() != first->link()->next())
return nullptr;
const Token * tok = first; const Token * tok = first;
if (tok->astOperand1() && tok->astOperand1()->str() == "(") if (tok->astOperand1() && tok->astOperand1()->str() == "(")

View File

@ -33,6 +33,7 @@
#include <cstddef> #include <cstddef>
#include <list> #include <list>
#include <functional>
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -587,6 +588,120 @@ void CheckAutoVariables::returnReference()
} }
} }
static bool isInScope(const Token * tok, const Scope * scope)
{
if(!tok)
return false;
if(!scope)
return false;
const Variable * var = tok->variable();
if(var && (var->isGlobal() || var->isStatic() || var->isExtern()))
return false;
if(tok->scope() && tok->scope()->isNestedIn(scope))
return true;
if(!var)
return false;
if(var->isArgument() && !var->isReference()) {
const Scope * tokScope = tok->scope();
if(!tokScope)
return false;
for(const Scope * argScope:tokScope->nestedList) {
if(argScope && argScope->isNestedIn(scope))
return true;
}
}
return false;
}
void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token * end)
{
if(!start)
return;
const Scope * scope = start->scope();
if(!scope)
return;
// If the scope is not set correctly then skip checking it
if(scope->bodyStart != start)
return;
for (const Token *tok = start; tok && tok != end; tok = tok->next()) {
// Skip duplicate warning from dangling references
if(Token::Match(tok, "& %var%"))
continue;
if(tok->variable() && tok->variable()->isPointer())
continue;
if(std::any_of(tok->values().begin(), tok->values().end(), std::mem_fn(&ValueFlow::Value::isTokValue)))
continue;
for(const ValueFlow::Value& val:tok->values()) {
if(!val.isLifetimeValue())
continue;
if(Token::Match(tok->astParent(), "return|throw")) {
if (isInScope(val.tokvalue, scope)) {
errorReturnDanglingLifetime(tok, &val);
break;
}
}
}
const Token *lambdaEndToken = findLambdaEndToken(tok);
if(lambdaEndToken) {
checkVarLifetimeScope(lambdaEndToken->link(), lambdaEndToken);
tok = lambdaEndToken;
}
}
}
void CheckAutoVariables::checkVarLifetime()
{
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
if (!scope->function)
continue;
// Skip if returning a container
const Library::Container * container = mSettings->library.detectContainer(scope->function->retDef);
if (container)
continue;
checkVarLifetimeScope(scope->bodyStart, scope->bodyEnd);
}
}
void CheckAutoVariables::errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value* val)
{
const Token *vartok = val->tokvalue;
ErrorPath errorPath = val->errorPath;
std::string msg = "";
switch(val->lifetimeKind) {
case ValueFlow::Value::Object:
msg = "Returning object";
break;
case ValueFlow::Value::Lambda:
msg = "Returning lambda";
break;
case ValueFlow::Value::Iterator:
msg = "Returning iterator";
break;
}
if(vartok) {
errorPath.emplace_back(vartok, "Variable created here.");
const Variable * var = vartok->variable();
if(var) {
switch(val->lifetimeKind) {
case ValueFlow::Value::Object:
msg += " that points to local variable";
break;
case ValueFlow::Value::Lambda:
msg += " that captures local variable";
break;
case ValueFlow::Value::Iterator:
msg += " to local container";
break;
}
msg += " '" + var->name() + "'";
}
}
errorPath.emplace_back(tok, "");
reportError(errorPath, Severity::error, "returnDanglingLifetime", msg + " that will be invalid when returning.", CWE562, false);
}
void CheckAutoVariables::errorReturnReference(const Token *tok) void CheckAutoVariables::errorReturnReference(const Token *tok)
{ {
reportError(tok, Severity::error, "returnReference", "Reference to auto variable returned.", CWE562, false); reportError(tok, Severity::error, "returnReference", "Reference to auto variable returned.", CWE562, false);

View File

@ -53,6 +53,7 @@ public:
CheckAutoVariables checkAutoVariables(tokenizer, settings, errorLogger); CheckAutoVariables checkAutoVariables(tokenizer, settings, errorLogger);
checkAutoVariables.assignFunctionArg(); checkAutoVariables.assignFunctionArg();
checkAutoVariables.returnReference(); checkAutoVariables.returnReference();
checkAutoVariables.checkVarLifetime();
} }
void runSimplifiedChecks(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) override { void runSimplifiedChecks(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) override {
@ -73,6 +74,10 @@ public:
/** Returning reference to local/temporary variable */ /** Returning reference to local/temporary variable */
void returnReference(); void returnReference();
void checkVarLifetime();
void checkVarLifetimeScope(const Token * start, const Token * end);
private: private:
/** /**
* Returning a temporary object? * Returning a temporary object?
@ -87,6 +92,7 @@ private:
void errorAssignAddressOfLocalVariableToGlobalPointer(const Token *pointer, const Token *variable); void errorAssignAddressOfLocalVariableToGlobalPointer(const Token *pointer, const Token *variable);
void errorReturnPointerToLocalArray(const Token *tok); void errorReturnPointerToLocalArray(const Token *tok);
void errorAutoVariableAssignment(const Token *tok, bool inconclusive); void errorAutoVariableAssignment(const Token *tok, bool inconclusive);
void errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value* val);
void errorReturnReference(const Token *tok); void errorReturnReference(const Token *tok);
void errorReturnTempReference(const Token *tok); void errorReturnTempReference(const Token *tok);
void errorInvalidDeallocation(const Token *tok, const ValueFlow::Value *val); void errorInvalidDeallocation(const Token *tok, const ValueFlow::Value *val);

View File

@ -959,6 +959,20 @@ public:
return nullptr; return nullptr;
} }
bool isNestedIn(const Scope * outer) const
{
if(!outer)
return false;
if(outer == this)
return true;
const Scope * parent = nestedIn;
while(outer != parent && parent)
parent = parent->nestedIn;
if(parent && parent == outer)
return true;
return false;
}
bool isClassOrStruct() const { bool isClassOrStruct() const {
return (type == eClass || type == eStruct); return (type == eClass || type == eStruct);
} }

View File

@ -1872,7 +1872,8 @@ static bool valueFlowForward(Token * const startToken,
// TODO: handle lambda functions // TODO: handle lambda functions
if (Token::simpleMatch(tok2, "= [")) { if (Token::simpleMatch(tok2, "= [")) {
Token *lambdaEndToken = const_cast<Token *>(findLambdaEndToken(tok2->next())); Token *lambdaEndToken = const_cast<Token *>(findLambdaEndToken(tok2->next()));
if (lambdaEndToken) { // Dont skip lambdas for lifetime values
if (lambdaEndToken && !std::all_of(values.begin(), values.end(), std::mem_fn(&ValueFlow::Value::isLifetimeValue))) {
tok2 = lambdaEndToken; tok2 = lambdaEndToken;
continue; continue;
} }
@ -2897,6 +2898,218 @@ static void valueFlowAfterCondition(TokenList *tokenlist, SymbolDatabase* symbol
} }
} }
static bool isNotLifetimeValue(const ValueFlow::Value& val)
{
return !val.isLifetimeValue();
}
static void valueFlowForwardLifetime(Token * tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings)
{
const Token * assignTok = tok->astParent();
// Assignment
if (!assignTok || (assignTok->str() != "=") || assignTok->astParent())
return;
// Lhs should be a variable
if (!assignTok->astOperand1() || !assignTok->astOperand1()->varId())
return;
const Variable *var = assignTok->astOperand1()->variable();
if (!var || (!var->isLocal() && !var->isGlobal() && !var->isArgument()))
return;
const Token * const endOfVarScope = var->typeStartToken()->scope()->bodyEnd;
// Rhs values..
if (!assignTok->astOperand2() || assignTok->astOperand2()->values().empty())
return;
std::list<ValueFlow::Value> values = assignTok->astOperand2()->values();
// Static variable initialisation?
if (var->isStatic() && var->nameToken() == assignTok->astOperand1())
changeKnownToPossible(values);
// Skip RHS
const Token * nextExpression = nextAfterAstRightmostLeaf(assignTok);
// Only forward lifetime values
values.remove_if(&isNotLifetimeValue);
valueFlowForward(const_cast<Token *>(nextExpression), endOfVarScope, var, var->declarationId(), values, false, false, tokenlist, errorLogger, settings);
}
static const Variable * getLifetimeVariable(const Token * tok, ErrorPath& errorPath)
{
const Variable * var = tok->variable();
if(!var)
return nullptr;
if(var->isReference() || var->isRValueReference()) {
for(const ValueFlow::Value& v:tok->values()) {
if(!v.isLifetimeValue() && !v.tokvalue)
continue;
errorPath.insert(errorPath.end(), v.errorPath.begin(), v.errorPath.end());
const Variable * var2 = getLifetimeVariable(v.tokvalue, errorPath);
if(var2)
return var2;
}
return nullptr;
}
return var;
}
struct Lambda
{
explicit Lambda(const Token * tok)
: capture(nullptr), arguments(nullptr), returnTok(nullptr), bodyTok(nullptr)
{
if(!Token::simpleMatch(tok, "[") || !tok->link())
return;
capture = tok;
if(Token::simpleMatch(capture->link(), "] (")) {
arguments = capture->link()->next();
}
const Token * afterArguments = arguments ? arguments->link()->next() : capture->link()->next();
if(afterArguments && afterArguments->originalName() == "->") {
returnTok = afterArguments->next();
bodyTok = Token::findsimplematch(returnTok, "{");
} else if(Token::simpleMatch(afterArguments, "{")) {
bodyTok = afterArguments;
}
}
const Token * capture;
const Token * arguments;
const Token * returnTok;
const Token * bodyTok;
bool isLambda() const
{
return capture && bodyTok;
}
};
static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase* symboldatabase, ErrorLogger *errorLogger, const Settings *settings)
{
for (Token *tok = tokenlist->front(); tok; tok = tok->next()) {
Lambda lam(tok);
// Lamdas
if(lam.isLambda()) {
const Scope * bodyScope = lam.bodyTok->scope();
std::set<const Scope *> scopes;
// TODO: Handle explicit capture
bool captureByRef = Token::Match(lam.capture, "[ & ]");
bool captureByValue = Token::Match(lam.capture, "[ = ]");
for(const Token * tok2 = lam.bodyTok;tok2 != lam.bodyTok->link();tok2 = tok2->next()) {
ErrorPath errorPath;
if(captureByRef) {
const Variable * var = getLifetimeVariable(tok2, errorPath);
if(!var)
continue;
const Scope * scope = var->scope();
if(scopes.count(scope) > 0)
continue;
if(scope->isNestedIn(bodyScope))
continue;
scopes.insert(scope);
errorPath.emplace_back(tok2, "Lambda captures variable by reference here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = ValueFlow::Value::Lambda;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
} else if(captureByValue) {
for(const ValueFlow::Value& v:tok2->values()) {
if(!v.isLifetimeValue() && !v.tokvalue)
continue;
const Token * tok3 = v.tokvalue;
errorPath = v.errorPath;
const Variable * var = getLifetimeVariable(tok3, errorPath);
if(!var)
continue;
const Scope * scope = var->scope();
if(scopes.count(scope) > 0)
continue;
if(scope->isNestedIn(bodyScope))
continue;
scopes.insert(scope);
errorPath.emplace_back(tok2, "Lambda captures variable by value here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = ValueFlow::Value::Lambda;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
}
}
}
}
// address of
else if (tok->isUnaryOp("&")) {
ErrorPath errorPath;
// child should be some buffer or variable
const Token *vartok = tok->astOperand1();
while (vartok) {
if (vartok->str() == "[")
vartok = vartok->astOperand1();
else if (vartok->str() == "." || vartok->str() == "::")
vartok = vartok->astOperand2();
else
break;
}
if(!vartok)
continue;
const Variable * var = getLifetimeVariable(vartok, errorPath);
if (!(var && !var->isPointer()))
continue;
errorPath.emplace_back(tok, "Address of variable taken here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
setTokenValue(tok, value, tokenlist->getSettings());
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
}
// container lifetimes
else if (tok->variable() && Token::Match(tok, "%var% . begin|cbegin|rbegin|crbegin|end|cend|rend|crend|data|c_str (")) {
ErrorPath errorPath;
const Library::Container * container = settings->library.detectContainer(tok->variable()->typeStartToken());
if(!container)
continue;
const Variable * var = tok->variable();
bool isIterator = !Token::Match(tok->tokAt(2), "data|c_str");
if(isIterator)
errorPath.emplace_back(tok, "Iterator to container is created here.");
else
errorPath.emplace_back(tok, "Pointer to container is created here.");
ValueFlow::Value value;
value.valueType = ValueFlow::Value::LIFETIME;
value.tokvalue = var->nameToken();
value.errorPath = errorPath;
value.lifetimeKind = isIterator ? ValueFlow::Value::Iterator : ValueFlow::Value::Object;
setTokenValue(tok->tokAt(3), value, tokenlist->getSettings());
valueFlowForwardLifetime(tok->tokAt(3), tokenlist, errorLogger, settings);
}
}
}
static void execute(const Token *expr, static void execute(const Token *expr,
ProgramMemory * const programMemory, ProgramMemory * const programMemory,
MathLib::bigint *result, MathLib::bigint *result,
@ -4050,6 +4263,7 @@ ValueFlow::Value::Value(const Token *c, long long val)
varId(0U), varId(0U),
conditional(false), conditional(false),
defaultArg(false), defaultArg(false),
lifetimeKind(Object),
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");
@ -4070,6 +4284,8 @@ std::string ValueFlow::Value::infoString() const
return "<Uninit>"; return "<Uninit>";
case CONTAINER_SIZE: case CONTAINER_SIZE:
return "size=" + MathLib::toString(intvalue); return "size=" + MathLib::toString(intvalue);
case LIFETIME:
return "lifetime=" + tokvalue->str();
}; };
throw InternalError(nullptr, "Invalid ValueFlow Value type"); throw InternalError(nullptr, "Invalid ValueFlow Value type");
} }
@ -4102,6 +4318,7 @@ void ValueFlow::setValues(TokenList *tokenlist, SymbolDatabase* symboldatabase,
valueFlowArray(tokenlist); valueFlowArray(tokenlist);
valueFlowGlobalStaticVar(tokenlist, settings); valueFlowGlobalStaticVar(tokenlist, settings);
valueFlowPointerAlias(tokenlist); valueFlowPointerAlias(tokenlist);
valueFlowLifetime(tokenlist, symboldatabase, errorLogger, settings);
valueFlowFunctionReturn(tokenlist, errorLogger); valueFlowFunctionReturn(tokenlist, errorLogger);
valueFlowBitAnd(tokenlist); valueFlowBitAnd(tokenlist);

View File

@ -39,7 +39,7 @@ 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), 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), 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 {
@ -68,6 +68,10 @@ namespace ValueFlow {
case CONTAINER_SIZE: case CONTAINER_SIZE:
if (intvalue != rhs.intvalue) if (intvalue != rhs.intvalue)
return false; return false;
break;
case LIFETIME:
if (tokvalue != rhs.tokvalue)
return false;
}; };
return varvalue == rhs.varvalue && return varvalue == rhs.varvalue &&
@ -80,7 +84,7 @@ namespace ValueFlow {
std::string infoString() const; std::string infoString() const;
enum ValueType { INT, TOK, FLOAT, MOVED, UNINIT, CONTAINER_SIZE } valueType; enum ValueType { INT, TOK, FLOAT, MOVED, UNINIT, CONTAINER_SIZE, LIFETIME } valueType;
bool isIntValue() const { bool isIntValue() const {
return valueType == INT; return valueType == INT;
} }
@ -99,6 +103,9 @@ namespace ValueFlow {
bool isContainerSizeValue() const { bool isContainerSizeValue() const {
return valueType == CONTAINER_SIZE; return valueType == CONTAINER_SIZE;
} }
bool isLifetimeValue() const {
return valueType == LIFETIME;
}
/** int value */ /** int value */
long long intvalue; long long intvalue;
@ -129,6 +136,8 @@ namespace ValueFlow {
/** Is this value passed as default parameter to the function? */ /** Is this value passed as default parameter to the function? */
bool defaultArg; bool defaultArg;
enum LifetimeKind {Object, Lambda, Iterator} lifetimeKind;
static const char * toString(MoveKind moveKind) { static const char * toString(MoveKind moveKind) {
switch (moveKind) { switch (moveKind) {
case NonMovedVariable: case NonMovedVariable:

View File

@ -37,6 +37,7 @@ private:
TEST_CASE(isReturnScope); TEST_CASE(isReturnScope);
TEST_CASE(isVariableChanged); TEST_CASE(isVariableChanged);
TEST_CASE(isVariableChangedByFunctionCall); TEST_CASE(isVariableChangedByFunctionCall);
TEST_CASE(nextAfterAstRightmostLeaf);
} }
bool findLambdaEndToken(const char code[]) { bool findLambdaEndToken(const char code[]) {
@ -129,6 +130,29 @@ private:
ASSERT_EQUALS(false, isVariableChangedByFunctionCall(code, "x ) ;", &inconclusive)); ASSERT_EQUALS(false, isVariableChangedByFunctionCall(code, "x ) ;", &inconclusive));
ASSERT_EQUALS(true, inconclusive); ASSERT_EQUALS(true, inconclusive);
} }
bool nextAfterAstRightmostLeaf(const char code[], const char parentPattern[], const char rightPattern[]) {
Settings settings;
Tokenizer tokenizer(&settings, this);
std::istringstream istr(code);
tokenizer.tokenize(istr, "test.cpp");
const Token * tok = Token::findsimplematch(tokenizer.tokens(), parentPattern);
return Token::simpleMatch(::nextAfterAstRightmostLeaf(tok), rightPattern);
}
void nextAfterAstRightmostLeaf() {
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("void f(int a, int b) { int x = a + b; }", "=", "; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a); }", "=", "; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b]; }", "=", "; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(g(a)[b]); }", "=", "; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(g(a)[b] + a); }", "=", "; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b + 1]; }", "=", "; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("void f() { int a; int b; int x = [](int a){}; }", "=", "; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = a + b; }", "+", "; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b + 1]; }", "+", "] ; }"));
ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a + 1)[b]; }", "+", ") ["));
}
}; };
REGISTER_TEST(TestAstUtils) REGISTER_TEST(TestAstUtils)

View File

@ -45,6 +45,7 @@ private:
CheckAutoVariables checkAutoVariables(&tokenizer, &settings, this); CheckAutoVariables checkAutoVariables(&tokenizer, &settings, this);
checkAutoVariables.returnReference(); checkAutoVariables.returnReference();
checkAutoVariables.assignFunctionArg(); checkAutoVariables.assignFunctionArg();
checkAutoVariables.checkVarLifetime();
if (runSimpleChecks) { if (runSimpleChecks) {
tokenizer.simplifyTokenList2(); tokenizer.simplifyTokenList2();
@ -118,6 +119,10 @@ private:
TEST_CASE(testconstructor); // ticket #5478 - crash TEST_CASE(testconstructor); // ticket #5478 - crash
TEST_CASE(variableIsUsedInScope); // ticket #5599 crash in variableIsUsedInScope() TEST_CASE(variableIsUsedInScope); // ticket #5599 crash in variableIsUsedInScope()
TEST_CASE(danglingLifetimeLambda);
TEST_CASE(danglingLifetimeContainer);
TEST_CASE(danglingLifetime);
} }
@ -1197,6 +1202,177 @@ private:
"}"); "}");
} }
void danglingLifetimeLambda() {
check("auto f() {\n"
" int a = 1;\n"
" auto l = [&](){ return a; };\n"
" return l;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2] -> [test.cpp:4]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning.\n", errout.str());
check("auto f() {\n"
" int a = 1;\n"
" return [&](){ return a; };\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning.\n", errout.str());
check("auto f(int a) {\n"
" return [&](){ return a; };\n"
"}\n");
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:1] -> [test.cpp:2]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning.\n", errout.str());
check("auto f(int a) {\n"
" auto p = &a;\n"
" return [=](){ return p; };\n"
"}\n");
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3] -> [test.cpp:1] -> [test.cpp:3]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning.\n", errout.str());
check("auto g(int& a) {\n"
" int p = a;\n"
" return [&](){ return p; };\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning lambda that captures local variable 'p' that will be invalid when returning.\n", errout.str());
check("auto f() {\n"
" return [=](){\n"
" int a = 1;\n"
" return [&](){ return a; };\n"
" };\n"
"}\n");
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:3] -> [test.cpp:4]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning.\n", errout.str());
// TODO: Variable is not set correctly for this case
check("auto f(int b) {\n"
" return [=](int a){\n"
" a += b;\n"
" return [&](){ return a; };\n"
" };\n"
"}\n");
TODO_ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning.\n", "", errout.str());
check("auto g(int& a) {\n"
" return [&](){ return a; };\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("auto g(int a) {\n"
" auto p = a;\n"
" return [=](){ return p; };\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("auto g(int& a) {\n"
" auto p = a;\n"
" return [=](){ return p; };\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("auto g(int& a) {\n"
" int& p = a;\n"
" return [&](){ return p; };\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("template<class F>\n"
"void g(F);\n"
"auto f() {\n"
" int x;\n"
" return g([&]() { return x; });\n"
"}\n");
ASSERT_EQUALS("", errout.str());
}
void danglingLifetimeContainer() {
check("auto f(const std::vector<int>& x) {\n"
" auto it = x.begin();\n"
" return it;\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("auto f() {\n"
" std::vector<int> x;\n"
" auto it = x.begin();\n"
" return it;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2] -> [test.cpp:4]: (error) Returning iterator to local container 'x' that will be invalid when returning.\n", errout.str());
check("auto f() {\n"
" std::vector<int> x;\n"
" auto p = x.data();\n"
" return p;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2] -> [test.cpp:4]: (error) Returning object that points to local variable 'x' that will be invalid when returning.\n", errout.str());
check("auto f(std::vector<int> x) {\n"
" auto it = x.begin();\n"
" return it;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:1] -> [test.cpp:3]: (error) Returning iterator to local container 'x' that will be invalid when returning.\n", errout.str());
check("auto f() {\n"
" static std::vector<int> x;\n"
" return x.begin();\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("std::string g() {\n"
" std::vector<char> v;\n"
" return v.data();\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("auto g() {\n"
" std::vector<char> v;\n"
" return {v, [v]() { return v.data(); }};\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("template<class F>\n"
"void g(F);\n"
"auto f() {\n"
" std::vector<char> v;\n"
" return g([&]() { return v.data(); });\n"
"}\n");
ASSERT_EQUALS("", errout.str());
}
void danglingLifetime() {
check("auto f() {\n"
" std::vector<int> a;\n"
" auto it = a.begin();\n"
" return [=](){ return it; };\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4] -> [test.cpp:2] -> [test.cpp:4]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning.\n", errout.str());
check("auto f(std::vector<int> a) {\n"
" auto it = a.begin();\n"
" return [=](){ return it; };\n"
"}\n");
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3] -> [test.cpp:1] -> [test.cpp:3]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning.\n", errout.str());
check("auto f(std::vector<int>& a) {\n"
" auto it = a.begin();\n"
" return [=](){ return it; };\n"
"}\n");
ASSERT_EQUALS("", errout.str());
// Make sure we dont hang
check("struct A;\n"
"void f() {\n"
" using T = A[3];\n"
" A &&a = T{1, 2, 3}[1];\n"
"}\n");
ASSERT_EQUALS("", errout.str());
// Make sure we dont hang
check("struct A;\n"
"void f() {\n"
" using T = A[3];\n"
" A &&a = T{1, 2, 3}[1]();\n"
"}\n");
ASSERT_EQUALS("", errout.str());
}
}; };
REGISTER_TEST(TestAutoVariables) REGISTER_TEST(TestAutoVariables)

View File

@ -53,6 +53,7 @@ private:
TEST_CASE(valueFlowNumber); TEST_CASE(valueFlowNumber);
TEST_CASE(valueFlowString); TEST_CASE(valueFlowString);
TEST_CASE(valueFlowPointerAlias); TEST_CASE(valueFlowPointerAlias);
TEST_CASE(valueFlowLifetime);
TEST_CASE(valueFlowArrayElement); TEST_CASE(valueFlowArrayElement);
TEST_CASE(valueFlowMove); TEST_CASE(valueFlowMove);
@ -193,7 +194,7 @@ private:
return ""; return "";
} }
bool testValueOfX(const char code[], unsigned int linenr, const char value[]) { bool testValueOfX(const char code[], unsigned int linenr, const char value[], ValueFlow::Value::ValueType type) {
// Tokenize.. // Tokenize..
Tokenizer tokenizer(&settings, this); Tokenizer tokenizer(&settings, this);
std::istringstream istr(code); std::istringstream istr(code);
@ -203,7 +204,7 @@ private:
if (tok->str() == "x" && tok->linenr() == linenr) { if (tok->str() == "x" && tok->linenr() == linenr) {
std::list<ValueFlow::Value>::const_iterator it; std::list<ValueFlow::Value>::const_iterator it;
for (it = tok->values().begin(); it != tok->values().end(); ++it) { for (it = tok->values().begin(); it != tok->values().end(); ++it) {
if (it->isTokValue() && Token::simpleMatch(it->tokvalue, value)) if (it->valueType == type && Token::simpleMatch(it->tokvalue, value))
return true; return true;
} }
} }
@ -305,7 +306,7 @@ private:
" if (a) x = \"123\";\n" " if (a) x = \"123\";\n"
" return x;\n" " return x;\n"
"}"; "}";
ASSERT_EQUALS(true, testValueOfX(code, 4, "\"123\"")); ASSERT_EQUALS(true, testValueOfX(code, 4, "\"123\"", ValueFlow::Value::TOK));
// valueFlowSubFunction // valueFlowSubFunction
code = "void dostuff(const char *x) {\n" code = "void dostuff(const char *x) {\n"
@ -313,7 +314,7 @@ private:
"}\n" "}\n"
"\n" "\n"
"void test() { dostuff(\"abc\"); }"; "void test() { dostuff(\"abc\"); }";
ASSERT_EQUALS(true, testValueOfX(code, 2, "\"abc\"")); ASSERT_EQUALS(true, testValueOfX(code, 2, "\"abc\"", ValueFlow::Value::TOK));
} }
void valueFlowPointerAlias() { void valueFlowPointerAlias() {
@ -325,7 +326,7 @@ private:
" if (a) x = &ret[0];\n" " if (a) x = &ret[0];\n"
" return x;\n" " return x;\n"
"}"; "}";
ASSERT_EQUALS(true, testValueOfX(code, 5, "& ret [ 0 ]")); ASSERT_EQUALS(true, testValueOfX(code, 5, "& ret [ 0 ]", ValueFlow::Value::TOK));
// dead pointer // dead pointer
code = "void f() {\n" code = "void f() {\n"
@ -333,7 +334,7 @@ private:
" if (cond) { int i; x = &i; }\n" " if (cond) { int i; x = &i; }\n"
" *x = 0;\n" // <- x can point at i " *x = 0;\n" // <- x can point at i
"}"; "}";
ASSERT_EQUALS(true, testValueOfX(code, 4, "& i")); ASSERT_EQUALS(true, testValueOfX(code, 4, "& i", ValueFlow::Value::TOK));
code = "void f() {\n" code = "void f() {\n"
" struct X *x;\n" " struct X *x;\n"
@ -343,6 +344,41 @@ private:
ASSERT_EQUALS(true, tokenValues(code, "x [").empty()); ASSERT_EQUALS(true, tokenValues(code, "x [").empty());
} }
void valueFlowLifetime() {
const char *code;
LOAD_LIB_2(settings.library, "std.cfg");
code = "void f() {\n"
" int a = 1;\n"
" auto x = [&]() { return a + 1; };\n"
" auto b = x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfX(code, 4, "a ;", ValueFlow::Value::LIFETIME));
code = "void f() {\n"
" int a = 1;\n"
" auto x = [=]() { return a + 1; };\n"
" auto b = x;\n"
"}\n";
ASSERT_EQUALS(false, testValueOfX(code, 4, "a ;", ValueFlow::Value::LIFETIME));
code = "void f(int v) {\n"
" int a = v;\n"
" int * p = &a;\n"
" auto x = [=]() { return p + 1; };\n"
" auto b = x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfX(code, 5, "a ;", ValueFlow::Value::LIFETIME));
code = "void f() {\n"
" std::vector<int> v;\n"
" auto x = v.begin();\n"
" auto it = x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfX(code, 4, "v ;", ValueFlow::Value::LIFETIME));
}
void valueFlowArrayElement() { void valueFlowArrayElement() {
const char *code; const char *code;
@ -350,26 +386,26 @@ private:
" const int x[] = {43,23,12};\n" " const int x[] = {43,23,12};\n"
" return x;\n" " return x;\n"
"}"; "}";
ASSERT_EQUALS(true, testValueOfX(code, 3U, "{ 43 , 23 , 12 }")); ASSERT_EQUALS(true, testValueOfX(code, 3U, "{ 43 , 23 , 12 }", ValueFlow::Value::TOK));
code = "void f() {\n" code = "void f() {\n"
" const char x[] = \"abcd\";\n" " const char x[] = \"abcd\";\n"
" return x;\n" " return x;\n"
"}"; "}";
ASSERT_EQUALS(true, testValueOfX(code, 3U, "\"abcd\"")); ASSERT_EQUALS(true, testValueOfX(code, 3U, "\"abcd\"", ValueFlow::Value::TOK));
code = "void f() {\n" code = "void f() {\n"
" char x[32] = \"abcd\";\n" " char x[32] = \"abcd\";\n"
" return x;\n" " return x;\n"
"}"; "}";
TODO_ASSERT_EQUALS(true, false, testValueOfX(code, 3U, "\"abcd\"")); TODO_ASSERT_EQUALS(true, false, testValueOfX(code, 3U, "\"abcd\"", ValueFlow::Value::TOK));
code = "void f() {\n" code = "void f() {\n"
" int a[10];\n" " int a[10];\n"
" int *x = a;\n" // <- a value is a " int *x = a;\n" // <- a value is a
" *x = 0;\n" // .. => x value is a " *x = 0;\n" // .. => x value is a
"}"; "}";
ASSERT_EQUALS(true, testValueOfX(code, 4, "a")); ASSERT_EQUALS(true, testValueOfX(code, 4, "a", ValueFlow::Value::TOK));
code = "char f() {\n" code = "char f() {\n"
" const char *x = \"abcd\";\n" " const char *x = \"abcd\";\n"
@ -1394,7 +1430,7 @@ private:
" a = ((x[0] == 'U') ?\n" " a = ((x[0] == 'U') ?\n"
" x[1] : 0);\n" // <- x is not "" " x[1] : 0);\n" // <- x is not ""
"}"; "}";
ASSERT_EQUALS(false, testValueOfX(code, 4U, "\"\"")); ASSERT_EQUALS(false, testValueOfX(code, 4U, "\"\"", ValueFlow::Value::TOK));
code = "void f() {\n" // #6973 code = "void f() {\n" // #6973
" char *x = getenv (\"LC_ALL\");\n" " char *x = getenv (\"LC_ALL\");\n"
@ -1407,10 +1443,10 @@ private:
" x[2] ))\n" // x can't be "" " x[2] ))\n" // x can't be ""
" {}\n" " {}\n"
"}\n"; "}\n";
ASSERT_EQUALS(true, testValueOfX(code, 6U, "\"\"")); ASSERT_EQUALS(true, testValueOfX(code, 6U, "\"\"", ValueFlow::Value::TOK));
ASSERT_EQUALS(false, testValueOfX(code, 7U, "\"\"")); ASSERT_EQUALS(false, testValueOfX(code, 7U, "\"\"", ValueFlow::Value::TOK));
ASSERT_EQUALS(false, testValueOfX(code, 8U, "\"\"")); ASSERT_EQUALS(false, testValueOfX(code, 8U, "\"\"", ValueFlow::Value::TOK));
ASSERT_EQUALS(false, testValueOfX(code, 9U, "\"\"")); ASSERT_EQUALS(false, testValueOfX(code, 9U, "\"\"", ValueFlow::Value::TOK));
code = "void f() {\n" // #7599 code = "void f() {\n" // #7599
" t *x = 0;\n" " t *x = 0;\n"