From 03e9408bcbc902f52bb039f11497c94b644bc29d Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Wed, 18 Feb 2026 14:14:10 +0100 Subject: [PATCH 1/7] 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 | 12 ++++- test/testtype.cpp | 42 +++++++++------ 4 files changed, 175 insertions(+), 17 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 2330a9d2b94..fc254dd53a6 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 509d13b54e0..159cda51b93 100644 --- a/lib/checktype.cpp +++ b/lib/checktype.cpp @@ -384,10 +384,18 @@ void CheckType::checkLongCast() if (type && checkTypeCombination(*type, *retVt, *mSettings) && type->pointer == 0U && type->originalTypeName.empty()) { - if (!tok->astOperand1()->hasKnownIntValue()) { + std::pair opRange1, 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; - } else if (!mSettings->platform.isIntValue(tok->astOperand1()->getKnownIntValue())) + } else if (!mSettings->platform.isIntValue(opRange1.first) || !mSettings->platform.isIntValue(opRange1.second) || + !mSettings->platform.isIntValue(opRange2.first) || !mSettings->platform.isIntValue(opRange2.second)) { ret = tok; + } else if (!isOperationResultWithinIntRange(tok->astOperand1(), *mSettings, &opRange1, &opRange2)) { + ret = tok; + } } } // All return statements must have problem otherwise no warning diff --git a/test/testtype.cpp b/test/testtype.cpp index ea31fbdc313..eadb56dedac 100644 --- a/test/testtype.cpp +++ b/test/testtype.cpp @@ -465,24 +465,36 @@ 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 code5[] = "long f() {\n" - " unsigned int n = 1U << 20;\n" - " return n << 20;\n" - "}\n"; - check(code5, dinit(CheckOptions, $.settings = &settings)); + 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" + "}\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 From defc2b916553906acd3aabc6d076966d7964e097 Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Fri, 20 Feb 2026 14:54:23 +0100 Subject: [PATCH 2/7] 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 fc254dd53a6..b4b91b77f77 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; }); From b82637bb37b9f8b05f3d5831c97661f52bd1588f Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Mon, 2 Mar 2026 10:45:36 +0100 Subject: [PATCH 3/7] Added parantheses to clarify order --- lib/astutils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index b4b91b77f77..059429a01fe 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -48,7 +48,7 @@ #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. +#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, From 2369925963af857156b6ecc9162e81bdb8a3b0f4 Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Mon, 2 Mar 2026 15:17:03 +0100 Subject: [PATCH 4/7] Fix findings --- lib/astutils.cpp | 51 ++++++------------------------------------------ 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 059429a01fe..4c55fc07e54 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -32,6 +32,7 @@ #include "utils.h" #include "valueflow.h" #include "valueptr.h" +#include "vf_common.h" #include "vfvalue.h" #include "checkclass.h" @@ -929,50 +930,6 @@ 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) @@ -1020,7 +977,7 @@ bool getExpressionResultRange(const Token* expr, const Settings& settings, std:: exprToCheck = castFrom; } - return getIntegralTypeRange(exprToCheck->valueType(), settings, exprRange); + return ValueFlow::getMinMaxValues(exprToCheck->valueType(), settings.platform, exprRange.first, exprRange.second); } template @@ -1045,6 +1002,10 @@ bool isOperationResultWithinIntRange(const Token* op, const Settings& settings, if ((rightRange->first >= INTEGER_SHIFT_LIMIT) || rightRange->second >= INTEGER_SHIFT_LIMIT) return false; + // Leftshift with negative values is undefined behavior. + if ((rightRange->first < 0) || rightRange->second < 0) + return false; + return checkAllRangeOperations(*leftRange, *rightRange, settings, [](MathLib::bigint a, MathLib::bigint b) { return a << b; From b786c080528afd3aba55786f9f880b73911fa33e Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Mon, 2 Mar 2026 16:19:08 +0100 Subject: [PATCH 5/7] updated makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 37156d357af..7cac9fc3efb 100644 --- a/Makefile +++ b/Makefile @@ -487,7 +487,7 @@ $(libcppdir)/addoninfo.o: lib/addoninfo.cpp externals/picojson/picojson.h lib/ad $(libcppdir)/analyzerinfo.o: lib/analyzerinfo.cpp externals/tinyxml2/tinyxml2.h lib/analyzerinfo.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/mathlib.h lib/path.h lib/platform.h lib/standards.h lib/utils.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/analyzerinfo.cpp -$(libcppdir)/astutils.o: lib/astutils.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkclass.h lib/checkers.h lib/config.h lib/errortypes.h lib/findtoken.h lib/infer.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vfvalue.h +$(libcppdir)/astutils.o: lib/astutils.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkclass.h lib/checkers.h lib/config.h lib/errortypes.h lib/findtoken.h lib/infer.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vf_common.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/astutils.cpp $(libcppdir)/check.o: lib/check.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h From b856407ec6557d95f26481bd8f568fcbcd03a429 Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Mon, 2 Mar 2026 16:34:54 +0100 Subject: [PATCH 6/7] updated makefile --- oss-fuzz/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oss-fuzz/Makefile b/oss-fuzz/Makefile index 5055ba71a8e..b2dc26d051a 100644 --- a/oss-fuzz/Makefile +++ b/oss-fuzz/Makefile @@ -167,7 +167,7 @@ $(libcppdir)/addoninfo.o: ../lib/addoninfo.cpp ../externals/picojson/picojson.h $(libcppdir)/analyzerinfo.o: ../lib/analyzerinfo.cpp ../externals/tinyxml2/tinyxml2.h ../lib/analyzerinfo.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/filesettings.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/standards.h ../lib/utils.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/analyzerinfo.cpp -$(libcppdir)/astutils.o: ../lib/astutils.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkclass.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/findtoken.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vfvalue.h +$(libcppdir)/astutils.o: ../lib/astutils.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkclass.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/findtoken.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vf_common.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/astutils.cpp $(libcppdir)/check.o: ../lib/check.cpp ../lib/addoninfo.h ../lib/check.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h From a8241f3e988f69a2c5b01938b18a5cbc4be596cd Mon Sep 17 00:00:00 2001 From: Johan Crone Date: Mon, 2 Mar 2026 16:55:39 +0100 Subject: [PATCH 7/7] do not perform shift if negative value --- lib/astutils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 4c55fc07e54..4795b9689f0 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1003,7 +1003,7 @@ bool isOperationResultWithinIntRange(const Token* op, const Settings& settings, return false; // Leftshift with negative values is undefined behavior. - if ((rightRange->first < 0) || rightRange->second < 0) + if ((rightRange->first < 0) || (rightRange->second < 0) || (leftRange->first < 0) || (leftRange->second < 0)) return false; return checkAllRangeOperations(*leftRange, *rightRange, settings,