Extend lifetime checking for references

This will use the lifetime checker for dangling references. It will find these cases for indirectly assigned reference:

```cpp
int &foo()
{
    int s = 0;
    int& x = s;
    return x;
}
```

This will also fix issue 510 as well:

```cpp
int &f( int k )
{
    static int &r = k;
    return r;
}
```
This commit is contained in:
Paul Fultz II 2019-01-23 07:29:16 +01:00 committed by Daniel Marjamäki
parent db413d254a
commit 3975913637
7 changed files with 93 additions and 45 deletions

View File

@ -527,31 +527,8 @@ void CheckAutoVariables::returnReference()
if (tok2->str() != "return")
continue;
// return..
if (Token::Match(tok2, "return %var% ;")) {
// is the returned variable a local variable?
if (isAutoVar(tok2->next())) {
const Variable *var1 = tok2->next()->variable();
// If reference variable is used, check what it references
if (Token::Match(var1->nameToken(), "%var% [=(]")) {
const Token *tok3 = var1->nameToken()->tokAt(2);
if (!Token::Match(tok3, "%var% [);.]"))
continue;
// Only report error if variable that is referenced is
// a auto variable
if (!isAutoVar(tok3))
continue;
}
// report error..
errorReturnReference(tok2);
}
}
// return reference to temporary..
else if (Token::Match(tok2, "return %name% (") &&
Token::simpleMatch(tok2->linkAt(2), ") ;")) {
if (Token::Match(tok2, "return %name% (") && Token::simpleMatch(tok2->linkAt(2), ") ;")) {
if (returnTemporary(tok2->next())) {
// report error..
errorReturnTempReference(tok2);
@ -559,7 +536,8 @@ void CheckAutoVariables::returnReference()
}
// Return reference to a literal or the result of a calculation
else if (tok2->astOperand1() && (tok2->astOperand1()->isCalculation() || tok2->next()->isLiteral()) && astHasAutoResult(tok2->astOperand1())) {
else if (tok2->astOperand1() && (tok2->astOperand1()->isCalculation() || tok2->next()->isLiteral()) &&
astHasAutoResult(tok2->astOperand1())) {
errorReturnTempReference(tok2);
}
}
@ -623,7 +601,25 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token
// If the scope is not set correctly then skip checking it
if (scope->bodyStart != start)
return;
bool returnRef = Function::returnsReference(scope->function);
for (const Token *tok = start; tok && tok != end; tok = tok->next()) {
if (returnRef && Token::simpleMatch(tok->astParent(), "return")) {
ErrorPath errorPath;
const Variable *var = getLifetimeVariable(tok, errorPath);
if (var && var->isLocal() && !var->isArgument() && isInScope(var->nameToken(), tok->scope())) {
errorReturnReference(tok, errorPath);
continue;
}
} else if (Token::Match(tok->astParent(), "&|&&") && Token::simpleMatch(tok->astParent()->astParent(), "=") &&
tok->variable() && tok->variable()->declarationId() == tok->varId() && tok->variable()->isStatic() &&
!tok->variable()->isArgument()) {
ErrorPath errorPath;
const Variable *var = getLifetimeVariable(tok, errorPath);
if (var && isInScope(var->nameToken(), tok->scope())) {
errorDanglingReference(tok, var, errorPath);
continue;
}
}
for (const ValueFlow::Value& val:tok->values()) {
if (!val.isLifetimeValue())
continue;
@ -756,9 +752,19 @@ void CheckAutoVariables::errorDanglngLifetime(const Token *tok, const ValueFlow:
reportError(errorPath, Severity::error, "danglingLifetime", msg + ".", CWE562, false);
}
void CheckAutoVariables::errorReturnReference(const Token *tok)
void CheckAutoVariables::errorReturnReference(const Token *tok, ErrorPath errorPath)
{
reportError(tok, Severity::error, "returnReference", "Reference to auto variable returned.", CWE562, false);
errorPath.emplace_back(tok, "");
reportError(errorPath, Severity::error, "returnReference", "Reference to local variable returned.", CWE562, false);
}
void CheckAutoVariables::errorDanglingReference(const Token *tok, const Variable *var, ErrorPath errorPath)
{
std::string tokName = tok ? tok->str() : "x";
std::string varName = var ? var->name() : "y";
std::string msg = "Non-local reference variable '" + tokName + "' to local variable '" + varName + "'";
errorPath.emplace_back(tok, "");
reportError(errorPath, Severity::error, "danglingReference", msg, CWE562, false);
}
void CheckAutoVariables::errorReturnTempReference(const Token *tok)

View File

@ -91,7 +91,8 @@ private:
void errorReturnDanglingLifetime(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 errorReturnReference(const Token *tok);
void errorReturnReference(const Token *tok, ErrorPath errorPath);
void errorDanglingReference(const Token *tok, const Variable *var, ErrorPath errorPath);
void errorReturnTempReference(const Token *tok);
void errorInvalidDeallocation(const Token *tok, const ValueFlow::Value *val);
void errorReturnAddressOfFunctionParameter(const Token *tok, const std::string &varname);
@ -99,12 +100,14 @@ private:
void errorUselessAssignmentPtrArg(const Token *tok);
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const OVERRIDE {
ErrorPath errorPath;
CheckAutoVariables c(nullptr,settings,errorLogger);
c.errorAutoVariableAssignment(nullptr, false);
c.errorReturnAddressToAutoVariable(nullptr);
c.errorAssignAddressOfLocalArrayToGlobalPointer(nullptr, nullptr);
c.errorReturnPointerToLocalArray(nullptr);
c.errorReturnReference(nullptr);
c.errorReturnReference(nullptr, errorPath);
c.errorDanglingReference(nullptr, nullptr, errorPath);
c.errorReturnTempReference(nullptr);
c.errorInvalidDeallocation(nullptr, nullptr);
c.errorReturnAddressOfFunctionParameter(nullptr, "parameter");

View File

@ -2049,6 +2049,15 @@ bool Function::argsMatch(const Scope *scope, const Token *first, const Token *se
return false;
}
bool Function::returnsReference(const Function *function)
{
if (!function)
return false;
if (function->type != Function::eFunction)
return false;
return function->tokenDef->strAt(-1) == "&";
}
const Token * Function::constructorMemberInitialization() const
{
if (!isConstructor() || !functionScope || !functionScope->bodyStart)

View File

@ -845,6 +845,8 @@ public:
static bool argsMatch(const Scope *scope, const Token *first, const Token *second, const std::string &path, unsigned int path_length);
static bool returnsReference(const Function *function);
/**
* @return token to ":" if the function is a constructor
* and it contains member initialization otherwise a nullptr is returned

View File

@ -2603,23 +2603,21 @@ static bool valueFlowForward(Token * const startToken,
return true;
}
static const Variable *getLifetimeVariable(const Token *tok, ErrorPath &errorPath)
const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPath &errorPath)
{
if (!tok)
return nullptr;
const Variable *var = tok->variable();
if (!var)
return nullptr;
if (var->isReference() || var->isRValueReference()) {
for (const ValueFlow::Value &v : tok->values()) {
if (!v.isLifetimeValue())
continue;
if (v.tokvalue == tok)
continue;
errorPath.insert(errorPath.end(), v.errorPath.begin(), v.errorPath.end());
const Variable *var2 = getLifetimeVariable(v.tokvalue, errorPath);
if (var2)
return var2;
}
return nullptr;
if (!var->declEndToken())
return nullptr;
errorPath.emplace_back(var->declEndToken(), "Assigned to reference.");
const Token *vartok = var->declEndToken()->astOperand2();
if (vartok == tok)
return nullptr;
return getLifetimeVariable(vartok, errorPath);
}
return var;
}

View File

@ -32,6 +32,7 @@ class Settings;
class SymbolDatabase;
class Token;
class TokenList;
class Variable;
namespace ValueFlow {
class CPPCHECKLIB Value {
@ -204,4 +205,6 @@ namespace ValueFlow {
std::string eitherTheConditionIsRedundant(const Token *condition);
}
const Variable *getLifetimeVariable(const Token *tok, ValueFlow::Value::ErrorPath &errorPath);
#endif // valueflowH

View File

@ -112,6 +112,8 @@ private:
TEST_CASE(returnReferenceLambda);
TEST_CASE(returnReferenceInnerScope);
TEST_CASE(danglingReference);
// global namespace
TEST_CASE(testglobalnamespace);
@ -837,19 +839,27 @@ private:
}
void returnReference1() {
check("int &foo()\n"
"{\n"
" int s = 0;\n"
" int& x = s;\n"
" return x;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:5]: (error) Reference to local variable returned.\n", errout.str());
check("std::string &foo()\n"
"{\n"
" std::string s;\n"
" return s;\n"
"}");
ASSERT_EQUALS("[test.cpp:4]: (error) Reference to auto variable returned.\n", errout.str());
ASSERT_EQUALS("[test.cpp:4]: (error) Reference to local variable returned.\n", errout.str());
check("std::vector<int> &foo()\n"
"{\n"
" std::vector<int> v;\n"
" return v;\n"
"}");
ASSERT_EQUALS("[test.cpp:4]: (error) Reference to auto variable returned.\n", errout.str());
ASSERT_EQUALS("[test.cpp:4]: (error) Reference to local variable returned.\n", errout.str());
check("std::vector<int> &foo()\n"
"{\n"
@ -942,7 +952,7 @@ private:
" std::string s;\n"
" return s;\n"
"}");
ASSERT_EQUALS("[test.cpp:7]: (error) Reference to auto variable returned.\n", errout.str());
ASSERT_EQUALS("[test.cpp:7]: (error) Reference to local variable returned.\n", errout.str());
check("class Fred {\n"
" std::vector<int> &foo();\n"
@ -952,7 +962,7 @@ private:
" std::vector<int> v;\n"
" return v;\n"
"}");
ASSERT_EQUALS("[test.cpp:7]: (error) Reference to auto variable returned.\n", errout.str());
ASSERT_EQUALS("[test.cpp:7]: (error) Reference to local variable returned.\n", errout.str());
check("class Fred {\n"
" std::vector<int> &foo();\n"
@ -1188,6 +1198,23 @@ private:
ASSERT_EQUALS("", errout.str());
}
void danglingReference() {
check("int &f( int k )\n"
"{\n"
" static int &r = k;\n"
" return r;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:3]: (error) Non-local reference variable 'r' to local variable 'k'\n",
errout.str());
check("int &f( int & k )\n"
"{\n"
" static int &r = k;\n"
" return r;\n"
"}\n");
ASSERT_EQUALS("", errout.str());
}
void testglobalnamespace() {
check("class SharedPtrHolder\n"
"{\n"