Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New rule S2198: Silly mathematical comparisons should not be made #6695

Merged
merged 28 commits into from Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e76d32a
Update RSPEC for S2198
gregory-paidis-sonarsource Feb 1, 2023
93391b2
Remove a useless auto-generated json
gregory-paidis-sonarsource Feb 1, 2023
ececfa2
Add some testcases
gregory-paidis-sonarsource Feb 1, 2023
46f3d70
Remove a useless using
gregory-paidis-sonarsource Feb 1, 2023
5bdd084
Initial implementation of the main logic
gregory-paidis-sonarsource Feb 1, 2023
fee626d
Initial working implementation of S2198
gregory-paidis-sonarsource Feb 2, 2023
36214a2
Refactor implementation
gregory-paidis-sonarsource Feb 2, 2023
2983cc7
Move some code around
gregory-paidis-sonarsource Feb 2, 2023
3374a35
Add a lot of testcases
gregory-paidis-sonarsource Feb 3, 2023
3564d52
Cut branches: only implement absolute inequality for float
gregory-paidis-sonarsource Feb 3, 2023
86696dc
Rewrite SillyMathematicalComparison, only out-of-range checks for flo…
gregory-paidis-sonarsource Feb 6, 2023
99f3b27
Apply PR review changes
gregory-paidis-sonarsource Feb 7, 2023
ee45a5c
Enrichen testcases, add long and ulong to the checked types
gregory-paidis-sonarsource Feb 7, 2023
ba6589f
Add 'is' pattern testcases, refactored a .ToString to a .ToDisplayString
gregory-paidis-sonarsource Feb 7, 2023
d463864
Simple rename
gregory-paidis-sonarsource Feb 7, 2023
4f0720a
Implement a constant-in-both-sides version of the rule.
gregory-paidis-sonarsource Feb 7, 2023
0070a96
Apply PR Review changes, some constant-check fixes and more testcases
gregory-paidis-sonarsource Feb 8, 2023
e3b6f24
Add enum, char and reference type to the testcases
gregory-paidis-sonarsource Feb 8, 2023
a84d441
Simplify the rule, only care about out-of-range mathematical checks
gregory-paidis-sonarsource Feb 8, 2023
4856e96
Add char out-of-range detection
gregory-paidis-sonarsource Feb 8, 2023
51d7b9c
Added some true negatives for const1 {{operator}} const2
gregory-paidis-sonarsource Feb 8, 2023
f79b42a
Bump coverage by adding a case I forgot about
gregory-paidis-sonarsource Feb 8, 2023
083ae47
Apply PR Review changes
gregory-paidis-sonarsource Feb 9, 2023
0980aa4
Add an is-pattern edgecase in the testcases
gregory-paidis-sonarsource Feb 9, 2023
16c517c
Fix a logical condition, refactor TryGetRange
gregory-paidis-sonarsource Feb 9, 2023
a0fd1ce
Minor naming refactor
gregory-paidis-sonarsource Feb 9, 2023
339ac49
Minor refactor for OutOfRange check
gregory-paidis-sonarsource Feb 10, 2023
a8f25d7
Annotate a testcase with an issue
gregory-paidis-sonarsource Feb 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions analyzers/rspec/cs/S2198_c#.html
@@ -0,0 +1,18 @@
<p>Certain mathematical comparisons will always return the same value, and should simply not be made.</p>
<p>This comparison will always return <code>false</code>:</p>
<ul>
<li> comparing a <code>float</code> with a <code>double</code> constant that’s outside the <code>float</code> range </li>
</ul>
<p>These will always return <code>true</code>:</p>
<ul>
<li> comparing <code>aByte &lt;= Byte.MaxValue</code> and <code>aByte &gt;= Byte.MinValue</code> </li>
<li> comparing <code>anInt &lt;= int.MaxValue</code> and <code>anInt &gt;= int.MinValue</code> </li>
<li> comparing <code>aLong &lt;= long.MaxValue</code> and <code>aLong &gt;= long.MinValue</code> </li>
</ul>
<h2>Noncompliant Code Example</h2>
<pre>
float f = 42.0f;
const double d = float.MaxValue + 1;
if (f &lt;= d) { } // Noncompliant
</pre>

