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

Add analyzer to detect use of Task<T>.ConfigureAwait with ConfigureAwaitOptions.SuppressThrowing #6669

Merged
merged 1 commit into from
Jun 28, 2023
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
1 change: 1 addition & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CA2261 | Usage | Warning | DoNotUseConfigureAwaitWithSuppressThrowing, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250)
Copy link

@StephenCleary StephenCleary Nov 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2261 ?

I believe this is the equivalent line in main:

CA2261 | Usage | Warning | DoNotUseConfigureAwaitWithSuppressThrowing, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. No idea how it ended up like that.

CA1510 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1510)
CA1511 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1511)
CA1512 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1512)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,15 @@
<data name="UseValueTasksCorrectlyMessage_AccessingIncompleteResult" xml:space="preserve">
<value>ValueTask instances should not have their result directly accessed unless the instance has already completed. Unlike Tasks, calling Result or GetAwaiter().GetResult() on a ValueTask is not guaranteed to block until the operation completes. If you can't simply await the instance, consider first checking its IsCompleted property (or asserting it's true if you know that to be the case).</value>
</data>
<data name="DoNotUseConfigureAwaitWithSuppressThrowingTitle" xml:space="preserve">
<value>Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</value>
</data>
<data name="DoNotUseConfigureAwaitWithSuppressThrowingMessage" xml:space="preserve">
<value>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</value>
</data>
<data name="DoNotUseConfigureAwaitWithSuppressThrowingDescription" xml:space="preserve">
<value>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</value>
</data>
<data name="DoNotCreateTaskCompletionSourceWithWrongArgumentsTitle" xml:space="preserve">
<value>Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum instead of TaskContinuationOptions enum</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.Analyzers.Tasks
{
using static MicrosoftNetCoreAnalyzersResources;

/// <summary>
/// CA2261: <inheritdoc cref="DoNotUseConfigureAwaitWithSuppressThrowingTitle"/>
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class DoNotUseConfigureAwaitWithSuppressThrowing : DiagnosticAnalyzer
{
internal const string RuleId = "CA2261";

internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(RuleId,
CreateLocalizableResourceString(nameof(DoNotUseConfigureAwaitWithSuppressThrowingTitle)),
CreateLocalizableResourceString(nameof(DoNotUseConfigureAwaitWithSuppressThrowingMessage)),
DiagnosticCategory.Usage,
RuleLevel.BuildWarning,
CreateLocalizableResourceString(nameof(DoNotUseConfigureAwaitWithSuppressThrowingDescription)),
isPortedFxCopRule: false,
isDataflowRule: false);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(compilationContext =>
{
// Only proceed if we have the Task<T>.ConfigureAwait(ConfigureAwaitOptions) method and if ConfigureAwaitOptions.SuppressThrowing is defined.
if (!compilationContext.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1, out var genericTask) ||
!compilationContext.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksConfigureAwaitOptions, out var configureAwaitOptionsType) ||
configureAwaitOptionsType.TypeKind != TypeKind.Enum ||
genericTask
.GetMembers("ConfigureAwait")
.OfType<IMethodSymbol>()
.Where(m => SymbolEqualityComparer.Default.Equals(m.ContainingType, genericTask) && m.Parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, configureAwaitOptionsType))
.FirstOrDefault() is not IMethodSymbol configureAwait ||
configureAwaitOptionsType.GetMembers("SuppressThrowing").FirstOrDefault() is not IFieldSymbol suppressThrowing ||
!DiagnosticHelpers.TryConvertToUInt64(suppressThrowing.ConstantValue, configureAwaitOptionsType.EnumUnderlyingType.SpecialType, out ulong suppressThrowingValue))
{
return;
}

// Raise a diagnostic if the invocation is to Task<T>.ConfigureAwait with a constant value that includes SuppressThrowing
compilationContext.RegisterOperationAction(operationContext =>
{
IInvocationOperation invocation = (IInvocationOperation)operationContext.Operation;

if (SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.OriginalDefinition, configureAwait) &&
invocation.Arguments.Length == 1 &&
invocation.Arguments[0].Value is IOperation { ConstantValue.HasValue: true } arg &&
DiagnosticHelpers.TryConvertToUInt64(arg.ConstantValue.Value, configureAwaitOptionsType.EnumUnderlyingType.SpecialType, out ulong argValue) &&
(argValue & suppressThrowingValue) != 0)
{
operationContext.ReportDiagnostic(arg.CreateDiagnostic(Rule));
}
}, OperationKind.Invocation);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,21 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E
<target state="translated">{0} používá prolomený kryptografický algoritmus {1}.</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingDescription">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingMessage">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingTitle">
<source>Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</source>
<target state="new">Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseCountAsyncWhenAnyAsyncCanBeUsedDescription">
<source>For non-empty collections, CountAsync() and LongCountAsync() enumerate the entire sequence, while AnyAsync() stops at the first item or the first item that satisfies a condition.</source>
<target state="translated">U neprázdných kolekcí CountAsync() a LongCountAsync() zobrazí výčet celé sekvence, zatímco AnyAsync() se zastaví u první položky nebo u první položky, která splňuje podmínku.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,21 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type
<target state="translated">"{0}" verwendet einen beschädigten kryptografischen Algorithmus: {1}</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingDescription">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingMessage">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingTitle">
<source>Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</source>
<target state="new">Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseCountAsyncWhenAnyAsyncCanBeUsedDescription">
<source>For non-empty collections, CountAsync() and LongCountAsync() enumerate the entire sequence, while AnyAsync() stops at the first item or the first item that satisfies a condition.</source>
<target state="translated">Bei nicht leeren Sammlungen listen CountAsync() und LongCountAsync() die gesamte Sequenz auf, während AnyAsync() beim ersten Element oder bei dem ersten Element, das eine Bedingung erfüllt, beendet wird.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,21 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip
<target state="translated">{0} usa un algoritmo criptográfico dañado {1}.</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingDescription">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingMessage">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingTitle">
<source>Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</source>
<target state="new">Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseCountAsyncWhenAnyAsyncCanBeUsedDescription">
<source>For non-empty collections, CountAsync() and LongCountAsync() enumerate the entire sequence, while AnyAsync() stops at the first item or the first item that satisfies a condition.</source>
<target state="translated">Para colecciones no vacías, CountAsync() y LongCountAsync() enumeran la secuencia completa, mientras que AnyAsync() se detiene en el primer elemento o en el primer elemento que satisface una condición.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,21 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en
<target state="translated">{0} utilise un algorithme de chiffrement cassé {1}</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingDescription">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingMessage">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingTitle">
<source>Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</source>
<target state="new">Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseCountAsyncWhenAnyAsyncCanBeUsedDescription">
<source>For non-empty collections, CountAsync() and LongCountAsync() enumerate the entire sequence, while AnyAsync() stops at the first item or the first item that satisfies a condition.</source>
<target state="translated">Pour les collections non vides, CountAsync() et LongCountAsync() énumèrent la séquence entière, alors que AnyAsync() s'arrête au premier élément ou au premier élément qui satisfait une condition.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,21 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi
<target state="translated">{0} usa un algoritmo di crittografia violato {1}</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingDescription">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingMessage">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingTitle">
<source>Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</source>
<target state="new">Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseCountAsyncWhenAnyAsyncCanBeUsedDescription">
<source>For non-empty collections, CountAsync() and LongCountAsync() enumerate the entire sequence, while AnyAsync() stops at the first item or the first item that satisfies a condition.</source>
<target state="translated">Nel caso di raccolte non vuote CountAsync() e LongCountAsync() enumerano l'intera sequenza, mentre AnyAsync() si arresta in corrispondenza del primo elemento o del primo elemento che soddisfa una condizione.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,21 @@ Enumerable.OfType&lt;T&gt; で使用されるジェネリック型チェック (
<target state="translated">{0} が、破られた暗号アルゴリズム {1} を使用します</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingDescription">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task, not a Task&lt;TResult&gt;. To use it with a Task&lt;TResult&gt;, first cast to the base Task.</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingMessage">
<source>The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</source>
<target state="new">The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseConfigureAwaitWithSuppressThrowingTitle">
<source>Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</source>
<target state="new">Do not use ConfigureAwaitOptions.SuppressThrowing with Task&lt;TResult&gt;</target>
<note />
</trans-unit>
<trans-unit id="DoNotUseCountAsyncWhenAnyAsyncCanBeUsedDescription">
<source>For non-empty collections, CountAsync() and LongCountAsync() enumerate the entire sequence, while AnyAsync() stops at the first item or the first item that satisfies a condition.</source>
<target state="translated">空ではないコレクションで、CountAsync() と LongCountAsync() ではシーケンス全体が列挙されますが、AnyAsync() では最初の項目または条件を満たす最初の項目で停止します。</target>
Expand Down
Loading