-
Notifications
You must be signed in to change notification settings - Fork 228
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
Fix S6588 FN: Rule should cover case with epoch ticks #7550
Changes from all commits
1bd94d3
64ab097
1c7caeb
61a1fcf
19c362a
cfb791c
99158df
ff82b9a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ public abstract class UseUnixEpochBase<TSyntaxKind, TLiteralExpression, TMemberA | |
where TLiteralExpression : SyntaxNode | ||
where TMemberAccessExpression : SyntaxNode | ||
{ | ||
private const long EpochTicks = 621_355_968_000_000_000; | ||
private const int EpochYear = 1970; | ||
private const int EpochMonth = 1; | ||
private const int EpochDay = 1; | ||
|
@@ -57,23 +58,29 @@ protected sealed override void Initialize(SonarAnalysisContext context) => | |
Language.GeneratedCodeRecognizer, | ||
c => | ||
{ | ||
var literalsArguments = Language.Syntax.ArgumentExpressions(c.Node).OfType<TLiteralExpression>(); | ||
if (!literalsArguments.Any(x => IsValueEqualTo(x, EpochYear) || literalsArguments.Count(x => IsValueEqualTo(x, EpochMonth)) != 2)) | ||
var arguments = Language.Syntax.ArgumentExpressions(c.Node); | ||
var literalsArguments = arguments.OfType<TLiteralExpression>(); | ||
|
||
if (literalsArguments.Any(x => IsValueEqualTo(x, EpochYear) | ||
&& literalsArguments.Count(x => IsValueEqualTo(x, EpochMonth)) == 2) | ||
&& CheckAndGetTypeName(c.Node, c.SemanticModel) is { } name | ||
&& IsEpochCtor(c.Node, c.SemanticModel)) | ||
{ | ||
return; | ||
c.ReportIssue(Diagnostic.Create(Rule, c.Node.GetLocation(), name)); | ||
} | ||
|
||
var type = c.SemanticModel.GetTypeInfo(c.Node).Type; | ||
if (type.IsAny(TypesWithUnixEpochField) && IsEpochCtor(c.Node, c.SemanticModel)) | ||
else if (arguments.Count() == 1 | ||
&& ((literalsArguments.Count() == 1 && IsValueEqualTo(literalsArguments.First(), EpochTicks)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can also be done inside the
|
||
|| (Language.FindConstantValue(c.SemanticModel, arguments.First()) is long ticks && ticks == EpochTicks)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this extra check is necessary. The previous check is more than enough. If someone gives the Epoch ticks in hexa or binary format, then I think we can live with that 😎 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The previous check is just for the hardcoded number: _ = new DateTime(621355968000000000); // Noncompliant while this check is actually looking for constants through the tracker: private const long EpochTicks = 621355968000000000;
...
_ = new DateTime(EpochTicks); // Noncompliant |
||
&& CheckAndGetTypeName(c.Node, c.SemanticModel) is { } typeName) | ||
{ | ||
c.ReportIssue(Diagnostic.Create(Rule, c.Node.GetLocation(), type.Name)); | ||
c.ReportIssue(Diagnostic.Create(Rule, c.Node.GetLocation(), typeName)); | ||
} | ||
}, | ||
Language.SyntaxKind.ObjectCreationExpressions); | ||
}); | ||
|
||
protected static bool IsValueEqualTo(TLiteralExpression literal, int value) => | ||
int.TryParse(literal.ChildTokens().First().ValueText, out var parsedValue) && parsedValue == value; | ||
protected static bool IsValueEqualTo(TLiteralExpression literal, long value) => | ||
long.TryParse(literal.ChildTokens().First().ValueText, out var parsedValue) && parsedValue == value; | ||
|
||
private bool IsEpochCtor(SyntaxNode node, SemanticModel model) | ||
{ | ||
|
@@ -102,6 +109,9 @@ private static bool IsParameterNonExistingOrLiteralEqualTo(string parameterName, | |
private static bool IsLiteralAndEqualTo(SyntaxNode node, int value) => | ||
node is TLiteralExpression literal && IsValueEqualTo(literal, value); | ||
|
||
private static string CheckAndGetTypeName(SyntaxNode node, SemanticModel model) => | ||
model.GetTypeInfo(node).Type is var type && type.IsAny(TypesWithUnixEpochField) ? type.Name : null; | ||
|
||
private bool IsDateTimeKindNonExistingOrUtc(IMethodParameterLookup lookup) => | ||
!lookup.TryGetSyntax("kind", out var expressions) | ||
|| (expressions[0] is TMemberAccessExpression memberAccess && IsDateTimeKindUtc(memberAccess)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two if statements can be merged if you move
CheckAndGetTypeName
to the end of the conditions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, the conditions are different: the first one is checking whether you have 3 literals (2 with number
1
, 1 with number1970
) and then proceeds to check the type and constructor.The second one is only searching for the constructor with a single argument of
621355968000000000
or pointing at a const with that value, and then proceeds to check semantically the type (not the constructor, since we have only 1 ctor with 1 argument for DateTime/DateTimeOffset).Although it's feasible to merge everything in a single if statement, all the variants I tried are really cumbersome to read and understand. Happy to discuss it further