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 9765a2dfab. 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.
This commit is contained in:
Rikard Falkeborn 2019-07-05 12:44:52 +02:00 committed by Daniel Marjamäki
parent e0ced1c415
commit 839fcddd8a
17 changed files with 474 additions and 39 deletions

View File

@ -37,6 +37,21 @@
</optional> </optional>
<ref name="DATA-NAME"/> <ref name="DATA-NAME"/>
</element> </element>
<element name="realloc">
<optional>
<attribute name="init"><ref name="DATA-BOOL"/></attribute>
</optional>
<optional>
<attribute name="arg"><ref name="ARGNO"/></attribute>
</optional>
<optional>
<attribute name="buffer-size"><ref name="DATA-BUFFER-SIZE"/></attribute>
</optional>
<optional>
<attribute name="realloc-arg"><ref name="ARGNO"/></attribute>
</optional>
<ref name="DATA-NAME"/>
</element>
<element name="use"><ref name="DATA-EXTNAME"/></element> <element name="use"><ref name="DATA-EXTNAME"/></element>
</choice> </choice>
</zeroOrMore> </zeroOrMore>
@ -62,6 +77,18 @@
</optional> </optional>
<ref name="DATA-NAME"/> <ref name="DATA-NAME"/>
</element> </element>
<element name="realloc">
<optional>
<attribute name="init"><ref name="DATA-BOOL"/></attribute>
</optional>
<optional>
<attribute name="arg"><ref name="ARGNO"/></attribute>
</optional>
<optional>
<attribute name="realloc-arg"><ref name="ARGNO"/></attribute>
</optional>
<ref name="DATA-NAME"/>
</element>
<element name="use"><ref name="DATA-EXTNAME"/></element> <element name="use"><ref name="DATA-EXTNAME"/></element>
</choice> </choice>
</zeroOrMore> </zeroOrMore>

View File

@ -7506,11 +7506,14 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init
<alloc init="false" buffer-size="malloc">malloc</alloc> <alloc init="false" buffer-size="malloc">malloc</alloc>
<alloc init="true" buffer-size="calloc">calloc</alloc> <alloc init="true" buffer-size="calloc">calloc</alloc>
<alloc init="false" buffer-size="malloc:2">aligned_alloc</alloc> <alloc init="false" buffer-size="malloc:2">aligned_alloc</alloc>
<realloc init="false" buffer-size="malloc:2">realloc</realloc>
<realloc init="false" buffer-size="calloc:2,3">reallocarray</realloc>
<dealloc>free</dealloc> <dealloc>free</dealloc>
</memory> </memory>
<resource> <resource>
<alloc init="true">fopen</alloc> <alloc init="true">fopen</alloc>
<alloc init="true">tmpfile</alloc> <alloc init="true">tmpfile</alloc>
<realloc init="true" realloc-arg="3">freopen</realloc>
<dealloc>fclose</dealloc> <dealloc>fclose</dealloc>
</resource> </resource>
<container id="stdContainer" endPattern="&gt; !!::" opLessAllowed="false" itEndPattern="&gt; :: iterator|const_iterator|reverse_iterator|const_reverse_iterator"> <container id="stdContainer" endPattern="&gt; !!::" opLessAllowed="false" itEndPattern="&gt; :: iterator|const_iterator|reverse_iterator|const_reverse_iterator">

View File

