1 /*
2 * Cppcheck - A tool for static C/C++ code analysis
3 * Copyright (C) 2007-2021 Cppcheck team.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 //---------------------------------------------------------------------------
20 // Leaks when using auto variables
21 //---------------------------------------------------------------------------
22
23 #include "checkleakautovar.h"
24
25 #include "astutils.h"
26 #include "checkmemoryleak.h" // <- CheckMemoryLeak::memoryLeak
27 #include "checknullpointer.h" // <- CheckNullPointer::isPointerDeRef
28 #include "mathlib.h"
29 #include "settings.h"
30 #include "errortypes.h"
31 #include "symboldatabase.h"
32 #include "token.h"
33 #include "tokenize.h"
34
35 #include <iostream>
36 #include <list>
37 #include <utility>
38
39 //---------------------------------------------------------------------------
40
41 // Register this check class (by creating a static instance of it)
42 namespace {
43 CheckLeakAutoVar instance;
44 }
45
46 static const CWE CWE672(672U);
47 static const CWE CWE415(415U);
48
49 // Hardcoded allocation types (not from library)
50 static const int NEW_ARRAY = -2;
51 static const int NEW = -1;
52
53 static const std::vector<std::pair<std::string, std::string>> alloc_failed_conds {{"==", "0"}, {"<", "0"}, {"==", "-1"}, {"<=", "-1"}};
54 static const std::vector<std::pair<std::string, std::string>> alloc_success_conds {{"!=", "0"}, {">", "0"}, {"!=", "-1"}, {">=", "0"}};
55
56 /**
57 * @brief Is variable type some class with automatic deallocation?
58 * @param var variable token
59 * @return true unless it can be seen there is no automatic deallocation
60 */
isAutoDealloc(const Variable * var)61 static bool isAutoDealloc(const Variable *var)
62 {
63 if (var->valueType() && var->valueType()->type != ValueType::Type::RECORD && var->valueType()->type != ValueType::Type::UNKNOWN_TYPE)
64 return false;
65
66 // return false if the type is a simple record type without side effects
67 // a type that has no side effects (no constructors and no members with constructors)
68 /** @todo false negative: check base class for side effects */
69 /** @todo false negative: check constructors for side effects */
70 if (var->typeScope() && var->typeScope()->numConstructors == 0 &&
71 (var->typeScope()->varlist.empty() || var->type()->needInitialization == Type::NeedInitialization::True) &&
72 var->type()->derivedFrom.empty())
73 return false;
74
75 return true;
76 }
77
isVarTokComparison(const Token * tok,const Token ** vartok,const std::vector<std::pair<std::string,std::string>> & ops)78 static bool isVarTokComparison(const Token * tok, const Token ** vartok,
79 const std::vector<std::pair<std::string, std::string>>& ops)
80 {
81 for (const auto & op : ops) {
82 if (astIsVariableComparison(tok, op.first, op.second, vartok))
83 return true;
84 }
85 return false;
86 }
87
88 //---------------------------------------------------------------------------
89
print()90 void VarInfo::print()
91 {
92 std::cout << "size=" << alloctype.size() << std::endl;
93 for (std::map<int, AllocInfo>::const_iterator it = alloctype.begin(); it != alloctype.end(); ++it) {
94 std::string strusage;
95 const std::map<int, std::string>::const_iterator use =
96 possibleUsage.find(it->first);
97 if (use != possibleUsage.end())
98 strusage = use->second;
99
100 std::string status;
101 switch (it->second.status) {
102 case OWNED:
103 status = "owned";
104 break;
105 case DEALLOC:
106 status = "dealloc";
107 break;
108 case ALLOC:
109 status = "alloc";
110 break;
111 case NOALLOC:
112 status = "noalloc";
113 break;
114 case REALLOC:
115 status = "realloc";
116 break;
117 default:
118 status = "?";
119 break;
120 }
121
122 std::cout << "status=" << status << " "
123 << "alloctype='" << it->second.type << "' "
124 << "possibleUsage='" << strusage << "' "
125 << "conditionalAlloc=" << (conditionalAlloc.find(it->first) != conditionalAlloc.end() ? "yes" : "no") << " "
126 << "referenced=" << (referenced.find(it->first) != referenced.end() ? "yes" : "no") << " "
127 << "reallocedFrom=" << it->second.reallocedFromType
128 << std::endl;
129 }
130 }
131
possibleUsageAll(const std::string & functionName)132 void VarInfo::possibleUsageAll(const std::string &functionName)
133 {
134 possibleUsage.clear();
135 for (std::map<int, AllocInfo>::const_iterator it = alloctype.begin(); it != alloctype.end(); ++it)
136 possibleUsage[it->first] = functionName;
137 }
138
139
leakError(const Token * tok,const std::string & varname,int type)140 void CheckLeakAutoVar::leakError(const Token *tok, const std::string &varname, int type)
141 {
142 const CheckMemoryLeak checkmemleak(mTokenizer, mErrorLogger, mSettings);
143 if (Library::isresource(type))
144 checkmemleak.resourceLeakError(tok, varname);
145 else
146 checkmemleak.memleakError(tok, varname);
147 }
148
mismatchError(const Token * deallocTok,const Token * allocTok,const std::string & varname)149 void CheckLeakAutoVar::mismatchError(const Token *deallocTok, const Token *allocTok, const std::string &varname)
150 {
151 const CheckMemoryLeak c(mTokenizer, mErrorLogger, mSettings);
152 const std::list<const Token *> callstack = { allocTok, deallocTok };
153 c.mismatchAllocDealloc(callstack, varname);
154 }
155
deallocUseError(const Token * tok,const std::string & varname)156 void CheckLeakAutoVar::deallocUseError(const Token *tok, const std::string &varname)
157 {
158 const CheckMemoryLeak c(mTokenizer, mErrorLogger, mSettings);
159 c.deallocuseError(tok, varname);
160 }
161
deallocReturnError(const Token * tok,const Token * deallocTok,const std::string & varname)162 void CheckLeakAutoVar::deallocReturnError(const Token *tok, const Token *deallocTok, const std::string &varname)
163 {
164 const std::list<const Token *> locations = { deallocTok, tok };
165 reportError(locations, Severity::error, "deallocret", "$symbol:" + varname + "\nReturning/dereferencing '$symbol' after it is deallocated / released", CWE672, Certainty::normal);
166 }
167
configurationInfo(const Token * tok,const std::string & functionName)168 void CheckLeakAutoVar::configurationInfo(const Token* tok, const std::string &functionName)
169 {
170 if (mSettings->checkLibrary && mSettings->severity.isEnabled(Severity::information)) {
171 reportError(tok,
172 Severity::information,
173 "checkLibraryUseIgnore",
174 "--check-library: Function " + functionName + "() should have <use>/<leak-ignore> configuration");
175 }
176 }
177
doubleFreeError(const Token * tok,const Token * prevFreeTok,const std::string & varname,int type)178 void CheckLeakAutoVar::doubleFreeError(const Token *tok, const Token *prevFreeTok, const std::string &varname, int type)
179 {
180 const std::list<const Token *> locations = { prevFreeTok, tok };
181
182 if (Library::isresource(type))
183 reportError(locations, Severity::error, "doubleFree", "$symbol:" + varname + "\nResource handle '$symbol' freed twice.", CWE415, Certainty::normal);
184 else
185 reportError(locations, Severity::error, "doubleFree", "$symbol:" + varname + "\nMemory pointed to by '$symbol' is freed twice.", CWE415, Certainty::normal);
186 }
187
188
check()189 void CheckLeakAutoVar::check()
190 {
191 if (mSettings->clang)
192 return;
193
194 const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
195
196 // Local variables that are known to be non-zero.
197 const std::set<int> notzero;
198
199 // Check function scopes
200 for (const Scope * scope : symbolDatabase->functionScopes) {
201 if (scope->hasInlineOrLambdaFunction())
202 continue;
203
204 // Empty variable info
205 VarInfo varInfo;
206
207 checkScope(scope->bodyStart, &varInfo, notzero, 0);
208 }
209 }
210
isVarUsedInTree(const Token * tok,nonneg int varid)211 static bool isVarUsedInTree(const Token *tok, nonneg int varid)
212 {
213 if (!tok)
214 return false;
215 if (tok->varId() == varid)
216 return true;
217 if (tok->str() == "(" && Token::simpleMatch(tok->astOperand1(), "sizeof"))
218 return false;
219 return isVarUsedInTree(tok->astOperand1(), varid) || isVarUsedInTree(tok->astOperand2(), varid);
220 }
221
isPointerReleased(const Token * startToken,const Token * endToken,nonneg int varid)222 static bool isPointerReleased(const Token *startToken, const Token *endToken, nonneg int varid)
223 {
224 for (const Token *tok = startToken; tok && tok != endToken; tok = tok->next()) {
225 if (tok->varId() != varid)
226 continue;
227 if (Token::Match(tok, "%var% . release ( )"))
228 return true;
229 if (Token::Match(tok, "%var% ="))
230 return false;
231 }
232 return false;
233 }
234
isLocalVarNoAutoDealloc(const Token * varTok,const bool isCpp)235 static bool isLocalVarNoAutoDealloc(const Token *varTok, const bool isCpp)
236 {
237 // not a local variable nor argument?
238 const Variable *var = varTok->variable();
239 if (!var)
240 return true;
241 if (!var->isArgument() && (!var->isLocal() || var->isStatic()))
242 return false;
243
244 // Don't check reference variables
245 if (var->isReference())
246 return false;
247
248 // non-pod variable
249 if (isCpp) {
250 // Possibly automatically deallocated memory
251 if (isAutoDealloc(var) && Token::Match(varTok, "%var% = new"))
252 return false;
253 if (!var->isPointer() && !var->typeStartToken()->isStandardType())
254 return false;
255 }
256 return true;
257 }
258
259 /** checks if nameToken is a name of a function in a function call:
260 * func(arg)
261 * or
262 * func<temp1_arg>(arg)
263 * @param nameToken Function name token
264 * @return opening parenthesis token or NULL if not a function call
265 */
266
isFunctionCall(const Token * nameToken)267 static const Token * isFunctionCall(const Token * nameToken)
268 {
269 if (nameToken->isName()) {
270 nameToken = nameToken->next();
271 // check if function is a template
272 if (nameToken && nameToken->link() && nameToken->str() == "<") {
273 // skip template arguments
274 nameToken = nameToken->link()->next();
275 }
276 // check for '('
277 if (nameToken && nameToken->link() && nameToken->str() == "(") {
278 // returning opening parenthesis pointer
279 return nameToken;
280 }
281 }
282 return nullptr;
283 }
284
checkScope(const Token * const startToken,VarInfo * varInfo,std::set<int> notzero,nonneg int recursiveCount)285 void CheckLeakAutoVar::checkScope(const Token * const startToken,
286 VarInfo *varInfo,
287 std::set<int> notzero,
288 nonneg int recursiveCount)
289 {
290 #if ASAN
291 static const nonneg int recursiveLimit = 300;
292 #else
293 static const nonneg int recursiveLimit = 1000;
294 #endif
295 if (++recursiveCount > recursiveLimit) // maximum number of "else if ()"
296 throw InternalError(startToken, "Internal limit: CheckLeakAutoVar::checkScope() Maximum recursive count of 1000 reached.", InternalError::LIMIT);
297
298 std::map<int, VarInfo::AllocInfo> &alloctype = varInfo->alloctype;
299 std::map<int, std::string> &possibleUsage = varInfo->possibleUsage;
300 const std::set<int> conditionalAlloc(varInfo->conditionalAlloc);
301
302 // Parse all tokens
303 const Token * const endToken = startToken->link();
304 for (const Token *tok = startToken; tok && tok != endToken; tok = tok->next()) {
305 if (!tok->scope()->isExecutable()) {
306 tok = tok->scope()->bodyEnd;
307 if (!tok) // Ticket #6666 (crash upon invalid code)
308 break;
309 }
310
311 // check each token
312 {
313 const Token * nextTok = checkTokenInsideExpression(tok, varInfo);
314 if (nextTok) {
315 tok = nextTok;
316 continue;
317 }
318 }
319
320
321 // look for end of statement
322 if (!Token::Match(tok, "[;{},]") || Token::Match(tok->next(), "[;{},]"))
323 continue;
324
325 tok = tok->next();
326 if (!tok || tok == endToken)
327 break;
328
329 if (Token::Match(tok, "const %type%"))
330 tok = tok->tokAt(2);
331
332 // parse statement, skip to last member
333 const Token *varTok = tok;
334 while (Token::Match(varTok, "%name% ::|. %name% !!("))
335 varTok = varTok->tokAt(2);
336
337 const Token *ftok = tok;
338 if (ftok->str() == "::")
339 ftok = ftok->next();
340 while (Token::Match(ftok, "%name% :: %name%"))
341 ftok = ftok->tokAt(2);
342
343 // assignment..
344 if (Token::Match(varTok, "%var% =")) {
345 const Token* const tokAssignOp = varTok->next();
346
347 // taking address of another variable..
348 if (Token::Match(tokAssignOp, "= %var% [+;]")) {
349 if (varTok->tokAt(2)->varId() != varTok->varId()) {
350 // If variable points at allocated memory => error
351 leakIfAllocated(varTok, *varInfo);
352
353 // no multivariable checking currently => bail out for rhs variables
354 for (const Token *tok2 = varTok; tok2; tok2 = tok2->next()) {
355 if (tok2->str() == ";") {
356 break;
357 }
358 if (tok2->varId()) {
359 varInfo->erase(tok2->varId());
360 }
361 }
362 }
363 }
364
365 // right ast part (after `=` operator)
366 const Token* tokRightAstOperand = tokAssignOp->astOperand2();
367 while (tokRightAstOperand && tokRightAstOperand->isCast())
368 tokRightAstOperand = tokRightAstOperand->astOperand2() ? tokRightAstOperand->astOperand2() : tokRightAstOperand->astOperand1();
369
370 // is variable used in rhs?
371 if (isVarUsedInTree(tokRightAstOperand, varTok->varId()))
372 continue;
373
374 // Variable has already been allocated => error
375 if (conditionalAlloc.find(varTok->varId()) == conditionalAlloc.end())
376 leakIfAllocated(varTok, *varInfo);
377 varInfo->erase(varTok->varId());
378
379 if (!isLocalVarNoAutoDealloc(varTok, mTokenizer->isCPP()))
380 continue;
381
382 // allocation?
383 const Token *const fTok = tokRightAstOperand ? tokRightAstOperand->previous() : nullptr;
384 if (Token::Match(fTok, "%type% (")) {
385 const Library::AllocFunc* f = mSettings->library.getAllocFuncInfo(fTok);
386 if (f && f->arg == -1) {
387 VarInfo::AllocInfo& varAlloc = alloctype[varTok->varId()];
388 varAlloc.type = f->groupId;
389 varAlloc.status = VarInfo::ALLOC;
390 varAlloc.allocTok = fTok;
391 }
392
393 changeAllocStatusIfRealloc(alloctype, fTok, varTok);
394 } else if (mTokenizer->isCPP() && Token::Match(varTok->tokAt(2), "new !!(")) {
395 const Token* tok2 = varTok->tokAt(2)->astOperand1();
396 const bool arrayNew = (tok2 && (tok2->str() == "[" || (tok2->str() == "(" && tok2->astOperand1() && tok2->astOperand1()->str() == "[")));
397 VarInfo::AllocInfo& varAlloc = alloctype[varTok->varId()];
398 varAlloc.type = arrayNew ? NEW_ARRAY : NEW;
399 varAlloc.status = VarInfo::ALLOC;
400 varAlloc.allocTok = varTok->tokAt(2);
401 }
402
403 // Assigning non-zero value variable. It might be used to
404 // track the execution for a later if condition.
405 if (Token::Match(varTok->tokAt(2), "%num% ;") && MathLib::toLongNumber(varTok->strAt(2)) != 0)
406 notzero.insert(varTok->varId());
407 else if (Token::Match(varTok->tokAt(2), "- %type% ;") && varTok->tokAt(3)->isUpperCaseName())
408 notzero.insert(varTok->varId());
409 else
410 notzero.erase(varTok->varId());
411 }
412
413 // if/else
414 else if (Token::simpleMatch(tok, "if (")) {
415 // Parse function calls inside the condition
416
417 const Token * closingParenthesis = tok->linkAt(1);
418 for (const Token *innerTok = tok->tokAt(2); innerTok && innerTok != closingParenthesis; innerTok = innerTok->next()) {
419 // TODO: replace with checkTokenInsideExpression()
420
421 if (!isLocalVarNoAutoDealloc(innerTok, mTokenizer->isCPP()))
422 continue;
423
424 if (Token::Match(innerTok, "%var% =") && innerTok->astParent() == innerTok->next()) {
425 // allocation?
426 // right ast part (after `=` operator)
427 const Token* tokRightAstOperand = innerTok->next()->astOperand2();
428 while (tokRightAstOperand && tokRightAstOperand->isCast())
429 tokRightAstOperand = tokRightAstOperand->astOperand2() ? tokRightAstOperand->astOperand2() : tokRightAstOperand->astOperand1();
430 if (tokRightAstOperand && Token::Match(tokRightAstOperand->previous(), "%type% (")) {
431 const Library::AllocFunc* f = mSettings->library.getAllocFuncInfo(tokRightAstOperand->previous());
432 if (f && f->arg == -1) {
433 VarInfo::AllocInfo& varAlloc = alloctype[innerTok->varId()];
434 varAlloc.type = f->groupId;
435 varAlloc.status = VarInfo::ALLOC;
436 varAlloc.allocTok = tokRightAstOperand->previous();
437 } else {
438 // Fixme: warn about leak
439 alloctype.erase(innerTok->varId());
440 }
441
442 changeAllocStatusIfRealloc(alloctype, innerTok->tokAt(2), varTok);
443 } else if (mTokenizer->isCPP() && Token::Match(innerTok->tokAt(2), "new !!(")) {
444 const Token* tok2 = innerTok->tokAt(2)->astOperand1();
445 const bool arrayNew = (tok2 && (tok2->str() == "[" || (tok2->str() == "(" && tok2->astOperand1() && tok2->astOperand1()->str() == "[")));
446 VarInfo::AllocInfo& varAlloc = alloctype[innerTok->varId()];
447 varAlloc.type = arrayNew ? NEW_ARRAY : NEW;
448 varAlloc.status = VarInfo::ALLOC;
449 varAlloc.allocTok = innerTok->tokAt(2);
450 }
451 }
452
453 // check for function call
454 const Token * const openingPar = isFunctionCall(innerTok);
455 if (openingPar) {
456 // innerTok is a function name
457 const VarInfo::AllocInfo allocation(0, VarInfo::NOALLOC);
458 functionCall(innerTok, openingPar, varInfo, allocation, nullptr);
459 innerTok = openingPar->link();
460 }
461 }
462
463 if (Token::simpleMatch(closingParenthesis, ") {")) {
464 VarInfo varInfo1(*varInfo); // VarInfo for if code
465 VarInfo varInfo2(*varInfo); // VarInfo for else code
466
467 // Skip expressions before commas
468 const Token * astOperand2AfterCommas = tok->next()->astOperand2();
469 while (Token::simpleMatch(astOperand2AfterCommas, ","))
470 astOperand2AfterCommas = astOperand2AfterCommas->astOperand2();
471
472 // Recursively scan variable comparisons in condition
473 visitAstNodes(astOperand2AfterCommas, [&](const Token *tok3) {
474 if (!tok3)
475 return ChildrenToVisit::none;
476 if (tok3->str() == "&&" || tok3->str() == "||") {
477 // FIXME: handle && ! || better
478 return ChildrenToVisit::op1_and_op2;
479 }
480 if (tok3->str() == "(" && Token::Match(tok3->astOperand1(), "UNLIKELY|LIKELY")) {
481 return ChildrenToVisit::op2;
482 } else if (tok3->str() == "(" && Token::Match(tok3->previous(), "%name%")) {
483 const std::vector<const Token *> params = getArguments(tok3->previous());
484 for (const Token *par : params) {
485 if (!par->isComparisonOp())
486 continue;
487 const Token *vartok = nullptr;
488 if (isVarTokComparison(par, &vartok, alloc_success_conds) ||
489 (isVarTokComparison(par, &vartok, alloc_failed_conds))) {
490 varInfo1.erase(vartok->varId());
491 varInfo2.erase(vartok->varId());
492 }
493 }
494 return ChildrenToVisit::none;
495 }
496
497 const Token *vartok = nullptr;
498 if (isVarTokComparison(tok3, &vartok, alloc_success_conds)) {
499 varInfo2.reallocToAlloc(vartok->varId());
500 varInfo2.erase(vartok->varId());
501 if (astIsVariableComparison(tok3, "!=", "0", &vartok) &&
502 (notzero.find(vartok->varId()) != notzero.end()))
503 varInfo2.clear();
504 } else if (isVarTokComparison(tok3, &vartok, alloc_failed_conds)) {
505 varInfo1.reallocToAlloc(vartok->varId());
506 varInfo1.erase(vartok->varId());
507 }
508 return ChildrenToVisit::none;
509 });
510
511 checkScope(closingParenthesis->next(), &varInfo1, notzero, recursiveCount);
512 closingParenthesis = closingParenthesis->linkAt(1);
513 if (Token::simpleMatch(closingParenthesis, "} else {")) {
514 checkScope(closingParenthesis->tokAt(2), &varInfo2, notzero, recursiveCount);
515 tok = closingParenthesis->linkAt(2)->previous();
516 } else {
517 tok = closingParenthesis->previous();
518 }
519
520 VarInfo old;
521 old.swap(*varInfo);
522
523 std::map<int, VarInfo::AllocInfo>::const_iterator it;
524
525 for (it = old.alloctype.begin(); it != old.alloctype.end(); ++it) {
526 const int varId = it->first;
527 if (old.conditionalAlloc.find(varId) == old.conditionalAlloc.end())
528 continue;
529 if (varInfo1.alloctype.find(varId) == varInfo1.alloctype.end() ||
530 varInfo2.alloctype.find(varId) == varInfo2.alloctype.end()) {
531 varInfo1.erase(varId);
532 varInfo2.erase(varId);
533 }
534 }
535
536 // Conditional allocation in varInfo1
537 for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) {
538 if (varInfo2.alloctype.find(it->first) == varInfo2.alloctype.end() &&
539 old.alloctype.find(it->first) == old.alloctype.end()) {
540 varInfo->conditionalAlloc.insert(it->first);
541 }
542 }
543
544 // Conditional allocation in varInfo2
545 for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) {
546 if (varInfo1.alloctype.find(it->first) == varInfo1.alloctype.end() &&
547 old.alloctype.find(it->first) == old.alloctype.end()) {
548 varInfo->conditionalAlloc.insert(it->first);
549 }
550 }
551
552 // Conditional allocation/deallocation
553 for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) {
554 if (it->second.managed() && conditionalAlloc.find(it->first) != conditionalAlloc.end()) {
555 varInfo->conditionalAlloc.erase(it->first);
556 varInfo2.erase(it->first);
557 }
558 }
559 for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) {
560 if (it->second.managed() && conditionalAlloc.find(it->first) != conditionalAlloc.end()) {
561 varInfo->conditionalAlloc.erase(it->first);
562 varInfo1.erase(it->first);
563 }
564 }
565
566 alloctype.insert(varInfo1.alloctype.begin(), varInfo1.alloctype.end());
567 alloctype.insert(varInfo2.alloctype.begin(), varInfo2.alloctype.end());
568
569 possibleUsage.insert(varInfo1.possibleUsage.begin(), varInfo1.possibleUsage.end());
570 possibleUsage.insert(varInfo2.possibleUsage.begin(), varInfo2.possibleUsage.end());
571 }
572 }
573
574 // unknown control.. (TODO: handle loops)
575 else if ((Token::Match(tok, "%type% (") && Token::simpleMatch(tok->linkAt(1), ") {")) || Token::simpleMatch(tok, "do {")) {
576 varInfo->clear();
577 break;
578 }
579
580 // return
581 else if (tok->str() == "return") {
582 ret(tok, *varInfo);
583 varInfo->clear();
584 }
585
586 // throw
587 else if (mTokenizer->isCPP() && tok->str() == "throw") {
588 bool tryFound = false;
589 const Scope* scope = tok->scope();
590 while (scope && scope->isExecutable()) {
591 if (scope->type == Scope::eTry)
592 tryFound = true;
593 scope = scope->nestedIn;
594 }
595 // If the execution leaves the function then treat it as return
596 if (!tryFound)
597 ret(tok, *varInfo);
598 varInfo->clear();
599 }
600
601 // delete
602 else if (mTokenizer->isCPP() && tok->str() == "delete") {
603 const Token * delTok = tok;
604 const bool arrayDelete = Token::simpleMatch(tok->next(), "[ ]");
605 if (arrayDelete)
606 tok = tok->tokAt(3);
607 else
608 tok = tok->next();
609 if (tok->str() == "(")
610 tok = tok->next();
611 while (Token::Match(tok, "%name% ::|."))
612 tok = tok->tokAt(2);
613 const bool isnull = tok->hasKnownIntValue() && tok->values().front().intvalue == 0;
614 if (!isnull && tok->varId() && tok->strAt(1) != "[") {
615 const VarInfo::AllocInfo allocation(arrayDelete ? NEW_ARRAY : NEW, VarInfo::DEALLOC, delTok);
616 changeAllocStatus(varInfo, allocation, tok, tok);
617 }
618 }
619
620 // Function call..
621 else if (isFunctionCall(ftok)) {
622 const Token * openingPar = isFunctionCall(ftok);
623 const Library::AllocFunc* af = mSettings->library.getDeallocFuncInfo(ftok);
624 VarInfo::AllocInfo allocation(af ? af->groupId : 0, VarInfo::DEALLOC, ftok);
625 if (allocation.type == 0)
626 allocation.status = VarInfo::NOALLOC;
627 functionCall(ftok, openingPar, varInfo, allocation, af);
628
629 tok = ftok->next()->link();
630
631 // Handle scopes that might be noreturn
632 if (allocation.status == VarInfo::NOALLOC && Token::simpleMatch(tok, ") ; }")) {
633 const std::string &functionName(tok->link()->previous()->str());
634 bool unknown = false;
635 if (mTokenizer->isScopeNoReturn(tok->tokAt(2), &unknown)) {
636 if (!unknown)
637 varInfo->clear();
638 else if (!mSettings->library.isLeakIgnore(functionName) && !mSettings->library.isUse(functionName))
639 varInfo->possibleUsageAll(functionName);
640 }
641 }
642
643 continue;
644 }
645
646 // goto => weird execution path
647 else if (tok->str() == "goto") {
648 varInfo->clear();
649 }
650
651 // continue/break
652 else if (Token::Match(tok, "continue|break ;")) {
653 varInfo->clear();
654 }
655
656 // Check smart pointer
657 else if (Token::Match(ftok, "%name% <") && mSettings->library.isSmartPointer(tok)) {
658 const Token * typeEndTok = ftok->linkAt(1);
659 if (!Token::Match(typeEndTok, "> %var% {|( %var% ,|)|}"))
660 continue;
661
662 tok = typeEndTok->linkAt(2);
663
664 const int varid = typeEndTok->next()->varId();
665 if (isPointerReleased(typeEndTok->tokAt(2), endToken, varid))
666 continue;
667
668 bool arrayDelete = false;
669 if (Token::findsimplematch(ftok->next(), "[ ]", typeEndTok))
670 arrayDelete = true;
671
672 // Check deleter
673 const Token * deleterToken = nullptr;
674 const Token * endDeleterToken = nullptr;
675 const Library::AllocFunc* af = nullptr;
676 if (Token::Match(ftok, "unique_ptr < %type% ,")) {
677 deleterToken = ftok->tokAt(4);
678 endDeleterToken = typeEndTok;
679 } else if (Token::Match(typeEndTok, "> %var% {|( %var% ,")) {
680 deleterToken = typeEndTok->tokAt(5);
681 endDeleterToken = typeEndTok->linkAt(2);
682 }
683 if (deleterToken) {
684 // Skip the decaying plus in expressions like +[](T*){}
685 if (deleterToken->str() == "+") {
686 deleterToken = deleterToken->next();
687 }
688 // Check if its a pointer to a function
689 const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken);
690 if (dtok) {
691 af = mSettings->library.getDeallocFuncInfo(dtok->tokAt(1));
692 } else {
693 const Token * tscopeStart = nullptr;
694 const Token * tscopeEnd = nullptr;
695 // If the deleter is a lambda, check if it calls the dealloc function
696 if (deleterToken->str() == "[" &&
697 Token::simpleMatch(deleterToken->link(), "] (") &&
698 // TODO: Check for mutable keyword
699 Token::simpleMatch(deleterToken->link()->linkAt(1), ") {")) {
700 tscopeStart = deleterToken->link()->linkAt(1)->tokAt(1);
701 tscopeEnd = tscopeStart->link();
702 // If the deleter is a class, check if class calls the dealloc function
703 } else if ((dtok = Token::findmatch(deleterToken, "%type%", endDeleterToken)) && dtok->type()) {
704 const Scope * tscope = dtok->type()->classScope;
705 if (tscope) {
706 tscopeStart = tscope->bodyStart;
707 tscopeEnd = tscope->bodyEnd;
708 }
709 }
710
711 if (tscopeStart && tscopeEnd) {
712 for (const Token *tok2 = tscopeStart; tok2 != tscopeEnd; tok2 = tok2->next()) {
713 af = mSettings->library.getDeallocFuncInfo(tok2);
714 if (af)
715 break;
716 }
717 }
718 }
719 }
720
721 const Token * vtok = typeEndTok->tokAt(3);
722 const VarInfo::AllocInfo allocation(af ? af->groupId : (arrayDelete ? NEW_ARRAY : NEW), VarInfo::OWNED, ftok);
723 changeAllocStatus(varInfo, allocation, vtok, vtok);
724 }
725 }
726 ret(endToken, *varInfo, true);
727 }
728
729
checkTokenInsideExpression(const Token * const tok,VarInfo * varInfo)730 const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const tok, VarInfo *varInfo)
731 {
732 // Deallocation and then dereferencing pointer..
733 if (tok->varId() > 0) {
734 // TODO : Write a separate checker for this that uses valueFlowForward.
735 const std::map<int, VarInfo::AllocInfo>::const_iterator var = varInfo->alloctype.find(tok->varId());
736 if (var != varInfo->alloctype.end()) {
737 bool unknown = false;
738 if (var->second.status == VarInfo::DEALLOC && CheckNullPointer::isPointerDeRef(tok, unknown, mSettings) && !unknown) {
739 deallocUseError(tok, tok->str());
740 } else if (Token::simpleMatch(tok->tokAt(-2), "= &")) {
741 varInfo->erase(tok->varId());
742 } else {
743 // check if tok is assigned into another variable
744 const Token *rhs = tok;
745 while (rhs->astParent()) {
746 if (rhs->astParent()->str() == "=")
747 break;
748 rhs = rhs->astParent();
749 }
750 while (rhs->isCast()) {
751 rhs = rhs->astOperand1();
752 }
753 if (rhs->varId() == tok->varId()) {
754 // simple assignment
755 varInfo->erase(tok->varId());
756 } else if (rhs->str() == "(" && mSettings->library.returnValue(rhs->astOperand1()) != emptyString) {
757 // #9298, assignment through return value of a function
758 const std::string &returnValue = mSettings->library.returnValue(rhs->astOperand1());
759 if (returnValue.compare(0, 3, "arg") == 0) {
760 int argn;
761 const Token *func = getTokenArgumentFunction(tok, argn);
762 if (func) {
763 const std::string arg = "arg" + std::to_string(argn + 1);
764 if (returnValue == arg) {
765 varInfo->erase(tok->varId());
766 }
767 }
768 }
769 }
770 }
771 } else if (Token::Match(tok->previous(), "& %name% = %var% ;")) {
772 varInfo->referenced.insert(tok->tokAt(2)->varId());
773 }
774 }
775
776 // check for function call
777 const Token * const openingPar = isFunctionCall(tok);
778 if (openingPar) {
779 const Library::AllocFunc* allocFunc = mSettings->library.getDeallocFuncInfo(tok);
780 VarInfo::AllocInfo alloc(allocFunc ? allocFunc->groupId : 0, VarInfo::DEALLOC, tok);
781 if (alloc.type == 0)
782 alloc.status = VarInfo::NOALLOC;
783 functionCall(tok, openingPar, varInfo, alloc, nullptr);
784 const std::string &returnValue = mSettings->library.returnValue(tok);
785 if (returnValue.compare(0, 3, "arg") == 0)
786 // the function returns one of its argument, we need to process a potential assignment
787 return openingPar;
788 return openingPar->link();
789 }
790
791 return nullptr;
792 }
793
794
changeAllocStatusIfRealloc(std::map<int,VarInfo::AllocInfo> & alloctype,const Token * fTok,const Token * retTok)795 void CheckLeakAutoVar::changeAllocStatusIfRealloc(std::map<int, VarInfo::AllocInfo> &alloctype, const Token *fTok, const Token *retTok)
796 {
797 const Library::AllocFunc* f = mSettings->library.getReallocFuncInfo(fTok);
798 if (f && f->arg == -1 && f->reallocArg > 0 && f->reallocArg <= numberOfArguments(fTok)) {
799 const Token* argTok = getArguments(fTok).at(f->reallocArg - 1);
800 if (alloctype.find(argTok->varId()) != alloctype.end()) {
801 VarInfo::AllocInfo& argAlloc = alloctype[argTok->varId()];
802 if (argAlloc.type != 0 && argAlloc.type != f->groupId)
803 mismatchError(fTok, argAlloc.allocTok, argTok->str());
804 argAlloc.status = VarInfo::REALLOC;
805 argAlloc.allocTok = fTok;
806 }
807 VarInfo::AllocInfo& retAlloc = alloctype[retTok->varId()];
808 retAlloc.type = f->groupId;
809 retAlloc.status = VarInfo::ALLOC;
810 retAlloc.allocTok = fTok;
811 retAlloc.reallocedFromType = argTok->varId();
812 }
813 }
814
815
changeAllocStatus(VarInfo * varInfo,const VarInfo::AllocInfo & allocation,const Token * tok,const Token * arg)816 void CheckLeakAutoVar::changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocInfo& allocation, const Token* tok, const Token* arg)
817 {
818 std::map<int, VarInfo::AllocInfo> &alloctype = varInfo->alloctype;
819 const std::map<int, VarInfo::AllocInfo>::iterator var = alloctype.find(arg->varId());
820 if (var != alloctype.end()) {
821 if (allocation.status == VarInfo::NOALLOC) {
822 // possible usage
823 varInfo->possibleUsage[arg->varId()] = tok->str();
824 if (var->second.status == VarInfo::DEALLOC && arg->previous()->str() == "&")
825 varInfo->erase(arg->varId());
826 } else if (var->second.managed()) {
827 doubleFreeError(tok, var->second.allocTok, arg->str(), allocation.type);
828 var->second.status = allocation.status;
829 } else if (var->second.type != allocation.type && var->second.type != 0) {
830 // mismatching allocation and deallocation
831 mismatchError(tok, var->second.allocTok, arg->str());
832 varInfo->erase(arg->varId());
833 } else {
834 // deallocation
835 var->second.status = allocation.status;
836 var->second.type = allocation.type;
837 var->second.allocTok = allocation.allocTok;
838 }
839 } else if (allocation.status != VarInfo::NOALLOC) {
840 alloctype[arg->varId()].status = VarInfo::DEALLOC;
841 alloctype[arg->varId()].allocTok = tok;
842 }
843 }
844
functionCall(const Token * tokName,const Token * tokOpeningPar,VarInfo * varInfo,const VarInfo::AllocInfo & allocation,const Library::AllocFunc * af)845 void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpeningPar, VarInfo *varInfo, const VarInfo::AllocInfo& allocation, const Library::AllocFunc* af)
846 {
847 // Ignore function call?
848 if (mSettings->library.isLeakIgnore(tokName->str()))
849 return;
850 if (mSettings->library.getReallocFuncInfo(tokName))
851 return;
852
853 const Token * const tokFirstArg = tokOpeningPar->next();
854 if (!tokFirstArg || tokFirstArg->str() == ")") {
855 // no arguments
856 return;
857 }
858
859 int argNr = 1;
860 for (const Token *funcArg = tokFirstArg; funcArg; funcArg = funcArg->nextArgument()) {
861 const Token* arg = funcArg;
862 if (mTokenizer->isCPP() && arg->str() == "new") {
863 arg = arg->next();
864 if (Token::simpleMatch(arg, "( std :: nothrow )"))
865 arg = arg->tokAt(5);
866 }
867
868 // Skip casts
869 while (arg && arg->isCast())
870 arg = arg->astOperand2() ? arg->astOperand2() : arg->astOperand1();
871 const Token * const argTypeStartTok = arg;
872
873 while (Token::Match(arg, "%name% .|:: %name%"))
874 arg = arg->tokAt(2);
875
876 if (Token::Match(arg, "%var% [-,)] !!.") || Token::Match(arg, "& %var%")) {
877 // goto variable
878 if (arg->str() == "&")
879 arg = arg->next();
880
881 const bool isnull = arg->hasKnownIntValue() && arg->values().front().intvalue == 0;
882
883 // Is variable allocated?
884 if (!isnull && (!af || af->arg == argNr))
885 changeAllocStatus(varInfo, allocation, tokName, arg);
886 }
887 // Check smart pointer
888 else if (Token::Match(arg, "%name% < %type%") && mSettings->library.isSmartPointer(argTypeStartTok)) {
889 const Token * typeEndTok = arg->linkAt(1);
890 const Token * allocTok = nullptr;
891 if (!Token::Match(typeEndTok, "> {|( %var% ,|)|}"))
892 continue;
893
894 bool arrayDelete = false;
895 if (Token::findsimplematch(arg->next(), "[ ]", typeEndTok))
896 arrayDelete = true;
897
898 // Check deleter
899 const Token * deleterToken = nullptr;
900 const Token * endDeleterToken = nullptr;
901 const Library::AllocFunc* sp_af = nullptr;
902 if (Token::Match(arg, "unique_ptr < %type% ,")) {
903 deleterToken = arg->tokAt(4);
904 endDeleterToken = typeEndTok;
905 } else if (Token::Match(typeEndTok, "> {|( %var% ,")) {
906 deleterToken = typeEndTok->tokAt(4);
907 endDeleterToken = typeEndTok->linkAt(1);
908 }
909 if (deleterToken) {
910 // Check if its a pointer to a function
911 const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken);
912 if (dtok) {
913 sp_af = mSettings->library.getDeallocFuncInfo(dtok->tokAt(1));
914 } else {
915 // If the deleter is a class, check if class calls the dealloc function
916 dtok = Token::findmatch(deleterToken, "%type%", endDeleterToken);
917 if (dtok && dtok->type()) {
918 const Scope * tscope = dtok->type()->classScope;
919 for (const Token *tok2 = tscope->bodyStart; tok2 != tscope->bodyEnd; tok2 = tok2->next()) {
920 sp_af = mSettings->library.getDeallocFuncInfo(tok2);
921 if (sp_af) {
922 allocTok = tok2;
923 break;
924 }
925 }
926 }
927 }
928 }
929
930 const Token * vtok = typeEndTok->tokAt(2);
931 const VarInfo::AllocInfo sp_allocation(sp_af ? sp_af->groupId : (arrayDelete ? NEW_ARRAY : NEW), VarInfo::OWNED, allocTok);
932 changeAllocStatus(varInfo, sp_allocation, vtok, vtok);
933 } else {
934 checkTokenInsideExpression(arg, varInfo);
935 }
936 // TODO: check each token in argument expression (could contain multiple variables)
937 argNr++;
938 }
939 }
940
941
leakIfAllocated(const Token * vartok,const VarInfo & varInfo)942 void CheckLeakAutoVar::leakIfAllocated(const Token *vartok,
943 const VarInfo &varInfo)
944 {
945 const std::map<int, VarInfo::AllocInfo> &alloctype = varInfo.alloctype;
946 const std::map<int, std::string> &possibleUsage = varInfo.possibleUsage;
947
948 const std::map<int, VarInfo::AllocInfo>::const_iterator var = alloctype.find(vartok->varId());
949 if (var != alloctype.end() && var->second.status == VarInfo::ALLOC) {
950 const std::map<int, std::string>::const_iterator use = possibleUsage.find(vartok->varId());
951 if (use == possibleUsage.end()) {
952 leakError(vartok, vartok->str(), var->second.type);
953 } else {
954 configurationInfo(vartok, use->second);
955 }
956 }
957 }
958
ret(const Token * tok,VarInfo & varInfo,const bool isEndOfScope)959 void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndOfScope)
960 {
961 const std::map<int, VarInfo::AllocInfo> &alloctype = varInfo.alloctype;
962 const std::map<int, std::string> &possibleUsage = varInfo.possibleUsage;
963 std::vector<int> toRemove;
964
965 const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
966 for (std::map<int, VarInfo::AllocInfo>::const_iterator it = alloctype.begin(); it != alloctype.end(); ++it) {
967 // don't warn if variable is conditionally allocated, unless it leaves the scope
968 if (!isEndOfScope && !it->second.managed() && varInfo.conditionalAlloc.find(it->first) != varInfo.conditionalAlloc.end())
969 continue;
970
971 // don't warn if there is a reference of the variable
972 if (varInfo.referenced.find(it->first) != varInfo.referenced.end())
973 continue;
974
975 const int varid = it->first;
976 const Variable *var = symbolDatabase->getVariableFromVarId(varid);
977 if (var) {
978 // don't warn if we leave an inner scope
979 if (isEndOfScope && var->scope() && tok != var->scope()->bodyEnd)
980 continue;
981 bool used = false;
982 for (const Token *tok2 = tok; tok2; tok2 = tok2->next()) {
983 if (tok2->str() == ";")
984 break;
985 if (!Token::Match(tok2, "return|(|{|,"))
986 continue;
987
988 const Token* tok3 = tok2->next();
989 while (tok3 && tok3->isCast() && tok3->valueType() &&
990 (tok3->valueType()->pointer ||
991 (tok3->valueType()->typeSize(*mSettings) == 0) ||
992 (tok3->valueType()->typeSize(*mSettings) >= mSettings->sizeof_pointer)))
993 tok3 = tok3->astOperand2() ? tok3->astOperand2() : tok3->astOperand1();
994 if (Token::Match(tok3, "%varid%", varid))
995 tok2 = tok3->next();
996 else if (Token::Match(tok3, "& %varid% . %name%", varid))
997 tok2 = tok3->tokAt(4);
998 else
999 continue;
1000 if (Token::Match(tok2, "[});,]")) {
1001 used = true;
1002 break;
1003 }
1004 }
1005
1006 // return deallocated pointer
1007 if (used && it->second.status == VarInfo::DEALLOC)
1008 deallocReturnError(tok, it->second.allocTok, var->name());
1009
1010 else if (!used && !it->second.managed()) {
1011 const std::map<int, std::string>::const_iterator use = possibleUsage.find(varid);
1012 if (use == possibleUsage.end()) {
1013 leakError(tok, var->name(), it->second.type);
1014 } else {
1015 configurationInfo(tok, use->second);
1016 }
1017 }
1018 toRemove.push_back(varid);
1019 }
1020 }
1021 for (int varId : toRemove)
1022 varInfo.erase(varId);
1023 }
1024