Skip to content

Fix #15000 (CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions) - #8819

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

Fix #15000 (CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions)#8819
jfdeverge wants to merge 1 commit into
cppcheck-opensource:mainfrom
jfdeverge:dev/fix_misra_14_3

Conversation

@jfdeverge

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.

Fix

The condition checker now:

  • calculates ranges for supported scalar integer types;
  • recursively evaluates integer casts, transparent parentheses, and addition
    or subtraction by constant expressions;
  • evaluates comparisons against the resulting interval when the result is
    provably always true or always false; and
  • retains the existing compareValueOutOfTypeRangeError diagnostic path.

Uncertain or unsupported ranges remain unreported to avoid speculative
findings. Interval arithmetic includes overflow guards.

Regression Coverage

The exact typedef-based reproducer is covered by
TestCondition::compareOutOfTypeRange in test/testcondition.cpp. The focused
test, the complete TestCondition suite, and the registered CTest target pass
with the fix.

Comment thread lib/checkcondition.cpp Fixed
Comment thread test/testcondition.cpp
" 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?

@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

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

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
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.

Comment thread lib/checkcondition.cpp
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.

Comment thread lib/checkcondition.cpp
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 +|- ?

Comment thread test/testcondition.cpp
" 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
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.

Comment thread lib/checkcondition.cpp
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?

Comment thread lib/checkcondition.cpp
}

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.

Comment thread lib/checkcondition.cpp
{
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.

@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?

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.

5 participants