Skip to content

Commit

Permalink
[EC86] GC.Collect should not be called (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
GregoireBo authored May 6, 2024
1 parent ecf4b0c commit 7d012b4
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace EcoCode.Analyzers;

/// <summary>The code fix provider for avoid async void methods.</summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(GCCollectShouldNotBeCalledFixer)), Shared]
public sealed class GCCollectShouldNotBeCalledFixer: CodeFixProvider
{
/// <inheritdoc/>
public override ImmutableArray<string> FixableDiagnosticIds => [GCCollectShouldNotBeCalled.Descriptor.Id];

/// <inheritdoc/>
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

/// <inheritdoc/>
public override Task RegisterCodeFixesAsync(CodeFixContext context) => Task.CompletedTask;
}
66 changes: 66 additions & 0 deletions src/EcoCode.Core/Analyzers/EC86_GCCollectShouldNotBeCalled.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Linq;

namespace EcoCode.Analyzers;

/// <summary>Analyzer for avoid async void methods.</summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class GCCollectShouldNotBeCalled: DiagnosticAnalyzer
{
/// <summary>The diagnostic descriptor.</summary>
public static DiagnosticDescriptor Descriptor { get; } = new(
Rule.Ids.EC86_GCCollectShouldNotBeCalled,
title: "Avoid calling GC.Collect() method",
messageFormat: "Avoid calling GC.Collect() method",
Rule.Categories.Performance,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: null,
helpLinkUri: Rule.GetHelpUri(Rule.Ids.EC86_GCCollectShouldNotBeCalled));

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Descriptor];

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterSyntaxNodeAction(static context => AnalyzeMethod(context), SyntaxKind.InvocationExpression);
}

private static void AnalyzeMethod(SyntaxNodeAnalysisContext context)
{
var invocationExpression = (InvocationExpressionSyntax)context.Node;

//if the expression is not a method or method name is not GC.Collect or Containing type is not System.GC, return
if (context.SemanticModel.GetSymbolInfo(invocationExpression).Symbol is not IMethodSymbol methodSymbol
|| methodSymbol.Name != nameof(GC.Collect)
|| !SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType,
context.SemanticModel.Compilation.GetTypeByMetadataName("System.GC")))
{
return;
}

//If there is no arguments or the "generation" argument is not 0, raise report
bool report = !invocationExpression.ArgumentList.Arguments.Any();
if (!report)
{
var firstArgument = invocationExpression.ArgumentList.Arguments[0];
if (firstArgument.NameColon is not null) // Named argument, may not be the one we want
{
string firstParameterName = methodSymbol.Parameters[0].Name; // Parameter name from the method signature
if (firstArgument.NameColon.Name.Identifier.Text != firstParameterName)
{
firstArgument = invocationExpression.ArgumentList.Arguments
.First(arg => arg.NameColon?.Name.Identifier.Text == firstParameterName);
}
}
var constantValue = context.SemanticModel.GetConstantValue(firstArgument.Expression);
if (constantValue.Value is not int intValue || intValue != 0)
report = true;
}

if(report)
context.ReportDiagnostic(Diagnostic.Create(Descriptor, invocationExpression.GetLocation()));
}
}
1 change: 1 addition & 0 deletions src/EcoCode.Core/Models/Rule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ public static class Ids
public const string EC83_ReplaceEnumToStringWithNameOf = "EC83";
public const string EC84_AvoidAsyncVoidMethods = "EC84";
public const string EC85_MakeTypeSealed = "EC85";
public const string EC86_GCCollectShouldNotBeCalled = "EC86";
}
}
121 changes: 121 additions & 0 deletions src/EcoCode.Tests/Tests/EC86_GCCollectShouldNotBeCalledUnitTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
namespace EcoCode.Tests;

[TestClass]
public class GCCollectShouldNotBeCalledUnitTests
{
private static readonly CodeFixerDlg VerifyAsync = TestRunner.VerifyAsync<
GCCollectShouldNotBeCalled,
GCCollectShouldNotBeCalledFixer>;

[TestMethod]
public async Task EmptyCodeAsync() => await VerifyAsync("").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledAsync() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
[|GC.Collect()|];
}
}
""").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledSystemAsync() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
[|System.GC.Collect()|];
}
}
""").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledNamedArgumentsAsync() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
[|GC.Collect(mode: GCCollectionMode.Optimized, generation: 1)|];
}
}
""").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledMultipleCodeAsync() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
string text=""; [|GC.Collect()|]; string text2 = "";
}
}
""").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledCommentedAsync() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
//GC.Collect();
}
}
""").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledGeneration0Async() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
GC.Collect(0);
}
}
""").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledGeneration10Async() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
[|GC.Collect(10)|];
}
}
""").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledGeneratio0CollectionModeAsync() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
GC.Collect(0, GCCollectionMode.Forced);
}
}
""").ConfigureAwait(false);

[TestMethod]
public async Task GCCollectShouldNotBeCalledGeneration10CollectionModeAsync() => await VerifyAsync("""
using System;
public static class Program
{
public static async void Main()
{
[|GC.Collect(10, GCCollectionMode.Forced)|];
}
}
""").ConfigureAwait(false);

}

0 comments on commit 7d012b4

Please sign in to comment.