cppcheck/lib/checkmemoryleak.cpp
Rikard Falkeborn fc1d5b187f leakNoVarFunctionCall: Use AST more (fix #9252) (#2086)
Use the AST a little bit more to improve the check. In order to do so,
rewrite the check to work from the outer function first and then check
the arguments, instead of the other way around.

It also fixes Trac ticket #9252, no warning is now given for

	void* malloc1() {
		return(malloc1(1));
	}

This FP seems to be common in daca results.

It also makes it possible to improve handling of casts, for example
cppcheck now warns about

	void f() {
		strcpy(a, (void*) strdup(p));
	}

But not for

	char* f() {
		char* ret = (char*)strcpy(malloc(10), "abc");
		return ret;
	}

These FP/FN were introduced when the check was switched to use the
simplified token list.
2019-08-14 22:01:40 +02:00

1139 lines
46 KiB
C++

/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2019 Cppcheck team.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "checkmemoryleak.h"
#include "astutils.h"
#include "library.h"
#include "mathlib.h"
#include "settings.h"
#include "standards.h"
#include "symboldatabase.h"
#include "token.h"
#include "tokenize.h"
#include "tokenlist.h"
#include "utils.h"
#include "valueflow.h"
#include <algorithm>
#include <cstddef>
#include <set>
#include <stack>
//---------------------------------------------------------------------------
// Register this check class (by creating a static instance of it)
namespace {
CheckMemoryLeakInFunction instance1;
CheckMemoryLeakInClass instance2;
CheckMemoryLeakStructMember instance3;
CheckMemoryLeakNoVar instance4;
}
// CWE ID used:
static const CWE CWE398(398U); // Indicator of Poor Code Quality
static const CWE CWE401(401U); // Improper Release of Memory Before Removing Last Reference ('Memory Leak')
static const CWE CWE771(771U); // Missing Reference to Active Allocated Resource
static const CWE CWE772(772U); // Missing Release of Resource after Effective Lifetime
/** List of functions that can be ignored when searching for memory leaks.
* These functions don't take the address of the given pointer
* This list contains function names with const parameters e.g.: atof(const char *)
* TODO: This list should be replaced by <leak-ignore/> in .cfg files.
*/
static const std::set<std::string> call_func_white_list = {
"_open", "_wopen", "access", "adjtime", "asctime_r", "asprintf", "chdir", "chmod", "chown"
, "creat", "ctime_r", "execl", "execle", "execlp", "execv", "execve", "fchmod", "fcntl"
, "fdatasync", "fclose", "flock", "fmemopen", "fnmatch", "fopen", "fopencookie", "for", "free"
, "freopen", "fseeko", "fstat", "fsync", "ftello", "ftruncate", "getgrnam", "gethostbyaddr", "gethostbyname"
, "getnetbyname", "getopt", "getopt_long", "getprotobyname", "getpwnam", "getservbyname", "getservbyport"
, "glob", "gmtime", "gmtime_r", "if", "index", "inet_addr", "inet_aton", "inet_network", "initgroups"
, "ioctl", "link", "localtime_r", "lockf", "lseek", "lstat", "mkdir", "mkfifo", "mknod", "mkstemp"
, "obstack_printf", "obstack_vprintf", "open", "opendir", "parse_printf_format", "pathconf"
, "perror", "popen", "posix_fadvise", "posix_fallocate", "pread", "psignal", "pwrite", "read", "readahead"
, "readdir", "readdir_r", "readlink", "readv", "realloc", "regcomp", "return", "rewinddir", "rindex"
, "rmdir", "scandir", "seekdir", "setbuffer", "sethostname", "setlinebuf", "sizeof", "strdup"
, "stat", "stpcpy", "strcasecmp", "stricmp", "strncasecmp", "switch"
, "symlink", "sync_file_range", "telldir", "tempnam", "time", "typeid", "unlink"
, "utime", "utimes", "vasprintf", "while", "wordexp", "write", "writev"
};
//---------------------------------------------------------------------------
CheckMemoryLeak::AllocType CheckMemoryLeak::getAllocationType(const Token *tok2, nonneg int varid, std::list<const Function*> *callstack) const
{
// What we may have...
// * var = (char *)malloc(10);
// * var = new char[10];
// * var = strdup("hello");
// * var = strndup("hello", 3);
if (tok2 && tok2->str() == "(") {
tok2 = tok2->link();
tok2 = tok2 ? tok2->next() : nullptr;
}
if (! tok2)
return No;
if (tok2->str() == "::")
tok2 = tok2->next();
if (! tok2->isName())
return No;
if (!Token::Match(tok2, "%name% ::|. %type%")) {
// Using realloc..
AllocType reallocType = getReallocationType(tok2, varid);
if (reallocType != No)
return reallocType;
if (mTokenizer_->isCPP() && tok2->str() == "new") {
if (tok2->strAt(1) == "(" && !Token::Match(tok2->next(),"( std| ::| nothrow )"))
return No;
if (tok2->astOperand1() && (tok2->astOperand1()->str() == "[" || (tok2->astOperand1()->astOperand1() && tok2->astOperand1()->astOperand1()->str() == "[")))
return NewArray;
const Token *typeTok = tok2->next();
while (Token::Match(typeTok, "%name% :: %name%"))
typeTok = typeTok->tokAt(2);
if (typeTok->type() && typeTok->type()->isClassType()) {
const Scope *classScope = typeTok->type()->classScope;
if (classScope && classScope->numConstructors > 0)
return No;
}
return New;
}
if (mSettings_->posix()) {
if (Token::Match(tok2, "open|openat|creat|mkstemp|mkostemp|socket (")) {
// simple sanity check of function parameters..
// TODO: Make such check for all these functions
const int num = numberOfArguments(tok2);
if (tok2->str() == "open" && num != 2 && num != 3)
return No;
// is there a user function with this name?
if (tok2->function())
return No;
return Fd;
}
if (Token::simpleMatch(tok2, "popen ("))
return Pipe;
}
// Does tok2 point on a Library allocation function?
const int alloctype = mSettings_->library.getAllocId(tok2, -1);
if (alloctype > 0) {
if (alloctype == mSettings_->library.deallocId("free"))
return Malloc;
if (alloctype == mSettings_->library.deallocId("fclose"))
return File;
return Library::ismemory(alloctype) ? OtherMem : OtherRes;
}
}
while (Token::Match(tok2,"%name% ::|. %type%"))
tok2 = tok2->tokAt(2);
// User function
const Function* func = tok2->function();
if (func == nullptr)
return No;
// Prevent recursion
if (callstack && std::find(callstack->begin(), callstack->end(), func) != callstack->end())
return No;
std::list<const Function*> cs;
if (!callstack)
callstack = &cs;
callstack->push_back(func);
return functionReturnType(func, callstack);
}
CheckMemoryLeak::AllocType CheckMemoryLeak::getReallocationType(const Token *tok2, nonneg int varid) const
{
// What we may have...
// * var = (char *)realloc(..;
if (tok2 && tok2->str() == "(") {
tok2 = tok2->link();
tok2 = tok2 ? tok2->next() : nullptr;
}
if (! tok2)
return No;
if (!Token::Match(tok2, "%name% ("))
return No;
const Library::AllocFunc *f = mSettings_->library.getReallocFuncInfo(tok2);
if (!(f && f->reallocArg > 0 && f->reallocArg <= numberOfArguments(tok2)))
return No;
const Token* arg = getArguments(tok2).at(f->reallocArg - 1);
while (arg && arg->isCast())
arg = arg->astOperand1();
while (arg && arg->isUnaryOp("*"))
arg = arg->astOperand1();
if (varid > 0 && !Token::Match(arg, "%varid% [,)]", varid))
return No;
const int realloctype = mSettings_->library.getReallocId(tok2, -1);
if (realloctype > 0) {
if (realloctype == mSettings_->library.deallocId("free"))
return Malloc;
if (realloctype == mSettings_->library.deallocId("fclose"))
return File;
return Library::ismemory(realloctype) ? OtherMem : OtherRes;
}
return No;
}
CheckMemoryLeak::AllocType CheckMemoryLeak::getDeallocationType(const Token *tok, nonneg int varid) const
{
if (mTokenizer_->isCPP() && tok->str() == "delete" && tok->astOperand1()) {
const Token* vartok = tok->astOperand1();
if (Token::Match(vartok, ".|::"))
vartok = vartok->astOperand2();
if (vartok && vartok->varId() == varid) {
if (tok->strAt(1) == "[")
return NewArray;
return New;
}
}
if (tok->str() == "::")
tok = tok->next();
if (Token::Match(tok, "%name% (")) {
if (Token::simpleMatch(tok, "fcloseall ( )"))
return File;
int argNr = 1;
for (const Token* tok2 = tok->tokAt(2); tok2; tok2 = tok2->nextArgument()) {
const Token* vartok = tok2;
while (Token::Match(vartok, "%name% .|::"))
vartok = vartok->tokAt(2);
if (Token::Match(vartok, "%varid% )|,|-", varid)) {
if (tok->str() == "realloc" && Token::simpleMatch(vartok->next(), ", 0 )"))
return Malloc;
if (mSettings_->posix()) {
if (tok->str() == "close")
return Fd;
if (tok->str() == "pclose")
return Pipe;
}
// Does tok point on a Library deallocation function?
const int dealloctype = mSettings_->library.getDeallocId(tok, argNr);
if (dealloctype > 0) {
if (dealloctype == mSettings_->library.deallocId("free"))
return Malloc;
if (dealloctype == mSettings_->library.deallocId("fclose"))
return File;
return Library::ismemory(dealloctype) ? OtherMem : OtherRes;
}
}
argNr++;
}
}
return No;
}
bool CheckMemoryLeak::isReopenStandardStream(const Token *tok) const
{
if (getReallocationType(tok, 0) == File) {
const Library::AllocFunc *f = mSettings_->library.getReallocFuncInfo(tok);
if (f && f->reallocArg > 0 && f->reallocArg <= numberOfArguments(tok)) {
const Token* arg = getArguments(tok).at(f->reallocArg - 1);
if (Token::Match(arg, "stdin|stdout|stderr"))
return true;
}
}
return false;
}
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
void CheckMemoryLeak::memoryLeak(const Token *tok, const std::string &varname, AllocType alloctype) const
{
if (alloctype == CheckMemoryLeak::File ||
alloctype == CheckMemoryLeak::Pipe ||
alloctype == CheckMemoryLeak::Fd ||
alloctype == CheckMemoryLeak::OtherRes)
resourceLeakError(tok, varname);
else
memleakError(tok, varname);
}
//---------------------------------------------------------------------------
void CheckMemoryLeak::reportErr(const Token *tok, Severity::SeverityType severity, const std::string &id, const std::string &msg, const CWE &cwe) const
{
std::list<const Token *> callstack;
if (tok)
callstack.push_back(tok);
reportErr(callstack, severity, id, msg, cwe);
}
void CheckMemoryLeak::reportErr(const std::list<const Token *> &callstack, Severity::SeverityType severity, const std::string &id, const std::string &msg, const CWE &cwe) const
{
const ErrorLogger::ErrorMessage errmsg(callstack, mTokenizer_ ? &mTokenizer_->list : nullptr, severity, id, msg, cwe, false);
if (mErrorLogger_)
mErrorLogger_->reportErr(errmsg);
else
Check::reportError(errmsg);
}
void CheckMemoryLeak::memleakError(const Token *tok, const std::string &varname) const
{
reportErr(tok, Severity::error, "memleak", "$symbol:" + varname + "\nMemory leak: $symbol", CWE(401U));
}
void CheckMemoryLeak::memleakUponReallocFailureError(const Token *tok, const std::string &reallocfunction, const std::string &varname) const
{
reportErr(tok, Severity::error, "memleakOnRealloc", "$symbol:" + varname + "\nCommon " + reallocfunction + " mistake: \'$symbol\' nulled but not freed upon failure", CWE(401U));
}
void CheckMemoryLeak::resourceLeakError(const Token *tok, const std::string &varname) const
{
std::string errmsg("Resource leak");
if (!varname.empty())
errmsg = "$symbol:" + varname + '\n' + errmsg + ": $symbol";
reportErr(tok, Severity::error, "resourceLeak", errmsg, CWE(775U));
}
void CheckMemoryLeak::deallocDeallocError(const Token *tok, const std::string &varname) const
{
reportErr(tok, Severity::error, "deallocDealloc", "$symbol:" + varname + "\nDeallocating a deallocated pointer: $symbol", CWE(415U));
}
void CheckMemoryLeak::deallocuseError(const Token *tok, const std::string &varname) const
{
reportErr(tok, Severity::error, "deallocuse", "$symbol:" + varname + "\nDereferencing '$symbol' after it is deallocated / released", CWE(416U));
}
void CheckMemoryLeak::mismatchSizeError(const Token *tok, const std::string &sz) const
{
reportErr(tok, Severity::error, "mismatchSize", "The allocated size " + sz + " is not a multiple of the underlying type's size.", CWE(131U));
}
void CheckMemoryLeak::mismatchAllocDealloc(const std::list<const Token *> &callstack, const std::string &varname) const
{
reportErr(callstack, Severity::error, "mismatchAllocDealloc", "$symbol:" + varname + "\nMismatching allocation and deallocation: $symbol", CWE(762U));
}
CheckMemoryLeak::AllocType CheckMemoryLeak::functionReturnType(const Function* func, std::list<const Function*> *callstack) const
{
if (!func || !func->hasBody() || !func->functionScope)
return No;
// Get return pointer..
int varid = 0;
for (const Token *tok2 = func->functionScope->bodyStart; tok2 != func->functionScope->bodyEnd; tok2 = tok2->next()) {
if (const Token *endOfLambda = findLambdaEndToken(tok2))
tok2 = endOfLambda;
if (tok2->str() == "{" && !tok2->scope()->isExecutable())
tok2 = tok2->link();
if (tok2->str() == "return") {
const AllocType allocType = getAllocationType(tok2->next(), 0, callstack);
if (allocType != No)
return allocType;
if (tok2->scope() != func->functionScope || !tok2->astOperand1())
return No;
const Token* tok = tok2->astOperand1();
if (Token::Match(tok, ".|::"))
tok = tok->astOperand2() ? tok->astOperand2() : tok->astOperand1();
if (tok)
varid = tok->varId();
break;
}
}
// Not returning pointer value..
if (varid == 0)
return No;
// If variable is not local then alloctype shall be "No"
// Todo: there can be false negatives about mismatching allocation/deallocation.
// => Generate "alloc ; use ;" if variable is not local?
const Variable *var = mTokenizer_->getSymbolDatabase()->getVariableFromVarId(varid);
if (!var || !var->isLocal() || var->isStatic())
return No;
// Check if return pointer is allocated..
AllocType allocType = No;
for (const Token* tok = func->functionScope->bodyStart; tok != func->functionScope->bodyEnd; tok = tok->next()) {
if (Token::Match(tok, "%varid% =", varid)) {
allocType = getAllocationType(tok->tokAt(2), varid, callstack);
}
if (Token::Match(tok, "= %varid% ;", varid)) {
return No;
}
if (!mTokenizer_->isC() && Token::Match(tok, "[(,] %varid% [,)]", varid)) {
return No;
}
if (Token::Match(tok, "[(,] & %varid% [.,)]", varid)) {
return No;
}
if (Token::Match(tok, "[;{}] %varid% .", varid)) {
return No;
}
if (allocType == No && tok->str() == "return")
return No;
}
return allocType;
}
const char *CheckMemoryLeak::functionArgAlloc(const Function *func, nonneg int targetpar, AllocType &allocType) const
{
allocType = No;
if (!func || !func->functionScope)
return "";
if (!Token::simpleMatch(func->retDef, "void"))
return "";
std::list<Variable>::const_iterator arg = func->argumentList.begin();
for (; arg != func->argumentList.end(); ++arg) {
if (arg->index() == targetpar-1)
break;
}
if (arg == func->argumentList.end())
return "";
// Is **
if (!arg->isPointer())
return "";
const Token* tok = arg->typeEndToken();
tok = tok->previous();
if (tok->str() != "*")
return "";
// Check if pointer is allocated.
bool realloc = false;
for (tok = func->functionScope->bodyStart; tok && tok != func->functionScope->bodyEnd; tok = tok->next()) {
if (tok->varId() == arg->declarationId()) {
if (Token::Match(tok->tokAt(-3), "free ( * %name% )")) {
realloc = true;
allocType = No;
} else if (Token::Match(tok->previous(), "* %name% =")) {
allocType = getAllocationType(tok->tokAt(2), arg->declarationId());
if (allocType != No) {
if (realloc)
return "realloc";
return "alloc";
}
} else {
// unhandled variable usage: bailout
return "";
}
}
}
return "";
}
static bool notvar(const Token *tok, nonneg int varid)
{
if (!tok)
return false;
if (Token::Match(tok, "&&|;"))
return notvar(tok->astOperand1(),varid) || notvar(tok->astOperand2(),varid);
if (tok->str() == "(" && Token::Match(tok->astOperand1(), "UNLIKELY|LIKELY"))
return notvar(tok->astOperand2(), varid);
const Token *vartok = astIsVariableComparison(tok, "==", "0");
return vartok && (vartok->varId() == varid);
}
static bool ifvar(const Token *tok, nonneg int varid, const std::string &comp, const std::string &rhs)
{
if (!Token::simpleMatch(tok, "if ("))
return false;
const Token *condition = tok->next()->astOperand2();
if (condition && condition->str() == "(" && Token::Match(condition->astOperand1(), "UNLIKELY|LIKELY"))
condition = condition->astOperand2();
if (!condition || condition->str() == "&&")
return false;
const Token *vartok = astIsVariableComparison(condition, comp, rhs);
return (vartok && vartok->varId() == varid);
}
bool CheckMemoryLeakInFunction::test_white_list(const std::string &funcname, const Settings *settings, bool cpp)
{
return ((call_func_white_list.find(funcname)!=call_func_white_list.end()) || settings->library.isLeakIgnore(funcname) || (cpp && funcname == "delete"));
}
//---------------------------------------------------------------------------
// Check for memory leaks due to improper realloc() usage.
// Below, "a" may be set to null without being freed if realloc() cannot
// allocate the requested memory:
// a = malloc(10); a = realloc(a, 100);
//---------------------------------------------------------------------------
static bool isNoArgument(const SymbolDatabase* symbolDatabase, nonneg int varid)
{
const Variable* var = symbolDatabase->getVariableFromVarId(varid);
return var && !var->isArgument();
}
void CheckMemoryLeakInFunction::checkReallocUsage()
{
// only check functions
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
// Search for the "var = realloc(var, 100" pattern within this function
for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (tok->varId() > 0 && Token::Match(tok, "%name% =")) {
// Get the parenthesis in "realloc("
const Token* parTok = tok->next()->astOperand2();
// Skip casts
while (parTok && parTok->isCast())
parTok = parTok->astOperand1();
if (!parTok)
continue;
const Token *const reallocTok = parTok->astOperand1();
if (!reallocTok)
continue;
const Library::AllocFunc* f = mSettings->library.getReallocFuncInfo(reallocTok);
if (!(f && f->arg == -1 && mSettings->library.isnotnoreturn(reallocTok)))
continue;
const AllocType allocType = getReallocationType(reallocTok, tok->varId());
if (!((allocType == Malloc || allocType == OtherMem)))
continue;
const Token* arg = getArguments(reallocTok).at(f->reallocArg - 1);
while (arg && arg->isCast())
arg = arg->astOperand1();
const Token* tok2 = tok;
while (arg && arg->isUnaryOp("*") && tok2 && tok2->astParent() && tok2->astParent()->isUnaryOp("*")) {
arg = arg->astOperand1();
tok2 = tok2->astParent();
}
if (!arg || !tok2)
continue;
if (!((tok->varId() == arg->varId()) && isNoArgument(symbolDatabase, tok->varId())))
continue;
// Check that another copy of the pointer wasn't saved earlier in the function
if (Token::findmatch(scope->bodyStart, "%name% = %varid% ;", tok, tok->varId()) ||
Token::findmatch(scope->bodyStart, "[{};] %varid% = %name% [;=]", tok, tok->varId()))
continue;
const Token* tokEndRealloc = reallocTok->linkAt(1);
// Check that the allocation isn't followed immediately by an 'if (!var) { error(); }' that might handle failure
if (Token::simpleMatch(tokEndRealloc->next(), "; if (") &&
notvar(tokEndRealloc->tokAt(3)->astOperand2(), tok->varId())) {
const Token* tokEndBrace = tokEndRealloc->linkAt(3)->linkAt(1);
if (tokEndBrace && mTokenizer->IsScopeNoReturn(tokEndBrace))
continue;
}
memleakUponReallocFailureError(tok, reallocTok->str(), tok->str());
}
}
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Checks for memory leaks in classes..
//---------------------------------------------------------------------------
void CheckMemoryLeakInClass::check()
{
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
// only check classes and structures
for (const Scope * scope : symbolDatabase->classAndStructScopes) {
for (const Variable &var : scope->varlist) {
if (!var.isStatic() && var.isPointer()) {
// allocation but no deallocation of private variables in public function..
const Token *tok = var.typeStartToken();
// Either it is of standard type or a non-derived type
if (tok->isStandardType() || (var.type() && var.type()->derivedFrom.empty())) {
if (var.isPrivate())
checkPublicFunctions(scope, var.nameToken());
variable(scope, var.nameToken());
}
}
}
}
}
void CheckMemoryLeakInClass::variable(const Scope *scope, const Token *tokVarname)
{
const std::string& varname = tokVarname->str();
const int varid = tokVarname->varId();
const std::string& classname = scope->className;
// Check if member variable has been allocated and deallocated..
CheckMemoryLeak::AllocType Alloc = CheckMemoryLeak::No;
CheckMemoryLeak::AllocType Dealloc = CheckMemoryLeak::No;
bool allocInConstructor = false;
bool deallocInDestructor = false;
// Inspect member functions
for (const Function &func : scope->functionList) {
const bool constructor = func.isConstructor();
const bool destructor = func.isDestructor();
if (!func.hasBody()) {
if (destructor) { // implementation for destructor is not seen => assume it deallocates all variables properly
deallocInDestructor = true;
Dealloc = CheckMemoryLeak::Many;
}
continue;
}
bool body = false;
const Token *end = func.functionScope->bodyEnd;
for (const Token *tok = func.arg->link(); tok != end; tok = tok->next()) {
if (tok == func.functionScope->bodyStart)
body = true;
else {
if (!body) {
if (!Token::Match(tok, ":|, %varid% (", varid))
continue;
}
// Allocate..
if (!body || Token::Match(tok, "%varid% =", varid)) {
// var1 = var2 = ...
// bail out
if (tok->strAt(-1) == "=")
return;
// Foo::var1 = ..
// bail out when not same class
if (tok->strAt(-1) == "::" &&
tok->strAt(-2) != scope->className)
return;
AllocType alloc = getAllocationType(tok->tokAt(body ? 2 : 3), 0);
if (alloc != CheckMemoryLeak::No) {
if (constructor)
allocInConstructor = true;
if (Alloc != No && Alloc != alloc)
alloc = CheckMemoryLeak::Many;
if (alloc != CheckMemoryLeak::Many && Dealloc != CheckMemoryLeak::No && Dealloc != CheckMemoryLeak::Many && Dealloc != alloc) {
std::list<const Token *> callstack;
callstack.push_back(tok);
mismatchAllocDealloc(callstack, classname + "::" + varname);
}
Alloc = alloc;
}
}
if (!body)
continue;
// Deallocate..
AllocType dealloc = getDeallocationType(tok, varid);
// some usage in the destructor => assume it's related
// to deallocation
if (destructor && tok->str() == varname)
dealloc = CheckMemoryLeak::Many;
if (dealloc != CheckMemoryLeak::No) {
if (destructor)
deallocInDestructor = true;
// several types of allocation/deallocation?
if (Dealloc != CheckMemoryLeak::No && Dealloc != dealloc)
dealloc = CheckMemoryLeak::Many;
if (dealloc != CheckMemoryLeak::Many && Alloc != CheckMemoryLeak::No && Alloc != Many && Alloc != dealloc) {
std::list<const Token *> callstack;
callstack.push_back(tok);
mismatchAllocDealloc(callstack, classname + "::" + varname);
}
Dealloc = dealloc;
}
// Function call .. possible deallocation
else if (Token::Match(tok->previous(), "[{};] %name% (")) {
if (!CheckMemoryLeakInFunction::test_white_list(tok->str(), mSettings, mTokenizer->isCPP())) {
return;
}
}
}
}
}
if (allocInConstructor && !deallocInDestructor) {
unsafeClassError(tokVarname, classname, classname + "::" + varname /*, Alloc*/);
} else if (Alloc != CheckMemoryLeak::No && Dealloc == CheckMemoryLeak::No) {
unsafeClassError(tokVarname, classname, classname + "::" + varname /*, Alloc*/);
}
}
void CheckMemoryLeakInClass::unsafeClassError(const Token *tok, const std::string &classname, const std::string &varname)
{
if (!mSettings->isEnabled(Settings::STYLE))
return;
reportError(tok, Severity::style, "unsafeClassCanLeak",
"$symbol:" + classname + "\n"
"$symbol:" + varname + "\n"
"Class '" + classname + "' is unsafe, '" + varname + "' can leak by wrong usage.\n"
"The class '" + classname + "' is unsafe, wrong usage can cause memory/resource leaks for '" + varname + "'. This can for instance be fixed by adding proper cleanup in the destructor.", CWE398, false);
}
void CheckMemoryLeakInClass::checkPublicFunctions(const Scope *scope, const Token *classtok)
{
// Check that public functions deallocate the pointers that they allocate.
// There is no checking how these functions are used and therefore it
// isn't established if there is real leaks or not.
if (!mSettings->isEnabled(Settings::WARNING))
return;
const int varid = classtok->varId();
// Parse public functions..
// If they allocate member variables, they should also deallocate
for (const Function &func : scope->functionList) {
if ((func.type == Function::eFunction || func.type == Function::eOperatorEqual) &&
func.access == AccessControl::Public && func.hasBody()) {
const Token *tok2 = func.functionScope->bodyStart->next();
if (Token::Match(tok2, "%varid% =", varid)) {
const CheckMemoryLeak::AllocType alloc = getAllocationType(tok2->tokAt(2), varid);
if (alloc != CheckMemoryLeak::No)
publicAllocationError(tok2, tok2->str());
} else if (Token::Match(tok2, "%type% :: %varid% =", varid) &&
tok2->str() == scope->className) {
const CheckMemoryLeak::AllocType alloc = getAllocationType(tok2->tokAt(4), varid);
if (alloc != CheckMemoryLeak::No)
publicAllocationError(tok2, tok2->strAt(2));
}
}
}
}
void CheckMemoryLeakInClass::publicAllocationError(const Token *tok, const std::string &varname)
{
reportError(tok, Severity::warning, "publicAllocationError", "$symbol:" + varname + "\nPossible leak in public function. The pointer '$symbol' is not deallocated before it is allocated.", CWE398, false);
}
void CheckMemoryLeakStructMember::check()
{
const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Variable* var : symbolDatabase->variableList()) {
if (!var || !var->isLocal() || var->isStatic() || var->isReference())
continue;
if (var->typeEndToken()->isStandardType())
continue;
checkStructVariable(var);
}
}
bool CheckMemoryLeakStructMember::isMalloc(const Variable *variable)
{
const int declarationId(variable->declarationId());
bool alloc = false;
for (const Token *tok2 = variable->nameToken(); tok2 && tok2 != variable->scope()->bodyEnd; tok2 = tok2->next()) {
if (Token::Match(tok2, "= %varid% [;=]", declarationId)) {
return false;
} else if (Token::Match(tok2, "%varid% = malloc|kmalloc (", declarationId)) {
alloc = true;
}
}
return alloc;
}
void CheckMemoryLeakStructMember::checkStructVariable(const Variable * const variable)
{
// Is struct variable a pointer?
if (variable->isPointer()) {
// Check that variable is allocated with malloc
if (!isMalloc(variable))
return;
} else if (!mTokenizer->isC() && (!variable->typeScope() || variable->typeScope()->getDestructor())) {
// For non-C code a destructor might cleanup members
return;
}
// Check struct..
int indentlevel2 = 0;
for (const Token *tok2 = variable->nameToken(); tok2 && tok2 != variable->scope()->bodyEnd; tok2 = tok2->next()) {
if (tok2->str() == "{")
++indentlevel2;
else if (tok2->str() == "}") {
if (indentlevel2 == 0)
break;
--indentlevel2;
}
// Unknown usage of struct
/** @todo Check how the struct is used. Only bail out if necessary */
else if (Token::Match(tok2, "[(,] %varid% [,)]", variable->declarationId()))
break;
// Struct member is allocated => check if it is also properly deallocated..
else if (Token::Match(tok2->previous(), "[;{}] %varid% . %var% =", variable->declarationId())) {
if (getAllocationType(tok2->tokAt(4), tok2->tokAt(2)->varId()) == AllocType::No)
continue;
const int structid(variable->declarationId());
const int structmemberid(tok2->tokAt(2)->varId());
// This struct member is allocated.. check that it is deallocated
int indentlevel3 = indentlevel2;
for (const Token *tok3 = tok2; tok3; tok3 = tok3->next()) {
if (tok3->str() == "{")
++indentlevel3;
else if (tok3->str() == "}") {
if (indentlevel3 == 0) {
memoryLeak(tok3, variable->name() + "." + tok2->strAt(2), Malloc);
break;
}
--indentlevel3;
}
// Deallocating the struct member..
else if (getDeallocationType(tok3, structmemberid) != AllocType::No) {
// If the deallocation happens at the base level, don't check this member anymore
if (indentlevel3 == 0)
break;
// deallocating and then returning from function in a conditional block =>
// skip ahead out of the block
bool ret = false;
while (tok3) {
if (tok3->str() == "return")
ret = true;
else if (tok3->str() == "{" || tok3->str() == "}")
break;
tok3 = tok3->next();
}
if (!ret || !tok3 || tok3->str() != "}")
break;
--indentlevel3;
continue;
}
// Deallocating the struct..
else if (Token::Match(tok3, "free|kfree ( %varid% )", structid)) {
if (indentlevel2 == 0)
memoryLeak(tok3, variable->name() + "." + tok2->strAt(2), Malloc);
break;
}
// failed allocation => skip code..
else if (Token::simpleMatch(tok3, "if (") &&
notvar(tok3->next()->astOperand2(), structmemberid)) {
// Goto the ")"
tok3 = tok3->next()->link();
// make sure we have ") {".. it should be
if (!Token::simpleMatch(tok3, ") {"))
break;
// Goto the "}"
tok3 = tok3->next()->link();
}
// succeeded allocation
else if (ifvar(tok3, structmemberid, "!=", "0")) {
// goto the ")"
tok3 = tok3->next()->link();
// check if the variable is deallocated or returned..
int indentlevel4 = 0;
for (const Token *tok4 = tok3; tok4; tok4 = tok4->next()) {
if (tok4->str() == "{")
++indentlevel4;
else if (tok4->str() == "}") {
--indentlevel4;
if (indentlevel4 == 0)
break;
} else if (Token::Match(tok4, "free|kfree ( %var% . %varid% )", structmemberid)) {
break;
}
}
// was there a proper deallocation?
if (indentlevel4 > 0)
break;
}
// Returning from function..
else if (tok3->str() == "return") {
// Returning from function without deallocating struct member?
if (!Token::Match(tok3, "return %varid% ;", structid) &&
!Token::Match(tok3, "return & %varid%", structid) &&
!(Token::Match(tok3, "return %varid% . %var%", structid) && tok3->tokAt(3)->varId() == structmemberid)) {
memoryLeak(tok3, variable->name() + "." + tok2->strAt(2), Malloc);
}
break;
}
// struct assignment..
else if (Token::Match(tok3, "= %varid% ;", structid)) {
break;
} else if (Token::Match(tok3, "= %var% . %varid% ;", structmemberid)) {
break;
}
// goto isn't handled well.. bail out even though there might be leaks
else if (tok3->str() == "goto")
break;
// using struct in a function call..
else if (Token::Match(tok3, "%name% (")) {
// Calling non-function / function that doesn't deallocate?
if (CheckMemoryLeakInFunction::test_white_list(tok3->str(), mSettings, mTokenizer->isCPP()))
continue;
// Check if the struct is used..
bool deallocated = false;
const Token* const end4 = tok3->linkAt(1);
for (const Token *tok4 = tok3; tok4 != end4; tok4 = tok4->next()) {
if (Token::Match(tok4, "[(,] &| %varid% [,)]", structid)) {
/** @todo check if the function deallocates the memory */
deallocated = true;
break;
}
if (Token::Match(tok4, "[(,] &| %varid% . %name% [,)]", structid)) {
/** @todo check if the function deallocates the memory */
deallocated = true;
break;
}
}
if (deallocated)
break;
}
}
}
}
}
void CheckMemoryLeakNoVar::check()
{
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
// only check functions
for (const Scope * scope : symbolDatabase->functionScopes) {
// Checks if a call to an allocation function like malloc() is made and its return value is not assigned.
checkForUnusedReturnValue(scope);
// Checks to see if a function is called with memory allocated for an argument that
// could be leaked if a function called for another argument throws.
checkForUnsafeArgAlloc(scope);
// Check for leaks where a the return value of an allocation function like malloc() is an input argument,
// for example f(malloc(1)), where f is known to not release the input argument.
checkForUnreleasedInputArgument(scope);
}
}
//---------------------------------------------------------------------------
// Checks if an input argument to a function is the return value of an allocation function
// like malloc(), and the function does not release it.
//---------------------------------------------------------------------------
void CheckMemoryLeakNoVar::checkForUnreleasedInputArgument(const Scope *scope)
{
// parse the executable scope until tok is reached...
for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
// allocating memory in parameter for function call..
if (!Token::Match(tok, "%name% ("))
continue;
// check if the output of the function is assigned
const Token* tok2 = tok->next()->astParent();
while (tok2 && tok2->isCast())
tok2 = tok2->astParent();
if (tok2 && tok2->isAssignmentOp())
continue;
const std::string& functionName = tok->str();
if ((mTokenizer->isCPP() && functionName == "delete") ||
functionName == "free" ||
functionName == "fclose" ||
functionName == "realloc")
break;
if (!CheckMemoryLeakInFunction::test_white_list(functionName, mSettings, mTokenizer->isCPP()))
continue;
const std::vector<const Token *> args = getArguments(tok);
for (const Token* arg : args) {
if (arg->isOp())
continue;
while (arg->astOperand1())
arg = arg->astOperand1();
if (getAllocationType(arg, 0) == No)
continue;
if (isReopenStandardStream(arg))
continue;
functionCallLeak(arg, arg->str(), functionName);
break;
}
}
}
//---------------------------------------------------------------------------
// Checks if a call to an allocation function like malloc() is made and its return value is not assigned.
//---------------------------------------------------------------------------
void CheckMemoryLeakNoVar::checkForUnusedReturnValue(const Scope *scope)
{
for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
if (!Token::Match(tok, "%name% ("))
continue;
if (tok->varId())
continue;
const AllocType allocType = getAllocationType(tok, 0);
if (allocType == No)
continue;
if (tok != tok->next()->astOperand1())
continue;
if (isReopenStandardStream(tok))
continue;
// get ast parent, skip casts
const Token *parent = tok->next()->astParent();
while (parent && parent->str() == "(" && !parent->astOperand2())
parent = parent->astParent();
if (!parent) {
// Check if we are in a C++11 constructor
const Token * closingBrace = Token::findmatch(tok, "}|;");
if (closingBrace->str() == "}" && Token::Match(closingBrace->link()->tokAt(-1), "%name%"))
continue;
returnValueNotUsedError(tok, tok->str());
} else if (Token::Match(parent, "%comp%|!")) {
returnValueNotUsedError(tok, tok->str());
}
}
}
//---------------------------------------------------------------------------
// Check if an exception could cause a leak in an argument constructed with
// shared_ptr/unique_ptr. For example, in the following code, it is possible
// that if g() throws an exception, the memory allocated by "new int(42)"
// could be leaked. See stackoverflow.com/questions/19034538/
// why-is-there-memory-leak-while-using-shared-ptr-as-a-function-parameter
//
// void x() {
// f(shared_ptr<int>(new int(42)), g());
// }
//---------------------------------------------------------------------------
void CheckMemoryLeakNoVar::checkForUnsafeArgAlloc(const Scope *scope)
{
// This test only applies to C++ source
if (!mTokenizer->isCPP() || !mSettings->inconclusive || !mSettings->isEnabled(Settings::WARNING))
return;
for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
if (Token::Match(tok, "%name% (")) {
const Token *endParamToken = tok->next()->link();
const Token* pointerType = nullptr;
const Token* functionCalled = nullptr;
// Scan through the arguments to the function call
for (const Token *tok2 = tok->tokAt(2); tok2 && tok2 != endParamToken; tok2 = tok2->nextArgument()) {
const Function *func = tok2->function();
const bool isNothrow = func && (func->isAttributeNothrow() || func->isThrow());
if (Token::Match(tok2, "shared_ptr|unique_ptr <") && Token::Match(tok2->next()->link(), "> ( new %name%")) {
pointerType = tok2;
} else if (!isNothrow) {
if (Token::Match(tok2, "%name% ("))
functionCalled = tok2;
else if (tok2->isName() && Token::simpleMatch(tok2->next()->link(), "> ("))
functionCalled = tok2;
}
}
if (pointerType && functionCalled) {
std::string functionName = functionCalled->str();
if (functionCalled->strAt(1) == "<") {
functionName += '<';
for (const Token* tok2 = functionCalled->tokAt(2); tok2 != functionCalled->next()->link(); tok2 = tok2->next())
functionName += tok2->str();
functionName += '>';
}
std::string objectTypeName;
for (const Token* tok2 = pointerType->tokAt(2); tok2 != pointerType->next()->link(); tok2 = tok2->next())
objectTypeName += tok2->str();
unsafeArgAllocError(tok, functionName, pointerType->str(), objectTypeName);
}
}
}
}
void CheckMemoryLeakNoVar::functionCallLeak(const Token *loc, const std::string &alloc, const std::string &functionCall)
{
reportError(loc, Severity::error, "leakNoVarFunctionCall", "Allocation with " + alloc + ", " + functionCall + " doesn't release it.", CWE772, false);
}
void CheckMemoryLeakNoVar::returnValueNotUsedError(const Token *tok, const std::string &alloc)
{
reportError(tok, Severity::error, "leakReturnValNotUsed", "$symbol:" + alloc + "\nReturn value of allocation function '$symbol' is not stored.", CWE771, false);
}
void CheckMemoryLeakNoVar::unsafeArgAllocError(const Token *tok, const std::string &funcName, const std::string &ptrType, const std::string& objType)
{
const std::string factoryFunc = ptrType == "shared_ptr" ? "make_shared" : "make_unique";
reportError(tok, Severity::warning, "leakUnsafeArgAlloc",
"$symbol:" + funcName + "\n"
"Unsafe allocation. If $symbol() throws, memory could be leaked. Use " + factoryFunc + "<" + objType + ">() instead.",
CWE401,
true); // Inconclusive because funcName may never throw
}