New check: Iterating a known empty container (#2740)

This commit is contained in:
Paul Fultz II 2020-08-22 02:16:26 -05:00 committed by GitHub
parent becdf20310
commit ac846b96d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 106 additions and 7 deletions

View File

@ -1349,6 +1349,13 @@ static const Variable* getArgumentVar(const Token* tok, int argnr)
return nullptr; return nullptr;
} }
static bool isCPPCastKeyword(const Token* tok)
{
if (!tok)
return false;
return endsWith(tok->str(), "_cast", 5);
}
bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Settings *settings, bool *inconclusive) bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Settings *settings, bool *inconclusive)
{ {
if (!tok) if (!tok)
@ -1366,6 +1373,8 @@ bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Setti
tok = getTokenArgumentFunction(tok, argnr); tok = getTokenArgumentFunction(tok, argnr);
if (!tok) if (!tok)
return false; // not a function => variable not changed return false; // not a function => variable not changed
if (tok->isKeyword() && !isCPPCastKeyword(tok))
return false;
const Token * parenTok = tok->next(); const Token * parenTok = tok->next();
if (Token::simpleMatch(parenTok, "<") && parenTok->link()) if (Token::simpleMatch(parenTok, "<") && parenTok->link())
parenTok = parenTok->link()->next(); parenTok = parenTok->link()->next();
@ -1734,7 +1743,7 @@ bool isLikelyStreamRead(bool cpp, const Token *op)
bool isCPPCast(const Token* tok) bool isCPPCast(const Token* tok)
{ {
return tok && Token::simpleMatch(tok->previous(), "> (") && tok->astOperand2() && tok->astOperand1() && tok->astOperand1()->str().find("_cast") != std::string::npos; return tok && Token::simpleMatch(tok->previous(), "> (") && tok->astOperand2() && tok->astOperand1() && isCPPCastKeyword(tok->astOperand1());
} }
bool isConstVarExpression(const Token *tok, const char* skipMatch) bool isConstVarExpression(const Token *tok, const char* skipMatch)

View File

@ -2461,6 +2461,48 @@ void CheckStl::useStlAlgorithm()
} }
} }
void CheckStl::knownEmptyContainerLoopError(const Token *tok)
{
const std::string cont = tok ? tok->expressionString() : std::string("var");
reportError(tok, Severity::style,
"knownEmptyContainerLoop",
"Iterating over container '" + cont + "' that is always empty.", CWE398, false);
}
void CheckStl::knownEmptyContainerLoop()
{
if (!mSettings->isEnabled(Settings::STYLE))
return;
for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) {
for (const Token *tok = function->bodyStart; tok != function->bodyEnd; tok = tok->next()) {
// Parse range-based for loop
if (!Token::simpleMatch(tok, "for ("))
continue;
if (!Token::simpleMatch(tok->next()->link(), ") {"))
continue;
const Token *bodyTok = tok->next()->link()->next();
const Token *splitTok = tok->next()->astOperand2();
if (!Token::simpleMatch(splitTok, ":"))
continue;
const Token* contTok = splitTok->astOperand2();
if (!contTok)
continue;
for(const ValueFlow::Value& v:contTok->values()) {
if (!v.isKnown())
continue;
if (!v.isContainerSizeValue())
continue;
if (v.intvalue != 0)
continue;
knownEmptyContainerLoopError(contTok);
}
}
}
}
static bool isMutex(const Variable* var) static bool isMutex(const Variable* var)
{ {
const Token* tok = Token::typeDecl(var->nameToken()).first; const Token* tok = Token::typeDecl(var->nameToken()).first;

View File

@ -81,6 +81,7 @@ public:
checkStl.invalidContainerLoop(); checkStl.invalidContainerLoop();
checkStl.mismatchingContainers(); checkStl.mismatchingContainers();
checkStl.mismatchingContainerIterator(); checkStl.mismatchingContainerIterator();
checkStl.knownEmptyContainerLoop();
checkStl.stlBoundaries(); checkStl.stlBoundaries();
checkStl.checkDereferenceInvalidIterator(); checkStl.checkDereferenceInvalidIterator();
@ -189,6 +190,8 @@ public:
/** @brief Look for loops that can replaced with std algorithms */ /** @brief Look for loops that can replaced with std algorithms */
void useStlAlgorithm(); void useStlAlgorithm();
void knownEmptyContainerLoop();
void checkMutexes(); void checkMutexes();
private: private:
@ -235,6 +238,8 @@ private:
void useStlAlgorithmError(const Token *tok, const std::string &algoName); void useStlAlgorithmError(const Token *tok, const std::string &algoName);
void knownEmptyContainerLoopError(const Token *tok);
void globalLockGuardError(const Token *tok); void globalLockGuardError(const Token *tok);
void localMutexError(const Token *tok); void localMutexError(const Token *tok);
@ -274,6 +279,7 @@ private:
c.dereferenceInvalidIteratorError(nullptr, "i"); c.dereferenceInvalidIteratorError(nullptr, "i");
c.readingEmptyStlContainerError(nullptr); c.readingEmptyStlContainerError(nullptr);
c.useStlAlgorithmError(nullptr, ""); c.useStlAlgorithmError(nullptr, "");
c.knownEmptyContainerLoopError(nullptr);
c.globalLockGuardError(nullptr); c.globalLockGuardError(nullptr);
c.localMutexError(nullptr); c.localMutexError(nullptr);
} }
@ -298,6 +304,7 @@ private:
"- useless calls of string and STL functions\n" "- useless calls of string and STL functions\n"
"- dereferencing an invalid iterator\n" "- dereferencing an invalid iterator\n"
"- reading from empty STL container\n" "- reading from empty STL container\n"
"- iterating over an empty STL container\n"
"- consider using an STL algorithm instead of raw loop\n" "- consider using an STL algorithm instead of raw loop\n"
"- incorrect locking with mutex\n"; "- incorrect locking with mutex\n";
} }

