Improve propogation of lifetimes of function arguments
This will now warn for cases like this: ```cpp int* f(int * x) { return x; } int * g(int x) { return f(&x); } ````
This commit is contained in:
parent
fbd7b5180b
commit
b049fd9303
|
@ -3034,14 +3034,43 @@ struct LifetimeStore {
|
||||||
ValueFlow::Value::LifetimeKind type;
|
ValueFlow::Value::LifetimeKind type;
|
||||||
ErrorPath errorPath;
|
ErrorPath errorPath;
|
||||||
|
|
||||||
|
LifetimeStore()
|
||||||
|
: argtok(nullptr), message(), type(), errorPath()
|
||||||
|
{}
|
||||||
|
|
||||||
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()
|
: argtok(argtok), message(message), type(type), errorPath()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
static LifetimeStore fromFunctionArg(const Function * f, Token *tok, const Variable *var, TokenList *tokenlist, ErrorLogger *errorLogger) {
|
||||||
|
if (!var)
|
||||||
|
return LifetimeStore{};
|
||||||
|
if (!var->isArgument())
|
||||||
|
return LifetimeStore{};
|
||||||
|
int n = getArgumentPos(var, f);
|
||||||
|
if (n < 0)
|
||||||
|
return LifetimeStore{};
|
||||||
|
std::vector<const Token *> args = getArguments(tok);
|
||||||
|
if (n >= args.size()) {
|
||||||
|
if (tokenlist->getSettings()->debugwarnings)
|
||||||
|
bailout(tokenlist,
|
||||||
|
errorLogger,
|
||||||
|
tok,
|
||||||
|
"Argument mismatch: Function '" + tok->str() + "' returning lifetime from argument index " +
|
||||||
|
std::to_string(n) + " but only " + std::to_string(args.size()) +
|
||||||
|
" arguments are available.");
|
||||||
|
return LifetimeStore{};
|
||||||
|
}
|
||||||
|
const Token *argtok2 = args[n];
|
||||||
|
return LifetimeStore{argtok2, "Passed to '" + tok->str() + "'.", ValueFlow::Value::LifetimeKind::Object};
|
||||||
|
}
|
||||||
|
|
||||||
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 (!argtok)
|
||||||
|
return;
|
||||||
ErrorPath er = errorPath;
|
ErrorPath er = errorPath;
|
||||||
const Token *lifeTok = getLifetimeToken(argtok, er);
|
const Token *lifeTok = getLifetimeToken(argtok, er);
|
||||||
if (!lifeTok)
|
if (!lifeTok)
|
||||||
|
@ -3071,6 +3100,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 (!argtok)
|
||||||
|
return;
|
||||||
if (argtok->values().empty()) {
|
if (argtok->values().empty()) {
|
||||||
ErrorPath er;
|
ErrorPath er;
|
||||||
er.emplace_back(argtok, message);
|
er.emplace_back(argtok, message);
|
||||||
|
@ -3124,6 +3155,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 (!argtok)
|
||||||
|
return;
|
||||||
for (const ValueFlow::Value &v : argtok->values()) {
|
for (const ValueFlow::Value &v : argtok->values()) {
|
||||||
if (!v.isLifetimeValue())
|
if (!v.isLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
|
@ -3184,32 +3217,20 @@ static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLog
|
||||||
const Token *returnTok = findSimpleReturn(f);
|
const Token *returnTok = findSimpleReturn(f);
|
||||||
if (!returnTok)
|
if (!returnTok)
|
||||||
return;
|
return;
|
||||||
|
const Variable *returnVar = returnTok->variable();
|
||||||
|
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()) {
|
for (const ValueFlow::Value &v : returnTok->values()) {
|
||||||
if (!v.isLifetimeValue())
|
if (!v.isLifetimeValue())
|
||||||
continue;
|
continue;
|
||||||
if (!v.tokvalue)
|
if (!v.tokvalue)
|
||||||
continue;
|
continue;
|
||||||
const Variable *var = v.tokvalue->variable();
|
const Variable *var = v.tokvalue->variable();
|
||||||
if (!var)
|
LifetimeStore ls = LifetimeStore::fromFunctionArg(f, tok, var, tokenlist, errorLogger);
|
||||||
|
if (!ls.argtok)
|
||||||
continue;
|
continue;
|
||||||
if (!var->isArgument())
|
|
||||||
continue;
|
|
||||||
int n = getArgumentPos(var, f);
|
|
||||||
if (n < 0)
|
|
||||||
continue;
|
|
||||||
std::vector<const Token *> args = getArguments(tok);
|
|
||||||
if (n >= args.size()) {
|
|
||||||
if (tokenlist->getSettings()->debugwarnings)
|
|
||||||
bailout(tokenlist,
|
|
||||||
errorLogger,
|
|
||||||
tok,
|
|
||||||
"Argument mismatch: Function '" + tok->str() + "' returning lifetime from argument index " +
|
|
||||||
std::to_string(n) + " but only " + std::to_string(args.size()) +
|
|
||||||
" arguments are available.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const Token *argtok = args[n];
|
|
||||||
LifetimeStore ls{argtok, "Passed to '" + tok->str() + "'.", ValueFlow::Value::LifetimeKind::Object};
|
|
||||||
ls.errorPath = v.errorPath;
|
ls.errorPath = v.errorPath;
|
||||||
ls.errorPath.emplace_front(returnTok, "Return " + lifetimeType(returnTok, &v) + ".");
|
ls.errorPath.emplace_front(returnTok, "Return " + lifetimeType(returnTok, &v) + ".");
|
||||||
if (var->isReference() || var->isRValueReference()) {
|
if (var->isReference() || var->isRValueReference()) {
|
||||||
|
|
|
@ -1161,6 +1161,23 @@ private:
|
||||||
"[test.cpp:1] -> [test.cpp:2] -> [test.cpp:6] -> [test.cpp:6] -> [test.cpp:5] -> [test.cpp:6]: (error) Returning pointer to local variable 'x' that will be invalid when returning.\n",
|
"[test.cpp:1] -> [test.cpp:2] -> [test.cpp:6] -> [test.cpp:6] -> [test.cpp:5] -> [test.cpp:6]: (error) Returning pointer to local variable 'x' that will be invalid when returning.\n",
|
||||||
errout.str());
|
errout.str());
|
||||||
|
|
||||||
|
check("int* f(int * x) {\n"
|
||||||
|
" return x;\n"
|
||||||
|
"}\n"
|
||||||
|
"int * g(int x) {\n"
|
||||||
|
" return f(&x);\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("[test.cpp:5] -> [test.cpp:5] -> [test.cpp:4] -> [test.cpp:5]: (error) Returning pointer to local variable 'x' that will be invalid when returning.\n", errout.str());
|
||||||
|
|
||||||
|
check("int* f(int * x) {\n"
|
||||||
|
" x = nullptr;\n"
|
||||||
|
" return x;\n"
|
||||||
|
"}\n"
|
||||||
|
"int * g(int x) {\n"
|
||||||
|
" return f(&x);\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
check("int f(int& a) {\n"
|
check("int f(int& a) {\n"
|
||||||
" return a;\n"
|
" return a;\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
|
|
Loading…
Reference in New Issue