Improve handling of realloc in memory leak checker (#3036)
Mark realloced variables as realloced instead of freed. This allows improved checking for code with error realloc handling. If cppcheck finds an if-statement which checks the validity of the allocated memory or resource, check if the memory/resource is reallocated from another variable. If so, we can add checking of that variable in the if-statement instead. This allows to check that variable for memleaks and double frees. This fixes #9292 and #9990 which both concern FPs with double frees after correct error handling.
This commit is contained in:
parent
707f1f2fbe
commit
f018163551
|
@ -101,6 +101,9 @@ void VarInfo::print()
|
|||
case NOALLOC:
|
||||
status = "noalloc";
|
||||
break;
|
||||
case REALLOC:
|
||||
status = "realloc";
|
||||
break;
|
||||
default:
|
||||
status = "?";
|
||||
break;
|
||||
|
@ -111,6 +114,7 @@ void VarInfo::print()
|
|||
<< "possibleUsage='" << strusage << "' "
|
||||
<< "conditionalAlloc=" << (conditionalAlloc.find(it->first) != conditionalAlloc.end() ? "yes" : "no") << " "
|
||||
<< "referenced=" << (referenced.find(it->first) != referenced.end() ? "yes" : "no") << " "
|
||||
<< "reallocedFrom=" << it->second.reallocedFromType
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
@ -499,18 +503,24 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
|
|||
|
||||
const Token *vartok = nullptr;
|
||||
if (astIsVariableComparison(tok3, "!=", "0", &vartok)) {
|
||||
varInfo2.reallocToAlloc(vartok->varId());
|
||||
varInfo2.erase(vartok->varId());
|
||||
if (notzero.find(vartok->varId()) != notzero.end())
|
||||
varInfo2.clear();
|
||||
} else if (astIsVariableComparison(tok3, "==", "0", &vartok)) {
|
||||
varInfo1.reallocToAlloc(vartok->varId());
|
||||
varInfo1.erase(vartok->varId());
|
||||
} else if (astIsVariableComparison(tok3, "<", "0", &vartok)) {
|
||||
varInfo1.reallocToAlloc(vartok->varId());
|
||||
varInfo1.erase(vartok->varId());
|
||||
} else if (astIsVariableComparison(tok3, ">", "0", &vartok)) {
|
||||
varInfo2.reallocToAlloc(vartok->varId());
|
||||
varInfo2.erase(vartok->varId());
|
||||
} else if (astIsVariableComparison(tok3, "==", "-1", &vartok)) {
|
||||
varInfo1.reallocToAlloc(vartok->varId());
|
||||
varInfo1.erase(vartok->varId());
|
||||
} else if (astIsVariableComparison(tok3, "!=", "-1", &vartok)) {
|
||||
varInfo2.reallocToAlloc(vartok->varId());
|
||||
varInfo2.erase(vartok->varId());
|
||||
}
|
||||
return ChildrenToVisit::none;
|
||||
|
@ -801,15 +811,18 @@ void CheckLeakAutoVar::changeAllocStatusIfRealloc(std::map<int, VarInfo::AllocIn
|
|||
const Library::AllocFunc* f = mSettings->library.getReallocFuncInfo(fTok);
|
||||
if (f && f->arg == -1 && f->reallocArg > 0 && f->reallocArg <= numberOfArguments(fTok)) {
|
||||
const Token* argTok = getArguments(fTok).at(f->reallocArg - 1);
|
||||
VarInfo::AllocInfo& argAlloc = alloctype[argTok->varId()];
|
||||
if (alloctype.find(argTok->varId()) != alloctype.end()) {
|
||||
VarInfo::AllocInfo& argAlloc = alloctype[argTok->varId()];
|
||||
if (argAlloc.type != 0 && argAlloc.type != f->groupId)
|
||||
mismatchError(fTok, argAlloc.allocTok, argTok->str());
|
||||
argAlloc.status = VarInfo::REALLOC;
|
||||
argAlloc.allocTok = fTok;
|
||||
}
|
||||
VarInfo::AllocInfo& retAlloc = alloctype[retTok->varId()];
|
||||
if (argAlloc.type != 0 && argAlloc.type != f->groupId)
|
||||
mismatchError(fTok, argAlloc.allocTok, argTok->str());
|
||||
argAlloc.status = VarInfo::DEALLOC;
|
||||
argAlloc.allocTok = fTok;
|
||||
retAlloc.type = f->groupId;
|
||||
retAlloc.status = VarInfo::ALLOC;
|
||||
retAlloc.allocTok = fTok;
|
||||
retAlloc.reallocedFromType = argTok->varId();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -826,7 +839,8 @@ void CheckLeakAutoVar::changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocI
|
|||
varInfo->erase(arg->varId());
|
||||
} else if (var->second.managed()) {
|
||||
doubleFreeError(tok, var->second.allocTok, arg->str(), allocation.type);
|
||||
} else if (var->second.type != allocation.type) {
|
||||
var->second.status = allocation.status;
|
||||
} else if (var->second.type != allocation.type && var->second.type != 0) {
|
||||
// mismatching allocation and deallocation
|
||||
mismatchError(tok, var->second.allocTok, arg->str());
|
||||
varInfo->erase(arg->varId());
|
||||
|
|
|
@ -39,7 +39,7 @@ class Tokenizer;
|
|||
|
||||
class CPPCHECKLIB VarInfo {
|
||||
public:
|
||||
enum AllocStatus { OWNED = -2, DEALLOC = -1, NOALLOC = 0, ALLOC = 1 };
|
||||
enum AllocStatus { REALLOC = -3, OWNED = -2, DEALLOC = -1, NOALLOC = 0, ALLOC = 1 };
|
||||
struct AllocInfo {
|
||||
AllocStatus status;
|
||||
/** Allocation type. If it is a positive value then it corresponds to
|
||||
|
@ -47,6 +47,7 @@ public:
|
|||
* checkleakautovar allocation type.
|
||||
*/
|
||||
int type;
|
||||
int reallocedFromType = -1;
|
||||
const Token * allocTok;
|
||||
AllocInfo(int type_ = 0, AllocStatus status_ = NOALLOC, const Token* allocTok_ = nullptr) : status(status_), type(type_), allocTok(allocTok_) {}
|
||||
|
||||
|
@ -80,6 +81,16 @@ public:
|
|||
referenced.swap(other.referenced);
|
||||
}
|
||||
|
||||
void reallocToAlloc(nonneg int varid) {
|
||||
const AllocInfo& alloc = alloctype[varid];
|
||||
if (alloc.reallocedFromType >= 0) {
|
||||
const std::map<int, VarInfo::AllocInfo>::iterator it = alloctype.find(alloc.reallocedFromType);
|
||||
if (it != alloctype.end() && it->second.status == REALLOC) {
|
||||
it->second.status = ALLOC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** set possible usage for all variables */
|
||||
void possibleUsageAll(const std::string &functionName);
|
||||
|
||||
|
|
|
@ -86,6 +86,8 @@ private:
|
|||
TEST_CASE(realloc1);
|
||||
TEST_CASE(realloc2);
|
||||
TEST_CASE(realloc3);
|
||||
TEST_CASE(realloc4);
|
||||
TEST_CASE(realloc5); // #9292, #9990
|
||||
TEST_CASE(freopen1);
|
||||
TEST_CASE(freopen2);
|
||||
|
||||
|
@ -108,6 +110,7 @@ private:
|
|||
TEST_CASE(doublefree8);
|
||||
TEST_CASE(doublefree9);
|
||||
TEST_CASE(doublefree10); // #8706
|
||||
TEST_CASE(doublefree11);
|
||||
|
||||
// exit
|
||||
TEST_CASE(exit1);
|
||||
|
@ -139,6 +142,8 @@ private:
|
|||
TEST_CASE(ifelse15); // #9206 - if (global_ptr = malloc(1))
|
||||
TEST_CASE(ifelse16); // #9635 - if (p = malloc(4), p == NULL)
|
||||
TEST_CASE(ifelse17); // if (!!(!p))
|
||||
TEST_CASE(ifelse18);
|
||||
TEST_CASE(ifelse19);
|
||||
|
||||
// switch
|
||||
TEST_CASE(switch1);
|
||||
|
@ -467,6 +472,40 @@ private:
|
|||
ASSERT_EQUALS("[test.c:4]: (error) Memory leak: q\n", errout.str());
|
||||
}
|
||||
|
||||
void realloc4() {
|
||||
check("void f(void *p) {\n"
|
||||
" void * q = realloc(p, 10);\n"
|
||||
" if (q == NULL)\n"
|
||||
" return;\n"
|
||||
"}");
|
||||
ASSERT_EQUALS("[test.c:5]: (error) Memory leak: q\n", errout.str());
|
||||
}
|
||||
|
||||
void realloc5() {
|
||||
// #9292
|
||||
check("void * f(void * ptr, size_t size) {\n"
|
||||
" void *datap = realloc(ptr, size);\n"
|
||||
" if (size && !datap)\n"
|
||||
" free(ptr);\n"
|
||||
" return datap;\n"
|
||||
"}");
|
||||
ASSERT_EQUALS("", errout.str());
|
||||
|
||||
// #9990
|
||||
check("void f() {\n"
|
||||
" void * p1 = malloc(10);\n"
|
||||
" if (!p1)\n"
|
||||
" return;\n"
|
||||
" void * p2 = realloc(p1, 42);\n"
|
||||
" if (!p2) {\n"
|
||||
" free(p1);\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" free(p2);\n"
|
||||
"}");
|
||||
ASSERT_EQUALS("", errout.str());
|
||||
}
|
||||
|
||||
void freopen1() {
|
||||
check("void f() {\n"
|
||||
" void *p = fopen(name,a);\n"
|
||||
|
@ -1196,6 +1235,22 @@ private:
|
|||
ASSERT_EQUALS("", errout.str());
|
||||
}
|
||||
|
||||
void doublefree11() {
|
||||
check("void f() {\n"
|
||||
" void * p = malloc(5);\n"
|
||||
" void * q = realloc(p, 10);\n"
|
||||
" if (q == NULL) {\n"
|
||||
" free(p);\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" free(p);\n"
|
||||
" if (q == NULL)\n"
|
||||
" return;\n"
|
||||
" free(q)\n"
|
||||
"}");
|
||||
ASSERT_EQUALS("[test.c:3] -> [test.c:8]: (error) Memory pointed to by 'p' is freed twice.\n", errout.str());
|
||||
}
|
||||
|
||||
void exit1() {
|
||||
check("void f() {\n"
|
||||
" char *p = malloc(10);\n"
|
||||
|
@ -1508,6 +1563,39 @@ private:
|
|||
ASSERT_EQUALS("", errout.str());
|
||||
}
|
||||
|
||||
void ifelse18() {
|
||||
check("void f() {\n"
|
||||
" void * p = malloc(10);\n"
|
||||
" void * q = realloc(p, 20);\n"
|
||||
" if (q == 0)\n"
|
||||
" return;\n"
|
||||
" free(q);\n"
|
||||
"}");
|
||||
ASSERT_EQUALS("[test.c:5]: (error) Memory leak: p\n", errout.str());
|
||||
|
||||
check("void f() {\n"
|
||||
" void * p = malloc(10);\n"
|
||||
" void * q = realloc(p, 20);\n"
|
||||
" if (q != 0) {\n"
|
||||
" free(q);\n"
|
||||
" return;\n"
|
||||
" } else\n"
|
||||
" return;\n"
|
||||
"}");
|
||||
ASSERT_EQUALS("[test.c:8]: (error) Memory leak: p\n", errout.str());
|
||||
}
|
||||
|
||||
void ifelse19() {
|
||||
check("void f() {\n"
|
||||
" static char * a;\n"
|
||||
" char * b = realloc(a, 10);\n"
|
||||
" if (!b)\n"
|
||||
" return;\n"
|
||||
" a = b;\n"
|
||||
"}");
|
||||
ASSERT_EQUALS("", errout.str());
|
||||
}
|
||||
|
||||
void switch1() {
|
||||
check("void f() {\n"
|
||||
" char *p = 0;\n"
|
||||
|
|
Loading…
Reference in New Issue