17 changes: 17 additions & 0 deletions analyzers/rspec/cs/S2198_c#.json
@@ -0,0 +1,17 @@
{
"title": "Silly mathematical comparisons should not be made",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "15min"
},
"tags": [
"suspicious"
],
"defaultSeverity": "Critical",
"ruleSpecification": "RSPEC-2198",
"sqKey": "S2198",
"scope": "Main",
"quickfix": "unknown"
}
1 change: 1 addition & 0 deletions analyzers/rspec/cs/Sonar_way_profile.json
Expand Up @@ -66,6 +66,7 @@
"S2184",
"S2187",
"S2190",
"S2198",
"S2201",
"S2219",
"S2222",
Expand Down
118 changes: 117 additions & 1 deletion analyzers/src/SonarAnalyzer.CSharp/Rules/SillyBitwiseOperation.cs
Expand Up @@ -25,6 +25,19 @@ public sealed class SillyBitwiseOperation : SillyBitwiseOperationBase
{
protected override ILanguageFacade Language => CSharpFacade.Instance;

// No codefix (yet?)
private const string ComparisonDiagnosticId = "S2198";
private const string ComparisonMessageFormat = "Remove this silly mathematical comparison.";
martin-strecker-sonarsource marked this conversation as resolved.
Show resolved Hide resolved

protected DiagnosticDescriptor ComparisonRule { get; set; }
martin-strecker-sonarsource marked this conversation as resolved.
Show resolved Hide resolved

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(BitwiseRule, ComparisonRule);

public SillyBitwiseOperation()
{
ComparisonRule = Language.CreateDescriptor(ComparisonDiagnosticId, ComparisonMessageFormat, fadeOutCode: true);
}

protected override void Initialize(SonarAnalysisContext context)
{
context.RegisterNodeAction(
Expand All @@ -44,6 +57,13 @@ protected override void Initialize(SonarAnalysisContext context)
c => CheckAssignment(c, 0),
SyntaxKind.OrAssignmentExpression,
SyntaxKind.ExclusiveOrAssignmentExpression);

context.RegisterNodeAction(
CheckComparison,
SyntaxKind.GreaterThanExpression,
SyntaxKind.GreaterThanOrEqualExpression,
SyntaxKind.LessThanExpression,
SyntaxKind.LessThanOrEqualExpression);
}

private void CheckAssignment(SonarSyntaxNodeReportingContext context, int constValueToLookFor)
Expand All @@ -55,7 +75,7 @@ private void CheckAssignment(SonarSyntaxNodeReportingContext context, int constV
var location = assignment.Parent is StatementSyntax
? assignment.Parent.GetLocation()
: assignment.OperatorToken.CreateLocation(assignment.Right);
context.ReportIssue(Diagnostic.Create(Rule, location));
context.ReportIssue(Diagnostic.Create(BitwiseRule, location));
}
}

Expand All @@ -64,5 +84,101 @@ private void CheckBinary(SonarSyntaxNodeReportingContext context, int constValue
var binary = (BinaryExpressionSyntax)context.Node;
CheckBinary(context, binary.Left, binary.OperatorToken, binary.Right, constValueToLookFor);
}

private void CheckComparison(SonarSyntaxNodeReportingContext context)
martin-strecker-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
{
var binary = (BinaryExpressionSyntax)context.Node;
if (ShouldRaise(context.SemanticModel, binary.Left, binary.Right, binary.Kind(), false))
{
context.ReportIssue(Diagnostic.Create(BitwiseRule, binary.GetLocation()));
martin-strecker-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
}

if (ShouldRaise(context.SemanticModel, binary.Right, binary.Left, binary.Kind(), true))
{
context.ReportIssue(Diagnostic.Create(BitwiseRule, binary.GetLocation()));
}
}

private bool ShouldRaise(SemanticModel semanticModel, SyntaxNode value, SyntaxNode expectedConstant, SyntaxKind kind, bool constantIsLeft)
martin-strecker-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
{
if (FindConstant(semanticModel, expectedConstant, Convert.ToDouble) is { } constant
&& semanticModel.GetSymbolInfo(value).Symbol.GetSymbolType() is { } symbol
&& Ranges.FirstOrDefault(x => symbol.Is(x.Type)) is { } range)
{
// Implement out-of-range checks for the types that CS0652 does not.
if (symbol.IsAny(FullyImplementedTypes) && (constant < range.MinValue || constant > range.MaxValue))
{
return true;
}

// Implement threshold checks for every type.
return GetThreshold(kind, constantIsLeft, range) is { } threshold && constant == threshold;
martin-strecker-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
}

return false;
}

private static double? GetThreshold(SyntaxKind operation, bool constantIsLeft, TypeRange range)
{
if (constantIsLeft)
{
return operation switch
{
SyntaxKind.LessThanExpression => range.MaxValue, // T.MaxValue < x
SyntaxKind.LessThanOrEqualExpression => range.MinValue, // T.MinValue <= x
SyntaxKind.GreaterThanExpression => range.MinValue, // T.MinValue > x
SyntaxKind.GreaterThanOrEqualExpression => range.MaxValue, // T.MaxValue >= x
_ => null
};
}
else
{

return operation switch
{
SyntaxKind.LessThanExpression => range.MinValue, // x < T.MinValue
SyntaxKind.LessThanOrEqualExpression => range.MaxValue, // x <= T.MaxValue
SyntaxKind.GreaterThanExpression => range.MaxValue, // x > T.MaxValue
SyntaxKind.GreaterThanOrEqualExpression => range.MinValue, // x >= T.MinValue
_ => null
};
}
}

private static readonly KnownType[] FullyImplementedTypes = new[]
{
KnownType.System_Int64,
KnownType.System_UInt64,
KnownType.System_Half,
KnownType.System_Single,
};

