Skip to content

Fix #15000: Propagate integral cast ranges with path-safe value flow - #8819

Open
jfdeverge wants to merge 1 commit into
cppcheck-opensource:mainfrom
jfdeverge:dev/fix_misra_14_3
Open

jfdeverge wants to merge 1 commit into
cppcheck-opensource:mainfrom
jfdeverge:dev/fix_misra_14_3

Conversation

@jfdeverge

@jfdeverge jfdeverge commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

There was missing detection of 'MISRA C:2012 Rule 14.3" on some specific code structure generated by Matlab Simulink coder.
We identified these issues by comparing its results with reports previously generated by our company’s internal tool on the same software.

The bug report would have looked like below.

Problem

Cppcheck did not report a MISRA C:2012 Rule 14.3 violation when a comparison
used an integer expression whose range was provably bounded by casts and
constant arithmetic. The existing check handled a variable compared with a
value outside the variable's declared type range, but did not evaluate this
equivalent expression form.

Minimal Reproducer

typedef unsigned int uint32;
typedef unsigned long long uint64;
typedef long long sint64;

void f(uint32 x)
{
    uint64 tmp = ((uint64)x) + 1ULL;
    if (tmp > 4294967295ULL)
        tmp = 4294967295ULL;

    if ((((sint64)((uint32)tmp)) - 1LL) > 4294967295LL) {
    }
}

After the clamp and casts, the left-hand expression cannot exceed
UINT32_MAX - 1. Therefore the final condition is always false and should
produce the compareValueOutOfTypeRangeError diagnostic, which the MISRA
postprocessor maps to Rule 14.3.

Observed Behavior

Before the fix, the final comparison produced no diagnostic. This allowed the
generated-code pattern to pass through the cppcheck-based MISRA pipeline
without the expected Rule 14.3 finding.

Comment thread lib/checkcondition.cpp Fixed
Comment thread test/testcondition.cpp Outdated
" if ((((sint64)((uint32)tmp)) - 1LL) < 0LL) {}\n"
" if ((((sint64)((uint32)tmp)) - 1LL) > 4294967295LL) {}\n"
"}\n", settingsUnix64);
ASSERT_EQUALS("[test.cpp:9:43]: (style) Comparing expression of type 'signed long long' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n",

@chrchr-github chrchr-github Sep 1, 2026 •

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.

The text/ID of the warning seems incorrect. 4294967295 is within range for signed long long.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hello! The condition is essentially (sint64), the diagnostic log is correct. But the check is only possible because there is a (uint32) inside.

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.

hmm it will at least be confusing. At first glance it sounds like 'signed long long' expressions shouldn't be compared against 4294967295.

It might be less confusing if the type 'signed long long' would be removed. The range of 'signed long long' expressions is much greater, the issue here is that the expression range corresponds to a 'uint32'. How problematic would that be to remove the type if the expression range does match the type range?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Issue fixed in latest version, please re-review.

@chrchr-github

Copy link
Copy Markdown
Collaborator

I have created this ticket: https://trac.cppcheck.net/ticket/15000

@jfdeverge

Copy link
Copy Markdown
Contributor Author

I will update the commit message with Fix #15000 ...

Comment thread lib/checkcondition.cpp Outdated
return false;

std::uint8_t bits = 0;
switch (tok->valueType()->type) {

@aadanen aadanen Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe this switch could be simplified to something like

if (tok->valueType()->isIntegral())
    bits = settings.platform.char_bit *
           tok->valueType()->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointee);

I think it would be shorter, duplicate less logic, and it would also support wchar_t

the check for isIntegral() could even be moved up to line 1980 to be very clear that this function only works for integral types

Cheers!
--Aaron

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing to getSizeOf, it simplifies a lot.

Comment thread lib/checkcondition.cpp Outdated
lower = -(MathLib::bigint(1) << (bits - 1));
upper = max / 2;
}
} else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

enum Sign : std::uint8_t { UNKNOWN_SIGN, SIGNED, UNSIGNED } sign = UNKNOWN_SIGN;

I think if the sign is unknown you should probably return false, not treat it the same as an UNSIGNED

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@danmar

danmar commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

The PR title should be updated. The commit message will not be shown in the git log but the PR title will.

@jfdeverge jfdeverge changed the title Fix Rule 14.3 detection for bounded expressions Fix #15000 CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions Sep 6, 2026
@jfdeverge

Copy link
Copy Markdown
Contributor Author

