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

Improve Rule S2223: cleanup and performance #6761

Merged
merged 14 commits into from Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
25 changes: 15 additions & 10 deletions analyzers/src/SonarAnalyzer.CSharp/Rules/StaticFieldVisible.cs
Expand Up @@ -34,22 +34,27 @@ public sealed class StaticFieldVisible : SonarDiagnosticAnalyzer
context.RegisterNodeAction(
c =>
{
var fieldDeclaration = (FieldDeclarationSyntax)c.Node;
foreach (var diagnostic in GetDiagnostics(fieldDeclaration.Declaration, c.SemanticModel))
foreach (var diagnostic in GetDiagnostics((FieldDeclarationSyntax)c.Node, c.SemanticModel))
{
c.ReportIssue(diagnostic);
}
},
SyntaxKind.FieldDeclaration);

private static IEnumerable<Diagnostic> GetDiagnostics(VariableDeclarationSyntax declaration, SemanticModel semanticModel) =>
declaration.Variables
.Where(x => FieldIsRelevant(semanticModel.GetDeclaredSymbol(x) as IFieldSymbol))
.Select(x => Diagnostic.Create(Rule, x.Identifier.GetLocation(), x.Identifier.ValueText));
private static IEnumerable<Diagnostic> GetDiagnostics(FieldDeclarationSyntax declaration, SemanticModel semanticModel) =>
antonioaversa marked this conversation as resolved.
Show resolved Hide resolved
FieldIsRelevant(declaration)
? declaration.Declaration.Variables
.Where(x => !FieldIsThreadSafe(semanticModel.GetDeclaredSymbol(x) as IFieldSymbol))
antonioaversa marked this conversation as resolved.
Show resolved Hide resolved
.Select(x => Diagnostic.Create(Rule, x.Identifier.GetLocation(), x.Identifier.ValueText))
: Enumerable.Empty<Diagnostic>();

private static bool FieldIsRelevant(IFieldSymbol fieldSymbol) =>
fieldSymbol is {IsStatic: true, IsConst: false, IsReadOnly: false}
&& fieldSymbol.DeclaredAccessibility != Accessibility.Private
&& !fieldSymbol.GetAttributes().Any(x => x.AttributeClass.Is(KnownType.System_ThreadStaticAttribute));
private static bool FieldIsRelevant(FieldDeclarationSyntax node) =>
node.Modifiers.Count > 1
&& node.Modifiers.Any(SyntaxKind.StaticKeyword)
&& (!node.Modifiers.Any(SyntaxKind.PrivateKeyword) || (node.Modifiers.Any(SyntaxKind.PrivateKeyword) && node.Modifiers.Any(SyntaxKind.ProtectedKeyword)))
antonioaversa marked this conversation as resolved.
Show resolved Hide resolved
&& !node.Modifiers.Any(SyntaxKind.ReadOnlyKeyword);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A volatile field should also be irrelevant as volatile indicates some advanced multi-threading voodoo is going on here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe good to exclude - per MSDN docs

The volatile keyword indicates that a field might be modified by multiple threads that are executing at the same time.

On a multiprocessor system, a volatile read operation does not guarantee to obtain the latest value written to that memory location by any processor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, while I see a public static volatile non-readonly field as dangerous as a public static non-volatile non-readonly one, and it seems that there are almost always ways to have the field private (at least for the typical scenarios of sentinel loopers or counters), let's assume that the person using it has a sound multi-threading model and a solid understanding of it.

So I am OK with disabling the check when volatile is encountered.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@martin-strecker-sonarsource to resolve this one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@martin-strecker-sonarsource and I had an offiline convertation on the matter and agreed to the current solution regarding volatile modifier.


private static bool FieldIsThreadSafe(IFieldSymbol fieldSymbol) =>
fieldSymbol.GetAttributes().Any(x => x.AttributeClass.Is(KnownType.System_ThreadStaticAttribute));
}
}
Expand Up @@ -6,6 +6,9 @@ public record StaticFieldVisible
// ^^
public const double Pi2 = 3.14;
public double Pi3 = 3.14;
private protected static double Pi4 = 3.14; // Noncompliant
protected private static double Pi15 = 3.14; // Noncompliant

public static Shape Empty = Shape.Empty; // Noncompliant {{Change the visibility of 'Empty' or make it 'const' or 'readonly'.}}

[ThreadStatic]
Expand Down
Expand Up @@ -6,11 +6,29 @@ public class StaticFieldVisible
{
public static double Pi = 3.14; // Noncompliant
antonioaversa marked this conversation as resolved.
Show resolved Hide resolved
// ^^
public static int X = 1, Y, Z = 100;
// ^
// ^@-1
// ^@-2
public const double Pi2 = 3.14;
public double Pi3 = 3.14;

protected static double Pi4 = 3.14; // Noncompliant
internal static double Pi5 = 3.14; // Noncompliant
internal static double Pi6 = 3.14; // Noncompliant
protected internal static double Pi7 = 3.14; // Noncompliant
antonioaversa marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to align vertically all these // Noncompliant.


private static double Pi8 = 3.14;
private double Pi9 = 3.14;
static double Pi10 = 3.14; // Compliant - if no access modifier exist, the field is private
double Pi11 = 3.14;
public readonly double Pi12 = 3.14;

[ThreadStatic]
public static int value; // Compliant, thread static field values are not shared between threads
public static int Value; // Compliant, thread static field values are not shared between threads

[NonSerialized]
public static int Value2; // Noncompliant
}

public class Shape
Expand Down