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

AK2001: detect when automatically handled messages are being handled inside MessageExtractor / IMessageExtractor #43

Merged
merged 30 commits into from
Jan 8, 2024
Merged
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
024b6ce
added Clustering / Sharding / Tools symbols
Aaronontheweb Jan 6, 2024
ab7a049
defined `IAkkaClusterContext`
Aaronontheweb Jan 6, 2024
0ab8bb9
working on sharding context
Aaronontheweb Jan 6, 2024
2d3e508
Merge branch 'dev' into cluster-sharding-handler
Aaronontheweb Jan 8, 2024
c83a436
added AK2001 rule description
Aaronontheweb Jan 8, 2024
e2ed0d1
added Akka.Cluster.Sharding context
Aaronontheweb Jan 8, 2024
e753993
updated `ReferenceAssembliesHelper`
Aaronontheweb Jan 8, 2024
66a0c8f
first pass at `AK2001`
Aaronontheweb Jan 8, 2024
470fa46
implemented test
Aaronontheweb Jan 8, 2024
6fbb150
need to fix interface analysis
Aaronontheweb Jan 8, 2024
1586178
fixing up how we evaluate the use of bad types
Aaronontheweb Jan 8, 2024
51e48e6
have test case now passing
Aaronontheweb Jan 8, 2024
7eb815b
added another failing test case
Aaronontheweb Jan 8, 2024
fab4a58
added case to detect multiple failures
Aaronontheweb Jan 8, 2024
72e23a0
added more complex test cases
Aaronontheweb Jan 8, 2024
8dd7352
added case for custom `IMessageExtractor`
Aaronontheweb Jan 8, 2024
138a137
refactoring and extracting methods
Aaronontheweb Jan 8, 2024
838b20f
added `HashCodeMessageExtractor.Create` edge case detection
Aaronontheweb Jan 8, 2024
e0f6e51
added `HashCodeMessageExtractor` success case
Aaronontheweb Jan 8, 2024
b15bbd4
adding fixer for `AK2001`
Aaronontheweb Jan 8, 2024
1227111
first stab at CodeFix implementation
Aaronontheweb Jan 8, 2024
f8b6ca0
avoid namespaces for AK2000 fixes
Aaronontheweb Jan 8, 2024
4a4ff2d
adding Fixer specs
Aaronontheweb Jan 8, 2024
38a90f3
creating some failure test cases
Aaronontheweb Jan 8, 2024
fa9a24b
added code write for removing switch expression arm
Aaronontheweb Jan 8, 2024
64a8ff8
added test for two `switch` expression arms
Aaronontheweb Jan 8, 2024
70e449a
fixed `else if` case
Aaronontheweb Jan 8, 2024
d10d3c4
fixed `case` section
Aaronontheweb Jan 8, 2024
e125b04
added test case for multiple `case` statements
Aaronontheweb Jan 8, 2024
b5519dc
ensured that cases with extra filtering are also handled
Aaronontheweb Jan 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactoring and extracting methods
Aaronontheweb committed Jan 8, 2024
commit 138a137427d9ae419005344b9397fa5e575b2fe5
Original file line number Diff line number Diff line change
@@ -314,7 +314,7 @@ IMessageExtractor Create(){
public async Task FailureCase((string testData, (int startLine, int startColumn, int endLine, int endColumn)[] spanData) d)
{
var (testData, spanData) = d;
DiagnosticResult[] expectedDiagnostics = new DiagnosticResult[spanData.Length];
var expectedDiagnostics = new DiagnosticResult[spanData.Length];
var currentDiagnosticIndex = 0;

// there can be multiple violations per test case
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
// </copyright>
// -----------------------------------------------------------------------

using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -26,73 +25,85 @@ public override void AnalyzeCompilation(CompilationStartAnalysisContext context,
if (akkaContext.HasAkkaClusterShardingInstalled == false)
return; // exit early if we don't have Akka.Cluster.Sharding installed

var methodDeclaration = (MethodDeclarationSyntax)ctx.Node;
var semanticModel = ctx.SemanticModel;
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration);
AnalyzeMethod(ctx, akkaContext);
}, SyntaxKind.MethodDeclaration);
}

INamedTypeSymbol? messageExtractorSymbol = akkaContext.AkkaClusterSharding.IMessageExtractorType;
private static void AnalyzeMethod(SyntaxNodeAnalysisContext ctx, AkkaContext akkaContext)
{
var methodDeclaration = (MethodDeclarationSyntax)ctx.Node;
var semanticModel = ctx.SemanticModel;
var methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration);