The PR title should be updated. The commit message will not be shown in the git log but the PR title will.

Done.

PR title updated to "Fix #15000 CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions" ; let me know if better convention would apply here.

Comment thread lib/checkcondition.cpp Outdated
if (tok->isCast() && tok->astOperand1()) {
MathLib::bigint typeLower;
MathLib::bigint typeUpper;
const bool hasSourceRange = getIntegerExpressionRange(tok->astOperand1(), settings, lower, upper);

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.

c++ casts have two operands.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in latest commit, please re-review.

Comment thread lib/checkcondition.cpp Outdated
return lower <= upper;
}

if (Token::simpleMatch(tok, "(") && tok->astOperand1())

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.

I am not sure what exact code you target here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed. This is dead code.

Comment thread lib/checkcondition.cpp Outdated
MathLib::bigint constantLower;
MathLib::bigint constantUpper;
if (!getIntegerExpressionRange(tok->astOperand1(), settings, operandLower, operandUpper) ||
!getIntegerExpressionRange(tok->astOperand2(), settings, constantLower, constantUpper) ||

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.

we will always return false for a unary +|- ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Buggy and completely removed in latest version, please re-review.

Comment thread test/testcondition.cpp Outdated
" if ((((sint64)((uint32)tmp)) - 1LL) < 0LL) {}\n"
" if ((((sint64)((uint32)tmp)) - 1LL) > 4294967295LL) {}\n"
"}\n", settingsUnix64);
ASSERT_EQUALS("[test.cpp:9:43]: (style) Comparing expression of type 'signed long long' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n",

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.

hmm it will at least be confusing. At first glance it sounds like 'signed long long' expressions shouldn't be compared against 4294967295.

It might be less confusing if the type 'signed long long' would be removed. The range of 'signed long long' expressions is much greater, the issue here is that the expression range corresponds to a 'uint32'. How problematic would that be to remove the type if the expression range does match the type range?

@danmar danmar changed the title Fix #15000 CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions Fix #15000 (CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions) Sep 6, 2026
@danmar

danmar commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

The PR title should be updated. The commit message will not be shown in the git log but the PR title will.

Done.

PR title updated to "Fix #15000 CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions" ; let me know if better convention would apply here.

Thanks! I don't have a strict pattern that must be followed but I typically put parentheses around the ticket title. Most important is that you indicated which ticket it solves..

Comment thread lib/checkcondition.cpp Outdated
Certainty::normal);
}

static bool getIntegerTypeRange(const Token* tok,

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.

I have the feeling we have a utility function to determine min/max values of an integer type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Replaced by stuffs from vf_common.cpp.

Comment thread lib/checkcondition.cpp Outdated
return true;
}

static bool getIntegerExpressionRange(const Token* tok,

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.

have you checked if there is some similar utility function somewhere?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Replaced by stuffs from vf_common.cpp.

Comment thread lib/checkcondition.cpp Outdated
}

