* 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:
parent
e0ced1c415
commit
839fcddd8a
|
@ -37,6 +37,21 @@
|
|||
</optional>
|
||||
<ref name="DATA-NAME"/>
|
||||
</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>
|
||||
</choice>
|
||||
</zeroOrMore>
|
||||
|
@ -62,6 +77,18 @@
|
|||
</optional>
|
||||
<ref name="DATA-NAME"/>
|
||||
</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>
|
||||
</choice>
|
||||
</zeroOrMore>
|
||||
|
|
|
@ -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="true" buffer-size="calloc">calloc</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>
|
||||
</memory>
|
||||
<resource>
|
||||
<alloc init="true">fopen</alloc>
|
||||
<alloc init="true">tmpfile</alloc>
|
||||
<realloc init="true" realloc-arg="3">freopen</realloc>
|
||||
<dealloc>fclose</dealloc>
|
||||
</resource>
|
||||
<container id="stdContainer" endPattern="> !!::" opLessAllowed="false" itEndPattern="> :: iterator|const_iterator|reverse_iterator|const_reverse_iterator">
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<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)
|
||||
{
|
||||
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?
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<unsigned int, VarInfo::AllocInfo> &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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<std::string> mFiles;
|
||||
std::map<std::string, AllocFunc> mAlloc; // allocation 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, std::string> mReturnValue;
|
||||
std::map<std::string, std::string> mReturnValueType;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1183,7 +1183,7 @@ Checking test.c...
|
|||
functions do not affect the allocation at all.</para>
|
||||
|
||||
<section>
|
||||
<title>alloc and dealloc</title>
|
||||
<title>alloc, realloc and dealloc</title>
|
||||
|
||||
<para>Here is an example program:</para>
|
||||
|
||||
|
@ -1217,6 +1217,17 @@ Checking pen1.c...
|
|||
</resource>
|
||||
</def></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><?xml version="1.0"?>
|
||||
<def>
|
||||
<resource>
|
||||
<alloc>fopen</alloc>
|
||||
<realloc realloc-arg="3">freopen</realloc>
|
||||
<dealloc>fclose</dealloc>
|
||||
</resource>
|
||||
</def></programlisting>
|
||||
|
||||
<para>The allocation and deallocation functions are organized in
|
||||
groups. Each group is defined in a <literal><resource></literal>
|
||||
or <literal><memory></literal> tag and is identified by its
|
||||
|
|
|
@ -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.
|
||||
|
||||
## `<alloc>` and `<dealloc>`
|
||||
## `<alloc>`, `<realloc>` and `<dealloc>`
|
||||
|
||||
Here is an example program:
|
||||
|
||||
|
@ -45,6 +45,17 @@ Here is a minimal windows.cfg file:
|
|||
</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 `<dealloc>` functions. This means, groups with overlapping `<dealloc>` tags are merged.
|
||||
|
||||
## `<leak-ignore>` and `<use>`
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<int[]> 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() {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue