#6572 False positive eraseDereference - in iterator class - flag error inconclusive if iterator is not STL type
This commit is contained in:
parent
d76c5c525c
commit
b17807c568
|
@ -61,7 +61,7 @@ void CheckStl::iteratorsError(const Token *tok, const std::string &container1, c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error message used when dereferencing an iterator that has been erased..
|
// Error message used when dereferencing an iterator that has been erased..
|
||||||
void CheckStl::dereferenceErasedError(const Token *erased, const Token* deref, const std::string &itername)
|
void CheckStl::dereferenceErasedError(const Token *erased, const Token* deref, const std::string &itername, bool inconclusive)
|
||||||
{
|
{
|
||||||
if (erased) {
|
if (erased) {
|
||||||
std::list<const Token*> callstack;
|
std::list<const Token*> callstack;
|
||||||
|
@ -70,12 +70,12 @@ void CheckStl::dereferenceErasedError(const Token *erased, const Token* deref, c
|
||||||
reportError(callstack, Severity::error, "eraseDereference",
|
reportError(callstack, Severity::error, "eraseDereference",
|
||||||
"Iterator '" + itername + "' used after element has been erased.\n"
|
"Iterator '" + itername + "' used after element has been erased.\n"
|
||||||
"The iterator '" + itername + "' is invalid after the element it pointed to has been erased. "
|
"The iterator '" + itername + "' is invalid after the element it pointed to has been erased. "
|
||||||
"Dereferencing or comparing it with another iterator is invalid operation.", CWE664, false);
|
"Dereferencing or comparing it with another iterator is invalid operation.", CWE664, inconclusive);
|
||||||
} else {
|
} else {
|
||||||
reportError(deref, Severity::error, "eraseDereference",
|
reportError(deref, Severity::error, "eraseDereference",
|
||||||
"Invalid iterator '" + itername + "' used.\n"
|
"Invalid iterator '" + itername + "' used.\n"
|
||||||
"The iterator '" + itername + "' is invalid before being assigned. "
|
"The iterator '" + itername + "' is invalid before being assigned. "
|
||||||
"Dereferencing or comparing it with another iterator is invalid operation.", CWE664, false);
|
"Dereferencing or comparing it with another iterator is invalid operation.", CWE664, inconclusive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ static const Token *skipMembers(const Token *tok)
|
||||||
return tok;
|
return tok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isIterator(const Variable *var)
|
static bool isIterator(const Variable *var, bool& inconclusiveType)
|
||||||
{
|
{
|
||||||
// Check that its an iterator
|
// Check that its an iterator
|
||||||
if (!var || !var->isLocal() || !Token::Match(var->typeEndToken(), "iterator|const_iterator|reverse_iterator|const_reverse_iterator|auto"))
|
if (!var || !var->isLocal() || !Token::Match(var->typeEndToken(), "iterator|const_iterator|reverse_iterator|const_reverse_iterator|auto"))
|
||||||
|
@ -99,8 +99,11 @@ static bool isIterator(const Variable *var)
|
||||||
// look for operator* and operator++
|
// look for operator* and operator++
|
||||||
const Function* end = var->type()->getFunction("operator*");
|
const Function* end = var->type()->getFunction("operator*");
|
||||||
const Function* incOperator = var->type()->getFunction("operator++");
|
const Function* incOperator = var->type()->getFunction("operator++");
|
||||||
if (!end || end->argCount() > 0 || !incOperator)
|
if (!end || end->argCount() > 0 || !incOperator) {
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
inconclusiveType = true; // heuristics only
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -126,7 +129,8 @@ void CheckStl::iterators()
|
||||||
for (unsigned int iteratorId = 1; iteratorId < symbolDatabase->getVariableListSize(); iteratorId++) {
|
for (unsigned int iteratorId = 1; iteratorId < symbolDatabase->getVariableListSize(); iteratorId++) {
|
||||||
const Variable* var = symbolDatabase->getVariableFromVarId(iteratorId);
|
const Variable* var = symbolDatabase->getVariableFromVarId(iteratorId);
|
||||||
|
|
||||||
if (!isIterator(var))
|
bool inconclusiveType=false;
|
||||||
|
if (!isIterator(var, inconclusiveType))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// the validIterator flag says if the iterator has a valid value or not
|
// the validIterator flag says if the iterator has a valid value or not
|
||||||
|
@ -202,7 +206,8 @@ void CheckStl::iterators()
|
||||||
while (par2->str() != ")") {
|
while (par2->str() != ")") {
|
||||||
if (par2->varId() == containerToken->varId())
|
if (par2->varId() == containerToken->varId())
|
||||||
break;
|
break;
|
||||||
if (isIterator(par2->variable()))
|
bool inconclusiveType2=false;
|
||||||
|
if (isIterator(par2->variable(), inconclusiveType2))
|
||||||
break; // TODO: check if iterator points at same container
|
break; // TODO: check if iterator points at same container
|
||||||
if (par2->str() == "(")
|
if (par2->str() == "(")
|
||||||
par2 = par2->link();
|
par2 = par2->link();
|
||||||
|
@ -266,10 +271,10 @@ void CheckStl::iterators()
|
||||||
|
|
||||||
// Dereferencing invalid iterator?
|
// Dereferencing invalid iterator?
|
||||||
else if (!validIterator && Token::Match(tok2, "* %varid%", iteratorId)) {
|
else if (!validIterator && Token::Match(tok2, "* %varid%", iteratorId)) {
|
||||||
dereferenceErasedError(eraseToken, tok2, tok2->strAt(1));
|
dereferenceErasedError(eraseToken, tok2, tok2->strAt(1), inconclusiveType);
|
||||||
tok2 = tok2->next();
|
tok2 = tok2->next();
|
||||||
} else if (!validIterator && Token::Match(tok2, "%varid% . %name%", iteratorId)) {
|
} else if (!validIterator && Token::Match(tok2, "%varid% . %name%", iteratorId)) {
|
||||||
dereferenceErasedError(eraseToken, tok2, tok2->str());
|
dereferenceErasedError(eraseToken, tok2, tok2->str(), inconclusiveType);
|
||||||
tok2 = tok2->tokAt(2);
|
tok2 = tok2->tokAt(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,7 +519,8 @@ void CheckStl::erase()
|
||||||
|
|
||||||
void CheckStl::eraseCheckLoopVar(const Scope &scope, const Variable *var)
|
void CheckStl::eraseCheckLoopVar(const Scope &scope, const Variable *var)
|
||||||
{
|
{
|
||||||
if (!var || !Token::simpleMatch(var->typeEndToken(), "iterator"))
|
bool inconclusiveType=false;
|
||||||
|
if (!isIterator(var, inconclusiveType))
|
||||||
return;
|
return;
|
||||||
for (const Token *tok = scope.classStart; tok != scope.classEnd; tok = tok->next()) {
|
for (const Token *tok = scope.classStart; tok != scope.classEnd; tok = tok->next()) {
|
||||||
if (tok->str() != "(")
|
if (tok->str() != "(")
|
||||||
|
@ -541,14 +547,14 @@ void CheckStl::eraseCheckLoopVar(const Scope &scope, const Variable *var)
|
||||||
if (tok2->varId() == var->declarationId()) {
|
if (tok2->varId() == var->declarationId()) {
|
||||||
if (Token::simpleMatch(tok2->next(), "="))
|
if (Token::simpleMatch(tok2->next(), "="))
|
||||||
break;
|
break;
|
||||||
dereferenceErasedError(tok, tok2, tok2->str());
|
dereferenceErasedError(tok, tok2, tok2->str(), inconclusiveType);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (indentlevel == 0U && Token::Match(tok2, "break|return|goto"))
|
if (indentlevel == 0U && Token::Match(tok2, "break|return|goto"))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (tok2 == scope.classEnd)
|
if (tok2 == scope.classEnd)
|
||||||
dereferenceErasedError(tok, scope.classDef, var->nameToken()->str());
|
dereferenceErasedError(tok, scope.classDef, var->nameToken()->str(), inconclusiveType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,14 +49,15 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This constructor is used when running checks. */
|
/** This constructor is used when running checks. */
|
||||||
CheckStl(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger)
|
CheckStl(const Tokenizer* tokenizer, const Settings* settings, ErrorLogger* errorLogger)
|
||||||
: Check(myName(), tokenizer, settings, errorLogger) {
|
: Check(myName(), tokenizer, settings, errorLogger) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Simplified checks. The token list is simplified. */
|
/** Simplified checks. The token list is simplified. */
|
||||||
void runSimplifiedChecks(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) {
|
void runSimplifiedChecks(const Tokenizer* tokenizer, const Settings* settings, ErrorLogger* errorLogger) {
|
||||||
if (!tokenizer->isCPP())
|
if (!tokenizer->isCPP()) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CheckStl checkStl(tokenizer, settings, errorLogger);
|
CheckStl checkStl(tokenizer, settings, errorLogger);
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ public:
|
||||||
* it is bad to dereference it after the erase.
|
* it is bad to dereference it after the erase.
|
||||||
*/
|
*/
|
||||||
void erase();
|
void erase();
|
||||||
void eraseCheckLoopVar(const Scope &scope, const Variable *var);
|
void eraseCheckLoopVar(const Scope& scope, const Variable* var);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,8 +162,9 @@ public:
|
||||||
* @param erased token where the erase occurs
|
* @param erased token where the erase occurs
|
||||||
* @param deref token where the dereference occurs
|
* @param deref token where the dereference occurs
|
||||||
* @param itername iterator name
|
* @param itername iterator name
|
||||||
|
* @param inconclusive inconclusive flag
|
||||||
*/
|
*/
|
||||||
void dereferenceErasedError(const Token* erased, const Token* deref, const std::string &itername);
|
void dereferenceErasedError(const Token* erased, const Token* deref, const std::string& itername, bool inconclusive);
|
||||||
|
|
||||||
/** @brief Reading from empty stl container */
|
/** @brief Reading from empty stl container */
|
||||||
void readingEmptyStlContainer();
|
void readingEmptyStlContainer();
|
||||||
|
@ -170,45 +172,45 @@ public:
|
||||||
private:
|
private:
|
||||||
void readingEmptyStlContainer_parseUsage(const Token* tok, const Library::Container* container, std::map<unsigned int, const Library::Container*>& empty, bool noerror);
|
void readingEmptyStlContainer_parseUsage(const Token* tok, const Library::Container* container, std::map<unsigned int, const Library::Container*>& empty, bool noerror);
|
||||||
|
|
||||||
void missingComparisonError(const Token *incrementToken1, const Token *incrementToken2);
|
void missingComparisonError(const Token* incrementToken1, const Token* incrementToken2);
|
||||||
void string_c_strThrowError(const Token *tok);
|
void string_c_strThrowError(const Token* tok);
|
||||||
void string_c_strError(const Token *tok);
|
void string_c_strError(const Token* tok);
|
||||||
void string_c_strReturn(const Token *tok);
|
void string_c_strReturn(const Token* tok);
|
||||||
void string_c_strParam(const Token *tok, unsigned int number);
|
void string_c_strParam(const Token* tok, unsigned int number);
|
||||||
|
|
||||||
void stlOutOfBoundsError(const Token *tok, const std::string &num, const std::string &var, bool at);
|
void stlOutOfBoundsError(const Token* tok, const std::string& num, const std::string& var, bool at);
|
||||||
void negativeIndexError(const Token *tok, const ValueFlow::Value &index);
|
void negativeIndexError(const Token* tok, const ValueFlow::Value& index);
|
||||||
void invalidIteratorError(const Token *tok, const std::string &iteratorName);
|
void invalidIteratorError(const Token* tok, const std::string& iteratorName);
|
||||||
void iteratorsError(const Token *tok, const std::string &container1, const std::string &container2);
|
void iteratorsError(const Token* tok, const std::string& container1, const std::string& container2);
|
||||||
void mismatchingContainersError(const Token *tok);
|
void mismatchingContainersError(const Token* tok);
|
||||||
void invalidIteratorError(const Token *tok, const std::string &func, const std::string &iterator_name);
|
void invalidIteratorError(const Token* tok, const std::string& func, const std::string& iterator_name);
|
||||||
void invalidPointerError(const Token *tok, const std::string &func, const std::string &pointer_name);
|
void invalidPointerError(const Token* tok, const std::string& func, const std::string& pointer_name);
|
||||||
void stlBoundariesError(const Token *tok);
|
void stlBoundariesError(const Token* tok);
|
||||||
void if_findError(const Token *tok, bool str);
|
void if_findError(const Token* tok, bool str);
|
||||||
void sizeError(const Token *tok);
|
void sizeError(const Token* tok);
|
||||||
void redundantIfRemoveError(const Token *tok);
|
void redundantIfRemoveError(const Token* tok);
|
||||||
|
|
||||||
void autoPointerError(const Token *tok);
|
void autoPointerError(const Token* tok);
|
||||||
void autoPointerContainerError(const Token *tok);
|
void autoPointerContainerError(const Token* tok);
|
||||||
void autoPointerArrayError(const Token *tok);
|
void autoPointerArrayError(const Token* tok);
|
||||||
void autoPointerMallocError(const Token *tok, const std::string& allocFunction);
|
void autoPointerMallocError(const Token* tok, const std::string& allocFunction);
|
||||||
|
|
||||||
void uselessCallsReturnValueError(const Token *tok, const std::string &varname, const std::string &function);
|
void uselessCallsReturnValueError(const Token* tok, const std::string& varname, const std::string& function);
|
||||||
void uselessCallsSwapError(const Token *tok, const std::string &varname);
|
void uselessCallsSwapError(const Token* tok, const std::string& varname);
|
||||||
void uselessCallsSubstrError(const Token *tok, bool empty);
|
void uselessCallsSubstrError(const Token* tok, bool empty);
|
||||||
void uselessCallsEmptyError(const Token *tok);
|
void uselessCallsEmptyError(const Token* tok);
|
||||||
void uselessCallsRemoveError(const Token *tok, const std::string& function);
|
void uselessCallsRemoveError(const Token* tok, const std::string& function);
|
||||||
|
|
||||||
void dereferenceInvalidIteratorError(const Token* deref, const std::string &iterName);
|
void dereferenceInvalidIteratorError(const Token* deref, const std::string& iterName);
|
||||||
|
|
||||||
void readingEmptyStlContainerError(const Token *tok);
|
void readingEmptyStlContainerError(const Token* tok);
|
||||||
|
|
||||||
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const {
|
void getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const {
|
||||||
CheckStl c(nullptr, settings, errorLogger);
|
CheckStl c(nullptr, settings, errorLogger);
|
||||||
c.invalidIteratorError(nullptr, "iterator");
|
c.invalidIteratorError(nullptr, "iterator");
|
||||||
c.iteratorsError(nullptr, "container1", "container2");
|
c.iteratorsError(nullptr, "container1", "container2");
|
||||||
c.mismatchingContainersError(nullptr);
|
c.mismatchingContainersError(nullptr);
|
||||||
c.dereferenceErasedError(nullptr, nullptr, "iter");
|
c.dereferenceErasedError(nullptr, nullptr, "iter", false);
|
||||||
c.stlOutOfBoundsError(nullptr, "i", "foo", false);
|
c.stlOutOfBoundsError(nullptr, "i", "foo", false);
|
||||||
c.negativeIndexError(nullptr, ValueFlow::Value(-1));
|
c.negativeIndexError(nullptr, ValueFlow::Value(-1));
|
||||||
c.invalidIteratorError(nullptr, "push_back|push_front|insert", "iterator");
|
c.invalidIteratorError(nullptr, "push_back|push_front|insert", "iterator");
|
||||||
|
|
|
@ -137,6 +137,7 @@ private:
|
||||||
TEST_CASE(stabilityOfChecks); // #4684 cppcheck crash in template function call
|
TEST_CASE(stabilityOfChecks); // #4684 cppcheck crash in template function call
|
||||||
|
|
||||||
TEST_CASE(dereferenceInvalidIterator);
|
TEST_CASE(dereferenceInvalidIterator);
|
||||||
|
TEST_CASE(dereferenceInvalidIterator2); // #6572
|
||||||
TEST_CASE(dereference_auto);
|
TEST_CASE(dereference_auto);
|
||||||
|
|
||||||
TEST_CASE(readingEmptyStlContainer);
|
TEST_CASE(readingEmptyStlContainer);
|
||||||
|
@ -1600,7 +1601,7 @@ private:
|
||||||
" iterator i;\n"
|
" iterator i;\n"
|
||||||
" return i.foo();;\n"
|
" return i.foo();;\n"
|
||||||
"}");
|
"}");
|
||||||
ASSERT_EQUALS("[test.cpp:8]: (error) Invalid iterator 'i' used.\n", errout.str());
|
ASSERT_EQUALS("[test.cpp:8]: (error, inconclusive) Invalid iterator 'i' used.\n", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void stlBoundaries6() { // #7106
|
void stlBoundaries6() { // #7106
|
||||||
|
@ -2896,6 +2897,31 @@ private:
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dereferenceInvalidIterator2() {
|
||||||
|
// Self-implemented iterator class
|
||||||
|
check("class iterator {\n"
|
||||||
|
"public:\n"
|
||||||
|
" CCommitPointer m_ptr;\n"
|
||||||
|
" iterator() {}\n"
|
||||||
|
" CCommitPointer& operator*() {\n"
|
||||||
|
" return m_ptr;\n"
|
||||||
|
" }\n"
|
||||||
|
" CCommitPointer* operator->() {\n"
|
||||||
|
" return &m_ptr;\n"
|
||||||
|
" }\n"
|
||||||
|
" iterator& operator++() {\n"
|
||||||
|
" ++m_ptr.m_place;\n"
|
||||||
|
" return *this;\n"
|
||||||
|
" }\n"
|
||||||
|
" }; \n"
|
||||||
|
" iterator begin() {\n"
|
||||||
|
" iterator it; \n"
|
||||||
|
" it->m_place = 0;\n"
|
||||||
|
" return it; \n"
|
||||||
|
"}\n");
|
||||||
|
ASSERT_EQUALS("[test.cpp:18]: (error, inconclusive) Invalid iterator 'it' used.\n", errout.str());
|
||||||
|
}
|
||||||
|
|
||||||
void readingEmptyStlContainer() {
|
void readingEmptyStlContainer() {
|
||||||
check("void f() {\n"
|
check("void f() {\n"
|
||||||
" std::map<int, std::string> CMap;\n"
|
" std::map<int, std::string> CMap;\n"
|
||||||
|
|
Loading…
Reference in New Issue