Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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"
}
}
3 changes: 2 additions & 1 deletion analyzers/rspec/cs/Sonar_way_profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@
"S6934",
"S6961",
"S6962",
"S6965"
"S6965",
"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,22 +31,32 @@ 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,
SyntaxKind.GroupClause,
SyntaxKindEx.InitAccessorDeclaration,
SyntaxKind.JoinClause,
SyntaxKind.LetClause,
SyntaxKindEx.LocalFunctionStatement,
SyntaxKind.MethodDeclaration,
SyntaxKind.OrderByClause,
SyntaxKind.OperatorDeclaration,
SyntaxKind.Parameter,
SyntaxKind.ParenthesizedLambdaExpression,
SyntaxKindEx.PrimaryConstructorBaseType,
SyntaxKind.PropertyDeclaration,
SyntaxKind.RemoveAccessorDeclaration,
SyntaxKind.QueryContinuation,
SyntaxKind.SelectClause,
SyntaxKind.SetAccessorDeclaration,
SyntaxKind.SimpleLambdaExpression,
SyntaxKind.ThisConstructorInitializer];
SyntaxKind.ThisConstructorInitializer,
SyntaxKind.WhereClause];

private static readonly SyntaxKind[] NegationOrConditionEnclosingSyntaxKinds = [
SyntaxKind.AnonymousMethodExpression,
Expand Down Expand Up @@ -564,7 +574,7 @@ public static bool IsFalse(this SyntaxNode node) =>
};

public static SyntaxNode EnclosingScope(this SyntaxNode node) =>
node.Ancestors().FirstOrDefault(x => x.IsAnyKind(EnclosingScopeSyntaxKinds));
node.AncestorsAndSelf().FirstOrDefault(x => x.IsAnyKind(EnclosingScopeSyntaxKinds));

private readonly record struct PathPosition(int Index, int TupleLength);

Expand Down
Loading