Fix issue 9077: False positive: Returning pointer to local variable (#1821)
* Avoid implicit conversion for lifetimes * Fix issue 9077 * Add more tests * Rename function * Fix implicit conversion with containers * Format * Fix crash
This commit is contained in:
parent
5b07901253
commit
8c03be3212
|
@ -590,7 +590,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token
|
||||||
return;
|
return;
|
||||||
bool returnRef = Function::returnsReference(scope->function);
|
bool returnRef = Function::returnsReference(scope->function);
|
||||||
for (const Token *tok = start; tok && tok != end; tok = tok->next()) {
|
for (const Token *tok = start; tok && tok != end; tok = tok->next()) {
|
||||||
// Return reference form function
|
// Return reference from function
|
||||||
if (returnRef && Token::simpleMatch(tok->astParent(), "return")) {
|
if (returnRef && Token::simpleMatch(tok->astParent(), "return")) {
|
||||||
ErrorPath errorPath;
|
ErrorPath errorPath;
|
||||||
const Variable *var = getLifetimeVariable(tok, errorPath);
|
const Variable *var = getLifetimeVariable(tok, errorPath);
|
||||||
|
@ -619,8 +619,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token
|
||||||
if (Token::Match(tok->astParent(), "return|throw")) {
|
if (Token::Match(tok->astParent(), "return|throw")) {
|
||||||
if (getPointerDepth(tok) < getPointerDepth(val.tokvalue))
|
if (getPointerDepth(tok) < getPointerDepth(val.tokvalue))
|
||||||
continue;
|
continue;
|
||||||
if (tok->astParent()->str() == "return" && !Token::simpleMatch(tok, "{") && !astIsContainer(tok) &&
|
if (!isLifetimeBorrowed(tok, mSettings))
|
||||||
astIsContainer(tok->astParent()))
|
|
||||||
continue;
|
continue;
|
||||||
if (isInScope(val.tokvalue->variable()->nameToken(), scope)) {
|
if (isInScope(val.tokvalue->variable()->nameToken(), scope)) {
|
||||||
errorReturnDanglingLifetime(tok, &val);
|
errorReturnDanglingLifetime(tok, &val);
|
||||||
|
|
|
@ -2829,6 +2829,83 @@ static const Variable *getLHSVariable(const Token *tok)
|
||||||
return getLHSVariableRecursive(tok->astOperand1());
|
return getLHSVariableRecursive(tok->astOperand1());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isLifetimeOwned(const ValueType *vt, const ValueType *vtParent)
|
||||||
|
{
|
||||||
|
if (!vtParent)
|
||||||
|
return false;
|
||||||
|
if (!vt) {
|
||||||
|
if (vtParent->type == ValueType::CONTAINER)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (vt->type != ValueType::UNKNOWN_TYPE && vtParent->type != ValueType::UNKNOWN_TYPE) {
|
||||||
|
if (vt->pointer != vtParent->pointer)
|
||||||
|
return true;
|
||||||
|
if (vt->type != vtParent->type) {
|
||||||
|
if (vtParent->type == ValueType::RECORD)
|
||||||
|
return true;
|
||||||
|
if (vtParent->type == ValueType::CONTAINER)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isLifetimeBorrowed(const ValueType *vt, const ValueType *vtParent)
|
||||||
|
{
|
||||||
|
if (!vtParent)
|
||||||
|
return false;
|
||||||
|
if (!vt)
|
||||||
|
return false;
|
||||||
|
if (vt->type != ValueType::UNKNOWN_TYPE && vtParent->type != ValueType::UNKNOWN_TYPE) {
|
||||||
|
if (vtParent->pointer > vt->pointer)
|
||||||
|
return true;
|
||||||
|
if (vtParent->pointer < vt->pointer && vtParent->isIntegral())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLifetimeBorrowed(const Token *tok, const Settings *settings)
|
||||||
|
{
|
||||||
|
if (!tok)
|
||||||
|
return true;
|
||||||
|
if (Token::simpleMatch(tok, ","))
|
||||||
|
return true;
|
||||||
|
if (!tok->astParent())
|
||||||
|
return true;
|
||||||
|
if (!Token::Match(tok->astParent()->previous(), "%name% (") && !Token::simpleMatch(tok->astParent(), ",")) {
|
||||||
|
if (!Token::simpleMatch(tok, "{")) {
|
||||||
|
const ValueType *vt = tok->valueType();
|
||||||
|
const ValueType *vtParent = tok->astParent()->valueType();
|
||||||
|
if (isLifetimeBorrowed(vt, vtParent))
|
||||||
|
return true;
|
||||||
|
if (isLifetimeOwned(vt, vtParent))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Type *t = Token::typeOf(tok);
|
||||||
|
const Type *parentT = Token::typeOf(tok->astParent());
|
||||||
|
if (t && parentT && t->classDef && parentT->classDef && t->classDef != parentT->classDef) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (Token::Match(tok->astParent()->tokAt(-3), "%var% . push_back|push_front|insert|push (") &&
|
||||||
|
astIsContainer(tok->astParent()->tokAt(-3))) {
|
||||||
|
const ValueType *vt = tok->valueType();
|
||||||
|
const ValueType *vtCont = tok->astParent()->tokAt(-3)->valueType();
|
||||||
|
if (!vtCont->containerTypeToken)
|
||||||
|
return true;
|
||||||
|
ValueType vtParent = ValueType::parseDecl(vtCont->containerTypeToken, settings);
|
||||||
|
if (isLifetimeBorrowed(vt, &vtParent))
|
||||||
|
return true;
|
||||||
|
if (isLifetimeOwned(vt, &vtParent))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings);
|
static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLogger *errorLogger, const Settings *settings);
|
||||||
|
|
||||||
static void valueFlowLifetimeConstructor(Token *tok,
|
static void valueFlowLifetimeConstructor(Token *tok,
|
||||||
|
@ -2855,8 +2932,7 @@ static void valueFlowForwardLifetime(Token * tok, TokenList *tokenlist, ErrorLog
|
||||||
if (!parent->astOperand2() || parent->astOperand2()->values().empty())
|
if (!parent->astOperand2() || parent->astOperand2()->values().empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (astIsPointer(parent->astOperand2()) && !var->isPointer() &&
|
if (!isLifetimeBorrowed(parent->astOperand2(), settings))
|
||||||
!(var->valueType() && var->valueType()->isIntegral()))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::list<ValueFlow::Value> values = parent->astOperand2()->values();
|
std::list<ValueFlow::Value> values = parent->astOperand2()->values();
|
||||||
|
@ -3075,9 +3151,6 @@ static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLog
|
||||||
}
|
}
|
||||||
} else if (Token::Match(tok->tokAt(-2), "%var% . push_back|push_front|insert|push|assign") &&
|
} else if (Token::Match(tok->tokAt(-2), "%var% . push_back|push_front|insert|push|assign") &&
|
||||||
astIsContainer(tok->tokAt(-2))) {
|
astIsContainer(tok->tokAt(-2))) {
|
||||||
const Token *containerTypeTok = tok->tokAt(-2)->valueType()->containerTypeToken;
|
|
||||||
const Token *endTypeTok = endTemplateArgument(containerTypeTok);
|
|
||||||
const bool isPointer = endTypeTok && Token::simpleMatch(endTypeTok->previous(), "*");
|
|
||||||
Token *vartok = tok->tokAt(-2);
|
Token *vartok = tok->tokAt(-2);
|
||||||
std::vector<const Token *> args = getArguments(tok);
|
std::vector<const Token *> args = getArguments(tok);
|
||||||
std::size_t n = args.size();
|
std::size_t n = args.size();
|
||||||
|
@ -3086,7 +3159,7 @@ static void valueFlowLifetimeFunction(Token *tok, TokenList *tokenlist, ErrorLog
|
||||||
(astIsPointer(args[n - 2]) && astIsPointer(args[n - 1]))))) {
|
(astIsPointer(args[n - 2]) && astIsPointer(args[n - 1]))))) {
|
||||||
LifetimeStore{args.back(), "Added to container '" + vartok->str() + "'.", ValueFlow::Value::Object} .byDerefCopy(
|
LifetimeStore{args.back(), "Added to container '" + vartok->str() + "'.", ValueFlow::Value::Object} .byDerefCopy(
|
||||||
vartok, tokenlist, errorLogger, settings);
|
vartok, tokenlist, errorLogger, settings);
|
||||||
} else if (!args.empty() && astIsPointer(args.back()) == isPointer) {
|
} else if (!args.empty() && isLifetimeBorrowed(args.back(), settings)) {
|
||||||
LifetimeStore{args.back(), "Added to container '" + vartok->str() + "'.", ValueFlow::Value::Object} .byVal(
|
LifetimeStore{args.back(), "Added to container '" + vartok->str() + "'.", ValueFlow::Value::Object} .byVal(
|
||||||
vartok, tokenlist, errorLogger, settings);
|
vartok, tokenlist, errorLogger, settings);
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,6 +238,8 @@ namespace ValueFlow {
|
||||||
|
|
||||||
const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPath &errorPath);
|
const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPath &errorPath);
|
||||||
|
|
||||||
|
bool isLifetimeBorrowed(const Token *tok, const Settings *settings);
|
||||||
|
|
||||||
std::string lifetimeType(const Token *tok, const ValueFlow::Value *val);
|
std::string lifetimeType(const Token *tok, const ValueFlow::Value *val);
|
||||||
|
|
||||||
#endif // valueflowH
|
#endif // valueflowH
|
||||||
|
|
|
@ -124,6 +124,7 @@ private:
|
||||||
TEST_CASE(danglingLifetimeFunction);
|
TEST_CASE(danglingLifetimeFunction);
|
||||||
TEST_CASE(danglingLifetimeAggegrateConstructor);
|
TEST_CASE(danglingLifetimeAggegrateConstructor);
|
||||||
TEST_CASE(danglingLifetimeInitList);
|
TEST_CASE(danglingLifetimeInitList);
|
||||||
|
TEST_CASE(danglingLifetimeImplicitConversion);
|
||||||
TEST_CASE(invalidLifetime);
|
TEST_CASE(invalidLifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1618,6 +1619,14 @@ private:
|
||||||
"[test.cpp:2] -> [test.cpp:1] -> [test.cpp:3]: (error) Returning iterator to local container 'v' that will be invalid when returning.\n",
|
"[test.cpp:2] -> [test.cpp:1] -> [test.cpp:3]: (error) Returning iterator to local container 'v' that will be invalid when returning.\n",
|
||||||
errout.str());
|
errout.str());
|
||||||
|
|
||||||
|
check("const char * f() {\n"
|
||||||
|
" std::string ba(\"hello\");\n"
|
||||||
|
" return ba.c_str();\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS(
|
||||||
|
"[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());
|
||||||
|
|
||||||
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"
|
||||||
|
@ -2010,6 +2019,33 @@ private:
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void danglingLifetimeImplicitConversion()
|
||||||
|
{
|
||||||
|
check("struct A { A(const char *a); };\n"
|
||||||
|
"A f() {\n"
|
||||||
|
" std::string ba(\"hello\");\n"
|
||||||
|
" return ba.c_str();\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("struct A { A(const char *a); };\n"
|
||||||
|
"A f() {\n"
|
||||||
|
" std::string ba(\"hello\");\n"
|
||||||
|
" A bp = ba.c_str();\n"
|
||||||
|
" return bp;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("struct A { A(const char *a); };\n"
|
||||||
|
"std::vector<A> f() {\n"
|
||||||
|
" std::string ba(\"hello\");\n"
|
||||||
|
" std::vector<A> v;\n"
|
||||||
|
" v.push_back(ba.c_str());\n"
|
||||||
|
" return v;\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