Added TestLocalLeaks

This commit is contained in:
Daniel Marjamäki 2009-12-14 20:30:31 +01:00
parent 42c2437f78
commit 0c13f9ba5c
7 changed files with 474 additions and 188 deletions

View File

@ -22,6 +22,7 @@ LIBOBJ = lib/checkautovariables.o \
lib/checkunusedfunctions.o \
lib/cppcheck.o \
lib/errorlogger.o \
lib/executionpath.o \
lib/filelister.o \
lib/mathlib.o \
lib/preprocessor.o \
@ -109,10 +110,10 @@ lib/checkexceptionsafety.o: lib/checkexceptionsafety.cpp lib/checkexceptionsafet
lib/checkheaders.o: lib/checkheaders.cpp lib/checkheaders.h lib/tokenize.h lib/classinfo.h lib/token.h lib/errorlogger.h lib/settings.h lib/filelister.h
$(CXX) $(CXXFLAGS) -Ilib -c -o lib/checkheaders.o lib/checkheaders.cpp
lib/checkmemoryleak.o: lib/checkmemoryleak.cpp lib/checkmemoryleak.h lib/check.h lib/token.h lib/tokenize.h lib/classinfo.h lib/settings.h lib/errorlogger.h lib/mathlib.h
lib/checkmemoryleak.o: lib/checkmemoryleak.cpp lib/checkmemoryleak.h lib/check.h lib/token.h lib/tokenize.h lib/classinfo.h lib/settings.h lib/errorlogger.h lib/mathlib.h lib/executionpath.h
$(CXX) $(CXXFLAGS) -Ilib -c -o lib/checkmemoryleak.o lib/checkmemoryleak.cpp
lib/checkother.o: lib/checkother.cpp lib/checkother.h lib/check.h lib/token.h lib/tokenize.h lib/classinfo.h lib/settings.h lib/errorlogger.h lib/mathlib.h
lib/checkother.o: lib/checkother.cpp lib/checkother.h lib/check.h lib/token.h lib/tokenize.h lib/classinfo.h lib/settings.h lib/errorlogger.h lib/mathlib.h lib/executionpath.h
$(CXX) $(CXXFLAGS) -Ilib -c -o lib/checkother.o lib/checkother.cpp
lib/checkstl.o: lib/checkstl.cpp lib/checkstl.h lib/check.h lib/token.h lib/tokenize.h lib/classinfo.h lib/settings.h lib/errorlogger.h
@ -127,6 +128,9 @@ lib/cppcheck.o: lib/cppcheck.cpp lib/cppcheck.h lib/settings.h lib/errorlogger.h
lib/errorlogger.o: lib/errorlogger.cpp lib/errorlogger.h lib/settings.h lib/tokenize.h lib/classinfo.h lib/token.h
$(CXX) $(CXXFLAGS) -Ilib -c -o lib/errorlogger.o lib/errorlogger.cpp
lib/executionpath.o: lib/executionpath.cpp lib/executionpath.h lib/token.h
$(CXX) $(CXXFLAGS) -Ilib -c -o lib/executionpath.o lib/executionpath.cpp
lib/filelister.o: lib/filelister.cpp lib/filelister.h
$(CXX) $(CXXFLAGS) -Ilib -c -o lib/filelister.o lib/filelister.cpp

View File

