Refactorized iterator checking:
- Fixed false positive #5669 - Use symboldatabase in CheckStl::pushback() - Improved support for erase on std::vector and find
This commit is contained in:
parent
1252c70449
commit
e8ac355b39
180
lib/checkstl.cpp
180
lib/checkstl.cpp
|
@ -150,7 +150,7 @@ void CheckStl::iterators()
|
||||||
}
|
}
|
||||||
|
|
||||||
// invalidate the iterator if it is erased
|
// invalidate the iterator if it is erased
|
||||||
else if (tok2->strAt(2) == std::string("erase")) {
|
else if (tok2->strAt(2) == "erase" && (tok2->strAt(4) != "*" || (container && tok2->varId() == container->declarationId()))) {
|
||||||
validIterator = false;
|
validIterator = false;
|
||||||
eraseToken = tok2;
|
eraseToken = tok2;
|
||||||
invalidationScope = tok2->scope();
|
invalidationScope = tok2->scope();
|
||||||
|
@ -593,128 +593,100 @@ void CheckStl::pushback()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterator becomes invalid after reserve, resize, insert, push_back or push_front..
|
// Iterator becomes invalid after reserve, resize, insert, push_back or push_front..
|
||||||
for (std::size_t i = 0; i < functions; ++i) {
|
for (unsigned int iteratorId = 1; iteratorId < symbolDatabase->getVariableListSize(); iteratorId++) {
|
||||||
const Scope * scope = symbolDatabase->functionScopes[i];
|
const Variable* var = symbolDatabase->getVariableFromVarId(iteratorId);
|
||||||
for (const Token* tok = scope->classStart->next(); tok && tok != scope->classEnd; tok = tok->next()) {
|
|
||||||
if (!Token::simpleMatch(tok, "vector <"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// if iterator declaration inside for() loop
|
// Check that its an iterator
|
||||||
bool iteratorDeclaredInsideLoop = false;
|
if (!var || !var->isLocal() || !Token::Match(var->typeEndToken(), "iterator|const_iterator|reverse_iterator|const_reverse_iterator"))
|
||||||
if ((tok->tokAt(-2) && Token::simpleMatch(tok->tokAt(-2), "for (")) ||
|
continue;
|
||||||
(tok->tokAt(-4) && Token::simpleMatch(tok->tokAt(-4), "for ( std ::"))) {
|
|
||||||
iteratorDeclaredInsideLoop = true;
|
// ... on std::vector
|
||||||
|
if (!Token::Match(var->typeStartToken(), "std| ::| vector <"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// the variable id for the vector
|
||||||
|
unsigned int vectorid = 0;
|
||||||
|
|
||||||
|
const Token* validatingToken = 0;
|
||||||
|
|
||||||
|
std::string invalidIterator;
|
||||||
|
const Token* end2 = var->scope()->classEnd;
|
||||||
|
for (const Token *tok2 = var->nameToken(); tok2 != end2; tok2 = tok2->next()) {
|
||||||
|
|
||||||
|
if (validatingToken == tok2) {
|
||||||
|
invalidIterator.clear();
|
||||||
|
validatingToken = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (tok && tok->str() != ">")
|
// Using push_back or push_front inside a loop..
|
||||||
tok = tok->next();
|
if (Token::simpleMatch(tok2, "for (")) {
|
||||||
if (!tok)
|
tok2 = tok2->tokAt(2);
|
||||||
break;
|
|
||||||
if (!Token::Match(tok, "> :: iterator|const_iterator %var% =|;"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const unsigned int iteratorid(tok->tokAt(3)->varId());
|
|
||||||
if (iteratorid == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (iteratorDeclaredInsideLoop && tok->strAt(4) == "=") {
|
|
||||||
// skip "> :: iterator|const_iterator"
|
|
||||||
tok = tok->tokAt(3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the variable id for the vector
|
if (Token::Match(tok2, "%varid% = %var% . begin|rbegin|cbegin|crbegin ( ) ; %varid% != %var% . end|rend|cend|crend ( ) ; ++| %varid% ++| ) {", iteratorId)) {
|
||||||
unsigned int vectorid = 0;
|
// variable id for the loop iterator
|
||||||
|
const unsigned int varId(tok2->tokAt(2)->varId());
|
||||||
|
if (varId == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
// count { , } and parentheses for tok2
|
const Token *pushbackTok = nullptr;
|
||||||
int indent = 0;
|
|
||||||
|
|
||||||
const Token* validatingToken = 0;
|
// Count { and } for tok3
|
||||||
|
const Token *tok3 = tok2->tokAt(20);
|
||||||
std::string invalidIterator;
|
for (const Token* const end3 = tok3->linkAt(-1); tok3 != end3; tok3 = tok3->next()) {
|
||||||
for (const Token *tok2 = tok; indent >= 0 && tok2; tok2 = tok2->next()) {
|
if (tok3->str() == "break" || tok3->str() == "return") {
|
||||||
if (tok2->str() == "{" || tok2->str() == "(")
|
pushbackTok = 0;
|
||||||
++indent;
|
break;
|
||||||
else if (tok2->str() == "}" || tok2->str() == ")") {
|
} else if (Token::Match(tok3, "%varid% . push_front|push_back|insert|reserve|resize|clear|erase (", varId) && !tok3->previous()->isAssignmentOp()) {
|
||||||
if (indent == 0 && Token::simpleMatch(tok2, ") {"))
|
if (tok3->strAt(2) != "erase" || (tok3->tokAt(4)->varId() != iteratorId && tok3->tokAt(5)->varId() != iteratorId)) // This case is handled in: CheckStl::iterators()
|
||||||
tok2 = tok2->next();
|
|
||||||
else
|
|
||||||
--indent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validatingToken == tok2) {
|
|
||||||
invalidIterator.clear();
|
|
||||||
validatingToken = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Using push_back or push_front inside a loop..
|
|
||||||
if (Token::simpleMatch(tok2, "for (")) {
|
|
||||||
tok2 = tok2->tokAt(2);
|
|
||||||
++indent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Token::Match(tok2, "%varid% = %var% . begin|rbegin|cbegin|crbegin ( ) ; %varid% != %var% . end|rend|cend|crend ( ) ; ++| %varid% ++| ) {", iteratorid)) {
|
|
||||||
// variable id for the loop iterator
|
|
||||||
const unsigned int varId(tok2->tokAt(2)->varId());
|
|
||||||
if (varId == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const Token *pushbackTok = nullptr;
|
|
||||||
|
|
||||||
// Count { and } for tok3
|
|
||||||
const Token *tok3 = tok2->tokAt(20);
|
|
||||||
for (const Token* const end3 = tok3->linkAt(-1); tok3 != end3; tok3 = tok3->next()) {
|
|
||||||
if (tok3->str() == "break" || tok3->str() == "return") {
|
|
||||||
pushbackTok = 0;
|
|
||||||
break;
|
|
||||||
} else if (Token::Match(tok3, "%varid% . push_front|push_back|insert|reserve|resize|clear (", varId) && !tok3->previous()->isAssignmentOp()) {
|
|
||||||
pushbackTok = tok3->tokAt(2);
|
pushbackTok = tok3->tokAt(2);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pushbackTok)
|
|
||||||
invalidIteratorError(pushbackTok, pushbackTok->str(), tok2->str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assigning iterator..
|
if (pushbackTok)
|
||||||
if (Token::Match(tok2, "%varid% =", iteratorid)) {
|
invalidIteratorError(pushbackTok, pushbackTok->str(), tok2->str());
|
||||||
if (Token::Match(tok2->tokAt(2), "%var% . begin|end|rbegin|rend|cbegin|cend|crbegin|crend|insert (")) {
|
}
|
||||||
if (!invalidIterator.empty() && Token::Match(tok2->tokAt(4), "insert ( %varid% ,", iteratorid)) {
|
|
||||||
invalidIteratorError(tok2, invalidIterator, tok2->strAt(6));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
vectorid = tok2->tokAt(2)->varId();
|
|
||||||
tok2 = tok2->linkAt(5);
|
|
||||||
} else {
|
|
||||||
vectorid = 0;
|
|
||||||
}
|
|
||||||
invalidIterator = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// push_back on vector..
|
// Assigning iterator..
|
||||||
if (vectorid > 0 && Token::Match(tok2, "%varid% . push_front|push_back|insert|reserve|resize|clear (", vectorid)) {
|
if (Token::Match(tok2, "%varid% =", iteratorId)) {
|
||||||
if (!invalidIterator.empty() && Token::Match(tok2->tokAt(2), "insert ( %varid% ,", iteratorid)) {
|
if (Token::Match(tok2->tokAt(2), "%var% . begin|end|rbegin|rend|cbegin|cend|crbegin|crend|insert|erase|find (")) {
|
||||||
invalidIteratorError(tok2, invalidIterator, tok2->strAt(4));
|
if (!invalidIterator.empty() && Token::Match(tok2->tokAt(4), "insert|erase ( *| %varid% )|,", iteratorId)) {
|
||||||
|
invalidIteratorError(tok2, invalidIterator, var->name());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
vectorid = tok2->tokAt(2)->varId();
|
||||||
|
tok2 = tok2->linkAt(5);
|
||||||
|
} else {
|
||||||
|
vectorid = 0;
|
||||||
|
}
|
||||||
|
invalidIterator = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// push_back on vector..
|
||||||
|
if (vectorid > 0 && Token::Match(tok2, "%varid% . push_front|push_back|insert|reserve|resize|clear|erase (", vectorid)) {
|
||||||
|
if (!invalidIterator.empty() && Token::Match(tok2->tokAt(2), "insert|erase ( *| %varid% ,|)", iteratorId)) {
|
||||||
|
invalidIteratorError(tok2, invalidIterator, var->name());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok2->strAt(2) != "erase" || (tok2->tokAt(4)->varId() != iteratorId && tok2->tokAt(5)->varId() != iteratorId)) // This case is handled in: CheckStl::iterators()
|
||||||
invalidIterator = tok2->strAt(2);
|
invalidIterator = tok2->strAt(2);
|
||||||
tok2 = tok2->linkAt(3);
|
tok2 = tok2->linkAt(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (tok2->str() == "return" || tok2->str() == "throw")
|
else if (tok2->str() == "return" || tok2->str() == "throw")
|
||||||
validatingToken = Token::findsimplematch(tok2->next(), ";");
|
validatingToken = Token::findsimplematch(tok2->next(), ";");
|
||||||
|
|
||||||
// TODO: instead of bail out for 'else' try to check all execution paths.
|
// TODO: instead of bail out for 'else' try to check all execution paths.
|
||||||
else if (tok2->str() == "break" || tok2->str() == "else")
|
else if (tok2->str() == "break" || tok2->str() == "else")
|
||||||
invalidIterator.clear();
|
invalidIterator.clear();
|
||||||
|
|
||||||
// Using invalid iterator..
|
// Using invalid iterator..
|
||||||
if (!invalidIterator.empty()) {
|
if (!invalidIterator.empty()) {
|
||||||
if (Token::Match(tok2, "++|--|*|+|-|(|,|=|!= %varid%", iteratorid))
|
if (Token::Match(tok2, "++|--|*|+|-|(|,|=|!= %varid%", iteratorId))
|
||||||
invalidIteratorError(tok2, invalidIterator, tok2->strAt(1));
|
invalidIteratorError(tok2, invalidIterator, tok2->strAt(1));
|
||||||
if (Token::Match(tok2, "%varid% ++|--|+|-|.", iteratorid))
|
if (Token::Match(tok2, "%varid% ++|--|+|-|.", iteratorId))
|
||||||
invalidIteratorError(tok2, invalidIterator, tok2->str());
|
invalidIteratorError(tok2, invalidIterator, tok2->str());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ private:
|
||||||
TEST_CASE(eraseAssignByFunctionCall);
|
TEST_CASE(eraseAssignByFunctionCall);
|
||||||
TEST_CASE(eraseErase);
|
TEST_CASE(eraseErase);
|
||||||
TEST_CASE(eraseByValue);
|
TEST_CASE(eraseByValue);
|
||||||
|
TEST_CASE(eraseOnVector);
|
||||||
|
|
||||||
TEST_CASE(pushback1);
|
TEST_CASE(pushback1);
|
||||||
TEST_CASE(pushback2);
|
TEST_CASE(pushback2);
|
||||||
|
@ -85,7 +86,6 @@ private:
|
||||||
TEST_CASE(pushback10);
|
TEST_CASE(pushback10);
|
||||||
TEST_CASE(pushback11);
|
TEST_CASE(pushback11);
|
||||||
TEST_CASE(pushback12);
|
TEST_CASE(pushback12);
|
||||||
|
|
||||||
TEST_CASE(insert1);
|
TEST_CASE(insert1);
|
||||||
TEST_CASE(insert2);
|
TEST_CASE(insert2);
|
||||||
|
|
||||||
|
@ -984,9 +984,56 @@ private:
|
||||||
" foo.erase(*it);\n"
|
" foo.erase(*it);\n"
|
||||||
"}");
|
"}");
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
// #5669
|
||||||
|
check("void f() {\n"
|
||||||
|
" HashSet_Ref::iterator aIt = m_ImplementationMap.find( xEle );\n"
|
||||||
|
" m_SetLoadedFactories.erase(*aIt);\n"
|
||||||
|
" m_SetLoadedFactories.erase(aIt);\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("void f(const std::list<int>& m_ImplementationMap) {\n"
|
||||||
|
" std::list<int>::iterator aIt = m_ImplementationMap.find( xEle );\n"
|
||||||
|
" m_ImplementationMap.erase(*aIt);\n"
|
||||||
|
" m_ImplementationMap.erase(aIt);\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) Invalid iterator: aIt\n", errout.str());
|
||||||
|
|
||||||
|
check("void f(const std::list<int>& m_ImplementationMap) {\n"
|
||||||
|
" std::list<int>::iterator aIt = m_ImplementationMap.find( xEle1 );\n"
|
||||||
|
" std::list<int>::iterator bIt = m_ImplementationMap.find( xEle2 );\n"
|
||||||
|
" m_ImplementationMap.erase(*bIt);\n"
|
||||||
|
" m_ImplementationMap.erase(aIt);\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void eraseOnVector() {
|
||||||
|
check("void f(const std::vector<int>& m_ImplementationMap) {\n"
|
||||||
|
" std::vector<int>::iterator aIt = m_ImplementationMap.find( xEle );\n"
|
||||||
|
" m_ImplementationMap.erase(something(unknown));\n" // All iterators become invalidated when erasing from std::vector
|
||||||
|
" m_ImplementationMap.erase(aIt);\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) After erase(), the iterator 'aIt' may be invalid.\n", errout.str());
|
||||||
|
|
||||||
|
check("void f(const std::vector<int>& m_ImplementationMap) {\n"
|
||||||
|
" std::vector<int>::iterator aIt = m_ImplementationMap.find( xEle );\n"
|
||||||
|
" m_ImplementationMap.erase(*aIt);\n" // All iterators become invalidated when erasing from std::vector
|
||||||
|
" m_ImplementationMap.erase(aIt);\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) Invalid iterator: aIt\n", errout.str());
|
||||||
|
|
||||||
|
check("void f(const std::vector<int>& m_ImplementationMap) {\n"
|
||||||
|
" std::vector<int>::iterator aIt = m_ImplementationMap.find( xEle1 );\n"
|
||||||
|
" std::vector<int>::iterator bIt = m_ImplementationMap.find( xEle2 );\n"
|
||||||
|
" m_ImplementationMap.erase(*bIt);\n" // All iterators become invalidated when erasing from std::vector
|
||||||
|
" aIt = m_ImplementationMap.erase(aIt);\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("[test.cpp:5]: (error) After erase(), the iterator 'aIt' may be invalid.\n", errout.str());
|
||||||
|
}
|
||||||
|
|
||||||
void pushback1() {
|
void pushback1() {
|
||||||
check("void f(const std::vector<int> &foo)\n"
|
check("void f(const std::vector<int> &foo)\n"
|
||||||
"{\n"
|
"{\n"
|
||||||
|
|
Loading…
Reference in New Issue