Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
521b41b
Scaffold rule
martin-strecker-sonarsource Apr 15, 2024
3e44c6b
Split test cases and fix compiler errors
martin-strecker-sonarsource Apr 15, 2024
a6ddf4e
Remove CoreMetadataReference.SystemLinqQueryable
martin-strecker-sonarsource Apr 15, 2024
3ca5936
Remove SystemComponentModelTypeConverter facade
martin-strecker-sonarsource Apr 15, 2024
81f0c08
Add ISymbolExtensions for IsAwaitableNonDynamic
martin-strecker-sonarsource Apr 16, 2024
0832792
WIP: First implementation
martin-strecker-sonarsource Apr 16, 2024
7c06ec1
Make tests green
martin-strecker-sonarsource Apr 16, 2024
efbd46c
Fix test case for Net48
martin-strecker-sonarsource Apr 16, 2024
3d77f07
Refactor
martin-strecker-sonarsource Apr 16, 2024
be4cba5
Fix test failures
martin-strecker-sonarsource Apr 16, 2024
8fb42ab
ITs Akka
martin-strecker-sonarsource Apr 16, 2024
6a27444
Rework type lookups for candidates
martin-strecker-sonarsource Apr 16, 2024
d05eb3c
Add FNs for local functions.
martin-strecker-sonarsource Apr 17, 2024
6f26336
Refactorings
martin-strecker-sonarsource Apr 17, 2024
df0c267
Improve await placement
martin-strecker-sonarsource Apr 17, 2024
3c47551
Remove uneccessary Parenthesis removal
martin-strecker-sonarsource Apr 17, 2024
42d8966
Add more overload tests
martin-strecker-sonarsource Apr 17, 2024
ef44048
Address SQ issues
martin-strecker-sonarsource Apr 17, 2024
a5274db
Code smell
martin-strecker-sonarsource Apr 17, 2024
38dc0e2
Update ITs
martin-strecker-sonarsource Apr 17, 2024
8ab8ee9
Apply suggestions from code review
martin-strecker-sonarsource Apr 18, 2024
9acfd85
Clean-up
martin-strecker-sonarsource Apr 18, 2024
5b9eb09
Add support for SocketTaskExtensions
martin-strecker-sonarsource Apr 18, 2024
fad1172
FP: Fix support for lambdas
martin-strecker-sonarsource Apr 18, 2024
2ad3efa
Fix EnclosingScope and tests
martin-strecker-sonarsource Apr 18, 2024
e7872d1
Simplify test case
martin-strecker-sonarsource Apr 18, 2024
d234de4
Use AnalyzerLanguage and expose OutputKind in SyntaxNodeExtensionsTest
martin-strecker-sonarsource Apr 18, 2024
83fcd4f
Fix conditional compilation
martin-strecker-sonarsource Apr 18, 2024
84c541e
Add EnumMember and Parameter to EnclosingScopeSyntaxKinds and tests
martin-strecker-sonarsource Apr 18, 2024
f88f805
Fix ISymbolExtensions annotations for Roslyn copied code.
martin-strecker-sonarsource Apr 18, 2024
3e31d6b
Improve IsAsyncCodeBlock and add more tests
martin-strecker-sonarsource Apr 18, 2024
6ef7f51
Remove whitespace
martin-strecker-sonarsource Apr 18, 2024
3d79d9d
Unify IsAsyncCodeBlock cases
martin-strecker-sonarsource Apr 18, 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
10 changes: 10 additions & 0 deletions analyzers/its/expected/BlazorSample/S6966-BlazorSample-net7.0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Issues": [
{
"Id": "S6966",
"Message": "Await RunAsync instead.",
"Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/BlazorSample/BlazorSample/Program.cs#L29",
"Location": "Line 29 Position 1-10"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Issues": [
{
"Id": "S6966",
"Message": "Await RenderSectionAsync instead.",
"Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/CSharpLatest/NetCore31WithConfigurableRules/Views/Shared/_Layout.cshtml#L46",
"Location": "Line 46 Position 7-48"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Issues": [
{
"Id": "S6966",
"Message": "Await RunAsync instead.",
"Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/ManuallyAddedNoncompliantIssues.CS/AspNetCore6/Program.cs#L27",
"Location": "Line 27 Position 1-10"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Issues": [
{
"Id": "S6966",
"Message": "Await RunAsync instead.",
"Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/ManuallyAddedNoncompliantIssues.CS/AspNetCore7/Program.cs#L27",
"Location": "Line 27 Position 1-10"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Issues": [
{
"Id": "S6966",
"Message": "Await RunAsync instead.",
"Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/ManuallyAddedNoncompliantIssues.CS/AspNetCore8/Program.cs#L27",
"Location": "Line 27 Position 1-10"
}
]
}
10 changes: 10 additions & 0 deletions analyzers/its/expected/RazorSample/S6966-RazorSample-net7.0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Issues": [
{
"Id": "S6966",
"Message": "Await RunAsync instead.",
"Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/RazorSample/RazorSample/Program.cs#L25",
"Location": "Line 25 Position 1-10"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Issues": [
{
"Id": "S6966",
"Message": "Await StartProxyAsync instead.",
"Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/akka.net/src/contrib/cluster/Akka.Cluster.Sharding/ClusterSharding.cs#L742",
"Location": "Line 742 Position 24-92"
}
]
}
70 changes: 70 additions & 0 deletions analyzers/rspec/cs/S6966.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<p>In an <code>async</code> method, any blocking operations should be avoided.</p>
<h2>Why is this an issue?</h2>
<p>Using a synchronous method instead of its <a href="https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/">asynchronous</a>
counterpart in an <code>async</code> method blocks the execution and is considered bad practice for several reasons:</p>
<dl>
<dt>
Resource Utilization
</dt>
<dd>
<p>Each thread consumes system resources, such as memory. When a thread is blocked, it’s not doing any useful work, but it’s still consuming these
resources. This can lead to inefficient use of system resources.</p>
</dd>
<dt>
Scalability
</dt>
<dd>
<p>Blocking threads can limit the scalability of your application. In a high-load scenario where many operations are happening concurrently, each
blocked thread represents a missed opportunity to do useful work. This can prevent your application from effectively handling increased load.</p>
</dd>
<dt>
Performance
</dt>
<dd>
<p>Blocking threads can degrade the performance of your application. If all threads in the thread pool become blocked, new tasks can’t start
executing until an existing task completes and frees up a thread. This can lead to delays and poor responsiveness.</p>
</dd>
</dl>
<p>Instead of blocking, it’s recommended to use the <a
href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/await"><code>async</code> operator</a> with async methods. This
allows the system to release the current thread back to the thread pool until the awaited task is complete, improving scalability and
responsiveness.</p>
<h2>How to fix it</h2>
<h3>Code examples</h3>
<h4>Noncompliant code example</h4>
<pre data-diff-id="1" data-diff-type="noncompliant">
public async Task Examples(Stream stream, DbSet&lt;Person&gt; dbSet)
{
stream.Read(array, 0, 1024); // Noncompliant
File.ReadAllLines("path"); // Noncompliant
dbSet.ToList(); // Noncompliant in Entity Framework Core queries
dbSet.FirstOrDefault(x =&gt; x.Age &gt;= 18); // Noncompliant in Entity Framework Core queries
}
</pre>
<h4>Compliant solution</h4>
<pre data-diff-id="1" data-diff-type="compliant">
public async Task Examples(Stream stream, DbSet&lt;Person&gt; dbSet)
{
await stream.ReadAsync(array, 0, 1024);
await File.ReadAllLinesAsync("path");
await dbSet.ToListAsync();
await dbSet.FirstOrDefaultAsync(x =&gt; x.Age &gt;= 18);
}
</pre>
<h2>Resources</h2>
<h3>Documentation</h3>
<ul>
<li> Microsoft Learn - <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/async">async (C# Reference)</a> </li>
<li> Microsoft Learn - <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/await">await operator - asynchronously
await for a task to complete</a> </li>
</ul>
<h3>Articles &amp; blog posts</h3>
<ul>
<li> Microsoft Learn - <a href="https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/#dont-block-await-instead">Asynchronous
programming with async and await - Don’t block, await instead</a> </li>
<li> Microsoft Learn - <a
href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming">Async/Await - Best
Practices in Asynchronous Programming</a> </li>
<li> Microsoft Developer Blog - <a href="https://devblogs.microsoft.com/pfxteam/asyncawait-faq/">Async/Await FAQ</a> </li>
</ul>

23 changes: 23 additions & 0 deletions analyzers/rspec/cs/S6966.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"title": "Awaitable method should be used",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"async-await"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-6966",
"sqKey": "S6966",
"scope": "All",
"quickfix": "targeted",
"code": {
"impacts": {
"RELIABILITY": "MEDIUM"
},
"attribute": "COMPLETE"
}
}
2 changes: 1 addition & 1 deletion analyzers/rspec/cs/Sonar_way_profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,6 @@
"S6930",
"S6931",
"S6934",
"S6961"
"S6966"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,20 @@ public static bool TryGetExpressionComparedToNull(this ExpressionSyntax expressi

return false;
}

