From 3e3fa632fd590f774bfa2b0a2318f93aaac1c9c5 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 CheckCondition: detect compareValueOutOfTypeRange for bounded cast/arithmetic expressions --- lib/checkcondition.cpp | 194 +++++++++++++++++++++++++++++++++++++++++ test/testcondition.cpp | 39 +++++++++ 2 files changed, 233 insertions(+) diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index 4776cbea38f..a35f9e0e2ff 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -1972,6 +1972,180 @@ void CheckConditionImpl::assignmentInCondition(const Token *eq) Certainty::normal); } +static bool getIntegerTypeRange(const Token* tok, + const Settings& settings, + MathLib::bigint& lower, + MathLib::bigint& upper) +{ + if (!tok || !tok->valueType() || tok->valueType()->pointer) + return false; + + std::uint8_t bits = 0; + switch (tok->valueType()->type) { + case ValueType::Type::BOOL: + bits = 1; + break; + case ValueType::Type::CHAR: + bits = settings.platform.char_bit; + break; + case ValueType::Type::SHORT: + bits = settings.platform.short_bit; + break; + case ValueType::Type::INT: + bits = settings.platform.int_bit; + break; + case ValueType::Type::LONG: + bits = settings.platform.long_bit; + break; + case ValueType::Type::LONGLONG: + bits = settings.platform.long_long_bit; + break; + default: + return false; + } + if (bits == 0 || bits > 64) + return false; + + if (bits == 64 && tok->valueType()->sign == ValueType::Sign::UNSIGNED) + return false; + const MathLib::bigint max = bits == 64 ? std::numeric_limits::max() : (MathLib::bigint(1) << bits) - 1; + if (tok->valueType()->sign == ValueType::Sign::SIGNED) { + if (bits == 64) { + lower = std::numeric_limits::min(); + upper = std::numeric_limits::max(); + } else { + lower = -(MathLib::bigint(1) << (bits - 1)); + upper = max / 2; + } + } else { + lower = 0; + upper = max; + } + return true; +} + +static bool getIntegerExpressionRange(const Token* tok, + const Settings& settings, + MathLib::bigint& lower, + MathLib::bigint& upper) +{ + if (!tok) + return false; + if (tok->hasKnownIntValue()) { + lower = upper = tok->getKnownIntValue(); + return true; + } + + if (tok->isCast() && tok->astOperand1()) { + MathLib::bigint typeLower; + MathLib::bigint typeUpper; + const bool hasSourceRange = getIntegerExpressionRange(tok->astOperand1(), settings, lower, upper); + const bool hasTypeRange = getIntegerTypeRange(tok, settings, typeLower, typeUpper); + if (!hasSourceRange && !hasTypeRange) + return false; + if (!hasSourceRange) { + lower = typeLower; + upper = typeUpper; + return true; + } + if (!hasTypeRange) + return true; + lower = std::max(lower, typeLower); + upper = std::min(upper, typeUpper); + return lower <= upper; + } + + if (Token::simpleMatch(tok, "(") && tok->astOperand1()) + return getIntegerExpressionRange(tok->astOperand1(), settings, lower, upper); + + if (Token::Match(tok, "+|-")) { + MathLib::bigint operandLower; + MathLib::bigint operandUpper; + MathLib::bigint constantLower; + MathLib::bigint constantUpper; + if (!getIntegerExpressionRange(tok->astOperand1(), settings, operandLower, operandUpper) || + !getIntegerExpressionRange(tok->astOperand2(), settings, constantLower, constantUpper) || + constantLower != constantUpper) + return false; + const MathLib::bigint minimum = std::numeric_limits::min(); + const MathLib::bigint maximum = std::numeric_limits::max(); + if (tok->str() == "+" && + ((constantLower > 0 && operandUpper > maximum - constantLower) || + (constantLower < 0 && operandLower < minimum - constantLower))) + return false; + if (tok->str() == "-" && + ((constantUpper > 0 && operandLower < minimum + constantUpper) || + (constantUpper < 0 && operandUpper > maximum + constantUpper))) + return false; + if (tok->str() == "+") { + lower = operandLower + constantLower; + upper = operandUpper + constantUpper; + } else { + lower = operandLower - constantUpper; + upper = operandUpper - constantLower; + } + return true; + } + + return getIntegerTypeRange(tok, settings, lower, upper); +} + +static bool compareIntegerRange(const Token* comparison, + MathLib::bigint lower, + MathLib::bigint upper, + MathLib::bigint value, + bool& result) +{ + if (!comparison || !comparison->isComparisonOp()) + return false; + if (comparison->str() == "<") { + if (upper < value) + result = true; + else if (lower >= value) + result = false; + else + return false; + } else if (comparison->str() == "<=") { + if (upper <= value) + result = true; + else if (lower > value) + result = false; + else + return false; + } else if (comparison->str() == ">") { + if (lower > value) + result = true; + else if (upper <= value) + result = false; + else + return false; + } else if (comparison->str() == ">=") { + if (lower >= value) + result = true; + else if (upper < value) + result = false; + else + return false; + } else if (comparison->str() == "==") { + if (lower == upper) + result = (lower == value); + else if (value < lower || value > upper) + result = false; + else + return false; + } else if (comparison->str() == "!=") { + if (lower == upper) + result = (lower != value); + else if (value < lower || value > upper) + result = true; + else + return false; + } else { + return false; + } + return true; +} + void CheckConditionImpl::checkCompareValueOutOfTypeRange() { if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("compareValueOutOfTypeRangeError")) @@ -1988,6 +2162,26 @@ void CheckConditionImpl::checkCompareValueOutOfTypeRange() if (!tok->isComparisonOp() || !tok->isBinaryOp()) continue; + for (int i = 0; i < 2; ++i) { + const Token* const expressionTok = (i == 0) ? tok->astOperand2() : tok->astOperand1(); + const Token* const valueTok = (i == 0) ? tok->astOperand1() : tok->astOperand2(); + if (!expressionTok || !valueTok || !valueTok->hasKnownIntValue() || expressionTok->hasKnownIntValue()) + continue; + if (expressionTok->str() != "(" && !expressionTok->isCast() && !expressionTok->isArithmeticalOp()) + continue; + MathLib::bigint lower; + MathLib::bigint upper; + if (!getIntegerExpressionRange(expressionTok, mSettings, lower, upper)) + continue; + bool result; + if (!compareIntegerRange(tok, lower, upper, valueTok->getKnownIntValue(), result) || diag(tok)) + continue; + compareValueOutOfTypeRangeError(valueTok, + expressionTok->valueType() ? expressionTok->valueType()->str() : "", + valueTok->getKnownIntValue(), + result); + } + for (int i = 0; i < 2; ++i) { const Token * const valueTok = (i == 0) ? tok->astOperand1() : tok->astOperand2(); const Token * const typeTok = valueTok->astSibling(); diff --git a/test/testcondition.cpp b/test/testcondition.cpp index 6d62b644043..4bb349dd20b 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -6546,6 +6546,45 @@ 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:43]: (style) Comparing expression of type 'signed long long' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\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()); + check("void f() {\n" " long long ll = 1024 * 1024 * 1024;\n" " if (ll * 8 < INT_MAX) {}\n"