if (methodSymbol == null || messageExtractorSymbol == null)
return;
INamedTypeSymbol? messageExtractorSymbol = akkaContext.AkkaClusterSharding.IMessageExtractorType;

var containingTypeIsMessageExtractor = methodSymbol.ContainingType.AllInterfaces.Any(i =>
SymbolEqualityComparer.Default.Equals(i, messageExtractorSymbol));
if (methodSymbol == null || messageExtractorSymbol == null)
return;

if (!containingTypeIsMessageExtractor)
return;
var containingTypeIsMessageExtractor = methodSymbol.ContainingType.AllInterfaces.Any(i =>
SymbolEqualityComparer.Default.Equals(i, messageExtractorSymbol));

var messageExtractorMethods = messageExtractorSymbol.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.Name is "EntityMessage" or "EntityId")
.ToArray();
if (!containingTypeIsMessageExtractor)
return;

var forbiddenTypes = new[]
{ akkaContext.AkkaClusterSharding.StartEntityType, akkaContext.AkkaClusterSharding.ShardEnvelopeType };
var messageExtractorMethods = messageExtractorSymbol.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.Name is "EntityMessage" or "EntityId")
.ToArray();

var forbiddenTypes = new[]
{ akkaContext.AkkaClusterSharding.StartEntityType, akkaContext.AkkaClusterSharding.ShardEnvelopeType };

var reportedLocations = new HashSet<Location>();
var reportedLocations = new HashSet<Location>();

// we know for sure that we are inside a message extractor now
foreach (var interfaceMember in methodSymbol.ContainingType.AllInterfaces.SelectMany(i =>
i.GetMembers().OfType<IMethodSymbol>()))
// we know for sure that we are inside a message extractor now
foreach (var interfaceMember in methodSymbol.ContainingType.AllInterfaces.SelectMany(i =>
i.GetMembers().OfType<IMethodSymbol>()))
{
foreach (var extractorMethod in messageExtractorMethods)
{
foreach (var extractorMethod in messageExtractorMethods)
if (SymbolEqualityComparer.Default.Equals(interfaceMember, extractorMethod))
{
if (SymbolEqualityComparer.Default.Equals(interfaceMember, extractorMethod))
// Retrieve all the descendant nodes of the method that are expressions
var descendantNodes = methodDeclaration.DescendantNodes();

foreach (var node in descendantNodes)
{
// Retrieve all the descendant nodes of the method that are expressions
var descendantNodes = methodDeclaration.DescendantNodes();

foreach (var node in descendantNodes)
{
switch (node)
{
case DeclarationPatternSyntax declarationPatternSyntax:
{
// get the symbol for the declarationPatternSyntax.Type
var variableType = semanticModel.GetTypeInfo(declarationPatternSyntax.Type).Type;

if (forbiddenTypes.Any(t => SymbolEqualityComparer.Default.Equals(t, variableType)))
{
var location = declarationPatternSyntax.GetLocation();

// duplicate
if(reportedLocations.Contains(location))
break;
var diagnostic = Diagnostic.Create(
RuleDescriptors
.Ak2001DoNotUseAutomaticallyHandledMessagesInShardMessageExtractor,
location);
ctx.ReportDiagnostic(diagnostic);
reportedLocations.Add(location);
}

break;
}
}
}
AnalyzeDeclaredVariableNodes(ctx, node, forbiddenTypes, reportedLocations);
}
}
}
}, SyntaxKind.MethodDeclaration);
}
}

private static void AnalyzeDeclaredVariableNodes(SyntaxNodeAnalysisContext ctx, SyntaxNode node,
INamedTypeSymbol?[] forbiddenTypes, HashSet<Location> reportedLocations)
{
var semanticModel = ctx.SemanticModel;
switch (node)
{
case DeclarationPatternSyntax declarationPatternSyntax:
{
// get the symbol for the declarationPatternSyntax.Type
var variableType = semanticModel.GetTypeInfo(declarationPatternSyntax.Type).Type;

if (forbiddenTypes.Any(t => SymbolEqualityComparer.Default.Equals(t, variableType)))
{
var location = declarationPatternSyntax.GetLocation();

// duplicate
if(reportedLocations.Contains(location))
break;
var diagnostic = Diagnostic.Create(
RuleDescriptors
.Ak2001DoNotUseAutomaticallyHandledMessagesInShardMessageExtractor,
location);
ctx.ReportDiagnostic(diagnostic);
reportedLocations.Add(location);
}

break;
}
}
}
}