Repository navigation
Fix #15006: FP invalidFree with new array - #8880
ludviggunne wants to merge 14 commits into
Conversation
018d27e to
061ab75
Compare
|
Test results for commit 7f23298 (tools/test-my-pr.py, main compared to this PR): Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8880/ 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. |
|
@claude review |
|
Review summary:
|
7f23298 to
ff2b855
Compare
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
The new symbolic offset propagation in valueFlowSymbolicOperators does not take integer promotion into account, and this causes a new false positive. If vartok has a type narrower than int (uint8_t, unsigned char, short), seq has the symbolic value last+1 in the narrow, wrapping type. But seq - 1 is evaluated as int, so it is not last when seq wrapped. The PR still assigns the known value symbolic=last. Because of that, if (seq - 1 != last), a common way to detect wraparound, is reported as always false. On main nothing is reported.
Apart from the problems already raised in comments.md, I found no other new problems. All tests pass.
Testing
I built cppcheck and testrunner in pr/ and cppcheck in base/. The full ./testrunner passes: 5353 tests, 0 failed, including TestOther, TestValueFlow and TestMathLib.
New false positive (integer promotion), t6.c:
#include <stdint.h>
int wrapped(uint8_t last) {
uint8_t seq = last;
seq++;
if (seq - 1 != last) // true when last == 255
return 1;
return 0;
}cppcheck -q --enable=style t6.c
- base: no output
- PR:
t6.c:5:17: style: Condition 'seq-1!=last' is always false [knownConditionTrueFalse]
t6.c:4:5: note: seq is incremented', new value is symbolic=last+1
The same happens for unsigned char c = a; c++; if (c - 1 == a), which is reported as always true, and for short. With unsigned int the PR's result b-1<a is always false, which is correct because of the modular arithmetic.
Other things I checked:
- Pointer casts between element types,
int *q = ip + 1; char *cp = (char*)q; cp - 1: the PR adds the wrong known valuesymbolic=(ip+1-1), because the offset is inintunits and1is incharunits. But main already hasx symbolic=((char*)ip)andip symbolic=(cp-1)for the same code, and I could not make any checker report a wrong result from it. So I do not report it as a new problem. - invalidFree with
p = q + 1; delete [] (p - 1);wherepandqcome from differentnew: the output is the same as on main.
Automated review of ff2b855 by Claude. The pull request was built and tested locally.
| continue; | ||
| if (!add && MathLib::subtractionWouldOverflow(value.intvalue, constant->intvalue)) | ||
| continue; | ||
| ValueFlow::Value newValue(value); |
There was a problem hiding this comment.
AI review
This does not take integer promotion into account. For a vartok narrower than int, the symbolic offset that vartok already has comes from arithmetic that wraps in the narrow type. But vartok + c / vartok - c is evaluated as int, so the shifted value is wrong when the narrow value wrapped. Example: uint8_t seq = last; seq++; if (seq - 1 != last) now gives Condition 'seq-1!=last' is always false, while main reports nothing. When last == 255, seq - 1 is -1. A fix could be to skip this when vartok has an integral type smaller than int, for example by checking vartok->valueType() against the promoted type.
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
The new symbolic offset propagation in valueFlowSymbolicOperators still gives a wrong known value when the expression is evaluated in a type wider than vartok. If unsigned int u holds the wrapped value a+1 and the code computes u - 1UL or u - 1LL, the subtraction happens in a 64-bit type, so the result is not a when a == UINT_MAX. The PR still assigns symbolic=a and reports knownConditionTrueFalse; main reports nothing. The promoted guard only looks at the operand type. It also has to make sure that the result type (tok->valueType()) is the same as the type of vartok, or at least has the same size and signedness. The rest of the PR worked as the tests expect. I found no other new problems.
Testing
I built cppcheck and testrunner in pr/ and cppcheck in base/. ./testrunner TestOther TestValueFlow TestMathLib TestCondition passes with 0 failures.
New false positive: unsigned operand widened by the constant, w3.c:
int g(unsigned int a) {
unsigned int u = a;
u++;
if (u - 1UL != a) // true when a == UINT_MAX (u == 0, u - 1UL == ULONG_MAX)
return 1;
return 0;
}cppcheck -q --enable=style w3.c
- base: no output
- PR:
w3.c:4:17: style: Condition 'u-1UL!=a' is always false [knownConditionTrueFalse]
The same happens with 1LL:
long long z = u - 1LL; if (z == a)givesCondition 'z==a' is always true.u += 2; if (u - 2LL < a)givesCondition 'u-2LL<a' is always false.
Base reports none of these.
invalidFree checks, PR compared with base:
q++; delete [] (q - 1);with a second, unrelatednew: the false positive is gone.p += 2; delete [] (p - 1);is still reported.(char*)p + 1on anint*allocation: the output is the same as on base.
Automated review of 873d25a by Claude. The pull request was built and tested locally.
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
The new symbolic offset in valueFlowSymbolicOperators adds a new false positive for unsigned types when the expression is used in a relational comparison. Assume u has the known value symbolic=a. The PR then gives u - 1 the known value symbolic=a-1, and the condition code treats that as if u - 1 < a always holds. With unsigned arithmetic u - 1 wraps when a == 0, so the condition can be false. For example, size_t i = n; if (i - 1 < n) is now reported as knownConditionTrueFalse (always true) with the default settings. Main reports nothing. The same thing happens for u + 1 > a, and after u++ / u += 3. Equality comparisons (==, !=) are still correct because of modular arithmetic, so this is a different problem from the promotion and wider-constant cases that were already reported. Pointers are not affected, because pointer overflow is UB. So the delete [] (p - 1) fix can be kept if non-pointer unsigned operands are skipped. Apart from this I found no new problems.
Testing
I built cppcheck and testrunner in pr/ and cppcheck in base/. ./testrunner TestOther TestValueFlow TestMathLib TestCondition ran 452 tests with 0 failures.
New FP: unsigned relational comparison, u3.c:
#include <stddef.h>
int last_index_valid(size_t n) {
size_t i = n;
if (i - 1 < n) /* false when n == 0 */
return 1;
return 0;
}cppcheck -q --enable=style,warning u3.c
- base: no output
- PR:
u3.c:4:15: style: Condition 'i-1<n' is always true [knownConditionTrueFalse]
u3.c:3:14: note: i is assigned 'n' here.
More variations with unsigned int a, run with --check-level=exhaustive. Base reports none of them, the PR reports all of them:
unsigned int u = a; u++; if (u - 2 < a)gives'u-2<a' is always true. This is wrong fora == 0.unsigned int u = a; if (u + 1 > a)gives'u+1>a' is always true. This is wrong fora == UINT_MAX.unsigned int u = a; u += 3; if (u - 1 > a)gives'u-1>a' is always true. This is wrong fora >= UINT_MAX-2.
For comparison: unsigned int u = a + 1; if (u < a) is already reported on base, so the existing wraparound handling already has a similar weakness. The PR extends it to new expressions.
Other checks:
- With
double,d - 1now gets the known valuesymbolic=xafterdouble d = x; d++;. That is not exact for floating point. I could not make any checker emit a warning from it, so I do not report it. int(signed)u - 1 < ais now reported as always true. That is acceptable because of signed-overflow UB.
Automated review of 6820464 by Claude. The pull request was built and tested locally.
| continue; | ||
| ValueFlow::Value newValue(value); | ||
| newValue.intvalue = add ? newValue.intvalue + constant->intvalue : newValue.intvalue - constant->intvalue; | ||
| setTokenValue(tok, std::move(newValue), settings); |
There was a problem hiding this comment.
AI review
For unsigned non-pointer operands this offset is only correct modulo 2^N. The condition code still uses it for ordering, which gives new false positives. Example: size_t i = n; if (i - 1 < n) is now reported as Condition 'i-1<n' is always true, but the condition is false for n == 0. Main reports nothing. unsigned u = a; if (u + 1 > a) has the same problem: it is reported as always true, but it is false for a == UINT_MAX. Unless the value can be marked as valid only for equality, consider skipping this when varVt->pointer == 0 && varVt->sign == ValueType::Sign::UNSIGNED. Pointers (the invalidFree case) and signed integers are fine, because overflow there is UB.
No description provided.