Skip to content

Fix #15034 FN arrayIndexOutOfBounds (ternary in subfunction) - #8856

Open
chrchr-github wants to merge 10 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_15034
Open

chrchr-github wants to merge 10 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_15034

Conversation

@chrchr-github

Copy link
Copy Markdown
Collaborator

No description provided.

@chrchr-github chrchr-github added the merge-after-next-release Wait with merging this PR until after the next Release label Sep 15, 2026
@chrchr-github
chrchr-github marked this pull request as ready for review September 15, 2026 14:58
@chrchr-github chrchr-github removed the merge-after-next-release Wait with merging this PR until after the next Release label Sep 20, 2026
Comment thread test/testsymboldatabase.cpp Outdated

// The struct Fred has two functions, a constructor and a destructor
ASSERT_EQUALS(2U, fredScope->functionList.size());
ASSERT_EQUALS(2U, fredScope->functionList.size()); // cppcheck-suppress nullPointer // see ticket #9747

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is an AI review. Take it with a grain of salt and feel free to reject it by resolving the comment.

I confirmed that this suppression is needed because of the PR. With selfcheck's options (--check-level=exhaustive --library=cppcheck-lib ...), the merge base gives no warning here, while this PR gives Possible null pointer dereference: fredScope. It is a false positive, since assert_() throws when the condition fails. The cause is that the removed "condition depends on only one variable" guard in setTokenValue() used to drop the nullptr from (it == db->scopeList.end()) ? nullptr : &*it, because the condition contains a function call. So user code with the same "ternary + throwing check" pattern will now also hit #9747.

To get a feel for how often that happens, I compared warnings for the merge base and the PR on lib/, cli/, test/cfg/, samples/, testsymboldatabase.cpp and testvalueflow.cpp (normal check level, style/warning/portability/performance, inconclusive). Both gave exactly the same 2407 warnings, so it doesn't look widespread. Some correlated-ternary probes such as d = (a > b) ? 0 : a - b; if (a > b) return 0; return 10 / d; also stayed silent. Just mentioning it so the trade-off is a conscious one.

@dmcppcheck

Copy link
Copy Markdown

This pull request has merge conflicts that must be resolved.

@dmcppcheck

Copy link
Copy Markdown

@claude review