@ -296,7 +296,7 @@ void CheckAutoVariables::autoVariables()
errorAutoVariableAssignment(tok->next(), false); errorAutoVariableAssignment(tok->next(), false);
} }
// Invalid pointer deallocation // 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% !!["))) { (mTokenizer->isCPP() && Token::Match(tok, "delete [| ]| (| %var% !!["))) {
tok = Token::findmatch(tok->next(), "%var%"); tok = Token::findmatch(tok->next(), "%var%");
if (isArrayVar(tok)) 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% !!["))) { (mTokenizer->isCPP() && Token::Match(tok, "delete [| ]| (| & %var% !!["))) {
tok = Token::findmatch(tok->next(), "%var%"); tok = Token::findmatch(tok->next(), "%var%");
if (isAutoVar(tok)) if (isAutoVar(tok))

View File

@ -331,7 +331,7 @@ void CheckClass::copyconstructors()
const Token* tok = func.token->linkAt(1); const Token* tok = func.token->linkAt(1);
for (const Token* const end = func.functionScope->bodyStart; tok != end; tok = tok->next()) { for (const Token* const end = func.functionScope->bodyStart; tok != end; tok = tok->next()) {
if (Token::Match(tok, "%var% ( new") || 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(); const Variable* var = tok->variable();
if (var && var->isPointer() && var->scope() == scope) if (var && var->isPointer() && var->scope() == scope)
allocatedVars[tok->varId()] = tok; allocatedVars[tok->varId()] = tok;
@ -339,7 +339,7 @@ void CheckClass::copyconstructors()
} }
for (const Token* const end = func.functionScope->bodyEnd; tok != end; tok = tok->next()) { for (const Token* const end = func.functionScope->bodyEnd; tok != end; tok = tok->next()) {
if (Token::Match(tok, "%var% = new") || 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(); const Variable* var = tok->variable();
if (var && var->isPointer() && var->scope() == scope && !var->isStatic()) if (var && var->isPointer() && var->scope() == scope && !var->isStatic())
allocatedVars[tok->varId()] = tok; allocatedVars[tok->varId()] = tok;

View File

@ -335,13 +335,16 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
} }
// allocation? // allocation?
if (tokRightAstOperand && Token::Match(tokRightAstOperand->previous(), "%type% (")) { const Token *const fTok = tokRightAstOperand ? tokRightAstOperand->previous() : nullptr;
const Library::AllocFunc* f = mSettings->library.alloc(tokRightAstOperand->previous()); if (Token::Match(fTok, "%type% (")) {
const Library::AllocFunc* f = mSettings->library.getAllocFuncInfo(fTok);
if (f && f->arg == -1) { if (f && f->arg == -1) {
VarInfo::AllocInfo& varAlloc = alloctype[varTok->varId()]; VarInfo::AllocInfo& varAlloc = alloctype[varTok->varId()];
varAlloc.type = f->groupId; varAlloc.type = f->groupId;
varAlloc.status = VarInfo::ALLOC; varAlloc.status = VarInfo::ALLOC;
} }
changeAllocStatusIfRealloc(alloctype, fTok, varTok);
} else if (mTokenizer->isCPP() && Token::Match(varTok->tokAt(2), "new !!(")) { } else if (mTokenizer->isCPP() && Token::Match(varTok->tokAt(2), "new !!(")) {
const Token* tok2 = varTok->tokAt(2)->astOperand1(); const Token* tok2 = varTok->tokAt(2)->astOperand1();
const bool arrayNew = (tok2 && (tok2->str() == "[" || (tok2->str() == "(" && tok2->astOperand1() && tok2->astOperand1()->str() == "["))); 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()) while (tokRightAstOperand && tokRightAstOperand->isCast())
tokRightAstOperand = tokRightAstOperand->astOperand2() ? tokRightAstOperand->astOperand2() : tokRightAstOperand->astOperand1(); tokRightAstOperand = tokRightAstOperand->astOperand2() ? tokRightAstOperand->astOperand2() : tokRightAstOperand->astOperand1();
if (tokRightAstOperand && Token::Match(tokRightAstOperand->previous(), "%type% (")) { 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) { if (f && f->arg == -1) {
VarInfo::AllocInfo& varAlloc = alloctype[innerTok->varId()]; VarInfo::AllocInfo& varAlloc = alloctype[innerTok->varId()];
varAlloc.type = f->groupId; varAlloc.type = f->groupId;
@ -384,6 +387,8 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
// Fixme: warn about leak // Fixme: warn about leak
alloctype.erase(innerTok->varId()); alloctype.erase(innerTok->varId());
} }
changeAllocStatusIfRealloc(alloctype, innerTok->tokAt(2), varTok);
} else if (mTokenizer->isCPP() && Token::Match(innerTok->tokAt(2), "new !!(")) { } else if (mTokenizer->isCPP() && Token::Match(innerTok->tokAt(2), "new !!(")) {
const Token* tok2 = innerTok->tokAt(2)->astOperand1(); const Token* tok2 = innerTok->tokAt(2)->astOperand1();
const bool arrayNew = (tok2 && (tok2->str() == "[" || (tok2->str() == "(" && tok2->astOperand1() && tok2->astOperand1()->str() == "["))); 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.. // Function call..
else if (isFunctionCall(ftok)) { else if (isFunctionCall(ftok)) {
const Token * openingPar = 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); VarInfo::AllocInfo allocation(af ? af->groupId : 0, VarInfo::DEALLOC);
if (allocation.type == 0) if (allocation.type == 0)
allocation.status = VarInfo::NOALLOC; allocation.status = VarInfo::NOALLOC;
@ -636,7 +641,7 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
// Check if its a pointer to a function // Check if its a pointer to a function
const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken); const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken);
if (dtok) { if (dtok) {
af = mSettings->library.dealloc(dtok->tokAt(1)); af = mSettings->library.getDeallocFuncInfo(dtok->tokAt(1));
} else { } else {
const Token * tscopeStart = nullptr; const Token * tscopeStart = nullptr;
const Token * tscopeEnd = nullptr; const Token * tscopeEnd = nullptr;
@ -658,7 +663,7 @@ void CheckLeakAutoVar::checkScope(const Token * const startToken,
if (tscopeStart && tscopeEnd) { if (tscopeStart && tscopeEnd) {
for (const Token *tok2 = tscopeStart; tok2 != tscopeEnd; tok2 = tok2->next()) { for (const Token *tok2 = tscopeStart; tok2 != tscopeEnd; tok2 = tok2->next()) {
af = mSettings->library.dealloc(tok2); af = mSettings->library.getDeallocFuncInfo(tok2);
if (af) if (af)
break; break;
} }
@ -697,7 +702,7 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t
// check for function call // check for function call
const Token * const openingPar = isFunctionCall(tok); const Token * const openingPar = isFunctionCall(tok);
if (openingPar) { 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); VarInfo::AllocInfo alloc(allocFunc ? allocFunc->groupId : 0, VarInfo::DEALLOC);
if (alloc.type == 0) if (alloc.type == 0)
alloc.status = VarInfo::NOALLOC; alloc.status = VarInfo::NOALLOC;
@ -709,6 +714,22 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t
} }
void CheckLeakAutoVar::changeAllocStatusIfRealloc(std::map<unsigned int, VarInfo::AllocInfo> &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) void CheckLeakAutoVar::changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocInfo& allocation, const Token* tok, const Token* arg)
{ {
std::map<unsigned int, VarInfo::AllocInfo> &alloctype = varInfo->alloctype; std::map<unsigned int, VarInfo::AllocInfo> &alloctype = varInfo->alloctype;
@ -740,6 +761,8 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin
// Ignore function call? // Ignore function call?
if (mSettings->library.isLeakIgnore(tokName->str())) if (mSettings->library.isLeakIgnore(tokName->str()))
return; return;
if (mSettings->library.getReallocFuncInfo(tokName))
return;
const Token * const tokFirstArg = tokOpeningPar->next(); const Token * const tokFirstArg = tokOpeningPar->next();
if (!tokFirstArg || tokFirstArg->str() == ")") { 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 // Check if its a pointer to a function
const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken); const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken);
if (dtok) { if (dtok) {
sp_af = mSettings->library.dealloc(dtok->tokAt(1)); sp_af = mSettings->library.getDeallocFuncInfo(dtok->tokAt(1));
} else { } else {
// If the deleter is a class, check if class calls the dealloc function // If the deleter is a class, check if class calls the dealloc function
dtok = Token::findmatch(deleterToken, "%type%", endDeleterToken); dtok = Token::findmatch(deleterToken, "%type%", endDeleterToken);
if (dtok && dtok->type()) { if (dtok && dtok->type()) {
const Scope * tscope = dtok->type()->classScope; const Scope * tscope = dtok->type()->classScope;
for (const Token *tok2 = tscope->bodyStart; tok2 != tscope->bodyEnd; tok2 = tok2->next()) { 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) if (sp_af)
break; break;
} }

View File

@ -131,6 +131,9 @@ private:
/** parse changes in allocation status */ /** parse changes in allocation status */
void changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocInfo& allocation, const Token* tok, const Token* arg); void changeAllocStatus(VarInfo *varInfo, const VarInfo::AllocInfo& allocation, const Token* tok, const Token* arg);
/** update allocation status if reallocation function */
void changeAllocStatusIfRealloc(std::map<unsigned int, VarInfo::AllocInfo> &alloctype, const Token *fTok, const Token *retTok);
/** return. either "return" or end of variable scope is seen */ /** return. either "return" or end of variable scope is seen */
void ret(const Token *tok, const VarInfo &varInfo); void ret(const Token *tok, const VarInfo &varInfo);

View File

@ -172,7 +172,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getAllocationType(const Token *tok2,
} }
// Does tok2 point on a Library allocation function? // 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 > 0) {
if (alloctype == mSettings_->library.deallocId("free")) if (alloctype == mSettings_->library.deallocId("free"))
return Malloc; return Malloc;
@ -263,7 +263,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getDeallocationType(const Token *tok
} }
// Does tok point on a Library deallocation function? // 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 > 0) {
if (dealloctype == mSettings_->library.deallocId("free")) if (dealloctype == mSettings_->library.deallocId("free"))
return Malloc; return Malloc;

View File

@ -188,7 +188,7 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc)
// add alloc/dealloc/use functions.. // add alloc/dealloc/use functions..
for (const tinyxml2::XMLElement *memorynode = node->FirstChildElement(); memorynode; memorynode = memorynode->NextSiblingElement()) { for (const tinyxml2::XMLElement *memorynode = node->FirstChildElement(); memorynode; memorynode = memorynode->NextSiblingElement()) {
const std::string memorynodename = memorynode->Name(); const std::string memorynodename = memorynode->Name();
if (memorynodename == "alloc") { if (memorynodename == "alloc" || memorynodename == "realloc") {
AllocFunc temp = {0}; AllocFunc temp = {0};
temp.groupId = allocationId; temp.groupId = allocationId;
@ -225,7 +225,18 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc)
return Error(BAD_ATTRIBUTE_VALUE, bufferSize); 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") { } else if (memorynodename == "dealloc") {
AllocFunc temp = {0}; AllocFunc temp = {0};
temp.groupId = allocationId; temp.groupId = allocationId;
@ -934,30 +945,44 @@ bool Library::isuninitargbad(const Token *ftok, int argnr) const
/** get allocation info for function */ /** 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); const std::string funcname = getFunctionName(tok);
return isNotLibraryFunction(tok) && functions.find(funcname) != functions.end() ? nullptr : getAllocDealloc(mAlloc, funcname); return isNotLibraryFunction(tok) && functions.find(funcname) != functions.end() ? nullptr : getAllocDealloc(mAlloc, funcname);
} }
/** get deallocation info for function */ /** 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); const std::string funcname = getFunctionName(tok);
return isNotLibraryFunction(tok) && functions.find(funcname) != functions.end() ? nullptr : getAllocDealloc(mDealloc, funcname); return isNotLibraryFunction(tok) && functions.find(funcname) != functions.end() ? nullptr : getAllocDealloc(mDealloc, funcname);
} }
/** get allocation id for function */ /** get reallocation info for function */
int Library::alloc(const Token *tok, int arg) const 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; return (af && af->arg == arg) ? af->groupId : 0;
} }
/** get deallocation id for function */ /** 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; return (af && af->arg == arg) ? af->groupId : 0;
} }

View File

@ -77,27 +77,34 @@ public:
BufferSize bufferSize; BufferSize bufferSize;
int bufferSizeArg1; int bufferSizeArg1;
int bufferSizeArg2; int bufferSizeArg2;
int reallocArg;
}; };
/** get allocation info for function */ /** get allocation info for function */
const AllocFunc* alloc(const Token *tok) const; const AllocFunc* getAllocFuncInfo(const Token *tok) const;
/** get deallocation info for function */ /** 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 */ /** 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 */ /** 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) */ /** 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); return getAllocDealloc(mAlloc, name);
} }
/** get deallocation info for function by name (deprecated, use other alloc) */ /** 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); return getAllocDealloc(mDealloc, name);
} }
@ -124,6 +131,12 @@ public:
mDealloc[functionname].arg = arg; 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 */ /** add noreturn function setting */
void setnoreturn(const std::string& funcname, bool noreturn) { void setnoreturn(const std::string& funcname, bool noreturn) {
mNoReturn[funcname] = noreturn; mNoReturn[funcname] = noreturn;
@ -523,6 +536,7 @@ private:
std::set<std::string> mFiles; std::set<std::string> mFiles;
std::map<std::string, AllocFunc> mAlloc; // allocation functions std::map<std::string, AllocFunc> mAlloc; // allocation functions
std::map<std::string, AllocFunc> mDealloc; // deallocation functions std::map<std::string, AllocFunc> mDealloc; // deallocation functions
std::map<std::string, AllocFunc> mRealloc; // reallocation functions
std::map<std::string, bool> mNoReturn; // is function noreturn? std::map<std::string, bool> mNoReturn; // is function noreturn?
std::map<std::string, std::string> mReturnValue; std::map<std::string, std::string> mReturnValue;
std::map<std::string, std::string> mReturnValueType; std::map<std::string, std::string> mReturnValueType;

View File

@ -5310,7 +5310,9 @@ static void valueFlowDynamicBufferSize(TokenList *tokenlist, SymbolDatabase *sym
if (!Token::Match(rhs->previous(), "%name% (")) if (!Token::Match(rhs->previous(), "%name% ("))
continue; 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) if (!allocFunc || allocFunc->bufferSize == Library::AllocFunc::BufferSize::none)
continue; continue;

View File

@ -1183,7 +1183,7 @@ Checking test.c...
functions do not affect the allocation at all.</para> functions do not affect the allocation at all.</para>
<section> <section>
<title>alloc and dealloc</title> <title>alloc, realloc and dealloc</title>
<para>Here is an example program:</para> <para>Here is an example program:</para>
@ -1217,6 +1217,17 @@ Checking pen1.c...
&lt;/resource&gt; &lt;/resource&gt;
&lt;/def&gt;</programlisting> &lt;/def&gt;</programlisting>
<para>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:
<programlisting>&lt;?xml version="1.0"?&gt;
&lt;def&gt;
&lt;resource&gt;
&lt;alloc&gt;fopen&lt;/alloc&gt;
&lt;realloc realloc-arg="3"&gt;freopen&lt;/realloc&gt;
&lt;dealloc&gt;fclose&lt;/dealloc&gt;
&lt;/resource&gt;
&lt;/def&gt;</programlisting>
<para>The allocation and deallocation functions are organized in <para>The allocation and deallocation functions are organized in
groups. Each group is defined in a <literal>&lt;resource&gt;</literal> groups. Each group is defined in a <literal>&lt;resource&gt;</literal>
or <literal>&lt;memory&gt;</literal> tag and is identified by its or <literal>&lt;memory&gt;</literal> tag and is identified by its

View File

@ -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. 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.
## `<alloc>` and `<dealloc>` ## `<alloc>`, `<realloc>` and `<dealloc>`
Here is an example program: Here is an example program:
@ -45,6 +45,17 @@ Here is a minimal windows.cfg file:
</resource> </resource>
</def> </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 `<dealloc>` functions. This means, groups with overlapping `<dealloc>` tags are merged. The allocation and deallocation functions are organized in groups. Each group is defined in a `<resource>` or `<memory>` tag and is identified by its `<dealloc>` functions. This means, groups with overlapping `<dealloc>` tags are merged.
## `<leak-ignore>` and `<use>` ## `<leak-ignore>` and `<use>`

View File

@ -1434,7 +1434,7 @@ void uninitvar_freopen(void)
FILE *stream; FILE *stream;
// cppcheck-suppress uninitvar // cppcheck-suppress uninitvar
FILE * p = freopen(filename,mode,stream); FILE * p = freopen(filename,mode,stream);
free(p); fclose(p);
} }
void uninitvar_frexp(void) void uninitvar_frexp(void)

View File

@ -36,9 +36,11 @@ private:
int id = 0; int id = 0;
while (!settings.library.ismemory(++id)); while (!settings.library.ismemory(++id));
settings.library.setalloc("malloc", id, -1); settings.library.setalloc("malloc", id, -1);
settings.library.setrealloc("realloc", id, -1);
settings.library.setdealloc("free", id, 1); settings.library.setdealloc("free", id, 1);
while (!settings.library.isresource(++id)); while (!settings.library.isresource(++id));
settings.library.setalloc("fopen", id, -1); settings.library.setalloc("fopen", id, -1);
settings.library.setrealloc("freopen", id, -1, 3);
settings.library.setdealloc("fclose", id, 1); settings.library.setdealloc("fclose", id, 1);
settings.library.smartPointers.insert("std::shared_ptr"); settings.library.smartPointers.insert("std::shared_ptr");
settings.library.smartPointers.insert("std::unique_ptr"); settings.library.smartPointers.insert("std::unique_ptr");
@ -63,6 +65,12 @@ private:
TEST_CASE(assign17); // #9047 TEST_CASE(assign17); // #9047
TEST_CASE(assign18); TEST_CASE(assign18);
TEST_CASE(realloc1);
TEST_CASE(realloc2);
TEST_CASE(realloc3);
TEST_CASE(freopen1);
TEST_CASE(freopen2);
TEST_CASE(deallocuse1); TEST_CASE(deallocuse1);
TEST_CASE(deallocuse2); TEST_CASE(deallocuse2);
TEST_CASE(deallocuse3); TEST_CASE(deallocuse3);
@ -350,6 +358,48 @@ private:
ASSERT_EQUALS("[test.c:3]: (error) Memory leak: p\n", errout.str()); 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() { void deallocuse1() {
check("void f(char *p) {\n" check("void f(char *p) {\n"
" free(p);\n" " free(p);\n"
@ -1327,6 +1377,29 @@ private:
" std::unique_ptr<int[]> x(i);\n" " std::unique_ptr<int[]> x(i);\n"
"}\n", true); "}\n", true);
ASSERT_EQUALS("[test.cpp:3]: (error) Mismatching allocation and deallocation: i\n", errout.str()); 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() { void smartPointerDeleter() {

View File

@ -622,11 +622,11 @@ private:
ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode); ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode);
ASSERT(library.functions.empty()); ASSERT(library.functions.empty());
ASSERT(Library::ismemory(library.alloc("CreateX"))); ASSERT(Library::ismemory(library.getAllocFuncInfo("CreateX")));
ASSERT_EQUALS(library.allocId("CreateX"), library.deallocId("DeleteX")); 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); ASSERT(af && af->arg == -1);
const Library::AllocFunc* df = library.dealloc("DeleteX"); const Library::AllocFunc* df = library.getDeallocFuncInfo("DeleteX");
ASSERT(df && df->arg == 1); ASSERT(df && df->arg == 1);
} }
void memory2() const { void memory2() const {
@ -665,9 +665,9 @@ private:
ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode); ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode);
ASSERT(library.functions.empty()); ASSERT(library.functions.empty());
const Library::AllocFunc* af = library.alloc("CreateX"); const Library::AllocFunc* af = library.getAllocFuncInfo("CreateX");
ASSERT(af && af->arg == 5); 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(df && df->arg == 2);
ASSERT(library.returnuninitdata.find("CreateX") != library.returnuninitdata.cend()); ASSERT(library.returnuninitdata.find("CreateX") != library.returnuninitdata.cend());

View File

@ -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 { class TestMemleakInClass : public TestFixture {
public: public:
TestMemleakInClass() : TestFixture("TestMemleakInClass") { TestMemleakInClass() : TestFixture("TestMemleakInClass") {

View File

@ -3887,6 +3887,20 @@ private:
" return x;\n" " return x;\n"
"}"; "}";
ASSERT_EQUALS(true, testValueOfX(code, 4U, 5, ValueFlow::Value::BUFFER_SIZE)); 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));
} }
}; };