diff --git a/lib/importproject.cpp b/lib/importproject.cpp index d81c95ab6fd..24597834694 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -848,6 +849,22 @@ static std::string safeFormat(const char *fmt, Args... args) { #pragma clang diagnostic pop #endif +// MSBuild evaluates the [MSBuild]::Add/Subtract/Multiply intrinsics in .NET's +// default unchecked context, so on overflow the result wraps (two's complement). +// Do the arithmetic on unsigned operands to get the same result without +// signed-overflow UB. +static long long wrapAdd(long long a, long long b) { + return static_cast(static_cast(a) + static_cast(b)); +} + +static long long wrapSub(long long a, long long b) { + return static_cast(static_cast(a) - static_cast(b)); +} + +static long long wrapMul(long long a, long long b) { + return static_cast(static_cast(a) * static_cast(b)); +} + // Evaluate a $([ClassName]::Method(args)) static property function. // Returns an empty string for unknown or unimplementable functions rather // than throwing, so import can continue gracefully. @@ -894,16 +911,20 @@ std::string ImportProject::applyMSBuildStaticFunction(const std::string &classNa long long a = 0, b = 0; if (toInt(args[0], a) && toInt(args[1], b)) { if (caseInsensitiveStringCompare(member, "Add") == 0) - return std::to_string(a + b); + return std::to_string(wrapAdd(a, b)); if (caseInsensitiveStringCompare(member, "Subtract") == 0) - return std::to_string(a - b); + return std::to_string(wrapSub(a, b)); if (caseInsensitiveStringCompare(member, "Multiply") == 0) - return std::to_string(a * b); + return std::to_string(wrapMul(a, b)); if (caseInsensitiveStringCompare(member, "Divide") == 0) { if (b == 0) { addDebug("MSBuild::Divide: division by zero"); return std::string(); } + if (a == LLONG_MIN && b == -1) { + addDebug("MSBuild::Divide: integer overflow"); + return std::string(); + } return std::to_string(a / b); } if (caseInsensitiveStringCompare(member, "Modulo") == 0) { @@ -911,6 +932,10 @@ std::string ImportProject::applyMSBuildStaticFunction(const std::string &classNa addDebug("MSBuild::Modulo: division by zero"); return std::string(); } + if (a == LLONG_MIN && b == -1) { + addDebug("MSBuild::Modulo: integer overflow"); + return std::string(); + } return std::to_string(a % b); } } diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index a9b756ff17a..2ecdf677381 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -1211,6 +1211,14 @@ class TestImportProject : public TestFixture { ASSERT_EQUALS("3", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Divide(9, 3))")); ASSERT_EQUALS("2", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Modulo(5, 3))")); + // Overflow wraps around like MSBuild (.NET unchecked arithmetic). + ASSERT_EQUALS("-9223372036854775808", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Add(9223372036854775807, 1))")); + ASSERT_EQUALS("9223372036854775807", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Subtract(-9223372036854775808, 1))")); + ASSERT_EQUALS("7766279611452241921", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Multiply(9999999999, 9999999999))")); + // .NET throws OverflowException for LLONG_MIN / -1 and LLONG_MIN % -1. + ASSERT_EQUALS("", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Divide(-9223372036854775808, -1))")); + ASSERT_EQUALS("", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Modulo(-9223372036854775808, -1))")); + // --- $([MSBuild]::...) path helpers --- ASSERT_EQUALS("foo/", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::EnsureTrailingSlash('foo'))")); ASSERT_EQUALS("foo/", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::EnsureTrailingSlash('foo/'))"));