Repository navigation
Fix #15098 Nullptr dereference in exprDependsOnThis() (II) - #8934
chrchr-github wants to merge 4 commits into
Conversation
dmcppcheck
left a comment
There was a problem hiding this comment.
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
::dleft 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.
| 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, ";")); |
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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* dauto 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.
|
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/ 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. |
No description provided.