Skip to content

Fix #15098 Nullptr dereference in exprDependsOnThis() (II) - #8934

Open
chrchr-github wants to merge 4 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_15098
Open

chrchr-github wants to merge 4 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_15098

Conversation

@chrchr-github

Copy link
Copy Markdown
Collaborator

No description provided.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

The fix only covers the first parameter of a prototype; the same crash still occurs when the shadowing parameter is in any later position. With void g(int x, double d); instead of void g(double d);, cppcheck still segfaults in exprDependsOnThis() exactly as on base.

Cause: at the changed line tok is the token that precedes the declaration. That is ( only for the first parameter; for later parameters it is ,, so TokenList::isFunctionHead(tok, ";") returns nullptr and the parameter is still added to the global variable map.

A related leftover that does not crash: parameters of a function definition at global scope (void g(double d) { }) are also still registered as global, so ::d(2) in a later function gets that parameter's varid. This is unchanged from base.

The added test passes and the full testrunner is green.

Testing

Built cppcheck and testrunner in pr/ and cppcheck in base/ (make -j8).

  • ./testrunner TestVarID: 227 tests, 0 failed.
  • Full ./testrunner: 5351 tests, 0 failed.
  • The test case from the PR as a file: base segfaults (exit 139), PR exits 0 with ::d left without a varid.

Still crashes: parameter is not the first one

b.cpp:

double d(int);
void g(int x, double d);
void f() {
int i = 1;
double d = 0;
if (d == ::d(i)) {
    d = ::d(2);
}
}
$ pr/cppcheck -q b.cpp
Segmentation fault (core dumped)      # exit 139, same on base

gdb backtrace on the PR build:

#0 exprDependsOnThis (expr=..., onVar=true, depth=3) at lib/astutils.cpp:1162
#1 exprDependsOnThis (..., depth=2) at lib/astutils.cpp:1171
#2 exprDependsOnThis (..., depth=1) at lib/astutils.cpp:1171
#3 operator() (...) at lib/astutils.cpp:3096
...
#7 findExpressionChangedImpl<ExpressionChangedSimpleFind> (...) at lib/astutils.cpp:3095

pr/cppcheck -q --debug b.cpp shows the wrong varid before the crash: if ( d@var3 == :: d@var1 ( i@var2 ) ) {.

Wrong varid, no crash: function definition

d.cpp (the PR's test with line 2 changed to void g(double d) { (void)d; }):

$ pr/cppcheck -q --debug d.cpp
3:2: void g ( double d@var1 ) { ( void ) d@var1 ; }
7:6: if ( d@var3 == :: d@var1 ( i@var2 ) ) {
8:7: d@var3 = :: d@var1 ( 2 ) ;

Exit 0 on both PR and base; I did not confirm the base varid output is identical beyond ::d being present in it.

Variations that are handled correctly by the PR

No crash and ::d without a varid for:

  • void g(double d, int x);
  • void g2(double d) noexcept;
  • template<class T> void g(T d);
  • namespace N { void g(double d); }
  • extern "C" void k(double d);
  • function pointer / typedef prototypes with a parameter d

Real globals with paren/brace init (int x(5); int y{6};) still resolve through ::x / ::y. A C file with the same prototypes also ran without problems.


Automated review of d724b2d by Claude. The pull request was built and tested locally.

Comment thread lib/tokenize.cpp Outdated
if (isC() && Token::Match(prev2->previous(), "&|&&"))
syntaxErrorC(prev2, prev2->strAt(-2) + prev2->strAt(-1) + " " + prev2->str());
variableMap.addVariable(prev2->str(), scopeStack.size() <= 1);
variableMap.addVariable(prev2->str(), scopeStack.size() <= 1 && !TokenList::isFunctionHead(tok, ";"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

This only excludes the first parameter of a prototype. tok is the token before the declaration, so it is ( for the first parameter but , for the following ones; isFunctionHead(tok, ";") then returns nullptr and the parameter is still put into the global map.

The crash from #15098 is still reproducible when the parameter is moved to second position:

double d(int);
void g(int x, double d);
void f() {
int i = 1;
double d = 0;
if (d == ::d(i)) {
    d = ::d(2);
}
}

cppcheck -q b.cpp segfaults in exprDependsOnThis() (lib/astutils.cpp:1162) with the PR, same as base; --debug shows :: d@var1 ( i@var2 ).

The same goes for parameters of a function definition, void g(double d) { (void)d; }: tok is ( but the suffix is {, so d stays global and ::d(2) in a later function gets d@var1 (no crash in my test, but a wrong varid).

It would be more robust to decide "is a function parameter" independently of the position in the list, i.e. whether the declaration is inside the parentheses of a function head, instead of checking tok itself. Please also add a test with the parameter in a non-first position.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

I found no new problems. The current version (scopeStack.size() <= 1 && !functionDeclEndToken) fixes the case the earlier review reported: parameters in any position of a prototype or a definition are no longer registered as global variables, and the crash from #15098 is gone in every variant I tried. I looked for the opposite regression (a real global variable that is no longer found through ::name because functionDeclEndToken is still set) and could not produce one.

Testing

Built cppcheck and testrunner in pr/ and cppcheck in base/.

  • ./testrunner TestVarID: 0 failed.
  • Full ./testrunner: 5351 tests, 0 failed.

Crash variants (double d(int); + a declaration with a parameter d + the f() from the ticket, 33 files, cppcheck -q --debug): base segfaults (exit 139) on 17 of them, the PR exits 0 on all 33 and leaves ::d without a varid in all of them. Among the variants that crash on base and are fixed:

  • void g(int x, double d);, void g(int x, double d = 1.0);, double d[3], double& d, const double* d
  • auto g(int x, double d) -> int;, ... noexcept(true);, ... = delete;, ... __attribute__((unused));
  • template<class T> void g(int x, T d);
  • void g(int (*x)(int), double d);, void g(int x = h(1, 2), double d = 0);
  • extern void g(int x, double d), k(int y, double d);

Definitions, which did not crash on base but gave ::d the varid of the parameter, are also correct now: void g(int x, double d) { (void)d; }, the template definition, function-try-block, out-of-line constructor with initializer list, and a global lambda auto l = [](int x, double d) { return d; };.

Regression search (real globals v/w used as ::v/::w after or next to function heads, about 80 files, PR and base --debug token output compared): the output is identical for all except the intended change (void f(int v, int w); no longer provides ::v/::w). Covered among others: int v(1), w(2);, int v{1}, w{2};, int f(int d), v, w;, int (*w)(int d), v;, struct S { S(int d) : m(d) {} int m; } v;, globals after prototypes with = delete, trailing return type, noexcept(noexcept(...)), requires, attributes, unknown macros before/after the head, function-try-blocks, lambdas in initializers, constructors with initializer lists containing lambdas, a K&R definition in a .c file.

Larger files: --debug output of PR and base is identical for samples/*/*, test/cfg/std.cpp, test/cfg/std.c, test/cfg/qt.cpp and test/cfg/boost.cpp.

No Python tests in test/cli are touched by the PR.


Automated review of d93e900 by Claude. The pull request was built and tested locally.

@dmcppcheck

Copy link
Copy Markdown

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

Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8934/
packages: 1000
Differing warnings: 1 (+1 -0)
AI review: 1 improvement (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8934/2026-10-12_10-35-14-d93e9004e6a5-review.txt)
Timing: -0.2% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8934/2026-10-12_10-35-14-d93e9004e6a5_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.

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.

2 participants