static bool getIntegerExpressionRange(const Token* tok,
const Settings& settings,

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.

I don't have a strong opinion but passing settings is overkill we could just pass a platform.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Code is removed now.

Comment thread lib/checkcondition.cpp Outdated
{
if (!tok)
return false;
if (tok->hasKnownIntValue()) {

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.

if there is not a known value.. I think it would be good to consider the "impossible" values.
Example:

void foo(uint32_t x) {
    if (x > 100) return;
    a = (uint64_t)x;
}

In the cast the values for token x are (this is the --debug output):

  x {!<=-1,!>=101,<=100}

The !>=101 means x cannot have values 101 and more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it is better now with the ValueFlow approach, please re-review.

@chrchr-github

Copy link
Copy Markdown
Collaborator

I have a feeling that some of the logic should be performed in ValueFlow and not in the check. Currently, ValueFlow only seems to handle bool value ranges. @danmar What do you think?

@danmar

danmar commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

I have a feeling that some of the logic should be performed in ValueFlow and not in the check. Currently, ValueFlow only seems to handle bool value ranges. @danmar What do you think?

I agree that checkers should have some nice way to determine min/max values for an expression.

If that is put in ValueFlow or a utility function I don't have a strong opinion it depends on what is more efficient.

@chrchr-github

Copy link
Copy Markdown
Collaborator

I have a feeling that some of the logic should be performed in ValueFlow and not in the check. Currently, ValueFlow only seems to handle bool value ranges. @danmar What do you think?

I agree that checkers should have some nice way to determine min/max values for an expression.

If that is put in ValueFlow or a utility function I don't have a strong opinion it depends on what is more efficient.

I meant that ValueFlow should determine that the condition is always true/false, instead of having complex parsing logic (in this case, for casts and arithmetic) in the checker.

@jfdeverge jfdeverge changed the title Fix #15000 (CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions) Fix #15000 Propagate integral cast ranges to condition analysis Sep 12, 2026
Comment thread lib/vf_common.cpp
} else {
} else if (vt->sign == ValueType::Sign::SIGNED) {
minValue = LLONG_MIN;
maxValue = LLONG_MAX;

@aadanen aadanen Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't understand how signed 64 bit and unsigned 64 bit have the same max value. Maybe for unsigned you meant ULLONG_MAX?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, ULLONG_MAX is the true maximum for unsigned 64-bit values. getMinMaxValues currently returns bigint, which is signed and cannot represent ULLONG_MAX. I will update the legacy comment "todo max unsigned value" to "bigint cannot represent ULLONG_MAX; conservative max for unsigned 64-bit"

A biguint-based API can be introduced separately if the exact unsigned 64-bit maximum is needed ? A quick grep shows there will be some updates needed in some call-sites.

Comment thread test/testcondition.cpp Outdated
@jfdeverge
jfdeverge force-pushed the dev/fix_misra_14_3 branch 2 times, most recently from 1f38959 to 1ebcbe5 Compare September 14, 2026 07:13
@jfdeverge

Copy link
Copy Markdown
Contributor Author

I rebased against main (no conflicts).

Comment thread lib/valueflow.cpp
@danmar

danmar commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

I am working on a server that provides test reports:
http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/

please take a look at the regressions.

@jfdeverge
jfdeverge force-pushed the dev/fix_misra_14_3 branch 2 times, most recently from 89e7250 to cc643ff Compare October 9, 2026 14:44
@dmcppcheck

Copy link
Copy Markdown

@claude review

Comment thread lib/token.cpp Outdated
Comment thread lib/valueflow.cpp Outdated
Comment thread lib/vf_settokenvalue.cpp Outdated
Comment thread lib/vf_common.cpp
@claude

claude Bot commented Oct 9, 2026

Copy link
Copy Markdown

Summary of review:

  • The PR description talks about changes to the condition checker, but the diff contains none. The fix is done entirely in ValueFlow: impossible bounds on integral casts.
  • Main concerns (see inline comments):
    • The Token::getValueLE change affects every caller and looks like it hides the FP that the new cast bounds cause (array_index_77). It risks FNs in negativeIndex and other checks, and getValueGE doesn't get the matching change.
    • Removing impossible bounds from all unsigned arithmetic in vf_settokenvalue.cpp, and removing conditional possible values in valueFlowInferCondition, are broad changes. They have no tests showing that existing diagnostics are kept. The predicate is also written out twice.
    • getMinMaxValues now returns false for UNKNOWN_SIGN (plain char on the unspecified platform), which can cause FNs for existing callers.
    • Minor performance cost: two list copies per %comp%|- token in valueFlowInferCondition.
  • It would help to run the daca/selfcheck comparison to measure FN/FP changes, given how far these ValueFlow changes reach.

🤖 Generated with Claude Code

@dmcppcheck

Copy link
Copy Markdown

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

Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8819/
packages: 1000
Differing warnings: 113 (+4 -109)
AI review: 26 improvement, 2 neutral, 20 regression, 2 unclear (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8819/2026-10-09_14-44-28-cc643ff72ecf-review.txt)
Timing: +0.1% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8819/2026-10-09_14-44-28-cc643ff72ecf_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.

@jfdeverge
jfdeverge force-pushed the dev/fix_misra_14_3 branch 2 times, most recently from d2d6820 to 8104e70 Compare October 12, 2026 10:05
@jfdeverge jfdeverge changed the title Fix #15000 Propagate integral cast ranges to condition analysis Fix #15000: Propagate integral cast ranges with path-safe value flow Oct 12, 2026
@jfdeverge
jfdeverge force-pushed the dev/fix_misra_14_3 branch 6 times, most recently from f2ff7f7 to fdaaa16 Compare October 13, 2026 10:17
@dmcppcheck

Copy link
Copy Markdown

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

Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8819/
packages: 1000
Differing warnings: 237 (+104 -133)
AI review: 23 improvement, 1 neutral, 23 regression, 3 unclear (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8819/2026-10-13_10-16-57-fdaaa169aea9-review.txt)
Timing: -0.0% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8819/2026-10-13_10-16-57-fdaaa169aea9_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 three new problems. Two are reproduced regressions against the merge base and one is a coverage gap. Earlier reviews did not raise any of them.

  1. New containerOutOfBounds false positive on a common resize guard. For if (v.size() < entries + 2) v.resize(entries + 2); v[0] = 1; the PR now warns that v is empty. The PR does not fix this; it changes the expected output in test/teststl.cpp:529 from "" to the warning. The warning depends only on entries + 2 wrapping to 0 or 1. The same happens with unsigned and n + 1u.

  2. The negativeIndex bypass for induction variables (isNonnegativeInductionVariable) can hide real negative indexes. It only checks whether the loop body changes the variable. It does not look at the loop condition, so for (int i = 0; i-- > -3; i++) a[i] = 0; is no longer reported. The base version reports it.

  3. The no-wrap check misses easy cases, which loses existing knownConditionTrueFalse results. When isUnsignedArithmeticNoWrap cannot prove that unsigned +/-/* does not wrap, the impossible bounds are dropped. This loses two correct warnings that the base version gives:

    • unsigned s = c; if (s + 1u == 0u) with c an unsigned char;
    • if (i + 1 == 0) inside for (unsigned long i = 0; i < n; i++).

    The tests check that the wrap cases are now silent, but none checks that diagnostics like these are kept.

The #15000 reproducer works. The PR also correctly removes the old false positive x - y > 100u "always false" when x < 10 && y < 20.

Testing

I built cppcheck and testrunner in pr/, and cppcheck in base/. TestCondition, TestBufferOverrun, TestStl, TestType, TestOther and TestValueFlow all pass (0 failures).

I compared PR and base on scratch files with cppcheck -q --enable=style,warning --platform=unix64 [--library=std] file.

containerOutOfBounds FP (new in the PR)

#include <vector>
#include <cstdint>
#include <cstddef>
void f(size_t entries) {
    std::vector<uint8_t> v;
    if (v.size() < entries + 2)
        v.resize(entries + 2);
    v[0] = 1;
}
void g(unsigned n) {
    std::vector<int> v;
    if (v.size() < n + 1u)
        v.resize(n + 1u);
    v[0] = 1;
}

PR:

b.cpp:8:6: warning: Out of bounds access in expression 'v[0]' because 'v' is empty. [containerOutOfBounds]
b.cpp:6:18: note: Assuming condition is false
b.cpp:14:6: warning: Out of bounds access in expression 'v[0]' because 'v' is empty. [containerOutOfBounds]

Base gives no containerOutOfBounds warning here.

negativeIndex FN (new in the PR)

int a[10];
void f1(void) {
    for (int i = 0; i-- > -3; i++) { a[i] = 0; }
}

Base: error: Array 'a[10]' accessed at index -1, which is out of bounds. [negativeIndex]. PR: no warning.

knownConditionTrueFalse FNs (new in the PR)

void use(int);
void f4(unsigned long n) { for (unsigned long i = 0; i < n; i++) { if (i + 1 == 0) use(3); } }
void f6(unsigned char c) { unsigned s = c; if (s + 1u == 0u) use(4); }

Base:

c.c:6:78: style: Condition 'i+1==0' is always false [knownConditionTrueFalse]
c.c:8:55: style: Condition 's+1u==0u' is always false [knownConditionTrueFalse]

PR: neither warning.

Other checks

  • With the PR, if (u < 10) { if (u + 1 > 20) ...} and if (u >= 5) { if (u - 1 == 0) ...} are still reported.
  • The #15000 pattern is reported.
  • A ternary-guarded shift x ? (1 << s) : 0 with s = x ? 40 : 3 still gives shiftTooManyBits, the same as base.

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

Comment thread test/teststl.cpp
" v[0] = 1;\n"
"}\n");
ASSERT_EQUALS("", errout_str());
ASSERT_EQUALS("[test.cpp:5:6]: warning: Out of bounds access in expression 'v[0]' because 'v' is empty. [containerOutOfBounds]\n", errout_str());

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 changes the expected result from no warning to a new containerOutOfBounds warning. The warning appears only because entries + 2 might wrap to 0 or 1. That is a false positive on a very common "grow before access" pattern. I reproduced it with size_t and n + 1u (unsigned); base gives no such warning. Please fix the false positive rather than accepting it in the test.

});
if (!hasNonnegativeLowerBound)
continue;
if (findVariableChanged(loop->bodyStart->next(),

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

Only the loop body is checked for changes to the induction variable. The loop condition is not checked. For for (int i = 0; i-- > -3; i++) { a[i] = 0; } the bypass is used, and the negativeIndex error that base reports (index -1) is lost. The condition (remainingClauses->astOperand1()) should also be checked with findVariableChanged.

Comment thread lib/vf_settokenvalue.cpp
const auto hasRangeBound = [](const Value& value) {
return (value.isIntValue() || value.isSymbolicValue()) && value.bound != Value::Bound::Point;
};
if (skipUnsignedRangeBounds && (hasRangeBound(value1) || hasRangeBound(value2)))

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

When isUnsignedArithmeticNoWrap cannot prove that the operation does not wrap, all range bounds are dropped. That check misses simple cases, so correct diagnostics from base are lost:

void f6(unsigned char c) { unsigned s = c; if (s + 1u == 0u) use(4); }
void f4(unsigned long n) { for (unsigned long i = 0; i < n; i++) { if (i + 1 == 0) use(3); } }

Base reports Condition 's+1u==0u' is always false and Condition 'i+1==0' is always false; the PR reports neither. Please add tests that check that such existing diagnostics are kept.

@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

New uninitvar false positive: with the PR, an ordinary float or long double parameter is reported as uninitialized after a >=/<= comparison with a literal. The cause is the new float branch in SimpleConditionHandler::parse (lib/valueflow.cpp:5120). When the type is not exactly double, it uses ValueFlow::Value::unknown() as the false-branch value. That value has valueType == UNINIT. It is then marked possible, given the condition, and propagated into the else path, where CheckUninitVar reports it. Base gives no warning. float is common, so this would cause many FPs.

The earlier reviews did not raise this. Apart from it, I found no new problems in the time I had. The earlier review covered the containerOutOfBounds, negativeIndex and no-wrap issues. The current version adds a check of the loop condition in isNonnegativeInductionVariable.

Testing

I built cppcheck and testrunner in pr/, and cppcheck in base/. These suites all pass (0 failures): TestCondition, TestBufferOverrun, TestStl, TestType, TestOther, TestValueFlow, TestFunctions, TestUninitVar and TestNullPointer.

uninitvar FP (new in the PR)

fl.c:

float f1(float x) { if (x >= 1.0f) return 1; return x; }
float f5(float x) { if (x >= 1.0f) return 1; if (x < 1.0f) return 2; return x; }
long double g1(long double x) { if (x <= 2.0L) return 1; return x; }
float g2(float x) { if (1.0f <= x) return 1; return x * 2; }

Command: cppcheck -q --enable=style,warning --platform=unix64 fl.c

Output with the PR:

fl.c:1:53: warning: Uninitialized variable: x [uninitvar]
fl.c:1:27: note: Assuming that condition 'x>=1.0f' is not redundant
...
fl.c:3:65: warning: Uninitialized variable: x [uninitvar]
fl.c:3:39: note: Assuming that condition 'x<=2.0L' is not redundant
fl.c:4:53: warning: Uninitialized variable: x [uninitvar]
fl.c:4:30: note: Assuming that condition '1.0f<=x' is not redundant

Base gives no warning for any of these. The same code with double gives no warning, because there the nextafter branch is taken.

Other checks

  • Casts compared with out-of-range values ((int32_t)x - 1 == -2147483649LL, (int8_t)x < -128, (_Bool)x > 1) give the same results as base.
  • (uint8_t)x + 1 == 256 is not reported, which is correct.

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

Comment thread lib/valueflow.cpp
ValueType::Accuracy::ExactOrZero,
ValueType::SizeOf::Pointer) : 0;
if (typeSize != static_cast<int>(sizeof(double))) {
result = ValueFlow::Value::unknown();

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

ValueFlow::Value::unknown() has valueType == UNINIT. The code below then gives it a bound, assumeCondition and setPossible, and it is propagated as a possible uninitialized value into the false branch. So with the PR, float f(float x) { if (x >= 1.0f) return 1; return x; } gives warning: Uninitialized variable: x [uninitvar]. The same happens for long double with <=, and for 1.0f <= x. Base gives no warning. When the strict bound cannot be computed, skip the false value (or do not create the condition at all) instead of inserting a placeholder value. A test with float would catch this.

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.

6 participants