|
Cppcheck
|
00001 /* 00002 * Cppcheck - A tool for static C/C++ code analysis 00003 * Copyright (C) 2007-2013 Daniel Marjamäki and Cppcheck team. 00004 * 00005 * This program is free software: you can redistribute it and/or modify 00006 * it under the terms of the GNU General Public License as published by 00007 * the Free Software Foundation, either version 3 of the License, or 00008 * (at your option) any later version. 00009 * 00010 * This program is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 * GNU General Public License for more details. 00014 * 00015 * You should have received a copy of the GNU General Public License 00016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 00017 */ 00018 00019 //--------------------------------------------------------------------------- 00020 // Leaks when using auto variables 00021 //--------------------------------------------------------------------------- 00022 00023 #include "checkleakautovar.h" 00024 00025 #include "checkmemoryleak.h" // <- CheckMemoryLeak::memoryLeak 00026 #include "checkother.h" // <- doubleFreeError 00027 00028 #include "tokenize.h" 00029 #include "errorlogger.h" 00030 #include "symboldatabase.h" 00031 00032 #include <fstream> 00033 00034 //--------------------------------------------------------------------------- 00035 00036 // Register this check class (by creating a static instance of it) 00037 namespace { 00038 CheckLeakAutoVar instance; 00039 } 00040 00041 //--------------------------------------------------------------------------- 00042 00043 void VarInfo::print() 00044 { 00045 std::cout << "size=" << alloctype.size() << std::endl; 00046 std::map<unsigned int, std::string>::const_iterator it; 00047 for (it = alloctype.begin(); it != alloctype.end(); ++it) { 00048 std::string strusage; 00049 std::map<unsigned int, std::string>::const_iterator use = possibleUsage.find(it->first); 00050 if (use != possibleUsage.end()) 00051 strusage = use->second; 00052 00053 std::cout << "alloctype='" << it->second << "' " 00054 << "possibleUsage='" << strusage << "'" << std::endl; 00055 } 00056 } 00057 00058 void VarInfo::possibleUsageAll(const std::string &functionName) 00059 { 00060 possibleUsage.clear(); 00061 std::map<unsigned int, std::string>::const_iterator it; 00062 for (it = alloctype.begin(); it != alloctype.end(); ++it) 00063 possibleUsage[it->first] = functionName; 00064 } 00065 00066 00067 void CheckLeakAutoVar::leakError(const Token *tok, const std::string &varname, const std::string &type) 00068 { 00069 const CheckMemoryLeak checkmemleak(_tokenizer, _errorLogger, _settings->standards); 00070 if (type == "fopen") 00071 checkmemleak.resourceLeakError(tok, varname); 00072 else 00073 checkmemleak.memleakError(tok, varname); 00074 //reportError(tok, Severity::error, "newleak", "New memory leak: " + varname); 00075 } 00076 00077 void CheckLeakAutoVar::mismatchError(const Token *tok, const std::string &varname) 00078 { 00079 const CheckMemoryLeak c(_tokenizer, _errorLogger, _settings->standards); 00080 std::list<const Token *> callstack(1, tok); 00081 c.mismatchAllocDealloc(callstack, varname); 00082 //reportError(tok, Severity::error, "newmismatch", "New mismatching allocation and deallocation: " + varname); 00083 } 00084 00085 void CheckLeakAutoVar::deallocUseError(const Token *tok, const std::string &varname) 00086 { 00087 const CheckMemoryLeak c(_tokenizer, _errorLogger, _settings->standards); 00088 c.deallocuseError(tok, varname); 00089 //reportError(tok, Severity::error, "newdeallocuse", "Using deallocated pointer " + varname); 00090 } 00091 00092 void CheckLeakAutoVar::deallocReturnError(const Token *tok, const std::string &varname) 00093 { 00094 reportError(tok, Severity::error, "deallocret", "Returning/dereferencing '" + varname + "' after it is deallocated / released"); 00095 } 00096 00097 void CheckLeakAutoVar::configurationInfo(const Token* tok, const std::string &functionName) 00098 { 00099 if (((!cfgalloc.empty() || !cfgdealloc.empty()) && _settings->isEnabled("information")) || _settings->experimental) { 00100 reportError(tok, 00101 Severity::information, 00102 "leakconfiguration", 00103 functionName + " configuration is needed to establish if there is a leak or not"); 00104 } 00105 } 00106 00107 void CheckLeakAutoVar::parseConfigurationFile(const std::string &filename) 00108 { 00109 std::ifstream fin(filename.c_str()); 00110 if (!fin.is_open()) 00111 return; 00112 00113 std::string line; 00114 while (std::getline(fin,line)) { 00115 if (line.compare(0,4,"MEM ",0,4) == 0) { 00116 std::string f1; 00117 enum {ALLOC, DEALLOC} type = ALLOC; 00118 std::string::size_type pos1 = line.find_first_not_of(" ", 4U); 00119 while (pos1 < line.size()) { 00120 const std::string::size_type pos2 = line.find(" ", pos1); 00121 std::string f; 00122 if (pos2 == std::string::npos) 00123 f = line.substr(pos1); 00124 else 00125 f = line.substr(pos1, pos2-pos1); 00126 if (f1.empty()) 00127 f1 = f; 00128 if (f == ":") 00129 type = DEALLOC; 00130 else if (type == ALLOC) 00131 cfgalloc[f] = f1; 00132 else if (type == DEALLOC) 00133 cfgdealloc[f] = f1; 00134 pos1 = line.find_first_not_of(" ", pos2); 00135 } 00136 } 00137 00138 else if (line.compare(0,7,"IGNORE ",0,7) == 0) { 00139 std::string::size_type pos1 = line.find_first_not_of(" ", 7U); 00140 while (pos1 < line.size()) { 00141 std::string::size_type pos2 = line.find_first_of(" ", pos1); 00142 std::string functionName; 00143 if (pos2 == std::string::npos) 00144 functionName = line.substr(pos1); 00145 else 00146 functionName = line.substr(pos1, pos2-pos1); 00147 cfgignore.insert(functionName); 00148 pos1 = line.find_first_not_of(" ", pos2); 00149 } 00150 } 00151 00152 else if (line.compare(0,4,"USE ",0,4) == 0) { 00153 std::string::size_type pos1 = line.find_first_not_of(" ", 4U); 00154 while (pos1 < line.size()) { 00155 std::string::size_type pos2 = line.find_first_of(" ", pos1); 00156 std::string functionName; 00157 if (pos2 == std::string::npos) 00158 functionName = line.substr(pos1); 00159 else 00160 functionName = line.substr(pos1, pos2-pos1); 00161 cfguse.insert(functionName); 00162 pos1 = line.find_first_not_of(" ", pos2); 00163 } 00164 } 00165 00166 else if (line.compare(0,9,"NORETURN ",0,9) == 0) { 00167 std::string::size_type pos1 = line.find_first_not_of(" ", 9U); 00168 while (pos1 < line.size()) { 00169 std::string::size_type pos2 = line.find_first_of(" ", pos1); 00170 std::string functionName; 00171 if (pos2 == std::string::npos) 00172 functionName = line.substr(pos1); 00173 else 00174 functionName = line.substr(pos1, pos2-pos1); 00175 cfgnoreturn.insert(functionName); 00176 pos1 = line.find_first_not_of(" ", pos2); 00177 } 00178 } 00179 } 00180 } 00181 00182 void CheckLeakAutoVar::check() 00183 { 00184 const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); 00185 00186 // Check function scopes 00187 const std::size_t functions = symbolDatabase->functionScopes.size(); 00188 for (std::size_t i = 0; i < functions; ++i) { 00189 const Scope * scope = symbolDatabase->functionScopes[i]; 00190 // Empty variable info 00191 VarInfo varInfo; 00192 00193 // Local variables that are known to be non-zero. 00194 static const std::set<unsigned int> notzero; 00195 00196 checkScope(scope->classStart, &varInfo, notzero); 00197 00198 varInfo.conditionalAlloc.clear(); 00199 00200 // Clear reference arguments from varInfo.. 00201 std::map<unsigned int, std::string>::iterator it = varInfo.alloctype.begin(); 00202 while (it != varInfo.alloctype.end()) { 00203 const Variable *var = symbolDatabase->getVariableFromVarId(it->first); 00204 if (!var || 00205 (var->isArgument() && var->isReference()) || 00206 (!var->isArgument() && !var->isLocal())) 00207 varInfo.alloctype.erase(it++); 00208 else 00209 ++it; 00210 } 00211 00212 ret(scope->classEnd, varInfo); 00213 } 00214 } 00215 00216 void CheckLeakAutoVar::checkScope(const Token * const startToken, 00217 VarInfo *varInfo, 00218 std::set<unsigned int> notzero) 00219 { 00220 std::map<unsigned int, std::string> &alloctype = varInfo->alloctype; 00221 std::map<unsigned int, std::string> &possibleUsage = varInfo->possibleUsage; 00222 const std::set<unsigned int> conditionalAlloc(varInfo->conditionalAlloc); 00223 00224 // Allocation functions. key = function name, value = allocation type 00225 std::map<std::string, std::string> allocFunctions(cfgalloc); 00226 allocFunctions["malloc"] = "malloc"; 00227 allocFunctions["strdup"] = "malloc"; 00228 allocFunctions["fopen"] = "fopen"; 00229 00230 // Deallocation functions. key = function name, value = allocation type 00231 std::map<std::string, std::string> deallocFunctions(cfgdealloc); 00232 deallocFunctions["free"] = "malloc"; 00233 deallocFunctions["fclose"] = "fopen"; 00234 00235 // Parse all tokens 00236 const Token * const endToken = startToken->link(); 00237 for (const Token *tok = startToken; tok && tok != endToken; tok = tok->next()) { 00238 // Deallocation and then dereferencing pointer.. 00239 if (tok->varId() > 0) { 00240 const std::map<unsigned int, std::string>::iterator var = alloctype.find(tok->varId()); 00241 if (var != alloctype.end()) { 00242 if (var->second == "dealloc" && !Token::Match(tok->previous(), "[;{},=] %var% =")) { 00243 deallocUseError(tok, tok->str()); 00244 } else if (Token::simpleMatch(tok->tokAt(-2), "= &")) { 00245 varInfo->erase(tok->varId()); 00246 } else if (tok->strAt(-1) == "=") { 00247 varInfo->erase(tok->varId()); 00248 } 00249 } else if (Token::Match(tok->previous(), "& %var% = %var% ;")) { 00250 varInfo->referenced.insert(tok->tokAt(2)->varId()); 00251 } 00252 } 00253 00254 if (tok->str() == "(" && tok->previous()->isName()) { 00255 functionCall(tok->previous(), varInfo, ""); 00256 tok = tok->link(); 00257 continue; 00258 } 00259 00260 // look for end of statement 00261 if (!Token::Match(tok, "[;{}]") || Token::Match(tok->next(), "[;{}]")) 00262 continue; 00263 tok = tok->next(); 00264 if (!tok || tok == endToken) 00265 break; 00266 00267 // parse statement 00268 00269 // assignment.. 00270 if (tok->varId() && Token::Match(tok, "%var% =")) { 00271 // taking address of another variable.. 00272 if (Token::Match(tok->next(), "= %var% [+;]")) { 00273 if (tok->tokAt(2)->varId() != tok->varId()) { 00274 // If variable points at allocated memory => error 00275 leakIfAllocated(tok, *varInfo); 00276 00277 // no multivariable checking currently => bail out for rhs variables 00278 for (const Token *tok2 = tok; tok2; tok2 = tok2->next()) { 00279 if (tok2->str() == ";") { 00280 break; 00281 } 00282 if (tok2->varId()) { 00283 varInfo->erase(tok2->varId()); 00284 } 00285 } 00286 } 00287 } 00288 00289 // is variable used in rhs? 00290 bool used_in_rhs = false; 00291 for (const Token *tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) { 00292 if (tok2->str() == ";") { 00293 break; 00294 } 00295 if (tok->varId() == tok2->varId()) { 00296 used_in_rhs = true; 00297 break; 00298 } 00299 } 00300 // TODO: Better checking how the pointer is used in rhs? 00301 if (used_in_rhs) 00302 continue; 00303 00304 // Variable has already been allocated => error 00305 if (conditionalAlloc.find(tok->varId()) == conditionalAlloc.end()) 00306 leakIfAllocated(tok, *varInfo); 00307 varInfo->erase(tok->varId()); 00308 00309 // not a local variable nor argument? 00310 const Variable *var = tok->variable(); 00311 if (var && !var->isArgument() && !var->isLocal()) { 00312 continue; 00313 } 00314 00315 // Don't check reference variables 00316 if (var && var->isReference()) 00317 continue; 00318 00319 // allocation? 00320 if (Token::Match(tok->tokAt(2), "%type% (")) { 00321 const std::map<std::string, std::string>::const_iterator it = allocFunctions.find(tok->strAt(2)); 00322 if (it != allocFunctions.end()) { 00323 alloctype[tok->varId()] = it->second; 00324 } 00325 } 00326 00327 // Assigning non-zero value variable. It might be used to 00328 // track the execution for a later if condition. 00329 if (Token::Match(tok->tokAt(2), "%num% ;") && MathLib::toLongNumber(tok->strAt(2)) != 0) 00330 notzero.insert(tok->varId()); 00331 else if (Token::Match(tok->tokAt(2), "- %type% ;") && tok->tokAt(3)->isUpperCaseName()) 00332 notzero.insert(tok->varId()); 00333 else 00334 notzero.erase(tok->varId()); 00335 } 00336 00337 // if/else 00338 else if (Token::simpleMatch(tok, "if (")) { 00339 // Parse function calls inside the condition 00340 for (const Token *innerTok = tok->tokAt(2); innerTok; innerTok = innerTok->next()) { 00341 if (innerTok->str() == ")") 00342 break; 00343 if (innerTok->str() == "(" && innerTok->previous()->isName()) { 00344 std::string dealloc; 00345 { 00346 const std::map<std::string, std::string>::iterator func = deallocFunctions.find(tok->str()); 00347 if (func != deallocFunctions.end()) { 00348 dealloc = func->second; 00349 } 00350 } 00351 00352 functionCall(innerTok->previous(), varInfo, dealloc); 00353 innerTok = innerTok->link(); 00354 } 00355 } 00356 00357 const Token *tok2 = tok->linkAt(1); 00358 if (Token::simpleMatch(tok2, ") {")) { 00359 VarInfo varInfo1(*varInfo); 00360 VarInfo varInfo2(*varInfo); 00361 00362 if (Token::Match(tok->next(), "( %var% )")) { 00363 varInfo2.erase(tok->tokAt(2)->varId()); 00364 if (notzero.find(tok->tokAt(2)->varId()) != notzero.end()) 00365 varInfo2.clear(); 00366 } else if (Token::Match(tok->next(), "( ! %var% )|&&")) { 00367 varInfo1.erase(tok->tokAt(3)->varId()); 00368 } else if (Token::Match(tok->next(), "( %var% ( ! %var% ) )|&&")) { 00369 varInfo1.erase(tok->tokAt(5)->varId()); 00370 } 00371 00372 checkScope(tok2->next(), &varInfo1, notzero); 00373 tok2 = tok2->linkAt(1); 00374 if (Token::simpleMatch(tok2, "} else {")) { 00375 checkScope(tok2->tokAt(2), &varInfo2, notzero); 00376 tok = tok2->linkAt(2)->previous(); 00377 } else { 00378 tok = tok2->previous(); 00379 } 00380 00381 VarInfo old; 00382 old.swap(*varInfo); 00383 00384 // Conditional allocation in varInfo1 00385 std::map<unsigned int, std::string>::const_iterator it; 00386 for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) { 00387 if (varInfo2.alloctype.find(it->first) == varInfo2.alloctype.end() && 00388 old.alloctype.find(it->first) == old.alloctype.end()) { 00389 varInfo->conditionalAlloc.insert(it->first); 00390 } 00391 } 00392 00393 // Conditional allocation in varInfo2 00394 for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) { 00395 if (varInfo1.alloctype.find(it->first) == varInfo1.alloctype.end() && 00396 old.alloctype.find(it->first) == old.alloctype.end()) { 00397 varInfo->conditionalAlloc.insert(it->first); 00398 } 00399 } 00400 00401 // Conditional allocation/deallocation 00402 for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) { 00403 if (it->second == "dealloc" && conditionalAlloc.find(it->first) != conditionalAlloc.end()) { 00404 varInfo->conditionalAlloc.erase(it->first); 00405 varInfo2.erase(it->first); 00406 } 00407 } 00408 for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) { 00409 if (it->second == "dealloc" && conditionalAlloc.find(it->first) != conditionalAlloc.end()) { 00410 varInfo->conditionalAlloc.erase(it->first); 00411 varInfo1.erase(it->first); 00412 } 00413 } 00414 00415 alloctype.insert(varInfo1.alloctype.begin(), varInfo1.alloctype.end()); 00416 alloctype.insert(varInfo2.alloctype.begin(), varInfo2.alloctype.end()); 00417 00418 possibleUsage.insert(varInfo1.possibleUsage.begin(), varInfo1.possibleUsage.end()); 00419 possibleUsage.insert(varInfo2.possibleUsage.begin(), varInfo2.possibleUsage.end()); 00420 } 00421 } 00422 00423 // unknown control.. 00424 else if (Token::Match(tok, "%type% (") && Token::simpleMatch(tok->linkAt(1), ") {")) { 00425 varInfo->clear(); 00426 break; 00427 } 00428 00429 // Function call.. 00430 else if (Token::Match(tok, "%type% (") && tok->str() != "return") { 00431 std::string dealloc; 00432 { 00433 const std::map<std::string, std::string>::iterator func = deallocFunctions.find(tok->str()); 00434 if (func != deallocFunctions.end()) { 00435 dealloc = func->second; 00436 } 00437 } 00438 00439 functionCall(tok, varInfo, dealloc); 00440 00441 tok = tok->next()->link(); 00442 00443 // Handle scopes that might be noreturn 00444 if (dealloc.empty() && Token::simpleMatch(tok, ") ; }")) { 00445 const std::string &functionName(tok->link()->previous()->str()); 00446 bool unknown = false; 00447 if (cfgignore.find(functionName) == cfgignore.end() && 00448 cfguse.find(functionName) == cfguse.end() && 00449 _tokenizer->IsScopeNoReturn(tok->tokAt(2), &unknown)) { 00450 if (unknown) { 00451 //const std::string &functionName(tok->link()->previous()->str()); 00452 varInfo->possibleUsageAll(functionName); 00453 } else { 00454 varInfo->clear(); 00455 } 00456 } 00457 } 00458 00459 continue; 00460 } 00461 00462 // return 00463 else if (tok->str() == "return") { 00464 ret(tok, *varInfo); 00465 varInfo->clear(); 00466 } 00467 00468 // goto => weird execution path 00469 else if (tok->str() == "goto") { 00470 varInfo->clear(); 00471 } 00472 00473 // throw 00474 // TODO: if the execution leave the function then treat it as return 00475 else if (tok->str() == "throw") { 00476 varInfo->clear(); 00477 } 00478 } 00479 } 00480 00481 void CheckLeakAutoVar::functionCall(const Token *tok, VarInfo *varInfo, const std::string &dealloc) 00482 { 00483 std::map<unsigned int, std::string> &alloctype = varInfo->alloctype; 00484 std::map<unsigned int, std::string> &possibleUsage = varInfo->possibleUsage; 00485 00486 // Ignore function call? 00487 const bool ignore = bool(cfgignore.find(tok->str()) != cfgignore.end()); 00488 //const bool use = bool(cfguse.find(tok->str()) != cfguse.end()); 00489 00490 if (ignore) 00491 return; 00492 00493 for (const Token *arg = tok->tokAt(2); arg; arg = arg->nextArgument()) { 00494 if ((Token::Match(arg, "%var% [-,)]") && arg->varId() > 0) || 00495 (Token::Match(arg, "& %var%") && arg->next()->varId() > 0)) { 00496 00497 // goto variable 00498 if (arg->str() == "&") 00499 arg = arg->next(); 00500 00501 // Is variable allocated? 00502 const std::map<unsigned int,std::string>::iterator var = alloctype.find(arg->varId()); 00503 if (var != alloctype.end()) { 00504 if (dealloc.empty()) { 00505 // possible usage 00506 possibleUsage[arg->varId()] = tok->str(); 00507 if (var->second == "dealloc" && arg->previous()->str() == "&") 00508 varInfo->erase(arg->varId()); 00509 } else if (var->second == "dealloc") { 00510 CheckOther checkOther(_tokenizer, _settings, _errorLogger); 00511 checkOther.doubleFreeError(tok, arg->str()); 00512 } else if (var->second != dealloc) { 00513 // mismatching allocation and deallocation 00514 mismatchError(tok, arg->str()); 00515 varInfo->erase(arg->varId()); 00516 } else { 00517 // deallocation 00518 var->second = "dealloc"; 00519 } 00520 } else if (!dealloc.empty()) { 00521 alloctype[arg->varId()] = "dealloc"; 00522 } 00523 } else if (Token::Match(arg, "%var% (")) { 00524 functionCall(arg, varInfo, dealloc); 00525 } 00526 } 00527 } 00528 00529 00530 void CheckLeakAutoVar::leakIfAllocated(const Token *vartok, 00531 const VarInfo &varInfo) 00532 { 00533 const std::map<unsigned int, std::string> &alloctype = varInfo.alloctype; 00534 const std::map<unsigned int, std::string> &possibleUsage = varInfo.possibleUsage; 00535 00536 const std::map<unsigned int,std::string>::const_iterator var = alloctype.find(vartok->varId()); 00537 if (var != alloctype.end() && var->second != "dealloc") { 00538 const std::map<unsigned int, std::string>::const_iterator use = possibleUsage.find(vartok->varId()); 00539 if (use == possibleUsage.end()) { 00540 leakError(vartok, vartok->str(), var->second); 00541 } else { 00542 configurationInfo(vartok, use->second); 00543 } 00544 } 00545 } 00546 00547 void CheckLeakAutoVar::ret(const Token *tok, const VarInfo &varInfo) 00548 { 00549 const std::map<unsigned int, std::string> &alloctype = varInfo.alloctype; 00550 const std::map<unsigned int, std::string> &possibleUsage = varInfo.possibleUsage; 00551 00552 const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); 00553 for (std::map<unsigned int, std::string>::const_iterator it = alloctype.begin(); it != alloctype.end(); ++it) { 00554 // don't warn if variable is conditionally allocated 00555 if (it->second != "dealloc" && varInfo.conditionalAlloc.find(it->first) != varInfo.conditionalAlloc.end()) 00556 continue; 00557 00558 // don't warn if there is a reference of the variable 00559 if (varInfo.referenced.find(it->first) != varInfo.referenced.end()) 00560 continue; 00561 00562 const unsigned int varid = it->first; 00563 const Variable *var = symbolDatabase->getVariableFromVarId(varid); 00564 if (var) { 00565 bool used = false; 00566 for (const Token *tok2 = tok; tok2; tok2 = tok2->next()) { 00567 if (tok2->str() == ";") 00568 break; 00569 if (Token::Match(tok2, "return|(|, %varid% [);,]", varid)) { 00570 used = true; 00571 break; 00572 } 00573 if (Token::Match(tok2, "return|(|, & %varid% . %var% [);,]", varid)) { 00574 used = true; 00575 break; 00576 } 00577 } 00578 00579 // return deallocated pointer 00580 if (used && it->second == "dealloc") 00581 deallocReturnError(tok, var->name()); 00582 00583 else if (!used && it->second != "dealloc") { 00584 00585 const std::map<unsigned int, std::string>::const_iterator use = possibleUsage.find(varid); 00586 if (use == possibleUsage.end()) { 00587 leakError(tok, var->name(), it->second); 00588 } else { 00589 configurationInfo(tok, use->second); 00590 } 00591 } 00592 } 00593 } 00594 }
1.7.6.1