From 271a5ea1104f551701d680eb00c0e61a72fdcedd Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Mon, 12 Oct 2026 16:30:47 +0200 Subject: [PATCH] Fix #15104 FP functionStatic and constParameterReference: call of overloaded member function template resolved to free function with the same name For `select(ds, cb)` with `DS& ds`, the overload `select(DS*, Callback)` was not removed from the candidates, so Scope::findFunction() couldn't choose between the member overloads. The lookup then continued in the enclosing scopes and picked a free function with the same name. An object of a class can't be passed for a pointer to this class, and a pointer can't be passed for a non-const reference to it, so these candidates are removed now. Co-Authored-By: Claude Opus 5.5 --- lib/symboldatabase.cpp | 44 +++++++++++- test/testclass.cpp | 16 +++++ test/testother.cpp | 12 ++++ test/testsymboldatabase.cpp | 132 ++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 1 deletion(-) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index c2ce2db812b..c1cd75f2e00 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -5935,6 +5936,39 @@ std::vector Scope::findAssociatedScopes() const //--------------------------------------------------------------------------- +// Might an object of the class be converted to a pointer by a conversion operator (also in an unknown base class)? +// operator C*(), operator ptr() +static bool mayConvertToPointer(const Scope* classScope) +{ + const std::vector scopes = classScope->findAssociatedScopes(); + return std::any_of(scopes.cbegin(), scopes.cend(), [](const Scope* scope) { + const bool hasConversionOperator = std::any_of(scope->functionList.cbegin(), scope->functionList.cend(), [](const Function& f) { + return f.name().size() > 8 && startsWith(f.name(), "operator") && + (std::isalpha(static_cast(f.name()[8])) || f.name()[8] == '_' || f.name()[8] == ':'); + }); + const bool hasUnknownBase = scope->definedType && std::any_of(scope->definedType->derivedFrom.cbegin(), scope->definedType->derivedFrom.cend(), [](const Type::BaseInfo& base) { + return !base.type || !base.type->classScope; + }); + return hasConversionOperator || hasUnknownBase; + }); +} + +// Returns true if the variable can't be passed for the function argument because only one of them is a pointer to +// the same class: struct C; void f(C* p); void g(C& r); C c; C* p; f(c); g(p); +static bool isPointerMismatch(const Variable* callarg, const Variable* funcarg) +{ + if (!callarg->type() || callarg->type() != funcarg->type() || callarg->isArray() || funcarg->isArray()) + return false; + // function pointer or reference: C (*f)() + if (Token::simpleMatch(callarg->nameToken()->next(), ") (") || (funcarg->nameToken() && Token::simpleMatch(funcarg->nameToken()->next(), ") ("))) + return false; + // assume that incomplete types have no conversion operator + if (funcarg->isPointer()) + return !callarg->isPointer() && (!callarg->type()->classScope || !mayConvertToPointer(callarg->type()->classScope)); + // a temporary from a converting constructor can bind to const C& and C&& + return callarg->isPointer() && funcarg->isReference() && !funcarg->isRValueReference() && !funcarg->isConst(); +} + static void checkVariableCallMatch(const Variable* callarg, const Variable* funcarg, size_t& same, size_t& fallback1, size_t& fallback2) { if (callarg) { @@ -6070,7 +6104,7 @@ const Function* Scope::findFunction(const Token* tok, bool requireConst, Referen addMatchingFunctions(nestedScope); } - const std::size_t numberOfMatchesNonBase = matches.size(); + std::size_t numberOfMatchesNonBase = matches.size(); // check in base classes findFunctionInBase(name, tok, args, matches); @@ -6184,6 +6218,14 @@ const Function* Scope::findFunction(const Token* tok, bool requireConst, Referen // check for a match with a variable if (Token::Match(arguments[j], "%var% ,|)")) { const Variable * callarg = arguments[j]->variable(); + if (callarg && isPointerMismatch(callarg, funcarg)) { + // can't match so remove this function from possible matches + matches.erase(matches.begin() + i); + if (i < numberOfMatchesNonBase) + --numberOfMatchesNonBase; + erased = true; + break; + } checkVariableCallMatch(callarg, funcarg, same, fallback1, fallback2); } diff --git a/test/testclass.cpp b/test/testclass.cpp index 96344e4b5f9..48fd513b494 100644 --- a/test/testclass.cpp +++ b/test/testclass.cpp @@ -200,6 +200,7 @@ class TestClass : public TestFixture { TEST_CASE(const101); TEST_CASE(const102); TEST_CASE(const103); + TEST_CASE(const104); // #15104 TEST_CASE(const_handleDefaultParameters); TEST_CASE(const_passThisToMemberOfOtherClass); @@ -7067,6 +7068,21 @@ class TestClass : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void const104() { // #15104 + checkConst("struct DS;\n" + "void select(int a, int b);\n" + "class Query {\n" + " int m = 0;\n" + "public:\n" + " template \n" + " int select(DS &ds, Callback cb) { cb(m); return 0; }\n" + " template \n" + " int select(DS *ds, Callback cb) { return select(*ds, cb); }\n" + " int select(DS &ds) { return select(ds, [](int) {}); }\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + void const_handleDefaultParameters() { checkConst("struct Foo {\n" " void foo1(int i, int j = 0) {\n" diff --git a/test/testother.cpp b/test/testother.cpp index d5d6036718e..7870c51ba43 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -4213,6 +4213,18 @@ class TestOther : public TestFixture { " }\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("struct DS;\n" // #15104 + "void select(int a, int b);\n" + "class Query {\n" + "public:\n" + " template \n" + " int select(DS &ds, Callback cb);\n" + " template \n" + " int select(DS *ds, Callback cb);\n" + " int select(DS &ds) { return select(ds, [](int) {}); }\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); } void constParameterCallback() { diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 93bc8861e3e..fb89e164a74 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -547,6 +547,7 @@ class TestSymbolDatabase : public TestFixture { TEST_CASE(findFunction63); // #14937 - member function of type returned by operator() TEST_CASE(findFunction64); // overloaded operator() TEST_CASE(findFunction65); + TEST_CASE(findFunction66); // #15104 - object passed for pointer to its class TEST_CASE(findFunctionRef1); TEST_CASE(findFunctionRef2); // #13328 TEST_CASE(findFunctionContainer); @@ -9005,6 +9006,137 @@ class TestSymbolDatabase : public TestFixture { } } + void findFunction66() { // #15104 - object passed for pointer to its class + { + GET_SYMBOL_DB("struct DS;\n" + "void select(int a, int b);\n" + "class Query {\n" + " int m = 0;\n" + "public:\n" + " template \n" + " int select(DS &ds, Callback cb) { cb(m); return 0; }\n" + " template \n" + " int select(DS *ds, Callback cb) { return select(*ds, cb); }\n" + " int select(DS &ds) { return select(ds, [](int) {}); }\n" + "};\n"); + const Token* call1 = Token::findsimplematch(tokenizer.tokens(), "select ( * ds , cb )"); + ASSERT(call1 && call1->function()); + ASSERT_EQUALS(7, call1->function()->tokenDef->linenr()); + const Token* call2 = Token::findsimplematch(tokenizer.tokens(), "select ( ds , ["); + ASSERT(call2 && call2->function()); + ASSERT_EQUALS(7, call2->function()->tokenDef->linenr()); + } + { + GET_SYMBOL_DB("struct DS;\n" + "void select(int a, int b);\n" + "struct Base {\n" + " int m = 0;\n" + " template \n" + " int select(DS &ds, Callback cb) { cb(m); return 0; }\n" + " template \n" + " int select(DS *ds, Callback cb) { return select(*ds, cb); }\n" + "};\n" + "struct Query : Base {\n" + " int run(DS &ds) { return select(ds, [](int) {}); }\n" + "};\n"); + const Token* call = Token::findsimplematch(tokenizer.tokens(), "select ( ds , ["); + ASSERT(call && call->function()); + ASSERT_EQUALS(6, call->function()->tokenDef->linenr()); + } + { + GET_SYMBOL_DB("struct DS;\n" + "void select(int a, int b);\n" + "namespace N {\n" + " template int select(DS &ds, C cb) { cb(1); return 0; }\n" + " template int select(DS *ds, C cb) { return select(*ds, cb); }\n" + " int f(DS &ds) { return select(ds, [](int) {}); }\n" + "}\n"); + const Token* call = Token::findsimplematch(tokenizer.tokens(), "select ( ds , ["); + ASSERT(call && call->function()); + ASSERT_EQUALS(4, call->function()->tokenDef->linenr()); + } + { // conversion operator + GET_SYMBOL_DB("struct C { operator C*(); };\n" + "void f(C* p);\n" + "void g(C c) { f(c); }\n"); + const Token* call = Token::findsimplematch(tokenizer.tokens(), "f ( c )"); + ASSERT(call && call->function()); + ASSERT_EQUALS(2, call->function()->tokenDef->linenr()); + } + { // converting constructor + GET_SYMBOL_DB("struct N { N(N* parent); };\n" + "void f(N&& n);\n" + "void g(const N& n);\n" + "void h(N* p) { f(p); g(p); }\n"); + const Token* call1 = Token::findsimplematch(tokenizer.tokens(), "f ( p )"); + ASSERT(call1 && call1->function()); + ASSERT_EQUALS(2, call1->function()->tokenDef->linenr()); + const Token* call2 = Token::findsimplematch(tokenizer.tokens(), "g ( p )"); + ASSERT(call2 && call2->function()); + ASSERT_EQUALS(3, call2->function()->tokenDef->linenr()); + } + { // function reference + GET_SYMBOL_DB("struct C { int x; };\n" + "C make();\n" + "void reg(C (*f)());\n" + "void g() { C (&fr)() = make; reg(fr); }\n"); + const Token* call = Token::findsimplematch(tokenizer.tokens(), "reg ( fr )"); + ASSERT(call && call->function()); + ASSERT_EQUALS(3, call->function()->tokenDef->linenr()); + } + { // conversion operator in base class or with type alias + GET_SYMBOL_DB("template struct PtrConv { operator D*(); };\n" + "struct C : PtrConv {};\n" + "struct F { using ptr = F*; operator ptr(); };\n" + "void f(C* p);\n" + "void k(F* p);\n" + "void g(C c, F e) { f(c); k(e); }\n"); + const Token* call1 = Token::findsimplematch(tokenizer.tokens(), "f ( c )"); + ASSERT(call1 && call1->function()); + ASSERT_EQUALS(4, call1->function()->tokenDef->linenr()); + const Token* call2 = Token::findsimplematch(tokenizer.tokens(), "k ( e )"); + ASSERT(call2 && call2->function()); + ASSERT_EQUALS(5, call2->function()->tokenDef->linenr()); + } + { // conversion operator in unknown base class or to qualified type + GET_SYMBOL_DB("struct C : Unknown { int x; };\n" + "struct D { operator ::D*(); };\n" + "void f(C* p);\n" + "void k(D* p);\n" + "void g(C c, D d) { f(c); k(d); }\n"); + const Token* call1 = Token::findsimplematch(tokenizer.tokens(), "f ( c )"); + ASSERT(call1 && call1->function()); + ASSERT_EQUALS(3, call1->function()->tokenDef->linenr()); + const Token* call2 = Token::findsimplematch(tokenizer.tokens(), "k ( d )"); + ASSERT(call2 && call2->function()); + ASSERT_EQUALS(4, call2->function()->tokenDef->linenr()); + } + { // pointer passed for non-const reference + GET_SYMBOL_DB("struct C { int x; };\n" + "void f(int a, int b);\n" + "namespace N {\n" + " template void f(C& r, T t);\n" + " template void f(C* p, T t);\n" + " void h(C* p) { f(p, 1); }\n" + "}\n"); + const Token* call = Token::findsimplematch(tokenizer.tokens(), "f ( p , 1 )"); + ASSERT(call && call->function()); + ASSERT_EQUALS(5, call->function()->tokenDef->linenr()); + } + { // removed candidate must not change name hiding of base class functions + GET_SYMBOL_DB("struct C { int x; };\n" + "struct Base { void f(const C& r, int i) const; };\n" + "struct Derived : Base {\n" + " void f(C* p, int i);\n" + " void f(C& r, double v);\n" + " void g(C& c) { f(c, 1); }\n" + "};\n"); + const Token* call = Token::findsimplematch(tokenizer.tokens(), "f ( c , 1 )"); + ASSERT(call && call->function()); + ASSERT_EQUALS(5, call->function()->tokenDef->linenr()); + } + } + void findFunctionRef1() { GET_SYMBOL_DB("struct X {\n" " const std::vector getInts() const & { return mInts; }\n"