Skip to content

Commit 9838782

Browse files
authored
Add AK1008 - Should not use ActorSystem to create child actor (#113)
1 parent 1ca52c8 commit 9838782

File tree

8 files changed

+623
-1
lines changed

8 files changed

+623
-1
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="ShouldNotUseSystemToCreateChildActorsAnalyzerSpecs.cs" company="Akka.NET Project">
3+
// Copyright (C) 2013-2025 .NET Foundation <https://github.com/akkadotnet/akka.net>
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
7+
using Microsoft.CodeAnalysis;
8+
using Verify = Akka.Analyzers.Tests.Utility.AkkaVerifier<Akka.Analyzers.ShouldNotUseSystemToCreateChildActorsAnalyzer>;
9+
10+
namespace Akka.Analyzers.Tests.Analyzers.AK1000;
11+
12+
public class ShouldNotUseSystemToCreateChildActorsAnalyzerSpecs
13+
{
14+
public static readonly TheoryData<string> SuccessCases = new()
15+
{
16+
// Non-actor class uses ActorSystem to create an actor
17+
"""
18+
using Akka.Actor;
19+
20+
public class SaveClass
21+
{
22+
public SaveClass(ActorSystem system)
23+
{
24+
system.ActorOf(Props.Create(() => new ChildActor())); // Shouldn't flag this
25+
system.ActorOf<ChildActor>(); // Shouldn't flag this
26+
}
27+
}
28+
29+
public class ChildActor : ReceiveActor
30+
{
31+
}
32+
""",
33+
34+
// Actors using Context to create child actors
35+
"""
36+
using Akka.Actor;
37+
38+
public class MyActor : ReceiveActor
39+
{
40+
public MyActor()
41+
{
42+
Context.ActorOf(Props.Create(() => new ChildActor())); // Shouldn't flag this
43+
Context.ActorOf<ChildActor>(); // Shouldn't flag this
44+
}
45+
}
46+
47+
public class ChildActor : ReceiveActor
48+
{
49+
}
50+
""",
51+
};
52+
53+
public static readonly
54+
TheoryData<(string testData, (int startLine, int startColumn, int endLine, int endColumn) spanData)>
55+
FailureCases = new()
56+
{
57+
// ReceiveActor invoking ActorSystem.ActorOf() directly
58+
(
59+
"""
60+
// 01
61+
using Akka.Actor;
62+
63+
public class MyActor : ReceiveActor
64+
{
65+
public MyActor(ActorSystem system)
66+
{
67+
system.ActorOf(Props.Create(() => new ChildActor()));
68+
}
69+
}
70+
71+
public class ChildActor : ReceiveActor
72+
{
73+
}
74+
""", (8, 9, 8, 61)),
75+
76+
// ReceiveActor invoking Context.System.ActorOf()
77+
(
78+
"""
79+
// 02
80+
using Akka.Actor;
81+
82+
public class MyActor : ReceiveActor
83+
{
84+
public MyActor()
85+
{
86+
Context.System.ActorOf(Props.Create(() => new ChildActor()));
87+
}
88+
}
89+
90+
public class ChildActor : ReceiveActor
91+
{
92+
}
93+
""", (8, 9, 8, 69)),
94+
95+
// ReceiveActor invoking ActorOf<T>() extension on ActorSystem directly
96+
(
97+
"""
98+
// 03
99+
using Akka.Actor;
100+
101+
public class MyActor : ReceiveActor
102+
{
103+
public MyActor(ActorSystem system)
104+
{
105+
system.ActorOf<ChildActor>();
106+
}
107+
}
108+
109+
public class ChildActor : ReceiveActor
110+
{
111+
}
112+
""", (8, 9, 8, 37)),
113+
114+
// ReceiveActor invoking ActorOf<T>() extension on Context.System
115+
(
116+
"""
117+
// 04
118+
using Akka.Actor;
119+
120+
public class MyActor : ReceiveActor
121+
{
122+
public MyActor()
123+
{
124+
Context.System.ActorOf<ChildActor>();
125+
}
126+
}
127+
128+
public class ChildActor : ReceiveActor
129+
{
130+
}
131+
""", (8, 9, 8, 45)),
132+
133+
134+
// UntypedActor invoking ActorSystem.ActorOf() directly
135+
(
136+
"""
137+
// 05
138+
using Akka.Actor;
139+
140+
public class MyActor : UntypedActor
141+
{
142+
public MyActor(ActorSystem system)
143+
{
144+
system.ActorOf(Props.Create(() => new ChildActor()));
145+
}
146+
147+
protected override void OnReceive(object message)
148+
{
149+
throw new System.NotImplementedException();
150+
}
151+
}
152+
153+
public class ChildActor : ReceiveActor
154+
{
155+
}
156+
""", (8, 9, 8, 61)),
157+
158+
// UntypedActor invoking Context.System.ActorOf()
159+
(
160+
"""
161+
// 06
162+
using Akka.Actor;
163+
164+
public class MyActor : UntypedActor
165+
{
166+
public MyActor()
167+
{
168+
Context.System.ActorOf(Props.Create(() => new ChildActor()));
169+
}
170+
171+
protected override void OnReceive(object message)
172+
{
173+
throw new System.NotImplementedException();
174+
}
175+
}
176+
177+
public class ChildActor : ReceiveActor
178+
{
179+
}
180+
""", (8, 9, 8, 69)),
181+
182+
// UntypedActor invoking ActorOf<T>() extension on ActorSystem directly
183+
(
184+
"""
185+
// 07
186+
using Akka.Actor;
187+
188+
public class MyActor : UntypedActor
189+
{
190+
public MyActor(ActorSystem system)
191+
{
192+
system.ActorOf<ChildActor>();
193+
}
194+
195+
protected override void OnReceive(object message)
196+
{
197+
throw new System.NotImplementedException();
198+
}
199+
}
200+
201+
public class ChildActor : ReceiveActor
202+
{
203+
}
204+
""", (8, 9, 8, 37)),
205+
206+
// UntypedActor invoking ActorOf<T>() extension on Context.System
207+
(
208+
"""
209+
// 08
210+
using Akka.Actor;
211+
212+
public class MyActor : UntypedActor
213+
{
214+
public MyActor()
215+
{
216+
Context.System.ActorOf<ChildActor>();
217+
}
218+
219+
protected override void OnReceive(object message)
220+
{
221+
throw new System.NotImplementedException();
222+
}
223+
}
224+
225+
public class ChildActor : ReceiveActor
226+
{
227+
}
228+
""", (8, 9, 8, 45)),
229+
};
230+
231+
[Theory]
232+
[MemberData(nameof(SuccessCases))]
233+
public async Task SuccessCase(string testCode)
234+
{
235+
await Verify.VerifyAnalyzer(testCode).ConfigureAwait(true);
236+
}
237+
238+
[Theory]
239+
[MemberData(nameof(FailureCases))]
240+
public Task FailureCase(
241+
(string testCode, (int startLine, int startColumn, int endLine, int endColumn) spanData) d)
242+
{
243+
var expected = Verify.Diagnostic()
244+
.WithSpan(d.spanData.startLine, d.spanData.startColumn, d.spanData.endLine, d.spanData.endColumn)
245+
.WithSeverity(DiagnosticSeverity.Warning);
246+
247+
return Verify.VerifyAnalyzer(d.testCode, expected);
248+
}
249+
250+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="MustCloseOverSenderWhenUsingReceiveAsyncAnalyzer.cs" company="Akka.NET Project">
3+
// Copyright (C) 2013-2024 .NET Foundation <https://github.com/akkadotnet/akka.net>
4+
// </copyright>
5+
// -----------------------------------------------------------------------
6+
7+
using System.Collections.Immutable;
8+
using Akka.Analyzers.Context;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
14+
namespace Akka.Analyzers;
15+
16+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
17+
public class ShouldNotUseSystemToCreateChildActorsAnalyzer()
18+
: AkkaDiagnosticAnalyzer(RuleDescriptors.Ak1008ShouldNotUseSystemToCreateChildActor)
19+
{
20+
public override void AnalyzeCompilation(CompilationStartAnalysisContext context, AkkaContext akkaContext)
21+
{
22+
Guard.AssertIsNotNull(context);
23+
Guard.AssertIsNotNull(akkaContext);
24+
25+
context.RegisterSyntaxNodeAction(ctx =>
26+
{
27+
var invocationExpression = (InvocationExpressionSyntax)ctx.Node;
28+
var semanticModel = ctx.SemanticModel;
29+
30+
// Get the member symbol from the invocation expression
31+
if(semanticModel.GetSymbolInfo(invocationExpression.Expression).Symbol is not IMethodSymbol methodInvocationSymbol)
32+
return;
33+
34+
// Make sure we get the actual method if it's an extension method
35+
if (methodInvocationSymbol.IsExtensionMethod)
36+
{
37+
// Method must be accessing a member of an instance
38+
if(invocationExpression.Expression is not MemberAccessExpressionSyntax memberAccess)
39+
return;
40+
41+
// Accessed member must be of type `ActorSystem`
42+
var receiverType = semanticModel.GetTypeInfo(memberAccess.Expression).Type;
43+
if (receiverType is null)
44+
return;
45+
46+
if (!receiverType.IsDerivedOrImplements(akkaContext.AkkaCore.Actor.ActorSystemType!))
47+
return;
48+
49+
// Make sure that we're accessing the actual method
50+
while (methodInvocationSymbol.ReducedFrom is not null)
51+
{
52+
methodInvocationSymbol = methodInvocationSymbol.ReducedFrom;
53+
}
54+
55+
// Method must be the `ActorOf<T>()` extension method
56+
if (!ReferenceEquals(methodInvocationSymbol, akkaContext.AkkaCore.Actor.ActorRefFactoryExtensions.ActorOf))
57+
return;
58+
}
59+
else
60+
{
61+
// Check if the method matches any `ActorOf` methods
62+
if (!ReferenceEquals(methodInvocationSymbol, akkaContext.AkkaCore.Actor.ActorSystem.ActorOf))
63+
return;
64+
}
65+
66+
// Traverse up the parent nodes to see if any of them are ActorBase classes
67+
AssertInvocationIsInClassType(invocationExpression, semanticModel, akkaContext.AkkaCore.Actor.ActorBaseType, ctx);
68+
69+
}, SyntaxKind.InvocationExpression);
70+
}
71+
72+
/// <summary>
73+
/// Traverse up the parent nodes to see if any of them derived from a certain class
74+
/// </summary>
75+
private static void AssertInvocationIsInClassType(
76+
InvocationExpressionSyntax invocationExpression,
77+
SemanticModel semanticModel,
78+
INamedTypeSymbol? classType,
79+
SyntaxNodeAnalysisContext context)
80+
{
81+
if(classType is null)
82+
return;
83+
84+
var parent = invocationExpression.Parent;
85+
while (parent != null)
86+
{
87+
if (parent is ClassDeclarationSyntax classDeclaration)
88+
{
89+
var isActorType = classDeclaration.IsDerivedOrImplements(semanticModel, classType);
90+
if (isActorType)
91+
{
92+
// If found, report a diagnostic
93+
var diagnostic = Diagnostic.Create(
94+
descriptor: RuleDescriptors.Ak1008ShouldNotUseSystemToCreateChildActor,
95+
location: invocationExpression.GetLocation());
96+
context.ReportDiagnostic(diagnostic);
97+
return;
98+
}
99+
}
100+
101+
parent = parent.Parent;
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)