Check for double frees when using smart pointers (#1172)
* Check for double frees when using smart pointers * Some updates from feedback * Add test for mismatch allocation * Constants * Check smart pointer deleter * Switch order * Use next * Add owned state * Fix handling of leaks * Use ast for checking addressof operator * Remove stray character * Add a test for mismatch allocator * Add another test for deallocating with custom function
This commit is contained in:
parent
19591298f1
commit
2a657cfd08
|
@ -67,6 +67,9 @@ void VarInfo::print()
|
||||||
|
|
||||||
std::string status;
|
std::string status;
|
||||||
switch (it->second.status) {
|
switch (it->second.status) {
|
||||||
|
case OWNED:
|
||||||
|
status = "owned";
|
||||||
|
break;
|
||||||
case DEALLOC:
|
case DEALLOC:
|
||||||
status = "dealloc";
|
status = "dealloc";
|
||||||
break;
|
break;
|
||||||
|
@ -456,13 +459,13 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
|
||||||
|
|
||||||
// Conditional allocation/deallocation
|
// Conditional allocation/deallocation
|
||||||
for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) {
|
for (it = varInfo1.alloctype.begin(); it != varInfo1.alloctype.end(); ++it) {
|
||||||
if (it->second.status == VarInfo::DEALLOC && conditionalAlloc.find(it->first) != conditionalAlloc.end()) {
|
if (it->second.managed() && conditionalAlloc.find(it->first) != conditionalAlloc.end()) {
|
||||||
varInfo->conditionalAlloc.erase(it->first);
|
varInfo->conditionalAlloc.erase(it->first);
|
||||||
varInfo2.erase(it->first);
|
varInfo2.erase(it->first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) {
|
for (it = varInfo2.alloctype.begin(); it != varInfo2.alloctype.end(); ++it) {
|
||||||
if (it->second.status == VarInfo::DEALLOC && conditionalAlloc.find(it->first) != conditionalAlloc.end()) {
|
if (it->second.managed() && conditionalAlloc.find(it->first) != conditionalAlloc.end()) {
|
||||||
varInfo->conditionalAlloc.erase(it->first);
|
varInfo->conditionalAlloc.erase(it->first);
|
||||||
varInfo1.erase(it->first);
|
varInfo1.erase(it->first);
|
||||||
}
|
}
|
||||||
|
@ -553,6 +556,53 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
|
||||||
else if (Token::Match(tok, "continue|break ;")) {
|
else if (Token::Match(tok, "continue|break ;")) {
|
||||||
varInfo->clear();
|
varInfo->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check smart pointer
|
||||||
|
else if (Token::Match(ftok, "auto_ptr|unique_ptr|shared_ptr < %type%")) {
|
||||||
|
|
||||||
|
|
||||||
|
const Token * endToken = ftok->linkAt(1);
|
||||||
|
if(!Token::Match(endToken, "> %var% {|( %var%"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool arrayDelete = false;
|
||||||
|
if(Token::findsimplematch(ftok->next(), "[ ]", endToken))
|
||||||
|
arrayDelete = true;
|
||||||
|
|
||||||
|
// Check deleter
|
||||||
|
const Token * deleterToken = nullptr;
|
||||||
|
const Token * endDeleterToken = nullptr;
|
||||||
|
const Library::AllocFunc* af = nullptr;
|
||||||
|
if(Token::Match(ftok, "unique_ptr < %type% ,")) {
|
||||||
|
deleterToken = ftok->tokAt(4);
|
||||||
|
endDeleterToken = endToken;
|
||||||
|
} else if(Token::Match(endToken, "> %var% {|( %var% ,")) {
|
||||||
|
deleterToken = endToken->tokAt(5);
|
||||||
|
endDeleterToken = endToken->linkAt(2);
|
||||||
|
}
|
||||||
|
if(deleterToken) {
|
||||||
|
// Check if its a pointer to a function
|
||||||
|
const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken);
|
||||||
|
if (dtok) {
|
||||||
|
af = _settings->library.dealloc(dtok->tokAt(1));
|
||||||
|
} else {
|
||||||
|
// If the deleter is a class, check if class calls the dealloc function
|
||||||
|
dtok = Token::findmatch(deleterToken, "%type%", endDeleterToken);
|
||||||
|
if (dtok && dtok->type()) {
|
||||||
|
const Scope * tscope = dtok->type()->classScope;
|
||||||
|
for (const Token *tok2 = tscope->classStart; tok2 != tscope->classEnd; tok2 = tok2->next()) {
|
||||||
|
af = _settings->library.dealloc(tok2);
|
||||||
|
if (af)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Token * vtok = endToken->tokAt(3);
|
||||||
|
const VarInfo::AllocInfo allocation(af ? af->groupId : (arrayDelete ? NEW_ARRAY : NEW), VarInfo::OWNED);
|
||||||
|
changeAllocStatus(varInfo, allocation, vtok, vtok);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,7 +617,7 @@ void CheckLeakAutoVar::changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocI
|
||||||
possibleUsage[arg->varId()] = tok->str();
|
possibleUsage[arg->varId()] = tok->str();
|
||||||
if (var->second.status == VarInfo::DEALLOC && arg->previous()->str() == "&")
|
if (var->second.status == VarInfo::DEALLOC && arg->previous()->str() == "&")
|
||||||
varInfo->erase(arg->varId());
|
varInfo->erase(arg->varId());
|
||||||
} else if (var->second.status == VarInfo::DEALLOC) {
|
} else if (var->second.managed()) {
|
||||||
doubleFreeError(tok, arg->str(), allocation.type);
|
doubleFreeError(tok, arg->str(), allocation.type);
|
||||||
} else if (var->second.type != allocation.type) {
|
} else if (var->second.type != allocation.type) {
|
||||||
// mismatching allocation and deallocation
|
// mismatching allocation and deallocation
|
||||||
|
@ -575,7 +625,7 @@ void CheckLeakAutoVar::changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocI
|
||||||
varInfo->erase(arg->varId());
|
varInfo->erase(arg->varId());
|
||||||
} else {
|
} else {
|
||||||
// deallocation
|
// deallocation
|
||||||
var->second.status = VarInfo::DEALLOC;
|
var->second.status = allocation.status;
|
||||||
var->second.type = allocation.type;
|
var->second.type = allocation.type;
|
||||||
}
|
}
|
||||||
} else if (allocation.status != VarInfo::NOALLOC) {
|
} else if (allocation.status != VarInfo::NOALLOC) {
|
||||||
|
@ -629,7 +679,7 @@ void CheckLeakAutoVar::leakIfAllocated(const Token *vartok,
|
||||||
const std::map<unsigned int, std::string> &possibleUsage = varInfo.possibleUsage;
|
const std::map<unsigned int, std::string> &possibleUsage = varInfo.possibleUsage;
|
||||||
|
|
||||||
const std::map<unsigned int, VarInfo::AllocInfo>::const_iterator var = alloctype.find(vartok->varId());
|
const std::map<unsigned int, VarInfo::AllocInfo>::const_iterator var = alloctype.find(vartok->varId());
|
||||||
if (var != alloctype.end() && var->second.status != VarInfo::DEALLOC) {
|
if (var != alloctype.end() && var->second.status == VarInfo::ALLOC) {
|
||||||
const std::map<unsigned int, std::string>::const_iterator use = possibleUsage.find(vartok->varId());
|
const std::map<unsigned int, std::string>::const_iterator use = possibleUsage.find(vartok->varId());
|
||||||
if (use == possibleUsage.end()) {
|
if (use == possibleUsage.end()) {
|
||||||
leakError(vartok, vartok->str(), var->second.type);
|
leakError(vartok, vartok->str(), var->second.type);
|
||||||
|
@ -647,7 +697,7 @@ void CheckLeakAutoVar::ret(const Token *tok, const VarInfo &varInfo)
|
||||||
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase();
|
||||||
for (std::map<unsigned int, VarInfo::AllocInfo>::const_iterator it = alloctype.begin(); it != alloctype.end(); ++it) {
|
for (std::map<unsigned int, VarInfo::AllocInfo>::const_iterator it = alloctype.begin(); it != alloctype.end(); ++it) {
|
||||||
// don't warn if variable is conditionally allocated
|
// don't warn if variable is conditionally allocated
|
||||||
if (it->second.status != VarInfo::DEALLOC && varInfo.conditionalAlloc.find(it->first) != varInfo.conditionalAlloc.end())
|
if (!it->second.managed() && varInfo.conditionalAlloc.find(it->first) != varInfo.conditionalAlloc.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// don't warn if there is a reference of the variable
|
// don't warn if there is a reference of the variable
|
||||||
|
@ -675,7 +725,7 @@ void CheckLeakAutoVar::ret(const Token *tok, const VarInfo &varInfo)
|
||||||
if (used && it->second.status == VarInfo::DEALLOC)
|
if (used && it->second.status == VarInfo::DEALLOC)
|
||||||
deallocReturnError(tok, var->name());
|
deallocReturnError(tok, var->name());
|
||||||
|
|
||||||
else if (!used && it->second.status != VarInfo::DEALLOC) {
|
else if (!used && !it->second.managed()) {
|
||||||
const std::map<unsigned int, std::string>::const_iterator use = possibleUsage.find(varid);
|
const std::map<unsigned int, std::string>::const_iterator use = possibleUsage.find(varid);
|
||||||
if (use == possibleUsage.end()) {
|
if (use == possibleUsage.end()) {
|
||||||
leakError(tok, var->name(), it->second.type);
|
leakError(tok, var->name(), it->second.type);
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Tokenizer;
|
||||||
|
|
||||||
class CPPCHECKLIB VarInfo {
|
class CPPCHECKLIB VarInfo {
|
||||||
public:
|
public:
|
||||||
enum AllocStatus { DEALLOC = -1, NOALLOC = 0, ALLOC = 1 };
|
enum AllocStatus { OWNED = -2, DEALLOC = -1, NOALLOC = 0, ALLOC = 1 };
|
||||||
struct AllocInfo {
|
struct AllocInfo {
|
||||||
AllocStatus status;
|
AllocStatus status;
|
||||||
/** Allocation type. If it is a positive value then it corresponds to
|
/** Allocation type. If it is a positive value then it corresponds to
|
||||||
|
@ -47,6 +47,11 @@ public:
|
||||||
*/
|
*/
|
||||||
int type;
|
int type;
|
||||||
AllocInfo(int type_ = 0, AllocStatus status_ = NOALLOC) : status(status_), type(type_) {}
|
AllocInfo(int type_ = 0, AllocStatus status_ = NOALLOC) : status(status_), type(type_) {}
|
||||||
|
|
||||||
|
bool managed() const
|
||||||
|
{
|
||||||
|
return status < 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
std::map<unsigned int, AllocInfo> alloctype;
|
std::map<unsigned int, AllocInfo> alloctype;
|
||||||
std::map<unsigned int, std::string> possibleUsage;
|
std::map<unsigned int, std::string> possibleUsage;
|
||||||
|
|
|
@ -770,7 +770,7 @@ std::string Library::getFunctionName(const Token *ftok, bool *error) const
|
||||||
|
|
||||||
std::string Library::getFunctionName(const Token *ftok) const
|
std::string Library::getFunctionName(const Token *ftok) const
|
||||||
{
|
{
|
||||||
if (!Token::Match(ftok, "%name% ("))
|
if (!Token::Match(ftok, "%name% (") && (ftok->strAt(-1) != "&" || ftok->previous()->astOperand2()))
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
// Lookup function name using AST..
|
// Lookup function name using AST..
|
||||||
|
|
|
@ -72,6 +72,7 @@ private:
|
||||||
TEST_CASE(doublefree5); // #5522
|
TEST_CASE(doublefree5); // #5522
|
||||||
TEST_CASE(doublefree6); // #7685
|
TEST_CASE(doublefree6); // #7685
|
||||||
TEST_CASE(doublefree7);
|
TEST_CASE(doublefree7);
|
||||||
|
TEST_CASE(doublefree8);
|
||||||
|
|
||||||
// exit
|
// exit
|
||||||
TEST_CASE(exit1);
|
TEST_CASE(exit1);
|
||||||
|
@ -105,6 +106,8 @@ private:
|
||||||
// mismatching allocation/deallocation
|
// mismatching allocation/deallocation
|
||||||
TEST_CASE(mismatchAllocDealloc);
|
TEST_CASE(mismatchAllocDealloc);
|
||||||
|
|
||||||
|
TEST_CASE(smartPointerDeleter);
|
||||||
|
|
||||||
// Execution reaches a 'return'
|
// Execution reaches a 'return'
|
||||||
TEST_CASE(return1);
|
TEST_CASE(return1);
|
||||||
TEST_CASE(return2);
|
TEST_CASE(return2);
|
||||||
|
@ -889,6 +892,58 @@ private:
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void doublefree8() {
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int;\n"
|
||||||
|
" std::unique_ptr<int> x(i);\n"
|
||||||
|
" delete i;\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) Memory pointed to by 'i' is freed twice.\n", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int;\n"
|
||||||
|
" delete i;\n"
|
||||||
|
" std::unique_ptr<int> x(i);\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) Memory pointed to by 'i' is freed twice.\n", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int;\n"
|
||||||
|
" std::unique_ptr<int> x{i};\n"
|
||||||
|
" delete i;\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) Memory pointed to by 'i' is freed twice.\n", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int;\n"
|
||||||
|
" std::shared_ptr<int> x(i);\n"
|
||||||
|
" delete i;\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) Memory pointed to by 'i' is freed twice.\n", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int;\n"
|
||||||
|
" std::shared_ptr<int> x{i};\n"
|
||||||
|
" delete i;\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) Memory pointed to by 'i' is freed twice.\n", errout.str());
|
||||||
|
|
||||||
|
// Check for use-after-free FP
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int;\n"
|
||||||
|
" std::shared_ptr<int> x{i};\n"
|
||||||
|
" *i = 123;\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int[1];\n"
|
||||||
|
" std::unique_ptr<int[]> x(i);\n"
|
||||||
|
" delete i;\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (error) Memory pointed to by 'i' is freed twice.\n", errout.str());
|
||||||
|
}
|
||||||
|
|
||||||
void exit1() {
|
void exit1() {
|
||||||
check("void f() {\n"
|
check("void f() {\n"
|
||||||
" char *p = malloc(10);\n"
|
" char *p = malloc(10);\n"
|
||||||
|
@ -1129,6 +1184,61 @@ private:
|
||||||
" char *cPtr = new (buf) char[100];\n"
|
" char *cPtr = new (buf) char[100];\n"
|
||||||
"}", true);
|
"}", true);
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int[1];\n"
|
||||||
|
" std::unique_ptr<int> x(i);\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:3]: (error) Mismatching allocation and deallocation: i\n", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" int * i = new int;\n"
|
||||||
|
" std::unique_ptr<int[]> x(i);\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:3]: (error) Mismatching allocation and deallocation: i\n", errout.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void smartPointerDeleter() {
|
||||||
|
check("void f() {\n"
|
||||||
|
" FILE*f=fopen(fname,a);\n"
|
||||||
|
" std::unique_ptr<FILE> fp{f};\n"
|
||||||
|
"}", true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:3]: (error) Mismatching allocation and deallocation: f\n", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" FILE*f=fopen(fname,a);\n"
|
||||||
|
" std::unique_ptr<FILE, decltype(&fclose)> fp{f, &fclose};\n"
|
||||||
|
"}", true);
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n"
|
||||||
|
" FILE*f=fopen(fname,a);\n"
|
||||||
|
" std::shared_ptr<FILE> fp{f, &fclose};\n"
|
||||||
|
"}", true);
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("struct deleter { void operator()(FILE* f) { fclose(f); }};\n"
|
||||||
|
"void f() {\n"
|
||||||
|
" FILE*f=fopen(fname,a);\n"
|
||||||
|
" std::unique_ptr<FILE, deleter> fp{f};\n"
|
||||||
|
"}", true);
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("int * create();\n"
|
||||||
|
"void destroy(int * x);\n"
|
||||||
|
"void f() {\n"
|
||||||
|
" int x * = create()\n"
|
||||||
|
" std::unique_ptr<int, decltype(&destroy)> xp{x, &destroy()};\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("int * create();\n"
|
||||||
|
"void destroy(int * x);\n"
|
||||||
|
"void f() {\n"
|
||||||
|
" int x * = create()\n"
|
||||||
|
" std::unique_ptr<int, decltype(&destroy)> xp(x, &destroy());\n"
|
||||||
|
"}\n", true);
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void return1() {
|
void return1() {
|
||||||
|
|
Loading…
Reference in New Issue