diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/TypeHelper.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/TypeHelper.cs index c8023f2f372..df5c64b9e20 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/TypeHelper.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/TypeHelper.cs @@ -18,6 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using Microsoft.CodeAnalysis; +using static Google.Protobuf.WellKnownTypes.Field; + namespace SonarAnalyzer.Helpers; internal static class TypeHelper @@ -105,6 +108,11 @@ public static bool IsAny(this ITypeSymbol typeSymbol, ImmutableArray return false; } + public static bool IsNullableOfAny(this ITypeSymbol type, ImmutableArray argumentTypes) => + NullableTypeArgument(type).IsAny(argumentTypes); + + public static bool IsNullableOf(this ITypeSymbol type, KnownType typeArgument) => + NullableTypeArgument(type).Is(typeArgument); public static bool IsType(this IParameterSymbol parameter, KnownType type) => parameter != null && parameter.Type.Is(type); @@ -121,10 +129,7 @@ public static bool IsInType(this ISymbol symbol, ImmutableArray types #endregion TypeName public static bool IsNullableBoolean(this ITypeSymbol type) => - type is INamedTypeSymbol namedType - && namedType.OriginalDefinition.Is(KnownType.System_Nullable_T) - && namedType.TypeArguments.Length == 1 - && namedType.TypeArguments[0].Is(KnownType.System_Boolean); + type.IsNullableOf(KnownType.System_Boolean); public static bool Implements(this ITypeSymbol typeSymbol, KnownType type) => typeSymbol is { } @@ -205,4 +210,9 @@ public static ITypeSymbol GetSymbolType(this ISymbol symbol) => ITypeSymbol x => x, _ => null, }; + + private static ITypeSymbol NullableTypeArgument(ITypeSymbol type) => + type is INamedTypeSymbol namedType && namedType.OriginalDefinition.Is(KnownType.System_Nullable_T) + ? namedType.TypeArguments[0] + : null; } diff --git a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Binary.cs b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Binary.cs index 928e7de78f4..547c794f4b4 100644 --- a/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Binary.cs +++ b/analyzers/src/SonarAnalyzer.Common/SymbolicExecution/Roslyn/OperationProcessors/Binary.cs @@ -97,16 +97,24 @@ private static ProgramState LearnBranchingNumberConstraint(ProgramState state, I var kind = falseBranch ? Opposite(binary.OperatorKind) : binary.OperatorKind; var leftNumber = state[binary.LeftOperand]?.Constraint(); var rightNumber = state[binary.RightOperand]?.Constraint(); - if (rightNumber is not null && !binary.LeftOperand.ConstantValue.HasValue && binary.LeftOperand.TrackedSymbol(state) is { } leftSymbol) + if (rightNumber is not null && OperandSymbol(binary.LeftOperand) is { } leftSymbol) { state = LearnBranching(leftSymbol, leftNumber, kind, rightNumber); } - if (leftNumber is not null && !binary.RightOperand.ConstantValue.HasValue && binary.RightOperand.TrackedSymbol(state) is { } rightSymbol) + if (leftNumber is not null && OperandSymbol(binary.RightOperand) is { } rightSymbol) { state = LearnBranching(rightSymbol, rightNumber, Flip(kind), leftNumber); } return state; + ISymbol OperandSymbol(IOperation operand) => + !operand.ConstantValue.HasValue + && operand.TrackedSymbol(state) is { } symbol + && symbol.GetSymbolType() is INamedTypeSymbol type + && (type.IsAny(KnownType.IntegralNumbersIncludingNative) || type.IsNullableOfAny(KnownType.IntegralNumbersIncludingNative)) + ? symbol + : null; + ProgramState LearnBranching(ISymbol symbol, NumberConstraint existingNumber, BinaryOperatorKind kind, NumberConstraint comparedNumber) => !(falseBranch && symbol.GetSymbolType().IsNullableValueType()) // Don't learn opposite for "nullable > 0", because it could also be . && RelationalNumberConstraint(existingNumber, kind, comparedNumber, isLoopCondition, visitCount) is { } newConstraint diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/ConditionEvaluatesToConstant.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/ConditionEvaluatesToConstant.cs index e2b0ecd0e7b..4884f9df5fe 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/ConditionEvaluatesToConstant.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Roslyn/ConditionEvaluatesToConstant.cs @@ -3487,17 +3487,59 @@ public static void Foo() // https://github.com/SonarSource/sonar-dotnet/issues/8470 public class Repro_8470 { - public string Go() + public string WithDouble() { double t = 0.5; if (t <= 0) { return "a"; } - if (t >= 1) // Noncompliant FP + if (t >= 1) // Compliant, we don't track floating point numbers { return "b"; } - return "c"; // Secondary FP + return "c"; + } + + public string WithDoubleSwappedOperands() + { + double t = 0.5; + if (0 >= t) + { + return "a"; + } + if (1 <= t) // Compliant, we don't track floating point numbers + { + return "b"; + } + return "c"; + } + + public string WithDecimal() + { + decimal t = 0.5M; + if (t <= 0) + { + return "a"; + } + if (t >= 1) // Compliant, we don't track floating point numbers + { + return "b"; + } + return "c"; + } + + public string WithFloat() + { + float t = 0.5F; + if (t <= 0) + { + return "a"; + } + if (t >= 1) // Compliant, we don't track floating point numbers + { + return "b"; + } + return "c"; } }