/// <summary>
/// Returns the expression, representing the left side of the dot. This is useful for finding the expression of an invoked expression. <br/>
/// For the expression of the invocation <c>M()</c> in the expression <c>this.A.B.M()</c> the member access <c>this.A.B</c> is returned and <br/>
/// for <c>this.A?.B?.M()</c> the member binding <c>.B</c> is returned.
/// </summary>
/// <param name="expression">The expression to start the search of. Should be an MemberAccess or a MemberBinding.</param>
/// <returns>The expression left of the dot or question mark dot.</returns>
public static ExpressionSyntax GetLeftOfDot(this ExpressionSyntax expression) =>
expression switch
{
MemberAccessExpressionSyntax memberAccessExpression => memberAccessExpression.Expression,
MemberBindingExpressionSyntax memberBindingExpression => memberBindingExpression.GetParentConditionalAccessExpression()?.Expression,
_ => null,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@ public static partial class SyntaxNodeExtensions
SyntaxKind.AddAccessorDeclaration,
SyntaxKind.AnonymousMethodExpression,
SyntaxKind.BaseConstructorInitializer,
SyntaxKind.CompilationUnit,
SyntaxKind.ConstructorDeclaration,
SyntaxKind.ConversionOperatorDeclaration,
SyntaxKind.DestructorDeclaration,
SyntaxKind.EqualsValueClause,
SyntaxKind.EnumMemberDeclaration,
SyntaxKind.FieldDeclaration,
SyntaxKind.GetAccessorDeclaration,
SyntaxKind.GlobalStatement,
SyntaxKindEx.InitAccessorDeclaration,
SyntaxKindEx.LocalFunctionStatement,
SyntaxKind.MethodDeclaration,
SyntaxKind.OperatorDeclaration,
SyntaxKind.Parameter,
SyntaxKind.ParenthesizedLambdaExpression,
SyntaxKindEx.PrimaryConstructorBaseType,
SyntaxKind.PropertyDeclaration,
SyntaxKind.RemoveAccessorDeclaration,
SyntaxKind.SetAccessorDeclaration,
SyntaxKind.SimpleLambdaExpression,
Expand Down
Loading