From bd8fb0a6b5b85b1efe5f83cae3d490bd3c402c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 26 May 2012 08:53:46 +0200 Subject: [PATCH] Memory leaks: Added new checking for memory leaks --- Makefile | 8 + lib/checkleakautovar.cpp | 456 +++++++++++++++++++++++++++++++++++++++ lib/checkleakautovar.h | 130 +++++++++++ lib/lib.pri | 2 + test/testleakautovar.cpp | 410 +++++++++++++++++++++++++++++++++++ 5 files changed, 1006 insertions(+) create mode 100644 lib/checkleakautovar.cpp create mode 100644 lib/checkleakautovar.h create mode 100644 test/testleakautovar.cpp diff --git a/Makefile b/Makefile index c67f08f57..d3f6ac8e1 100644 --- a/Makefile +++ b/Makefile @@ -88,6 +88,7 @@ LIBOBJ = lib/check64bit.o \ lib/checkexceptionsafety.o \ lib/checkinternal.o \ lib/checkio.o \ + lib/checkleakautovar.o \ lib/checkmemoryleak.o \ lib/checknonreentrantfunctions.o \ lib/checknullpointer.o \ @@ -138,6 +139,7 @@ TESTOBJ = test/options.o \ test/testincompletestatement.o \ test/testinternal.o \ test/testio.o \ + test/testleakautovar.o \ test/testmathlib.o \ test/testmemleak.o \ test/testnonreentrantfunctions.o \ @@ -238,6 +240,9 @@ lib/checkinternal.o: lib/checkinternal.cpp lib/checkinternal.h lib/check.h lib/t lib/checkio.o: lib/checkio.cpp lib/checkio.h lib/check.h lib/token.h lib/tokenize.h lib/errorlogger.h lib/suppressions.h lib/tokenlist.h lib/settings.h lib/standards.h lib/symboldatabase.h lib/mathlib.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_LIB} -c -o lib/checkio.o lib/checkio.cpp +lib/checkleakautovar.o: lib/checkleakautovar.cpp lib/checkleakautovar.h lib/check.h lib/token.h lib/tokenize.h lib/errorlogger.h lib/suppressions.h lib/tokenlist.h lib/settings.h lib/standards.h lib/symboldatabase.h lib/mathlib.h lib/checknullpointer.h + $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_LIB} -c -o lib/checkleakautovar.o lib/checkleakautovar.cpp + lib/checkmemoryleak.o: lib/checkmemoryleak.cpp lib/checkmemoryleak.h lib/check.h lib/token.h lib/tokenize.h lib/errorlogger.h lib/suppressions.h lib/tokenlist.h lib/settings.h lib/standards.h lib/symboldatabase.h lib/mathlib.h lib/checkuninitvar.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_LIB} -c -o lib/checkmemoryleak.o lib/checkmemoryleak.cpp @@ -382,6 +387,9 @@ test/testinternal.o: test/testinternal.cpp lib/tokenize.h lib/errorlogger.h lib/ test/testio.o: test/testio.cpp lib/checkio.h lib/check.h lib/token.h lib/tokenize.h lib/errorlogger.h lib/suppressions.h lib/tokenlist.h lib/settings.h lib/standards.h test/testsuite.h test/redirect.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_TEST} -c -o test/testio.o test/testio.cpp +test/testleakautovar.o: test/testleakautovar.cpp lib/tokenize.h lib/errorlogger.h lib/suppressions.h lib/tokenlist.h lib/checkleakautovar.h lib/check.h lib/token.h lib/settings.h lib/standards.h test/testsuite.h test/redirect.h + $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_TEST} -c -o test/testleakautovar.o test/testleakautovar.cpp + test/testmathlib.o: test/testmathlib.cpp lib/mathlib.h test/testsuite.h lib/errorlogger.h lib/suppressions.h test/redirect.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) ${INCLUDE_FOR_TEST} -c -o test/testmathlib.o test/testmathlib.cpp diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp new file mode 100644 index 000000000..c1a0ea874 --- /dev/null +++ b/lib/checkleakautovar.cpp @@ -0,0 +1,456 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2012 Daniel Marjamäki and 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 . + */ + +//--------------------------------------------------------------------------- +// Leaks when using auto variables +//--------------------------------------------------------------------------- + +#include "checkleakautovar.h" + +#include "tokenize.h" +#include "errorlogger.h" +#include "symboldatabase.h" + +#include "checknullpointer.h" // <- isUpper + +#include + +//--------------------------------------------------------------------------- + +// Register this check class (by creating a static instance of it) +namespace { + CheckLeakAutoVar instance; +} + +//--------------------------------------------------------------------------- + +void VarInfo::print() +{ + std::cout << "size=" << alloctype.size() << std::endl; + std::map::const_iterator it; + for (it = alloctype.begin(); it != alloctype.end(); ++it) { + std::string strusage; + std::map::const_iterator use = possibleUsage.find(it->first); + if (use != possibleUsage.end()) + strusage = use->second; + + std::cout << "alloctype='" << it->second << "' " + << "possibleUsage='" << strusage << "'" << std::endl; + } +} + +void VarInfo::possibleUsageAll(const std::string &functionName) +{ + possibleUsage.clear(); + std::map::const_iterator it; + for (it = alloctype.begin(); it != alloctype.end(); ++it) + possibleUsage[it->first] = functionName; +} + + +void CheckLeakAutoVar::leakError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::error, "newleak", "New memory leak: " + varname); +} + +void CheckLeakAutoVar::mismatchError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::error, "newmismatch", "New mismatching allocation and deallocation: " + varname); +} + +void CheckLeakAutoVar::deallocUseError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::error, "newdeallocuse", "Using deallocated pointer " + varname); +} + +void CheckLeakAutoVar::doubleDeallocationError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::error, "doubledeallocation", "Double deallocation: " + varname); +} + +void CheckLeakAutoVar::configurationInfo(const Token* tok, const std::string &functionName) +{ + if (_settings->experimental) + { + reportError(tok, + Severity::information, + "leakconfiguration", + functionName + " configuration is needed to establish if there is a leak or not"); + } +} + +void CheckLeakAutoVar::check() +{ + const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); + + // Check function scopes + for (std::list::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) { + if (i->type == Scope::eFunction) { + // Empty variable info + VarInfo varInfo; + + // Local variables that are known to be non-zero. + const std::set notzero; + + checkScope(i->classStart, &varInfo, notzero); + + varInfo.conditionalAlloc.clear(); + + ret(i->classEnd, varInfo); + } + } +} + +void CheckLeakAutoVar::checkScope(const Token * const startToken, + VarInfo *varInfo, + std::set notzero) +{ + std::map &alloctype = varInfo->alloctype; + std::map &possibleUsage = varInfo->possibleUsage; + const std::set conditionalAlloc(varInfo->conditionalAlloc); + + // Allocation functions. key = function name, value = allocation type + std::map allocFunctions; + allocFunctions["malloc"] = "malloc"; + allocFunctions["strdup"] = "malloc"; + allocFunctions["fopen"] = "fopen"; + + // Deallocation functions. key = function name, value = allocation type + std::map deallocFunctions; + deallocFunctions["free"] = "malloc"; + deallocFunctions["fclose"] = "fopen"; + + // Parse all tokens + const Token * const endToken = startToken->link(); + for (const Token *tok = startToken; tok && tok != endToken; tok = tok->next()) { + // Deallocation and then dereferencing pointer.. + if (tok->varId() > 0) { + const std::map::iterator var = alloctype.find(tok->varId()); + if (var != alloctype.end()) { + if (var->second == "dealloc") { + deallocUseError(tok, tok->str()); + } else if (Token::simpleMatch(tok->tokAt(-2), "= &")) { + varInfo->erase(tok->varId()); + } else if (Token::simpleMatch(tok->previous(), "=")) { + varInfo->erase(tok->varId()); + } + } + } + + if (tok->str() == "(" && tok->previous()->isName()) { + functionCall(tok->previous(), varInfo, ""); + tok = tok->link(); + continue; + } + + // look for end of statement + if (!Token::Match(tok, "[;{}]") || Token::Match(tok->next(), "[;{}]")) + continue; + tok = tok->next(); + if (tok == endToken) + break; + + // parse statement + + // assignment.. + if (tok && tok->varId() && Token::Match(tok, "%var% =")) { + // taking address of another variable.. + if (Token::Match(tok->next(), "= %var% [+;]")) { + if (tok->tokAt(2)->varId() != tok->varId()) { + // If variable points at allocated memory => error + leakIfAllocated(tok, *varInfo); + + // no multivariable checking currently => bail out for rhs variables + for (const Token *tok2 = tok; tok2; tok2 = tok2->next()) { + if (tok2->str() == ";") { + break; + } + if (tok2->varId()) { + varInfo->erase(tok2->varId()); + } + } + } + } + + // is variable used in rhs? + bool used_in_rhs = false; + for (const Token *tok2 = tok->tokAt(2); tok2; tok2 = tok2->next()) { + if (tok2->str() == ";") { + break; + } + if (tok->varId() == tok2->varId()) { + used_in_rhs = true; + break; + } + } + // TODO: Better checking how the pointer is used in rhs? + if (used_in_rhs) + continue; + + // Variable has already been allocated => error + leakIfAllocated(tok, *varInfo); + varInfo->erase(tok->varId()); + + // not a local variable nor argument? + const Variable *var = _tokenizer->getSymbolDatabase()->getVariableFromVarId(tok->varId()); + if (var && !var->isArgument() && !var->isLocal()) { + continue; + } + + // allocation? + if (Token::Match(tok->tokAt(2), "%type% (")) { + const std::map::const_iterator it = allocFunctions.find(tok->strAt(2)); + if (it != allocFunctions.end()) { + alloctype[tok->varId()] = it->second; + } + } + + // Assigning non-zero value variable. It might be used to + // track the execution for a later if condition. + if (Token::Match(tok->tokAt(2), "%num% ;") && MathLib::toLongNumber(tok->strAt(2)) != 0) + notzero.insert(tok->varId()); + else if (Token::Match(tok->tokAt(2), "- %type% ;") && CheckNullPointer::isUpper(tok->strAt(3))) + notzero.insert(tok->varId()); + else + notzero.erase(tok->varId()); + } + + // if/else + else if (Token::simpleMatch(tok, "if (")) { + // Parse function calls inside the condition + for (const Token *innerTok = tok->tokAt(2); innerTok; innerTok = innerTok->next()) { + if (innerTok->str() == ")") + break; + if (innerTok->str() == "(" && innerTok->previous()->isName()) { + std::string dealloc; + { + const std::map::iterator func = deallocFunctions.find(tok->str()); + if (func != deallocFunctions.end()) { + dealloc = func->second; + } + } + + functionCall(innerTok->previous(), varInfo, dealloc); + innerTok = innerTok->link(); + } + } + + const Token *tok2 = tok->linkAt(1); + if (Token::simpleMatch(tok2, ") {")) { + VarInfo varInfo1(*varInfo); + VarInfo varInfo2(*varInfo); + + if (Token::Match(tok->next(), "( %var% )")) { + varInfo2.erase(tok->tokAt(2)->varId()); + if (notzero.find(tok->tokAt(2)->varId()) != notzero.end()) + varInfo2.clear(); + } else if (Token::Match(tok->next(), "( ! %var% )")) + varInfo1.erase(tok->tokAt(3)->varId()); + + checkScope(tok2->next(), &varInfo1, notzero); + tok2 = tok2->linkAt(1); + if (Token::simpleMatch(tok2, "} else {")) { + checkScope(tok2->tokAt(2), &varInfo2, notzero); + tok = tok2->linkAt(2)->previous(); + } else { + tok = tok2->previous(); + } + + VarInfo old; + old.swap(*varInfo); + + // Conditional allocation in varInfo1 + std::map::const_iterator it; + for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) { + if (varInfo2.alloctype.find(it->first) == varInfo2.alloctype.end() && + old.alloctype.find(it->first) == old.alloctype.end()) { + varInfo->conditionalAlloc.insert(it->first); + } + } + + // Conditional allocation in varInfo2 + for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) { + if (varInfo1.alloctype.find(it->first) == varInfo1.alloctype.end() && + old.alloctype.find(it->first) == old.alloctype.end()) { + varInfo->conditionalAlloc.insert(it->first); + } + } + + // Conditional allocation/deallocation + for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) { + if (it->second == "dealloc" && conditionalAlloc.find(it->first) != conditionalAlloc.end()) { + varInfo->conditionalAlloc.erase(it->first); + varInfo2.erase(it->first); + } + } + for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) { + if (it->second == "dealloc" && conditionalAlloc.find(it->first) != conditionalAlloc.end()) { + varInfo->conditionalAlloc.erase(it->first); + varInfo1.erase(it->first); + } + } + + alloctype.insert(varInfo1.alloctype.begin(), varInfo1.alloctype.end()); + alloctype.insert(varInfo2.alloctype.begin(), varInfo2.alloctype.end()); + + possibleUsage.insert(varInfo1.possibleUsage.begin(), varInfo1.possibleUsage.end()); + possibleUsage.insert(varInfo2.possibleUsage.begin(), varInfo2.possibleUsage.end()); + } + } + + // unknown control.. + else if (Token::Match(tok, "%type% (") && Token::simpleMatch(tok->linkAt(1), ") {")) { + varInfo->clear(); + break; + } + + // Function call.. + else if (Token::Match(tok, "%type% (")) { + std::string dealloc; + { + const std::map::iterator func = deallocFunctions.find(tok->str()); + if (func != deallocFunctions.end()) { + dealloc = func->second; + } + } + + functionCall(tok, varInfo, dealloc); + + tok = tok->next()->link(); + + // Handle scopes that might be noreturn + if (dealloc.empty() && Token::simpleMatch(tok, ") ; }")) { + bool unknown = false; + if (_tokenizer->IsScopeNoReturn(tok->tokAt(2), &unknown)) { + if (unknown) { + const std::string &functionName(tok->link()->previous()->str()); + varInfo->possibleUsageAll(functionName); + } else { + varInfo->clear(); + } + } + } + + continue; + } + + // return + else if (tok->str() == "return") { + ret(tok, *varInfo); + varInfo->clear(); + } + } +} + +void CheckLeakAutoVar::functionCall(const Token *tok, VarInfo *varInfo, const std::string &dealloc) +{ + std::map &alloctype = varInfo->alloctype; + std::map &possibleUsage = varInfo->possibleUsage; + + for (const Token *arg = tok->tokAt(2); arg; arg = arg->nextArgument()) { + if ((Token::Match(arg, "%var% [-,)]") && arg->varId() > 0) || + (Token::Match(arg, "& %var%") && arg->next()->varId() > 0)) { + + // goto variable + if (arg->str() == "&") + arg = arg->next(); + + // Is variable allocated? + const std::map::iterator var = alloctype.find(arg->varId()); + if (var != alloctype.end()) { + if (dealloc.empty()) { + // possible usage + possibleUsage[arg->varId()] = tok->str(); + } else if (var->second == "dealloc") { + doubleDeallocationError(tok, arg->str()); + } else if (var->second != dealloc) { + // mismatching allocation and deallocation + mismatchError(tok, arg->str()); + varInfo->erase(arg->varId()); + } else { + // deallocation + var->second = "dealloc"; + } + } else if (!dealloc.empty()) { + alloctype[arg->varId()] = "dealloc"; + } + } + } +} + + +void CheckLeakAutoVar::leakIfAllocated(const Token *vartok, + const VarInfo &varInfo) +{ + const std::map &alloctype = varInfo.alloctype; + const std::map &possibleUsage = varInfo.possibleUsage; + + const std::map::const_iterator var = alloctype.find(vartok->varId()); + if (var != alloctype.end() && var->second != "dealloc") { + const std::map::const_iterator use = possibleUsage.find(vartok->varId()); + if (use == possibleUsage.end()) { + leakError(vartok, vartok->str()); + } else { + configurationInfo(vartok, use->second); + } + } +} + +void CheckLeakAutoVar::ret(const Token *tok, const VarInfo &varInfo) +{ + const std::map &alloctype = varInfo.alloctype; + const std::map &possibleUsage = varInfo.possibleUsage; + + const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); + for (std::map::const_iterator it = alloctype.begin(); it != alloctype.end(); ++it) { + // has pointer been deallocated? + if (it->second == "dealloc") + continue; + + // don't warn if variable is conditionally allocated + if (varInfo.conditionalAlloc.find(it->first) != varInfo.conditionalAlloc.end()) + continue; + + const unsigned int varid = it->first; + const Variable *var = symbolDatabase->getVariableFromVarId(varid); + if (var) { + bool used = false; + for (const Token *tok2 = tok; tok2; tok2 = tok2->next()) { + if (tok2->str() == ";") + break; + if (Token::Match(tok2, "return|(|, %varid% [);,]", varid)) { + used = true; + break; + } + if (Token::Match(tok2, "return|(|, & %varid% . %var% [);,]", varid)) { + used = true; + break; + } + } + if (used) + continue; + + const std::map::const_iterator use = possibleUsage.find(varid); + if (use == possibleUsage.end()) { + leakError(tok, var->name()); + } else { + configurationInfo(tok, use->second); + } + } + } +} diff --git a/lib/checkleakautovar.h b/lib/checkleakautovar.h new file mode 100644 index 000000000..c7698c5f5 --- /dev/null +++ b/lib/checkleakautovar.h @@ -0,0 +1,130 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2012 Daniel Marjamäki and 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 . + */ + + +//--------------------------------------------------------------------------- +#ifndef checkleakautovarH +#define checkleakautovarH +//--------------------------------------------------------------------------- + +#include "check.h" + + +class VarInfo { +public: + std::map alloctype; + std::map possibleUsage; + std::set conditionalAlloc; + + void clear() { + alloctype.clear(); + possibleUsage.clear(); + conditionalAlloc.clear(); + } + + void erase(unsigned int varid) { + alloctype.erase(varid); + possibleUsage.erase(varid); + conditionalAlloc.erase(varid); + } + + void swap(VarInfo &other) { + alloctype.swap(other.alloctype); + possibleUsage.swap(other.possibleUsage); + conditionalAlloc.swap(other.conditionalAlloc); + } + + /** set possible usage for all variables */ + void possibleUsageAll(const std::string &functionName); + + void print(); +}; + + +/// @addtogroup Checks +/// @{ + +/** + * @brief Check for leaks + */ + +class CheckLeakAutoVar : public Check { +public: + /** This constructor is used when registering the CheckLeakAutoVar */ + CheckLeakAutoVar() : Check(myName()) { + } + + /** This constructor is used when running checks. */ + CheckLeakAutoVar(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) + : Check(myName(), tokenizer, settings, errorLogger) { + } + + /** @brief Run checks against the simplified token list */ + void runSimplifiedChecks(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) { + CheckLeakAutoVar checkLeakAutoVar(tokenizer, settings, errorLogger); + checkLeakAutoVar.check(); + } + +private: + + /** check for leaks in all scopes */ + void check(); + + /** check for leaks in a function scope */ + void checkScope(const Token * const startToken, + VarInfo *varInfo, + std::set notzero); + + /** parse function call */ + void functionCall(const Token *tok, VarInfo *varInfo, const std::string &dealloc); + + /** return. either "return" or end of variable scope is seen */ + void ret(const Token *tok, const VarInfo &varInfo); + + /** if variable is allocated then there is a leak */ + void leakIfAllocated(const Token *vartok, const VarInfo &varInfo); + + void leakError(const Token* tok, const std::string &varname); + void mismatchError(const Token* tok, const std::string &varname); + void deallocUseError(const Token *tok, const std::string &varname); + void doubleDeallocationError(const Token *tok, const std::string &varname); + + /** message: user configuration is needed to complete analysis */ + void configurationInfo(const Token* tok, const std::string &functionName); + + void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const { + CheckLeakAutoVar c(0, settings, errorLogger); + c.leakError(NULL, "p"); + c.mismatchError(NULL, "p"); + c.deallocUseError(NULL, "p"); + c.doubleDeallocationError(NULL, "p"); + c.configurationInfo(0, "f"); // user configuration is needed to complete analysis + } + + std::string myName() const { + return "Leaks in functions"; + } + + std::string classInfo() const { + return ""; + } +}; +/// @} +//--------------------------------------------------------------------------- +#endif + diff --git a/lib/lib.pri b/lib/lib.pri index 3fdf6082e..95deec5aa 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -12,6 +12,7 @@ HEADERS += $${BASEPATH}check.h \ $${BASEPATH}checkexceptionsafety.h \ $${BASEPATH}checkinternal.h \ $${BASEPATH}checkio.h \ + $${BASEPATH}checkleakautovar.h \ $${BASEPATH}checkmemoryleak.h \ $${BASEPATH}checknonreentrantfunctions.h \ $${BASEPATH}checknullpointer.h \ @@ -46,6 +47,7 @@ SOURCES += $${BASEPATH}check64bit.cpp \ $${BASEPATH}checkexceptionsafety.cpp \ $${BASEPATH}checkinternal.cpp \ $${BASEPATH}checkio.cpp \ + $${BASEPATH}checkleakautovar.cpp \ $${BASEPATH}checkmemoryleak.cpp \ $${BASEPATH}checknonreentrantfunctions.cpp \ $${BASEPATH}checknullpointer.cpp \ diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp new file mode 100644 index 000000000..52ab1483e --- /dev/null +++ b/test/testleakautovar.cpp @@ -0,0 +1,410 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2012 Daniel Marjamäki and 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 . + */ + + +#include "tokenize.h" +#include "checkleakautovar.h" +#include "testsuite.h" +#include + +extern std::ostringstream errout; + +class TestLeakAutoVar : public TestFixture { +public: + TestLeakAutoVar() : TestFixture("TestLeakAutoVar") + { } + +private: + + void run() { + // Assign + TEST_CASE(assign1); + TEST_CASE(assign2); + TEST_CASE(assign3); + TEST_CASE(assign4); + TEST_CASE(assign5); + TEST_CASE(assign6); + TEST_CASE(assign7); + TEST_CASE(assign8); + TEST_CASE(assign9); + + TEST_CASE(deallocuse1); + TEST_CASE(deallocuse2); + TEST_CASE(deallocuse3); + + TEST_CASE(doublefree); + + // exit + TEST_CASE(exit1); + TEST_CASE(exit2); + + // goto + TEST_CASE(goto1); + + // if/else + TEST_CASE(ifelse1); + TEST_CASE(ifelse2); + TEST_CASE(ifelse3); + TEST_CASE(ifelse4); + + // switch + TEST_CASE(switch1); + + // loops + TEST_CASE(loop1); + + // mismatching allocation/deallocation + TEST_CASE(mismatch_fopen_free); + + // Execution reaches a 'return' + TEST_CASE(return1); + TEST_CASE(return2); + TEST_CASE(return3); + + // Possible leak => Further configuration is needed for complete analysis + TEST_CASE(configuration1); + TEST_CASE(configuration2); + TEST_CASE(configuration3); + TEST_CASE(configuration4); + } + + void check(const char code[]) { + // Clear the error buffer.. + errout.str(""); + + // Tokenize.. + Settings settings; + Tokenizer tokenizer(&settings, this); + std::istringstream istr(code); + tokenizer.tokenize(istr, "test.c"); + tokenizer.simplifyTokenList(); + + // Check for leaks.. + CheckLeakAutoVar c; + settings.experimental = true; + c.runSimplifiedChecks(&tokenizer, &settings, this); + } + + void assign1() { + check("void f() {\n" + " char *p = malloc(10);\n" + " p = NULL;\n" + " free(p);\n" + "}\n"); + ASSERT_EQUALS("[test.c:3]: (error) New memory leak: p\n", errout.str()); + } + + void assign2() { + check("void f() {\n" + " char *p = malloc(10);\n" + " char *q = p;\n" + " free(q);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void assign3() { + check("void f() {\n" + " char *p = malloc(10);\n" + " char *q = p + 1;\n" + " free(q - 1);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void assign4() { + check("void f() {\n" + " char *a = malloc(10);\n" + " a += 10;\n" + " free(a - 10);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void assign5() { + check("void foo()\n" + "{\n" + " char *p = new char[100];\n" + " list += p;\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void assign6() { // #2806 - FP when there is redundant assignment + check("void foo() {\n" + " char *p = malloc(10);\n" + " p = strcpy(p,q);\n" + " free(p);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void assign7() { + check("void foo(struct str *d) {\n" + " struct str *p = malloc(10);\n" + " d->p = p;\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void assign8() { // linux list + check("void foo(struct str *d) {\n" + " struct str *p = malloc(10);\n" + " d->p = &p->x;\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void assign9() { + check("void foo() {\n" + " char *p = x();\n" + " free(p);\n" + " p = NULL;\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void deallocuse1() { + check("void f(char *p) {\n" + " free(p);\n" + " *p = 0;\n" + "}"); + ASSERT_EQUALS("[test.c:3]: (error) Using deallocated pointer p\n", errout.str()); + + check("void f(char *p) {\n" + " free(p);\n" + " char c = *p;\n" + "}"); + ASSERT_EQUALS("[test.c:3]: (error) Using deallocated pointer p\n", errout.str()); + } + + void deallocuse2() { + check("void f(char *p) {\n" + " free(p);\n" + " strcpy(a, p);\n" + "}"); + TODO_ASSERT_EQUALS("error", "", errout.str()); + + check("void f(char *p) {\n" // #3041 - assigning pointer when it's used + " free(p);\n" + " strcpy(a, p=b());\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void deallocuse3() { + check("void f(struct str *p) {\n" + " free(p);\n" + " p = p->next;\n" + "}"); + ASSERT_EQUALS("[test.c:3]: (error) Using deallocated pointer p\n", errout.str()); + } + + void doublefree() { + check("void f(char *p) {\n" + " free(p);\n" + " free(p);\n" + "}"); + ASSERT_EQUALS("[test.c:3]: (error) Double deallocation: p\n", errout.str()); + } + + void exit1() { + check("void f() {\n" + " char *p = malloc(10);\n" + " exit(0);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void exit2() { + check("void f() {\n" + " char *p = malloc(10);\n" + " fatal_error();\n" + "}"); + ASSERT_EQUALS("[test.c:4]: (information) fatal_error configuration is needed to establish if there is a leak or not\n", errout.str()); + } + + void goto1() { + check("static void f() {\n" + " int err = -ENOMEM;\n" + " char *reg = malloc(100);\n" + " if (err) {\n" + " free(reg);\n" + " }\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void ifelse1() { + check("int f() {\n" + " char *p = NULL;\n" + " if (x) { p = malloc(10); }\n" + " else { return 0; }\n" + " free(p);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void ifelse2() { + check("int f() {\n" + " char *p = NULL;\n" + " if (x) { p = malloc(10); }\n" + " else { return 0; }\n" + "}"); + ASSERT_EQUALS("[test.c:5]: (error) New memory leak: p\n", errout.str()); + } + + void ifelse3() { + check("void f() {\n" + " char *p = malloc(10);\n" + " if (!p) { return; }\n" + " free(p);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + check("void f() {\n" + " char *p = malloc(10);\n" + " if (p) { } else { return; }\n" + " free(p);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void ifelse4() { + check("void f(int x) {\n" + " char *p;\n" + " if (x) { p = malloc(10); }\n" + " if (x) { free(p); }\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + check("void f(int x) {\n" + " char *p;\n" + " if (x) { p = malloc(10); }\n" + " if (!x) { return; }\n" + " free(p);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void switch1() { + check("void f() {\n" + " char *p = 0;\n" + " switch (x) {\n" + " case 123: p = malloc(100); break;\n" + " default: return;\n" + " }\n" + " free(p);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void loop1() { + // test the handling of { } + check("void f() {\n" + " char *p;\n" + " for (i=0;i<5;i++) { }\n" + " if (x) { free(p) }\n" + " else { a = p; }\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void mismatch_fopen_free() { + check("void f() {\n" + " FILE*f=fopen(fname,a);\n" + " free(f);\n" + "}"); + ASSERT_EQUALS("[test.c:3]: (error) New mismatching allocation and deallocation: f\n", errout.str()); + } + + void return1() { + check("int f() {\n" + " char *p = malloc(100);\n" + " return 123;\n" + "}"); + ASSERT_EQUALS("[test.c:3]: (error) New memory leak: p\n", errout.str()); + } + + void return2() { + check("char *f() {\n" + " char *p = malloc(100);\n" + " return p;\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void return3() { + check("struct dev * f() {\n" + " struct ABC *abc = malloc(100);\n" + " return &abc->dev;\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void configuration1() { + // Possible leak => configuration is required for complete analysis + // The user should be able to "white list" and "black list" functions. + + // possible leak. If the function 'x' deallocates the pointer or + // takes the address, there is no leak. + check("void f() {\n" + " char *p = malloc(10);\n" + " x(p);\n" + "}"); + ASSERT_EQUALS("[test.c:4]: (information) x configuration is needed to establish if there is a leak or not\n", errout.str()); + } + + void configuration2() { + // possible leak. If the function 'x' deallocates the pointer or + // takes the address, there is no leak. + check("void f() {\n" + " char *p = malloc(10);\n" + " x(&p);\n" + "}"); + ASSERT_EQUALS("[test.c:4]: (information) x configuration is needed to establish if there is a leak or not\n", errout.str()); + } + + void configuration3() { + check("void f() {\n" + " char *p = malloc(10);\n" + " if (set_data(p)) { }\n" + "}"); + ASSERT_EQUALS("[test.c:4]: (information) set_data configuration is needed to establish if there is a leak or not\n", errout.str()); + + check("void f() {\n" + " char *p = malloc(10);\n" + " if (set_data(p)) { return; }\n" + "}"); + ASSERT_EQUALS("[test.c:3]: (information) set_data configuration is needed to establish if there is a leak or not\n" + "[test.c:4]: (information) set_data configuration is needed to establish if there is a leak or not\n" + , errout.str()); + } + + void configuration4() { + check("void f() {\n" + " char *p = malloc(10);\n" + " int ret = set_data(p);\n" + " return ret;\n" + "}"); + ASSERT_EQUALS("[test.c:4]: (information) set_data configuration is needed to establish if there is a leak or not\n", errout.str()); + } +}; + +REGISTER_TEST(TestLeakAutoVar) +