Memory leaks: Added new checking for memory leaks
This commit is contained in:
parent
f4bf94ca4f
commit
bd8fb0a6b5
8
Makefile
8
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
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Leaks when using auto variables
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "checkleakautovar.h"
|
||||
|
||||
#include "tokenize.h"
|
||||
#include "errorlogger.h"
|
||||
#include "symboldatabase.h"
|
||||
|
||||
#include "checknullpointer.h" // <- isUpper
|
||||
|
||||
#include <iostream>
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// 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<unsigned int, std::string>::const_iterator it;
|
||||
for (it = alloctype.begin(); it != alloctype.end(); ++it) {
|
||||
std::string strusage;
|
||||
std::map<unsigned int, std::string>::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<unsigned int, std::string>::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<Scope>::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<unsigned int> notzero;
|
||||
|
||||
checkScope(i->classStart, &varInfo, notzero);
|
||||
|
||||
varInfo.conditionalAlloc.clear();
|
||||
|
||||
ret(i->classEnd, varInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckLeakAutoVar::checkScope(const Token * const startToken,
|
||||
VarInfo *varInfo,
|
||||
std::set<unsigned int> notzero)
|
||||
{
|
||||
std::map<unsigned int, std::string> &alloctype = varInfo->alloctype;
|
||||
std::map<unsigned int, std::string> &possibleUsage = varInfo->possibleUsage;
|
||||
const std::set<unsigned int> conditionalAlloc(varInfo->conditionalAlloc);
|
||||
|
||||
// Allocation functions. key = function name, value = allocation type
|
||||
std::map<std::string, std::string> allocFunctions;
|
||||
allocFunctions["malloc"] = "malloc";
|
||||
allocFunctions["strdup"] = "malloc";
|
||||
allocFunctions["fopen"] = "fopen";
|
||||
|
||||
// Deallocation functions. key = function name, value = allocation type
|
||||
std::map<std::string, std::string> 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<unsigned int, std::string>::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<std::string, std::string>::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<std::string, std::string>::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<unsigned int, std::string>::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<std::string, std::string>::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<unsigned int, std::string> &alloctype = varInfo->alloctype;
|
||||
std::map<unsigned int, std::string> &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<unsigned int,std::string>::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<unsigned int, std::string> &alloctype = varInfo.alloctype;
|
||||
const std::map<unsigned int, std::string> &possibleUsage = varInfo.possibleUsage;
|
||||
|
||||
const std::map<unsigned int,std::string>::const_iterator var = alloctype.find(vartok->varId());
|
||||
if (var != alloctype.end() && var->second != "dealloc") {
|
||||
const std::map<unsigned int, std::string>::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<unsigned int, std::string> &alloctype = varInfo.alloctype;
|
||||
const std::map<unsigned int, std::string> &possibleUsage = varInfo.possibleUsage;
|
||||
|
||||
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
||||
for (std::map<unsigned int, std::string>::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<unsigned int, std::string>::const_iterator use = possibleUsage.find(varid);
|
||||
if (use == possibleUsage.end()) {
|
||||
leakError(tok, var->name());
|
||||
} else {
|
||||
configurationInfo(tok, use->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
#ifndef checkleakautovarH
|
||||
#define checkleakautovarH
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "check.h"
|
||||
|
||||
|
||||
class VarInfo {
|
||||
public:
|
||||
std::map<unsigned int, std::string> alloctype;
|
||||
std::map<unsigned int, std::string> possibleUsage;
|
||||
std::set<unsigned int> 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<unsigned int> 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
|
||||
|
|
@ -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 \
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "tokenize.h"
|
||||
#include "checkleakautovar.h"
|
||||
#include "testsuite.h"
|
||||
#include <sstream>
|
||||
|
||||
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)
|
||||
|
Loading…
Reference in New Issue