/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2015 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 "checkuninitvar.h" #include "mathlib.h" #include "executionpath.h" #include "checknullpointer.h" // CheckNullPointer::parseFunctionCall #include "symboldatabase.h" #include #include #include #include //--------------------------------------------------------------------------- // Register this check class (by creating a static instance of it) namespace { CheckUninitVar instance; } //--------------------------------------------------------------------------- // Skip [ .. ] static const Token * skipBrackets(const Token *tok) { while (tok && tok->str() == "[") tok = tok->link()->next(); return tok; } /// @addtogroup Checks /// @{ /** * @brief %Check that uninitialized variables aren't used (using ExecutionPath) * */ class UninitVar : public ExecutionPath { public: /** Startup constructor */ explicit UninitVar(Check *c, const SymbolDatabase* db, const Library *lib, bool isc) : ExecutionPath(c, 0), symbolDatabase(db), library(lib), isC(isc), var(0), alloc(false), strncpy_(false), memset_nonzero(false) { } private: /** Create a copy of this check */ ExecutionPath *copy() { return new UninitVar(*this); } /** internal constructor for creating extra checks */ UninitVar(Check *c, const Variable* v, const SymbolDatabase* db, const Library *lib, bool isc) : ExecutionPath(c, v->declarationId()), symbolDatabase(db), library(lib), isC(isc), var(v), alloc(false), strncpy_(false), memset_nonzero(false) { } /** is other execution path equal? */ bool is_equal(const ExecutionPath *e) const { const UninitVar *c = static_cast(e); return (var == c->var && alloc == c->alloc && strncpy_ == c->strncpy_ && memset_nonzero == c->memset_nonzero); } /** pointer to symbol database */ const SymbolDatabase* symbolDatabase; /** pointer to library */ const Library *library; const bool isC; /** variable for this check */ const Variable* var; /** is this variable allocated? */ bool alloc; /** is this variable initialized with strncpy (not always zero-terminated) */ bool strncpy_; /** is this variable initialized but not zero-terminated (memset) */ bool memset_nonzero; /** allocating pointer. For example : p = malloc(10); */ static void alloc_pointer(std::list &checks, unsigned int varid) { // loop through the checks and perform a allocation if the // variable id matches std::list::const_iterator it; for (it = checks.begin(); it != checks.end(); ++it) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == varid) { if (c->var->isPointer() && !c->var->isArray()) c->alloc = true; else bailOutVar(checks, varid); break; } } } /** Initializing a pointer value. For example: *p = 0; */ static void init_pointer(std::list &checks, const Token *tok) { const unsigned int varid(tok->varId()); if (!varid) return; // loop through the checks and perform a initialization if the // variable id matches std::list::iterator it = checks.begin(); while (it != checks.end()) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == varid) { if (c->alloc || c->var->isArray()) { delete c; checks.erase(it++); continue; } else { use_pointer(checks, tok); } } ++it; } } /** Deallocate a pointer. For example: free(p); */ static void dealloc_pointer(std::list &checks, const Token *tok) { const unsigned int varid(tok->varId()); if (!varid) return; // loop through the checks and perform a deallocation if the // variable id matches std::list::const_iterator it; for (it = checks.begin(); it != checks.end(); ++it) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == varid) { // unallocated pointer variable => error if (c->var->isPointer() && !c->var->isArray() && !c->alloc) { CheckUninitVar *checkUninitVar = dynamic_cast(c->owner); if (checkUninitVar) { checkUninitVar->uninitvarError(tok, c->var->name()); break; } } c->alloc = false; } } } /** * Pointer assignment: p = x; * if p is a pointer and x is an array/pointer then bail out * \param checks all available checks * \param tok1 the "p" token * \param tok2 the "x" token */ static void pointer_assignment(std::list &checks, const Token *tok1, const Token *tok2) { // Variable id for "left hand side" variable const unsigned int varid1(tok1->varId()); if (varid1 == 0) return; // Variable id for "right hand side" variable const unsigned int varid2(tok2->varId()); if (varid2 == 0) return; std::list::const_iterator it; // bail out if first variable is a pointer for (it = checks.begin(); it != checks.end(); ++it) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == varid1 && c->var->isPointer() && !c->var->isArray()) { bailOutVar(checks, varid1); break; } } // bail out if second variable is a array/pointer for (it = checks.begin(); it != checks.end(); ++it) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == varid2 && (c->var->isPointer() || c->var->isArray())) { bailOutVar(checks, varid2); break; } } } /** Initialize an array with strncpy. */ static void init_strncpy(std::list &checks, const Token *tok) { const unsigned int varid(tok->varId()); if (!varid) return; std::list::const_iterator it; for (it = checks.begin(); it != checks.end(); ++it) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == varid) { c->strncpy_ = true; } } } /** Initialize an array with memset (not zero). */ static void init_memset_nonzero(std::list &checks, const Token *tok) { const unsigned int varid(tok->varId()); if (!varid) return; std::list::const_iterator it; for (it = checks.begin(); it != checks.end(); ++it) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == varid) { c->memset_nonzero = true; } } } /** * use - called from the use* functions below. * @param checks all available checks * @param tok variable token * @param mode specific behaviour * @return if error is found, true is returned */ static bool use(std::list &checks, const Token *tok, const int mode) { const unsigned int varid(tok->varId()); if (varid == 0) return false; std::list::const_iterator it; for (it = checks.begin(); it != checks.end(); ++it) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == varid) { // mode 0 : the variable is used "directly" // example: .. = var; // it is ok to read the address of an uninitialized array. // it is ok to read the address of an allocated pointer if (mode == 0 && (c->var->isArray() || (c->var->isPointer() && c->alloc))) continue; // mode 2 : reading array data with mem.. function. It's ok if the // array is not null-terminated if (mode == 2 && c->strncpy_) continue; // mode 3 : bad usage of pointer. if it's not a pointer then the usage is ok. // example: ptr->foo(); if (mode == 3 && (!c->var->isPointer() || c->var->isArray())) continue; // mode 4 : using dead pointer is invalid. if (mode == 4 && (!c->var->isPointer() || c->var->isArray() || c->alloc)) continue; // mode 5 : reading uninitialized array or pointer is invalid. if (mode == 5 && (!c->var->isArray() && !c->var->isPointer())) continue; CheckUninitVar *checkUninitVar = dynamic_cast(c->owner); if (checkUninitVar) { if (c->strncpy_ || c->memset_nonzero) { if (!Token::Match(c->var->typeStartToken(), "char|wchar_t")) { continue; } if (Token::Match(tok->next(), "[")) { // Check if it's not being accessed like: 'str[1]' continue; } checkUninitVar->uninitstringError(tok, c->var->name(), c->strncpy_); } else if (c->var->isPointer() && !c->var->isArray() && c->alloc) checkUninitVar->uninitdataError(tok, c->var->name()); else checkUninitVar->uninitvarError(tok, c->var->name()); return true; } } } // No error found return false; } /** * Reading variable. Use this function in situations when it is * invalid to read the data of the variable but not the address. * @param checks all available checks * @param tok variable token * @return if error is found, true is returned */ static bool use(std::list &checks, const Token *tok) { return use(checks, tok, 0); } /** * Reading array elements. If the variable is not an array then the usage is ok. * @param checks all available checks * @param tok variable token */ static void use_array(std::list &checks, const Token *tok) { use(checks, tok, 1); } /** * Reading array elements with a "mem.." function. It's ok if the array is not null-terminated. * @param checks all available checks * @param tok variable token */ static void use_array_mem(std::list &checks, const Token *tok) { use(checks, tok, 2); } /** * Bad pointer usage. If the variable is not a pointer then the usage is ok. * @param checks all available checks * @param tok variable token * @return if error is found, true is returned */ static bool use_pointer(std::list &checks, const Token *tok) { return use(checks, tok, 3); } /** * Using variable.. if it's a dead pointer the usage is invalid. * @param checks all available checks * @param tok variable token * @return if error is found, true is returned */ static bool use_dead_pointer(std::list &checks, const Token *tok) { return use(checks, tok, 4); } /** * Using variable.. reading from uninitialized array or pointer data is invalid. * Example: = x[0]; * @param checks all available checks * @param tok variable token * @return if error is found, true is returned */ static bool use_array_or_pointer_data(std::list &checks, const Token *tok) { return use(checks, tok, 5); } /** * Parse right hand side expression in statement * @param tok2 start token of rhs * @param checks the execution paths */ static void parserhs(const Token *tok2, std::list &checks) { // check variable usages in rhs/index while (nullptr != (tok2 = tok2->next())) { if (Token::Match(tok2, "[;)=]")) break; if (Token::Match(tok2, "%name% (")) break; if (Token::Match(tok2, "%name% <") && Token::simpleMatch(tok2->linkAt(1), "> (")) break; if (tok2->varId() && !Token::Match(tok2->previous(), "&|::") && !Token::simpleMatch(tok2->tokAt(-2), "& (") && !Token::Match(tok2->tokAt(1), ")| =")) { // Multiple assignments.. if (Token::Match(tok2->next(), ".|[")) { const Token * tok3 = tok2; while (tok3) { if (Token::Match(tok3->next(), ". %name%")) tok3 = tok3->tokAt(2); else if (tok3->strAt(1) == "[") tok3 = tok3->next()->link(); else break; } if (tok3 && tok3->strAt(1) == "=") continue; } bool foundError; if (tok2->previous()->str() == "*" || tok2->next()->str() == "[") foundError = use_array_or_pointer_data(checks, tok2); else foundError = use(checks, tok2); // prevent duplicate error messages if (foundError) { bailOutVar(checks, tok2->varId()); } } } } /** parse tokens. @sa ExecutionPath::parse */ const Token *parse(const Token &tok, std::list &checks) const { // Variable declaration.. if (Token::Match(&tok, "%var% [[;]")) { const Variable* var2 = tok.variable(); if (var2 && var2->nameToken() == &tok && !var2->isStatic() && !var2->isExtern() && !Token::simpleMatch(tok.linkAt(1), "] [")) { if (tok.linkAt(1)) { // array const Token* endtok = tok.next(); while (endtok->link()) endtok = endtok->link()->next(); if (endtok->str() != ";") return &tok; } const Scope* parent = var2->scope()->nestedIn; while (parent) { for (std::list::const_iterator j = parent->varlist.begin(); j != parent->varlist.end(); ++j) { if (j->name() == var2->name()) { ExecutionPath::bailOutVar(checks, j->declarationId()); // If there is a variable with the same name in other scopes, this might cause false positives, if there are unexpanded macros break; } } parent = parent->nestedIn; } if (var2->isPointer()) checks.push_back(new UninitVar(owner, var2, symbolDatabase, library, isC)); else if (var2->typeEndToken()->str() != ">") { const bool stdtype = var2->typeStartToken()->isStandardType(); // TODO: change to isC to handle unknown types better if (stdtype && (!var2->isArray() || var2->nameToken()->linkAt(1)->strAt(1) == ";")) checks.push_back(new UninitVar(owner, var2, symbolDatabase, library, isC)); } return &tok; } } if (tok.str() == "return") { // is there assignment or ternary operator in the return statement? bool assignment = false; for (const Token *tok2 = tok.next(); tok2 && tok2->str() != ";"; tok2 = tok2->next()) { if (tok2->str() == "=" || (!isC && tok2->str() == ">>") || Token::Match(tok2, "(|, &")) { assignment = true; break; } if (Token::Match(tok2, "[(,] &| %var% [,)]")) { tok2 = tok2->next(); if (!tok2->isName()) tok2 = tok2->next(); ExecutionPath::bailOutVar(checks, tok2->varId()); } } if (!assignment) { for (const Token *tok2 = tok.next(); tok2 && tok2->str() != ";"; tok2 = tok2->next()) { if (tok2->isName() && tok2->strAt(1) == "(") tok2 = tok2->next()->link(); else if (tok2->varId()) use(checks, tok2); } } } if (tok.varId()) { // array variable passed as function parameter.. if (Token::Match(tok.previous(), "[(,] %var% [+-,)]")) { // #4896 : This checking was removed because of FP, // the new uninitvar checking is used instead to catch // these errors. ExecutionPath::bailOutVar(checks, tok.varId()); return &tok; } // Used.. if (Token::Match(tok.previous(), "[[(,+-*/|=] %var% ]|)|,|;|%op%") && !tok.next()->isAssignmentOp()) { // Taking address of array.. std::list::const_iterator it; for (it = checks.begin(); it != checks.end(); ++it) { UninitVar *c = dynamic_cast(*it); if (c && c->varId == tok.varId()) { if (c->var->isArray() || c->alloc) bailOutVar(checks, tok.varId()); break; } } // initialize reference variable if (Token::Match(tok.tokAt(-3), "& %var% =")) bailOutVar(checks, tok.varId()); else use(checks, &tok); return &tok; } if ((tok.previous() && tok.previous()->type() == Token::eIncDecOp) || (tok.next() && tok.next()->type() == Token::eIncDecOp)) { use(checks, &tok); return &tok; } if (Token::Match(tok.previous(), "[;{}] %name% [=[.]")) { if (tok.next()->str() == ".") { if (Token::Match(&tok, "%name% . %name% (")) { const Function *function = tok.tokAt(2)->function(); if (function && function->isStatic()) return &tok; } if (use_dead_pointer(checks, &tok)) { return &tok; } } else { const Token *tok2 = tok.next(); if (tok2->str() == "[") { const Token *tok3 = tok2->link(); while (Token::simpleMatch(tok3, "] [")) tok3 = tok3->next()->link(); // Possible initialization if (Token::simpleMatch(tok3, "] >>")) return &tok; if (Token::simpleMatch(tok3, "] =")) { if (use_dead_pointer(checks, &tok)) { return &tok; } parserhs(tok2, checks); tok2 = tok3->next(); } } parserhs(tok2, checks); } // pointer aliasing? if (Token::Match(tok.tokAt(2), "%name% ;")) { pointer_assignment(checks, &tok, tok.tokAt(2)); } } if (tok.strAt(1) == "(") { use_pointer(checks, &tok); } if (Token::Match(tok.tokAt(-2), "[;{}] *")) { if (tok.strAt(1) == "=") { // is the pointer used in the rhs? bool used = false; for (const Token *tok2 = tok.tokAt(2); tok2; tok2 = tok2->next()) { if (Token::Match(tok2, "[,;=(]")) break; else if (Token::Match(tok2, "* %varid%", tok.varId())) { used = true; break; } } if (used) use_pointer(checks, &tok); else init_pointer(checks, &tok); } else { use_pointer(checks, &tok); } return &tok; } if (Token::Match(tok.next(), "= malloc|kmalloc") || (!isC && Token::simpleMatch(tok.next(), "= new char [")) || (Token::Match(tok.next(), "= %name% (") && library->returnuninitdata.find(tok.strAt(2)) != library->returnuninitdata.end())) { alloc_pointer(checks, tok.varId()); if (tok.strAt(3) == "(") return tok.tokAt(3); } else if ((!isC && (Token::Match(tok.previous(), "<<|>>") || Token::Match(tok.previous(), "[;{}] %name% <<"))) || tok.strAt(1) == "=") { // TODO: Don't bail out for "<<" and ">>" if these are // just computations ExecutionPath::bailOutVar(checks, tok.varId()); return &tok; } if (tok.strAt(1) == "[" && tok.next()->link()) { const Token *tok2 = tok.next()->link(); if (tok2->strAt(1) == "=") { ExecutionPath::bailOutVar(checks, tok.varId()); return &tok; } } if (tok.strAt(-1) == "delete" || Token::simpleMatch(tok.tokAt(-3), "delete [ ]")) { dealloc_pointer(checks, &tok); return &tok; } } if (Token::Match(&tok, "%name% (")) { // sizeof/typeof doesn't dereference. A function name that is all uppercase // might be an unexpanded macro that uses sizeof/typeof if (Token::Match(&tok, "sizeof|typeof (")) return tok.next()->link(); // deallocate pointer if (Token::Match(&tok, "free|kfree|fclose ( %var% )") || Token::Match(&tok, "realloc ( %name%")) { dealloc_pointer(checks, tok.tokAt(2)); if (tok.str() == "realloc") ExecutionPath::bailOutVar(checks, tok.tokAt(2)->varId()); return tok.tokAt(3); } // parse usage.. { std::list var1; CheckNullPointer::parseFunctionCall(tok, var1, library, 1); for (std::list::const_iterator it = var1.begin(); it != var1.end(); ++it) { // does iterator point at first function parameter? const bool firstPar(*it == tok.tokAt(2)); // is function memset/memcpy/etc? if (tok.str().compare(0,3,"mem") == 0) use_array_mem(checks, *it); // second parameter for strncpy/strncat/etc else if (!firstPar && tok.str().compare(0,4,"strn") == 0) use_array_mem(checks, *it); else use_array(checks, *it); use_dead_pointer(checks, *it); } // Using uninitialized pointer is bad if using null pointer is bad std::list var2; CheckNullPointer::parseFunctionCall(tok, var2, library, 0); for (std::list::const_iterator it = var2.begin(); it != var2.end(); ++it) { if (std::find(var1.begin(), var1.end(), *it) == var1.end()) use_dead_pointer(checks, *it); } } // strncpy doesn't null-terminate first parameter if (Token::Match(&tok, "strncpy ( %name% ,")) { if (Token::Match(tok.tokAt(4), "%str% ,")) { if (Token::Match(tok.tokAt(6), "%num% )")) { const std::size_t len = Token::getStrLength(tok.tokAt(4)); const MathLib::bigint sz = MathLib::toLongNumber(tok.strAt(6)); if (sz >= 0 && len >= static_cast(sz)) { init_strncpy(checks, tok.tokAt(2)); return tok.next()->link(); } } } else { init_strncpy(checks, tok.tokAt(2)); return tok.next()->link(); } } // memset (not zero terminated).. if (Token::Match(&tok, "memset ( %name% , !!0 , %num% )")) { init_memset_nonzero(checks, tok.tokAt(2)); return tok.next()->link(); } if (Token::Match(&tok, "asm ( %str% )")) { ExecutionPath::bailOut(checks); return &tok; } // is the variable passed as a parameter to some function? unsigned int parlevel = 0; std::set bailouts; for (const Token *tok2 = tok.next(); tok2; tok2 = tok2->next()) { if (tok2->str() == "(") ++parlevel; else if (tok2->str() == ")") { if (parlevel <= 1) break; --parlevel; } else if (Token::Match(tok2, "sizeof|typeof (")) { tok2 = tok2->next()->link(); if (!tok2) break; } // ticket #2367 : unexpanded macro that uses sizeof|typeof? else if (Token::Match(tok2, "%type% (") && tok2->isUpperCaseName()) { tok2 = tok2->next()->link(); if (!tok2) break; } else if (tok2->varId()) { if (Token::Match(tok2->tokAt(-2), "[(,] *") || Token::Match(tok2->next(), ". %name%")) { // find function call.. const Token *functionCall = tok2; while (nullptr != (functionCall = functionCall ? functionCall->previous() : 0)) { if (functionCall->str() == "(") break; if (functionCall->str() == ")") functionCall = functionCall->link(); } functionCall = functionCall ? functionCall->previous() : 0; if (functionCall) { if (functionCall->isName() && !functionCall->isUpperCaseName() && use_dead_pointer(checks, tok2)) ExecutionPath::bailOutVar(checks, tok2->varId()); } } // it is possible that the variable is initialized here if (Token::Match(tok2->previous(), "[(,] %var% [,)]")) bailouts.insert(tok2->varId()); // array initialization.. if (Token::Match(tok2->previous(), "[,(] %var% [+-]")) { // if var is array, bailout for (std::list::const_iterator it = checks.begin(); it != checks.end(); ++it) { if ((*it)->varId == tok2->varId()) { const UninitVar *c = dynamic_cast(*it); if (c && (c->var->isArray() || (c->var->isPointer() && c->alloc))) bailouts.insert(tok2->varId()); break; } } } } } for (std::set::const_iterator it = bailouts.begin(); it != bailouts.end(); ++it) ExecutionPath::bailOutVar(checks, *it); } // function call via function pointer if (Token::Match(&tok, "( * %name% ) (") || (Token::Match(&tok, "( *| %name% .|::") && Token::Match(tok.link()->tokAt(-2), ".|:: %name% ) ("))) { // is the variable passed as a parameter to some function? const Token *tok2 = tok.link()->next(); for (const Token* const end2 = tok2->link(); tok2 != end2; tok2 = tok2->next()) { if (tok2->varId()) { // it is possible that the variable is initialized here ExecutionPath::bailOutVar(checks, tok2->varId()); } } } if (tok.str() == "return") { // Todo: if (!array && .. if (Token::Match(tok.next(), "%name% ;")) { use(checks, tok.next()); } else if (Token::Match(tok.next(), "%name% [")) { use_array_or_pointer_data(checks, tok.next()); } } if (tok.varId()) { if (tok.strAt(-1) == "=") { if (Token::Match(tok.tokAt(-3), "& %var% =")) { bailOutVar(checks, tok.varId()); return &tok; } if (!Token::Match(tok.tokAt(-3), ". %name% =")) { if (!Token::Match(tok.tokAt(-3), "[;{}] %name% =")) { use(checks, &tok); return &tok; } const unsigned int varid2 = tok.tokAt(-2)->varId(); if (varid2) { { use(checks, &tok); return &tok; } } } } if (tok.strAt(1) == ".") { bailOutVar(checks, tok.varId()); return &tok; } if (tok.strAt(1) == "[") { ExecutionPath::bailOutVar(checks, tok.varId()); return &tok; } if (Token::Match(tok.tokAt(-2), "[,(=] *")) { use_pointer(checks, &tok); return &tok; } if (tok.strAt(-1) == "&") { ExecutionPath::bailOutVar(checks, tok.varId()); } } // Parse "for" if (Token::Match(&tok, "[;{}] for (")) { // initialized variables std::set varid1; varid1.insert(0); // Parse token const Token *tok2; // parse setup for (tok2 = tok.tokAt(3); tok2 != tok.link(); tok2 = tok2->next()) { if (tok2->str() == ";") break; if (tok2->varId()) varid1.insert(tok2->varId()); } if (tok2 == tok.link()) return &tok; // parse condition if (Token::Match(tok2, "; %var% <|<=|>=|> %num% ;")) { // If the variable hasn't been initialized then call "use" if (varid1.find(tok2->next()->varId()) == varid1.end()) use(checks, tok2->next()); } // goto stepcode tok2 = tok2->next(); while (tok2 && tok2->str() != ";") tok2 = tok2->next(); // parse the stepcode if (Token::Match(tok2, "; ++|-- %var% ) {") || Token::Match(tok2, "; %var% ++|-- ) {")) { // get id of variable.. unsigned int varid = tok2->next()->varId(); if (!varid) varid = tok2->tokAt(2)->varId(); // Check that the variable hasn't been initialized and // that it isn't initialized in the body.. if (varid1.find(varid) == varid1.end()) { for (const Token *tok3 = tok2->tokAt(5); tok3 && tok3 != tok2->linkAt(4); tok3 = tok3->next()) { if (tok3->varId() == varid) { varid = 0; // variable is used.. maybe it's initialized. clear the variable id. break; } } // If the variable isn't initialized in the body call "use" if (varid != 0) { // goto variable tok2 = tok2->next(); if (!tok2->varId()) tok2 = tok2->next(); // call "use" use(checks, tok2); } } } } return &tok; } bool parseCondition(const Token &tok, std::list &checks) { if (tok.varId() && Token::Match(&tok, "%name% <|<=|==|!=|)")) use(checks, &tok); else if (Token::Match(&tok, "!| %name% [") && !Token::simpleMatch(skipBrackets(tok.next()), "=")) use_array_or_pointer_data(checks, tok.str() == "!" ? tok.next() : &tok); else if (Token::Match(&tok, "!| %name% (")) { const Token * const ftok = (tok.str() == "!") ? tok.next() : &tok; std::list var1; CheckNullPointer::parseFunctionCall(*ftok, var1, library, 1); for (std::list::const_iterator it = var1.begin(); it != var1.end(); ++it) { // is function memset/memcpy/etc? if (ftok->str().compare(0,3,"mem") == 0) use_array_mem(checks, *it); else use_array(checks, *it); } } else if (Token::Match(&tok, "! %name% )")) { use(checks, &tok); return false; } return ExecutionPath::parseCondition(tok, checks); } void parseLoopBody(const Token *tok, std::list &checks) const { while (tok) { if (tok->str() == "{" || tok->str() == "}" || tok->str() == "for") return; if (Token::simpleMatch(tok, "if (")) { // bail out all variables that are used in the condition const Token* const end2 = tok->linkAt(1); for (const Token *tok2 = tok->tokAt(2); tok2 != end2; tok2 = tok2->next()) { if (tok2->varId()) ExecutionPath::bailOutVar(checks, tok2->varId()); } } const Token *next = parse(*tok, checks); tok = next->next(); } } public: static void analyseFunctions(const Token * const tokens, std::set &func) { for (const Token *tok = tokens; tok; tok = tok->next()) { if (tok->str() == "{") { tok = tok->link(); continue; } if (tok->str() != "::" && Token::Match(tok->next(), "%name% ( %type%")) { if (!Token::Match(tok->linkAt(2), ") [{;]")) continue; const Token *tok2 = tok->tokAt(3); while (tok2 && tok2->str() != ")") { if (tok2->str() == ",") tok2 = tok2->next(); if (Token::Match(tok2, "%type% %name% ,|)") && tok2->isStandardType()) { tok2 = tok2->tokAt(2); continue; } if (tok2->isStandardType() && Token::Match(tok2, "%type% & %name% ,|)")) { const unsigned int varid(tok2->tokAt(2)->varId()); // flags for read/write bool r = false, w = false; // check how the variable is used in the function unsigned int indentlevel = 0; for (const Token *tok3 = tok2; tok3; tok3 = tok3->next()) { if (tok3->str() == "{") ++indentlevel; else if (tok3->str() == "}") { if (indentlevel <= 1) break; --indentlevel; } else if (indentlevel == 0 && tok3->str() == ";") break; else if (indentlevel >= 1 && tok3->varId() == varid) { if (tok3->previous()->type() == Token::eIncDecOp || tok3->next()->type() == Token::eIncDecOp) { r = true; } else { w = true; break; } } } if (!r || w) break; tok2 = tok2->tokAt(3); continue; } if (Token::Match(tok2, "const %type% &|*| const| %name% ,|)") && tok2->next()->isStandardType()) { tok2 = tok2->tokAt(3); while (tok2->isName()) tok2 = tok2->next(); continue; } if (Token::Match(tok2, "const %type% %name% [ ] ,|)") && tok2->next()->isStandardType()) { tok2 = tok2->tokAt(5); continue; } /// @todo enable this code. if pointer is written in function then dead pointer is invalid but valid pointer is ok. /* if (Token::Match(tok2, "const| struct| %type% * %name% ,|)")) { while (tok2->isName()) tok2 = tok2->next(); tok2 = tok2->tokAt(2); continue; } */ break; } // found simple function.. if (tok2 && tok2->link() == tok->tokAt(2)) func.insert(tok->next()->str()); } } } }; /// @} Check::FileInfo *CheckUninitVar::getFileInfo(const Tokenizer *tokenizer, const Settings *settings) const { (void)settings; MyFileInfo * mfi = new MyFileInfo; analyseFunctions(tokenizer, mfi->uvarFunctions); // TODO: add suspicious function calls return mfi; } void CheckUninitVar::analyseWholeProgram(const std::list &fileInfo, const Settings&, ErrorLogger &errorLogger) { (void)fileInfo; (void)errorLogger; } void CheckUninitVar::analyseFunctions(const Tokenizer *tokenizer, std::set &f) const { UninitVar::analyseFunctions(tokenizer->tokens(), f); } void CheckUninitVar::executionPaths() { // check if variable is accessed uninitialized.. UninitVar c(this, _tokenizer->getSymbolDatabase(), &_settings->library, _tokenizer->isC()); checkExecutionPaths(_tokenizer->getSymbolDatabase(), &c); } void CheckUninitVar::check() { const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); std::list::const_iterator scope; // check every executable scope for (scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) { if (scope->isExecutable()) { checkScope(&*scope); } } } void CheckUninitVar::checkScope(const Scope* scope) { for (std::list::const_iterator i = scope->varlist.begin(); i != scope->varlist.end(); ++i) { if ((_tokenizer->isCPP() && i->type() && !i->isPointer() && i->type()->needInitialization != Type::True) || i->isStatic() || i->isExtern() || i->isArray() || i->isReference()) continue; // don't warn for try/catch exception variable if (i->isThrow()) continue; if (i->nameToken()->strAt(1) == "(" || i->nameToken()->strAt(1) == "{") continue; if (Token::Match(i->nameToken(), "%name% =")) { // Variable is initialized, but Rhs might be not checkRhs(i->nameToken(), *i, NO_ALLOC, ""); continue; } if (Token::Match(i->nameToken(), "%name% ) (") && Token::simpleMatch(i->nameToken()->linkAt(2), ") =")) { // Function pointer is initialized, but Rhs might be not checkRhs(i->nameToken()->linkAt(2)->next(), *i, NO_ALLOC, ""); continue; } bool stdtype = _tokenizer->isC(); const Token* tok = i->typeStartToken(); for (; tok && tok->str() != ";" && tok->str() != "<"; tok = tok->next()) { if (tok->isStandardType()) stdtype = true; } while (tok && tok->str() != ";") tok = tok->next(); if (!tok) continue; if (stdtype || i->isPointer()) { Alloc alloc = NO_ALLOC; checkScopeForVariable(tok, *i, nullptr, nullptr, &alloc, ""); } if (i->type()) checkStruct(tok, *i); } if (scope->function) { for (unsigned int i = 0; i < scope->function->argCount(); i++) { const Variable *arg = scope->function->getArgumentVar(i); if (arg && arg->declarationId() && Token::Match(arg->typeStartToken(), "struct| %type% * %name% [,)]")) { // Treat the pointer as initialized until it is assigned by malloc for (const Token *tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) { if (Token::Match(tok, "[;{}] %varid% = %name% (", arg->declarationId()) && _settings->library.returnuninitdata.count(tok->strAt(3)) == 1U) { if (arg->typeStartToken()->str() == "struct") checkStruct(tok, *arg); else if (arg->typeStartToken()->isStandardType()) { Alloc alloc = NO_ALLOC; checkScopeForVariable(tok->next(), *arg, nullptr, nullptr, &alloc, ""); } } } } } } } void CheckUninitVar::checkStruct(const Token *tok, const Variable &structvar) { const Token *typeToken = structvar.typeStartToken(); if (typeToken->str() == "struct") typeToken = typeToken->next(); const SymbolDatabase * symbolDatabase = _tokenizer->getSymbolDatabase(); for (std::size_t j = 0U; j < symbolDatabase->classAndStructScopes.size(); ++j) { const Scope *scope2 = symbolDatabase->classAndStructScopes[j]; if (scope2->className == typeToken->str() && scope2->numConstructors == 0U) { for (std::list::const_iterator it = scope2->varlist.begin(); it != scope2->varlist.end(); ++it) { const Variable &var = *it; if (var.hasDefault() || var.isArray() || (!_tokenizer->isC() && var.isClass() && (!var.type() || var.type()->needInitialization != Type::True))) continue; // is the variable declared in a inner union? bool innerunion = false; for (std::list::const_iterator it2 = symbolDatabase->scopeList.begin(); it2 != symbolDatabase->scopeList.end(); ++it2) { const Scope &innerScope = *it2; if (innerScope.type == Scope::eUnion && innerScope.nestedIn == scope2) { if (var.typeStartToken()->linenr() >= innerScope.classStart->linenr() && var.typeStartToken()->linenr() <= innerScope.classEnd->linenr()) { innerunion = true; break; } } } if (!innerunion) { Alloc alloc = NO_ALLOC; const Token *tok2 = tok; if (tok->str() == "}") tok2 = tok2->next(); checkScopeForVariable(tok2, structvar, nullptr, nullptr, &alloc, var.name()); } } } } } static void conditionAlwaysTrueOrFalse(const Token *tok, const std::map &variableValue, bool *alwaysTrue, bool *alwaysFalse) { assert(Token::simpleMatch(tok, "if (")); const Token *vartok = tok->tokAt(2); const bool NOT(vartok->str() == "!"); if (NOT) vartok = vartok->next(); while (Token::Match(vartok, "%name% . %name%")) vartok = vartok->tokAt(2); std::map::const_iterator it = variableValue.find(vartok->varId()); if (it == variableValue.end()) return; // always true if (Token::Match(vartok, "%name% %oror%|)")) { if (NOT) *alwaysTrue = bool(it->second == 0); else *alwaysTrue = bool(it->second != 0); } else if (Token::Match(vartok, "%name% == %num% %or%|)")) { *alwaysTrue = bool(it->second == MathLib::toLongNumber(vartok->strAt(2))); } else if (Token::Match(vartok, "%name% != %num% %or%|)")) { *alwaysTrue = bool(it->second != MathLib::toLongNumber(vartok->strAt(2))); } // always false if (Token::Match(vartok, "%name% &&|)")) { if (NOT) *alwaysFalse = bool(it->second != 0); else *alwaysFalse = bool(it->second == 0); } else if (Token::Match(vartok, "%name% == %num% &&|)")) { *alwaysFalse = bool(it->second != MathLib::toLongNumber(vartok->strAt(2))); } else if (Token::Match(vartok, "%name% != %num% &&|)")) { *alwaysFalse = bool(it->second == MathLib::toLongNumber(vartok->strAt(2))); } } static bool isVariableUsed(const Token *tok, const Variable& var) { if (!tok) return false; if (tok->str() == "&" && !tok->astOperand2()) return false; if (Token::Match(tok, "%cop%|:")) return isVariableUsed(tok->astOperand1(),var) || isVariableUsed(tok->astOperand2(),var); return (tok->varId() == var.declarationId()); } bool CheckUninitVar::checkScopeForVariable(const Token *tok, const Variable& var, bool * const possibleInit, bool * const noreturn, Alloc* const alloc, const std::string &membervar) { const bool suppressErrors(possibleInit && *possibleInit); const bool printDebug = _settings->debugwarnings; if (possibleInit) *possibleInit = false; unsigned int number_of_if = 0; if (var.declarationId() == 0U) return true; // variable values std::map variableValue; static const int NOT_ZERO = (1<<30); // special variable value for (; tok; tok = tok->next()) { // End of scope.. if (tok->str() == "}") { if (number_of_if && possibleInit) *possibleInit = true; // might be a noreturn function.. if (_tokenizer->IsScopeNoReturn(tok)) { if (noreturn) *noreturn = true; return false; } break; } // Unconditional inner scope or try.. if (tok->str() == "{" && Token::Match(tok->previous(), ";|{|}|try")) { if (checkScopeForVariable(tok->next(), var, possibleInit, noreturn, alloc, membervar)) return true; tok = tok->link(); continue; } // assignment with nonzero constant.. if (Token::Match(tok->previous(), "[;{}] %var% = - %name% ;")) variableValue[tok->varId()] = NOT_ZERO; // Inner scope.. else if (Token::simpleMatch(tok, "if (")) { bool alwaysTrue = false; bool alwaysFalse = false; conditionAlwaysTrueOrFalse(tok, variableValue, &alwaysTrue, &alwaysFalse); // initialization / usage in condition.. if (!alwaysTrue && checkIfForWhileHead(tok->next(), var, suppressErrors, bool(number_of_if == 0), *alloc, membervar)) return true; // checking if a not-zero variable is zero => bail out unsigned int condVarId = 0, condVarValue = 0; if (Token::Match(tok, "if ( %name% )")) { std::map::const_iterator it = variableValue.find(tok->tokAt(2)->varId()); if (it != variableValue.end() && it->second == NOT_ZERO) return true; // this scope is not fully analysed => return true else { condVarId = tok->tokAt(2)->varId(); condVarValue = NOT_ZERO; } } // goto the { tok = tok->next()->link()->next(); if (!tok) break; if (tok->str() == "{") { bool possibleInitIf(number_of_if > 0 || suppressErrors); bool noreturnIf = false; const bool initif = !alwaysFalse && checkScopeForVariable(tok->next(), var, &possibleInitIf, &noreturnIf, alloc, membervar); // bail out for such code: // if (a) x=0; // conditional initialization // if (b) return; // cppcheck doesn't know if b can be false when a is false. // x++; // it's possible x is always initialized if (!alwaysTrue && noreturnIf && number_of_if > 0) { if (printDebug) { std::string condition; for (const Token *tok2 = tok->linkAt(-1); tok2 != tok; tok2 = tok2->next()) { condition += tok2->str(); if (tok2->isName() && tok2->next()->isName()) condition += ' '; } reportError(tok, Severity::debug, "debug", "bailout uninitialized variable checking for '" + var.name() + "'. can't determine if this condition can be false when previous condition is false: " + condition); } return true; } if (alwaysTrue && noreturnIf) return true; std::map varValueIf; if (!alwaysFalse && !initif && !noreturnIf) { for (const Token *tok2 = tok; tok2 && tok2 != tok->link(); tok2 = tok2->next()) { if (Token::Match(tok2, "[;{}.] %name% = - %name% ;")) varValueIf[tok2->next()->varId()] = NOT_ZERO; else if (Token::Match(tok2, "[;{}.] %name% = %num% ;")) varValueIf[tok2->next()->varId()] = (int)MathLib::toLongNumber(tok2->strAt(3)); } } if (initif && condVarId > 0U) variableValue[condVarId] = condVarValue ^ NOT_ZERO; // goto the } tok = tok->link(); if (!Token::simpleMatch(tok, "} else {")) { if (initif || possibleInitIf) { ++number_of_if; if (number_of_if >= 2) return true; } } else { // goto the { tok = tok->tokAt(2); bool possibleInitElse(number_of_if > 0 || suppressErrors); bool noreturnElse = false; const bool initelse = !alwaysTrue && checkScopeForVariable(tok->next(), var, &possibleInitElse, &noreturnElse, alloc, membervar); std::map varValueElse; if (!alwaysTrue && !initelse && !noreturnElse) { for (const Token *tok2 = tok; tok2 && tok2 != tok->link(); tok2 = tok2->next()) { if (Token::Match(tok2, "[;{}.] %var% = - %name% ;")) varValueElse[tok2->next()->varId()] = NOT_ZERO; else if (Token::Match(tok2, "[;{}.] %var% = %num% ;")) varValueElse[tok2->next()->varId()] = (int)MathLib::toLongNumber(tok2->strAt(3)); } } if (initelse && condVarId > 0U && !noreturnIf && !noreturnElse) variableValue[condVarId] = condVarValue; // goto the } tok = tok->link(); if ((alwaysFalse || initif || noreturnIf) && (alwaysTrue || initelse || noreturnElse)) return true; if (initif || initelse || possibleInitElse) ++number_of_if; if (!initif && !noreturnIf) variableValue.insert(varValueIf.begin(), varValueIf.end()); if (!initelse && !noreturnElse) variableValue.insert(varValueElse.begin(), varValueElse.end()); } } } // = { .. } else if (Token::simpleMatch(tok, "= {")) { // end token const Token *end = tok->next()->link(); // If address of variable is taken in the block then bail out if (Token::findmatch(tok->tokAt(2), "& %varid%", end, var.declarationId())) return true; // Skip block tok = end; continue; } // skip sizeof / offsetof if (Token::Match(tok, "sizeof|typeof|offsetof|decltype (")) tok = tok->next()->link(); // for/while.. else if (Token::Match(tok, "for|while (") || Token::simpleMatch(tok, "do {")) { const bool forwhile = Token::Match(tok, "for|while ("); // is variable initialized in for-head (don't report errors yet)? if (forwhile && checkIfForWhileHead(tok->next(), var, true, false, *alloc, membervar)) return true; // goto the { const Token *tok2 = forwhile ? tok->next()->link()->next() : tok->next(); if (tok2 && tok2->str() == "{") { bool init = checkLoopBody(tok2, var, *alloc, membervar, (number_of_if > 0) || suppressErrors); // variable is initialized in the loop.. if (init) return true; // is variable used in for-head? bool initcond = false; if (!suppressErrors) { const Token *startCond = forwhile ? tok->next() : tok->next()->link()->tokAt(2); initcond = checkIfForWhileHead(startCond, var, false, bool(number_of_if == 0), *alloc, membervar); } // goto "}" tok = tok2->link(); // do-while => goto ")" if (!forwhile) { // Assert that the tokens are '} while (' if (!Token::simpleMatch(tok, "} while (")) { if (printDebug) reportError(tok,Severity::debug,"","assertion failed '} while ('"); break; } // Goto ')' tok = tok->linkAt(2); if (!tok) // bailout : invalid code / bad tokenizer break; if (initcond) // variable is initialized in while-condition return true; } } } // Unknown or unhandled inner scope else if (Token::simpleMatch(tok, ") {") || (Token::Match(tok, "%name% {") && tok->str() != "try")) { if (tok->str() == "struct" || tok->str() == "union") { tok = tok->linkAt(1); continue; } return true; } // bailout if there is ({ if (Token::simpleMatch(tok, "( {")) { return true; } // bailout if there is assembler code or setjmp if (Token::Match(tok, "asm|setjmp (")) { return true; } if (tok->str() == "?") { const bool used1 = isVariableUsed(tok->astOperand2()->astOperand1(), var); const bool used0 = isVariableUsed(tok->astOperand2()->astOperand2(), var); const bool err = (number_of_if == 0) ? (used1 || used0) : (used1 && used0); if (err) { if (*alloc != NO_ALLOC) uninitdataError(tok, var.nameToken()->str()); else uninitvarError(tok, var.nameToken()->str()); } // Todo: skip expression if there is no error return true; } if (Token::Match(tok, "return|break|continue|throw|goto")) { if (noreturn) *noreturn = true; while (tok && tok->str() != ";") { // variable is seen.. if (tok->varId() == var.declarationId()) { if (!membervar.empty()) { if (Token::Match(tok, "%name% . %name% ;|%cop%") && tok->strAt(2) == membervar) uninitStructMemberError(tok, tok->str() + "." + membervar); else return true; } // Use variable else if (!suppressErrors && isVariableUsage(tok, var.isPointer(), *alloc)) { if (*alloc != NO_ALLOC) uninitdataError(tok, tok->str()); else uninitvarError(tok, tok->str()); } else // assume that variable is assigned return true; } else if (Token::Match(tok, "sizeof|typeof|offsetof|decltype (")) tok = tok->linkAt(1); else if (tok->str() == "?") { const bool used1 = isVariableUsed(tok->astOperand2()->astOperand1(), var); const bool used0 = isVariableUsed(tok->astOperand2()->astOperand2(), var); const bool err = (number_of_if == 0) ? (used1 || used0) : (used1 && used0); if (err) { if (*alloc != NO_ALLOC) uninitdataError(tok, var.nameToken()->str()); else uninitvarError(tok, var.nameToken()->str()); } return true; } tok = tok->next(); } return bool(noreturn==nullptr); } // variable is seen.. if (tok->varId() == var.declarationId()) { // calling function that returns uninit data through pointer.. if (var.isPointer() && Token::Match(tok->next(), "= %name% (") && Token::simpleMatch(tok->linkAt(3), ") ;") && _settings->library.returnuninitdata.count(tok->strAt(2)) > 0U) { *alloc = NO_CTOR_CALL; continue; } if (var.isPointer() && (var.typeStartToken()->isStandardType() || (var.type() && var.type()->needInitialization == Type::True)) && Token::simpleMatch(tok->next(), "= new")) { *alloc = CTOR_CALL; if (var.typeScope() && var.typeScope()->numConstructors > 0) return true; continue; } if (!membervar.empty()) { if (isMemberVariableAssignment(tok, membervar)) { checkRhs(tok, var, *alloc, membervar); return true; } if (isMemberVariableUsage(tok, var.isPointer(), *alloc, membervar)) uninitStructMemberError(tok, tok->str() + "." + membervar); else if (Token::Match(tok->previous(), "[(,] %name% [,)]")) return true; } else { // Use variable if (!suppressErrors && isVariableUsage(tok, var.isPointer(), *alloc)) { if (*alloc != NO_ALLOC) uninitdataError(tok, tok->str()); else uninitvarError(tok, tok->str()); } else { if (tok->strAt(1) == "=") checkRhs(tok, var, *alloc, ""); // assume that variable is assigned return true; } } } } return false; } bool CheckUninitVar::checkIfForWhileHead(const Token *startparentheses, const Variable& var, bool suppressErrors, bool isuninit, Alloc alloc, const std::string &membervar) { const Token * const endpar = startparentheses->link(); if (Token::Match(startparentheses, "( ! %name% %oror%") && startparentheses->tokAt(2)->getValue(0)) suppressErrors = true; for (const Token *tok = startparentheses->next(); tok && tok != endpar; tok = tok->next()) { if (tok->varId() == var.declarationId()) { if (Token::Match(tok, "%name% . %name%")) { if (tok->strAt(2) == membervar) { if (isMemberVariableAssignment(tok, membervar)) return true; if (!suppressErrors && isMemberVariableUsage(tok, var.isPointer(), alloc, membervar)) uninitStructMemberError(tok, tok->str() + "." + membervar); } continue; } if (isVariableUsage(tok, var.isPointer(), alloc)) { if (suppressErrors) continue; uninitvarError(tok, tok->str()); } return true; } if (Token::Match(tok, "sizeof|decltype|offsetof (")) tok = tok->next()->link(); if ((!isuninit || !membervar.empty()) && tok->str() == "&&") suppressErrors = true; } return false; } bool CheckUninitVar::checkLoopBody(const Token *tok, const Variable& var, const Alloc alloc, const std::string &membervar, const bool suppressErrors) { const Token *usetok = nullptr; assert(tok->str() == "{"); for (const Token * const end = tok->link(); tok != end; tok = tok->next()) { if (tok->varId() == var.declarationId()) { if (!membervar.empty()) { if (isMemberVariableAssignment(tok, membervar)) { bool assign = true; bool rhs = false; for (const Token *tok2 = tok->next(); tok2; tok2 = tok2->next()) { if (tok2->str() == "=") rhs = true; if (tok2->str() == ";") break; if (rhs && tok2->varId() == var.declarationId() && isMemberVariableUsage(tok2, var.isPointer(), alloc, membervar)) { assign = false; break; } } if (assign) return true; } if (Token::Match(tok, "%name% =")) return true; if (isMemberVariableUsage(tok, var.isPointer(), alloc, membervar)) usetok = tok; else if (Token::Match(tok->previous(), "[(,] %name% [,)]")) return true; } else { if (isVariableUsage(tok, var.isPointer(), alloc)) usetok = tok; else if (tok->strAt(1) == "=") { // Is var used in rhs? bool rhs = false; std::stack tokens; tokens.push(tok->next()->astOperand2()); while (!tokens.empty()) { const Token *t = tokens.top(); tokens.pop(); if (!t) continue; if (t->varId() == var.declarationId()) { // var is used in rhs rhs = true; break; } if (Token::simpleMatch(t->previous(),"sizeof (")) continue; tokens.push(t->astOperand1()); tokens.push(t->astOperand2()); } if (!rhs) return true; } else { return true; } } } if (Token::Match(tok, "sizeof|typeof (")) tok = tok->next()->link(); if (Token::Match(tok, "asm ( %str% ) ;")) return true; } if (!suppressErrors && usetok) { if (membervar.empty()) uninitvarError(usetok, usetok->str()); else uninitStructMemberError(usetok, usetok->str() + "." + membervar); return true; } return false; } void CheckUninitVar::checkRhs(const Token *tok, const Variable &var, Alloc alloc, const std::string &membervar) { bool rhs = false; unsigned int indent = 0; while (nullptr != (tok = tok->next())) { if (tok->str() == "=") rhs = true; else if (rhs && tok->varId() == var.declarationId()) { if (membervar.empty() && isVariableUsage(tok, var.isPointer(), alloc)) uninitvarError(tok, tok->str()); else if (!membervar.empty() && isMemberVariableUsage(tok, var.isPointer(), alloc, membervar)) uninitStructMemberError(tok, tok->str() + "." + membervar); } else if (tok->str() == ";" || (indent==0 && tok->str() == ",")) break; else if (tok->str() == "(") ++indent; else if (tok->str() == ")") { if (indent == 0) break; --indent; } else if (Token::simpleMatch(tok, "sizeof (")) tok = tok->next()->link(); } } bool CheckUninitVar::isVariableUsage(const Token *vartok, bool pointer, Alloc alloc) const { if (alloc == NO_ALLOC && ((Token::Match(vartok->previous(), "return|delete") && vartok->strAt(1) != "=") || (vartok->strAt(-1) == "]" && vartok->linkAt(-1)->strAt(-1) == "delete"))) return true; // Passing variable to typeof/__alignof__ if (Token::Match(vartok->tokAt(-3), "typeof|__alignof__ ( * %name%")) return false; // Accessing Rvalue member using "." or "->" if (vartok->strAt(1) == "." && vartok->strAt(-1) != "&") { // Is struct member passed to function? if (!pointer && Token::Match(vartok->previous(), "[,(] %name% . %name%")) { // TODO: there are FN currently: // - should only return false if struct member is (or might be) array. // - should only return false if function argument is (or might be) non-const pointer or reference const Token *tok2 = vartok->next(); do { tok2 = tok2->tokAt(2); } while (Token::Match(tok2, ". %name%")); if (Token::Match(tok2, "[,)]")) return false; } else if (pointer && alloc != CTOR_CALL && Token::Match(vartok, "%name% . %name% (")) { return true; } bool assignment = false; const Token* parent = vartok->astParent(); while (parent) { if (parent->str() == "=") { assignment = true; break; } if (alloc != NO_ALLOC && parent->str() == "(") { if (_settings->library.functionpure.find(parent->strAt(-1)) == _settings->library.functionpure.end()) { assignment = true; break; } } parent = parent->astParent(); } if (!assignment) return true; } // Passing variable to function.. if (Token::Match(vartok->previous(), "[(,] %name% [,)]") || Token::Match(vartok->tokAt(-2), "[(,] & %name% [,)]")) { const int use = isFunctionParUsage(vartok, pointer, alloc); if (use >= 0) return (use>0); } if (Token::Match(vartok->previous(), "++|--|%cop%")) { if (_tokenizer->isCPP() && Token::Match(vartok->previous(), ">>|<<")) { const Token* tok2 = vartok->previous(); if (Token::simpleMatch(tok2->astOperand1(), ">>")) return false; // Looks like stream operator, initializes the variable if (Token::simpleMatch(tok2, "<<")) { // Looks like stream operator, but could also initialize the variable. Check lhs. do { tok2 = tok2->astOperand1(); } while (Token::simpleMatch(tok2, "<<")); if (tok2 && tok2->strAt(-1) == "::") tok2 = tok2->previous(); if (tok2 && (Token::simpleMatch(tok2->previous(), "std ::") || (tok2->variable() && tok2->variable()->isStlType()) || tok2->isStandardType())) return true; } const Variable *var = vartok->tokAt(-2)->variable(); return (var && var->typeStartToken()->isStandardType()); } // is there something like: ; "*((&var ..expr.. =" => the variable is assigned if (vartok->previous()->str() == "&" && !vartok->previous()->astOperand2()) return false; // bailout to avoid fp for 'int x = 2 + x();' where 'x()' is a unseen preprocessor macro (seen in linux) if (!pointer && vartok->next() && vartok->next()->str() == "(") return false; if (vartok->previous()->str() != "&" || !Token::Match(vartok->tokAt(-2), "[(,=?:]")) { if (alloc != NO_ALLOC && vartok->previous()->str() == "*") { const Token *parent = vartok->previous()->astParent(); if (parent && parent->str() == "=" && parent->astOperand1() == vartok->previous()) return false; return true; } return alloc == NO_ALLOC; } } if (alloc == NO_ALLOC && Token::Match(vartok->previous(), "= %name% ;|%cop%")) return true; if (Token::Match(vartok->previous(), "? %name%")) { // this is only variable usage if variable is either: // * unconditionally uninitialized // * used in both rhs and lhs of ':' operator bool rhs = false; for (const Token *tok2 = vartok; tok2; tok2 = tok2->next()) { if (tok2->str() == "(") tok2 = tok2->link(); else if (tok2->str() == ":") rhs = true; else if (Token::Match(tok2, "[)];,{}=]")) break; else if (rhs && tok2->varId() == vartok->varId()) return true; } } bool unknown = false; if (pointer && CheckNullPointer::isPointerDeRef(vartok, unknown)) { // pointer is allocated - dereferencing it is ok. if (alloc != NO_ALLOC) return false; // function parameter? bool functionParameter = false; if (Token::Match(vartok->tokAt(-2), "%name% (") || vartok->previous()->str() == ",") functionParameter = true; // if this is not a function parameter report this dereference as variable usage if (!functionParameter) return true; } if (_tokenizer->isCPP() && Token::Match(vartok->next(), "<<|>>")) { // Is this calculation done in rhs? const Token *tok = vartok; while (tok && Token::Match(tok, "%name%|.|::")) tok = tok->previous(); if (Token::Match(tok, "[;{}]")) return false; // Is variable a known POD type then this is a variable usage, // otherwise we assume it's not. const Variable *var = vartok->variable(); return (var && var->typeStartToken()->isStandardType()); } if (alloc == NO_ALLOC && vartok->next() && vartok->next()->isOp() && !vartok->next()->isAssignmentOp()) return true; if (vartok->strAt(1) == "]") return true; return false; } /*** * Is function parameter "used" so a "usage of uninitialized variable" can * be written? If parameter is passed "by value" then it is "used". If it * is passed "by reference" then it is not necessarily "used". * @return -1 => unknown 0 => not used 1 => used */ int CheckUninitVar::isFunctionParUsage(const Token *vartok, bool pointer, Alloc alloc) const { if (!Token::Match(vartok->previous(), "[(,]") && !Token::Match(vartok->tokAt(-2), "[(,] &")) return -1; // locate start parentheses in function call.. unsigned int argumentNumber = 0; const Token *start = vartok; while (start && !Token::Match(start, "[;{}(]")) { if (start->str() == ")") start = start->link(); else if (start->str() == ",") ++argumentNumber; start = start->previous(); } // is this a function call? if (start && Token::Match(start->previous(), "%name% (")) { const bool address(vartok->previous()->str() == "&"); // check how function handle uninitialized data arguments.. const Function *func = start->previous()->function(); if (func) { const Variable *arg = func->getArgumentVar(argumentNumber); if (arg) { const Token *argStart = arg->typeStartToken(); if (!address && Token::Match(argStart, "struct| %type% [,)]")) return 1; if (!address && Token::Match(argStart, "struct| %type% %name% [,)]")) return 1; if (pointer && !address && alloc == NO_ALLOC && Token::Match(argStart, "struct| %type% * %name% [,)]")) return 1; while (argStart->previous() && argStart->previous()->isName()) argStart = argStart->previous(); if (Token::Match(argStart, "const %type% & %name% [,)]")) return 1; if ((pointer || address) && alloc == NO_ALLOC && Token::Match(argStart, "const struct| %type% * %name% [,)]")) return 1; if ((pointer || address) && Token::Match(argStart, "const %type% %name% [") && Token::Match(argStart->linkAt(3), "] [,)]")) return 1; } } else if (Token::Match(start->previous(), "if|while|for")) { // control-flow statement reading the variable "by value" return alloc == NO_ALLOC; } else { const bool isnullbad = _settings->library.isnullargbad(start->previous(), argumentNumber + 1); if (pointer && !address && isnullbad && alloc == NO_ALLOC) return true; const bool isuninitbad = _settings->library.isuninitargbad(start->previous(), argumentNumber + 1); if (alloc != NO_ALLOC) return isnullbad && isuninitbad; return isuninitbad && (!address || isnullbad); } } // unknown return -1; } bool CheckUninitVar::isMemberVariableAssignment(const Token *tok, const std::string &membervar) const { if (Token::Match(tok, "%name% . %name%") && tok->strAt(2) == membervar) { if (Token::Match(tok->tokAt(3), "[=.[]")) return true; else if (Token::Match(tok->tokAt(-2), "[(,=] &")) return true; else if (Token::Match(tok->tokAt(-2), "%name% >>") && Token::Match(tok->tokAt(3), ";|>>")) // #6680 return true; else if ((tok->previous() && tok->previous()->isConstOp()) || Token::Match(tok->previous(), "[|=")) ; // member variable usage else if (tok->tokAt(3)->isConstOp()) ; // member variable usage else if (Token::Match(tok->previous(), "[(,] %name% . %name% [,)]") && 1 == isFunctionParUsage(tok, false, NO_ALLOC)) { return false; } else return true; } else if (tok->strAt(1) == "=") return true; else if (tok->strAt(-1) == "&") { if (Token::Match(tok->tokAt(-2), "[(,] & %name%")) { // locate start parentheses in function call.. unsigned int argumentNumber = 0; const Token *ftok = tok; while (ftok && !Token::Match(ftok, "[;{}(]")) { if (ftok->str() == ")") ftok = ftok->link(); else if (ftok->str() == ",") ++argumentNumber; ftok = ftok->previous(); } // is this a function call? ftok = ftok ? ftok->previous() : NULL; if (Token::Match(ftok, "%name% (")) { // check how function handle uninitialized data arguments.. const Function *function = ftok->function(); const Variable *arg = function ? function->getArgumentVar(argumentNumber) : NULL; const Token *argStart = arg ? arg->typeStartToken() : NULL; while (argStart && argStart->previous() && argStart->previous()->isName()) argStart = argStart->previous(); if (Token::Match(argStart, "const struct| %type% * const| %name% [,)]")) return false; } else if (ftok && Token::simpleMatch(ftok->previous(), "= * (")) return false; } return true; } return false; } bool CheckUninitVar::isMemberVariableUsage(const Token *tok, bool isPointer, Alloc alloc, const std::string &membervar) const { if (Token::Match(tok->previous(), "[(,] %name% . %name% [,)]") && tok->strAt(2) == membervar) { int use = isFunctionParUsage(tok, isPointer, alloc); if (use == 1) return true; } if (isMemberVariableAssignment(tok, membervar)) return false; if (Token::Match(tok, "%name% . %name%") && tok->strAt(2) == membervar && !(tok->tokAt(-2)->variable() && tok->tokAt(-2)->variable()->isReference())) return true; else if (!isPointer && Token::Match(tok->previous(), "[(,] %name% [,)]") && isVariableUsage(tok, isPointer, alloc)) return true; else if (!isPointer && Token::Match(tok->previous(), "= %name% ;")) return true; // = *(&var); else if (!isPointer && Token::simpleMatch(tok->astParent(),"&") && Token::simpleMatch(tok->astParent()->astParent(),"*") && Token::Match(tok->astParent()->astParent()->astParent(), "= * (| &") && tok->astParent()->astParent()->astParent()->astOperand2() == tok->astParent()->astParent()) return true; else if (_settings->experimental && !isPointer && Token::Match(tok->tokAt(-2), "[(,] & %name% [,)]") && isVariableUsage(tok, isPointer, alloc)) return true; return false; } void CheckUninitVar::uninitstringError(const Token *tok, const std::string &varname, bool strncpy_) { reportError(tok, Severity::error, "uninitstring", "Dangerous usage of '" + varname + "'" + (strncpy_ ? " (strncpy doesn't always null-terminate it)." : " (not null-terminated).")); } void CheckUninitVar::uninitdataError(const Token *tok, const std::string &varname) { reportError(tok, Severity::error, "uninitdata", "Memory is allocated but not initialized: " + varname); } void CheckUninitVar::uninitvarError(const Token *tok, const std::string &varname) { reportError(tok, Severity::error, "uninitvar", "Uninitialized variable: " + varname); } void CheckUninitVar::uninitStructMemberError(const Token *tok, const std::string &membername) { reportError(tok, Severity::error, "uninitStructMember", "Uninitialized struct member: " + membername); } void CheckUninitVar::deadPointer() { const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); std::list::const_iterator scope; // check every executable scope for (scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) { if (!scope->isExecutable()) continue; // Dead pointers.. for (const Token* tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) { if (tok->variable() && tok->variable()->isPointer() && isVariableUsage(tok, true, NO_ALLOC)) { const Token *alias = tok->getValueTokenDeadPointer(); if (alias) { deadPointerError(tok,alias); } } } } } void CheckUninitVar::deadPointerError(const Token *pointer, const Token *alias) { const std::string strpointer(pointer ? pointer->str() : std::string("pointer")); const std::string stralias(alias ? alias->expressionString() : std::string("&x")); reportError(pointer, Severity::error, "deadpointer", "Dead pointer usage. Pointer '" + strpointer + "' is dead if it has been assigned '" + stralias + "' at line " + MathLib::toString(alias ? alias->linenr() : 0U) + "."); }