diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index e5220e701a3..bf40904f2f4 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -267,6 +267,50 @@ 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) + * + * @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) +{ + // match one of the supported LHS patterns + // TODO: check if tok->previous()->isCast(). can't right now because + // + // auto x = [](void *ptr) { g(ptr) }; + // void *p = malloc(1); + // (x)(p); + // ^ + // the lpar surrounding x has isCast() == true, so checking isCast() would + // have false positive leaks, while allowing casts to take ownership of + // resources is instead a false negative + if (tok->previous()->str() == "(" && !tok->previous()->isBinaryOp() && + tok->linkAt(-1) && !tok->isStandardType()) { + tok = tok->linkAt(-1)->next(); + } else if (!tok->isStandardType() && tok->isName() && tok->linkAt(1)) { + tok = tok->linkAt(1)->next(); + } else { + return nullptr; + } + + // skip over potential template arguments + if (tok->link() && tok->str() == "<") + tok = tok->link()->next(); + + // return the opening parenthesis + if (tok && tok->link() && !tok->isCast() && tok->str() == "(") + return tok; + + return nullptr; +} + static const Token* getOutparamAllocation(const Token* tok, const Library& library) { if (!tok) @@ -787,11 +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; - } - // goto => weird execution path - else if (tok->str() == "goto") { + // top level call to an anonymous function + } else if (const Token *lpar = isAnonymousFunctionCall(tok)) { + functionCall(nullptr, lpar, varInfo, VarInfo::AllocInfo(0, VarInfo::NOALLOC), nullptr); + tok = lpar->link(); + + // goto => weird execution path + } else if (tok->str() == "goto") { varInfo.clear(); return false; } @@ -941,6 +996,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); @@ -951,7 +1007,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; @@ -1018,11 +1082,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(); @@ -1071,26 +1134,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 @@ -1269,7 +1336,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); } } diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp index cb5f9d53c8a..097a09ac508 100644 --- a/test/testleakautovar.cpp +++ b/test/testleakautovar.cpp @@ -118,6 +118,7 @@ class TestLeakAutoVar : public TestFixture { // handling function calls TEST_CASE(functioncall1); + TEST_CASE(anonymousFunctionCall1); // goto TEST_CASE(goto1); @@ -1910,6 +1911,54 @@ 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"); + 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()); + + // 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 configuration [checkLibraryNoReturn]\n" + "[test.c:4:1]: (information) --check-library: Function foo() should have / 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 / configuration [checkLibraryUseIgnore]\n", errout_str()); + } + void goto1() { check("static void f() {\n" " int err = -ENOMEM;\n"