From 21b26254535748f78fd376b20685526e73715448 Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Mon, 9 Feb 2026 16:54:18 +0100 Subject: [PATCH 1/5] Do not warn for truncLongCastReturn if operands have known valid int value --- lib/checktype.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/checktype.cpp b/lib/checktype.cpp index 68b6580d270..025c14b4620 100644 --- a/lib/checktype.cpp +++ b/lib/checktype.cpp @@ -383,7 +383,8 @@ void CheckType::checkLongCast() const ValueType *type = tok->astOperand1()->valueType(); if (type && checkTypeCombination(*type, *retVt, *mSettings) && type->pointer == 0U && - type->originalTypeName.empty()) + type->originalTypeName.empty() && + !tok->astOperand1()->hasKnownIntValue()) ret = tok; } // All return statements must have problem otherwise no warning From fe0381f7f55a1186217e5d08bf3c813bb1143d6e Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Mon, 16 Feb 2026 15:28:41 +0100 Subject: [PATCH 2/5] Added test case with and without long cast warning --- test/testtype.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/testtype.cpp b/test/testtype.cpp index b8cfae820d5..8873956217b 100644 --- a/test/testtype.cpp +++ b/test/testtype.cpp @@ -465,6 +465,19 @@ class TestType : public TestFixture { check(code2, dinit(CheckOptions, $.settings = &settingsWin)); ASSERT_EQUALS("[test.cpp:2:3]: (style) int result is returned as long long value. If the return value is long long to avoid loss of information, then you have loss of information. [truncLongCastReturn]\n", errout_str()); + const char code3[] = "long f() {\n" + " int n = 1;\n" + " return n << 12;\n" + "}\n"; + check(code3, dinit(CheckOptions, $.settings = &settings)); + ASSERT_EQUALS("", errout_str()); + + const char code4[] = "long f(int n) {\n" + " return n << 12;\n" + "}\n"; + check(code4, dinit(CheckOptions, $.settings = &settings)); + ASSERT_EQUALS("[test.cpp:2:5]: (style) int result is returned as long value. If the return value is long to avoid loss of information, then you have loss of information. [truncLongCastReturn]\n", errout_str()); + // typedef check("size_t f(int x, int y) {\n" " return x * y;\n" From 920f386aaf97f01594af3e677daab5aff0cfeceb Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Wed, 18 Feb 2026 14:14:10 +0100 Subject: [PATCH 3/5] Extended checkLongCast function with expression max min check and added astutils functions for getting expression max min values and a check if bigint within int range. --- lib/astutils.cpp | 134 ++++++++++++++++++++++++++++++++++++++++++++++ lib/astutils.h | 4 ++ lib/checktype.cpp | 20 ++++++- test/testtype.cpp | 32 +++++++---- 4 files changed, 178 insertions(+), 12 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index e140d965114..d798093b0da 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -41,12 +41,15 @@ #include #include #include +#include #include #include #include #include #include +#define INTEGER_SHIFT_LIMIT (sizeof(int) * 8 - 1) // The number of bits, where a left shift cannot be guaranteed to be within int range. + const Token* findExpression(const nonneg int exprid, const Token* start, const Token* end, @@ -926,6 +929,137 @@ const Token* getCondTokFromEnd(const Token* endBlock) return getCondTokFromEndImpl(endBlock); } +static std::pair getIntegralMinMaxValues(int bits, ValueType::Sign sign) +{ + using bigint = MathLib::bigint; + using biguint = MathLib::biguint; + + if (bits <= 0) + return { bigint(0), bigint(0) }; + + // Unsigned: [0, 2^bits - 1] + if (sign == ValueType::Sign::UNSIGNED) { + // If bits exceed what MathLib can safely shift, saturate to max representable + if (bits >= MathLib::bigint_bits) { + biguint max_u = std::numeric_limits::max(); + return { bigint(0), bigint(max_u) }; + } + biguint max_u = (biguint(1) << bits) - 1; + return { bigint(0), bigint(max_u) }; + } + + // Signed: [-(2^(bits-1)), 2^(bits-1)-1] + if (bits >= MathLib::bigint_bits) { + bigint min_s = std::numeric_limits::min(); + bigint max_s = std::numeric_limits::max(); + return { min_s, max_s }; + } + bigint max_s = (bigint(1) << (bits - 1)) - 1; + bigint min_s = -(bigint(1) << (bits - 1)); + return { min_s, max_s }; +} + +static bool getIntegralTypeRange(const ValueType* type, const Settings& settings, std::pair& range) +{ + if (!type || !type->isIntegral()) + return false; + + const int bits = type->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer) * 8; + if (bits <= 0 || bits > 64) + return false; + + range = getIntegralMinMaxValues(bits, type->sign); + + return true; +} + +bool getExpressionResultRange(const Token* expr, const Settings& settings, std::pair& exprRange) +{ + if (!expr) + return false; + + // Early return for known values + if (expr->hasKnownIntValue()) { + exprRange = { expr->getKnownIntValue(), expr->getKnownIntValue() }; + return true; + } + + // Early return for non-integral expressions + if (!expr->valueType() || !expr->valueType()->isIntegral()) + return false; + + //Check binary op - bitwise and + if (expr->isBinaryOp() && expr->str() == "&") { + std::pair leftRange, rightRange; + if (getExpressionResultRange(expr->astOperand1(), settings, leftRange) && + getExpressionResultRange(expr->astOperand2(), settings, rightRange)) { + + if (leftRange.second >= INT64_MAX || rightRange.second >= INT64_MAX) + // Abort for values larger than INT64_MAX since bigint do not handle them well + return false; + + exprRange.first = leftRange.first & rightRange.first; + exprRange.second = leftRange.second & rightRange.second; + + // Return false if negative values after bitwise & + return !(exprRange.first < 0 || exprRange.second < 0); + } + } + + // Find original type before casts + const Token* exprToCheck = expr; + while (exprToCheck->isCast()) { + const Token* castFrom = exprToCheck->astOperand2() ? exprToCheck->astOperand2() : exprToCheck->astOperand1(); + if (!castFrom || !castFrom->valueType() || !castFrom->valueType()->isIntegral()) + break; + if (castFrom->valueType()->pointer >= 1) + break; + if (castFrom->valueType()->type >= exprToCheck->valueType()->type && + castFrom->valueType()->sign == ValueType::Sign::SIGNED) + break; + exprToCheck = castFrom; + } + + return getIntegralTypeRange(exprToCheck->valueType(), settings, exprRange); +} + +template +static bool checkAllRangeOperations(const std::pair& left, + const std::pair& right, + const Settings& settings, + Op operation) +{ + return settings.platform.isIntValue(operation(left.first, right.first)) && + settings.platform.isIntValue(operation(left.first, right.second)) && + settings.platform.isIntValue(operation(left.second, right.first)) && + settings.platform.isIntValue(operation(left.second, right.second)); +} + +bool isOperationResultWithinIntRange(const Token* op, const Settings& settings, std::pair* leftRange, std::pair* rightRange) +{ + if (!op || !leftRange || !rightRange) + return false; + + if (op->str() == "<<") { + // If the lefthand operand is 31 or higher the resulting shift will be a negative value or greater than int range. + if ((rightRange->first >= INTEGER_SHIFT_LIMIT) || rightRange->second >= INTEGER_SHIFT_LIMIT) + return false; + + return checkAllRangeOperations(*leftRange, *rightRange, settings, + [](auto a, auto b) { + return a << b; + }); + } + + if (op->str() == "*") + return checkAllRangeOperations(*leftRange, *rightRange, settings, + [](auto a, auto b) { + return a * b; + }); + + return false; +} + Token* getInitTok(Token* tok) { return getInitTokImpl(tok); } diff --git a/lib/astutils.h b/lib/astutils.h index 4002e065ab5..54de1944d1b 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -225,6 +225,10 @@ const Token* getStepTok(const Token* tok); Token* getCondTokFromEnd(Token* endBlock); const Token* getCondTokFromEnd(const Token* endBlock); +bool getExpressionResultRange(const Token* expr, const Settings& settings, std::pair& exprRange); +bool isOperationResultWithinIntRange(const Token* op, const Settings& settings, std::pair* leftRange, std::pair* rightRange); + + /// For a "break" token, locate the next token to execute. The token will /// be either a "}" or a ";". const Token *findNextTokenFromBreak(const Token *breakToken); diff --git a/lib/checktype.cpp b/lib/checktype.cpp index 025c14b4620..014c958aba8 100644 --- a/lib/checktype.cpp +++ b/lib/checktype.cpp @@ -384,8 +384,24 @@ void CheckType::checkLongCast() if (type && checkTypeCombination(*type, *retVt, *mSettings) && type->pointer == 0U && type->originalTypeName.empty() && - !tok->astOperand1()->hasKnownIntValue()) - ret = tok; + !tok->astOperand1()->hasKnownIntValue()) { + std::pair opRange1, opRange2; + if (!getExpressionResultRange(tok->astOperand1()->astOperand1(), *mSettings, opRange1) || !getExpressionResultRange(tok->astOperand1()->astOperand2(), *mSettings, opRange2)) { + ret = tok; + break; + } + + if (!mSettings->platform.isIntValue(opRange1.first) || !mSettings->platform.isIntValue(opRange1.second) || + !mSettings->platform.isIntValue(opRange2.first) || !mSettings->platform.isIntValue(opRange2.second)) { + ret = tok; + break; + } + + if (!isOperationResultWithinIntRange(tok->astOperand1(), *mSettings, &opRange1, &opRange2)) { + ret = tok; + break; + } + } } // All return statements must have problem otherwise no warning if (ret != tok) { diff --git a/test/testtype.cpp b/test/testtype.cpp index 8873956217b..556a1229198 100644 --- a/test/testtype.cpp +++ b/test/testtype.cpp @@ -465,19 +465,31 @@ class TestType : public TestFixture { check(code2, dinit(CheckOptions, $.settings = &settingsWin)); ASSERT_EQUALS("[test.cpp:2:3]: (style) int result is returned as long long value. If the return value is long long to avoid loss of information, then you have loss of information. [truncLongCastReturn]\n", errout_str()); - const char code3[] = "long f() {\n" - " int n = 1;\n" - " return n << 12;\n" - "}\n"; - check(code3, dinit(CheckOptions, $.settings = &settings)); + const char code3a[] = "long f() {\n" + " int n = 1;\n" + " return n << 12;\n" + "}\n"; + check(code3a, dinit(CheckOptions, $.settings = &settings)); ASSERT_EQUALS("", errout_str()); - - const char code4[] = "long f(int n) {\n" - " return n << 12;\n" - "}\n"; - check(code4, dinit(CheckOptions, $.settings = &settings)); + const char code3b[] = "long f(int n) {\n" + " return n << 12;\n" + "}\n"; + check(code3b, dinit(CheckOptions, $.settings = &settings)); ASSERT_EQUALS("[test.cpp:2:5]: (style) int result is returned as long value. If the return value is long to avoid loss of information, then you have loss of information. [truncLongCastReturn]\n", errout_str()); + const char code4a[] = "long f(int x) {\n" + " int y = 0x07FFFF;\n" + " return ((x & y) << (12 & x));\n" + "}\n"; + check(code4a, dinit(CheckOptions, $.settings = &settings)); + ASSERT_EQUALS("", errout_str()); + const char code4b[] = "long f(int x) {\n" + " int y = 0x080000;\n" + " return ((x & y) << (12 & x));\n" + "}\n"; + check(code4b, dinit(CheckOptions, $.settings = &settings)); + ASSERT_EQUALS("[test.cpp:3:5]: (style) int result is returned as long value. If the return value is long to avoid loss of information, then you have loss of information. [truncLongCastReturn]\n", errout_str()); + // typedef check("size_t f(int x, int y) {\n" " return x * y;\n" From 884a95d4c9d3085d54ad75de985543383ffc1651 Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Fri, 20 Feb 2026 10:25:35 +0100 Subject: [PATCH 4/5] Modified check that hasKnownInt is within integer range --- lib/checktype.cpp | 21 ++++++++------------- test/testtype.cpp | 7 +++++++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/checktype.cpp b/lib/checktype.cpp index 014c958aba8..159cda51b93 100644 --- a/lib/checktype.cpp +++ b/lib/checktype.cpp @@ -383,23 +383,18 @@ void CheckType::checkLongCast() const ValueType *type = tok->astOperand1()->valueType(); if (type && checkTypeCombination(*type, *retVt, *mSettings) && type->pointer == 0U && - type->originalTypeName.empty() && - !tok->astOperand1()->hasKnownIntValue()) { + type->originalTypeName.empty()) { std::pair opRange1, opRange2; - if (!getExpressionResultRange(tok->astOperand1()->astOperand1(), *mSettings, opRange1) || !getExpressionResultRange(tok->astOperand1()->astOperand2(), *mSettings, opRange2)) { + if (tok->astOperand1()->hasKnownIntValue()) { + if (!mSettings->platform.isIntValue(tok->astOperand1()->getKnownIntValue())) + ret = tok; + } else if (!getExpressionResultRange(tok->astOperand1()->astOperand1(), *mSettings, opRange1) || !getExpressionResultRange(tok->astOperand1()->astOperand2(), *mSettings, opRange2)) { ret = tok; - break; - } - - if (!mSettings->platform.isIntValue(opRange1.first) || !mSettings->platform.isIntValue(opRange1.second) || - !mSettings->platform.isIntValue(opRange2.first) || !mSettings->platform.isIntValue(opRange2.second)) { + } else if (!mSettings->platform.isIntValue(opRange1.first) || !mSettings->platform.isIntValue(opRange1.second) || + !mSettings->platform.isIntValue(opRange2.first) || !mSettings->platform.isIntValue(opRange2.second)) { ret = tok; - break; - } - - if (!isOperationResultWithinIntRange(tok->astOperand1(), *mSettings, &opRange1, &opRange2)) { + } else if (!isOperationResultWithinIntRange(tok->astOperand1(), *mSettings, &opRange1, &opRange2)) { ret = tok; - break; } } } diff --git a/test/testtype.cpp b/test/testtype.cpp index 556a1229198..eadb56dedac 100644 --- a/test/testtype.cpp +++ b/test/testtype.cpp @@ -477,6 +477,13 @@ class TestType : public TestFixture { check(code3b, dinit(CheckOptions, $.settings = &settings)); ASSERT_EQUALS("[test.cpp:2:5]: (style) int result is returned as long value. If the return value is long to avoid loss of information, then you have loss of information. [truncLongCastReturn]\n", errout_str()); + const char code3c[] = "long f() {\n" + " unsigned int n = 1U << 20;\n" + " return n << 20;\n" + "}\n"; + check(code3c, dinit(CheckOptions, $.settings = &settings)); + ASSERT_EQUALS("[test.cpp:3:5]: (style) int result is returned as long value. If the return value is long to avoid loss of information, then you have loss of information. [truncLongCastReturn]\n", errout_str()); + const char code4a[] = "long f(int x) {\n" " int y = 0x07FFFF;\n" " return ((x & y) << (12 & x));\n" From 7b90541a6620b068f5261a58d70b8a7397ea35d2 Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Fri, 20 Feb 2026 14:54:23 +0100 Subject: [PATCH 5/5] fix: no lambda with auto --- lib/astutils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index d798093b0da..6ee3e32ad2e 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1046,14 +1046,14 @@ bool isOperationResultWithinIntRange(const Token* op, const Settings& settings, return false; return checkAllRangeOperations(*leftRange, *rightRange, settings, - [](auto a, auto b) { + [](MathLib::bigint a, MathLib::bigint b) { return a << b; }); } if (op->str() == "*") return checkAllRangeOperations(*leftRange, *rightRange, settings, - [](auto a, auto b) { + [](MathLib::bigint a, MathLib::bigint b) { return a * b; });