From b6f4da1ba3fd01ed39e2463a5405075d4bc94e45 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 24 Jul 2026 11:16:43 -0500 Subject: [PATCH 1/3] Fix 14937: False positive: inconclusive nullPointerRedundantCheck with thunk --- lib/symboldatabase.cpp | 24 +++++++---- lib/symboldatabase.h | 6 ++- test/testnullpointer.cpp | 17 ++++++++ test/testsymboldatabase.cpp | 84 +++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 10 deletions(-) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index d98c66770e1..42d700496ba 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -5861,7 +5861,7 @@ bool Scope::hasInlineOrLambdaFunction(const Token** tokStart, bool onlyInline) c }); } -void Scope::findFunctionInBase(const Token* tok, size_t args, std::vector & matches) const +void Scope::findFunctionInBase(const std::string& name, const Token* tok, size_t args, std::vector & matches) const { if (isClassOrStruct() && definedType && !definedType->derivedFrom.empty()) { const std::vector &derivedFrom = definedType->derivedFrom; @@ -5871,7 +5871,7 @@ void Scope::findFunctionInBase(const Token* tok, size_t args, std::vectorclassScope == this) // Ticket #5120, #5125: Recursive class; tok should have been found already continue; - auto range = base->classScope->functionMap.equal_range(tok->str()); + auto range = base->classScope->functionMap.equal_range(name); for (auto it = range.first; it != range.second; ++it) { const Function *func = it->second; if (func->isDestructor() && !Token::simpleMatch(tok->tokAt(-1), "~")) @@ -5882,7 +5882,7 @@ void Scope::findFunctionInBase(const Token* tok, size_t args, std::vectorclassScope->findFunctionInBase(tok, args, matches); + base->classScope->findFunctionInBase(name, tok, args, matches); } } } @@ -6021,8 +6021,10 @@ static bool hasMatchingConstructor(const Scope* classScope, const ValueType* arg }); } -const Function* Scope::findFunction(const Token *tok, bool requireConst, Reference ref) const +const Function* Scope::findFunction(const Token *tok, bool requireConst, Reference ref, const std::string &funcName) const { + const std::string &name = funcName.empty() ? tok->str() : funcName; + const bool isCall = Token::Match(tok->next(), "(|{"); const std::vector arguments = getArguments(tok); @@ -6033,7 +6035,7 @@ const Function* Scope::findFunction(const Token *tok, bool requireConst, Referen const std::size_t args = arguments.size(); auto addMatchingFunctions = [&](const Scope *scope) { - auto range = scope->functionMap.equal_range(tok->str()); + auto range = scope->functionMap.equal_range(name); for (auto it = range.first; it != range.second; ++it) { const Function *func = it->second; if (ref == Reference::LValue && func->hasRvalRefQualifier()) @@ -6068,7 +6070,7 @@ const Function* Scope::findFunction(const Token *tok, bool requireConst, Referen const std::size_t numberOfMatchesNonBase = matches.size(); // check in base classes - findFunctionInBase(tok, args, matches); + findFunctionInBase(name, tok, args, matches); // Non-call => Do not match parameters if (!isCall) { @@ -6298,8 +6300,8 @@ const Function* Scope::findFunction(const Token *tok, bool requireConst, Referen matches.erase(itPure); // Only one candidate left - if (matches.size() == 1 && std::none_of(functionList.begin(), functionList.end(), [tok](const Function& f) { - return startsWith(f.name(), tok->str() + " <"); + if (matches.size() == 1 && std::none_of(functionList.begin(), functionList.end(), [&name](const Function& f) { + return startsWith(f.name(), name + " <"); })) return matches[0]; @@ -7792,6 +7794,12 @@ static const Function* getFunction(const Token* tok) { lambda = lvar->nameToken()->tokAt(2)->function(); if (lambda && lambda->retDef) return lambda; + // calling an object of a class that overloads operator() + if (tok != lvar->nameToken() && !lvar->isPointer() && !lvar->isArray() && lvar->typeScope()) { + const Function* callOp = lvar->typeScope()->findFunction(tok, lvar->isConst(), Reference::LValue, "operator()"); + if (callOp && callOp->retDef) + return callOp; + } } return nullptr; } diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index 7d52d0bfb32..06c90b55d16 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -1144,9 +1144,11 @@ class CPPCHECKLIB Scope { * @brief find a function * @param tok token of function call * @param requireConst if const refers to a const variable only const methods should be matched + * @param ref reference qualification of the object the function is called on + * @param funcName name to look up instead of tok->str(), e.g. "operator()" when tok is a variable that is called * @return pointer to function if found or NULL if not found */ - const Function *findFunction(const Token *tok, bool requireConst=false, Reference ref=Reference::None) const; + const Function *findFunction(const Token *tok, bool requireConst=false, Reference ref=Reference::None, const std::string &funcName = "") const; const Scope *findRecordInNestedList(const std::string & name, bool isC = false) const; Scope *findRecordInNestedList(const std::string & name, bool isC = false); @@ -1210,7 +1212,7 @@ class CPPCHECKLIB Scope { */ bool isVariableDeclaration(const Token* tok, const Token*& vartok, const Token*& typetok) const; - void findFunctionInBase(const Token* tok, size_t args, std::vector & matches) const; + void findFunctionInBase(const std::string& name, const Token* tok, size_t args, std::vector & matches) const; /** @brief initialize varlist */ void getVariableList(const Token *start, const Token *end); diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index 698946b8efa..a2e59ac444d 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -147,6 +147,7 @@ class TestNullPointer : public TestFixture { TEST_CASE(nullpointer107); // #13682 (FP/FN cases around guards that depend on the pointer indirectly) TEST_CASE(nullpointer108); TEST_CASE(nullpointer109); + TEST_CASE(nullpointer110); // #14937 TEST_CASE(nullpointer_addressOf); // address of TEST_CASE(nullpointerSwitch); // #2626 TEST_CASE(nullpointer_cast); // #4692 @@ -3128,6 +3129,22 @@ class TestNullPointer : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void nullpointer110() { // #14937 - noreturn member function called on operator() result + check("struct A {\n" + " [[noreturn]] void g(int);\n" + "};\n" + "template\n" + "struct Thunk {\n" + " T& operator()() const;\n" + "};\n" + "void f(Thunk thunk, int* p) {\n" + " if (!p)\n" + " thunk().g(0);\n" + " *p = 1;\n" + "}", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("", errout_str()); + } + void nullpointer_addressOf() { // address of check("void f() {\n" " struct X *x = 0;\n" diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 69d345a1cc5..683aeff6769 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -542,6 +542,8 @@ class TestSymbolDatabase : public TestFixture { TEST_CASE(findFunction60); TEST_CASE(findFunction61); TEST_CASE(findFunction62); // #14272 - pointer passed to function is const + TEST_CASE(findFunction63); // #14937 - member function of type returned by operator() + TEST_CASE(findFunction64); // overloaded operator() TEST_CASE(findFunctionRef1); TEST_CASE(findFunctionRef2); // #13328 TEST_CASE(findFunctionContainer); @@ -8875,6 +8877,88 @@ class TestSymbolDatabase : public TestFixture { ASSERT_EQUALS(2, functionCall->function()->token->linenr()); } + void findFunction63() { // #14937 + GET_SYMBOL_DB("struct A {\n" + " void g(int);\n" + "};\n" + "template\n" + "struct Thunk {\n" + " T& operator()() const;\n" + "};\n" + "void f(Thunk thunk) {\n" + " thunk().g(0);\n" + "}\n"); + const Token* g = Token::findsimplematch(tokenizer.tokens(), "g ( 0 )"); + ASSERT(g); + ASSERT(g->function()); + ASSERT(g->function()->tokenDef); + ASSERT_EQUALS(2, g->function()->tokenDef->linenr()); + const Token* call = Token::findsimplematch(tokenizer.tokens(), "( ) . g"); + ASSERT(call && call->valueType()); + ASSERT(call->valueType()->typeScope && call->valueType()->typeScope->className == "A"); + ASSERT_EQUALS(static_cast(Reference::LValue), static_cast(call->valueType()->reference)); + } + + void findFunction64() { // overloaded operator() + { + GET_SYMBOL_DB("struct A { void g(int); };\n" // overloads distinguished by argument count + "struct B { void h(int); };\n" + "template\n" + "struct C {\n" + " A& operator()();\n" + " B& operator()(int);\n" + "};\n" + "void f(C c) {\n" + " c().g(1);\n" + " c(1).h(1);\n" + "}\n"); + const Token* g = Token::findsimplematch(tokenizer.tokens(), "g ( 1 )"); + ASSERT(g && g->function()); + ASSERT_EQUALS(1, g->function()->tokenDef->linenr()); + const Token* h = Token::findsimplematch(tokenizer.tokens(), "h ( 1 )"); + ASSERT(h && h->function()); + ASSERT_EQUALS(2, h->function()->tokenDef->linenr()); + } + { + GET_SYMBOL_DB("struct A { void g(int); };\n" // overloads distinguished by constness of the object + "struct B { void h(int); };\n" + "template\n" + "struct C {\n" + " A& operator()();\n" + " B& operator()() const;\n" + "};\n" + "void f(C c, const C& k) {\n" + " c().g(1);\n" + " k().h(1);\n" + "}\n"); + const Token* g = Token::findsimplematch(tokenizer.tokens(), "g ( 1 )"); + ASSERT(g && g->function()); + ASSERT_EQUALS(1, g->function()->tokenDef->linenr()); + const Token* h = Token::findsimplematch(tokenizer.tokens(), "h ( 1 )"); + ASSERT(h && h->function()); + ASSERT_EQUALS(2, h->function()->tokenDef->linenr()); + } + { + GET_SYMBOL_DB("struct A { void g(int); };\n" // overloads distinguished by argument type + "struct B { void h(int); };\n" + "template\n" + "struct C {\n" + " A& operator()(int);\n" + " B& operator()(double);\n" + "};\n" + "void f(C c, int i, double d) {\n" + " c(i).g(1);\n" + " c(d).h(1);\n" + "}\n"); + const Token* g = Token::findsimplematch(tokenizer.tokens(), "g ( 1 )"); + ASSERT(g && g->function()); + ASSERT_EQUALS(1, g->function()->tokenDef->linenr()); + const Token* h = Token::findsimplematch(tokenizer.tokens(), "h ( 1 )"); + ASSERT(h && h->function()); + ASSERT_EQUALS(2, h->function()->tokenDef->linenr()); + } + } + void findFunctionRef1() { GET_SYMBOL_DB("struct X {\n" " const std::vector getInts() const & { return mInts; }\n" From d6663e4a4a9282ad773ab15d0f31bb5fc5794379 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 24 Jul 2026 11:18:29 -0500 Subject: [PATCH 2/3] Format --- lib/symboldatabase.cpp | 14 +++++++++----- lib/symboldatabase.h | 10 ++++++++-- test/testnullpointer.cpp | 6 ++++-- test/testsymboldatabase.cpp | 6 ++++-- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 42d700496ba..e6f676e7a20 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -5861,7 +5861,10 @@ bool Scope::hasInlineOrLambdaFunction(const Token** tokStart, bool onlyInline) c }); } -void Scope::findFunctionInBase(const std::string& name, const Token* tok, size_t args, std::vector & matches) const +void Scope::findFunctionInBase(const std::string& name, + const Token* tok, + size_t args, + std::vector& matches) const { if (isClassOrStruct() && definedType && !definedType->derivedFrom.empty()) { const std::vector &derivedFrom = definedType->derivedFrom; @@ -6021,9 +6024,9 @@ static bool hasMatchingConstructor(const Scope* classScope, const ValueType* arg }); } -const Function* Scope::findFunction(const Token *tok, bool requireConst, Reference ref, const std::string &funcName) const +const Function* Scope::findFunction(const Token* tok, bool requireConst, Reference ref, const std::string& funcName) const { - const std::string &name = funcName.empty() ? tok->str() : funcName; + const std::string& name = funcName.empty() ? tok->str() : funcName; const bool isCall = Token::Match(tok->next(), "(|{"); @@ -6034,7 +6037,7 @@ const Function* Scope::findFunction(const Token *tok, bool requireConst, Referen // find all the possible functions that could match const std::size_t args = arguments.size(); - auto addMatchingFunctions = [&](const Scope *scope) { + auto addMatchingFunctions = [&](const Scope* scope) { auto range = scope->functionMap.equal_range(name); for (auto it = range.first; it != range.second; ++it) { const Function *func = it->second; @@ -7796,7 +7799,8 @@ static const Function* getFunction(const Token* tok) { return lambda; // calling an object of a class that overloads operator() if (tok != lvar->nameToken() && !lvar->isPointer() && !lvar->isArray() && lvar->typeScope()) { - const Function* callOp = lvar->typeScope()->findFunction(tok, lvar->isConst(), Reference::LValue, "operator()"); + const Function* callOp = + lvar->typeScope()->findFunction(tok, lvar->isConst(), Reference::LValue, "operator()"); if (callOp && callOp->retDef) return callOp; } diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index 06c90b55d16..86ce9419999 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -1148,7 +1148,10 @@ class CPPCHECKLIB Scope { * @param funcName name to look up instead of tok->str(), e.g. "operator()" when tok is a variable that is called * @return pointer to function if found or NULL if not found */ - const Function *findFunction(const Token *tok, bool requireConst=false, Reference ref=Reference::None, const std::string &funcName = "") const; + const Function* findFunction(const Token* tok, + bool requireConst = false, + Reference ref = Reference::None, + const std::string& funcName = "") const; const Scope *findRecordInNestedList(const std::string & name, bool isC = false) const; Scope *findRecordInNestedList(const std::string & name, bool isC = false); @@ -1212,7 +1215,10 @@ class CPPCHECKLIB Scope { */ bool isVariableDeclaration(const Token* tok, const Token*& vartok, const Token*& typetok) const; - void findFunctionInBase(const std::string& name, const Token* tok, size_t args, std::vector & matches) const; + void findFunctionInBase(const std::string& name, + const Token* tok, + size_t args, + std::vector& matches) const; /** @brief initialize varlist */ void getVariableList(const Token *start, const Token *end); diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index a2e59ac444d..c1bf766b8bb 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -3129,7 +3129,8 @@ class TestNullPointer : public TestFixture { ASSERT_EQUALS("", errout_str()); } - void nullpointer110() { // #14937 - noreturn member function called on operator() result + void nullpointer110() + { // #14937 - noreturn member function called on operator() result check("struct A {\n" " [[noreturn]] void g(int);\n" "};\n" @@ -3141,7 +3142,8 @@ class TestNullPointer : public TestFixture { " if (!p)\n" " thunk().g(0);\n" " *p = 1;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}", + dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 683aeff6769..1ce349cf969 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -8877,7 +8877,8 @@ class TestSymbolDatabase : public TestFixture { ASSERT_EQUALS(2, functionCall->function()->token->linenr()); } - void findFunction63() { // #14937 + void findFunction63() + { // #14937 GET_SYMBOL_DB("struct A {\n" " void g(int);\n" "};\n" @@ -8899,7 +8900,8 @@ class TestSymbolDatabase : public TestFixture { ASSERT_EQUALS(static_cast(Reference::LValue), static_cast(call->valueType()->reference)); } - void findFunction64() { // overloaded operator() + void findFunction64() + { // overloaded operator() { GET_SYMBOL_DB("struct A { void g(int); };\n" // overloads distinguished by argument count "struct B { void h(int); };\n" From ab01b8530084d5383f50c756db0ac9b501bdb0ce Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 24 Jul 2026 13:53:14 -0500 Subject: [PATCH 3/3] Rebuild