Skip to content

Fix #15094 SymbolDatabase: Variable::isReference() is true for function pointer returning a reference - #8931

Open
autoantwort wants to merge 7 commits into
cppcheck-opensource:mainfrom
autoantwort:fix-isreference-funcptr
Open

autoantwort wants to merge 7 commits into
cppcheck-opensource:mainfrom
autoantwort:fix-isreference-funcptr

Conversation

@autoantwort

@autoantwort autoantwort commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

https://trac.cppcheck.net/ticket/15094

For a function pointer returning a reference, the reference of the return type was taken as reference of the variable:

struct S {
    std::string& (*f)(int, int);
    int& (*g[2])();
};
void foo(int& (*fp)(), int&& (*fp2)());

Variable::isReference() (and isRValueReference() for fp2) was true for all of them.

Variable::evaluate() now drops the reference collected before the ( of a function declarator, as it belongs to the return type. A & after the ( still makes the variable a reference, so int (*&f)(), int (&f)(), int*& r, int (&a)[2] and int& (r) are unchanged.

The ValueType of the function pointer is not changed: the type of a call fp() is derived from it, so its reference has to stay the one of the return type (clearing it caused returnTempReference FPs).

Two places relied on the wrong flags and are adjusted:

  • checkConstVariable: a reference to a function returning a reference (int& (&f)()) was skipped only because it was wrongly flagged as rvalue reference. References to functions are now skipped explicitly (this also fixes the existing FP for int (&f)()).
  • Copy/move constructor detection: Foo(Foo&& (*f)()) was a move constructor. With the fix it is a normal constructor, and parameters that are references to functions (Foo(Foo& (&f)())) no longer make a copy/move constructor either.

Found with #8910, where it suppressed uninitMemberVarNoCtor for a struct with such a member (codelite in the PR test results). #8910 contains a workaround in CheckClass for this; with this PR merged it can be replaced by var.isReference() again.

This PR was created by Claude Code (an AI coding agent) on behalf of @autoantwort.

🤖 Generated with Claude Code

…on 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 <noreply@anthropic.com>
@dmcppcheck

Copy link
Copy Markdown

@claude review

Comment thread lib/symboldatabase.cpp Outdated
@claude

claude Bot commented Oct 9, 2026

Copy link
Copy Markdown

Review summary: The fix looks correct for int& (*f)(), arrays of function pointers, and int (*&f)(). Redundant declarator parentheses such as char &(b); are already removed by the tokenizer (removeParentheses22), so the new ( reset in Variable::evaluate() shouldn't misclassify real references.

One possible gap (inline comment): pointers to member functions returning a reference (int& (S::*pm)()). evaluate() clears the flag, but parsedecl() breaks before the reset because it only handles ( *, so ValueType::reference may still be LValue. A test for that case would be good.

🤖 Generated with Claude Code

autoantwort and others added 2 commits October 9, 2026 21:53
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`int& (r)` and `int& UNUSED(r)` lost their reference flag, which gave
uninitvar false positives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dmcppcheck

Copy link
Copy Markdown

@claude review

Comment thread lib/symboldatabase.cpp Outdated
@claude

claude Bot commented Oct 9, 2026

Copy link
Copy Markdown

Review summary: The Variable::evaluate() change looks right. Only a reference that comes before the declarator ( is dropped, so int (*&f)(), int (&f)(), int& (r) and UNUSED(r) keep their reference. I left one inline comment: in parsedecl() the reset only applies to ( *. It looks like pointer-to-member declarators (int& (S::*pm)()) leave the loop before the reset, so their ValueType would still say LValue reference. Please check that the new pm test really passes. I reviewed the code by reading it only; I didn't build it or run the tests.

@dmcppcheck

Copy link
Copy Markdown

Test results for commit da724f2 (tools/test-my-pr.py, main compared to this PR):

Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8931/
packages: 100
Differing warnings: 0 (+0 -0)
Timing: -0.2% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8931/2026-10-09_20-01-23-da724f2e5700_timing.html)

Posted automatically by the cppcheck PR test runner. +N: warnings only with this PR, -N: warnings only with main. The AI review is written by Claude and can be wrong.

@danmar

danmar commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

one more claude review I did says:

The PR should not be merged as it is: it introduces error-severity false positives on valid code. I built the PR and its merge-base (sequentially, -j4) and compared them; I did not run the testrunner.

The false positives

All of these are clean on the merge-base and reported with the PR:

  int& use1(int& (*fp)()) { return fp(); }                 // returnTempReference
  void use3(int& (*fp)()) { int& r = fp(); r = 2; }        // danglingTempReference
  struct D {
      int& call() { return f(); }                          // returnTempReference
      int& (*f)();
  };
  const int& use4(const int& (*fp)(int)) { return fp(1); } // returnTempReference
  int& (*g)();
  int& use5() { return g(); }                              // returnTempReference
  typedef int& (*FP)();
  int& use8(FP fp) { return fp(); }                        // returnTempReference
  int* use10(int& (*fp)()) { return &fp(); }               // returnDanglingLifetime

autoantwort and others added 4 commits October 12, 2026 19:10
…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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ameter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@autoantwort

Copy link
Copy Markdown
Contributor Author

Thanks, all of these are valid. They came from the parsedecl() part of the PR: clearing the reference in the function pointer's ValueType also changed the type of the call fp() from int & to int, which is derived from that ValueType. So the result was treated as a temporary.

I removed that part. The ValueType is unchanged compared to main now, and only Variable::isReference() is fixed. Your examples are added as returnReference30 in testautovariables.cpp, plus typeOf tests for the call.

A self review found two more places that depended on the wrong flags, which are fixed too:

  • int& (&f)() (reference to a function returning a reference) was skipped by checkConstVariable only because it was flagged as rvalue reference. It would have got constParameterReference. References to functions are now skipped explicitly.
  • Constructors taking Foo& (&f)() changed from move to copy constructor, which lost noCopyConstructor warnings. Function references are no longer taken as copy/move constructor parameters.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants