From 6b8f345d10601c69ff8ed06f20220aa8c30c8c6d 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 c2ce2db812b..c67b4ce4714 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -2595,6 +2595,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()) @@ -7578,6 +7582,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 93bc8861e3e..8c97405915b 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1602,6 +1602,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 0fb00fbd5a641d2f038e6fc2ffc22c15343204f5 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 8c97405915b..224853cf5e7 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1627,6 +1627,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 da724f2e57007185f2db13d9252655017ada8eca 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 c67b4ce4714..06eda1d3b9f 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -2595,7 +2595,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 224853cf5e7..76690d32336 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1637,6 +1637,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 440310ecd9cd7a70003ce65ab56e1fb362476373 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 06eda1d3b9f..c4f108a854f 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -7582,8 +7582,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 76690d32336..2b997aba5b6 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1602,14 +1602,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"); @@ -1617,15 +1617,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" @@ -1634,8 +1632,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"); @@ -9910,6 +9906,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 7bd171197ff035053174266063b0ca89d728ff5c 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 c229cfed789..02d2fdc08dc 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1768,6 +1768,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 62824c16b36..f07f68ed951 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -4213,6 +4213,17 @@ class TestOther : public TestFixture { " }\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 2b997aba5b6..7152e0610ff 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1653,6 +1653,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() { @@ -2461,6 +2473,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 dbfcc30838708dfc6332b5ff3835fd8ce57e680d 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 7152e0610ff..c1bc011716f 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -1602,22 +1602,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 7e48369bcf19fc71236db2f779b7eff815d5b95f 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 02d2fdc08dc..53edbcb508d 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -1768,7 +1768,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 c4f108a854f..7c44fcd4746 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -916,7 +916,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 c1bc011716f..7447c687ff1 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -2475,6 +2475,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);