Skip to content
Open
114 changes: 91 additions & 23 deletions lib/checkleakautovar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,49 @@ static const Token * isFunctionCall(const Token * nameToken)
return nullptr;
}

/** checks if tok is part of the LHS of an anonymous function call:
* tok should be the first token in a unary expression that evaluates to a
* function pointer, or the name of a lambda or function
* (*func_ptr)(arg)
* or
* (lambda)(arg)
* or
* get_function()(arg)
* or
* arr[idx](arg)
* @param tok on the LHS of a function call
* @return opening parenthesis token or nullptr if not a function call
*/
static const Token *isAnonymousFunctionCall(const Token *tok)
Comment thread
aadanen marked this conversation as resolved.
Resolved
{
if (!tok || tok->isStandardType())
return nullptr;

auto isLparNotCast = [](const Token *lpar) -> bool {
return !lpar->isCast() && lpar->str() == "(";
};

// function pointer or lambda
if (isLparNotCast(tok->previous())) {
tok = tok->linkAt(-1)->next();
} else if (tok->isName() && (isLparNotCast(tok->next()) || tok->strAt(1) == "[")) {
// call to result of a function
tok = tok->linkAt(1)->next();
Comment thread
aadanen marked this conversation as resolved.
Resolved
Comment thread
aadanen marked this conversation as resolved.
Resolved
while (Token::simpleMatch(tok->previous(), "] ["))
tok = tok->link()->next();
} else {
return nullptr;
}

// < could be a less than, not a template operator
if (tok->link() && tok->str() == "<")
tok = tok->link()->next();

if (isLparNotCast(tok))
return tok;
return nullptr;
}

static const Token* getOutparamAllocation(const Token* tok, const Library& library)
{
if (!tok)
Expand Down Expand Up @@ -788,9 +831,22 @@ bool CheckLeakAutoVarImpl::checkScope(const Token * const startToken,
}
}

// a regular function call can return an anonymous function
openingPar = isAnonymousFunctionCall(ftok);
if (openingPar) {
functionCall(nullptr, openingPar, varInfo, VarInfo::AllocInfo(0, VarInfo::NOALLOC), nullptr);
tok = openingPar->link();
}

