Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion lib/symboldatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstring>
#include <functional>
#include <initializer_list>
Expand Down Expand Up @@ -5935,6 +5936,39 @@ std::vector<const Scope*> 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<const Scope*> 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<unsigned char>(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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down
16 changes: 16 additions & 0 deletions test/testclass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 <typename Callback>\n"
" int select(DS &ds, Callback cb) { cb(m); return 0; }\n"
" template <typename Callback>\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"
Expand Down
12 changes: 12 additions & 0 deletions test/testother.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename Callback>\n"
" int select(DS &ds, Callback cb);\n"
" template <typename Callback>\n"
" int select(DS *ds, Callback cb);\n"
" int select(DS &ds) { return select(ds, [](int) {}); }\n"
"};\n");
ASSERT_EQUALS("", errout_str());
}

void constParameterCallback() {
Expand Down
132 changes: 132 additions & 0 deletions test/testsymboldatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 <typename Callback>\n"
" int select(DS &ds, Callback cb) { cb(m); return 0; }\n"
" template <typename Callback>\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 <typename Callback>\n"
" int select(DS &ds, Callback cb) { cb(m); return 0; }\n"
" template <typename Callback>\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 <typename C> int select(DS &ds, C cb) { cb(1); return 0; }\n"
" template <typename C> 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 <class D> struct PtrConv { operator D*(); };\n"
"struct C : PtrConv<C> {};\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 <class T> void f(C& r, T t);\n"
" template <class T> 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<int> getInts() const & { return mInts; }\n"
Expand Down
Loading