Comment thread lib/vf_settokenvalue.cpp
if (std::find(values.cbegin(), values.cend(), value) != values.cend())
setTokenValue(parent, std::move(value), settings);
}
} else if (!value.isImpossible()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Removing the "condition depends on only 1 variable / no function call" guard entirely means any possible value from either branch now goes up to ? whatever the condition is (several variables, function calls, values that carry a varId). That's a lot wider than what #15034 needs, so I'd expect new false positives (nullPointer, arrayIndexOutOfBounds, zerodiv) downstream. The selfcheck suppression added in testsymboldatabase.cpp suggests this has already happened. Could the guard be relaxed just for the case in the ticket, where the branch values come from the same variable that's in the condition, instead of being dropped? Please also add some negative tests, e.g. x ? y : 0 where y has a possible value that's unrelated to x.

Comment thread test/testsymboldatabase.cpp Outdated

// The struct Fred has two functions, a constructor and a destructor
ASSERT_EQUALS(2U, fredScope->functionList.size());
ASSERT_EQUALS(2U, fredScope->functionList.size()); // cppcheck-suppress nullPointer // see ticket #9747

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 suppression is only needed because of the valueflow change, so selfcheck now reports a new nullPointer false positive (after ASSERT(fredScope != nullptr)). I'd fix the regression rather than suppress it here. Users will hit the same pattern.

Comment thread test/testvalueflow.cpp
ASSERT_EQUALS_ENUM(ValueFlow::Value::ValueType::INT, it->valueType);
ASSERT_EQUALS(5, it->intvalue);
ASSERT_EQUALS_ENUM(ValueFlow::Value::Bound::Point, it->bound);
ASSERT_EQUALS_ENUM(ValueFlow::Value::ValueKind::Possible, it->valueKind);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The ticket is about an arrayIndexOutOfBounds false negative, but there's no checker-level test for it. Please add the ticket example to testbufferoverrun.cpp, together with a negative case such as a[i < 0 ? 0 : i] called with g(-5), which must not warn.

@claude

claude Bot commented Oct 10, 2026

Copy link
Copy Markdown

Summary:

  • The valueFlowInferCondition change, which now infers only for the LHS of ?, looks fine.
  • Removing the single-variable / function-call guard in setTokenValue for ? widens value propagation through every ternary. That will likely cause new false positives, and the selfcheck nullPointer suppression added in testsymboldatabase.cpp looks like one already. I'd narrow this to the pattern in the ticket instead.
  • There's no checker-level test in testbufferoverrun.cpp for #15034, and no negative tests for the wider propagation.
  • The PR currently has merge conflicts.

🤖 Generated with Claude Code

@dmcppcheck

Copy link
Copy Markdown

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

Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8856/
packages: 1000
Differing warnings: 145 (+139 -6)
AI review: 20 improvement, 29 regression, 1 unclear (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8856/2026-10-10_20-28-58-72fa07506cc7-review.txt)
Timing: -2.1% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8856/2026-10-10_20-28-58-72fa07506cc7_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.

@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 correctness problems. In my tests the current version of the change (only dropping value.varId != 0 from the guard in setTokenValue(), plus inferring only for the LHS of ?) gave no new false positives. It also fixes a wrong base value: in c = (a > 0) ? a : -a base gave c >= 1, while the PR gives the correct c >= 0.

One new remark, about the test: the second added valueFlowTernary case is invalid code because it redeclares the parameter a. Its two assertions check the same fields and leave out the symbol, so they can't tell the two values apart and pin down little. Details are in the inline comment.

The missing checker-level test in testbufferoverrun.cpp and the wider propagation concern have already been raised in comments.md, so I don't repeat them.

Testing

Build: make -j8 cppcheck testrunner in pr/ and make -j8 cppcheck in base/. Both built cleanly.

Unit tests: ./testrunner TestValueFlow TestSymbolDatabase TestBufferOverrun TestNullPointer TestUninitVar TestOther TestCondition ran 1253 tests with 0 failures.

Ticket case (int a[5]; int g(int i){ return a[i < 0 ? -i : i]; } int f(void){ return g(-5); }): base is silent. The PR reports arrayIndexOutOfBounds at index 5 with a correct "Assuming condition 'i<0' is true" note.

Probes, base vs PR (cppcheck -q --enable=warning), all in /work/scratch:

  • New true positives with the PR:
    • a[i > 0 ? i : -i] with g(-7)
    • a[i < 0 ? -i : (i > 9 ? 0 : i)] with g(-6)
  • No false positives on the excluded branch, in either base or PR:
    • a[i < 0 ? 0 : i + 5] with g(-1) and with g(1)
    • a[i > 4 ? 4 : i], a[i != 10 ? i : 0], a[!(i<5) ? 0 : i], a[(i & 8) ? 0 : i], a[i*2 > 8 ? 0 : i], a[(i>4||i<0) ? 0 : i], each with an out-of-range argument
    • a[(unsigned)i < 5u ? i : 4] with g(-1)
    • 100 / (d ? d : 1) and 100 / (d == 0 ? 1 : d) with g(0)
  • Correlated conditions on different variables (the case I expected to regress, since Token::addValue gives every value on a variable token a varId): in all of these base and PR gave the same output.
    • ok ? i : 0, n > 4 ? 0 : i, x > 0 ? i : 0 after if (x > 0) i = 1;, x ? d : 1, n > 0 ? q : p
    • y = x*2; a[x < 2 ? y : 0]
    • for-loop variables in a branch
    • global, member and parameter conditions
  • Larger run: I ran base and PR over lib/{valueflow,astutils,token,tokenize,checkother,checkcondition,programmemory}.cpp, test/cfg and samples with warning, style and portability enabled, inconclusive, and --library=cppcheck-lib. Both gave the same 2376 warnings.
  • Second new test's code (debug value flow): in the PR, : no longer gets the bogus value 1 from the old ?-RHS inference, and a/c become >=0 instead of the incorrect >=1 in base. cppcheck also reports legacyUninitvar on that snippet because of the int a redeclaration, in both base and PR.

Not run: the selfcheck and the 1000-package comparison. The PR test-runner numbers in comments.md are for an older commit (72fa075), before the later vf_settokenvalue.cpp changes.


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

Comment thread test/testvalueflow.cpp
code = "int f(int a, int b) {\n"
" if (b <= 0)\n"
" return b;\n"
" int a = (a > 0) ? a : -a;\n"

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 snippet isn't valid C/C++: int a = (a > 0) ? a : -a; redeclares the parameter a in the function's outermost block, and it initialises a from itself. Running cppcheck on it gives legacyUninitvar on line 4. Please use a fresh variable, e.g. int c = (a > 0) ? a : -a; return 1 / (c < b ? b : c);, which gives the same value-flow shape.

Also, the two values below are asserted with identical fields (SYMBOLIC, -1, Upper, Impossible), and it->tokvalue is never checked. That means the test can't tell the !<=symbolic=(a-1) value from the !<=symbolic=(b-1) value. Checking the symbol, e.g. ASSERT_EQUALS("b", it->tokvalue->expressionString()), or using isImpossibleContainerSizeValue-style helpers would make the test pin down the intended behaviour.

@dmcppcheck

Copy link
Copy Markdown

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

Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8856/
packages: 1000
Differing warnings: 70 (+69 -1)
AI review: 3 improvement, 15 neutral, 13 regression, 19 unclear (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8856/2026-10-14_06-45-12-7ae3a34445a5-review.txt)
Timing: -0.0% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8856/2026-10-14_06-45-12-7ae3a34445a5_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.

3 participants