Skip to content

Fix #15006: FP invalidFree with new array - #8880

Open
ludviggunne wants to merge 14 commits into
cppcheck-opensource:mainfrom
ludviggunne:15006-preinc-delete
Open

ludviggunne wants to merge 14 commits into
cppcheck-opensource:mainfrom
ludviggunne:15006-preinc-delete

Conversation

@ludviggunne

Copy link
Copy Markdown
Collaborator

No description provided.

@ludviggunne
ludviggunne force-pushed the 15006-preinc-delete branch 3 times, most recently from 018d27e to 061ab75 Compare September 21, 2026 08:22
Comment thread lib/checkother.cpp
Comment thread lib/checkother.cpp
@dmcppcheck

Copy link
Copy Markdown

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/
packages: 1000
Differing warnings: 122 (+116 -6)
AI review: 37 improvement, 11 regression, 2 unclear (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8880/2026-10-05_07-31-15-7f232989d1f9-review.txt)
Timing: +0.6% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8880/2026-10-05_07-31-15-7f232989d1f9_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

Copy link
Copy Markdown

@claude review

Comment thread lib/valueflow.cpp Outdated
Comment thread lib/valueflow.cpp
@claude

claude Bot commented Oct 9, 2026

Copy link
Copy Markdown

Review summary:

  • The invalidFree fix works for the new case. The malloc case is still a known FP (TODO test).
  • valueFlowSymbolicOperators: the new offset propagation has no overflow check on intvalue += constant, which is UB. It also shifts impossible and bound symbolic values, so it changes behaviour for all checkers. daca shows +116/-6 warnings, with 11 flagged as regressions; please look at those.
  • Missing tests in testvalueflow.cpp for the new symbolic propagation.

@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 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 value symbolic=(ip+1-1), because the offset is in int units and 1 is in char units. But main already has x symbolic=((char*)ip) and ip 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); where p and q come from different new: the output is the same as on main.

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

Comment thread lib/valueflow.cpp Outdated
continue;
if (!add && MathLib::subtractionWouldOverflow(value.intvalue, constant->intvalue))
continue;
ValueFlow::Value newValue(value);

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 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 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 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) gives Condition 'z==a' is always true.
  • u += 2; if (u - 2LL < a) gives Condition 'u-2LL<a' is always false.

Base reports none of these.

invalidFree checks, PR compared with base:

  • q++; delete [] (q - 1); with a second, unrelated new: the false positive is gone.
  • p += 2; delete [] (p - 1); is still reported.
  • (char*)p + 1 on an int* allocation: the output is the same as on base.

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

Comment thread lib/valueflow.cpp Outdated

@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 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 for a == 0.
  • unsigned int u = a; if (u + 1 > a) gives 'u+1>a' is always true. This is wrong for a == UINT_MAX.
  • unsigned int u = a; u += 3; if (u - 1 > a) gives 'u-1>a' is always true. This is wrong for a >= 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 - 1 now gets the known value symbolic=x after double 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 < a is 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.

Comment thread lib/valueflow.cpp
continue;
ValueFlow::Value newValue(value);
newValue.intvalue = add ? newValue.intvalue + constant->intvalue : newValue.intvalue - constant->intvalue;
setTokenValue(tok, std::move(newValue), settings);

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

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.

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