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