void CheckLeakAutoVar::checkScope(const Token * const startToken, VarInfo *varInfo, std::set<unsigned int> notzero) { std::map<unsigned int, VarInfo::AllocInfo> &alloctype = varInfo->alloctype; std::map<unsigned int, std::string> &possibleUsage = varInfo->possibleUsage; const std::set<unsigned int> conditionalAlloc(varInfo->conditionalAlloc); // Parse all tokens const Token * const endToken = startToken->link(); for (const Token *tok = startToken; tok && tok != endToken; tok = tok->next()) { if (!tok->scope()->isExecutable()) { tok = tok->scope()->classEnd; if (!tok) // Ticket #6666 (crash upon invalid code) break; } // Deallocation and then dereferencing pointer.. if (tok->varId() > 0) { const std::map<unsigned int, VarInfo::AllocInfo>::const_iterator var = alloctype.find(tok->varId()); if (var != alloctype.end()) { if (var->second.status == VarInfo::DEALLOC && tok->strAt(-1) != "&" && (!Token::Match(tok, "%name% =") || tok->strAt(-1) == "*")) { deallocUseError(tok, tok->str()); } else if (Token::simpleMatch(tok->tokAt(-2), "= &")) { varInfo->erase(tok->varId()); } else if (tok->strAt(-1) == "=") { varInfo->erase(tok->varId()); } } else if (Token::Match(tok->previous(), "& %name% = %var% ;")) { varInfo->referenced.insert(tok->tokAt(2)->varId()); } } if (tok->str() == "(" && tok->previous()->isName()) { VarInfo::AllocInfo allocation(0, VarInfo::NOALLOC); functionCall(tok->previous(), varInfo, allocation); tok = tok->link(); continue; } // look for end of statement if (!Token::Match(tok, "[;{}]") || Token::Match(tok->next(), "[;{}]")) continue; tok = tok->next(); if (!tok || tok == endToken) break; // parse statement, skip to last member while (Token::Match(tok, "%name% ::|. %name% !!(")) tok = tok->tokAt(2); // assignment.. if (Token::Match(tok, "%var% =")) { // taking address of another variable.. if (Token::Match(tok->next(), "= %var% [+;]")) { if (tok->tokAt(2)->varId() != tok->varId()) { // If variable points at allocated memory => error leakIfAllocated(tok, *varInfo); // no multivariable checking currently => bail out for rhs variables for (const Token *tok2 = tok; tok2; tok2 = tok2->next()) { if (tok2->str() == ";") { break; } if (tok2->varId()) { varInfo->erase(tok2->varId()); } } } } // is variable used in rhs? bool used_in_rhs = false; for (const Token *tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) { if (tok2->str() == ";") { break; } if (tok->varId() == tok2->varId()) { used_in_rhs = true; break; } } // TODO: Better checking how the pointer is used in rhs? if (used_in_rhs) continue; // Variable has already been allocated => error if (conditionalAlloc.find(tok->varId()) == conditionalAlloc.end()) leakIfAllocated(tok, *varInfo); varInfo->erase(tok->varId()); // not a local variable nor argument? const Variable *var = tok->variable(); if (var && !var->isArgument() && (!var->isLocal() || var->isStatic())) continue; // Don't check reference variables if (var && var->isReference()) continue; // non-pod variable if (_tokenizer->isCPP()) { if (!var) continue; // Possibly automatically deallocated memory if (!var->typeStartToken()->isStandardType() && Token::Match(tok, "%var% = new")) continue; } // allocation? if (tok->next()->astOperand2() && Token::Match(tok->next()->astOperand2()->previous(), "%type% (")) { int i = _settings->library.alloc(tok->next()->astOperand2()->previous()); if (i > 0) { alloctype[tok->varId()].type = i; alloctype[tok->varId()].status = VarInfo::ALLOC; } } else if (_tokenizer->isCPP() && tok->strAt(2) == "new") { alloctype[tok->varId()].type = -1; alloctype[tok->varId()].status = VarInfo::ALLOC; } // Assigning non-zero value variable. It might be used to // track the execution for a later if condition. if (Token::Match(tok->tokAt(2), "%num% ;") && MathLib::toLongNumber(tok->strAt(2)) != 0) notzero.insert(tok->varId()); else if (Token::Match(tok->tokAt(2), "- %type% ;") && tok->tokAt(3)->isUpperCaseName()) notzero.insert(tok->varId()); else notzero.erase(tok->varId()); } // if/else else if (Token::simpleMatch(tok, "if (")) { // Parse function calls inside the condition for (const Token *innerTok = tok->tokAt(2); innerTok; innerTok = innerTok->next()) { if (innerTok->str() == ")") break; if (innerTok->str() == "(" && innerTok->previous()->isName()) { VarInfo::AllocInfo allocation(_settings->library.dealloc(tok), VarInfo::DEALLOC); if (allocation.type == 0) allocation.status = VarInfo::NOALLOC; functionCall(innerTok->previous(), varInfo, allocation); innerTok = innerTok->link(); } } const Token *tok2 = tok->linkAt(1); if (Token::simpleMatch(tok2, ") {")) { VarInfo varInfo1(*varInfo); // VarInfo for if code VarInfo varInfo2(*varInfo); // VarInfo for else code // Recursively scan variable comparisons in condition std::stack<const Token *> tokens; tokens.push(tok->next()->astOperand2()); while (!tokens.empty()) { const Token *tok3 = tokens.top(); tokens.pop(); if (!tok3) continue; if (tok3->str() == "&&") { tokens.push(tok3->astOperand1()); tokens.push(tok3->astOperand2()); continue; } if (tok3->str() == "(" && Token::Match(tok3->astOperand1(), "UNLIKELY|LIKELY")) { tokens.push(tok3->astOperand2()); continue; } const Token *vartok = nullptr; if (astIsVariableComparison(tok3, "!=", "0", &vartok)) { varInfo2.erase(vartok->varId()); if (notzero.find(vartok->varId()) != notzero.end()) varInfo2.clear(); } else if (astIsVariableComparison(tok3, "==", "0", &vartok)) { varInfo1.erase(vartok->varId()); } else if (astIsVariableComparison(tok3, "<", "0", &vartok)) { varInfo1.erase(vartok->varId()); } else if (astIsVariableComparison(tok3, ">", "0", &vartok)) { varInfo2.erase(vartok->varId()); } else if (astIsVariableComparison(tok3, "==", "-1", &vartok)) { varInfo1.erase(vartok->varId()); } } checkScope(tok2->next(), &varInfo1, notzero); tok2 = tok2->linkAt(1); if (Token::simpleMatch(tok2, "} else {")) { checkScope(tok2->tokAt(2), &varInfo2, notzero); tok = tok2->linkAt(2)->previous(); } else { tok = tok2->previous(); } VarInfo old; old.swap(*varInfo); // Conditional allocation in varInfo1 std::map<unsigned int, VarInfo::AllocInfo>::const_iterator it; for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) { if (varInfo2.alloctype.find(it->first) == varInfo2.alloctype.end() && old.alloctype.find(it->first) == old.alloctype.end()) { varInfo->conditionalAlloc.insert(it->first); } } // Conditional allocation in varInfo2 for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) { if (varInfo1.alloctype.find(it->first) == varInfo1.alloctype.end() && old.alloctype.find(it->first) == old.alloctype.end()) { varInfo->conditionalAlloc.insert(it->first); } } // Conditional allocation/deallocation for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) { if (it->second.status == VarInfo::DEALLOC && conditionalAlloc.find(it->first) != conditionalAlloc.end()) { varInfo->conditionalAlloc.erase(it->first); varInfo2.erase(it->first); } } for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) { if (it->second.status == VarInfo::DEALLOC && conditionalAlloc.find(it->first) != conditionalAlloc.end()) { varInfo->conditionalAlloc.erase(it->first); varInfo1.erase(it->first); } } alloctype.insert(varInfo1.alloctype.begin(), varInfo1.alloctype.end()); alloctype.insert(varInfo2.alloctype.begin(), varInfo2.alloctype.end()); possibleUsage.insert(varInfo1.possibleUsage.begin(), varInfo1.possibleUsage.end()); possibleUsage.insert(varInfo2.possibleUsage.begin(), varInfo2.possibleUsage.end()); } } // unknown control.. (TODO: handle loops) else if ((Token::Match(tok, "%type% (") && Token::simpleMatch(tok->linkAt(1), ") {")) || Token::simpleMatch(tok, "do {")) { varInfo->clear(); break; } // return else if (tok->str() == "return") { ret(tok, *varInfo); varInfo->clear(); } // throw else if (_tokenizer->isCPP() && tok->str() == "throw") { bool tryFound = false; const Scope* scope = tok->scope(); while (scope && scope->isExecutable()) { if (scope->type == Scope::eTry) tryFound = true; scope = scope->nestedIn; } // If the execution leaves the function then treat it as return if (!tryFound) ret(tok, *varInfo); varInfo->clear(); } // Function call.. else if (Token::Match(tok, "%type% (")) { VarInfo::AllocInfo allocation(_settings->library.dealloc(tok), VarInfo::DEALLOC); if (allocation.type == 0) allocation.status = VarInfo::NOALLOC; functionCall(tok, varInfo, allocation); tok = tok->next()->link(); // Handle scopes that might be noreturn if (allocation.status == VarInfo::NOALLOC && Token::simpleMatch(tok, ") ; }")) { const std::string &functionName(tok->link()->previous()->str()); bool unknown = false; if (_tokenizer->IsScopeNoReturn(tok->tokAt(2), &unknown)) { if (!unknown) varInfo->clear(); else if (_settings->library.leakignore.find(functionName) == _settings->library.leakignore.end() && _settings->library.use.find(functionName) == _settings->library.use.end()) varInfo->possibleUsageAll(functionName); } } continue; } // delete else if (_tokenizer->isCPP() && tok->str() == "delete") { if (tok->strAt(1) == "[") tok = tok->tokAt(3); else tok = tok->next(); while (Token::Match(tok, "%name% ::|.")) tok = tok->tokAt(2); if (tok->varId() && tok->strAt(1) != "[") { VarInfo::AllocInfo allocation(-1, VarInfo::DEALLOC); changeAllocStatus(varInfo, allocation, tok, tok); } } // goto => weird execution path else if (tok->str() == "goto") { varInfo->clear(); } // continue/break else if (Token::Match(tok, "continue|break ;")) { varInfo->clear(); } } }
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<unsigned int, VariableValue> variableValue; 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()] = !VariableValue(0); // Inner scope.. else if (Token::simpleMatch(tok, "if (")) { bool alwaysTrue = false; bool alwaysFalse = false; conditionAlwaysTrueOrFalse(tok->next()->astOperand2(), 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; VariableValue condVarValue(0); const Token *condVarTok = nullptr; if (alwaysFalse) ; else if (Token::simpleMatch(tok, "if (") && astIsVariableComparison(tok->next()->astOperand2(), "!=", "0", &condVarTok)) { std::map<unsigned int,VariableValue>::const_iterator it = variableValue.find(condVarTok->varId()); if (it != variableValue.end() && it->second != 0) return true; // this scope is not fully analysed => return true else { condVarId = condVarTok->varId(); condVarValue = !VariableValue(0); } } else if (Token::simpleMatch(tok, "if (") && Token::Match(tok->next()->astOperand2(), "==|!=")) { const Token *condition = tok->next()->astOperand2(); const Token *lhs = condition->astOperand1(); const Token *rhs = condition->astOperand2(); const Token *vartok = rhs && rhs->isNumber() ? lhs : rhs; const Token *numtok = rhs && rhs->isNumber() ? rhs : lhs; while (Token::simpleMatch(vartok, ".")) vartok = vartok->astOperand2(); if (vartok && vartok->varId() && numtok) { std::map<unsigned int,VariableValue>::const_iterator it = variableValue.find(vartok->varId()); if (it != variableValue.end() && it->second != MathLib::toLongNumber(numtok->str())) return true; // this scope is not fully analysed => return true else { condVarId = vartok->varId(); condVarValue = VariableValue(MathLib::toLongNumber(numtok->str())); if (condition->str() == "!=") condVarValue = !condVarValue; } } } // goto the { tok = tok->next()->link()->next(); if (!tok) break; if (tok->str() == "{") { bool possibleInitIf((!alwaysTrue && 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 && (initif || noreturnIf)) return true; std::map<unsigned int, VariableValue> 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()] = !VariableValue(0); else if (Token::Match(tok2, "[;{}.] %name% = %num% ;")) varValueIf[tok2->next()->varId()] = VariableValue(MathLib::toLongNumber(tok2->strAt(3))); } } if (initif && condVarId > 0U) variableValue[condVarId] = !condVarValue; // 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((!alwaysFalse && number_of_if > 0) || suppressErrors); bool noreturnElse = false; const bool initelse = !alwaysTrue && checkScopeForVariable(tok->next(), var, &possibleInitElse, &noreturnElse, alloc, membervar); std::map<unsigned int, VariableValue> 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()] = !VariableValue(0); else if (Token::Match(tok2, "[;{}.] %var% = %num% ;")) varValueElse[tok2->next()->varId()] = VariableValue(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 (var.isPointer() || var.isArray()) { if (Token::findmatch(tok->tokAt(2), "%varid%", end, var.declarationId())) return true; } else 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; } // bailout if there is a goto label if (Token::Match(tok, "[;{}] %name% :")) { return true; } if (tok->str() == "?") { if (!tok->astOperand2()) return true; 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) uninitvarError(tok, var.nameToken()->str(), *alloc); // Todo: skip expression if there is no error return true; } if (Token::Match(tok, "return|break|continue|throw|goto")) { if (noreturn) *noreturn = true; tok = tok->next(); 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)) uninitvarError(tok, tok->str(), *alloc); 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() == "?") { if (!tok->astOperand2()) return true; 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) uninitvarError(tok, var.nameToken()->str(), *alloc); 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.typeStartToken()->isEnumType() || (var.type() && var.type()->needInitialization == Type::True)) && Token::simpleMatch(tok->next(), "= new")) { *alloc = CTOR_CALL; // type has constructor(s) if (var.typeScope() && var.typeScope()->numConstructors > 0) return true; // standard or enum type: check if new initializes the allocated memory if (var.typeStartToken()->isStandardType() || var.typeStartToken()->isEnumType()) { // scalar new with initialization if (Token::Match(tok->next(), "= new %type% (")) return true; // array new if (Token::Match(tok->next(), "= new %type% [")) { const Token* tokClosingBracket=tok->linkAt(4); // array new with initialization if (tokClosingBracket && Token::simpleMatch(tokClosingBracket->next(), "( )")) return true; } } continue; } if (!membervar.empty()) { if (isMemberVariableAssignment(tok, membervar)) { checkRhs(tok, var, *alloc, number_of_if, 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)) uninitvarError(tok, tok->str(), *alloc); else { if (tok->strAt(1) == "=") checkRhs(tok, var, *alloc, number_of_if, ""); // assume that variable is assigned return true; } } } } return false; }