Check conditions in return statements (#1411)
* Identify return conditions in multiconditions * Improve error messages * Check return statements are always true or false * Add more tests for FPs * Fix FP when returning const like variables * Fix FP when returning pointers or classes * Fix FP with member variable access * Check non-local variables * Use simplematch * Check for null
This commit is contained in:
parent
465db2dff7
commit
40cb9cb1bc
|
@ -878,6 +878,20 @@ bool isVariableChanged(const Token *start, const Token *end, const unsigned int
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isVariableChanged(const Variable * var, const Settings *settings, bool cpp)
|
||||||
|
{
|
||||||
|
if(!var)
|
||||||
|
return false;
|
||||||
|
if(!var->scope())
|
||||||
|
return false;
|
||||||
|
const Token * start = var->declEndToken();
|
||||||
|
if(!start)
|
||||||
|
return false;
|
||||||
|
if(Token::Match(start, "; %varid% =", var->declarationId()))
|
||||||
|
start = start->tokAt(2);
|
||||||
|
return isVariableChanged(start->next(), var->scope()->bodyEnd, var->declarationId(), var->isGlobal(), settings, cpp);
|
||||||
|
}
|
||||||
|
|
||||||
int numberOfArguments(const Token *start)
|
int numberOfArguments(const Token *start)
|
||||||
{
|
{
|
||||||
int arguments=0;
|
int arguments=0;
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
class Library;
|
class Library;
|
||||||
class Settings;
|
class Settings;
|
||||||
class Token;
|
class Token;
|
||||||
|
class Variable;
|
||||||
|
|
||||||
/** Is expression a 'signed char' if no promotion is used */
|
/** Is expression a 'signed char' if no promotion is used */
|
||||||
bool astIsSignedChar(const Token *tok);
|
bool astIsSignedChar(const Token *tok);
|
||||||
|
@ -110,6 +111,8 @@ bool isVariableChangedByFunctionCall(const Token *tok, const Settings *settings,
|
||||||
/** Is variable changed in block of code? */
|
/** Is variable changed in block of code? */
|
||||||
bool isVariableChanged(const Token *start, const Token *end, const unsigned int varid, bool globalvar, const Settings *settings, bool cpp);
|
bool isVariableChanged(const Token *start, const Token *end, const unsigned int varid, bool globalvar, const Settings *settings, bool cpp);
|
||||||
|
|
||||||
|
bool isVariableChanged(const Variable * var, const Settings *settings, bool cpp);
|
||||||
|
|
||||||
/** 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
|
||||||
* @param start token which is supposed to be the function/macro name.
|
* @param start token which is supposed to be the function/macro name.
|
||||||
* \return Number of arguments
|
* \return Number of arguments
|
||||||
|
|
|
@ -535,21 +535,27 @@ void CheckCondition::multiCondition2()
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// parse until second condition is reached..
|
// parse until second condition is reached..
|
||||||
enum MULTICONDITIONTYPE { INNER, AFTER } type;
|
enum MULTICONDITIONTYPE { INNER, AFTER };
|
||||||
const Token *tok;
|
const Token *tok;
|
||||||
if (Token::Match(scope.bodyStart, "{ return|throw|continue|break")) {
|
|
||||||
|
// Parse inner condition first and then early return condition
|
||||||
|
std::vector<MULTICONDITIONTYPE> types = {MULTICONDITIONTYPE::INNER};
|
||||||
|
if (Token::Match(scope.bodyStart, "{ return|throw|continue|break"))
|
||||||
|
types.push_back(MULTICONDITIONTYPE::AFTER);
|
||||||
|
for(MULTICONDITIONTYPE type:types) {
|
||||||
|
if(type == MULTICONDITIONTYPE::AFTER) {
|
||||||
tok = scope.bodyEnd->next();
|
tok = scope.bodyEnd->next();
|
||||||
type = MULTICONDITIONTYPE::AFTER;
|
|
||||||
} else {
|
} else {
|
||||||
tok = scope.bodyStart;
|
tok = scope.bodyStart;
|
||||||
type = MULTICONDITIONTYPE::INNER;
|
|
||||||
}
|
}
|
||||||
const Token * const endToken = tok->scope()->bodyEnd;
|
const Token * const endToken = tok->scope()->bodyEnd;
|
||||||
|
|
||||||
for (; tok && tok != endToken; tok = tok->next()) {
|
for (; tok && tok != endToken; tok = tok->next()) {
|
||||||
if (Token::simpleMatch(tok, "if (")) {
|
if (Token::Match(tok, "if|return")) {
|
||||||
|
const Token * condStartToken = tok->str() == "if" ? tok->next() : tok;
|
||||||
|
const Token * condEndToken = tok->str() == "if" ? condStartToken->link() : Token::findsimplematch(condStartToken, ";");
|
||||||
// Does condition modify tracked variables?
|
// Does condition modify tracked variables?
|
||||||
if (const Token *op = Token::findmatch(tok, "++|--", tok->linkAt(1))) {
|
if (const Token *op = Token::findmatch(tok, "++|--", condEndToken)) {
|
||||||
bool bailout = false;
|
bool bailout = false;
|
||||||
while (op) {
|
while (op) {
|
||||||
if (vars.find(op->astOperand1()->varId()) != vars.end()) {
|
if (vars.find(op->astOperand1()->varId()) != vars.end()) {
|
||||||
|
@ -560,14 +566,24 @@ void CheckCondition::multiCondition2()
|
||||||
bailout = true;
|
bailout = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
op = Token::findmatch(op->next(), "++|--", tok->linkAt(1));
|
op = Token::findmatch(op->next(), "++|--", condEndToken);
|
||||||
}
|
}
|
||||||
if (bailout)
|
if (bailout)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Condition..
|
// Condition..
|
||||||
const Token *cond2 = tok->next()->astOperand2();
|
const Token *cond2 = tok->str() == "if" ? condStartToken->astOperand2() : condStartToken->astOperand1();
|
||||||
|
// Check if returning boolean values
|
||||||
|
if (tok->str() == "return") {
|
||||||
|
const Variable * condVar = nullptr;
|
||||||
|
if (Token::Match(cond2, "%var% ;"))
|
||||||
|
condVar = cond2->variable();
|
||||||
|
else if(Token::Match(cond2, ". %var% ;"))
|
||||||
|
condVar = cond2->next()->variable();
|
||||||
|
if (condVar && (condVar->isClass() || condVar->isPointer()))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
ErrorPath errorPath;
|
ErrorPath errorPath;
|
||||||
|
|
||||||
|
@ -682,16 +698,30 @@ void CheckCondition::multiCondition2()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string innerSmtString(const Token * tok)
|
||||||
|
{
|
||||||
|
if(!tok)
|
||||||
|
return "if";
|
||||||
|
if(!tok->astTop())
|
||||||
|
return "if";
|
||||||
|
const Token * top = tok->astTop();
|
||||||
|
if(top->str() == "(" && top->astOperand1())
|
||||||
|
return top->astOperand1()->str();
|
||||||
|
return top->str();
|
||||||
|
}
|
||||||
|
|
||||||
void CheckCondition::oppositeInnerConditionError(const Token *tok1, const Token* tok2, ErrorPath errorPath)
|
void CheckCondition::oppositeInnerConditionError(const Token *tok1, const Token* tok2, ErrorPath errorPath)
|
||||||
{
|
{
|
||||||
const std::string s1(tok1 ? tok1->expressionString() : "x");
|
const std::string s1(tok1 ? tok1->expressionString() : "x");
|
||||||
const std::string s2(tok2 ? tok2->expressionString() : "!x");
|
const std::string s2(tok2 ? tok2->expressionString() : "!x");
|
||||||
|
const std::string innerSmt = innerSmtString(tok2);
|
||||||
errorPath.emplace_back(ErrorPathItem(tok1, "outer condition: " + s1));
|
errorPath.emplace_back(ErrorPathItem(tok1, "outer condition: " + s1));
|
||||||
errorPath.emplace_back(ErrorPathItem(tok2, "opposite inner condition: " + s2));
|
errorPath.emplace_back(ErrorPathItem(tok2, "opposite inner condition: " + s2));
|
||||||
|
|
||||||
const std::string msg("Opposite inner 'if' condition leads to a dead code block.\n"
|
const std::string msg("Opposite inner '" + innerSmt + "' condition leads to a dead code block.\n"
|
||||||
"Opposite inner 'if' condition leads to a dead code block (outer condition is '" + s1 + "' and inner condition is '" + s2 + "').");
|
"Opposite inner '" + innerSmt + "' condition leads to a dead code block (outer condition is '" + s1 + "' and inner condition is '" + s2 + "').");
|
||||||
reportError(errorPath, Severity::warning, "oppositeInnerCondition", msg, CWE398, false);
|
reportError(errorPath, Severity::warning, "oppositeInnerCondition", msg, CWE398, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,11 +729,12 @@ void CheckCondition::identicalInnerConditionError(const Token *tok1, const Token
|
||||||
{
|
{
|
||||||
const std::string s1(tok1 ? tok1->expressionString() : "x");
|
const std::string s1(tok1 ? tok1->expressionString() : "x");
|
||||||
const std::string s2(tok2 ? tok2->expressionString() : "x");
|
const std::string s2(tok2 ? tok2->expressionString() : "x");
|
||||||
|
const std::string innerSmt = innerSmtString(tok2);
|
||||||
errorPath.emplace_back(ErrorPathItem(tok1, "outer condition: " + s1));
|
errorPath.emplace_back(ErrorPathItem(tok1, "outer condition: " + s1));
|
||||||
errorPath.emplace_back(ErrorPathItem(tok2, "identical inner condition: " + s2));
|
errorPath.emplace_back(ErrorPathItem(tok2, "identical inner condition: " + s2));
|
||||||
|
|
||||||
const std::string msg("Identical inner 'if' condition is always true.\n"
|
const std::string msg("Identical inner '" + innerSmt + "' condition is always true.\n"
|
||||||
"Identical inner 'if' condition is always true (outer condition is '" + s1 + "' and inner condition is '" + s2 + "').");
|
"Identical inner '" + innerSmt + "' condition is always true (outer condition is '" + s1 + "' and inner condition is '" + s2 + "').");
|
||||||
reportError(errorPath, Severity::warning, "identicalInnerCondition", msg, CWE398, false);
|
reportError(errorPath, Severity::warning, "identicalInnerCondition", msg, CWE398, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1222,7 +1253,7 @@ void CheckCondition::alwaysTrueFalse()
|
||||||
continue;
|
continue;
|
||||||
if (Token::Match(tok, "! %num%|%bool%|%char%"))
|
if (Token::Match(tok, "! %num%|%bool%|%char%"))
|
||||||
continue;
|
continue;
|
||||||
if (Token::Match(tok, "%oror%|&&"))
|
if (Token::Match(tok, "%oror%|&&|:"))
|
||||||
continue;
|
continue;
|
||||||
if (Token::Match(tok, "%comp%") && isSameExpression(mTokenizer->isCPP(), true, tok->astOperand1(), tok->astOperand2(), mSettings->library, true, true))
|
if (Token::Match(tok, "%comp%") && isSameExpression(mTokenizer->isCPP(), true, tok->astOperand1(), tok->astOperand2(), mSettings->library, true, true))
|
||||||
continue;
|
continue;
|
||||||
|
@ -1232,8 +1263,20 @@ void CheckCondition::alwaysTrueFalse()
|
||||||
(Token::Match(tok->astParent(), "%oror%|&&") || Token::Match(tok->astParent()->astOperand1(), "if|while"));
|
(Token::Match(tok->astParent(), "%oror%|&&") || Token::Match(tok->astParent()->astOperand1(), "if|while"));
|
||||||
const bool constValExpr = tok->isNumber() && Token::Match(tok->astParent(),"%oror%|&&|?"); // just one number in boolean expression
|
const bool constValExpr = tok->isNumber() && Token::Match(tok->astParent(),"%oror%|&&|?"); // just one number in boolean expression
|
||||||
const bool compExpr = Token::Match(tok, "%comp%|!"); // a compare expression
|
const bool compExpr = Token::Match(tok, "%comp%|!"); // a compare expression
|
||||||
|
const bool returnStatement = Token::simpleMatch(tok->astTop(), "return") &&
|
||||||
|
Token::Match(tok->astParent(), "%oror%|&&|return");
|
||||||
|
|
||||||
if (!(constIfWhileExpression || constValExpr || compExpr))
|
if (!(constIfWhileExpression || constValExpr || compExpr || returnStatement))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(returnStatement && (tok->isEnumerator() || Token::Match(tok, "nullptr|NULL")))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(returnStatement && Token::simpleMatch(tok->astParent(), "return") && tok->variable() && (
|
||||||
|
!tok->variable()->isLocal() ||
|
||||||
|
tok->variable()->isReference() ||
|
||||||
|
tok->variable()->isConst() ||
|
||||||
|
!isVariableChanged(tok->variable(), mSettings, mTokenizer->isCPP())))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Don't warn in assertions. Condition is often 'always true' by intention.
|
// Don't warn in assertions. Condition is often 'always true' by intention.
|
||||||
|
|
|
@ -1441,6 +1441,13 @@ private:
|
||||||
"}");
|
"}");
|
||||||
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (warning) Opposite inner 'if' condition leads to a dead code block.\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (warning) Opposite inner 'if' condition leads to a dead code block.\n", errout.str());
|
||||||
|
|
||||||
|
check("bool foo(int a, int b) {\n"
|
||||||
|
" if(a==b)\n"
|
||||||
|
" return a!=b;\n"
|
||||||
|
" return false;\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (warning) Opposite inner 'return' condition leads to a dead code block.\n", errout.str());
|
||||||
|
|
||||||
check("void foo(int a, int b) {\n"
|
check("void foo(int a, int b) {\n"
|
||||||
" if(a==b)\n"
|
" if(a==b)\n"
|
||||||
" if(b!=a)\n"
|
" if(b!=a)\n"
|
||||||
|
@ -2049,6 +2056,25 @@ private:
|
||||||
"}\n");
|
"}\n");
|
||||||
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (warning) Identical inner 'if' condition is always true.\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (warning) Identical inner 'if' condition is always true.\n", errout.str());
|
||||||
|
|
||||||
|
check("bool f(bool a) {\n"
|
||||||
|
" if(a) { return a; }\n"
|
||||||
|
" return false;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Identical inner 'return' condition is always true.\n", errout.str());
|
||||||
|
|
||||||
|
check("int* f(int* a, int * b) {\n"
|
||||||
|
" if(a) { return a; }\n"
|
||||||
|
" return b;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("struct A { int * x; };\n"
|
||||||
|
"int* f(A a, int * b) {\n"
|
||||||
|
" if(a.x) { return a.x; }\n"
|
||||||
|
" return b;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
check("void f() {\n"
|
check("void f() {\n"
|
||||||
" uint32_t value;\n"
|
" uint32_t value;\n"
|
||||||
" get_value(&value);\n"
|
" get_value(&value);\n"
|
||||||
|
@ -2069,6 +2095,12 @@ private:
|
||||||
"}");
|
"}");
|
||||||
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (warning) Identical condition 'x>100', second condition is always false\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (warning) Identical condition 'x>100', second condition is always false\n", errout.str());
|
||||||
|
|
||||||
|
check("bool f(int x) {\n"
|
||||||
|
" if (x > 100) { return false; }\n"
|
||||||
|
" return x > 100;\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (warning) Identical condition 'x>100', second condition is always false\n", errout.str());
|
||||||
|
|
||||||
check("void f(int x) {\n"
|
check("void f(int x) {\n"
|
||||||
" if (x > 100) { return; }\n"
|
" if (x > 100) { return; }\n"
|
||||||
" if (x > 100 || y > 100) {}\n"
|
" if (x > 100 || y > 100) {}\n"
|
||||||
|
@ -2444,6 +2476,12 @@ private:
|
||||||
"}");
|
"}");
|
||||||
ASSERT_EQUALS("[test.cpp:4]: (style) Condition '!x' is always true\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:4]: (style) Condition '!x' is always true\n", errout.str());
|
||||||
|
|
||||||
|
check("bool f(int x) {\n"
|
||||||
|
" if(x == 0) { x++; return x == 0; } \n"
|
||||||
|
" return false;\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (style) Condition 'x==0' is always false\n", errout.str());
|
||||||
|
|
||||||
check("void f() {\n" // #6898 (Token::expressionString)
|
check("void f() {\n" // #6898 (Token::expressionString)
|
||||||
" int x = 0;\n"
|
" int x = 0;\n"
|
||||||
" A(x++ == 1);\n"
|
" A(x++ == 1);\n"
|
||||||
|
@ -2546,6 +2584,42 @@ private:
|
||||||
" {}\n"
|
" {}\n"
|
||||||
"}\n");
|
"}\n");
|
||||||
ASSERT_EQUALS("[test.cpp:4]: (style) Condition '!b' is always true\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:4]: (style) Condition '!b' is always true\n", errout.str());
|
||||||
|
|
||||||
|
check("bool f() { return nullptr; }");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("enum E { A };\n"
|
||||||
|
"bool f() { return A; }\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("bool f() { \n"
|
||||||
|
" const int x = 0;\n"
|
||||||
|
" return x; \n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("bool f() { \n"
|
||||||
|
" int x = 0;\n"
|
||||||
|
" return x; \n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("bool& g();\n"
|
||||||
|
"bool f() {\n"
|
||||||
|
" bool & b = g();\n"
|
||||||
|
" b = false;\n"
|
||||||
|
" return b;\n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("struct A {\n"
|
||||||
|
" bool b;\n"
|
||||||
|
" bool f() {\n"
|
||||||
|
" b = false;\n"
|
||||||
|
" return b;\n"
|
||||||
|
" }\n"
|
||||||
|
"};\n");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void multiConditionAlwaysTrue() {
|
void multiConditionAlwaysTrue() {
|
||||||
|
|
Loading…
Reference in New Issue