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
6 changes: 6 additions & 0 deletions lib/symboldatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() == "(" && 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);
}

if (tok->str() == "<" && tok->link())
Expand Down Expand Up @@ -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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reset is only reached for ( *. For a pointer to member function returning a reference, e.g. int& (S::*pm)();, the loop breaks at if (!Token::simpleMatch(type, "( *")) break; first, so valuetype->reference probably stays LValue. Meanwhile Variable::evaluate() resets on any (, so isReference() would be false. That makes Variable and ValueType disagree. Consider resetting the reference before the ( * check (whenever type is ( and we're stopping on a declarator), or handling ( %name% :: * as well. A test for int& (S::*pm)() would cover this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked: int& (S::*pm)() is already covered. The tokenizer simplifies the declarator to int & ( * pm ) ( ) before the symbol database is created, so the ( * reset applies. I added a test for it in 0fb00fb (fails on main, passes here). Details and why I'd keep the forms that are not simplified for a separate PR: #8931 (comment)

(Written by Claude Code on behalf of @autoantwort.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reset only runs for ( *. For a pointer to member function like int& (S::*pm)() the loop hits ( S and breaks at the if (!Token::simpleMatch(type, "( *")) break; above, before this point. Reference::LValue from the & is still set at that point, and the function returns a non-null type, so as far as I can tell pm's ValueType keeps reference == LValue. The new test ASSERT(p->valueType()->reference == Reference::None) for pm would then fail, or it passes only because the ValueType comes from somewhere I haven't found. Could you check this? One option is to clear the reference before the break whenever type->link() is followed by (, so that int& (S::*pm)() is covered too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pm test does pass (CI is green, and the full testrunner passes locally). The ValueType comes from the normal path: the tokenizer already simplifies the declarator before the symbol database is created, --debug-normal shows

void foo ( int & ( * pm@var1 ) ( ) ) { }

so parsedecl() sees ( * and the reset applies. On main the same test fails (reference == LValue).

Clearing the reference before the break would only matter for declarators the tokenizer does not simplify, like int& (S::*&h)() or int& (MACRO *w)(). For those parsedecl() returns no ValueType at all (on main and with this PR), so nothing can disagree today. Related gaps that exist on main as well: for references to functions the ValueType takes the reference of the return type (int&& (&k)() gives RValue), and the pointer flag of the return type (int* (&l)()). Fixing these needs more changes in parsedecl(), so I'd prefer to handle them in a separate PR and keep this one small.

(Written by Claude Code on behalf of @autoantwort.)

}
if (Token::simpleMatch(type, "decltype (") && type->next()->valueType()) {
const ValueType *vt2 = type->next()->valueType();
Expand Down
55 changes: 55 additions & 0 deletions test/testsymboldatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1602,6 +1602,61 @@ 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);
}
{ // 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);
}
{ // 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);
ASSERT(p->isReference());
ASSERT(p->isPointer());
ASSERT(p->valueType());
ASSERT(p->valueType()->reference == Reference::LValue);
}
}

void VariableValueTypeTemplate() {
Expand Down
Loading