@ -20,6 +20,7 @@
#include "checkmemoryleak.h"
#include "mathlib.h"
#include "tokenize.h"
#include "executionpath.h"
#include <algorithm>
#include <cstring>
@ -2513,3 +2514,169 @@ void CheckMemoryLeakStructMember::check()
class CheckLocalLeaks : public ExecutionPath
{
public:
// Startup constructor
CheckLocalLeaks(CheckMemoryLeak *c) : ExecutionPath(), ownerCheck(c), varId(0), allocated(false)
{
}
static void printOut(const std::list<ExecutionPath *> &checks)
{
std::ostringstream ostr;
ostr << "CheckLocalLeaks::printOut" << std::endl;
for (std::list<ExecutionPath *>::const_iterator it = checks.begin(); it != checks.end(); ++it)
{
CheckLocalLeaks *c = dynamic_cast<CheckLocalLeaks *>(*it);
if (c)
{
ostr << std::hex << (int)c << ": varId=" << c->varId << " allocated=" << (c->allocated ? "true" : "false") << std::endl;
}
}
std::cout << ostr.str();
}
private:
ExecutionPath *copy()
{
return new CheckLocalLeaks(*this);
}
/** start checking of given variable */
CheckLocalLeaks(CheckMemoryLeak *c, unsigned int v, const std::string &s) : ExecutionPath(), ownerCheck(c), varId(v), allocated(false), varname(s)
{
}
CheckMemoryLeak * const ownerCheck;
const unsigned int varId;
bool allocated;
const std::string varname;
/* no implementation */
void operator=(const CheckLocalLeaks &);
static void alloc(std::list<ExecutionPath *> &checks, const unsigned int varid)
{
if (varid == 0)
return;
std::list<ExecutionPath *>::iterator it;
for (it = checks.begin(); it != checks.end(); ++it)
{
CheckLocalLeaks *C = dynamic_cast<CheckLocalLeaks *>(*it);
if (C && C->varId == varid)
C->allocated = true;
}
}
static void dealloc(std::list<ExecutionPath *> &checks, const Token *tok)
{
if (tok->varId() == 0)
return;
std::list<ExecutionPath *>::iterator it;
for (it = checks.begin(); it != checks.end(); ++it)
{
CheckLocalLeaks *C = dynamic_cast<CheckLocalLeaks *>(*it);
if (C && C->varId == tok->varId())
C->allocated = false;
}
}
static void ret(const std::list<ExecutionPath *> &checks, const Token *tok)
{
std::list<ExecutionPath *>::const_iterator it;
for (it = checks.begin(); it != checks.end(); ++it)
{
CheckLocalLeaks *C = dynamic_cast<CheckLocalLeaks *>(*it);
if (C && C->allocated)
{
C->ownerCheck->memleakError(tok, C->varname, false);
}
}
}
const Token *parse(const Token &tok, bool &, std::list<ExecutionPath *> &checks) const
{
//std::cout << "CheckLocalLeaks::parse " << tok.str() << std::endl;
//printOut(checks);
if (!Token::Match(tok.previous(), "[;{}]"))
return &tok;
if (Token::Match(&tok, "%type% * %var% ;"))
{
const Token * vartok = tok.tokAt(2);
if (vartok->varId() != 0)
checks.push_back(new CheckLocalLeaks(ownerCheck, vartok->varId(), vartok->str()));
return vartok->next();
}
if (Token::Match(&tok, "%var% = new"))
{
alloc(checks, tok.varId());
// goto end of statement
const Token *tok2 = &tok;
while (tok2 && tok2->str() != ";")
tok2 = tok2->next();
return tok2;
}
if (Token::Match(&tok, "delete %var% ;"))
{
dealloc(checks, tok.next());
return tok.tokAt(2);
}
if (Token::Match(&tok, "delete [ ] %var% ;"))
{
dealloc(checks, tok.tokAt(3));
return tok.tokAt(4);
}
if (tok.str() == "return")
{
ret(checks, &tok);
}
return &tok;
}
};
void CheckMemoryLeakInFunction::localleaks()
{
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next())
{
if (tok->str() != ")")
continue;
// Start of implementation..
if (Token::Match(tok, ") const| {"))
{
// goto the "{"
tok = tok->next();
if (tok->str() == "const")
tok = tok->next();
// Check this scope..
std::list<ExecutionPath *> checks;
checks.push_back(new CheckLocalLeaks(this));
checkExecutionPaths(tok->next(), checks);
while (!checks.empty())
{
delete checks.back();
checks.pop_back();
}
// skip this scope - it has been checked
tok = tok->link();
}
}
}

View File

@ -161,6 +161,8 @@ public:
void check();
void localleaks();
#ifndef _MSC_VER
private:
#endif

View File