private static readonly TypeRange[] Ranges = new[]
{
new TypeRange(KnownType.System_SByte, sbyte.MinValue, sbyte.MaxValue),
new TypeRange(KnownType.System_Byte, byte.MinValue, byte.MaxValue),
new TypeRange(KnownType.System_Int16, short.MinValue, short.MaxValue),
new TypeRange(KnownType.System_UInt16, ushort.MinValue, ushort.MaxValue),
new TypeRange(KnownType.System_Int32, int.MinValue, int.MaxValue),
new TypeRange(KnownType.System_UInt32, uint.MinValue, uint.MaxValue),
new TypeRange(KnownType.System_Int64, long.MinValue, long.MaxValue),
new TypeRange(KnownType.System_UInt64, ulong.MinValue, ulong.MaxValue),
new TypeRange(KnownType.System_Single, float.MinValue, float.MaxValue),
//new TypeRange(KnownType.System_Half, half.MinValue, half.MaxValue),
};

private class TypeRange
{
public KnownType Type { get; init; }
public double MinValue { get; init; }
public double MaxValue { get; init; }

public TypeRange(KnownType type, double minValue, double maxValue)
{
Type = type;
MinValue = minValue;
MaxValue = maxValue;
}
}
}
}
Expand Up @@ -20,19 +20,23 @@

namespace SonarAnalyzer.Rules
{
// FIXME: Rename this to something like SillyNumericalOperatorUsage
public abstract class SillyBitwiseOperationBase : SonarDiagnosticAnalyzer
{
internal const string DiagnosticId = "S2437";
internal const string BitwiseDiagnosticId = "S2437";
internal const string IsReportingOnLeftKey = "IsReportingOnLeft";
private const string MessageFormat = "Remove this silly bit operation.";
private const string BitwiseMessageFormat = "Remove this silly bit operation.";

protected abstract ILanguageFacade Language { get; }

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
protected DiagnosticDescriptor Rule { get; }
protected DiagnosticDescriptor BitwiseRule { get; }

protected SillyBitwiseOperationBase() =>
Rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, fadeOutCode: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(BitwiseRule);

protected SillyBitwiseOperationBase()
{
BitwiseRule = Language.CreateDescriptor(BitwiseDiagnosticId, BitwiseMessageFormat, fadeOutCode: true);
}

protected void CheckBinary(SonarSyntaxNodeReportingContext context, SyntaxNode left, SyntaxToken @operator, SyntaxNode right, int constValueToLookFor)
{
Expand All @@ -53,7 +57,7 @@ protected void CheckBinary(SonarSyntaxNodeReportingContext context, SyntaxNode l
return;
}

context.ReportIssue(Diagnostic.Create(Rule, location, ImmutableDictionary<string, string>.Empty.Add(IsReportingOnLeftKey, isReportingOnLeftKey.ToString())));
context.ReportIssue(Diagnostic.Create(BitwiseRule, location, ImmutableDictionary<string, string>.Empty.Add(IsReportingOnLeftKey, isReportingOnLeftKey.ToString())));
}

protected int? FindIntConstant(SemanticModel semanticModel, SyntaxNode node) =>
Expand All @@ -64,6 +68,17 @@ protected void CheckBinary(SonarSyntaxNodeReportingContext context, SyntaxNode l
? ConversionHelper.TryConvertToInt(value)
: null;

protected T? FindConstant<T>(SemanticModel semanticModel, SyntaxNode node, Func<object, T> converter) where T : struct
{
return semanticModel.GetSymbolInfo(node).Symbol is var symbol
&& !IsFieldOrPropertyOutsideSystemNamespace(symbol)
&& !IsEnum(symbol)
&& Language.FindConstantValue(semanticModel, node) is { } value
&& ConversionHelper.TryConvertWith(value, converter, out T typedValue)
? typedValue
: null;
}

private static bool IsEnum(ISymbol symbol) =>
symbol.GetSymbolType() is INamedTypeSymbol { EnumUnderlyingType: { } };

Expand Down
Expand Up @@ -30,7 +30,7 @@ public abstract class SillyBitwiseOperationCodeFixBase : SonarCodeFix

protected abstract Func<SyntaxNode> CreateNewRoot(SyntaxNode root, TextSpan diagnosticSpan, bool isReportingOnLeft);

public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(SillyBitwiseOperationBase.DiagnosticId);
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(SillyBitwiseOperationBase.BitwiseDiagnosticId);

protected override Task RegisterCodeFixesAsync(SyntaxNode root, CodeFixContext context)
{
Expand Down
Expand Up @@ -2122,7 +2122,7 @@ internal static class RuleTypeMappingCS
// ["S2195"],
// ["S2196"],
["S2197"] = "CODE_SMELL",
// ["S2198"],
["S2198"] = "CODE_SMELL",
// ["S2199"],
// ["S2200"],
["S2201"] = "BUG",
Expand Down
@@ -0,0 +1,34 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using CS = SonarAnalyzer.Rules.CSharp;

namespace SonarAnalyzer.UnitTest.Rules
{
[TestClass]
public class SillyMathematicalComparisonTest
{
private readonly VerifierBuilder builderCS = new VerifierBuilder<CS.SillyBitwiseOperation>();

[TestMethod]
public void SillyMathematicalComparison_CS() =>
builderCS.AddPaths("SillyMathematicalComparison.cs").Verify();
}
}