Track lifetime across multiple returns
This will now warn when doing something like this: ```cpp template <class T, class K, class V> const V& get_default(const T& t, const K& k, const V& v) { auto it = t.find(k); if (it == t.end()) return v; return it->second; } const int& bar(const std::unordered_map<int, int>& m, int k) { auto x = 0; return get_default(m, k, x); } ``` The lifetime warning is considered inconclusive in this case. I also updated valueflow to no tinject inconclusive values unless `--inconclusive` flag is passed. This creates some false negatives because library functions are not configured to not modify their input parameters, and there are some checks that do not check if the value is inconclusive or not.
This commit is contained in:
parent
a56bc006b7
commit
ba037837c9
|
@ -292,6 +292,23 @@ bool precedes(const Token * tok1, const Token * tok2)
|
||||||
return tok1->index() < tok2->index();
|
return tok1->index() < tok2->index();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isAliasOf(const Token *tok, nonneg int varid)
|
||||||
|
{
|
||||||
|
if (tok->varId() == varid)
|
||||||
|
return false;
|
||||||
|
if (tok->varId() == 0)
|
||||||
|
return false;
|
||||||
|
for (const ValueFlow::Value &val : tok->values()) {
|
||||||
|
if (!val.isLocalLifetimeValue())
|
||||||
|
continue;
|
||||||
|
if (val.isInconclusive())
|
||||||
|
continue;
|
||||||
|
if (val.tokvalue->varId() == varid)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static bool isAliased(const Token *startTok, const Token *endTok, nonneg int varid)
|
static bool isAliased(const Token *startTok, const Token *endTok, nonneg int varid)
|
||||||
{
|
{
|
||||||
if (!precedes(startTok, endTok))
|
if (!precedes(startTok, endTok))
|
||||||
|
@ -299,18 +316,8 @@ static bool isAliased(const Token *startTok, const Token *endTok, nonneg int var
|
||||||
for (const Token *tok = startTok; tok != endTok; tok = tok->next()) {
|
for (const Token *tok = startTok; tok != endTok; tok = tok->next()) {
|
||||||
if (Token::Match(tok, "= & %varid% ;", varid))
|
if (Token::Match(tok, "= & %varid% ;", varid))
|
||||||
return true;
|
return true;
|
||||||
if (tok->varId() == varid)
|
if (isAliasOf(tok, varid))
|
||||||
continue;
|
return true;
|
||||||
if (tok->varId() == 0)
|
|
||||||
continue;
|
|
||||||
if (!astIsPointer(tok))
|
|
||||||
continue;
|
|
||||||
for (const ValueFlow::Value &val : tok->values()) {
|
|
||||||
if (!val.isLocalLifetimeValue())
|
|
||||||
continue;
|
|
||||||
if (val.tokvalue->varId() == varid)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,6 +154,9 @@ bool isVariableChanged(const Variable * var, const Settings *settings, bool cpp,
|
||||||
const Token* findVariableChanged(const Token *start, const Token *end, int indirect, const nonneg int varid, bool globalvar, const Settings *settings, bool cpp, int depth = 20);
|
const Token* findVariableChanged(const Token *start, const Token *end, int indirect, const nonneg int varid, bool globalvar, const Settings *settings, bool cpp, int depth = 20);
|
||||||
Token* findVariableChanged(Token *start, const Token *end, int indirect, const nonneg int varid, bool globalvar, const Settings *settings, bool cpp, int depth = 20);
|
Token* findVariableChanged(Token *start, const Token *end, int indirect, const nonneg int varid, bool globalvar, const Settings *settings, bool cpp, int depth = 20);
|
||||||
|
|
||||||
|
/// If token is an alias if another variable
|
||||||
|
bool isAliasOf(const Token *tok, nonneg int varid);
|
||||||
|
|
||||||
bool isAliased(const Variable *var);
|
bool isAliased(const Variable *var);
|
||||||
|
|
||||||
/** Determines the number of arguments - if token is a function call or macro
|
/** Determines the number of arguments - if token is a function call or macro
|
||||||
|
|
|
@ -548,12 +548,15 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token
|
||||||
for (const Token *tok = start; tok && tok != end; tok = tok->next()) {
|
for (const Token *tok = start; tok && tok != end; tok = tok->next()) {
|
||||||
// Return reference from function
|
// Return reference from function
|
||||||
if (returnRef && Token::simpleMatch(tok->astParent(), "return")) {
|
if (returnRef && Token::simpleMatch(tok->astParent(), "return")) {
|
||||||
ErrorPath errorPath;
|
for (const LifetimeToken& lt : getLifetimeTokens(tok)) {
|
||||||
const Variable *var = getLifetimeVariable(tok, errorPath);
|
const Variable* var = lt.token->variable();
|
||||||
if (var && !var->isGlobal() && !var->isStatic() && !var->isReference() && !var->isRValueReference() &&
|
if (!var)
|
||||||
isInScope(var->nameToken(), tok->scope())) {
|
continue;
|
||||||
errorReturnReference(tok, errorPath);
|
if (!var->isGlobal() && !var->isStatic() && !var->isReference() && !var->isRValueReference() &&
|
||||||
continue;
|
isInScope(var->nameToken(), tok->scope())) {
|
||||||
|
errorReturnReference(tok, lt.errorPath, lt.inconclusive);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Assign reference to non-local variable
|
// Assign reference to non-local variable
|
||||||
} else if (Token::Match(tok->previous(), "&|&& %var% =") && tok->astParent() == tok->next() &&
|
} else if (Token::Match(tok->previous(), "&|&& %var% =") && tok->astParent() == tok->next() &&
|
||||||
|
@ -635,33 +638,37 @@ void CheckAutoVariables::checkVarLifetime()
|
||||||
|
|
||||||
void CheckAutoVariables::errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value *val)
|
void CheckAutoVariables::errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value *val)
|
||||||
{
|
{
|
||||||
|
const bool inconclusive = val ? val->isInconclusive() : false;
|
||||||
ErrorPath errorPath = val ? val->errorPath : ErrorPath();
|
ErrorPath errorPath = val ? val->errorPath : ErrorPath();
|
||||||
std::string msg = "Returning " + lifetimeMessage(tok, val, errorPath);
|
std::string msg = "Returning " + lifetimeMessage(tok, val, errorPath);
|
||||||
errorPath.emplace_back(tok, "");
|
errorPath.emplace_back(tok, "");
|
||||||
reportError(errorPath, Severity::error, "returnDanglingLifetime", msg + " that will be invalid when returning.", CWE562, false);
|
reportError(errorPath, Severity::error, "returnDanglingLifetime", msg + " that will be invalid when returning.", CWE562, inconclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckAutoVariables::errorInvalidLifetime(const Token *tok, const ValueFlow::Value* val)
|
void CheckAutoVariables::errorInvalidLifetime(const Token *tok, const ValueFlow::Value* val)
|
||||||
{
|
{
|
||||||
|
const bool inconclusive = val ? val->isInconclusive() : false;
|
||||||
ErrorPath errorPath = val ? val->errorPath : ErrorPath();
|
ErrorPath errorPath = val ? val->errorPath : ErrorPath();
|
||||||
std::string msg = "Using " + lifetimeMessage(tok, val, errorPath);
|
std::string msg = "Using " + lifetimeMessage(tok, val, errorPath);
|
||||||
errorPath.emplace_back(tok, "");
|
errorPath.emplace_back(tok, "");
|
||||||
reportError(errorPath, Severity::error, "invalidLifetime", msg + " that is out of scope.", CWE562, false);
|
reportError(errorPath, Severity::error, "invalidLifetime", msg + " that is out of scope.", CWE562, inconclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckAutoVariables::errorDanglngLifetime(const Token *tok, const ValueFlow::Value *val)
|
void CheckAutoVariables::errorDanglngLifetime(const Token *tok, const ValueFlow::Value *val)
|
||||||
{
|
{
|
||||||
|
const bool inconclusive = val ? val->isInconclusive() : false;
|
||||||
ErrorPath errorPath = val ? val->errorPath : ErrorPath();
|
ErrorPath errorPath = val ? val->errorPath : ErrorPath();
|
||||||
std::string tokName = tok ? tok->expressionString() : "x";
|
std::string tokName = tok ? tok->expressionString() : "x";
|
||||||
std::string msg = "Non-local variable '" + tokName + "' will use " + lifetimeMessage(tok, val, errorPath);
|
std::string msg = "Non-local variable '" + tokName + "' will use " + lifetimeMessage(tok, val, errorPath);
|
||||||
errorPath.emplace_back(tok, "");
|
errorPath.emplace_back(tok, "");
|
||||||
reportError(errorPath, Severity::error, "danglingLifetime", msg + ".", CWE562, false);
|
reportError(errorPath, Severity::error, "danglingLifetime", msg + ".", CWE562, inconclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckAutoVariables::errorReturnReference(const Token *tok, ErrorPath errorPath)
|
void CheckAutoVariables::errorReturnReference(const Token* tok, ErrorPath errorPath, bool inconclusive)
|
||||||
{
|
{
|
||||||
errorPath.emplace_back(tok, "");
|
errorPath.emplace_back(tok, "");
|
||||||
reportError(errorPath, Severity::error, "returnReference", "Reference to local variable returned.", CWE562, false);
|
reportError(
|
||||||
|
errorPath, Severity::error, "returnReference", "Reference to local variable returned.", CWE562, inconclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckAutoVariables::errorDanglingReference(const Token *tok, const Variable *var, ErrorPath errorPath)
|
void CheckAutoVariables::errorDanglingReference(const Token *tok, const Variable *var, ErrorPath errorPath)
|
||||||
|
|
|
@ -85,7 +85,7 @@ private:
|
||||||
void errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value* val);
|
void errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value* val);
|
||||||
void errorInvalidLifetime(const Token *tok, const ValueFlow::Value* val);
|
void errorInvalidLifetime(const Token *tok, const ValueFlow::Value* val);
|
||||||
void errorDanglngLifetime(const Token *tok, const ValueFlow::Value *val);
|
void errorDanglngLifetime(const Token *tok, const ValueFlow::Value *val);
|
||||||
void errorReturnReference(const Token *tok, ErrorPath errorPath);
|
void errorReturnReference(const Token* tok, ErrorPath errorPath, bool inconclusive);
|
||||||
void errorDanglingReference(const Token *tok, const Variable *var, ErrorPath errorPath);
|
void errorDanglingReference(const Token *tok, const Variable *var, ErrorPath errorPath);
|
||||||
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);
|
||||||
|
@ -99,7 +99,7 @@ private:
|
||||||
c.errorAutoVariableAssignment(nullptr, false);
|
c.errorAutoVariableAssignment(nullptr, false);
|
||||||
c.errorReturnAddressToAutoVariable(nullptr);
|
c.errorReturnAddressToAutoVariable(nullptr);
|
||||||
c.errorReturnPointerToLocalArray(nullptr);
|
c.errorReturnPointerToLocalArray(nullptr);
|
||||||
c.errorReturnReference(nullptr, errorPath);
|
c.errorReturnReference(nullptr, errorPath, false);
|
||||||
c.errorDanglingReference(nullptr, nullptr, errorPath);
|
c.errorDanglingReference(nullptr, nullptr, errorPath);
|
||||||
c.errorReturnTempReference(nullptr);
|
c.errorReturnTempReference(nullptr);
|
||||||
c.errorInvalidDeallocation(nullptr, nullptr);
|
c.errorInvalidDeallocation(nullptr, nullptr);
|
||||||
|
|
|
@ -881,13 +881,14 @@ void CheckStl::invalidContainer()
|
||||||
|
|
||||||
void CheckStl::invalidContainerError(const Token *tok, const Token * contTok, const ValueFlow::Value *val, ErrorPath errorPath)
|
void CheckStl::invalidContainerError(const Token *tok, const Token * contTok, const ValueFlow::Value *val, ErrorPath errorPath)
|
||||||
{
|
{
|
||||||
|
const bool inconclusive = val ? val->isInconclusive() : false;
|
||||||
std::string method = contTok ? contTok->strAt(2) : "erase";
|
std::string method = contTok ? contTok->strAt(2) : "erase";
|
||||||
errorPath.emplace_back(contTok, "After calling '" + method + "', iterators or references to the container's data may be invalid .");
|
errorPath.emplace_back(contTok, "After calling '" + method + "', iterators or references to the container's data may be invalid .");
|
||||||
if (val)
|
if (val)
|
||||||
errorPath.insert(errorPath.begin(), val->errorPath.begin(), val->errorPath.end());
|
errorPath.insert(errorPath.begin(), val->errorPath.begin(), val->errorPath.end());
|
||||||
std::string msg = "Using " + lifetimeMessage(tok, val, errorPath);
|
std::string msg = "Using " + lifetimeMessage(tok, val, errorPath);
|
||||||
errorPath.emplace_back(tok, "");
|
errorPath.emplace_back(tok, "");
|
||||||
reportError(errorPath, Severity::error, "invalidContainer", msg + " that may be invalid.", CWE664, false);
|
reportError(errorPath, Severity::error, "invalidContainer", msg + " that may be invalid.", CWE664, inconclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckStl::invalidContainerReferenceError(const Token* tok, const Token* contTok, ErrorPath errorPath)
|
void CheckStl::invalidContainerReferenceError(const Token* tok, const Token* contTok, ErrorPath errorPath)
|
||||||
|
|
|
@ -5912,7 +5912,7 @@ ValueType::MatchResult ValueType::matchParameter(const ValueType *call, const Va
|
||||||
if (call->type == ValueType::Type::VOID || func->type == ValueType::Type::VOID)
|
if (call->type == ValueType::Type::VOID || func->type == ValueType::Type::VOID)
|
||||||
return ValueType::MatchResult::FALLBACK1;
|
return ValueType::MatchResult::FALLBACK1;
|
||||||
if (call->pointer > 0 && func->pointer > 0)
|
if (call->pointer > 0 && func->pointer > 0)
|
||||||
return ValueType::MatchResult::NOMATCH;
|
return func->type == ValueType::UNKNOWN_TYPE ? ValueType::MatchResult::UNKNOWN : ValueType::MatchResult::NOMATCH;
|
||||||
if (call->isIntegral() && func->isIntegral())
|
if (call->isIntegral() && func->isIntegral())
|
||||||
return call->type < func->type ?
|
return call->type < func->type ?
|
||||||
ValueType::MatchResult::FALLBACK1 :
|
ValueType::MatchResult::FALLBACK1 :
|
||||||
|
|
|
@ -2165,25 +2165,6 @@ static bool evalAssignment(ValueFlow::Value &lhsValue, const std::string &assign
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isAliasOf(const Token *tok, nonneg int varid)
|
|
||||||
{
|
|
||||||
if (tok->varId() == varid)
|
|
||||||
return false;
|
|
||||||
if (tok->varId() == 0)
|
|
||||||
return false;
|
|
||||||
if (!astIsPointer(tok))
|
|
||||||
return false;
|
|
||||||
for (const ValueFlow::Value &val : tok->values()) {
|
|
||||||
if (!val.isLocalLifetimeValue())
|
|
||||||
continue;
|
|
||||||
if (val.lifetimeKind != ValueFlow::Value::LifetimeKind::Address)
|
|
||||||
continue;
|
|
||||||
if (val.tokvalue->varId() == varid)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if its an alias of the variable or is being aliased to this variable
|
// Check if its an alias of the variable or is being aliased to this variable
|
||||||
static bool isAliasOf(const Variable * var, const Token *tok, nonneg int varid, const std::list<ValueFlow::Value>& values)
|
static bool isAliasOf(const Variable * var, const Token *tok, nonneg int varid, const std::list<ValueFlow::Value>& values)
|
||||||
{
|
{
|
||||||
|
@ -2199,6 +2180,8 @@ static bool isAliasOf(const Variable * var, const Token *tok, nonneg int varid,
|
||||||
for (const ValueFlow::Value &val : values) {
|
for (const ValueFlow::Value &val : values) {
|
||||||
if (!val.isNonValue())
|
if (!val.isNonValue())
|
||||||
continue;
|
continue;
|
||||||
|
if (val.isInconclusive())
|
||||||
|
continue;
|
||||||
if (val.isLifetimeValue() && !val.isLocalLifetimeValue())
|
if (val.isLifetimeValue() && !val.isLocalLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
if (val.isLifetimeValue() && val.lifetimeKind != ValueFlow::Value::LifetimeKind::Address)
|
if (val.isLifetimeValue() && val.lifetimeKind != ValueFlow::Value::LifetimeKind::Address)
|
||||||
|
@ -2897,10 +2880,17 @@ static bool valueFlowForward(Token * const startToken,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (inconclusive) {
|
if (inconclusive) {
|
||||||
for (ValueFlow::Value &v : values) {
|
if (settings->inconclusive) {
|
||||||
if (v.indirect != i)
|
for (ValueFlow::Value &v : values) {
|
||||||
continue;
|
if (v.indirect != i)
|
||||||
v.setInconclusive();
|
continue;
|
||||||
|
v.setInconclusive();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If inconclusive flag not enable then remove the values
|
||||||
|
values.remove_if([&](const ValueFlow::Value &v) {
|
||||||
|
return v.indirect == i;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2981,6 +2971,30 @@ static const Token *findSimpleReturn(const Function *f)
|
||||||
return returnTok;
|
return returnTok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<const Token*> findReturns(const Function* f)
|
||||||
|
{
|
||||||
|
std::vector<const Token*> result;
|
||||||
|
const Scope* scope = f->functionScope;
|
||||||
|
if (!scope)
|
||||||
|
return result;
|
||||||
|
for (const Token* tok = scope->bodyStart->next(); tok && tok != scope->bodyEnd; tok = tok->next()) {
|
||||||
|
if (tok->str() == "{" && tok->scope() &&
|
||||||
|
(tok->scope()->type == Scope::eLambda || tok->scope()->type == Scope::eClass)) {
|
||||||
|
tok = tok->link();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Token::simpleMatch(tok->astParent(), "return")) {
|
||||||
|
result.push_back(tok);
|
||||||
|
}
|
||||||
|
// Skip lambda functions since the scope may not be set correctly
|
||||||
|
const Token* lambdaEndToken = findLambdaEndToken(tok);
|
||||||
|
if (lambdaEndToken) {
|
||||||
|
tok = lambdaEndToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static int getArgumentPos(const Variable *var, const Function *f)
|
static int getArgumentPos(const Variable *var, const Function *f)
|
||||||
{
|
{
|
||||||
auto arg_it = std::find_if(f->argumentList.begin(), f->argumentList.end(), [&](const Variable &v) {
|
auto arg_it = std::find_if(f->argumentList.begin(), f->argumentList.end(), [&](const Variable &v) {
|
||||||
|
@ -3052,6 +3066,8 @@ ValueFlow::Value getLifetimeObjValue(const Token *tok)
|
||||||
auto pred = [](const ValueFlow::Value &v) {
|
auto pred = [](const ValueFlow::Value &v) {
|
||||||
if (!v.isLocalLifetimeValue())
|
if (!v.isLocalLifetimeValue())
|
||||||
return false;
|
return false;
|
||||||
|
if (v.isInconclusive())
|
||||||
|
return false;
|
||||||
if (!v.tokvalue->variable())
|
if (!v.tokvalue->variable())
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
@ -3066,69 +3082,67 @@ ValueFlow::Value getLifetimeObjValue(const Token *tok)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const Token* getLifetimeToken(const Token* tok,
|
std::vector<LifetimeToken> getLifetimeTokens(const Token* tok, ValueFlow::Value::ErrorPath errorPath, int depth)
|
||||||
ValueFlow::Value::ErrorPath& errorPath,
|
|
||||||
bool* addressOf = nullptr,
|
|
||||||
int depth = 20)
|
|
||||||
{
|
{
|
||||||
if (!tok)
|
if (!tok)
|
||||||
return nullptr;
|
return std::vector<LifetimeToken> {};
|
||||||
const Variable *var = tok->variable();
|
const Variable *var = tok->variable();
|
||||||
if (depth < 0)
|
if (depth < 0)
|
||||||
return tok;
|
return {{tok, std::move(errorPath)}};
|
||||||
if (var && var->declarationId() == tok->varId()) {
|
if (var && var->declarationId() == tok->varId()) {
|
||||||
if (var->isReference() || var->isRValueReference()) {
|
if (var->isReference() || var->isRValueReference()) {
|
||||||
if (addressOf)
|
|
||||||
*addressOf = true;
|
|
||||||
if (!var->declEndToken())
|
if (!var->declEndToken())
|
||||||
return tok;
|
return {{tok, true, std::move(errorPath)}};
|
||||||
if (var->isArgument()) {
|
if (var->isArgument()) {
|
||||||
errorPath.emplace_back(var->declEndToken(), "Passed to reference.");
|
errorPath.emplace_back(var->declEndToken(), "Passed to reference.");
|
||||||
return tok;
|
return {{tok, true, std::move(errorPath)}};
|
||||||
} else if (Token::simpleMatch(var->declEndToken(), "=")) {
|
} else if (Token::simpleMatch(var->declEndToken(), "=")) {
|
||||||
errorPath.emplace_back(var->declEndToken(), "Assigned to reference.");
|
errorPath.emplace_back(var->declEndToken(), "Assigned to reference.");
|
||||||
const Token *vartok = var->declEndToken()->astOperand2();
|
const Token *vartok = var->declEndToken()->astOperand2();
|
||||||
if (vartok == tok)
|
if (vartok == tok)
|
||||||
return tok;
|
return {{tok, true, std::move(errorPath)}};
|
||||||
if (vartok)
|
if (vartok)
|
||||||
return getLifetimeToken(vartok, errorPath, addressOf, depth - 1);
|
return getLifetimeTokens(vartok, std::move(errorPath), depth - 1);
|
||||||
} else {
|
} else {
|
||||||
return nullptr;
|
return std::vector<LifetimeToken> {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (Token::Match(tok->previous(), "%name% (")) {
|
} else if (Token::Match(tok->previous(), "%name% (")) {
|
||||||
const Function *f = tok->previous()->function();
|
const Function *f = tok->previous()->function();
|
||||||
if (f) {
|
if (f) {
|
||||||
if (!Function::returnsReference(f))
|
if (!Function::returnsReference(f))
|
||||||
return tok;
|
return {{tok, std::move(errorPath)}};
|
||||||
const Token* returnTok = findSimpleReturn(f);
|
std::vector<LifetimeToken> result;
|
||||||
if (!returnTok)
|
std::vector<const Token*> returns = findReturns(f);
|
||||||
return tok;
|
for (const Token* returnTok : returns) {
|
||||||
if (returnTok == tok)
|
if (returnTok == tok)
|
||||||
return tok;
|
continue;
|
||||||
const Token* argvarTok = getLifetimeToken(returnTok, errorPath, addressOf, depth - 1);
|
for (LifetimeToken& lt : getLifetimeTokens(returnTok, std::move(errorPath), depth - 1)) {
|
||||||
if (!argvarTok)
|
const Token* argvarTok = lt.token;
|
||||||
return tok;
|
const Variable* argvar = argvarTok->variable();
|
||||||
const Variable* argvar = argvarTok->variable();
|
if (!argvar)
|
||||||
if (!argvar)
|
continue;
|
||||||
return tok;
|
if (argvar->isArgument() && (argvar->isReference() || argvar->isRValueReference())) {
|
||||||
if (argvar->isArgument() && (argvar->isReference() || argvar->isRValueReference())) {
|
int n = getArgumentPos(argvar, f);
|
||||||
int n = getArgumentPos(argvar, f);
|
if (n < 0)
|
||||||
if (n < 0)
|
return std::vector<LifetimeToken> {};
|
||||||
return nullptr;
|
const Token* argTok = getArguments(tok->previous()).at(n);
|
||||||
const Token* argTok = getArguments(tok->previous()).at(n);
|
lt.errorPath.emplace_back(returnTok, "Return reference.");
|
||||||
errorPath.emplace_back(returnTok, "Return reference.");
|
lt.errorPath.emplace_back(tok->previous(), "Called function passing '" + argTok->str() + "'.");
|
||||||
errorPath.emplace_back(tok->previous(), "Called function passing '" + argTok->str() + "'.");
|
std::vector<LifetimeToken> arglts = LifetimeToken::setInconclusive(
|
||||||
return getLifetimeToken(argTok, errorPath, addressOf, depth - 1);
|
getLifetimeTokens(argTok, std::move(lt.errorPath), depth - 1), returns.size() > 1);
|
||||||
|
result.insert(result.end(), arglts.begin(), arglts.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
} else if (Token::Match(tok->tokAt(-2), ". %name% (") && astIsContainer(tok->tokAt(-2)->astOperand1())) {
|
} else if (Token::Match(tok->tokAt(-2), ". %name% (") && astIsContainer(tok->tokAt(-2)->astOperand1())) {
|
||||||
const Library::Container* library = getLibraryContainer(tok->tokAt(-2)->astOperand1());
|
const Library::Container* library = getLibraryContainer(tok->tokAt(-2)->astOperand1());
|
||||||
Library::Container::Yield y = library->getYield(tok->previous()->str());
|
Library::Container::Yield y = library->getYield(tok->previous()->str());
|
||||||
if (y == Library::Container::Yield::AT_INDEX || y == Library::Container::Yield::ITEM) {
|
if (y == Library::Container::Yield::AT_INDEX || y == Library::Container::Yield::ITEM) {
|
||||||
errorPath.emplace_back(tok->previous(), "Accessing container.");
|
errorPath.emplace_back(tok->previous(), "Accessing container.");
|
||||||
if (addressOf)
|
return LifetimeToken::setAddressOf(
|
||||||
*addressOf = false;
|
getLifetimeTokens(tok->tokAt(-2)->astOperand1(), std::move(errorPath), depth - 1), false);
|
||||||
return getLifetimeToken(tok->tokAt(-2)->astOperand1(), errorPath, nullptr, depth - 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (Token::Match(tok, ".|::|[")) {
|
} else if (Token::Match(tok, ".|::|[")) {
|
||||||
|
@ -3143,7 +3157,7 @@ static const Token* getLifetimeToken(const Token* tok,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vartok)
|
if (!vartok)
|
||||||
return tok;
|
return {{tok, std::move(errorPath)}};
|
||||||
const Variable *tokvar = vartok->variable();
|
const Variable *tokvar = vartok->variable();
|
||||||
if (!astIsContainer(vartok) && !(tokvar && tokvar->isArray()) &&
|
if (!astIsContainer(vartok) && !(tokvar && tokvar->isArray()) &&
|
||||||
(Token::Match(vartok->astParent(), "[|*") || vartok->astParent()->originalName() == "->")) {
|
(Token::Match(vartok->astParent(), "[|*") || vartok->astParent()->originalName() == "->")) {
|
||||||
|
@ -3151,17 +3165,27 @@ static const Token* getLifetimeToken(const Token* tok,
|
||||||
if (!v.isLocalLifetimeValue())
|
if (!v.isLocalLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
errorPath.insert(errorPath.end(), v.errorPath.begin(), v.errorPath.end());
|
errorPath.insert(errorPath.end(), v.errorPath.begin(), v.errorPath.end());
|
||||||
return getLifetimeToken(v.tokvalue, errorPath, addressOf);
|
return getLifetimeTokens(v.tokvalue, std::move(errorPath));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (addressOf && astIsContainer(vartok) && Token::simpleMatch(vartok->astParent(), "[")) {
|
return LifetimeToken::setAddressOf(getLifetimeTokens(vartok, std::move(errorPath)),
|
||||||
*addressOf = false;
|
!(astIsContainer(vartok) && Token::simpleMatch(vartok->astParent(), "[")));
|
||||||
addressOf = nullptr;
|
|
||||||
}
|
|
||||||
return getLifetimeToken(vartok, errorPath, addressOf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tok;
|
return {{tok, std::move(errorPath)}};
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Token* getLifetimeToken(const Token* tok, ValueFlow::Value::ErrorPath& errorPath, bool* addressOf = nullptr)
|
||||||
|
{
|
||||||
|
std::vector<LifetimeToken> lts = getLifetimeTokens(tok);
|
||||||
|
if (lts.size() != 1)
|
||||||
|
return nullptr;
|
||||||
|
if (lts.front().inconclusive)
|
||||||
|
return nullptr;
|
||||||
|
if (addressOf)
|
||||||
|
*addressOf = lts.front().addressOf;
|
||||||
|
errorPath.insert(errorPath.end(), lts.front().errorPath.begin(), lts.front().errorPath.end());
|
||||||
|
return lts.front().token;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Variable* getLifetimeVariable(const Token* tok, ValueFlow::Value::ErrorPath& errorPath, bool* addressOf)
|
const Variable* getLifetimeVariable(const Token* tok, ValueFlow::Value::ErrorPath& errorPath, bool* addressOf)
|
||||||
|
@ -3360,15 +3384,17 @@ struct LifetimeStore {
|
||||||
std::string message;
|
std::string message;
|
||||||
ValueFlow::Value::LifetimeKind type;
|
ValueFlow::Value::LifetimeKind type;
|
||||||
ErrorPath errorPath;
|
ErrorPath errorPath;
|
||||||
|
bool inconclusive;
|
||||||
|
|
||||||
LifetimeStore()
|
LifetimeStore()
|
||||||
: argtok(nullptr), message(), type(), errorPath()
|
: argtok(nullptr), message(), type(), errorPath(), inconclusive(false)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
LifetimeStore(const Token *argtok,
|
LifetimeStore(const Token *argtok,
|
||||||
const std::string &message,
|
const std::string &message,
|
||||||
ValueFlow::Value::LifetimeKind type = ValueFlow::Value::LifetimeKind::Object)
|
ValueFlow::Value::LifetimeKind type = ValueFlow::Value::LifetimeKind::Object,
|
||||||
: argtok(argtok), message(message), type(type), errorPath()
|
bool inconclusive = false)
|
||||||
|
: argtok(argtok), message(message), type(type), errorPath(), inconclusive(inconclusive)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
static LifetimeStore fromFunctionArg(const Function * f, Token *tok, const Variable *var, TokenList *tokenlist, ErrorLogger *errorLogger) {
|
static LifetimeStore fromFunctionArg(const Function * f, Token *tok, const Variable *var, TokenList *tokenlist, ErrorLogger *errorLogger) {
|
||||||
|
@ -3396,27 +3422,34 @@ struct LifetimeStore {
|
||||||
|
|
||||||
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 {
|
||||||
|
if (!settings->inconclusive && inconclusive)
|
||||||
|
return;
|
||||||
if (!argtok)
|
if (!argtok)
|
||||||
return;
|
return;
|
||||||
ErrorPath er = errorPath;
|
for (const LifetimeToken& lt : getLifetimeTokens(argtok)) {
|
||||||
const Token *lifeTok = getLifetimeToken(argtok, er);
|
if (!settings->inconclusive && lt.inconclusive)
|
||||||
if (!lifeTok)
|
continue;
|
||||||
return;
|
ErrorPath er = errorPath;
|
||||||
if (!pred(lifeTok))
|
er.insert(er.end(), lt.errorPath.begin(), lt.errorPath.end());
|
||||||
return;
|
if (!lt.token)
|
||||||
er.emplace_back(argtok, message);
|
return;
|
||||||
|
if (!pred(lt.token))
|
||||||
|
return;
|
||||||
|
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::LifetimeScope::Local;
|
value.lifetimeScope = ValueFlow::Value::LifetimeScope::Local;
|
||||||
value.tokvalue = lifeTok;
|
value.tokvalue = lt.token;
|
||||||
value.errorPath = er;
|
value.errorPath = std::move(er);
|
||||||
value.lifetimeKind = type;
|
value.lifetimeKind = type;
|
||||||
// Don't add the value a second time
|
value.setInconclusive(lt.inconclusive || inconclusive);
|
||||||
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
// Don't add the value a second time
|
||||||
return;
|
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
||||||
setTokenValue(tok, value, tokenlist->getSettings());
|
return;
|
||||||
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
setTokenValue(tok, value, tokenlist->getSettings());
|
||||||
|
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void byRef(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings) const {
|
void byRef(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings) const {
|
||||||
|
@ -3427,6 +3460,8 @@ 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 (!settings->inconclusive && inconclusive)
|
||||||
|
return;
|
||||||
if (!argtok)
|
if (!argtok)
|
||||||
return;
|
return;
|
||||||
if (argtok->values().empty()) {
|
if (argtok->values().empty()) {
|
||||||
|
@ -3440,6 +3475,7 @@ struct LifetimeStore {
|
||||||
value.tokvalue = var->nameToken();
|
value.tokvalue = var->nameToken();
|
||||||
value.errorPath = er;
|
value.errorPath = er;
|
||||||
value.lifetimeKind = type;
|
value.lifetimeKind = type;
|
||||||
|
value.setInconclusive(inconclusive);
|
||||||
// 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())
|
||||||
return;
|
return;
|
||||||
|
@ -3451,26 +3487,31 @@ struct LifetimeStore {
|
||||||
if (!v.isLifetimeValue())
|
if (!v.isLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
const Token *tok3 = v.tokvalue;
|
const Token *tok3 = v.tokvalue;
|
||||||
ErrorPath er = v.errorPath;
|
for (const LifetimeToken& lt : getLifetimeTokens(tok3)) {
|
||||||
const Token *lifeTok = getLifetimeToken(tok3, er);
|
if (!settings->inconclusive && lt.inconclusive)
|
||||||
if (!lifeTok)
|
continue;
|
||||||
return;
|
ErrorPath er = v.errorPath;
|
||||||
if (!pred(lifeTok))
|
er.insert(er.end(), lt.errorPath.begin(), lt.errorPath.end());
|
||||||
return;
|
if (!lt.token)
|
||||||
er.emplace_back(argtok, message);
|
return;
|
||||||
er.insert(er.end(), errorPath.begin(), errorPath.end());
|
if (!pred(lt.token))
|
||||||
|
return;
|
||||||
|
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.lifetimeScope = v.lifetimeScope;
|
||||||
value.tokvalue = lifeTok;
|
value.tokvalue = lt.token;
|
||||||
value.errorPath = er;
|
value.errorPath = std::move(er);
|
||||||
value.lifetimeKind = type;
|
value.lifetimeKind = type;
|
||||||
// Don't add the value a second time
|
value.setInconclusive(lt.inconclusive || v.isInconclusive() || inconclusive);
|
||||||
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
// Don't add the value a second time
|
||||||
continue;
|
if (std::find(tok->values().begin(), tok->values().end(), value) != tok->values().end())
|
||||||
setTokenValue(tok, value, tokenlist->getSettings());
|
continue;
|
||||||
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
setTokenValue(tok, value, tokenlist->getSettings());
|
||||||
|
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3482,6 +3523,8 @@ struct LifetimeStore {
|
||||||
|
|
||||||
template <class Predicate>
|
template <class Predicate>
|
||||||
void byDerefCopy(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings, Predicate pred) const {
|
void byDerefCopy(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings, Predicate pred) const {
|
||||||
|
if (!settings->inconclusive && inconclusive)
|
||||||
|
return;
|
||||||
if (!argtok)
|
if (!argtok)
|
||||||
return;
|
return;
|
||||||
for (const ValueFlow::Value &v : argtok->values()) {
|
for (const ValueFlow::Value &v : argtok->values()) {
|
||||||
|
@ -3495,7 +3538,7 @@ struct LifetimeStore {
|
||||||
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()) {
|
||||||
if (tok3->varId() == var->declarationId()) {
|
if (tok3->varId() == var->declarationId()) {
|
||||||
LifetimeStore{tok3, message, type} .byVal(tok, tokenlist, errorLogger, settings, pred);
|
LifetimeStore{tok3, message, type, inconclusive} .byVal(tok, tokenlist, errorLogger, settings, pred);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3541,30 +3584,35 @@ static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLog
|
||||||
const Function *f = tok->function();
|
const Function *f = tok->function();
|
||||||
if (Function::returnsReference(f))
|
if (Function::returnsReference(f))
|
||||||
return;
|
return;
|
||||||
const Token *returnTok = findSimpleReturn(f);
|
std::vector<const Token*> returns = findReturns(f);
|
||||||
if (!returnTok)
|
const bool inconclusive = returns.size() > 1;
|
||||||
return;
|
for (const Token* returnTok : returns) {
|
||||||
const Variable *returnVar = returnTok->variable();
|
if (returnTok == tok)
|
||||||
if (returnVar && returnVar->isArgument() && (returnVar->isConst() || !isVariableChanged(returnVar, settings, tokenlist->isCPP()))) {
|
|
||||||
LifetimeStore ls = LifetimeStore::fromFunctionArg(f, tok, returnVar, tokenlist, errorLogger);
|
|
||||||
ls.byVal(tok->next(), tokenlist, errorLogger, settings);
|
|
||||||
}
|
|
||||||
for (const ValueFlow::Value &v : returnTok->values()) {
|
|
||||||
if (!v.isLifetimeValue())
|
|
||||||
continue;
|
continue;
|
||||||
if (!v.tokvalue)
|
const Variable *returnVar = returnTok->variable();
|
||||||
continue;
|
if (returnVar && returnVar->isArgument() && (returnVar->isConst() || !isVariableChanged(returnVar, settings, tokenlist->isCPP()))) {
|
||||||
const Variable *var = v.tokvalue->variable();
|
LifetimeStore ls = LifetimeStore::fromFunctionArg(f, tok, returnVar, tokenlist, errorLogger);
|
||||||
LifetimeStore ls = LifetimeStore::fromFunctionArg(f, tok, var, tokenlist, errorLogger);
|
ls.inconclusive = inconclusive;
|
||||||
if (!ls.argtok)
|
|
||||||
continue;
|
|
||||||
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);
|
ls.byVal(tok->next(), tokenlist, errorLogger, settings);
|
||||||
}
|
}
|
||||||
|
for (const ValueFlow::Value &v : returnTok->values()) {
|
||||||
|
if (!v.isLifetimeValue())
|
||||||
|
continue;
|
||||||
|
if (!v.tokvalue)
|
||||||
|
continue;
|
||||||
|
const Variable *var = v.tokvalue->variable();
|
||||||
|
LifetimeStore ls = LifetimeStore::fromFunctionArg(f, tok, var, tokenlist, errorLogger);
|
||||||
|
if (!ls.argtok)
|
||||||
|
continue;
|
||||||
|
ls.inconclusive = inconclusive;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3719,23 +3767,24 @@ static void valueFlowLifetime(TokenList *tokenlist, SymbolDatabase*, ErrorLogger
|
||||||
}
|
}
|
||||||
// address of
|
// address of
|
||||||
else if (tok->isUnaryOp("&")) {
|
else if (tok->isUnaryOp("&")) {
|
||||||
ErrorPath errorPath;
|
for (const LifetimeToken& lt : getLifetimeTokens(tok->astOperand1())) {
|
||||||
const Token *lifeTok = getLifetimeToken(tok->astOperand1(), errorPath);
|
if (!settings->inconclusive && lt.inconclusive)
|
||||||
if (!lifeTok)
|
continue;
|
||||||
continue;
|
ErrorPath errorPath = lt.errorPath;
|
||||||
|
errorPath.emplace_back(tok, "Address of variable taken here.");
|
||||||
|
|
||||||
errorPath.emplace_back(tok, "Address of variable taken here.");
|
ValueFlow::Value value;
|
||||||
|
value.valueType = ValueFlow::Value::ValueType::LIFETIME;
|
||||||
|
value.lifetimeScope = ValueFlow::Value::LifetimeScope::Local;
|
||||||
|
value.tokvalue = lt.token;
|
||||||
|
value.errorPath = std::move(errorPath);
|
||||||
|
if (astIsPointer(lt.token) || !Token::Match(lt.token->astParent(), ".|["))
|
||||||
|
value.lifetimeKind = ValueFlow::Value::LifetimeKind::Address;
|
||||||
|
value.setInconclusive(lt.inconclusive);
|
||||||
|
setTokenValue(tok, value, tokenlist->getSettings());
|
||||||
|
|
||||||
ValueFlow::Value value;
|
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
||||||
value.valueType = ValueFlow::Value::ValueType::LIFETIME;
|
}
|
||||||
value.lifetimeScope = ValueFlow::Value::LifetimeScope::Local;
|
|
||||||
value.tokvalue = lifeTok;
|
|
||||||
value.errorPath = errorPath;
|
|
||||||
if (astIsPointer(lifeTok) || !Token::Match(lifeTok->astParent(), ".|["))
|
|
||||||
value.lifetimeKind = ValueFlow::Value::LifetimeKind::Address;
|
|
||||||
setTokenValue(tok, value, tokenlist->getSettings());
|
|
||||||
|
|
||||||
valueFlowForwardLifetime(tok, tokenlist, errorLogger, settings);
|
|
||||||
}
|
}
|
||||||
// container lifetimes
|
// container lifetimes
|
||||||
else if (astIsContainer(tok)) {
|
else if (astIsContainer(tok)) {
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class ErrorLogger;
|
class ErrorLogger;
|
||||||
class Settings;
|
class Settings;
|
||||||
|
@ -259,6 +260,37 @@ namespace ValueFlow {
|
||||||
std::string eitherTheConditionIsRedundant(const Token *condition);
|
std::string eitherTheConditionIsRedundant(const Token *condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LifetimeToken {
|
||||||
|
const Token* token;
|
||||||
|
bool addressOf;
|
||||||
|
ValueFlow::Value::ErrorPath errorPath;
|
||||||
|
bool inconclusive;
|
||||||
|
|
||||||
|
LifetimeToken() : token(nullptr), addressOf(false), errorPath(), inconclusive(false) {}
|
||||||
|
|
||||||
|
LifetimeToken(const Token* token, ValueFlow::Value::ErrorPath errorPath)
|
||||||
|
: token(token), addressOf(false), errorPath(std::move(errorPath)), inconclusive(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
LifetimeToken(const Token* token, bool addressOf, ValueFlow::Value::ErrorPath errorPath)
|
||||||
|
: token(token), addressOf(addressOf), errorPath(std::move(errorPath)), inconclusive(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
static std::vector<LifetimeToken> setAddressOf(std::vector<LifetimeToken> v, bool b) {
|
||||||
|
for (LifetimeToken& x : v)
|
||||||
|
x.addressOf = b;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<LifetimeToken> setInconclusive(std::vector<LifetimeToken> v, bool b) {
|
||||||
|
for (LifetimeToken& x : v)
|
||||||
|
x.inconclusive = b;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<LifetimeToken> getLifetimeTokens(const Token* tok, ValueFlow::Value::ErrorPath errorPath = ValueFlow::Value::ErrorPath{}, int depth = 20);
|
||||||
|
|
||||||
const Variable* getLifetimeVariable(const Token* tok, ValueFlow::Value::ErrorPath& errorPath, bool* addressOf = nullptr);
|
const Variable* getLifetimeVariable(const Token* tok, ValueFlow::Value::ErrorPath& errorPath, bool* addressOf = nullptr);
|
||||||
|
|
||||||
bool isLifetimeBorrowed(const Token *tok, const Settings *settings);
|
bool isLifetimeBorrowed(const Token *tok, const Settings *settings);
|
||||||
|
|
|
@ -59,13 +59,6 @@ if (BUILD_TESTS)
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Set cost of the more expensive tests to help improve parallel scheduling
|
|
||||||
# of tests
|
|
||||||
fixture_cost(TestIO 20)
|
|
||||||
fixture_cost(TestThreadExecutor 5)
|
|
||||||
fixture_cost(TestLeakAutoVar 4)
|
|
||||||
fixture_cost(TestTokenizer 4)
|
|
||||||
|
|
||||||
function(add_cfg CFG_TEST)
|
function(add_cfg CFG_TEST)
|
||||||
set(options INCONCLUSIVE)
|
set(options INCONCLUSIVE)
|
||||||
set(oneValueArgs PLATFORM NAME)
|
set(oneValueArgs PLATFORM NAME)
|
||||||
|
@ -118,10 +111,18 @@ if (BUILD_TESTS)
|
||||||
add_cfg(python.c)
|
add_cfg(python.c)
|
||||||
add_cfg(qt.cpp)
|
add_cfg(qt.cpp)
|
||||||
add_cfg(sqlite3.c INCONCLUSIVE)
|
add_cfg(sqlite3.c INCONCLUSIVE)
|
||||||
add_cfg(std.c)
|
add_cfg(std.c INCONCLUSIVE)
|
||||||
add_cfg(std.cpp)
|
add_cfg(std.cpp INCONCLUSIVE)
|
||||||
add_cfg(windows.cpp INCONCLUSIVE NAME windows32A PLATFORM win32A)
|
add_cfg(windows.cpp INCONCLUSIVE NAME windows32A PLATFORM win32A)
|
||||||
add_cfg(windows.cpp INCONCLUSIVE NAME windows32W PLATFORM win32W)
|
add_cfg(windows.cpp INCONCLUSIVE NAME windows32W PLATFORM win32W)
|
||||||
add_cfg(windows.cpp INCONCLUSIVE NAME windows64 PLATFORM win64)
|
add_cfg(windows.cpp INCONCLUSIVE NAME windows64 PLATFORM win64)
|
||||||
add_cfg(wxwidgets.cpp INCONCLUSIVE)
|
add_cfg(wxwidgets.cpp INCONCLUSIVE)
|
||||||
|
|
||||||
|
# Set cost of the more expensive tests to help improve parallel scheduling
|
||||||
|
# of tests
|
||||||
|
fixture_cost(TestIO 20)
|
||||||
|
fixture_cost(cfg-std_c 8)
|
||||||
|
fixture_cost(TestThreadExecutor 5)
|
||||||
|
fixture_cost(TestLeakAutoVar 4)
|
||||||
|
fixture_cost(TestTokenizer 4)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -63,9 +63,9 @@ ${CPPCHECK} ${CPPCHECK_OPT} --library=bsd ${DIR}bsd.c
|
||||||
|
|
||||||
# std.c
|
# std.c
|
||||||
${CC} ${CC_OPT} ${DIR}std.c
|
${CC} ${CC_OPT} ${DIR}std.c
|
||||||
${CPPCHECK} ${CPPCHECK_OPT} ${DIR}std.c
|
${CPPCHECK} ${CPPCHECK_OPT} --inconclusive ${DIR}std.c
|
||||||
${CXX} ${CXX_OPT} ${DIR}std.cpp
|
${CXX} ${CXX_OPT} ${DIR}std.cpp
|
||||||
${CPPCHECK} ${CPPCHECK_OPT} ${DIR}std.cpp
|
${CPPCHECK} ${CPPCHECK_OPT} --inconclusive ${DIR}std.cpp
|
||||||
|
|
||||||
# windows.cpp
|
# windows.cpp
|
||||||
# Syntax check via g++ does not work because it can not find a valid windows.h
|
# Syntax check via g++ does not work because it can not find a valid windows.h
|
||||||
|
|
|
@ -47,6 +47,7 @@ void bufferAccessOutOfBounds(void)
|
||||||
// cppcheck-suppress bufferAccessOutOfBounds
|
// cppcheck-suppress bufferAccessOutOfBounds
|
||||||
strcpy_s(a, 10, "abcdefghij");
|
strcpy_s(a, 10, "abcdefghij");
|
||||||
// TODO cppcheck-suppress redundantCopy
|
// TODO cppcheck-suppress redundantCopy
|
||||||
|
// cppcheck-suppress terminateStrncpy
|
||||||
strncpy(a,"abcde",5);
|
strncpy(a,"abcde",5);
|
||||||
// cppcheck-suppress bufferAccessOutOfBounds
|
// cppcheck-suppress bufferAccessOutOfBounds
|
||||||
// TODO cppcheck-suppress redundantCopy
|
// TODO cppcheck-suppress redundantCopy
|
||||||
|
|
|
@ -78,6 +78,7 @@ void bufferAccessOutOfBounds(void)
|
||||||
// TODO cppcheck-suppress redundantCopy
|
// TODO cppcheck-suppress redundantCopy
|
||||||
std::strcpy(a, "abcde");
|
std::strcpy(a, "abcde");
|
||||||
// TODO cppcheck-suppress redundantCopy
|
// TODO cppcheck-suppress redundantCopy
|
||||||
|
// cppcheck-suppress terminateStrncpy
|
||||||
std::strncpy(a,"abcde",5);
|
std::strncpy(a,"abcde",5);
|
||||||
// cppcheck-suppress bufferAccessOutOfBounds
|
// cppcheck-suppress bufferAccessOutOfBounds
|
||||||
// TODO cppcheck-suppress redundantCopy
|
// TODO cppcheck-suppress redundantCopy
|
||||||
|
|
|
@ -1228,6 +1228,21 @@ private:
|
||||||
"}\n");
|
"}\n");
|
||||||
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4]: (error) Reference to local variable returned.\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4]: (error) Reference to local variable returned.\n", errout.str());
|
||||||
|
|
||||||
|
check("template <class T, class K, class V>\n"
|
||||||
|
"const V& get_default(const T& t, const K& k, const V& v) {\n"
|
||||||
|
" auto it = t.find(k);\n"
|
||||||
|
" if (it == t.end()) return v;\n"
|
||||||
|
" return it->second;\n"
|
||||||
|
"}\n"
|
||||||
|
"const int& bar(const std::unordered_map<int, int>& m, int k) {\n"
|
||||||
|
" auto x = 0;\n"
|
||||||
|
" return get_default(m, k, x);\n"
|
||||||
|
"}\n",
|
||||||
|
true);
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:2] -> [test.cpp:4] -> [test.cpp:9] -> [test.cpp:9]: (error, inconclusive) Reference to local variable returned.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
check("struct A { int foo; };\n"
|
check("struct A { int foo; };\n"
|
||||||
"int& f(std::vector<A>& v) {\n"
|
"int& f(std::vector<A>& v) {\n"
|
||||||
" auto it = v.begin();\n"
|
" auto it = v.begin();\n"
|
||||||
|
@ -1666,6 +1681,21 @@ private:
|
||||||
"[test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning pointer to local variable 'ba' that will be invalid when returning.\n",
|
"[test.cpp:3] -> [test.cpp:2] -> [test.cpp:3]: (error) Returning pointer to local variable 'ba' that will be invalid when returning.\n",
|
||||||
errout.str());
|
errout.str());
|
||||||
|
|
||||||
|
check("template <class T, class K, class V>\n"
|
||||||
|
"const V* get_default(const T& t, const K& k, const V* v) {\n"
|
||||||
|
" auto it = t.find(k);\n"
|
||||||
|
" if (it == t.end()) return v;\n"
|
||||||
|
" return &it->second;\n"
|
||||||
|
"}\n"
|
||||||
|
"const int* bar(const std::unordered_map<int, int>& m, int k) {\n"
|
||||||
|
" auto x = 0;\n"
|
||||||
|
" return get_default(m, k, &x);\n"
|
||||||
|
"}\n",
|
||||||
|
true);
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[test.cpp:9] -> [test.cpp:9] -> [test.cpp:8] -> [test.cpp:9]: (error, inconclusive) Returning pointer to local variable 'x' that will be invalid when returning.\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
check("struct A {\n"
|
check("struct A {\n"
|
||||||
" std::vector<std::string> v;\n"
|
" std::vector<std::string> v;\n"
|
||||||
" void f() {\n"
|
" void f() {\n"
|
||||||
|
|
|
@ -4454,7 +4454,7 @@ private:
|
||||||
" sprintf_s(lineBuffer, 100, format1, \"type\", \"sum\", \"avg\", \"min\", i, 0);\n"
|
" sprintf_s(lineBuffer, 100, format1, \"type\", \"sum\", \"avg\", \"min\", i, 0);\n"
|
||||||
" sprintf_s(lineBuffer, 100, format2, \"type\", \"sum\", \"avg\", \"min\", i, 0);\n"
|
" sprintf_s(lineBuffer, 100, format2, \"type\", \"sum\", \"avg\", \"min\", i, 0);\n"
|
||||||
" sprintf_s(lineBuffer, 100, format3, \"type\", \"sum\", \"avg\", \"min\", i, 0);\n"
|
" sprintf_s(lineBuffer, 100, format3, \"type\", \"sum\", \"avg\", \"min\", i, 0);\n"
|
||||||
"}\n", false, false, Settings::Win32A);
|
"}\n", true, false, Settings::Win32A);
|
||||||
ASSERT_EQUALS("[test.cpp:6]: (warning) %s in format string (no. 5) requires 'char *' but the argument type is 'signed int'.\n"
|
ASSERT_EQUALS("[test.cpp:6]: (warning) %s in format string (no. 5) requires 'char *' but the argument type is 'signed int'.\n"
|
||||||
"[test.cpp:6]: (warning) sprintf_s format string requires 5 parameters but 6 are given.\n"
|
"[test.cpp:6]: (warning) sprintf_s format string requires 5 parameters but 6 are given.\n"
|
||||||
"[test.cpp:7]: (warning) %s in format string (no. 5) requires 'char *' but the argument type is 'signed int'.\n"
|
"[test.cpp:7]: (warning) %s in format string (no. 5) requires 'char *' but the argument type is 'signed int'.\n"
|
||||||
|
|
|
@ -1188,8 +1188,8 @@ private:
|
||||||
" iter = ints.begin() + 2;\n"
|
" iter = ints.begin() + 2;\n"
|
||||||
" ints.erase(iter);\n"
|
" ints.erase(iter);\n"
|
||||||
" std::cout << (*iter) << std::endl;\n"
|
" std::cout << (*iter) << std::endl;\n"
|
||||||
"}");
|
"}", true);
|
||||||
ASSERT_EQUALS("[test.cpp:5] -> [test.cpp:6] -> [test.cpp:3] -> [test.cpp:7]: (error) Using iterator to local container 'ints' that may be invalid.\n", errout.str());
|
TODO_ASSERT_EQUALS("[test.cpp:5] -> [test.cpp:6] -> [test.cpp:3] -> [test.cpp:7]: (error) Using iterator to local container 'ints' that may be invalid.\n", "[test.cpp:5] -> [test.cpp:6] -> [test.cpp:3] -> [test.cpp:7]: (error, inconclusive) Using iterator to local container 'ints' that may be invalid.\n", errout.str());
|
||||||
|
|
||||||
// #6554 "False positive eraseDereference - erase in while() loop"
|
// #6554 "False positive eraseDereference - erase in while() loop"
|
||||||
check("typedef std::map<Packet> packetMap;\n"
|
check("typedef std::map<Packet> packetMap;\n"
|
||||||
|
@ -1259,8 +1259,8 @@ private:
|
||||||
" auto iter = ints.begin() + 2;\n"
|
" auto iter = ints.begin() + 2;\n"
|
||||||
" ints.erase(iter);\n"
|
" ints.erase(iter);\n"
|
||||||
" std::cout << (*iter) << std::endl;\n"
|
" std::cout << (*iter) << std::endl;\n"
|
||||||
"}");
|
"}", true);
|
||||||
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:5] -> [test.cpp:3] -> [test.cpp:6]: (error) Using iterator to local container 'ints' that may be invalid.\n", errout.str());
|
TODO_ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:5] -> [test.cpp:3] -> [test.cpp:6]: (error) Using iterator to local container 'ints' that may be invalid.\n", "[test.cpp:4] -> [test.cpp:5] -> [test.cpp:3] -> [test.cpp:6]: (error, inconclusive) Using iterator to local container 'ints' that may be invalid.\n", errout.str());
|
||||||
|
|
||||||
check("void f() {\n"
|
check("void f() {\n"
|
||||||
" auto x = *myList.begin();\n"
|
" auto x = *myList.begin();\n"
|
||||||
|
@ -1766,8 +1766,8 @@ private:
|
||||||
" iter = ints.begin() + 2;\n"
|
" iter = ints.begin() + 2;\n"
|
||||||
" ints.erase(iter);\n"
|
" ints.erase(iter);\n"
|
||||||
" ints.erase(iter);\n"
|
" ints.erase(iter);\n"
|
||||||
"}");
|
"}", true);
|
||||||
ASSERT_EQUALS("[test.cpp:1] -> [test.cpp:4] -> [test.cpp:5] -> [test.cpp:1] -> [test.cpp:6]: (error) Using iterator to local container 'ints' that may be invalid.\n", errout.str());
|
TODO_ASSERT_EQUALS("[test.cpp:1] -> [test.cpp:4] -> [test.cpp:5] -> [test.cpp:1] -> [test.cpp:6]: (error) Using iterator to local container 'ints' that may be invalid.\n", "[test.cpp:1] -> [test.cpp:4] -> [test.cpp:5] -> [test.cpp:1] -> [test.cpp:6]: (error, inconclusive) Using iterator to local container 'ints' that may be invalid.\n", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void eraseByValue() {
|
void eraseByValue() {
|
||||||
|
@ -2053,7 +2053,7 @@ private:
|
||||||
" *it;\n"
|
" *it;\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"}");
|
"}");
|
||||||
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:6] -> [test.cpp:3] -> [test.cpp:9]: (error) Using iterator to local container 'vec' that may be invalid.\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:4] -> [test.cpp:6] -> [test.cpp:3] -> [test.cpp:4]: (error) Using iterator to local container 'vec' that may be invalid.\n", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushback13() {
|
void pushback13() {
|
||||||
|
@ -2089,8 +2089,8 @@ private:
|
||||||
" std::vector<int>::iterator iter = ints.begin();\n"
|
" std::vector<int>::iterator iter = ints.begin();\n"
|
||||||
" ints.insert(iter, 1);\n"
|
" ints.insert(iter, 1);\n"
|
||||||
" ints.insert(iter, 2);\n"
|
" ints.insert(iter, 2);\n"
|
||||||
"}");
|
"}", true);
|
||||||
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:5] -> [test.cpp:3] -> [test.cpp:6]: (error) Using iterator to local container 'ints' that may be invalid.\n", errout.str());
|
TODO_ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:5] -> [test.cpp:3] -> [test.cpp:6]: (error) Using iterator to local container 'ints' that may be invalid.\n", "[test.cpp:4] -> [test.cpp:5] -> [test.cpp:3] -> [test.cpp:6]: (error, inconclusive) Using iterator to local container 'ints' that may be invalid.\n", errout.str());
|
||||||
|
|
||||||
check("void* f(const std::vector<Bar>& bars) {\n"
|
check("void* f(const std::vector<Bar>& bars) {\n"
|
||||||
" std::vector<Bar>::iterator i = bars.begin();\n"
|
" std::vector<Bar>::iterator i = bars.begin();\n"
|
||||||
|
|
Loading…
Reference in New Issue