@ -21,6 +21,7 @@
#include "checkother.h"
#include "mathlib.h"
#include "tokenize.h"
#include "executionpath.h"
#include <algorithm>
#include <list>
@ -1117,50 +1118,6 @@ void CheckOther::nullPointer()
}
/**
* Base class for Execution Paths checking
* An execution path is a linear list of statements. There are no "if"/.. to worry about.
**/
class ExecutionPath
{
private:
mutable bool bailout_;
public:
ExecutionPath() : bailout_(false)
{ }
virtual ~ExecutionPath()
{ }
virtual ExecutionPath *copy() = 0;
bool bailOut() const
{
return bailout_;
}
/**
* bail out all execution paths
* @param checks the execution paths to bail out on
**/
static void bailOut(const std::list<ExecutionPath *> &checks)
{
std::list<ExecutionPath *>::const_iterator it;
for (it = checks.begin(); it != checks.end(); ++it)
(*it)->bailout_ = true;
}
/**
* Parse tokens at given location
* @param tok token to parse
* @param foundError If an error is found this is set to true and the return token is the error token
* @param checks The execution paths. All execution paths in the list are executed in the current scope.
* @return if error is found => error token. if you want to skip tokens, return the last skipped token. otherwise return tok.
**/
virtual const Token *parse(const Token &tok, bool &foundError, std::list<ExecutionPath *> &checks) const = 0;
};
class CheckNullpointer : public ExecutionPath
@ -1416,148 +1373,6 @@ private:
};
static const Token *checkExecutionPaths(const Token *tok, std::list<ExecutionPath *> &checks)
{
const std::auto_ptr<ExecutionPath> check(checks.front()->copy());
// Number of "if" blocks in current scope
unsigned int numberOfIf = 0;
for (; tok; tok = tok->next())
{
if (tok->str() == "}")
return 0;
// todo: handle for/while
if (Token::Match(tok, "for|while|switch"))
{
ExecutionPath::bailOut(checks);
return 0;
}
if (Token::Match(tok, "= {|("))
{
tok = tok->next()->link();
if (Token::simpleMatch(tok, ") {"))
tok = tok->next()->link();
if (!tok)
{
ExecutionPath::bailOut(checks);
return 0;
}
continue;
}
if (tok->str() == "if")
{
++numberOfIf;
if (numberOfIf > 1)
return 0;
std::list<ExecutionPath *> newchecks;
while (tok->str() == "if")
{
// goto "("
tok = tok->next();
// goto ")"
tok = tok ? tok->link() : 0;
// Check if the condition contains the variable
if (tok)
{
for (const Token *tok2 = tok->link(); tok2 && tok2 != tok; tok2 = tok2->next())
{
// The variable may be initialized..
// if (someFunction(&var)) ..
if (tok2->varId())
{
ExecutionPath::bailOut(checks);
return 0;
}
}
}
// goto "{"
tok = tok ? tok->next() : 0;
if (!Token::simpleMatch(tok, "{"))
return 0;
// Recursively check into the if ..
{
std::list<ExecutionPath *> c;
std::list<ExecutionPath *>::iterator it;
for (it = checks.begin(); it != checks.end(); ++it)
c.push_back((*it)->copy());
const Token *tokerr = checkExecutionPaths(tok->next(), c);
if (tokerr)
return tokerr;
while (!c.empty())
{
newchecks.push_back(c.back());
c.pop_back();
}
}
// goto "}"
tok = tok->link();
// there is no else => break out
if (Token::Match(tok, "} !!else"))
break;
// parse next "if"..
tok = tok->tokAt(2);
if (tok->str() == "if")
continue;
// there is no "if"..
const Token *tokerr = checkExecutionPaths(tok->next(), checks);
if (tokerr)
return tokerr;
tok = tok->link();
if (!tok)
return 0;
}
std::list<ExecutionPath *>::iterator it;
for (it = newchecks.begin(); it != newchecks.end(); ++it)
checks.push_back((*it)->copy());
}
{
bool foundError = false;
tok = check->parse(*tok, foundError, checks);
std::list<ExecutionPath *>::iterator it;
for (it = checks.begin(); it != checks.end();)
{
if ((*it)->bailOut())
{
delete *it;
it = checks.erase(it);
}
else
{
++it;
}
}
if (checks.empty())
return 0;
else if (foundError)
return tok;
}
// return ends all execution paths
if (tok->str() == "return")
{
ExecutionPath::bailOut(checks);
}
}
return 0;
}
void CheckOther::executionPaths()
{

167
lib/executionpath.cpp Normal file
View File

@ -0,0 +1,167 @@
/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2009 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 "executionpath.h"
#include "token.h"
#include <memory>
const Token *checkExecutionPaths(const Token *tok, std::list<ExecutionPath *> &checks)
{
const std::auto_ptr<ExecutionPath> check(checks.front()->copy());
// Number of "if" blocks in current scope
unsigned int numberOfIf = 0;
for (; tok; tok = tok->next())
{
if (tok->str() == "}")
return 0;
// todo: handle for/while
if (Token::Match(tok, "for|while|switch"))
{
ExecutionPath::bailOut(checks);
return 0;
}
if (Token::Match(tok, "= {|("))
{
tok = tok->next()->link();
if (Token::simpleMatch(tok, ") {"))
tok = tok->next()->link();
if (!tok)
{
ExecutionPath::bailOut(checks);
return 0;
}
continue;
}
if (tok->str() == "if")
{
++numberOfIf;
if (numberOfIf > 1)
return 0;
std::list<ExecutionPath *> newchecks;
while (tok->str() == "if")
{
// goto "("
tok = tok->next();
// goto ")"
tok = tok ? tok->link() : 0;
// Check if the condition contains the variable
if (tok)
{
for (const Token *tok2 = tok->link(); tok2 && tok2 != tok; tok2 = tok2->next())
{
// The variable may be initialized..
// if (someFunction(&var)) ..
if (tok2->varId())
{
ExecutionPath::bailOut(checks);
return 0;
}
}
}
// goto "{"
tok = tok ? tok->next() : 0;
if (!Token::simpleMatch(tok, "{"))
return 0;
// Recursively check into the if ..
{
std::list<ExecutionPath *> c;
std::list<ExecutionPath *>::iterator it;
for (it = checks.begin(); it != checks.end(); ++it)
c.push_back((*it)->copy());
const Token *tokerr = checkExecutionPaths(tok->next(), c);
if (tokerr)
return tokerr;
while (!c.empty())
{
newchecks.push_back(c.back());
c.pop_back();
}
}
// goto "}"
tok = tok->link();
// there is no else => break out
if (Token::Match(tok, "} !!else"))
break;
// parse next "if"..
tok = tok->tokAt(2);
if (tok->str() == "if")
continue;
// there is no "if"..
const Token *tokerr = checkExecutionPaths(tok->next(), checks);
if (tokerr)
return tokerr;
tok = tok->link();
if (!tok)
return 0;
}
std::list<ExecutionPath *>::iterator it;
for (it = newchecks.begin(); it != newchecks.end(); ++it)
checks.push_back((*it)->copy());
}
{
bool foundError = false;
tok = check->parse(*tok, foundError, checks);
std::list<ExecutionPath *>::iterator it;
for (it = checks.begin(); it != checks.end();)
{
if ((*it)->bailOut())
{
delete *it;
it = checks.erase(it);
}
else
{
++it;
}
}
if (checks.empty())
return 0;
else if (foundError)
return tok;
}
// return ends all execution paths
if (tok->str() == "return")
{
ExecutionPath::bailOut(checks);
}
}
return 0;
}

74
lib/executionpath.h Normal file
View File

@ -0,0 +1,74 @@
/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2009 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 executionpathH
#define executionpathH
#include <list>
class Token;
/**
* Base class for Execution Paths checking
* An execution path is a linear list of statements. There are no "if"/.. to worry about.
**/
class ExecutionPath
{
private:
mutable bool bailout_;
public:
ExecutionPath() : bailout_(false)
{ }
virtual ~ExecutionPath()
{ }
virtual ExecutionPath *copy() = 0;
bool bailOut() const
{
return bailout_;
}
/**
* bail out all execution paths
* @param checks the execution paths to bail out on
**/
static void bailOut(const std::list<ExecutionPath *> &checks)
{
std::list<ExecutionPath *>::const_iterator it;
for (it = checks.begin(); it != checks.end(); ++it)
(*it)->bailout_ = true;
}
/**
* Parse tokens at given location
* @param tok token to parse
* @param foundError If an error is found this is set to true and the return token is the error token
* @param checks The execution paths. All execution paths in the list are executed in the current scope.
* @return if error is found => error token. if you want to skip tokens, return the last skipped token. otherwise return tok.
**/
virtual const Token *parse(const Token &tok, bool &foundError, std::list<ExecutionPath *> &checks) const = 0;
};
const Token *checkExecutionPaths(const Token *tok, std::list<ExecutionPath *> &checks);
#endif

View File

@ -29,6 +29,63 @@
extern std::ostringstream errout;
class TestLocalLeaks : private TestFixture
{
public:
TestLocalLeaks() : TestFixture("TestLocalLeaks")
{ }
private:
void run()
{
TEST_CASE(test1);
TEST_CASE(test2);
}
void check(const char code[])
{
// Tokenize..
Tokenizer tokenizer;
std::istringstream istr(code);
tokenizer.tokenize(istr, "test.cpp");
tokenizer.setVarId();
tokenizer.simplifyTokenList();
// Clear the error buffer..
errout.str("");
// Check for memory leaks..
Settings settings;
CheckMemoryLeakInFunction checkMemoryLeak(&tokenizer, &settings, this);
checkMemoryLeak.localleaks();
}
void test1()
{
check("void foo()\n"
"{\n"
" char *p = new char[100];\n"
" return;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:4]: (error) Memory leak: p\n", errout.str());
}
void test2()
{
check("void foo()\n"
"{\n"
" char *p = new char[100];\n"
" delete [] p;\n"
" return;\n"
"}\n");
ASSERT_EQUALS("", errout.str());
}
};
static TestLocalLeaks testLocalLeaks;
class TestMemleak : private TestFixture
{
public: