Track variable lifetime through function calls (#1481)
This commit is contained in:
parent
d880d64a9c
commit
d376e9f245
|
@ -2468,61 +2468,19 @@ static bool valueFlowForward(Token * const startToken,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isNotLifetimeValue(const ValueFlow::Value& val)
|
static const Variable *getLifetimeVariable(const Token *tok, ErrorPath &errorPath)
|
||||||
{
|
{
|
||||||
return !val.isLifetimeValue();
|
const Variable *var = tok->variable();
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (astIsPointer(assignTok->astOperand2()) && !var->isPointer() && !(var->valueType() && var->valueType()->isIntegral()))
|
|
||||||
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)
|
if (!var)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
if (var->isReference() || var->isRValueReference()) {
|
if (var->isReference() || var->isRValueReference()) {
|
||||||
for (const ValueFlow::Value& v:tok->values()) {
|
for (const ValueFlow::Value &v : tok->values()) {
|
||||||
if (!v.isLifetimeValue())
|
if (!v.isLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
if (v.tokvalue == tok)
|
if (v.tokvalue == tok)
|
||||||
continue;
|
continue;
|
||||||
errorPath.insert(errorPath.end(), v.errorPath.begin(), v.errorPath.end());
|
errorPath.insert(errorPath.end(), v.errorPath.begin(), v.errorPath.end());
|
||||||
const Variable * var2 = getLifetimeVariable(v.tokvalue, errorPath);
|
const Variable *var2 = getLifetimeVariable(v.tokvalue, errorPath);
|
||||||
if (var2)
|
if (var2)
|
||||||
return var2;
|
return var2;
|
||||||
}
|
}
|
||||||
|
@ -2531,6 +2489,150 @@ static const Variable * getLifetimeVariable(const Token * tok, ErrorPath& errorP
|
||||||
return var;
|
return var;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isNotLifetimeValue(const ValueFlow::Value& val)
|
||||||
|
{
|
||||||
|
return !val.isLifetimeValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings);
|
||||||
|
|
||||||
|
static void valueFlowForwardLifetime(Token * tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings)
|
||||||
|
{
|
||||||
|
const Token *parent = tok->astParent();
|
||||||
|
while (parent && parent->isArithmeticalOp())
|
||||||
|
parent = parent->astParent();
|
||||||
|
if (!parent)
|
||||||
|
return;
|
||||||
|
// Assignment
|
||||||
|
if (parent->str() == "=" && !parent->astParent()) {
|
||||||
|
// Lhs should be a variable
|
||||||
|
if (!parent->astOperand1() || !parent->astOperand1()->varId())
|
||||||
|
return;
|
||||||
|
const Variable *var = parent->astOperand1()->variable();
|
||||||
|
if (!var || (!var->isLocal() && !var->isGlobal() && !var->isArgument()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Token *const endOfVarScope = var->typeStartToken()->scope()->bodyEnd;
|
||||||
|
|
||||||
|
// Rhs values..
|
||||||
|
if (!parent->astOperand2() || parent->astOperand2()->values().empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (astIsPointer(parent->astOperand2()) && !var->isPointer() &&
|
||||||
|
!(var->valueType() && var->valueType()->isIntegral()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::list<ValueFlow::Value> values = parent->astOperand2()->values();
|
||||||
|
|
||||||
|
// Static variable initialisation?
|
||||||
|
if (var->isStatic() && var->nameToken() == parent->astOperand1())
|
||||||
|
changeKnownToPossible(values);
|
||||||
|
|
||||||
|
// Skip RHS
|
||||||
|
const Token *nextExpression = nextAfterAstRightmostLeaf(parent);
|
||||||
|
|
||||||
|
// Only forward lifetime values
|
||||||
|
values.remove_if(&isNotLifetimeValue);
|
||||||
|
valueFlowForward(const_cast<Token *>(nextExpression),
|
||||||
|
endOfVarScope,
|
||||||
|
var,
|
||||||
|
var->declarationId(),
|
||||||
|
values,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
tokenlist,
|
||||||
|
errorLogger,
|
||||||
|
settings);
|
||||||
|
// Function call
|
||||||
|
} else if (Token::Match(parent->previous(), "%name% (")) {
|
||||||
|
valueFlowLifetimeFunction(const_cast<Token *>(parent->previous()), tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LifetimeStore {
|
||||||
|
const Token *argtok;
|
||||||
|
std::string message;
|
||||||
|
ValueFlow::Value::LifetimeKind type;
|
||||||
|
|
||||||
|
template <class Predicate>
|
||||||
|
void byRef(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings, Predicate pred) const {
|
||||||
|
ErrorPath errorPath;
|
||||||
|
const Variable *var = getLifetimeVariable(argtok, errorPath);
|
||||||
|
if (!var)
|
||||||
|
return;
|
||||||
|
if (!pred(var))
|
||||||
|
return;
|
||||||
|
errorPath.emplace_back(argtok, message);
|
||||||
|
|
||||||
|
ValueFlow::Value value;
|
||||||
|
value.valueType = ValueFlow::Value::LIFETIME;
|
||||||
|
value.tokvalue = var->nameToken();
|
||||||
|
value.errorPath = errorPath;
|
||||||
|
value.lifetimeKind = type;
|
||||||
|
// Dont 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void byRef(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings) const {
|
||||||
|
byRef(tok, tokenlist, errorLogger, settings, [](const Variable *) {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Predicate>
|
||||||
|
void byVal(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings, Predicate pred) const {
|
||||||
|
for (const ValueFlow::Value &v : argtok->values()) {
|
||||||
|
if (!v.isLifetimeValue())
|
||||||
|
continue;
|
||||||
|
const Token *tok3 = v.tokvalue;
|
||||||
|
ErrorPath errorPath = v.errorPath;
|
||||||
|
const Variable *var = getLifetimeVariable(tok3, errorPath);
|
||||||
|
if (!var)
|
||||||
|
continue;
|
||||||
|
if (!pred(var))
|
||||||
|
return;
|
||||||
|
errorPath.emplace_back(argtok, message);
|
||||||
|
|
||||||
|
ValueFlow::Value value;
|
||||||
|
value.valueType = ValueFlow::Value::LIFETIME;
|
||||||
|
value.tokvalue = var->nameToken();
|
||||||
|
value.errorPath = errorPath;
|
||||||
|
value.lifetimeKind = type;
|
||||||
|
// Dont add the value a second time
|
||||||
|
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
||||||
|
continue;
|
||||||
|
setTokenValue(tok, value, tokenlist->getSettings());
|
||||||
|
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void byVal(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings) const {
|
||||||
|
byVal(tok, tokenlist, errorLogger, settings, [](const Variable *) {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings)
|
||||||
|
{
|
||||||
|
if (!Token::Match(tok, "%name% ("))
|
||||||
|
return;
|
||||||
|
if (Token::Match(tok->tokAt(-2), "std :: ref|cref|tie|front_inserter|back_inserter")) {
|
||||||
|
for (const Token *argtok : getArguments(tok)) {
|
||||||
|
LifetimeStore{argtok, "Passed to '" + tok->str() + "'.", ValueFlow::Value::Object} .byRef(
|
||||||
|
tok->next(), tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
|
} else if (Token::Match(tok->tokAt(-2), "std :: make_tuple|tuple_cat|make_pair|make_reverse_iterator|next|prev|move")) {
|
||||||
|
for (const Token *argtok : getArguments(tok)) {
|
||||||
|
LifetimeStore{argtok, "Passed to '" + tok->str() + "'.", ValueFlow::Value::Object} .byVal(
|
||||||
|
tok->next(), tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Lambda {
|
struct Lambda {
|
||||||
explicit Lambda(const Token * tok)
|
explicit Lambda(const Token * tok)
|
||||||
: capture(nullptr), arguments(nullptr), returnTok(nullptr), bodyTok(nullptr) {
|
: capture(nullptr), arguments(nullptr), returnTok(nullptr), bodyTok(nullptr) {
|
||||||
|
@ -2570,6 +2672,16 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
|
|
||||||
std::set<const Scope *> scopes;
|
std::set<const Scope *> scopes;
|
||||||
|
|
||||||
|
auto isCapturingVariable = [&](const Variable *var) {
|
||||||
|
const Scope *scope = var->scope();
|
||||||
|
if (scopes.count(scope) > 0)
|
||||||
|
return false;
|
||||||
|
if (scope->isNestedIn(bodyScope))
|
||||||
|
return false;
|
||||||
|
scopes.insert(scope);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Handle explicit capture
|
// TODO: Handle explicit capture
|
||||||
bool captureByRef = Token::Match(lam.capture, "[ & ]");
|
bool captureByRef = Token::Match(lam.capture, "[ & ]");
|
||||||
bool captureByValue = Token::Match(lam.capture, "[ = ]");
|
bool captureByValue = Token::Match(lam.capture, "[ = ]");
|
||||||
|
@ -2577,51 +2689,11 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
for (const Token * tok2 = lam.bodyTok; tok2 != lam.bodyTok->link(); tok2 = tok2->next()) {
|
for (const Token * tok2 = lam.bodyTok; tok2 != lam.bodyTok->link(); tok2 = tok2->next()) {
|
||||||
ErrorPath errorPath;
|
ErrorPath errorPath;
|
||||||
if (captureByRef) {
|
if (captureByRef) {
|
||||||
const Variable * var = getLifetimeVariable(tok2, errorPath);
|
LifetimeStore{tok2, "Lambda captures variable by reference here.", ValueFlow::Value::Lambda} .byRef(
|
||||||
if (!var)
|
tok, tokenlist, errorLogger, settings, isCapturingVariable);
|
||||||
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) {
|
} else if (captureByValue) {
|
||||||
for (const ValueFlow::Value& v:tok2->values()) {
|
LifetimeStore{tok2, "Lambda captures variable by value here.", ValueFlow::Value::Lambda} .byVal(
|
||||||
if (!v.isLifetimeValue() && !v.tokvalue)
|
tok, tokenlist, errorLogger, settings, isCapturingVariable);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2681,6 +2753,10 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
valueFlowForwardLifetime(tok->tokAt(3), tokenlist, errorLogger, settings);
|
valueFlowForwardLifetime(tok->tokAt(3), tokenlist, errorLogger, settings);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// Check function calls
|
||||||
|
else if (Token::Match(tok, "%name% (")) {
|
||||||
|
valueFlowLifetimeFunction(tok, tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
// Check variables
|
// Check variables
|
||||||
else if (tok->variable()) {
|
else if (tok->variable()) {
|
||||||
ErrorPath errorPath;
|
ErrorPath errorPath;
|
||||||
|
|
|
@ -123,6 +123,7 @@ private:
|
||||||
TEST_CASE(danglingLifetimeLambda);
|
TEST_CASE(danglingLifetimeLambda);
|
||||||
TEST_CASE(danglingLifetimeContainer);
|
TEST_CASE(danglingLifetimeContainer);
|
||||||
TEST_CASE(danglingLifetime);
|
TEST_CASE(danglingLifetime);
|
||||||
|
TEST_CASE(danglingLifetimeFunction);
|
||||||
TEST_CASE(invalidLifetime);
|
TEST_CASE(invalidLifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1319,6 +1320,33 @@ private:
|
||||||
"}\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());
|
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"
|
||||||
|
" std::vector<int> x;\n"
|
||||||
|
" auto it = x.begin();\n"
|
||||||
|
" return std::next(it);\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:3] -> [test.cpp:4] -> [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() {\n"
|
||||||
|
" std::vector<int> x;\n"
|
||||||
|
" auto it = x.begin();\n"
|
||||||
|
" return it + 1;\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 it = x.begin();\n"
|
||||||
|
" return std::next(it + 1);\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:3] -> [test.cpp:4] -> [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() {\n"
|
check("auto f() {\n"
|
||||||
" static std::vector<int> x;\n"
|
" static std::vector<int> x;\n"
|
||||||
" return x.begin();\n"
|
" return x.begin();\n"
|
||||||
|
@ -1383,6 +1411,31 @@ private:
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void danglingLifetimeFunction() {
|
||||||
|
check("auto f() {\n"
|
||||||
|
" int a;\n"
|
||||||
|
" return std::ref(a);\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[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());
|
||||||
|
|
||||||
|
check("auto f() {\n"
|
||||||
|
" int a;\n"
|
||||||
|
" return std::make_tuple(std::ref(a));\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[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());
|
||||||
|
|
||||||
|
check("auto f(int x) {\n"
|
||||||
|
" int a;\n"
|
||||||
|
" std::tie(a) = x;\n"
|
||||||
|
" return a;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
}
|
||||||
|
|
||||||
void invalidLifetime() {
|
void invalidLifetime() {
|
||||||
check("void foo(int a) {\n"
|
check("void foo(int a) {\n"
|
||||||
" std::function<void()> f;\n"
|
" std::function<void()> f;\n"
|
||||||
|
|
Loading…
Reference in New Issue