Skip to content

Commit

Permalink
Custom message for IsNaN
Browse files Browse the repository at this point in the history
  • Loading branch information
antonioaversa committed Jan 23, 2023
1 parent 0bad0c7 commit 2af8818
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ namespace SonarAnalyzer.Rules.CSharp
public sealed class EqualityOnFloatingPoint : SonarDiagnosticAnalyzer
{
internal const string DiagnosticId = "S1244";
private const string MessageFormat = "Do not check floating point {0} with exact values, use a range instead.";
private const string MessageFormat = "Do not check floating point {0} with exact values, use {1} instead.";

private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat);

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

private static readonly ISet<string> EqualityOperators = new HashSet<string> { "op_Equality", "op_Inequality" };

private static readonly IDictionary<string, string> FloatingPointEpsilonDependantMembers = new Dictionary<string, string>
{
[nameof(double.NaN)] = $"{nameof(double.IsNaN)}()",
};

protected override void Initialize(SonarAnalysisContext context)
{
context.RegisterNodeAction(
Expand Down Expand Up @@ -80,12 +85,21 @@ private static void CheckEquality(SonarSyntaxNodeReportingContext context)
&& IsFloatingPointNumberType(container)
&& EqualityOperators.Contains(equalitySymbolName))
{
var messageEqualityPart = GetMessageEqualityPart(equals.IsKind(SyntaxKind.EqualsExpression));

context.ReportIssue(Diagnostic.Create(rule, equals.OperatorToken.GetLocation(), messageEqualityPart));
var messageEqualityPart = MessageEqualityPart(equals.IsKind(SyntaxKind.EqualsExpression));
var proposed = ProposedMessage(context, equals.Right) ?? ProposedMessage(context, equals.Left) ?? "a range";
context.ReportIssue(Diagnostic.Create(Rule, equals.OperatorToken.GetLocation(), messageEqualityPart, proposed));
}
}

private static string ProposedMessage(SonarSyntaxNodeReportingContext context, ExpressionSyntax expression) =>
expression is MemberAccessExpressionSyntax memberAccess
&& memberAccess.GetName() is var memberName
&& FloatingPointEpsilonDependantMembers.TryGetValue(memberName, out var proposedMethod)
&& context.SemanticModel.GetSymbolInfo(memberAccess.Expression).Symbol is ITypeSymbol type
&& IsFloatingPointNumberType(type)
? $"{type}.{proposedMethod}"
: null;

// Returns true for the floating point types that suffer from equivalence problems. All .NET floating point types have this problem except `decimal.`
// - Reason for excluding `decimal`: the documentation for the `decimal.Equals()` method does not have a "Precision in Comparisons" section as the other .NET floating point types.
// - Power-2-based types like `double` implement `IFloatingPointIeee754`, but power-10-based `decimal` implements `IFloatingPoint`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@

public class EqualityOnFloatingPoint
{
void testHalf()
{
bool b;
var h1 = Half.NaN;

b = h1 == Half.NaN; // Noncompliant {{Do not check floating point equality with exact values, use System.Half.IsNaN() instead.}}
// ^^
}

void testNFloat()
{
bool b;
var nf1 = System.Runtime.InteropServices.NFloat.NaN;

b = nf1 == System.Runtime.InteropServices.NFloat.NaN; // Noncompliant {{Do not check floating point equality with exact values, use System.Runtime.InteropServices.NFloat.IsNaN() instead.}}
b = nf1 == NFloat.NaN; // Noncompliant {{Do not check floating point equality with exact values, use System.Runtime.InteropServices.NFloat.IsNaN() instead.}}
}

bool HalfEqual(Half first, Half second)
=> first == second; // Noncompliant {{Do not check floating point equality with exact values, use a range instead.}}
// ^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Tests.Diagnostics
{
using DoubleAlias = Double;

public class EqualityOnFloatingPoint
{
void test(float f, double d1, double d2)
Expand All @@ -14,7 +16,7 @@ void test(float f, double d1, double d2)
}

if (f == 3.14F) //Noncompliant {{Do not check floating point equality with exact values, use a range instead.}}
// ^^
// ^^
{
}

Expand Down Expand Up @@ -83,5 +85,101 @@ void test(float f, double d1, double d2)
{
}
}

private class ReportSpecificMessage_NaN
{

public void DoubleNaNEquality(int iPar1, double dPar1, float fPar1)
{
bool b1;
double d1 = 1.0;

// In a if statement
if (d1 == double.NaN) // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}
{ }

// Special value in assigned boolean expression
b1 = d1 == double.NaN; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}

// Special value on the left side of the equality
b1 = double.NaN == d1; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}

// Special value on both sides of the equality
b1 = double.NaN == double.NaN; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}

// With integer method parameter promotion
b1 = iPar1 == double.NaN; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}

// With double method parameter
b1 = dPar1 == double.NaN; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}

// With float method parameter promotion
b1 = fPar1 == double.NaN; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}


// With float NaN promotion, on right side
// Wrong message: should be "use double.IsNaN()" instead
b1 = double.NaN == float.NaN; // Noncompliant {{Do not check floating point equality with exact values, use float.IsNaN() instead.}}

// With float NaN promotion, on left side
b1 = float.NaN == double.NaN; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}
}

public void DoubleNaNInequality(int iPar1, double dPar1, float fPar1)
{
bool b1;
double d1 = 1.0;

if (d1 != double.NaN) // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}
{ }

b1 = d1 != double.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}
b1 = double.NaN != d1; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}
b1 = double.NaN != double.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}

b1 = iPar1 != double.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}
b1 = dPar1 != double.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}
b1 = fPar1 != double.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}

b1 = double.NaN != float.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use float.IsNaN() instead.}}
b1 = float.NaN != double.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}
}

public void FloatNaN(int iPar1, float fPar1)
{
bool b1;
float f1 = 1.0f;

b1 = f1 == float.NaN; // Noncompliant {{Do not check floating point equality with exact values, use float.IsNaN() instead.}}
b1 = f1 != float.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use float.IsNaN() instead.}}

b1 = iPar1 == float.NaN; // Noncompliant {{Do not check floating point equality with exact values, use float.IsNaN() instead.}}
b1 = fPar1 == float.NaN; // Noncompliant {{Do not check floating point equality with exact values, use float.IsNaN() instead.}}
b1 = iPar1 != float.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use float.IsNaN() instead.}}
b1 = fPar1 != float.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use float.IsNaN() instead.}}
}

public void DoublePascalCaseNaN()
{
bool b1;
Double d1 = 1.0;

b1 = d1 == Double.NaN; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}
b1 = d1 == System.Double.NaN; // Noncompliant {{Do not check floating point equality with exact values, use double.IsNaN() instead.}}
b1 = d1 != Double.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}
b1 = d1 != System.Double.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use double.IsNaN() instead.}}
}

public void SingleNaN()
{
bool b1;
Single f1 = 3.14159f;

b1 = f1 == Single.NaN; // Noncompliant {{Do not check floating point equality with exact values, use float.IsNaN() instead.}}
b1 = f1 == System.Single.NaN; // Noncompliant {{Do not check floating point equality with exact values, use float.IsNaN() instead.}}
b1 = f1 != Single.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use float.IsNaN() instead.}}
b1 = f1 != System.Single.NaN; // Noncompliant {{Do not check floating point inequality with exact values, use float.IsNaN() instead.}}
}
}
}
}

0 comments on commit 2af8818

Please sign in to comment.