From 894382aac9d308605aa7a77e87a2d607dff31e8e Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Fri, 9 Oct 2026 16:20:10 +0200 Subject: [PATCH 1/7] Fix #15094 SymbolDatabase: Variable::isReference() is true for function pointer returning a reference For `int& (*f)()` the reference of the return type was taken as reference of the variable, in Variable::isReference() and in its ValueType. Co-Authored-By: Claude Opus 5.5 --- lib/symboldatabase.cpp | 6 ++++++ test/testsymboldatabase.cpp | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index c1cd75f2e00..5c1f34b4ff8 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -2596,6 +2596,10 @@ void Variable::evaluate(const Settings& settings) } else if (tok->str() == "&&") { // Before simplification, && isn't split up setFlag(fIsRValueRef, true); setFlag(fIsReference, true); // Set also fIsReference + } else if (tok->str() == "(") { + // a reference before the parentheses belongs to the return type of a function pointer: int& (*f)() + setFlag(fIsRValueRef, false); + setFlag(fIsReference, false); } if (tok->str() == "<" && tok->link()) @@ -7620,6 +7624,8 @@ static const Token* parsedecl(const Token* type, if (par) break; par = true; + // a reference before the parentheses belongs to the return type of the function pointer + valuetype->reference = Reference::None; } if (Token::simpleMatch(type, "decltype (") && type->next()->valueType()) { const ValueType *vt2 = type->next()->valueType(); diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index fb89e164a74..533b15d001f 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1603,6 +1603,39 @@ class TestSymbolDatabase : public TestFixture { ASSERT(p->valueType()->originalTypeName == "ubFunctionPointer_fp"); ASSERT(p->valueType()->reference == Reference::None); } + { // function pointer returning a reference + GET_SYMBOL_DB("void foo(int& (*f)()) {}\n"); + const Variable* const p = db->getVariableFromVarId(1); + ASSERT(!p->isReference()); + ASSERT(p->isPointer()); + ASSERT(p->valueType()); + ASSERT(p->valueType()->pointer == 1); + ASSERT(p->valueType()->reference == Reference::None); + } + { + GET_SYMBOL_DB("void foo(int&& (*f)()) {}\n"); + const Variable* const p = db->getVariableFromVarId(1); + ASSERT(!p->isReference()); + ASSERT(!p->isRValueReference()); + ASSERT(p->valueType()); + ASSERT(p->valueType()->reference == Reference::None); + } + { + GET_SYMBOL_DB("struct S { int& (*f[2])(); };\n"); + const Variable* const p = db->getVariableFromVarId(1); + ASSERT(!p->isReference()); + ASSERT(p->isArray()); + ASSERT(p->valueType()); + ASSERT(p->valueType()->reference == Reference::None); + } + { // reference to function pointer + GET_SYMBOL_DB("void foo(int (*&f)()) {}\n"); + const Variable* const p = db->getVariableFromVarId(1); + ASSERT(p->isReference()); + ASSERT(p->isPointer()); + ASSERT(p->valueType()); + ASSERT(p->valueType()->reference == Reference::LValue); + } } void VariableValueTypeTemplate() { From 6e50c419b015ee04a3a31fa14ed4c2d169bb1d99 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Fri, 9 Oct 2026 21:53:36 +0200 Subject: [PATCH 2/7] Add test for pointer to member function returning a reference Co-Authored-By: Claude Opus 5.5 --- test/testsymboldatabase.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 533b15d001f..65182642a84 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1628,6 +1628,16 @@ class TestSymbolDatabase : public TestFixture { ASSERT(p->valueType()); ASSERT(p->valueType()->reference == Reference::None); } + { // pointer to member function returning a reference + GET_SYMBOL_DB("struct S { int& g(); };\n" + "void foo(int& (S::*pm)()) {}\n"); + const Variable* const p = db->getVariableFromVarId(1); + ASSERT(p); + ASSERT_EQUALS("pm", p->name()); + ASSERT(!p->isReference()); + ASSERT(p->valueType()); + ASSERT(p->valueType()->reference == Reference::None); + } { // reference to function pointer GET_SYMBOL_DB("void foo(int (*&f)()) {}\n"); const Variable* const p = db->getVariableFromVarId(1); From 5402108ebfb05be9892ee4207fd35844223b3c70 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Fri, 9 Oct 2026 22:01:23 +0200 Subject: [PATCH 3/7] Only reset reference at function declarator parentheses `int& (r)` and `int& UNUSED(r)` lost their reference flag, which gave uninitvar false positives. Co-Authored-By: Claude Opus 5.5 --- lib/symboldatabase.cpp | 2 +- test/testsymboldatabase.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 5c1f34b4ff8..6a6b1341100 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -2596,7 +2596,7 @@ void Variable::evaluate(const Settings& settings) } else if (tok->str() == "&&") { // Before simplification, && isn't split up setFlag(fIsRValueRef, true); setFlag(fIsReference, true); // Set also fIsReference - } else if (tok->str() == "(") { + } else if (tok->str() == "(" && Token::simpleMatch(tok->link(), ") (")) { // a reference before the parentheses belongs to the return type of a function pointer: int& (*f)() setFlag(fIsRValueRef, false); setFlag(fIsReference, false); diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 65182642a84..9c4c59de609 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1638,6 +1638,18 @@ class TestSymbolDatabase : public TestFixture { ASSERT(p->valueType()); ASSERT(p->valueType()->reference == Reference::None); } + { // parentheses that are no function declarator + GET_SYMBOL_DB("void foo(int& (r)) {}\n"); + const Variable* const p = db->functionScopes.front()->function->getArgumentVar(0); + ASSERT(p); + ASSERT(p->isReference()); + } + { + GET_SYMBOL_DB("void foo(int& UNUSED(r)) {}\n"); + const Variable* const p = db->functionScopes.front()->function->getArgumentVar(0); + ASSERT(p); + ASSERT(p->isReference()); + } { // reference to function pointer GET_SYMBOL_DB("void foo(int (*&f)()) {}\n"); const Variable* const p = db->getVariableFromVarId(1); From f3f950f9a0ae82866356a75e77ca8cc42b3eb28c Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Mon, 12 Oct 2026 19:10:45 +0200 Subject: [PATCH 4/7] Keep the reference of the return type in the ValueType of a function pointer The type of a call through a function pointer is derived from the ValueType of the pointer, so resetting the reference caused false positives like returnTempReference for `int& f(int& (*fp)()) { return fp(); }`. Co-Authored-By: Claude Opus 5.5 --- lib/symboldatabase.cpp | 2 -- test/testautovariables.cpp | 18 ++++++++++++++++++ test/testsymboldatabase.cpp | 12 +++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 6a6b1341100..48164c550a8 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -7624,8 +7624,6 @@ static const Token* parsedecl(const Token* type, if (par) break; par = true; - // a reference before the parentheses belongs to the return type of the function pointer - valuetype->reference = Reference::None; } if (Token::simpleMatch(type, "decltype (") && type->next()->valueType()) { const ValueType *vt2 = type->next()->valueType(); diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index d1bc3bf830f..a2fa4168ea8 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -133,6 +133,7 @@ class TestAutoVariables : public TestFixture { TEST_CASE(returnReference27); TEST_CASE(returnReference28); TEST_CASE(returnReference29); + TEST_CASE(returnReference30); // #15094 TEST_CASE(returnReferenceFunction); TEST_CASE(returnReferenceContainer); TEST_CASE(returnReferenceLiteral); @@ -1772,6 +1773,23 @@ class TestAutoVariables : public TestFixture { errout_str()); } + void returnReference30() // #15094 + { + check("int& f1(int& (*fp)()) { return fp(); }\n" + "void f2(int& (*fp)()) { int& r = fp(); r = 2; }\n" + "struct D {\n" + " int& call() { return f(); }\n" + " int& (*f)();\n" + "};\n" + "const int& f3(const int& (*fp)(int)) { return fp(1); }\n" + "int& (*g)();\n" + "int& f4() { return g(); }\n" + "typedef int& (*FP)();\n" + "int& f5(FP fp) { return fp(); }\n" + "int* f6(int& (*fp)()) { return &fp(); }\n"); + ASSERT_EQUALS("", errout_str()); + } + void returnReferenceFunction() { check("int& f(int& a) {\n" " return a;\n" diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 9c4c59de609..993be545dd5 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1603,14 +1603,14 @@ class TestSymbolDatabase : public TestFixture { ASSERT(p->valueType()->originalTypeName == "ubFunctionPointer_fp"); ASSERT(p->valueType()->reference == Reference::None); } - { // function pointer returning a reference + { // function pointer returning a reference, the ValueType keeps the reference of the return type GET_SYMBOL_DB("void foo(int& (*f)()) {}\n"); const Variable* const p = db->getVariableFromVarId(1); ASSERT(!p->isReference()); ASSERT(p->isPointer()); ASSERT(p->valueType()); ASSERT(p->valueType()->pointer == 1); - ASSERT(p->valueType()->reference == Reference::None); + ASSERT(p->valueType()->reference == Reference::LValue); } { GET_SYMBOL_DB("void foo(int&& (*f)()) {}\n"); @@ -1618,15 +1618,13 @@ class TestSymbolDatabase : public TestFixture { ASSERT(!p->isReference()); ASSERT(!p->isRValueReference()); ASSERT(p->valueType()); - ASSERT(p->valueType()->reference == Reference::None); + ASSERT(p->valueType()->reference == Reference::RValue); } { GET_SYMBOL_DB("struct S { int& (*f[2])(); };\n"); const Variable* const p = db->getVariableFromVarId(1); ASSERT(!p->isReference()); ASSERT(p->isArray()); - ASSERT(p->valueType()); - ASSERT(p->valueType()->reference == Reference::None); } { // pointer to member function returning a reference GET_SYMBOL_DB("struct S { int& g(); };\n" @@ -1635,8 +1633,6 @@ class TestSymbolDatabase : public TestFixture { ASSERT(p); ASSERT_EQUALS("pm", p->name()); ASSERT(!p->isReference()); - ASSERT(p->valueType()); - ASSERT(p->valueType()->reference == Reference::None); } { // parentheses that are no function declarator GET_SYMBOL_DB("void foo(int& (r)) {}\n"); @@ -10042,6 +10038,8 @@ class TestSymbolDatabase : public TestFixture { ASSERT_EQUALS("signed long", typeOf("a = (long)32;\n", "(")); ASSERT_EQUALS("signed long", typeOf("a = (long int)32;\n", "(")); ASSERT_EQUALS("signed long long", typeOf("a = (long long)32;\n", "(")); + ASSERT_EQUALS("signed int &", typeOf("void f(int& (*fp)()) { fp(); }\n", "( ) ;")); // #15094 + ASSERT_EQUALS("signed int &&", typeOf("void f(int&& (*fp)()) { fp(); }\n", "( ) ;")); ASSERT_EQUALS("long double", typeOf("a = (long double)32;\n", "(")); ASSERT_EQUALS("char", typeOf("a = static_cast(32);\n", "(")); ASSERT_EQUALS("", typeOf("a = (unsigned x)0;\n", "(")); From 7718a639120b01c6013d8aa0987dc9f3c30e9d91 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Mon, 12 Oct 2026 19:34:47 +0200 Subject: [PATCH 5/7] Skip references to functions in checkConstVariable A reference to a function returning a reference was no longer skipped because isRValueReference() is now false for it. A function reference can't be const-qualified, so it is never reported. Co-Authored-By: Claude Opus 5.5 --- lib/checkother.cpp | 2 ++ test/testother.cpp | 11 +++++++++++ test/testsymboldatabase.cpp | 17 +++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 4476814fcbb..4bbf2333c82 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1783,6 +1783,8 @@ void CheckOtherImpl::checkConstVariable() continue; if (var->isConst()) continue; + if (Token::Match(var->nameToken(), "%name% ) (")) // reference to function + continue; const Scope* scope = var->scope(); if (!scope) continue; diff --git a/test/testother.cpp b/test/testother.cpp index f1d06182184..a16c20be51d 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -4225,6 +4225,17 @@ class TestOther : public TestFixture { " int select(DS &ds) { return select(ds, [](int) {}); }\n" "};\n"); ASSERT_EQUALS("", errout_str()); + + check("int& g();\n" // reference to function + "int k();\n" + "int f1() { int& (&r)() = g; return r(); }\n" + "int f2() { int (&r)() = k; return r(); }\n" + "int& f3(int& (&f)()) { return f(); }\n" + "int f4(int&& (&f)()) { return f(); }\n" + "int f5(int (&f)()) { return f(); }\n" + "using FR = int&(&)();\n" + "void f6(FR f) { f(); }\n"); + ASSERT_EQUALS("", errout_str()); } void constParameterCallback() { diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 993be545dd5..f0a41414c6e 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1654,6 +1654,18 @@ class TestSymbolDatabase : public TestFixture { ASSERT(p->valueType()); ASSERT(p->valueType()->reference == Reference::LValue); } + { // reference to function returning a reference + GET_SYMBOL_DB("void foo(int& (&f)()) {}\n"); + const Variable* const p = db->getVariableFromVarId(1); + ASSERT(p->isReference()); + ASSERT(!p->isRValueReference()); + } + { + GET_SYMBOL_DB("void foo(int&& (&f)()) {}\n"); + const Variable* const p = db->getVariableFromVarId(1); + ASSERT(p->isReference()); + ASSERT(!p->isRValueReference()); + } } void VariableValueTypeTemplate() { @@ -2462,6 +2474,11 @@ class TestSymbolDatabase : public TestFixture { ASSERT(db && ctor && ctor->type == FunctionType::eMoveConstructor); ASSERT(ctor && ctor->retDef == nullptr); } + { + GET_SYMBOL_DB("class Foo { Foo(Foo&& (*f)()); };\n"); + const Function* ctor = tokenizer.tokens()->tokAt(3)->function(); + ASSERT(db && ctor && ctor->type == FunctionType::eConstructor); + } { GET_SYMBOL_DB("void f() { extern void f(); }\n"); ASSERT(db && db->scopeList.size() == 2); From 8a8f0f9fd69985795d0bd9e16dfe7cc7df69e85c Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Mon, 12 Oct 2026 19:39:18 +0200 Subject: [PATCH 6/7] Don't assert the reference of the function pointer ValueType Co-Authored-By: Claude Opus 5.5 --- test/testsymboldatabase.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index f0a41414c6e..b2b3cc6394c 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1603,22 +1603,19 @@ class TestSymbolDatabase : public TestFixture { ASSERT(p->valueType()->originalTypeName == "ubFunctionPointer_fp"); ASSERT(p->valueType()->reference == Reference::None); } - { // function pointer returning a reference, the ValueType keeps the reference of the return type + { // function pointer returning a reference GET_SYMBOL_DB("void foo(int& (*f)()) {}\n"); const Variable* const p = db->getVariableFromVarId(1); ASSERT(!p->isReference()); ASSERT(p->isPointer()); ASSERT(p->valueType()); ASSERT(p->valueType()->pointer == 1); - ASSERT(p->valueType()->reference == Reference::LValue); } { GET_SYMBOL_DB("void foo(int&& (*f)()) {}\n"); const Variable* const p = db->getVariableFromVarId(1); ASSERT(!p->isReference()); ASSERT(!p->isRValueReference()); - ASSERT(p->valueType()); - ASSERT(p->valueType()->reference == Reference::RValue); } { GET_SYMBOL_DB("struct S { int& (*f[2])(); };\n"); From f64500ebe36fb5c0dcbe757b34b6a3532749642d Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Mon, 19 Oct 2026 17:25:31 +0200 Subject: [PATCH 7/7] Don't treat a reference to a function as copy or move constructor parameter Co-Authored-By: Claude Opus 5.5 --- lib/checkother.cpp | 2 +- lib/symboldatabase.cpp | 3 ++- test/testsymboldatabase.cpp | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 4bbf2333c82..a67475f45ee 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1783,7 +1783,7 @@ void CheckOtherImpl::checkConstVariable() continue; if (var->isConst()) continue; - if (Token::Match(var->nameToken(), "%name% ) (")) // reference to function + if (Tokenizer::isFunctionPointer(var->nameToken())) // reference to function continue; const Scope* scope = var->scope(); if (!scope) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 48164c550a8..540ec5ca5da 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -917,7 +917,8 @@ void SymbolDatabase::createSymbolDatabaseCopyAndMoveConstructors() continue; const Variable* firstArg = func->getArgumentVar(0); - if (firstArg->type() == scope.definedType) { + // skip references to functions: Foo(Foo& (&f)()) + if (firstArg->type() == scope.definedType && !Token::Match(firstArg->typeEndToken(), "&|&& %name%| ) (")) { if (firstArg->isRValueReference()) func->type = FunctionType::eMoveConstructor; else if (firstArg->isReference() && !firstArg->isPointer()) diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index b2b3cc6394c..85ee8430a77 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -2476,6 +2476,26 @@ class TestSymbolDatabase : public TestFixture { const Function* ctor = tokenizer.tokens()->tokAt(3)->function(); ASSERT(db && ctor && ctor->type == FunctionType::eConstructor); } + { + GET_SYMBOL_DB("class Foo { Foo(Foo& (&f)()); };\n"); + const Function* ctor = tokenizer.tokens()->tokAt(3)->function(); + ASSERT(db && ctor && ctor->type == FunctionType::eConstructor); + } + { + GET_SYMBOL_DB("class Foo { Foo(Foo&& (&f)()); };\n"); + const Function* ctor = tokenizer.tokens()->tokAt(3)->function(); + ASSERT(db && ctor && ctor->type == FunctionType::eConstructor); + } + { + GET_SYMBOL_DB("class Foo { Foo(Foo (&f)()); };\n"); + const Function* ctor = tokenizer.tokens()->tokAt(3)->function(); + ASSERT(db && ctor && ctor->type == FunctionType::eConstructor); + } + { + GET_SYMBOL_DB("class Foo { Foo(Foo& (&)()); };\n"); + const Function* ctor = tokenizer.tokens()->tokAt(3)->function(); + ASSERT(db && ctor && ctor->type == FunctionType::eConstructor); + } { GET_SYMBOL_DB("void f() { extern void f(); }\n"); ASSERT(db && db->scopeList.size() == 2);