From 839fcddd8a1bd21960c8cfc42b5ea16aaea6b395 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Fri, 5 Jul 2019 12:44:52 +0200 Subject: [PATCH] Fix #6115 (Add support to realloc to cfg files) (#1953) * Allow to configure realloc like functions * memleakonrealloc: Bring back tests. The old memleak checker was removed, and the tests for it was removed in commit 9765a2dfab4f59102507fe759ef2184b5ed41c9f. This also removed the tests for memleakOnRealloc. Bring back those tests, somewhat modified since the checker no longer checks for memory leaks. * Add realloc to mem leak check * Add tests of realloc buffer size * Configure realloc functions * Add test of freopen * Allow to configure which element is realloc argument * Fix wrong close in test cppcheck now warns for this * Update manual * Update docs * Rename alloc/dalloc/realloc functions Naming the member function realloc caused problems on appveyor. Rename the alloc and dealloc functions as well for consistency. * Change comparisson order * Remove variable and use function call directly * Create temporary variable to simplify * Throw mismatchError on mismatching allocation/reallocation * Refactor to separate function * Fix potential nullptr dereference As pointed out by cppcheck. --- cfg/cppcheck-cfg.rng | 27 +++++ cfg/std.cfg | 3 + lib/checkautovariables.cpp | 4 +- lib/checkclass.cpp | 4 +- lib/checkleakautovar.cpp | 41 +++++-- lib/checkleakautovar.h | 3 + lib/checkmemoryleak.cpp | 4 +- lib/library.cpp | 43 +++++-- lib/library.h | 26 +++- lib/valueflow.cpp | 4 +- man/manual.docbook | 13 +- man/reference-cfg-format.md | 13 +- test/cfg/std.c | 2 +- test/testleakautovar.cpp | 73 ++++++++++++ test/testlibrary.cpp | 10 +- test/testmemleak.cpp | 229 ++++++++++++++++++++++++++++++++++++ test/testvalueflow.cpp | 14 +++ 17 files changed, 474 insertions(+), 39 deletions(-) diff --git a/cfg/cppcheck-cfg.rng b/cfg/cppcheck-cfg.rng index 2a52b3203..6aaeaba8d 100644 --- a/cfg/cppcheck-cfg.rng +++ b/cfg/cppcheck-cfg.rng @@ -37,6 +37,21 @@ + + + + + + + + + + + + + + + @@ -62,6 +77,18 @@ + + + + + + + + + + + + diff --git a/cfg/std.cfg b/cfg/std.cfg index 83f7b6036..84079a959 100644 --- a/cfg/std.cfg +++ b/cfg/std.cfg @@ -7506,11 +7506,14 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init malloc calloc aligned_alloc + realloc + reallocarray free fopen tmpfile + freopen fclose diff --git a/lib/checkautovariables.cpp b/lib/checkautovariables.cpp index 3f7be8672..9c76190df 100644 --- a/lib/checkautovariables.cpp +++ b/lib/checkautovariables.cpp @@ -296,7 +296,7 @@ void CheckAutoVariables::autoVariables() errorAutoVariableAssignment(tok->next(), false); } // Invalid pointer deallocation - else if ((Token::Match(tok, "%name% ( %var% ) ;") && mSettings->library.dealloc(tok)) || + else if ((Token::Match(tok, "%name% ( %var% ) ;") && mSettings->library.getDeallocFuncInfo(tok)) || (mTokenizer->isCPP() && Token::Match(tok, "delete [| ]| (| %var% !!["))) { tok = Token::findmatch(tok->next(), "%var%"); if (isArrayVar(tok)) @@ -309,7 +309,7 @@ void CheckAutoVariables::autoVariables() } } } - } else if ((Token::Match(tok, "%name% ( & %var% ) ;") && mSettings->library.dealloc(tok)) || + } else if ((Token::Match(tok, "%name% ( & %var% ) ;") && mSettings->library.getDeallocFuncInfo(tok)) || (mTokenizer->isCPP() && Token::Match(tok, "delete [| ]| (| & %var% !!["))) { tok = Token::findmatch(tok->next(), "%var%"); if (isAutoVar(tok)) diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index 5cf2da01a..1548d5034 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -331,7 +331,7 @@ void CheckClass::copyconstructors() const Token* tok = func.token->linkAt(1); for (const Token* const end = func.functionScope->bodyStart; tok != end; tok = tok->next()) { if (Token::Match(tok, "%var% ( new") || - (Token::Match(tok, "%var% ( %name% (") && mSettings->library.alloc(tok->tokAt(2)))) { + (Token::Match(tok, "%var% ( %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2)))) { const Variable* var = tok->variable(); if (var && var->isPointer() && var->scope() == scope) allocatedVars[tok->varId()] = tok; @@ -339,7 +339,7 @@ void CheckClass::copyconstructors() } for (const Token* const end = func.functionScope->bodyEnd; tok != end; tok = tok->next()) { if (Token::Match(tok, "%var% = new") || - (Token::Match(tok, "%var% = %name% (") && mSettings->library.alloc(tok->tokAt(2)))) { + (Token::Match(tok, "%var% = %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2)))) { const Variable* var = tok->variable(); if (var && var->isPointer() && var->scope() == scope && !var->isStatic()) allocatedVars[tok->varId()] = tok; diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index aecd93fe8..b2553173a 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -335,13 +335,16 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, } // allocation? - if (tokRightAstOperand && Token::Match(tokRightAstOperand->previous(), "%type% (")) { - const Library::AllocFunc* f = mSettings->library.alloc(tokRightAstOperand->previous()); + const Token *const fTok = tokRightAstOperand ? tokRightAstOperand->previous() : nullptr; + if (Token::Match(fTok, "%type% (")) { + const Library::AllocFunc* f = mSettings->library.getAllocFuncInfo(fTok); if (f && f->arg == -1) { VarInfo::AllocInfo& varAlloc = alloctype[varTok->varId()]; varAlloc.type = f->groupId; varAlloc.status = VarInfo::ALLOC; } + + changeAllocStatusIfRealloc(alloctype, fTok, varTok); } else if (mTokenizer->isCPP() && Token::Match(varTok->tokAt(2), "new !!(")) { const Token* tok2 = varTok->tokAt(2)->astOperand1(); const bool arrayNew = (tok2 && (tok2->str() == "[" || (tok2->str() == "(" && tok2->astOperand1() && tok2->astOperand1()->str() == "["))); @@ -375,7 +378,7 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, while (tokRightAstOperand && tokRightAstOperand->isCast()) tokRightAstOperand = tokRightAstOperand->astOperand2() ? tokRightAstOperand->astOperand2() : tokRightAstOperand->astOperand1(); if (tokRightAstOperand && Token::Match(tokRightAstOperand->previous(), "%type% (")) { - const Library::AllocFunc* f = mSettings->library.alloc(tokRightAstOperand->previous()); + const Library::AllocFunc* f = mSettings->library.getAllocFuncInfo(tokRightAstOperand->previous()); if (f && f->arg == -1) { VarInfo::AllocInfo& varAlloc = alloctype[innerTok->varId()]; varAlloc.type = f->groupId; @@ -384,6 +387,8 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, // Fixme: warn about leak alloctype.erase(innerTok->varId()); } + + changeAllocStatusIfRealloc(alloctype, innerTok->tokAt(2), varTok); } else if (mTokenizer->isCPP() && Token::Match(innerTok->tokAt(2), "new !!(")) { const Token* tok2 = innerTok->tokAt(2)->astOperand1(); const bool arrayNew = (tok2 && (tok2->str() == "[" || (tok2->str() == "(" && tok2->astOperand1() && tok2->astOperand1()->str() == "["))); @@ -552,7 +557,7 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, // Function call.. else if (isFunctionCall(ftok)) { const Token * openingPar = isFunctionCall(ftok); - const Library::AllocFunc* af = mSettings->library.dealloc(ftok); + const Library::AllocFunc* af = mSettings->library.getDeallocFuncInfo(ftok); VarInfo::AllocInfo allocation(af ? af->groupId : 0, VarInfo::DEALLOC); if (allocation.type == 0) allocation.status = VarInfo::NOALLOC; @@ -636,7 +641,7 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, // Check if its a pointer to a function const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken); if (dtok) { - af = mSettings->library.dealloc(dtok->tokAt(1)); + af = mSettings->library.getDeallocFuncInfo(dtok->tokAt(1)); } else { const Token * tscopeStart = nullptr; const Token * tscopeEnd = nullptr; @@ -658,7 +663,7 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken, if (tscopeStart && tscopeEnd) { for (const Token *tok2 = tscopeStart; tok2 != tscopeEnd; tok2 = tok2->next()) { - af = mSettings->library.dealloc(tok2); + af = mSettings->library.getDeallocFuncInfo(tok2); if (af) break; } @@ -697,7 +702,7 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t // check for function call const Token * const openingPar = isFunctionCall(tok); if (openingPar) { - const Library::AllocFunc* allocFunc = mSettings->library.dealloc(tok); + const Library::AllocFunc* allocFunc = mSettings->library.getDeallocFuncInfo(tok); VarInfo::AllocInfo alloc(allocFunc ? allocFunc->groupId : 0, VarInfo::DEALLOC); if (alloc.type == 0) alloc.status = VarInfo::NOALLOC; @@ -709,6 +714,22 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t } +void CheckLeakAutoVar::changeAllocStatusIfRealloc(std::map &alloctype, const Token *fTok, const Token *retTok) +{ + 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()]; + VarInfo::AllocInfo& retAlloc = alloctype[retTok->varId()]; + if (argAlloc.type != 0 && argAlloc.type != f->groupId) + mismatchError(fTok, argTok->str()); + argAlloc.status = VarInfo::DEALLOC; + retAlloc.type = f->groupId; + retAlloc.status = VarInfo::ALLOC; + } +} + + void CheckLeakAutoVar::changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocInfo& allocation, const Token* tok, const Token* arg) { std::map &alloctype = varInfo->alloctype; @@ -740,6 +761,8 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin // Ignore function call? if (mSettings->library.isLeakIgnore(tokName->str())) return; + if (mSettings->library.getReallocFuncInfo(tokName)) + return; const Token * const tokFirstArg = tokOpeningPar->next(); if (!tokFirstArg || tokFirstArg->str() == ")") { @@ -796,14 +819,14 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin // Check if its a pointer to a function const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken); if (dtok) { - sp_af = mSettings->library.dealloc(dtok->tokAt(1)); + sp_af = mSettings->library.getDeallocFuncInfo(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->bodyStart; tok2 != tscope->bodyEnd; tok2 = tok2->next()) { - sp_af = mSettings->library.dealloc(tok2); + sp_af = mSettings->library.getDeallocFuncInfo(tok2); if (sp_af) break; } diff --git a/lib/checkleakautovar.h b/lib/checkleakautovar.h index 2b4ab49dd..a7acc2ee4 100644 --- a/lib/checkleakautovar.h +++ b/lib/checkleakautovar.h @@ -131,6 +131,9 @@ private: /** parse changes in allocation status */ void changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocInfo& allocation, const Token* tok, const Token* arg); + /** update allocation status if reallocation function */ + void changeAllocStatusIfRealloc(std::map &alloctype, const Token *fTok, const Token *retTok); + /** return. either "return" or end of variable scope is seen */ void ret(const Token *tok, const VarInfo &varInfo); diff --git a/lib/checkmemoryleak.cpp b/lib/checkmemoryleak.cpp index 06324799a..23c6a1a8e 100644 --- a/lib/checkmemoryleak.cpp +++ b/lib/checkmemoryleak.cpp @@ -172,7 +172,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getAllocationType(const Token *tok2, } // Does tok2 point on a Library allocation function? - const int alloctype = mSettings_->library.alloc(tok2, -1); + const int alloctype = mSettings_->library.getAllocId(tok2, -1); if (alloctype > 0) { if (alloctype == mSettings_->library.deallocId("free")) return Malloc; @@ -263,7 +263,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getDeallocationType(const Token *tok } // Does tok point on a Library deallocation function? - const int dealloctype = mSettings_->library.dealloc(tok, argNr); + const int dealloctype = mSettings_->library.getDeallocId(tok, argNr); if (dealloctype > 0) { if (dealloctype == mSettings_->library.deallocId("free")) return Malloc; diff --git a/lib/library.cpp b/lib/library.cpp index affcb26cf..393088179 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -188,7 +188,7 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc) // add alloc/dealloc/use functions.. for (const tinyxml2::XMLElement *memorynode = node->FirstChildElement(); memorynode; memorynode = memorynode->NextSiblingElement()) { const std::string memorynodename = memorynode->Name(); - if (memorynodename == "alloc") { + if (memorynodename == "alloc" || memorynodename == "realloc") { AllocFunc temp = {0}; temp.groupId = allocationId; @@ -225,7 +225,18 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc) return Error(BAD_ATTRIBUTE_VALUE, bufferSize); } - mAlloc[memorynode->GetText()] = temp; + if (memorynodename == "realloc") { + const char *reallocArg = memorynode->Attribute("realloc-arg"); + if (reallocArg) + temp.reallocArg = atoi(reallocArg); + else + temp.reallocArg = 1; + } + + if (memorynodename != "realloc") + mAlloc[memorynode->GetText()] = temp; + else + mRealloc[memorynode->GetText()] = temp; } else if (memorynodename == "dealloc") { AllocFunc temp = {0}; temp.groupId = allocationId; @@ -934,30 +945,44 @@ bool Library::isuninitargbad(const Token *ftok, int argnr) const /** get allocation info for function */ -const Library::AllocFunc* Library::alloc(const Token *tok) const +const Library::AllocFunc* Library::getAllocFuncInfo(const Token *tok) const { const std::string funcname = getFunctionName(tok); return isNotLibraryFunction(tok) && functions.find(funcname) != functions.end() ? nullptr : getAllocDealloc(mAlloc, funcname); } /** get deallocation info for function */ -const Library::AllocFunc* Library::dealloc(const Token *tok) const +const Library::AllocFunc* Library::getDeallocFuncInfo(const Token *tok) const { const std::string funcname = getFunctionName(tok); return isNotLibraryFunction(tok) && functions.find(funcname) != functions.end() ? nullptr : getAllocDealloc(mDealloc, funcname); } -/** get allocation id for function */ -int Library::alloc(const Token *tok, int arg) const +/** get reallocation info for function */ +const Library::AllocFunc* Library::getReallocFuncInfo(const Token *tok) const { - const Library::AllocFunc* af = alloc(tok); + const std::string funcname = getFunctionName(tok); + return isNotLibraryFunction(tok) && functions.find(funcname) != functions.end() ? nullptr : getAllocDealloc(mRealloc, funcname); +} + +/** get allocation id for function */ +int Library::getAllocId(const Token *tok, int arg) const +{ + const Library::AllocFunc* af = getAllocFuncInfo(tok); return (af && af->arg == arg) ? af->groupId : 0; } /** get deallocation id for function */ -int Library::dealloc(const Token *tok, int arg) const +int Library::getDeallocId(const Token *tok, int arg) const { - const Library::AllocFunc* af = dealloc(tok); + const Library::AllocFunc* af = getDeallocFuncInfo(tok); + return (af && af->arg == arg) ? af->groupId : 0; +} + +/** get reallocation id for function */ +int Library::getReallocId(const Token *tok, int arg) const +{ + const Library::AllocFunc* af = getReallocFuncInfo(tok); return (af && af->arg == arg) ? af->groupId : 0; } diff --git a/lib/library.h b/lib/library.h index d550b139a..2dc4449fd 100644 --- a/lib/library.h +++ b/lib/library.h @@ -77,27 +77,34 @@ public: BufferSize bufferSize; int bufferSizeArg1; int bufferSizeArg2; + int reallocArg; }; /** get allocation info for function */ - const AllocFunc* alloc(const Token *tok) const; + const AllocFunc* getAllocFuncInfo(const Token *tok) const; /** get deallocation info for function */ - const AllocFunc* dealloc(const Token *tok) const; + const AllocFunc* getDeallocFuncInfo(const Token *tok) const; + + /** get reallocation info for function */ + const AllocFunc* getReallocFuncInfo(const Token *tok) const; /** get allocation id for function */ - int alloc(const Token *tok, int arg) const; + int getAllocId(const Token *tok, int arg) const; /** get deallocation id for function */ - int dealloc(const Token *tok, int arg) const; + int getDeallocId(const Token *tok, int arg) const; + + /** get reallocation id for function */ + int getReallocId(const Token *tok, int arg) const; /** get allocation info for function by name (deprecated, use other alloc) */ - const AllocFunc* alloc(const char name[]) const { + const AllocFunc* getAllocFuncInfo(const char name[]) const { return getAllocDealloc(mAlloc, name); } /** get deallocation info for function by name (deprecated, use other alloc) */ - const AllocFunc* dealloc(const char name[]) const { + const AllocFunc* getDeallocFuncInfo(const char name[]) const { return getAllocDealloc(mDealloc, name); } @@ -124,6 +131,12 @@ public: mDealloc[functionname].arg = arg; } + void setrealloc(const std::string &functionname, int id, int arg, int reallocArg = 1) { + mRealloc[functionname].groupId = id; + mRealloc[functionname].arg = arg; + mRealloc[functionname].reallocArg = reallocArg; + } + /** add noreturn function setting */ void setnoreturn(const std::string& funcname, bool noreturn) { mNoReturn[funcname] = noreturn; @@ -523,6 +536,7 @@ private: std::set mFiles; std::map mAlloc; // allocation functions std::map mDealloc; // deallocation functions + std::map mRealloc; // reallocation functions std::map mNoReturn; // is function noreturn? std::map mReturnValue; std::map mReturnValueType; diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index f90e88922..0ff9f6abf 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -5310,7 +5310,9 @@ static void valueFlowDynamicBufferSize(TokenList *tokenlist, SymbolDatabase *sym if (!Token::Match(rhs->previous(), "%name% (")) continue; - const Library::AllocFunc *allocFunc = settings->library.alloc(rhs->previous()); + const Library::AllocFunc *allocFunc = settings->library.getAllocFuncInfo(rhs->previous()); + if (!allocFunc) + allocFunc = settings->library.getReallocFuncInfo(rhs->previous()); if (!allocFunc || allocFunc->bufferSize == Library::AllocFunc::BufferSize::none) continue; diff --git a/man/manual.docbook b/man/manual.docbook index 519773ed8..a72bed604 100644 --- a/man/manual.docbook +++ b/man/manual.docbook @@ -1183,7 +1183,7 @@ Checking test.c... functions do not affect the allocation at all.
- alloc and dealloc + alloc, realloc and dealloc Here is an example program: @@ -1217,6 +1217,17 @@ Checking pen1.c... </resource> </def> + Functions that reallocate memory can be configured using a realloc tag. The input argument which points to the memory that shall be reallocated can also be configured (the default is the first argument). As an example, here is a configuration file for the fopen, freopen and fclose functions from the c standard library: + + <?xml version="1.0"?> +<def> + <resource> + <alloc>fopen</alloc> + <realloc realloc-arg="3">freopen</realloc> + <dealloc>fclose</dealloc> + </resource> +</def> + The allocation and deallocation functions are organized in groups. Each group is defined in a <resource> or <memory> tag and is identified by its diff --git a/man/reference-cfg-format.md b/man/reference-cfg-format.md index c736cff2c..f15b471b6 100644 --- a/man/reference-cfg-format.md +++ b/man/reference-cfg-format.md @@ -15,7 +15,7 @@ This is a reference for the .cfg file format that Cppcheck uses. Cppcheck has configurable checking for leaks, e.g. you can specify which functions allocate and free memory or resources and which functions do not affect the allocation at all. -## `` and `` +## ``, `` and `` Here is an example program: @@ -45,6 +45,17 @@ Here is a minimal windows.cfg file: +Functions that reallocate memory can be configured using a `` tag. The input argument which points to the memory that shall be reallocated can also be configured (the default is the first argument). As an example, here is a configuration file for the fopen, freopen and fclose functions from the c standard library: + + + + + fopen + freopen + fclose + + + The allocation and deallocation functions are organized in groups. Each group is defined in a `` or `` tag and is identified by its `` functions. This means, groups with overlapping `` tags are merged. ## `` and `` diff --git a/test/cfg/std.c b/test/cfg/std.c index 869da96fd..ef7d2b5bd 100644 --- a/test/cfg/std.c +++ b/test/cfg/std.c @@ -1434,7 +1434,7 @@ void uninitvar_freopen(void) FILE *stream; // cppcheck-suppress uninitvar FILE * p = freopen(filename,mode,stream); - free(p); + fclose(p); } void uninitvar_frexp(void) diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp index 3eac62d88..7fe1b7036 100644 --- a/test/testleakautovar.cpp +++ b/test/testleakautovar.cpp @@ -36,9 +36,11 @@ private: int id = 0; while (!settings.library.ismemory(++id)); settings.library.setalloc("malloc", id, -1); + settings.library.setrealloc("realloc", id, -1); settings.library.setdealloc("free", id, 1); while (!settings.library.isresource(++id)); settings.library.setalloc("fopen", id, -1); + settings.library.setrealloc("freopen", id, -1, 3); settings.library.setdealloc("fclose", id, 1); settings.library.smartPointers.insert("std::shared_ptr"); settings.library.smartPointers.insert("std::unique_ptr"); @@ -63,6 +65,12 @@ private: TEST_CASE(assign17); // #9047 TEST_CASE(assign18); + TEST_CASE(realloc1); + TEST_CASE(realloc2); + TEST_CASE(realloc3); + TEST_CASE(freopen1); + TEST_CASE(freopen2); + TEST_CASE(deallocuse1); TEST_CASE(deallocuse2); TEST_CASE(deallocuse3); @@ -350,6 +358,48 @@ private: ASSERT_EQUALS("[test.c:3]: (error) Memory leak: p\n", errout.str()); } + void realloc1() { + check("void f() {\n" + " void *p = malloc(10);\n" + " void *q = realloc(p, 20);\n" + " free(q)\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc2() { + check("void f() {\n" + " void *p = malloc(10);\n" + " void *q = realloc(p, 20);\n" + "}"); + ASSERT_EQUALS("[test.c:4]: (error) Memory leak: q\n", errout.str()); + } + + void realloc3() { + check("void f() {\n" + " char *p = malloc(10);\n" + " char *q = (char*) realloc(p, 20);\n" + "}"); + ASSERT_EQUALS("[test.c:4]: (error) Memory leak: q\n", errout.str()); + } + + void freopen1() { + check("void f() {\n" + " void *p = fopen(name,a);\n" + " void *q = freopen(name, b, p);\n" + " fclose(q)\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void freopen2() { + check("void f() {\n" + " void *p = fopen(name,a);\n" + " void *q = freopen(name, b, p);\n" + "}"); + ASSERT_EQUALS("[test.c:4]: (error) Resource leak: q\n", errout.str()); + } + void deallocuse1() { check("void f(char *p) {\n" " free(p);\n" @@ -1327,6 +1377,29 @@ private: " std::unique_ptr x(i);\n" "}\n", true); ASSERT_EQUALS("[test.cpp:3]: (error) Mismatching allocation and deallocation: i\n", errout.str()); + + check("void f() {\n" + " void* a = malloc(1);\n" + " void* b = freopen(f, p, a);\n" + " free(b);\n" + "}"); + ASSERT_EQUALS("[test.c:3]: (error) Mismatching allocation and deallocation: a\n" + "[test.c:4]: (error) Mismatching allocation and deallocation: b\n", errout.str()); + + check("void f() {\n" + " void* a;\n" + " void* b = realloc(a, 10);\n" + " free(b);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + check("void f() {\n" + " int * i = new int;\n" + " int * j = realloc(i, 2 * sizeof(int));\n" + " delete[] j;\n" + "}", true); + ASSERT_EQUALS("[test.cpp:3]: (error) Mismatching allocation and deallocation: i\n" + "[test.cpp:4]: (error) Mismatching allocation and deallocation: j\n", errout.str()); } void smartPointerDeleter() { diff --git a/test/testlibrary.cpp b/test/testlibrary.cpp index 7ff70b48d..31bb57a98 100644 --- a/test/testlibrary.cpp +++ b/test/testlibrary.cpp @@ -622,11 +622,11 @@ private: ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode); ASSERT(library.functions.empty()); - ASSERT(Library::ismemory(library.alloc("CreateX"))); + ASSERT(Library::ismemory(library.getAllocFuncInfo("CreateX"))); ASSERT_EQUALS(library.allocId("CreateX"), library.deallocId("DeleteX")); - const Library::AllocFunc* af = library.alloc("CreateX"); + const Library::AllocFunc* af = library.getAllocFuncInfo("CreateX"); ASSERT(af && af->arg == -1); - const Library::AllocFunc* df = library.dealloc("DeleteX"); + const Library::AllocFunc* df = library.getDeallocFuncInfo("DeleteX"); ASSERT(df && df->arg == 1); } void memory2() const { @@ -665,9 +665,9 @@ private: ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode); ASSERT(library.functions.empty()); - const Library::AllocFunc* af = library.alloc("CreateX"); + const Library::AllocFunc* af = library.getAllocFuncInfo("CreateX"); ASSERT(af && af->arg == 5); - const Library::AllocFunc* df = library.dealloc("DeleteX"); + const Library::AllocFunc* df = library.getDeallocFuncInfo("DeleteX"); ASSERT(df && df->arg == 2); ASSERT(library.returnuninitdata.find("CreateX") != library.returnuninitdata.cend()); diff --git a/test/testmemleak.cpp b/test/testmemleak.cpp index 89fbbdcfb..24bcd7cd9 100644 --- a/test/testmemleak.cpp +++ b/test/testmemleak.cpp @@ -121,6 +121,235 @@ REGISTER_TEST(TestMemleak) +class TestMemleakInFunction : public TestFixture { +public: + TestMemleakInFunction() : TestFixture("TestMemleakInFunction") { + } + +private: + Settings settings0; + Settings settings1; + Settings settings2; + + void check(const char code[]) { + // Clear the error buffer.. + errout.str(""); + + Settings *settings = &settings1; + + // Tokenize.. + Tokenizer tokenizer(settings, this); + std::istringstream istr(code); + tokenizer.tokenize(istr, "test.cpp"); + tokenizer.simplifyTokenList2(); + + // Check for memory leaks.. + CheckMemoryLeakInFunction checkMemoryLeak(&tokenizer, settings, this); + checkMemoryLeak.checkReallocUsage(); + } + + + void run() OVERRIDE { + LOAD_LIB_2(settings1.library, "std.cfg"); + LOAD_LIB_2(settings1.library, "posix.cfg"); + LOAD_LIB_2(settings2.library, "std.cfg"); + + TEST_CASE(realloc1); + TEST_CASE(realloc2); + TEST_CASE(realloc3); + TEST_CASE(realloc4); + TEST_CASE(realloc5); + TEST_CASE(realloc7); + TEST_CASE(realloc8); + TEST_CASE(realloc9); + TEST_CASE(realloc10); + TEST_CASE(realloc11); + TEST_CASE(realloc12); + TEST_CASE(realloc13); + TEST_CASE(realloc14); + TEST_CASE(realloc15); + TEST_CASE(realloc16); + } + + void realloc1() { + check("void foo()\n" + "{\n" + " char *a = (char *)malloc(10);\n" + " a = realloc(a, 100);\n" + "}"); + ASSERT_EQUALS("[test.cpp:4]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure\n", errout.str()); + } + + void realloc2() { + check("void foo()\n" + "{\n" + " char *a = (char *)malloc(10);\n" + " a = (char *)realloc(a, 100);\n" + " free(a);\n" + "}"); + + ASSERT_EQUALS("[test.cpp:4]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure\n", errout.str()); + } + + void realloc3() { + check("void foo()\n" + "{\n" + " char *a = 0;\n" + " if ((a = realloc(a, 100)) == NULL)\n" + " return;\n" + " free(a);\n" + "}"); + + ASSERT_EQUALS("", errout.str()); + } + + void realloc4() { + check("void foo()\n" + "{\n" + " static char *a = 0;\n" + " if ((a = realloc(a, 100)) == NULL)\n" + " return;\n" + " free(a);\n" + "}"); + + TODO_ASSERT_EQUALS("[test.cpp:5]: (error) Memory leak: a\n", + "[test.cpp:4]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure\n", + errout.str()); + } + + void realloc5() { + check("void foo()\n" + "{\n" + " char *buf;\n" + " char *new_buf;\n" + " buf = calloc( 10 );\n" + " new_buf = realloc ( buf, 20);\n" + " if ( !new_buf )\n" + " free(buf);\n" + " else\n" + " free(new_buf);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc7() { + check("bool foo(size_t nLen, char* pData)\n" + "{\n" + " pData = (char*) realloc(pData, sizeof(char) + (nLen + 1)*sizeof(char));\n" + " if ( pData == NULL )\n" + " {\n" + " return false;\n" + " }\n" + " free(pData);\n" + " return true;\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc8() { + check("void foo()\n" + "{\n" + " char *origBuf = m_buf;\n" + " m_buf = (char *) realloc (m_buf, m_capacity + growBy);\n" + " if (!m_buf) {\n" + " m_buf = origBuf;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc9() { + check("void foo()\n" + "{\n" + " x = realloc(x,100);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc10() { + check("void foo() {\n" + " char *pa, *pb;\n" + " pa = pb = malloc(10);\n" + " pa = realloc(pa, 20);" + " exit();\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc11() { + check("void foo() {\n" + " char *p;\n" + " p = realloc(p, size);\n" + " if (!p)\n" + " error();\n" + " usep(p);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc12() { + check("void foo(int x)\n" + "{\n" + " char *a = 0;\n" + " if ((a = realloc(a, x + 100)) == NULL)\n" + " return;\n" + " free(a);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc13() { + check("void foo()\n" + "{\n" + " char **str;\n" + " *str = realloc(*str,100);\n" + " free (*str);\n" + "}"); + ASSERT_EQUALS("[test.cpp:4]: (error) Common realloc mistake: \'str\' nulled but not freed upon failure\n", errout.str()); + } + + void realloc14() { + check("void foo() {\n" + " char *p;\n" + " p = realloc(p, size + 1);\n" + " if (!p)\n" + " error();\n" + " usep(p);\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + + void realloc15() { + check("bool foo() {\n" + " char ** m_options;\n" + " m_options = (char**)realloc( m_options, 2 * sizeof(char*));\n" + " if( m_options == NULL )\n" + " return false;\n" + " return true;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3]: (error) Common realloc mistake: \'m_options\' nulled but not freed upon failure\n", errout.str()); + } + + void realloc16() { + check("void f(char *zLine) {\n" + " zLine = realloc(zLine, 42);\n" + " if (zLine) {\n" + " free(zLine);\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } +}; + +REGISTER_TEST(TestMemleakInFunction) + + + + + + + + class TestMemleakInClass : public TestFixture { public: TestMemleakInClass() : TestFixture("TestMemleakInClass") { diff --git a/test/testvalueflow.cpp b/test/testvalueflow.cpp index 45a2fcdd4..2c1037e32 100644 --- a/test/testvalueflow.cpp +++ b/test/testvalueflow.cpp @@ -3887,6 +3887,20 @@ private: " return x;\n" "}"; ASSERT_EQUALS(true, testValueOfX(code, 4U, 5, ValueFlow::Value::BUFFER_SIZE)); + + code = "void* f() {\n" + " void* y = malloc(10);\n" + " void* x = realloc(y, 20);\n" + " return x;\n" + "}"; + ASSERT_EQUALS(true, testValueOfX(code, 4U, 20, ValueFlow::Value::BUFFER_SIZE)); + + code = "void* f() {\n" + " void* y = calloc(10, 4);\n" + " void* x = reallocarray(y, 20, 5);\n" + " return x;\n" + "}"; + ASSERT_EQUALS(true, testValueOfX(code, 4U, 100, ValueFlow::Value::BUFFER_SIZE)); } };