continue;

}
// top level call to an anonymous function
Comment thread
aadanen marked this conversation as resolved.
Resolved
else if (const Token *lpar = isAnonymousFunctionCall(tok)) {
functionCall(nullptr, lpar, varInfo, VarInfo::AllocInfo(0, VarInfo::NOALLOC), nullptr);
tok = lpar->link();
Comment thread
aadanen marked this conversation as resolved.
Resolved

}
// goto => weird execution path
else if (tok->str() == "goto") {
varInfo.clear();
Expand Down Expand Up @@ -942,6 +998,7 @@ const Token * CheckLeakAutoVarImpl::checkTokenInsideExpression(const Token * con

// check for function call
const Token * const openingPar = inFuncCall ? nullptr : isFunctionCall(tok);
const Token * const anonOpeningPar = isAnonymousFunctionCall(tok);
if (openingPar) {
const Library::AllocFunc* allocFunc = mSettings.library.getDeallocFuncInfo(tok);
VarInfo::AllocInfo alloc(allocFunc ? allocFunc->groupId : 0, VarInfo::DEALLOC, tok);
Expand All @@ -952,7 +1009,15 @@ const Token * CheckLeakAutoVarImpl::checkTokenInsideExpression(const Token * con
if (startsWith(returnValue, "arg"))
// the function returns one of its argument, we need to process a potential assignment
return openingPar;
return isCPPCast(tok->astParent()) ? openingPar : openingPar->link();

if (!anonOpeningPar)
return isCPPCast(tok->astParent()) ? openingPar : openingPar->link();
}

// check for anonymous function call
if (anonOpeningPar) {
functionCall(nullptr, anonOpeningPar, varInfo, VarInfo::AllocInfo(0, VarInfo::NOALLOC), nullptr);
return anonOpeningPar->link();
}

return nullptr;
Expand Down Expand Up @@ -1019,11 +1084,10 @@ void CheckLeakAutoVarImpl::changeAllocStatus(VarInfo &varInfo, const VarInfo::Al

void CheckLeakAutoVarImpl::functionCall(const Token *tokName, const Token *tokOpeningPar, VarInfo &varInfo, const VarInfo::AllocInfo& allocation, const Library::AllocFunc* af)
{
// Ignore function call?
const bool isLeakIgnore = mSettings.library.isLeakIgnore(mSettings.library.getFunctionName(tokName));
if (mSettings.library.getReallocFuncInfo(tokName))
const bool isLeakIgnore = tokName ? mSettings.library.isLeakIgnore(mSettings.library.getFunctionName(tokName)) : false;
if (tokName && mSettings.library.getReallocFuncInfo(tokName))
return;
if (tokName->next()->valueType() && tokName->next()->valueType()->container && tokName->next()->valueType()->container->stdStringLike)
if (tokName && tokName->next()->valueType() && tokName->next()->valueType()->container && tokName->next()->valueType()->container->stdStringLike)
return;

const Token * const tokFirstArg = tokOpeningPar->next();
Expand Down Expand Up @@ -1072,26 +1136,30 @@ void CheckLeakAutoVarImpl::functionCall(const Token *tokName, const Token *tokOp

// Is variable allocated?
if (!isnull && (!af || af->arg == argNr)) {
const Library::AllocFunc* deallocFunc = mSettings.library.getDeallocFuncInfo(tokName);
const Library::AllocFunc* deallocFunc = tokName ? mSettings.library.getDeallocFuncInfo(tokName) : nullptr;
VarInfo::AllocInfo dealloc(deallocFunc ? deallocFunc->groupId : 0, VarInfo::DEALLOC, tokName);
if (const Library::AllocFunc* allocFunc = mSettings.library.getAllocFuncInfo(tokName)) {
if (mSettings.library.getDeallocFuncInfo(tokName)) {
changeAllocStatus(varInfo, dealloc.type == 0 ? allocation : dealloc, tokName, arg);
}
if (allocFunc->arg == argNr &&
!(arg->variable() && arg->variable()->isArgument() && arg->valueType() && arg->valueType()->pointer > 1) &&
(isAddressOf || (arg->valueType() && arg->valueType()->pointer == 2))) {
leakIfAllocated(arg, varInfo);
VarInfo::AllocInfo& varAlloc = varInfo.alloctype[arg->varId()];
varAlloc.type = allocFunc->groupId;
varAlloc.status = VarInfo::ALLOC;
varAlloc.allocTok = arg;
if (tokName) {
if (const Library::AllocFunc* allocFunc = mSettings.library.getAllocFuncInfo(tokName)) {
if (mSettings.library.getDeallocFuncInfo(tokName)) {
changeAllocStatus(varInfo, dealloc.type == 0 ? allocation : dealloc, tokName, arg);
}
if (allocFunc->arg == argNr &&
!(arg->variable() && arg->variable()->isArgument() && arg->valueType() && arg->valueType()->pointer > 1) &&
(isAddressOf || (arg->valueType() && arg->valueType()->pointer == 2))) {
leakIfAllocated(arg, varInfo);
VarInfo::AllocInfo& varAlloc = varInfo.alloctype[arg->varId()];
varAlloc.type = allocFunc->groupId;
varAlloc.status = VarInfo::ALLOC;
varAlloc.allocTok = arg;
}
}
else if (isLeakIgnore)
checkTokenInsideExpression(arg, varInfo);
else
changeAllocStatus(varInfo, dealloc.type == 0 ? allocation : dealloc, tokName, arg);
} else {
changeAllocStatus(varInfo, allocation, nullptr, arg);
}
else if (isLeakIgnore)
checkTokenInsideExpression(arg, varInfo);
else
changeAllocStatus(varInfo, dealloc.type == 0 ? allocation : dealloc, tokName, arg);
}
}
// Check smart pointer
Expand Down Expand Up @@ -1270,7 +1338,7 @@ void CheckLeakAutoVarImpl::ret(const Token *tok, VarInfo &varInfo, const bool is
const auto use = possibleUsage.find(varid);
if (use == possibleUsage.end()) {
leakError(tok, var->name(), it->second.type);
} else if (!use->second.first->variable()) { // TODO: handle constructors
} else if (use->second.first && !use->second.first->variable()) { // TODO: handle constructors
configurationInfo(tok, use->second);
}
}
Expand Down
63 changes: 63 additions & 0 deletions test/testleakautovar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class TestLeakAutoVar : public TestFixture {

// handling function calls
TEST_CASE(functioncall1);
TEST_CASE(anonymousFunctionCall1);

// goto
TEST_CASE(goto1);
Expand Down Expand Up @@ -1911,6 +1912,68 @@ class TestLeakAutoVar : public TestFixture {
ASSERT_EQUALS("[test.cpp:4:1]: (error) Memory leak: b [memleak]\n", errout_str());
}

void anonymousFunctionCall1() { // #14990
// function pointer
check("void f(void (*fptr)(void *)) {\n"
"void *buf = malloc(1);\n"
"(*fptr)(buf);\n"
"}\n");
ASSERT_EQUALS("", errout_str());

// lambda
check("void f() {\n"
"auto x = [](void *ptr) { g(ptr) };\n"
"void *p = malloc(1);\n"
"(x)(p);\n"
"}\n", dinit(CheckOptions, $.cpp = true));
ASSERT_EQUALS("", errout_str());

// Function returning a function pointer
check("void f() {\n"
" void *buf = malloc(1);\n"
" get_function()(buf);\n"
"}\n");
ASSERT_EQUALS("", errout_str());

// array of function pointers
check("void f() {\n"
" void *buf = malloc(1);\n"
" arr[0](buf);\n"
"}\n");
ASSERT_EQUALS("", errout_str());

// multidim array of function pointers
check("void f() {\n"
" void *buf = malloc(1);\n"
" arr[0][1][2](buf);\n"
"}\n");
ASSERT_EQUALS("", errout_str());

// Function returning a function pointer, passed as an arg to a normal
// function
check("void f() {\n"
" void *buf = malloc(1);\n"
" foo(get_function()(buf));\n"
"}\n");
ASSERT_EQUALS("[test.c:3:29]: (information) --check-library: Function foo() should have <noreturn> configuration [checkLibraryNoReturn]\n"
"[test.c:4:1]: (information) --check-library: Function foo() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n",
errout_str());

// Function returning a function pointer, passed as an arg to another
// function returning a function pointer
check("void f() {\n"
" void *buf = malloc(1);\n"
" get_function()(get_function()(buf));\n"
"}\n");
ASSERT_EQUALS("", errout_str());

check("void f() {\n"
" void *buf = malloc(1);\n"
" get_function(buf)(get_function(NULL)(NULL));\n"
"}\n");
ASSERT_EQUALS("[test.c:4:1]: (information) --check-library: Function get_function() should have <use>/<leak-ignore> configuration [checkLibraryUseIgnore]\n", errout_str());
}

void goto1() {
check("static void f() {\n"
" int err = -ENOMEM;\n"
Expand Down
Loading