/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2012 Daniel Marjamäki and Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "checkstl.h" #include "executionpath.h" #include "symboldatabase.h" #include // Register this check class (by creating a static instance of it) namespace { CheckStl instance; } // Error message for bad iterator usage.. void CheckStl::invalidIteratorError(const Token *tok, const std::string &iteratorName) { reportError(tok, Severity::error, "invalidIterator1", "Invalid iterator: " + iteratorName); } void CheckStl::iteratorsError(const Token *tok, const std::string &container1, const std::string &container2) { reportError(tok, Severity::error, "iterators", "Same iterator is used with both " + container1 + " and " + container2); } // Error message used when dereferencing an iterator that has been erased.. void CheckStl::dereferenceErasedError(const Token *tok, const std::string &itername) { reportError(tok, Severity::error, "eraseDereference", "Dereferenced iterator '" + itername + "' has been erased"); } void CheckStl::iterators() { // Using same iterator against different containers. // for (it = foo.begin(); it != bar.end(); ++it) for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { // Locate an iterator.. if (!Token::Match(tok, "%var% = %var% . begin|rbegin|cbegin|crbegin ( ) ;|+") && !(Token::Match(tok, "%var% = %var% . find (") && Token::simpleMatch(tok->linkAt(5), ") ;"))) continue; // Get variable ids for both the iterator and container const unsigned int iteratorId(tok->varId()); const unsigned int containerId(tok->tokAt(2)->varId()); if (iteratorId == 0 || containerId == 0) continue; // check that it's an iterator.. { const Variable *var = _tokenizer->getSymbolDatabase()->getVariableFromVarId(iteratorId); if (!var || !Token::Match(var->nameToken()->previous(), "iterator|const_iterator|reverse_iterator|const_reverse_iterator")) continue; } // the validIterator flag says if the iterator has a valid value or not bool validIterator = true; // When "validatingToken" is reached the validIterator is set to true const Token* validatingToken = 0; // counter for { and } unsigned int indent = 0; // Scan through the rest of the code and see if the iterator is // used against other containers. for (const Token *tok2 = tok->tokAt(7); tok2; tok2 = tok2->next()) { if (tok2 == validatingToken) validIterator = true; // If a { is found then count it and continue if (tok2->str() == "{" && ++indent) continue; // If a } is found then count it. break if indentlevel becomes 0. if (tok2->str() == "}" && --indent == 0) break; // Is iterator compared against different container? if (Token::Match(tok2, "%varid% !=|== %var% . end|rend|cend|crend ( )", iteratorId) && tok2->tokAt(2)->varId() != containerId) { iteratorsError(tok2, tok->strAt(2), tok2->strAt(2)); tok2 = tok2->tokAt(6); } // Is the iterator used in a insert/erase operation? else if (Token::Match(tok2, "%var% . insert|erase ( *| %varid% )|,", iteratorId)) { const Token* itTok = tok2->tokAt(4); if (itTok->str() == "*") { if (tok->strAt(2) == "insert") continue; itTok = itTok->next(); } // It is bad to insert/erase an invalid iterator if (!validIterator) invalidIteratorError(tok2, itTok->str()); // If insert/erase is used on different container then // report an error if (tok2->varId() != containerId) { // skip error message if container is a set.. if (tok2->varId() > 0) { const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); const Variable *variableInfo = symbolDatabase->getVariableFromVarId(tok2->varId()); const Token *decltok = variableInfo ? variableInfo->typeStartToken() : NULL; if (Token::Match(decltok, "const| std :: set")) continue; // No warning } // skip error message if the iterator is erased/inserted by value if (itTok->previous()->str() == "*") continue; // Show error message, mismatching iterator is used. iteratorsError(tok2, tok->strAt(2), tok2->str()); } // invalidate the iterator if it is erased else if (tok2->strAt(2) == std::string("erase")) validIterator = false; // skip the operation tok2 = itTok->next(); } // it = foo.erase(.. // taking the result of an erase is ok else if (Token::Match(tok2, "%varid% = %var% . erase (", iteratorId)) { // the returned iterator is valid validatingToken = tok2->linkAt(5); tok2 = tok2->tokAt(5); } // Reassign the iterator else if (Token::Match(tok2, "%varid% = %var% ;", iteratorId)) { // Assume that the iterator becomes valid. // TODO: add checking that checks if the iterator becomes valid or not validIterator = true; // skip ahead tok2 = tok2->tokAt(3); } // Dereferencing invalid iterator? else if (!validIterator && Token::Match(tok2, "* %varid%", iteratorId)) { dereferenceErasedError(tok2, tok2->strAt(1)); tok2 = tok2->next(); } else if (!validIterator && Token::Match(tok2, "%varid% . %var%", iteratorId)) { dereferenceErasedError(tok2, tok2->str()); tok2 = tok2->tokAt(2); } // bailout handling. Assume that the iterator becomes valid if we see return/break. // TODO: better handling else if (Token::Match(tok2, "return|break")) { validatingToken = Token::findsimplematch(tok2->next(), ";"); } // bailout handling. Assume that the iterator becomes valid if we see else. // TODO: better handling else if (tok2->str() == "else") { validIterator = true; } } } } // Error message for bad iterator usage.. void CheckStl::mismatchingContainersError(const Token *tok) { reportError(tok, Severity::error, "mismatchingContainers", "Iterators of mismatching containers are used together."); } void CheckStl::mismatchingContainers() { static const char* const algorithm2_strings[] = { // func(begin1, end1 "adjacent_find", "all_of", "any_of", "binary_search", "copy", "copy_if", "count", "count_if", "equal", "equal_range", "find", "find_if", "find_if_not", "for_each", "generate", "is_heap", "is_heap_until", "is_partitioned", "is_permutation", "is_sorted", "is_sorted_until", "lower_bound", "make_heap", "max_element", "minmax_element", "min_element", "mismatch", "move", "move_backward", "next_permutation", "none_of", "partition", "partition_copy", "partition_point", "pop_heap", "prev_permutation", "push_heap", "random_shuffle", "remove", "remove_copy", "remove_copy_if", "remove_if", "replace", "replace_copy", "replace_copy_if", "replace_if", "reverse", "reverse_copy", "search_n", "shuffle", "sort", "sort_heap", "stable_partition", "stable_sort", "swap_ranges", "transform", "unique", "unique_copy", "upper_bound" }; static const char* const algorithm22_strings[] = { // func(begin1, end1, begin2, end2 "find_end", "find_first_of", "includes", "lexicographical_compare", "merge", "partial_sort_copy", "search", "set_difference", "set_intersection", "set_symmetric_difference", "set_union" }; static const char* const algorithm1x1_strings[] = { // func(begin1, x, end1 "inplace_merge", "nth_element", "partial_sort", "rotate", "rotate_copy" }; static const std::set algorithm2(algorithm2_strings, &algorithm2_strings[sizeof(algorithm2_strings) / sizeof(*algorithm2_strings)]); static const std::set algorithm22(algorithm22_strings, &algorithm22_strings[sizeof(algorithm22_strings) / sizeof(*algorithm22_strings)]); static const std::set algorithm1x1(algorithm1x1_strings, &algorithm1x1_strings[sizeof(algorithm1x1_strings) / sizeof(*algorithm1x1_strings)]); static const std::string iteratorBeginFuncPattern = "begin|cbegin|rbegin|crbegin"; static const std::string iteratorEndFuncPattern = "end|cend|rend|crend"; static const std::string pattern1x1_1 = "%var% . " + iteratorBeginFuncPattern + " ( ) , "; static const std::string pattern1x1_2 = "%var% . " + iteratorEndFuncPattern + " ( ) ,|)"; static const std::string pattern2 = pattern1x1_1 + pattern1x1_2; // Check if different containers are used in various calls of standard functions for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { if (!Token::Match(tok, "std :: %type% ( !!)")) continue; const Token* arg1 = tok->tokAt(4); // TODO: If iterator variables are used instead then there are false negatives. if (Token::Match(arg1, pattern2.c_str()) && algorithm2.find(tok->strAt(2)) != algorithm2.end()) { if (arg1->str() != arg1->strAt(6)) { mismatchingContainersError(arg1); } } else if (algorithm22.find(tok->strAt(2)) != algorithm22.end()) { if (Token::Match(arg1, pattern2.c_str()) && arg1->str() != arg1->strAt(6)) mismatchingContainersError(arg1); // Find third parameter const Token* arg3 = arg1; for (unsigned int i = 0; i < 2 && arg3; i++) arg3 = arg3->nextArgument(); if (Token::Match(arg3, pattern2.c_str()) && arg3->str() != arg3->strAt(6)) mismatchingContainersError(arg3); } else if (Token::Match(arg1, pattern1x1_1.c_str()) && algorithm1x1.find(tok->strAt(2)) != algorithm1x1.end()) { // Find third parameter const Token *arg3 = arg1->tokAt(6)->nextArgument(); if (Token::Match(arg3, pattern1x1_2.c_str())) { if (arg1->str() != arg3->str()) { mismatchingContainersError(arg1); } } } tok = arg1->linkAt(-1); } } void CheckStl::stlOutOfBounds() { const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase(); // Scan through all tokens.. for (std::list::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) { const Token* const tok = i->classDef; // only interested in "for" loops if (i->type != Scope::eFor || !tok) continue; // check if the for loop condition is wrong for (const Token *tok2 = tok->tokAt(2); tok2 && tok2 != tok->next()->link(); tok2 = tok2->next()) { if (Token::Match(tok2, "; %var% <= %var% . size ( ) ;")) { // variable id for loop variable. unsigned int numId = tok2->next()->varId(); // variable id for the container variable unsigned int varId = tok2->tokAt(3)->varId(); for (const Token *tok3 = tok2->tokAt(8); tok3 && tok3 != i->classEnd; tok3 = tok3->next()) { if (tok3->varId() == varId) { if (Token::simpleMatch(tok3->next(), ". size ( )")) break; else if (Token::Match(tok3->next(), "[ %varid% ]", numId)) stlOutOfBoundsError(tok3, tok3->strAt(2), tok3->str(), false); else if (Token::Match(tok3->next(), ". at ( %varid% )", numId)) stlOutOfBoundsError(tok3, tok3->strAt(4), tok3->str(), true); } } break; } } } } // Error message for bad iterator usage.. void CheckStl::stlOutOfBoundsError(const Token *tok, const std::string &num, const std::string &var, bool at) { if (at) reportError(tok, Severity::error, "stlOutOfBounds", "When " + num + "==" + var + ".size(), " + var + ".at(" + num + ") is out of bounds"); else reportError(tok, Severity::error, "stlOutOfBounds", "When " + num + "==" + var + ".size(), " + var + "[" + num + "] is out of bounds"); } /** * @brief %Check for invalid iterator usage after erase/insert/etc */ class EraseCheckLoop : public ExecutionPath { public: static void checkScope(CheckStl *checkStl, const Token *it) { const Token *tok = it; // Search for the start of the loop body.. int indentlevel = 1; while (indentlevel > 0 && 0 != (tok = tok->next())) { if (tok->str() == "(") tok = tok->link(); else if (tok->str() == ")") break; // reassigning iterator in loop head else if (Token::Match(tok, "%var% =") && tok->str() == it->str()) break; } if (! Token::simpleMatch(tok, ") {")) return; EraseCheckLoop c(checkStl, it->varId()); std::list checks; checks.push_back(c.copy()); ExecutionPath::checkScope(tok->tokAt(2), checks); c.end(checks, tok->link()); while (!checks.empty()) { delete checks.back(); checks.pop_back(); } } private: /** Startup constructor */ EraseCheckLoop(Check *o, unsigned int varid) : ExecutionPath(o, varid), eraseToken(0) { } /** @brief token where iterator is erased (non-zero => the iterator is invalid) */ const Token *eraseToken; /** @brief Copy this check. Called from the ExecutionPath baseclass. */ ExecutionPath *copy() { return new EraseCheckLoop(*this); } /** @brief is another execution path equal? */ bool is_equal(const ExecutionPath *e) const { const EraseCheckLoop *c = static_cast(e); return (eraseToken == c->eraseToken); } /** @brief no implementation => compiler error if used by accident */ void operator=(const EraseCheckLoop &); /** @brief parse tokens */ const Token *parse(const Token &tok, std::list &checks) const { // bail out if there are assignments. We don't check the assignments properly. if (Token::Match(&tok, "[;{}] %var% =") || Token::Match(&tok, "= %var% ;")) { ExecutionPath::bailOutVar(checks, tok.next()->varId()); } // the loop stops here. Bail out all execution checks that reach // this statement if (Token::Match(&tok, "[;{}] break ;")) { ExecutionPath::bailOut(checks); } // erasing iterator => it is invalidated if (Token::Match(&tok, "erase ( ++|--| %var% )")) { // check if there is a "it = ints.erase(it);" pattern. if so // the it is not invalidated. const Token *token = &tok; while (NULL != (token = token ? token->previous() : 0)) { if (Token::Match(token, "[;{}]")) break; else if (token->str() == "=") token = 0; } // the it is invalidated by the erase.. if (token) { // get variable id for the iterator unsigned int iteratorId = 0; if (tok.tokAt(2)->isName()) iteratorId = tok.tokAt(2)->varId(); else iteratorId = tok.tokAt(3)->varId(); // invalidate this iterator in the corresponding checks for (std::list::const_iterator it = checks.begin(); it != checks.end(); ++it) { EraseCheckLoop *c = dynamic_cast(*it); if (c && c->varId == iteratorId) { c->eraseToken = &tok; } } } } // don't skip any tokens. return the token that we received. return &tok; } /** * Parse condition. @sa ExecutionPath::parseCondition * @param tok first token in condition. * @param checks The execution paths. All execution paths in the list are executed in the current scope * @return true => bail out all checking **/ bool parseCondition(const Token &tok, std::list &checks) { // no checking of conditions. (void)tok; (void)checks; return false; } /** @brief going out of scope - all execution paths end */ void end(const std::list &checks, const Token * /*tok*/) const { // check if there are any invalid iterators. If so there is an error. for (std::list::const_iterator it = checks.begin(); it != checks.end(); ++it) { EraseCheckLoop *c = dynamic_cast(*it); if (c && c->eraseToken) { CheckStl *checkStl = dynamic_cast(c->owner); if (checkStl) { checkStl->eraseError(c->eraseToken); } } } } }; void CheckStl::erase() { const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase(); for (std::list::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) { const Token* const tok = i->classDef; if (tok && i->type == Scope::eFor) { for (const Token *tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) { if (tok2->str() == ";") { if (Token::Match(tok2, "; %var% !=")) { // Get declaration token for var.. const Variable *variableInfo = symbolDatabase->getVariableFromVarId(tok2->next()->varId()); const Token *decltok = variableInfo ? variableInfo->typeEndToken() : NULL; // Is variable an iterator? bool isIterator = false; if (decltok && Token::Match(decltok->tokAt(-2), "> :: iterator %varid%", tok2->next()->varId())) isIterator = true; // If tok2->next() is an iterator, check scope if (isIterator) EraseCheckLoop::checkScope(this, tok2->next()); } break; } if (Token::Match(tok2, "%var% = %var% . begin|rbegin|cbegin|crbegin ( ) ; %var% != %var% . end|rend|cend|crend ( )") && tok2->str() == tok2->strAt(8) && tok2->strAt(2) == tok2->strAt(10)) { EraseCheckLoop::checkScope(this, tok2); break; } } } else if (i->type == Scope::eWhile && Token::Match(tok, "while ( %var% !=")) { const unsigned int varid = tok->tokAt(2)->varId(); if (varid > 0 && Token::findmatch(_tokenizer->tokens(), "> :: iterator %varid%", varid)) EraseCheckLoop::checkScope(this, tok->tokAt(2)); } } } // Error message for bad iterator usage.. void CheckStl::eraseError(const Token *tok) { reportError(tok, Severity::error, "erase", "Dangerous iterator usage after erase()-method.\n" "The iterator is invalid after it has been used in erase() function. " "Dereferencing or comparing it with another iterator is invalid operation."); } void CheckStl::pushback() { // Pointer can become invalid after push_back, push_front, reserve or resize.. for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { if (Token::Match(tok, "%var% = & %var% [")) { // Variable id for pointer const unsigned int pointerId(tok->varId()); // Variable id for the container variable const unsigned int containerId(tok->tokAt(3)->varId()); if (pointerId == 0 || containerId == 0) continue; // Count { , } and parentheses for tok2 int indent = 0; bool invalidPointer = false; for (const Token *tok2 = tok; indent >= 0 && tok2; tok2 = tok2->next()) { if (tok2->str() == "{" || tok2->str() == "(") ++indent; else if (tok2->str() == "}" || tok2->str() == ")") { if (indent == 0 && Token::simpleMatch(tok2, ") {")) tok2 = tok2->next(); else --indent; } // push_back on vector.. if (Token::Match(tok2, "%varid% . push_front|push_back|resize|reserve", containerId)) invalidPointer = true; // Using invalid pointer.. if (invalidPointer && tok2->varId() == pointerId) { if (tok2->previous()->str() == "*") invalidPointerError(tok2, tok2->str()); else if (tok2->next()->str() == ".") invalidPointerError(tok2, tok2->str()); break; } } } } // Iterator becomes invalid after reserve, resize, insert, push_back or push_front.. for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { if (!Token::simpleMatch(tok, "vector <")) continue; // if iterator declaration inside for() loop bool iteratorDeclaredInsideLoop = false; if ((tok->tokAt(-2) && Token::simpleMatch(tok->tokAt(-2), "for (")) || (tok->tokAt(-4) && Token::simpleMatch(tok->tokAt(-4), "for ( std ::"))) { iteratorDeclaredInsideLoop = true; } while (tok && tok->str() != ">") tok = tok->next(); if (!tok) 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 unsigned int vectorid = 0; // count { , } and parentheses for tok2 int indent = 0; std::string invalidIterator; for (const Token *tok2 = tok; indent >= 0 && tok2; tok2 = tok2->next()) { if (tok2->str() == "{" || tok2->str() == "(") ++indent; else if (tok2->str() == "}" || tok2->str() == ")") { if (indent == 0 && Token::simpleMatch(tok2, ") {")) tok2 = tok2->next(); else --indent; } // Using push_back or push_front inside a loop.. if (Token::simpleMatch(tok2, "for (")) { tok2 = tok2->tokAt(2); } 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 = 0; // Count { and } for tok3 unsigned int indent3 = 0; for (const Token *tok3 = tok2->tokAt(20); tok3; tok3 = tok3->next()) { if (tok3->str() == "{") ++indent3; else if (tok3->str() == "}") { if (indent3 <= 1) break; --indent3; } else if (tok3->str() == "break" || tok3->str() == "return") { pushbackTok = 0; break; } else if (Token::Match(tok3, "%varid% . push_front|push_back|insert|reserve|resize (", varId)) { pushbackTok = tok3->tokAt(2); } } if (pushbackTok) invalidIteratorError(pushbackTok, pushbackTok->str(), tok2->str()); } // Assigning iterator.. if (Token::Match(tok2, "%varid% =", iteratorid)) { if (Token::Match(tok2->tokAt(2), "%var% . begin|end|rbegin|rend|cbegin|cend|crbegin|crend ( )")) { vectorid = tok2->tokAt(2)->varId(); tok2 = tok2->tokAt(6); } else { vectorid = 0; } invalidIterator = ""; } // push_back on vector.. if (vectorid > 0 && Token::Match(tok2, "%varid% . push_front|push_back|insert|reserve|resize (", vectorid)) { if (!invalidIterator.empty() && Token::Match(tok2->tokAt(2), "insert ( %varid% ,", iteratorid)) { invalidIteratorError(tok2, invalidIterator, tok2->strAt(4)); break; } invalidIterator = tok2->strAt(2); tok2 = tok2->linkAt(3); } // TODO: instead of bail out for 'else' try to check all execution paths. else if (tok2->str() == "return" || tok2->str() == "break" || tok2->str() == "else") { invalidIterator.clear(); } // Using invalid iterator.. if (!invalidIterator.empty()) { if (Token::Match(tok2, "++|--|*|+|-|(|,|=|!= %varid%", iteratorid)) invalidIteratorError(tok2, invalidIterator, tok2->strAt(1)); if (Token::Match(tok2, "%varid% ++|--|+|-", iteratorid)) invalidIteratorError(tok2, invalidIterator, tok2->str()); } } } } // Error message for bad iterator usage.. void CheckStl::invalidIteratorError(const Token *tok, const std::string &func, const std::string &iterator_name) { reportError(tok, Severity::error, "invalidIterator2", "After " + func + ", the iterator '" + iterator_name + "' may be invalid"); } // Error message for bad iterator usage.. void CheckStl::invalidPointerError(const Token *tok, const std::string &pointer_name) { reportError(tok, Severity::error, "invalidPointer", "Invalid pointer '" + pointer_name + "' after push_back / push_front"); } void CheckStl::stlBoundries() { // containers (not the vector).. static const char STL_CONTAINER_LIST[] = "bitset|deque|list|forward_list|map|multimap|multiset|priority_queue|queue|set|stack|hash_map|hash_multimap|hash_set|unordered_map|unordered_multimap|unordered_set|unordered_multiset"; for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { // Declaring iterator.. if (tok->str() == "<" && Token::Match(tok->previous(), STL_CONTAINER_LIST)) { const std::string container_name(tok->strAt(-1)); while (tok && tok->str() != ">") tok = tok->next(); if (!tok) break; if (Token::Match(tok, "> :: iterator|const_iterator %var% =|;")) { const unsigned int iteratorid(tok->tokAt(3)->varId()); if (iteratorid == 0) continue; // Using "iterator < ..." is not allowed unsigned int indentlevel = 0; for (const Token *tok2 = tok; tok2; tok2 = tok2->next()) { if (tok2->str() == "{") ++indentlevel; else if (tok2->str() == "}") { if (indentlevel == 0) break; --indentlevel; } else if (Token::Match(tok2, "!!* %varid% <", iteratorid)) { stlBoundriesError(tok2, container_name); } else if (Token::Match(tok2, "> %varid%", iteratorid)) { stlBoundriesError(tok2, container_name); } } } } } } // Error message for bad boundry usage.. void CheckStl::stlBoundriesError(const Token *tok, const std::string &container_name) { reportError(tok, Severity::error, "stlBoundries", "Dangerous container iterator compare using < operator for " + container_name + "\n" "Container '" + container_name + "' iterator compared with < operator. " "Using < operator with container type iterators is dangerous since the order of " "the items is not guaranteed. One should use != operator instead when comparing " "iterators in the container."); } void CheckStl::if_find() { if (!_settings->isEnabled("style")) return; const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); for (std::list::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) { if ((i->type != Scope::eIf && i->type != Scope::eElseIf && i->type != Scope::eWhile) || !i->classDef) continue; const Token* tok = i->classDef->next(); if (tok->str() == "if") tok = tok->next(); for (const Token* const end = tok->link(); tok != end; tok = (tok == end) ? end : tok->next()) { if (Token::Match(tok, "&&|(|%oror%")) tok = tok->next(); else continue; while (tok->str() == "(") tok = tok->next(); if (tok->str() == "!") tok = tok->next(); if (Token::Match(tok, "%var% . find (")) { if (!Token::Match(tok->linkAt(3), ") &&|)|%oror%")) continue; const unsigned int varid = tok->varId(); const Variable *var = symbolDatabase->getVariableFromVarId(varid); if (var) { // Is the variable a std::string or STL container? const Token * decl = var->typeStartToken(); if (decl->str() == "const") decl = decl->next(); // stl container if (Token::Match(decl, "std :: %var% < %type% > &| %varid%", varid)) if_findError(tok, false); else if (Token::Match(decl, "std :: string &| %varid%", varid)) if_findError(tok, true); } } //check also for vector-like or pointer containers else if (Token::Match(tok, "* %var%") || Token::Match(tok, "%var% [")) { // goto %var% if (tok->str() == "*") tok = tok->next(); const Token *tok2 = tok->next(); if (tok2->str() == "[") tok2 = tok2->link()->next(); if (!Token::simpleMatch(tok2, ". find (")) continue; if (!Token::Match(tok2->linkAt(2), ") &&|)|%oror%")) continue; const unsigned int varid = tok->varId(); const Variable *var = symbolDatabase->getVariableFromVarId(varid); if (var) { // Is the variable a std::string or STL container? const Token * decl = var->typeStartToken(); //jump next to 'const' if (decl->str() == "const") decl = decl->next(); //pretty bad limitation.. but it is there in order to avoid //own implementations of 'find' or any container if (!Token::simpleMatch(decl, "std ::")) continue; decl = decl->tokAt(2); if (Token::Match(decl, "%var% <")) { decl = decl->tokAt(2); //stl-like if (Token::Match(decl, "std :: %var% < %type% > > &| %varid%", varid)) if_findError(tok, false); //not stl-like, then let's hope it's a pointer or an array else if (Token::Match(decl, "%type% >")) { decl = decl->tokAt(2); if (Token::Match(decl, "* &| %varid%", varid) || Token::Match(decl, "&| %varid% [ ]| %any% ]| ", varid)) if_findError(tok, false); } else if (Token::Match(decl, "std :: string > &| %varid%", varid)) if_findError(tok, true); } else if (decl && decl->str() == "string") { decl = decl->next(); if (Token::Match(decl, "* &| %varid%", varid) || Token::Match(decl, "&| %varid% [ ]| %any% ]| ", varid)) if_findError(tok, true); } } } else if (Token::Match(tok, "std :: find|find_if (")) { // check that result is checked properly if (Token::Match(tok->linkAt(3), ") &&|)|%oror%")) { if_findError(tok, false); } } } } } void CheckStl::if_findError(const Token *tok, bool str) { if (str) reportError(tok, Severity::performance, "stlIfStrFind", "Inefficient usage of string::find in condition; string::compare would be faster.\n" "Either inefficent or wrong usage of string::find. string::compare will be faster if " "string::find's result is compared with 0, because it will not scan the whole " "string. If your intention is to check that there are no findings in the string, " "you should compare with string::npos."); else reportError(tok, Severity::warning, "stlIfFind", "Suspicious condition. The result of find is an iterator, but it is not properly checked."); } bool CheckStl::isStlContainer(unsigned int varid) { // check if this token is defined if (varid) { // find where this token is defined const Variable *var = _tokenizer->getSymbolDatabase()->getVariableFromVarId(varid); if (!var) return false; // find where this tokens type starts const Token *type = var->typeStartToken(); // ignore "const" if (type->str() == "const") type = type->next(); // discard namespace if supplied if (Token::simpleMatch(type, "std ::")) type = type->tokAt(2); // all possible stl containers as a token static const char STL_CONTAINER_LIST[] = "array|bitset|deque|list|forward_list|map|multimap|multiset|priority_queue|queue|set|stack|vector|hash_map|hash_multimap|hash_set|unordered_map|unordered_multimap|unordered_set|unordered_multiset|basic_string"; // check if it's an stl template if (Token::Match(type, STL_CONTAINER_LIST)) return true; } return false; } void CheckStl::size() { if (!_settings->isEnabled("performance")) return; for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { if (Token::Match(tok, "%var% . size ( )") || Token::Match(tok, "%var% . %var% . size ( )")) { const Token *tok1 = tok; unsigned int varid = 0; // get the variable id if (tok->strAt(2) != "size") { tok1 = tok1->tokAt(2); // found a.b.size(), lookup class/struct variable const Variable *var = _tokenizer->getSymbolDatabase()->getVariableFromVarId(tok->varId()); if (var && var->type()) { // get class/struct variable type const Scope *type = var->type(); // lookup variable member std::list::const_iterator it; for (it = type->varlist.begin(); it != type->varlist.end(); ++it) { if (it->name() == tok1->str()) { // found member variable, save varid varid = it->varId(); break; } } } } else varid = tok1->varId(); const Token* const end = tok1->tokAt(5); if (varid) { // check for comparison to zero if (Token::Match(end, "==|<=|!=|> 0") || Token::Match(tok->tokAt(-2), "0 ==|>=|!=|<")) { if (isStlContainer(varid)) sizeError(tok1); } // check for using as boolean expression else if (tok->strAt(-1) == "!" || (Token::Match(tok->tokAt(-2), "if|while (") && end->str() == ")") || (Token::Match(tok->previous(), "&&|%oror%") && Token::Match(end, "&&|)|%oror%"))) { if (isStlContainer(varid)) sizeError(tok1); } } } } } void CheckStl::sizeError(const Token *tok) { const std::string varname = tok ? tok->str() : std::string("list"); reportError(tok, Severity::performance, "stlSize", "Possible inefficient checking for '" + varname + "' emptiness.\n" "Checking for '" + varname + "' emptiness might be inefficient. " "Using " + varname + ".empty() instead of " + varname + ".size() can be faster. " + varname + ".size() can take linear time but " + varname + ".empty() is " "guaranteed to take constant time."); } void CheckStl::redundantCondition() { const char pattern[] = "if ( %var% . find ( %any% ) != %var% . end|rend|cend|crend ( ) ) " "{" " %var% . remove ( %any% ) ;"; const Token *tok = Token::findmatch(_tokenizer->tokens(), pattern); while (tok) { // Get tokens for the fields %var% and %any% const Token *var1 = tok->tokAt(2); const Token *any1 = var1->tokAt(4); const Token *var2 = any1->tokAt(3); const Token *var3 = var2->tokAt(7); const Token *any2 = var3->tokAt(4); // Check if all the "%var%" fields are the same and if all the "%any%" are the same.. if (var1->str() == var2->str() && var2->str() == var3->str() && any1->str() == any2->str()) { redundantIfRemoveError(tok); } tok = Token::findmatch(tok->next(), pattern); } } void CheckStl::redundantIfRemoveError(const Token *tok) { reportError(tok, Severity::style, "redundantIfRemove", "Redundant checking of STL container element.\n" "Redundant checking of STL container element existence before removing it. " "The remove method in the STL will not do anything if element doesn't exist"); } void CheckStl::missingComparison() { const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase(); for (std::list::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) { if (i->type != Scope::eFor || !i->classDef) continue; for (const Token *tok2 = i->classDef->tokAt(2); tok2 != i->classStart; tok2 = tok2->next()) { if (tok2->str() == ";") break; if (!Token::Match(tok2, "%var% = %var% . begin|rbegin|cbegin|crbegin ( ) ; %var% != %var% . end|rend|cend|crend ( ) ; ++| %var% ++| ) {")) continue; // same container if (tok2->strAt(2) != tok2->strAt(10)) break; const unsigned int iteratorId(tok2->varId()); if (iteratorId == 0) break; // same iterator if (iteratorId == tok2->tokAt(10)->varId()) break; // increment iterator if (!Token::Match(tok2->tokAt(16), "++ %varid% )", iteratorId) && !Token::Match(tok2->tokAt(16), "%varid% ++ )", iteratorId)) { break; } const Token *incrementToken = 0; // Parse loop.. for (const Token *tok3 = i->classStart; tok3 != i->classEnd; tok3 = tok3->next()) { if (Token::Match(tok3, "%varid% ++", iteratorId)) incrementToken = tok3; else if (Token::Match(tok3->previous(), "++ %varid% !!.", iteratorId)) incrementToken = tok3; else if (Token::Match(tok3, "%varid% !=|==", iteratorId)) incrementToken = 0; else if (tok3->str() == "break" || tok3->str() == "return") incrementToken = 0; else if (Token::Match(tok3, "%varid% = %var% . insert ( ++| %varid% ++| ,", iteratorId)) { // skip insertion.. tok3 = tok3->linkAt(6); if (!tok3) break; } } if (incrementToken) missingComparisonError(incrementToken, tok2->tokAt(16)); } } } void CheckStl::missingComparisonError(const Token *incrementToken1, const Token *incrementToken2) { std::ostringstream errmsg; errmsg << "Missing bounds check for extra iterator increment in loop.\n" << "The iterator incrementing is suspicious - it is incremented at line " << incrementToken1->linenr() << " and then at line " << incrementToken2->linenr() << " The loop might unintentionally skip an element in the container. " << "There is no comparison between these increments to prevent that the iterator is " << "incremented beyond the end."; reportError(incrementToken1, Severity::warning, "StlMissingComparison", errmsg.str()); } static bool isLocal(const SymbolDatabase* symbolDatabase, unsigned int varid) { const Variable* var = symbolDatabase->getVariableFromVarId(varid); return var && !var->isStatic() && var->isLocal(); } void CheckStl::string_c_str() { if (!_tokenizer->isCPP()) return; const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase(); // Find all functions that take std::string as argument std::multimap c_strFuncParam; if (_settings->isEnabled("performance")) { for (std::list::const_iterator scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) { for (std::list::const_iterator func = scope->functionList.begin(); func != scope->functionList.end(); ++func) { if (c_strFuncParam.erase(func->tokenDef->str()) != 0) { // Check if function with this name was already found c_strFuncParam.insert(std::make_pair(func->tokenDef->str(), 0)); // Disable, because there are overloads. TODO: Handle overloads continue; } unsigned int numpar = 0; c_strFuncParam.insert(std::make_pair(func->tokenDef->str(), numpar)); // Insert function as dummy, to indicate that there is at least one function with that name for (const Token* tok = func->argDef->next(); tok != 0; tok = tok->nextArgument()) { numpar++; if (Token::Match(tok, "std :: string !!&") || Token::simpleMatch(tok, "const std :: string")) c_strFuncParam.insert(std::make_pair(func->tokenDef->str(), numpar)); } } } } // Try to detect common problems when using string::c_str() for (std::list::const_iterator scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) { if (scope->type != Scope::eFunction || !scope->function) continue; enum {charPtr, stdString, stdStringConstRef, Other} returnType = Other; if (Token::simpleMatch(scope->function->tokenDef->tokAt(-2), "char *")) returnType = charPtr; else if (Token::simpleMatch(scope->function->tokenDef->tokAt(-5), "const std :: string &")) returnType = stdStringConstRef; else if (Token::Match(scope->function->tokenDef->tokAt(-3), "std :: string !!&")) returnType = stdString; for (const Token *tok = scope->function->start; tok && tok != scope->function->start->link(); tok = tok->next()) { // Invalid usage.. if (Token::Match(tok, "throw %var% . c_str ( ) ;") && isLocal(symbolDatabase, tok->next()->varId())) { string_c_strThrowError(tok); } else if (Token::Match(tok, "[;{}] %var% = %var% . str ( ) . c_str ( ) ;")) { const Variable* var = symbolDatabase->getVariableFromVarId(tok->next()->varId()); if (var && var->isPointer()) string_c_strError(tok); } else if (Token::Match(tok, "[;{}] %var% = %var% (") && Token::simpleMatch(tok->linkAt(4), ") . c_str ( ) ;") && Token::findmatch(_tokenizer->tokens(), ("std :: string " + tok->strAt(3) + " (").c_str())) { const Variable* var = symbolDatabase->getVariableFromVarId(tok->next()->varId()); if (var && var->isPointer()) string_c_strError(tok); } else if (Token::Match(tok, "%var% ( !!)") && c_strFuncParam.find(tok->str()) != c_strFuncParam.end() && _settings->isEnabled("performance") && !Token::Match(tok->previous(), "::|.") && tok->varId() == 0) { // calling function. TODO: Add support for member functions std::pair::const_iterator, std::multimap::const_iterator> range = c_strFuncParam.equal_range(tok->str()); for (std::multimap::const_iterator i = range.first; i != range.second; ++i) { if (i->second == 0) continue; const Token* tok2 = tok->tokAt(2); unsigned int j; for (j = 0; tok2 && j < i->second-1; j++) tok2 = tok2->nextArgument(); if (tok2) tok2 = tok2->nextArgument(); else break; if (!tok2 && j == i->second-1) tok2 = tok->next()->link(); else tok2 = tok2->previous(); if (tok2 && Token::simpleMatch(tok2->tokAt(-4), ". c_str ( )")) string_c_strParam(tok, i->second); } } // Using c_str() to get the return value is only dangerous if the function returns a char* if (returnType == charPtr) { if (Token::Match(tok, "return %var% . c_str ( ) ;") && isLocal(symbolDatabase, tok->next()->varId())) { string_c_strError(tok); } else if (Token::Match(tok, "return %var% . str ( ) . c_str ( ) ;") && isLocal(symbolDatabase, tok->next()->varId())) { string_c_strError(tok); } else if (Token::simpleMatch(tok, "return std :: string (") && Token::simpleMatch(tok->linkAt(4), ") . c_str ( ) ;")) { string_c_strError(tok); } else if (Token::simpleMatch(tok, "return (") && Token::simpleMatch(tok->next()->link(), ") . c_str ( ) ;")) { // Check for "+ localvar" or "+ std::string(" inside the bracket bool is_implicit_std_string = _settings->inconclusive; const Token *search_end = tok->next()->link(); for (const Token *search_tok = tok->tokAt(2); search_tok != search_end; search_tok = search_tok->next()) { if (Token::Match(search_tok, "+ %var%") && isLocal(symbolDatabase, search_tok->next()->varId())) { is_implicit_std_string = true; break; } else if (Token::simpleMatch(search_tok, "+ std :: string (")) { is_implicit_std_string = true; break; } } if (is_implicit_std_string) string_c_strError(tok); } } // Using c_str() to get the return value is redundant if the function returns std::string or const std::string&. else if ((returnType == stdString || returnType == stdStringConstRef) && _settings->isEnabled("performance")) { if (tok->str() == "return") { const Token* tok2 = Token::findsimplematch(tok->next(), ";"); if (Token::simpleMatch(tok2->tokAt(-4), ". c_str ( )")) string_c_strReturn(tok); } } } } } void CheckStl::string_c_strThrowError(const Token* tok) { reportError(tok, Severity::error, "stlcstrthrow", "Dangerous usage of c_str(). The returned value by c_str() is invalid after throw call.\n" "Dangerous usage of c_str(). The string is destroyed after the c_str() call so the thrown pointer is invalid."); } void CheckStl::string_c_strError(const Token* tok) { reportError(tok, Severity::error, "stlcstr", "Dangerous usage of c_str(). The returned value by c_str() is invalid after this call.\n" "Dangerous usage of c_str(). The c_str() return value is only valid until its string is deleted."); } void CheckStl::string_c_strReturn(const Token* tok) { reportError(tok, Severity::performance, "stlcstrReturn", "Returning the result of c_str() in a function that returns std::string is slow and redundant.\n" "The conversion from const char* as returned by c_str to std::string creates an unnecessary string copy. Solve that by directly returning the string."); } void CheckStl::string_c_strParam(const Token* tok, unsigned int number) { std::ostringstream oss; oss << "Passing the result of c_str() to a function that takes std::string as argument " << number << " is slow and redundant.\n" "The conversion from const char* as returned by c_str to std::string creates an unnecessary string copy. Solve that by directly passing the string."; reportError(tok, Severity::performance, "stlcstrParam", oss.str()); } static bool hasArrayEnd(const Token *tok1) { const Token *end = Token::findsimplematch(tok1, ";"); return (end && Token::simpleMatch(end->previous(), "] ;")); } static bool hasArrayEndParen(const Token *tok1) { const Token *end = Token::findsimplematch(tok1, ";"); return (end && end->previous() && Token::simpleMatch(end->tokAt(-2), "] ) ;")); } //--------------------------------------------------------------------------- // //--------------------------------------------------------------------------- void CheckStl::checkAutoPointer() { if (!_tokenizer->isCPP()) return; std::set autoPtrVarId; static const char STL_CONTAINER_LIST[] = "array|bitset|deque|list|forward_list|map|multimap|multiset|priority_queue|queue|set|stack|vector|hash_map|hash_multimap|hash_set|unordered_map|unordered_multimap|unordered_set|unordered_multiset|basic_string"; for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { if (Token::simpleMatch(tok, "auto_ptr <")) { if ((tok->previous() && tok->previous()->str() == "<" && Token::Match(tok->tokAt(-2), STL_CONTAINER_LIST)) || (Token::simpleMatch(tok->tokAt(-3), "< std :: auto_ptr") && Token::Match(tok->tokAt(-4), STL_CONTAINER_LIST))) { autoPointerContainerError(tok); } else { const Token *tok2 = tok->tokAt(2); while (tok2) { if (Token::Match(tok2, "> %var%")) { const Token *tok3 = tok2->tokAt(2); if (Token::Match(tok3, "( new %type%") && hasArrayEndParen(tok3)) { autoPointerArrayError(tok2->next()); break; } while (tok3 && tok3->str() != ";") { tok3 = tok3->next(); } if (tok3) { tok3 = tok3->tokAt(-2); if (Token::simpleMatch(tok3->previous(), "[ ] )")) { autoPointerArrayError(tok2->next()); } else if (tok3->varId()) { const Token *decltok = Token::findmatch(_tokenizer->tokens(), "%varid% = new %type%", tok3->varId()); if (decltok && hasArrayEnd(decltok)) { autoPointerArrayError(tok2->next()); } } if (tok2->next()->varId()) { autoPtrVarId.insert(tok2->next()->varId()); } break; } } tok2 = tok2->next(); } } } else { if (Token::Match(tok, "%var% = %var% ;")) { if (_settings->isEnabled("style")) { std::set::const_iterator iter = autoPtrVarId.find(tok->tokAt(2)->varId()); if (iter != autoPtrVarId.end()) { autoPointerError(tok->tokAt(2)); } } } else if ((Token::Match(tok, "%var% = new %type% ") && hasArrayEnd(tok)) || (Token::Match(tok, "%var% . reset ( new %type% ") && hasArrayEndParen(tok))) { std::set::const_iterator iter = autoPtrVarId.find(tok->varId()); if (iter != autoPtrVarId.end()) { autoPointerArrayError(tok); } } } } } void CheckStl::autoPointerError(const Token *tok) { reportError(tok, Severity::style, "useAutoPointerCopy", "Copy 'auto_ptr' pointer to another do not create two equal objects since one has lost its ownership of the pointer.\n" "The auto_ptr has semantics of strict ownership, meaning that the auto_ptr instance is the sole entity responsible for the object's lifetime. If an auto_ptr is copied, the source loses the reference." ); } void CheckStl::autoPointerContainerError(const Token *tok) { reportError(tok, Severity::error, "useAutoPointerContainer", "You can randomly lose access to pointers if you store 'auto_ptr' pointers in a container because the copy-semantics of 'auto_ptr' are not compatible with containers.\n" "An element of container must be able to be copied but 'auto_ptr' does not fulfill this requirement. You should consider to use 'shared_ptr' or 'unique_ptr'. It is suitable for use in containers, because they no longer copy their values, they move them." ); } void CheckStl::autoPointerArrayError(const Token *tok) { reportError(tok, Severity::error, "useAutoPointerArray", "Object pointed by an 'auto_ptr' is destroyed using operator 'delete'. You should not use 'auto_ptr' for pointers obtained with operator 'new[]'.\n" "Object pointed by an 'auto_ptr' is destroyed using operator 'delete'. This means that you should only use 'auto_ptr' for pointers obtained with operator 'new'. This excludes arrays, which are allocated by operator 'new[]' and must be deallocated by operator 'delete[]'." ); } void CheckStl::uselessCalls() { for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { if (tok->varId() && Token::Match(tok, "%var% . compare|find|rfind|find_first_not_of|find_first_of|find_last_not_of|find_last_of ( %var% [,)]") && tok->varId() == tok->tokAt(4)->varId()) { uselessCallsReturnValueError(tok->tokAt(4), tok->str(), tok->strAt(2)); } else if (tok->varId() && Token::Match(tok, "%var% . swap ( %var% )") && tok->varId() == tok->tokAt(4)->varId()) { uselessCallsSwapError(tok, tok->str()); } else if (Token::Match(tok, ". substr (")) { if (Token::Match(tok->tokAt(3), "0| )")) uselessCallsSubstrError(tok, false); else if (tok->strAt(3) == "0" && tok->linkAt(2)->strAt(-1) == "npos") uselessCallsSubstrError(tok, false); else if (Token::Match(tok->linkAt(2)->tokAt(-2), ", 0 )")) uselessCallsSubstrError(tok, true); } } } void CheckStl::uselessCallsReturnValueError(const Token *tok, const std::string &varname, const std::string &function) { std::ostringstream errmsg; errmsg << "It is inefficient to call '" << varname << "." << function << "(" << varname << ")' as it always returns 0.\n" << "The 'std::string::" << function << "()' returns zero when given itself as parameter " << "(" << varname << "." << function << "(" << varname << ")). As it is currently the " << "code is inefficient. It is also possible either the string searched ('" << varname << "') or searched for ('" << varname << "') is wrong/mixed in the code?"; reportError(tok, Severity::warning, "uselessCallsCompare", errmsg.str()); } void CheckStl::uselessCallsSwapError(const Token *tok, const std::string &varname) { std::ostringstream errmsg; errmsg << "It is inefficient to swap a object with itself by calling '" << varname << ".swap(" << varname << ")'\n" << "The 'swap()' function has no logical effect when given itself as parameter " << "(" << varname << ".swap(" << varname << ")). As it is currently the " << "code is inefficient. It is possible either the object or the parameter is wrong/mixed in the code?"; reportError(tok, Severity::performance, "uselessCallsSwap", errmsg.str()); } void CheckStl::uselessCallsSubstrError(const Token *tok, bool empty) { if (empty) reportError(tok, Severity::performance, "uselessCallsSubstr", "Useless call of function 'substr' because it returns an empty string."); else reportError(tok, Severity::performance, "uselessCallsSubstr", "Useless call of function 'substr' because it returns a copy of the object. Use operator= instead."); }