From a8d48d37f34c35726d003fc76dc1a3d5421d1534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Deverge?= Date: Fri, 21 Aug 2026 10:41:19 +0200 Subject: [PATCH] Fix #15000: Propagate integral cast ranges to condition analysis --- lib/valueflow.cpp | 18 +++++++++++ lib/vf_common.cpp | 18 ++++++++--- test/testcondition.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++ test/testvalueflow.cpp | 16 +++++++++ 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 325a55e98e5..8eb9b2a1c54 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -1087,6 +1087,24 @@ static void valueFlowImpossibleValues(TokenList& tokenList, const Settings& sett upper.bound = ValueFlow::Value::Bound::Lower; upper.setImpossible(); setTokenValue(tok, std::move(upper), settings); + } else if (tok->isCast() && tok->valueType() && tok->valueType()->isIntegral() && !tok->valueType()->pointer) { + MathLib::bigint minValue; + MathLib::bigint maxValue; + if (!ValueFlow::getMinMaxValues(tok->valueType(), settings.platform, minValue, maxValue)) + continue; + + if (minValue > std::numeric_limits::min()) { + ValueFlow::Value lower{minValue - 1}; + lower.bound = ValueFlow::Value::Bound::Upper; + lower.setImpossible(); + setTokenValue(tok, std::move(lower), settings); + } + if (maxValue < std::numeric_limits::max()) { + ValueFlow::Value upper{maxValue + 1}; + upper.bound = ValueFlow::Value::Bound::Lower; + upper.setImpossible(); + setTokenValue(tok, std::move(upper), settings); + } } else if (astIsUnsigned(tok) && !astIsPointer(tok)) { std::vector minvalue = minUnsignedValue(tok); if (minvalue.empty()) diff --git a/lib/vf_common.cpp b/lib/vf_common.cpp index f9eecc20fb9..72c9515dee8 100644 --- a/lib/vf_common.cpp +++ b/lib/vf_common.cpp @@ -46,7 +46,7 @@ namespace ValueFlow if (!vt || !vt->isIntegral() || vt->pointer) return false; - std::uint8_t bits; + std::size_t bits; switch (vt->type) { case ValueType::Type::BOOL: bits = 1; @@ -66,10 +66,16 @@ namespace ValueFlow case ValueType::Type::LONGLONG: bits = platform.long_long_bit; break; + case ValueType::Type::WCHAR_T: + bits = platform.sizeof_wchar_t * platform.char_bit; + break; default: return false; } + if (bits == 0) { + return false; + } if (bits == 1) { minValue = 0; maxValue = 1; @@ -77,18 +83,20 @@ namespace ValueFlow if (vt->sign == ValueType::Sign::UNSIGNED) { minValue = 0; maxValue = (1LL << bits) - 1; - } else { + } else if (vt->sign == ValueType::Sign::SIGNED) { minValue = -(1LL << (bits - 1)); maxValue = (1LL << (bits - 1)) - 1; - } + } else + return false; } else if (bits == 64) { if (vt->sign == ValueType::Sign::UNSIGNED) { minValue = 0; maxValue = LLONG_MAX; // todo max unsigned value - } else { + } else if (vt->sign == ValueType::Sign::SIGNED) { minValue = LLONG_MIN; maxValue = LLONG_MAX; - } + } else + return false; } else { return false; } diff --git a/test/testcondition.cpp b/test/testcondition.cpp index 6d62b644043..2e7d0927d39 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -6546,6 +6546,79 @@ class TestCondition : public TestFixture { "[test.cpp:4:13]: (style) Comparing expression of type 'const unsigned int &' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", errout_str()); + check("typedef unsigned int uint32;\n" + "typedef unsigned long long uint64;\n" + "typedef long long sint64;\n" + "void f(uint32 x) {\n" + " uint64 tmp = ((uint64)x) + 1ULL;\n" + " if (tmp > 4294967295ULL)\n" + " tmp = 4294967295ULL;\n" + " if ((((sint64)((uint32)tmp)) - 1LL) < 0LL) {}\n" + " if ((((sint64)((uint32)tmp)) - 1LL) > 4294967295LL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:9:41]: (style) Condition '(((long long)((unsigned int)tmp))-1LL)>4294967295LL' is always false [knownConditionTrueFalse]\n", + errout_str()); + + check("typedef unsigned int uint32;\n" + "typedef unsigned long long uint64;\n" + "typedef long long sint64;\n" + "void f(uint64 tmp) {\n" + " if (tmp > 4294967295ULL)\n" + " tmp = 4294967295ULL;\n" + " if (static_cast(static_cast(tmp)) - 1LL > 4294967295LL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:7:61]: (style) Condition 'static_cast(static_cast(tmp))-1LL>4294967295LL' is always false [knownConditionTrueFalse]\n", + errout_str()); + + // cast directly around variable: both the range-based and the declared-type + // analysis can prove the condition invariant; diag() must prevent duplicates + check("void f(unsigned char c) {\n" + " if ((unsigned char)c > 255) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:28]: (style) Comparing expression of type 'unsigned char' against value 255. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + check("void f(unsigned char c) {\n" + " if ((unsigned char)c == 256) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:29]: (style) Comparing expression of type 'unsigned char' against value 256. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + check("void f(unsigned int u) {\n" + " if ((unsigned int)u > 4294967295ULL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:27]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + check("void f(unsigned short s) {\n" + " if ((unsigned int)s > 4294967295ULL) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:27]: (style) Comparing expression of type 'unsigned int' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + // wchar_t range is derived through ValueType::getSizeOf() + check("void f(wchar_t c) {\n" + " if ((wchar_t)c > 0x7fffffff) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:20]: (style) Condition '(wchar_t)c>0x7fffffff' is always false [knownConditionTrueFalse]\n", + errout_str()); + + check("void f(unsigned int x) {\n" + " if (-(signed char)x < -129) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:25]: (style) Condition '-(char)x<-129' is always false [knownConditionTrueFalse]\n", + errout_str()); + + check("void f(int x) {\n" + " if ((x) > 0) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + + check("void f(unsigned int x) {\n" + " if ((unsigned int)x > 0) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("", errout_str()); + check("void f() {\n" " long long ll = 1024 * 1024 * 1024;\n" " if (ll * 8 < INT_MAX) {}\n" diff --git a/test/testvalueflow.cpp b/test/testvalueflow.cpp index 56bd0e26ce2..9cf4dd11930 100644 --- a/test/testvalueflow.cpp +++ b/test/testvalueflow.cpp @@ -9572,6 +9572,22 @@ class TestValueFlow : public TestFixture { "}\n"; ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, "a", -1)); ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, -1)); + + const Settings settingsUnix64 = settingsBuilder().platform(Platform::Type::Unix64).build(); + code = "void f(unsigned long long x) {\n" + " return (unsigned int)x;\n" + "}\n"; + SimpleTokenizer tokenizer(settingsUnix64, *this); + ASSERT(tokenizer.tokenize(code)); + const Token* returnTok = Token::findmatch(tokenizer.tokens(), "return ("); + ASSERT(returnTok && returnTok->next()); + const std::list& castValues = returnTok->next()->values(); + ASSERT(std::any_of(castValues.cbegin(), castValues.cend(), [](const ValueFlow::Value& value) { + return value.isImpossible() && value.intvalue == -1; + })); + ASSERT(std::any_of(castValues.cbegin(), castValues.cend(), [](const ValueFlow::Value& value) { + return value.isImpossible() && value.intvalue == 4294967296; + })); } void valueFlowImpossibleIncDec()