View File

@ -330,9 +330,19 @@ struct ForwardTraversal {
if (initTok && updateRecursive(initTok) == Progress::Break) if (initTok && updateRecursive(initTok) == Progress::Break)
return Progress::Break; return Progress::Break;
if (Token::Match(tok, "for|while (")) { if (Token::Match(tok, "for|while (")) {
// For-range loop
if (Token::simpleMatch(condTok, ":")) {
Token* conTok = condTok->astOperand2();
if (conTok && updateRecursive(conTok) == Progress::Break)
return Progress::Break;
if (updateLoop(endBlock, condTok) == Progress::Break)
return Progress::Break;
} else {
Token* stepTok = getStepTok(tok); Token* stepTok = getStepTok(tok);
if (updateLoop(endBlock, condTok, initTok, stepTok) == Progress::Break) if (updateLoop(endBlock, condTok, initTok, stepTok) == Progress::Break)
return Progress::Break; return Progress::Break;
}
tok = endBlock; tok = endBlock;
} else { } else {
// Traverse condition // Traverse condition

View File

@ -1961,7 +1961,7 @@ static bool isConditionKnown(const Token* tok, bool then)
if (then) if (then)
op = "&&"; op = "&&";
const Token* parent = tok->astParent(); const Token* parent = tok->astParent();
while (parent && parent->str() == op) while (parent && (parent->str() == op || parent->str() == "!"))
parent = parent->astParent(); parent = parent->astParent();
return (parent && parent->str() == "("); return (parent && parent->str() == "(");
} }
@ -5977,7 +5977,7 @@ static void valueFlowContainerAfterCondition(TokenList *tokenlist,
return cond; return cond;
const Token *parent = tok->astParent(); const Token *parent = tok->astParent();
while (parent) { while (parent) {
if (Token::Match(parent, "%comp%|!")) if (Token::Match(parent, "%comp%"))
return cond; return cond;
parent = parent->astParent(); parent = parent->astParent();
} }

View File

@ -166,6 +166,7 @@ private:
TEST_CASE(invalidContainerLoop); TEST_CASE(invalidContainerLoop);
TEST_CASE(findInsert); TEST_CASE(findInsert);
TEST_CASE(checkKnownEmptyContainerLoop);
TEST_CASE(checkMutexes); TEST_CASE(checkMutexes);
} }
@ -4524,6 +4525,36 @@ private:
ASSERT_EQUALS("", errout.str()); ASSERT_EQUALS("", errout.str());
} }
void checkKnownEmptyContainerLoop() {
check("void f() {\n"
" std::vector<int> v;\n"
" for(auto x:v) {}\n"
"}\n",
true);
ASSERT_EQUALS("[test.cpp:3]: (style) Iterating over container 'v' that is always empty.\n", errout.str());
check("void f(std::vector<int> v) {\n"
" v.clear();\n"
" for(auto x:v) {}\n"
"}\n",
true);
ASSERT_EQUALS("[test.cpp:3]: (style) Iterating over container 'v' that is always empty.\n", errout.str());
check("void f(std::vector<int> v) {\n"
" if (!v.empty()) { return; }\n"
" for(auto x:v) {}\n"
"}\n",
true);
ASSERT_EQUALS("[test.cpp:3]: (style) Iterating over container 'v' that is always empty.\n", errout.str());
check("void f(std::vector<int> v) {\n"
" if (v.empty()) { return; }\n"
" for(auto x:v) {}\n"
"}\n",
true);
ASSERT_EQUALS("", errout.str());
}
void checkMutexes() { void checkMutexes() {
check("void f() {\n" check("void f() {\n"
" static std::mutex m;\n" " static std::mutex m;\n"

View File

@ -4068,7 +4068,7 @@ private:
" if (!static_cast<bool>(ints.empty()))\n" " if (!static_cast<bool>(ints.empty()))\n"
" ints.front();\n" " ints.front();\n"
"}"; "}";
ASSERT(tokenValues(code, "ints . front").empty()); ASSERT_EQUALS("", isImpossibleContainerSizeValue(tokenValues(code, "ints . front"), 0));
// valueFlowContainerReverse // valueFlowContainerReverse
code = "void f(const std::list<int> &ints) {\n" code = "void f(const std::list<int> &ints) {\n"