diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Binary.Arithmetic.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Binary.Arithmetic.cs index 89b0db8f694..2d6d5063ad2 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Binary.Arithmetic.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Binary.Arithmetic.cs @@ -31,6 +31,8 @@ internal sealed partial class Binary BinaryOperatorKind.Subtract => NumberConstraint.From(left.Min - right.Max, left.Max - right.Min), BinaryOperatorKind.Multiply => CalculateMultiply(left, right), BinaryOperatorKind.Divide => CalculateDivide(left, right), + BinaryOperatorKind.Remainder when left.IsSingleValue && right.IsSingleValue && right.Min != 0 => NumberConstraint.From(left.Min.Value % right.Min.Value), + BinaryOperatorKind.Remainder => CalculateRemainder(left, right), BinaryOperatorKind.And when left.IsSingleValue && right.IsSingleValue => NumberConstraint.From(left.Min.Value & right.Min.Value), BinaryOperatorKind.And => NumberConstraint.From(CalculateAndMin(left, right), CalculateAndMax(left, right)), BinaryOperatorKind.Or when left.IsSingleValue && right.IsSingleValue => NumberConstraint.From(left.Min.Value | right.Min.Value), @@ -113,6 +115,69 @@ private static NumberConstraint CalculateDivide(NumberConstraint left, NumberCon return NumberConstraint.From(min, max); } + private static NumberConstraint CalculateRemainder(NumberConstraint left, NumberConstraint right) + { + if (right.Min == 0 && right.Max == 0) + { + return null; + } + else + { + right = AccountForZero(right); + return NumberConstraint.From(CalculateRemainderMin(left, right), CalculateRemainderMax(left, right)); + } + } + + private static BigInteger? CalculateRemainderMin(NumberConstraint left, NumberConstraint right) + { + // If the absolute value of the divisor is bigger than the absolute value of the dividend, the result is equal to the dividend => + // If the absolute value of every divisor is bigger than the absolute value of any dividend => resulting range == dividend range. + // Otherwise, the result is between 0 and the absolute value of the divisor - 1 for positive dividends or in the same range multiplied by -1 for negative dividends. + if (left.CanBeNegative || left.Max < MinOfAbsoluteValues(right)) + { + if (right.Min is null || right.Max is null) + { + // If right is not finite, there will always be a divisor for which its absolute value is bigger than the absolute value of any value in left => resulting range == dividend range. + return left.Min; + } + else + { + // Otherwise, the result value is limited by the dividend and the divisor. + var minDerivedFromRight = -MaxOfAbsoluteValues(right) + 1; + return left.Min is null ? minDerivedFromRight : BigInteger.Max(left.Min.Value, minDerivedFromRight); + } + } + else + { + return 0; + } + } + + private static BigInteger? CalculateRemainderMax(NumberConstraint left, NumberConstraint right) + { + // If the absolute value of the divisor is bigger than the absolute value of the dividend, the result is equal to the dividend => + // If the absolute value of every divisor is bigger than the absolute value of any dividend => resulting range == dividend range. + // Otherwise, the result is between 0 and the absolute value of the divisor - 1 for positive dividends or in the same range multiplied by -1 for negative dividends. + if (left.CanBePositive || -left.Max < MinOfAbsoluteValues(right)) + { + if (right.Min is null || right.Max is null) + { + // If right is not finite, there will always be a divisor for which its absolute value is bigger than the absolute value of any value in left => resulting range == dividend range. + return left.Max; + } + else + { + // Otherwise, the result value is limited by the dividend and the divisor. + var maxDerivedFromRight = MaxOfAbsoluteValues(right) - 1; + return left.Max is null ? maxDerivedFromRight : BigInteger.Min(left.Max.Value, maxDerivedFromRight); + } + } + else + { + return 0; + } + } + private static NumberConstraint AccountForZero(NumberConstraint constraint) { if (constraint.Min == 0) @@ -129,6 +194,25 @@ private static NumberConstraint AccountForZero(NumberConstraint constraint) } } + private static BigInteger MaxOfAbsoluteValues(NumberConstraint constraint) => + BigInteger.Max(BigInteger.Abs(constraint.Min.Value), BigInteger.Abs(constraint.Max.Value)); + + private static BigInteger MinOfAbsoluteValues(NumberConstraint constraint) + { + if (constraint.IsPositive) + { + return constraint.Min.Value; + } + else if (constraint.IsNegative) + { + return -constraint.Max.Value; + } + else + { + return 0; + } + } + private static BigInteger? CalculateAndMin(NumberConstraint left, NumberConstraint right) { // The result can only be negative if both operands are negative => The result must be >= 0 unless both ranges include negative numbers. diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Binary.cs b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Binary.cs index efbca840eb4..b25ade67a39 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Binary.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/SymbolicExecution/Roslyn/RoslynSymbolicExecutionTest.Binary.cs @@ -908,6 +908,121 @@ public void Binary_Division_Range(string expression, int? expectedMin, int? expe SETestContext.CreateCS(code, "int i, int j").Validator.TagValue("Value").Should().HaveOnlyConstraints(ObjectConstraint.NotNull, NumberConstraint.From(expectedMin, expectedMax)); } + [DataTestMethod] + [DataRow(21, 4, 1)] + [DataRow(21, -4, 1)] + [DataRow(-21, 4, -1)] + [DataRow(-21, -4, -1)] + [DataRow(4, 5, 4)] + [DataRow(-4, 5, -4)] + [DataRow(4, -5, 4)] + [DataRow(-4, -5, -4)] + [DataRow(5, 5, 0)] + [DataRow(5, -5, 0)] + [DataRow(-5, 5, 0)] + [DataRow(-5, -5, 0)] + [DataRow(5, 1, 0)] + [DataRow(5, -1, 0)] + [DataRow(-5, 1, 0)] + [DataRow(-5, -1, 0)] + public void Binary_Remainder_SingleValue(int left, int right, int expected) + { + var code = $""" + var left = {left}; + var right = {right}; + var value = left % right; + Tag("Value", value); + """; + SETestContext.CreateCS(code).Validator.TagValue("Value").Should().HaveOnlyConstraints(ObjectConstraint.NotNull, NumberConstraint.From(expected)); + } + + [DataTestMethod] + [DataRow("i >= 21 && j >= 5", 0, null)] + [DataRow("i >= 21 && j >= -5", 0, null)] + [DataRow("i >= 21 && j == 5", 0, 4)] + [DataRow("i >= 21 && j == -5", 0, 4)] + [DataRow("i >= 21 && j <= 5", 0, null)] + [DataRow("i >= 21 && j <= -5", 0, null)] + [DataRow("i >= -21 && j >= 5", -21, null)] + [DataRow("i >= -21 && j >= -5", -21, null)] + [DataRow("i >= -21 && j == 5", -4, 4)] + [DataRow("i >= -21 && j == -5", -4, 4)] + [DataRow("i >= -21 && j <= 5", -21, null)] + [DataRow("i >= -21 && j <= -5", -21, null)] + [DataRow("i == 21 && j >= 5", 0, 21)] + [DataRow("i == 21 && j >= -5", 0, 21)] + [DataRow("i == 21 && j <= 5", 0, 21)] + [DataRow("i == 21 && j <= -5", 0, 21)] + [DataRow("i == -21 && j >= 5", -21, 0)] + [DataRow("i == -21 && j >= -5", -21, 0)] + [DataRow("i == -21 && j <= 5", -21, 0)] + [DataRow("i == -21 && j <= -5", -21, 0)] + [DataRow("i <= 21 && j >= 5", null, 21)] + [DataRow("i <= 21 && j >= -5", null, 21)] + [DataRow("i <= 21 && j == 5", -4, 4)] + [DataRow("i <= 21 && j == -5", -4, 4)] + [DataRow("i <= 21 && j <= 5", null, 21)] + [DataRow("i <= 21 && j <= -5", null, 21)] + [DataRow("i <= -21 && j >= 5", null, 0)] + [DataRow("i <= -21 && j >= -5", null, 0)] + [DataRow("i <= -21 && j == 5", -4, 0)] + [DataRow("i <= -21 && j == -5", -4, 0)] + [DataRow("i <= -21 && j <= 5", null, 0)] + [DataRow("i <= -21 && j <= -5", null, 0)] + [DataRow("i >= 21 && j >= 0", 0, null)] + [DataRow("i >= 21 && j == 0", null, null)] + [DataRow("i >= 21 && j <= 0", 0, null)] + [DataRow("i >= -21 && j >= 0", -21, null)] + [DataRow("i >= -21 && j == 0", null, null)] + [DataRow("i >= -21 && j <= 0", -21, null)] + [DataRow("i <= 21 && j >= 0", null, 21)] + [DataRow("i <= 21 && j == 0", null, null)] + [DataRow("i <= 21 && j <= 0", null, 21)] + [DataRow("i <= -21 && j >= 0", null, 0)] + [DataRow("i <= -21 && j == 0", null, null)] + [DataRow("i <= -21 && j <= 0", null, 0)] + [DataRow("i >= 21 && j == 1", 0, 0)] + [DataRow("i >= -21 && j == 1", 0, 0)] + [DataRow("i <= 21 && j == 1", 0, 0)] + [DataRow("i <= -21 && j == 1", 0, 0)] + [DataRow("i >= 21 && j >= 5 && j <= 10", 0, 9)] + [DataRow("i >= 21 && j >= -5 && j <= 10", 0, 9)] + [DataRow("i >= 21 && j >= -10 && j <= 5", 0, 9)] + [DataRow("i >= 21 && j >= -10 && j <= -5", 0, 9)] + [DataRow("i >= -21 && j >= 5 && j <= 10", -9, 9)] + [DataRow("i >= -21 && j >= -5 && j <= 10", -9, 9)] + [DataRow("i >= -21 && j >= -10 && j <= 5", -9, 9)] + [DataRow("i >= -21 && j >= -10 && j <= -5", -9, 9)] + [DataRow("i <= 21 && j >= 5 && j <= 10", -9, 9)] + [DataRow("i <= 21 && j >= -5 && j <= 10", -9, 9)] + [DataRow("i <= 21 && j >= -10 && j <= 5", -9, 9)] + [DataRow("i <= 21 && j >= -10 && j <= -5", -9, 9)] + [DataRow("i <= -21 && j >= 5 && j <= 10", -9, 0)] + [DataRow("i <= -21 && j >= -5 && j <= 10", -9, 0)] + [DataRow("i <= -21 && j >= -10 && j <= 5", -9, 0)] + [DataRow("i <= -21 && j >= -10 && j <= -5", -9, 0)] + [DataRow("i >= 5 && i <= 10 && j >= 21", 5, 10)] + [DataRow("i >= 5 && i <= 10 && j <= -21", 5, 10)] + [DataRow("i >= -5 && i <= 10 && j >= 21", -5, 10)] + [DataRow("i >= -5 && i <= 10 && j <= -21", -5, 10)] + [DataRow("i >= -10 && i <= 5 && j >= 21", -10, 5)] + [DataRow("i >= -10 && i <= 5 && j <= -21", -10, 5)] + [DataRow("i >= -10 && i <= -5 && j >= 21", -10, -5)] + [DataRow("i >= -10 && i <= -5 && j <= -21", -10, -5)] + [DataRow("i >= 4 && i <= 7 && j >= 5 && j <= 10", 0, 7)] + [DataRow("i == 5 && j == 0", null, null)] + public void Binary_Remainder_Range(string expression, int? expectedMin, int? expectedMax) + { + var code = $$""" + if ({{expression}}) + { + var value = i % j; + Tag("Value", value); + } + """; + SETestContext.CreateCS(code, "int i, int j").Validator.TagValue("Value").Should().HaveOnlyConstraints(ObjectConstraint.NotNull, NumberConstraint.From(expectedMin, expectedMax)); + } + [DataTestMethod] [DataRow(0b0000, 0b0000, 0b0000)] [DataRow(0b0101, 0b0101, 0b0101)] diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/CalculationsShouldNotOverflow.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/CalculationsShouldNotOverflow.cs index c80e187bba7..fcecf1ee009 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/CalculationsShouldNotOverflow.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/CalculationsShouldNotOverflow.cs @@ -118,7 +118,7 @@ public void BasicOperators() _ = i * 2147483600; // FN i = 2 % j; - _ = i * 2147483600; // FN + _ = i * 2147483600; // Noncompliant } public void AssignmentOperators() diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/CalculationsShouldNotOverflow.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/CalculationsShouldNotOverflow.vb index 9d08f8490e1..dd1fa5adff3 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/CalculationsShouldNotOverflow.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/CalculationsShouldNotOverflow.vb @@ -79,7 +79,7 @@ Public Class Sample __ = i * 2147483600 ' FN i = 2 Mod j - __ = i * 2147483600 ' FN + __ = i * 2147483600 ' Noncompliant Dim Result = Integer.MaxValue ^ 2 ' Compliant, result is Double End Sub