diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 68b6a480e9..a82b9f618e 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -12,6 +12,7 @@ CA1842 | Performance | Info | DoNotUseWhenAllOrWaitAllWithSingleArgument, [Docum CA1843 | Performance | Info | DoNotUseWhenAllOrWaitAllWithSingleArgument, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1843) CA1844 | Performance | Info | ProvideStreamMemoryBasedAsyncOverrides, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1844) CA1845 | Performance | Info | UseSpanBasedStringConcat, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845) +CA2250 | Usage | Info | UseCancellationTokenThrowIfCancellationRequested, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250) ### Removed Rules diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index b42dc67e73..723dba5b18 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1534,6 +1534,18 @@ and all other platforms This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + Use 'ThrowIfCancellationRequested' + + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + To improve performance, override the memory-based async methods when subclassing 'Stream'. Then implement the array-based methods in terms of the memory-based methods. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequested.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequested.Fixer.cs new file mode 100644 index 0000000000..d6118ceae4 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequested.Fixer.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Operations; +using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.UseCancellationTokenThrowIfCancellationRequested.RequiredSymbols; +using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + /// + /// Use instead of checking and + /// throwing . + /// + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public sealed class UseCancellationTokenThrowIfCancellationRequestedFixer : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseCancellationTokenThrowIfCancellationRequested.RuleId); + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SemanticModel model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (!RequiredSymbols.TryGetSymbols(model.Compilation, out RequiredSymbols symbols)) + return; + SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode node = root.FindNode(context.Span); + if (model.GetOperation(node, context.CancellationToken) is not IConditionalOperation conditional) + return; + + Func> createChangedDocument; + if (symbols.IsSimpleAffirmativeCheck(conditional, out IPropertyReferenceOperation? propertyReference)) + { + // For simple checks of the form: + // if (token.IsCancellationRequested) + // throw new OperationCanceledException(); + // Replace with: + // token.ThrowIfCancellationRequested(); + // + // For simple checks of the form: + // if (token.IsCancellationRequested) + // throw new OperationCanceledException(); + // else + // Frob(); + // Replace with: + // token.ThrowIfCancellationRequested(); + // Frob(); + createChangedDocument = async token => + { + var editor = await DocumentEditor.CreateAsync(context.Document, token).ConfigureAwait(false); + SyntaxNode expressionStatement = CreateThrowIfCancellationRequestedExpressionStatement(editor, conditional, propertyReference); + editor.ReplaceNode(conditional.Syntax, expressionStatement); + + if (conditional.WhenFalse is IBlockOperation block) + { + editor.InsertAfter(expressionStatement, block.Operations.Select(x => x.Syntax.WithAdditionalAnnotations(Formatter.Annotation))); + } + else if (conditional.WhenFalse is not null) + { + editor.InsertAfter(expressionStatement, conditional.WhenFalse.Syntax); + } + + return editor.GetChangedDocument(); + }; + } + else if (symbols.IsNegatedCheckWithThrowingElseClause(conditional, out propertyReference)) + { + // For negated checks of the form: + // if (!token.IsCancellationRequested) { DoStatements(); } + // else { throw new OperationCanceledException(); } + // Replace with: + // token.ThrowIfCancellationRequested(); + // DoStatements(); + createChangedDocument = async token => + { + var editor = await DocumentEditor.CreateAsync(context.Document, token).ConfigureAwait(false); + + SyntaxNode expressionStatement = CreateThrowIfCancellationRequestedExpressionStatement(editor, conditional, propertyReference) + .WithAdditionalAnnotations(Formatter.Annotation); + editor.ReplaceNode(conditional.Syntax, expressionStatement); + + if (conditional.WhenTrue is IBlockOperation block) + { + editor.InsertAfter(expressionStatement, block.Operations.Select(x => x.Syntax.WithAdditionalAnnotations(Formatter.Annotation))); + } + else + { + editor.InsertAfter(expressionStatement, conditional.WhenTrue.Syntax); + } + + return editor.GetChangedDocument(); + }; + } + else + { + return; + } + + var codeAction = CodeAction.Create( + Resx.UseCancellationTokenThrowIfCancellationRequestedCodeFixTitle, + createChangedDocument, + Resx.UseCancellationTokenThrowIfCancellationRequestedCodeFixTitle); + context.RegisterCodeFix(codeAction, context.Diagnostics); + } + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + private static SyntaxNode CreateThrowIfCancellationRequestedExpressionStatement( + DocumentEditor editor, + IConditionalOperation conditional, + IPropertyReferenceOperation isCancellationRequestedPropertyReference) + { + SyntaxNode memberAccess = editor.Generator.MemberAccessExpression( + isCancellationRequestedPropertyReference.Instance.Syntax, + nameof(CancellationToken.ThrowIfCancellationRequested)); + SyntaxNode invocation = editor.Generator.InvocationExpression(memberAccess, Array.Empty()); + var firstWhenTrueStatement = conditional.WhenTrue is IBlockOperation block ? block.Operations.FirstOrDefault() : conditional.WhenTrue; + + var result = editor.Generator.ExpressionStatement(invocation); + result = firstWhenTrueStatement is not null ? result.WithTriviaFrom(firstWhenTrueStatement.Syntax) : result; + return result.WithAdditionalAnnotations(Formatter.Annotation); + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequested.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequested.cs new file mode 100644 index 0000000000..d59131fcd7 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequested.cs @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class UseCancellationTokenThrowIfCancellationRequested : DiagnosticAnalyzer + { + internal const string RuleId = "CA2250"; + + private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(Resx.UseCancellationTokenThrowIfCancellationRequestedTitle), Resx.ResourceManager, typeof(Resx)); + private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(Resx.UseCancellationTokenThrowIfCancellationRequestedMessage), Resx.ResourceManager, typeof(Resx)); + private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(Resx.UseCancellationTokenThrowIfCancellationRequestedDescription), Resx.ResourceManager, typeof(Resx)); + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + s_localizableTitle, + s_localizableMessage, + DiagnosticCategory.Usage, + RuleLevel.IdeSuggestion, + s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private static void OnCompilationStart(CompilationStartAnalysisContext context) + { + if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols)) + return; + + context.RegisterOperationAction(AnalyzeOperation, OperationKind.Conditional); + return; + + void AnalyzeOperation(OperationAnalysisContext context) + { + var conditional = (IConditionalOperation)context.Operation; + + if (symbols.IsSimpleAffirmativeCheck(conditional, out _) || symbols.IsNegatedCheckWithThrowingElseClause(conditional, out _)) + { + context.ReportDiagnostic(conditional.CreateDiagnostic(Rule)); + } + } + } + + /// + /// If is a block operation with one child, returns that child. + /// If is a block operation with more than one child, returns . + /// If is not a block operation, returns . + /// + /// The operation to unwrap. + internal static IOperation? GetSingleStatementOrDefault(IOperation? singleOrBlock) + { + if (singleOrBlock is IBlockOperation blockOperation) + { + return blockOperation.Operations.Length is 1 ? blockOperation.Operations[0] : default; + } + + return singleOrBlock; + } + + // Use readonly struct to avoid allocations. +#pragma warning disable CA1815 // Override equals and operator equals on value types + internal readonly struct RequiredSymbols +#pragma warning restore CA1815 // Override equals and operator equals on value types + { + public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) + { + symbols = default; + INamedTypeSymbol boolType = compilation.GetSpecialType(SpecialType.System_Boolean); + if (boolType is null) + return false; + + if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingCancellationToken, out INamedTypeSymbol? cancellationTokenType)) + return false; + + IMethodSymbol? throwIfCancellationRequestedMethod = cancellationTokenType.GetMembers(nameof(CancellationToken.ThrowIfCancellationRequested)) + .OfType() + .GetFirstOrDefaultMemberWithParameterInfos(); + IPropertySymbol? isCancellationRequestedProperty = cancellationTokenType.GetMembers(nameof(CancellationToken.IsCancellationRequested)) + .OfType() + .FirstOrDefault(); + + if (throwIfCancellationRequestedMethod is null || isCancellationRequestedProperty is null) + return false; + + if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemOperationCanceledException, out INamedTypeSymbol? operationCanceledExceptionType)) + return false; + + IMethodSymbol? operationCanceledExceptionDefaultCtor = operationCanceledExceptionType.InstanceConstructors + .GetFirstOrDefaultMemberWithParameterInfos(); + IMethodSymbol? operationCanceledExceptionTokenCtor = operationCanceledExceptionType.InstanceConstructors + .GetFirstOrDefaultMemberWithParameterInfos(ParameterInfo.GetParameterInfo(cancellationTokenType)); + + if (operationCanceledExceptionDefaultCtor is null || operationCanceledExceptionTokenCtor is null) + return false; + + symbols = new RequiredSymbols + { + IsCancellationRequestedProperty = isCancellationRequestedProperty, + OperationCanceledExceptionDefaultCtor = operationCanceledExceptionDefaultCtor, + OperationCanceledExceptionTokenCtor = operationCanceledExceptionTokenCtor + }; + + return true; + } + + public IPropertySymbol IsCancellationRequestedProperty { get; init; } + public IMethodSymbol OperationCanceledExceptionDefaultCtor { get; init; } + public IMethodSymbol OperationCanceledExceptionTokenCtor { get; init; } + + /// + /// Indicates whether the specified operation is a conditional statement of the form + /// + /// if (token.IsCancellationRequested) + /// throw new OperationCanceledException(); + /// + /// + public bool IsSimpleAffirmativeCheck(IConditionalOperation conditional, [NotNullWhen(true)] out IPropertyReferenceOperation? isCancellationRequestedPropertyReference) + { + IOperation? whenTrueUnwrapped = GetSingleStatementOrDefault(conditional.WhenTrue); + + if (conditional.Condition is IPropertyReferenceOperation propertyReference && + SymbolEqualityComparer.Default.Equals(propertyReference.Property, IsCancellationRequestedProperty) && + whenTrueUnwrapped is IThrowOperation @throw && + @throw.Exception is IObjectCreationOperation objectCreation && + IsDefaultOrTokenOperationCanceledExceptionCtor(objectCreation.Constructor)) + { + isCancellationRequestedPropertyReference = propertyReference; + return true; + } + + isCancellationRequestedPropertyReference = default; + return false; + } + + /// + /// Indicates whether the specified operation is a conditional statement of the form + /// + /// if (!token.IsCancellationRequested) + /// { + /// // statements + /// } + /// else + /// { + /// throw new OperationCanceledException(); + /// } + /// + /// + public bool IsNegatedCheckWithThrowingElseClause(IConditionalOperation conditional, [NotNullWhen(true)] out IPropertyReferenceOperation? isCancellationRequestedPropertyReference) + { + IOperation? whenFalseUnwrapped = GetSingleStatementOrDefault(conditional.WhenFalse); + + if (conditional.Condition is IUnaryOperation { OperatorKind: UnaryOperatorKind.Not } unary && + unary.Operand is IPropertyReferenceOperation propertyReference && + SymbolEqualityComparer.Default.Equals(propertyReference.Property, IsCancellationRequestedProperty) && + whenFalseUnwrapped is IThrowOperation @throw && + @throw.Exception is IObjectCreationOperation objectCreation && + IsDefaultOrTokenOperationCanceledExceptionCtor(objectCreation.Constructor)) + { + isCancellationRequestedPropertyReference = propertyReference; + return true; + } + + isCancellationRequestedPropertyReference = default; + return false; + } + + private bool IsDefaultOrTokenOperationCanceledExceptionCtor(IMethodSymbol method) + { + return SymbolEqualityComparer.Default.Equals(method, OperationCanceledExceptionDefaultCtor) || + SymbolEqualityComparer.Default.Equals(method, OperationCanceledExceptionTokenCtor); + } + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 877daf3f0d..6af04a2095 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2177,6 +2177,26 @@ Metoda {0} zpracovává požadavek {1} bez ověřování tokenu proti padělkům. Je potřeba také zajistit, aby váš formulář HTML odesílal tokeny proti padělkům. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Použít zásady přístupu na úrovni kontejneru diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 6211868eb7..cfaa562463 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2177,6 +2177,26 @@ Die Methode "{0}" verarbeitet eine {1}-Anforderung ohne Überprüfung eines Fälschungssicherheitstokens. Sie müssen außerdem sicherstellen, dass Ihr HTML-Formular ein Fälschungssicherheitstoken sendet. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Zugriffsrichtlinie auf Containerebene verwenden diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 243edec0db..0018af8f92 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2177,6 +2177,26 @@ El método {0} controla una solicitud de {1} sin validar un token antifalsificación. También debe asegurarse de que el formulario HTML envíe un token antifalsificación. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Usar una directiva de acceso de nivel de contenedor diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 70735bfdbd..f915bf31cd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2177,6 +2177,26 @@ La méthode {0} traite une requête {1} sans validation de jeton antifalsification. Vous devez également vérifier que votre formulaire HTML envoie un jeton antifalsification. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Utiliser une stratégie d'accès au niveau du conteneur diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index aa98c4d18b..8eb665ee47 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2177,6 +2177,26 @@ Il metodo {0} gestisce una richiesta {1} senza eseguire la convalida del token antifalsificazione. È necessario assicurarsi anche che il modulo HTML invii un token antifalsificazione. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Usa criteri di accesso a livello di contenitore diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index d8a54a2373..8e53387376 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2177,6 +2177,26 @@ メソッド {0} では、偽造防止トークンの検証を実行せずに {1} 要求が処理されます。また、HTML フォームで偽造防止トークンが送信されるようにする必要もあります。 + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy コンテナー レベルのアクセス ポリシーを使用する diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 84d01add8a..40a9ba2dee 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2177,6 +2177,26 @@ {0} 메서드는 위조 방지 토큰 유효성 검사를 수행하지 않고 {1} 요청을 처리합니다. 또한 HTML 양식이 위조 방지 토큰을 보내는지 확인해야 합니다. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy 컨테이너 수준 액세스 정책 사용 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 43401d506d..592daa7204 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2177,6 +2177,26 @@ Metoda {0} obsługuje żądanie {1} bez przeprowadzania weryfikacji tokenu zabezpieczającego przed fałszerstwem. Należy również upewnić się, że formularz HTML wysyła token zabezpieczający przed fałszerstwem. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Użyj zasad dostępu na poziomie kontenera diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index e81557ff9f..e285035d7e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2177,6 +2177,26 @@ O método {0} lida com uma solicitação de {1} sem executar a validação de token antifalsificação. Também é necessário garantir que o seu formulário em HTML envie um token antifalsificação. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Usar Política de Acesso no Nível de Contêiner diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 81e7b44255..85434b1f50 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2177,6 +2177,26 @@ Метод {0} обрабатывает запрос {1} без проверки маркера для защиты от подделки. Также убедитесь в том, что HTML-форма отправляет маркер для защиты от подделки. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Использовать политику доступа на уровне контейнера diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index ae104436b8..dc94be6024 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2177,6 +2177,26 @@ {0} metodu, {1} isteğini sahtecilik önleme belirtecini doğrulamadan işler. Ayrıca HTML formunuzun sahtecilik önleme belirteci gönderdiğinden emin olmanız gerekir. + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy Kapsayıcı Düzeyinde Erişim İlkesi Kullan diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index cd724db3c5..dc86c9a46a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2177,6 +2177,26 @@ 方法 {0} 在不执行防伪造令牌验证的情况下处理 {1} 请求。你还需要确保 HTML 窗体发送防伪造令牌。 + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy 使用容器级别访问策略 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 1f9bcceb8f..16bfe4a6d0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2177,6 +2177,26 @@ 方法 {0} 會在不驗證 antiforgery 權杖的情況下處理 {1} 要求。您也必須確保 HTML 表單傳送 antiforgery 權杖。 + + Replace with 'CancellationToken.ThrowIfCancellationRequested' + Replace with 'CancellationToken.ThrowIfCancellationRequested' + + + + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + 'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + + + + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + Use 'ThrowIfCancellationRequested' instead of checking 'IsCancellationRequested' and throwing 'OperationCanceledException' + + + + Use 'ThrowIfCancellationRequested' + Use 'ThrowIfCancellationRequested' + + Use Container Level Access Policy 使用容器層級存取原則 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index e9b7dcd5b1..a3222d52e0 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1944,6 +1944,18 @@ Calls to 'string.IndexOf' where the result is used to check for the presence/abs |CodeFix|True| --- +## [CA2250](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250): Use 'ThrowIfCancellationRequested' + +'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has. + +|Item|Value| +|-|-| +|Category|Usage| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2300](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2300): Do not use insecure deserializer BinaryFormatter The method '{0}' is insecure when deserializing untrusted data. If you need to instead detect BinaryFormatter deserialization without a SerializationBinder set, then disable rule CA2300, and enable rules CA2301 and CA2302. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 03446defa1..b7a3af4bb9 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3430,6 +3430,26 @@ ] } }, + "CA2250": { + "id": "CA2250", + "shortDescription": "Use 'ThrowIfCancellationRequested'", + "fullDescription": "'ThrowIfCancellationRequested' automatically checks whether the token has been canceled, and throws an 'OperationCanceledException' if it has.", + "defaultLevel": "note", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250", + "properties": { + "category": "Usage", + "isEnabledByDefault": true, + "typeName": "UseCancellationTokenThrowIfCancellationRequested", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2300": { "id": "CA2300", "shortDescription": "Do not use insecure deserializer BinaryFormatter", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 77999224e4..abf017a075 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,6 +2,7 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| +CA2250 | | Use 'ThrowIfCancellationRequested' | CA1418 | | Use valid platform string | CA1839 | | Use 'Environment.ProcessPath' | CA1840 | | Use 'Environment.CurrentManagedThreadId' | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequestedTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequestedTests.cs new file mode 100644 index 0000000000..58d7e3d486 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseCancellationTokenThrowIfCancellationRequestedTests.cs @@ -0,0 +1,683 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Xunit; + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.UseCancellationTokenThrowIfCancellationRequested, + Microsoft.NetCore.Analyzers.Runtime.UseCancellationTokenThrowIfCancellationRequestedFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.UseCancellationTokenThrowIfCancellationRequested, + Microsoft.NetCore.Analyzers.Runtime.UseCancellationTokenThrowIfCancellationRequestedFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class UseCancellationTokenThrowIfCancellationRequestedTests + { + private static IEnumerable OperationCanceledExceptionCtors + { + get + { + yield return "OperationCanceledException()"; + yield return "OperationCanceledException(token)"; + } + } + + #region Reports Diagnostic + public static IEnumerable Data_SimpleAffirmativeCheck_ReportedAndFixed_CS + { + get + { + static IEnumerable ConditionalFormatStrings() + { + yield return @"if ({0}) {1}"; + yield return @" +if ({0}) + {1}"; + yield return @" +if ({0}) +{{ + {1} +}}"; + } + + return CartesianProduct(OperationCanceledExceptionCtors, ConditionalFormatStrings()); + } + } + + [Theory] + [MemberData(nameof(Data_SimpleAffirmativeCheck_ReportedAndFixed_CS))] + public Task SimpleAffirmativeCheck_ReportedAndFixed_CS(string operationCanceledExceptionCtor, string simpleConditionalFormatString) + { + string testStatements = Markup( + FormatInvariant( + simpleConditionalFormatString, + @"token.IsCancellationRequested", + $@"throw new {operationCanceledExceptionCtor};"), 0); + string fixedStatements = @"token.ThrowIfCancellationRequested();"; + + var test = new VerifyCS.Test + { + TestCode = CS.CreateBlock(testStatements), + FixedCode = CS.CreateBlock(fixedStatements), + ExpectedDiagnostics = { CS.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + public static IEnumerable Data_SimpleAffirmativeCheck_ReportedAndFixed_VB + { + get + { + static IEnumerable ConditionalFormatStrings() + { + yield return @"If {0} Then {1}"; + yield return @" +If {0} Then + {1} +End If"; + } + + return CartesianProduct(OperationCanceledExceptionCtors, ConditionalFormatStrings()); + } + } + + [Theory] + [MemberData(nameof(Data_SimpleAffirmativeCheck_ReportedAndFixed_VB))] + public Task SimpleAffirmativeCheck_ReportedAndFixed_VB(string operationCanceledExceptionCtor, string conditionalFormatString) + { + string testStatements = Markup( + FormatInvariant( + conditionalFormatString, + "token.IsCancellationRequested", + $"Throw New {operationCanceledExceptionCtor}"), + 0); + string fixedStatements = @"token.ThrowIfCancellationRequested()"; + + var test = new VerifyVB.Test + { + TestCode = VB.CreateBlock(testStatements), + FixedCode = VB.CreateBlock(fixedStatements), + ExpectedDiagnostics = { VB.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + public static IEnumerable Data_NegatedCheckWithElse_ReportedAndFixed_CS + { + get + { + static IEnumerable ConditionalFormatStrings() + { + yield return @" +if ({0}) {1} +else {2}"; + yield return @" +if ({0}) + {1} +else + {2}"; + yield return @" +if ({0}) +{{ + {1} +}} +else +{{ + {2} +}}"; + yield return @" +if ({0}) + {1} +else +{{ + {2} +}}"; + yield return @" +if ({0}) +{{ + {1} +}} +else + {2}"; + } + + return CartesianProduct(OperationCanceledExceptionCtors, ConditionalFormatStrings()); + } + } + + [Fact] + public Task SimpleAffirmativeCheckWithElseClause_ReportedAndFixed_CS() + { + var test = new VerifyCS.Test + { + TestCode = @" +using System; +using System.Threading; + +public class C +{ + private CancellationToken token; + + public void M() + { + {|#0:if (token.IsCancellationRequested) + { + throw new OperationCanceledException(); + } + else + { + Frob(); + }|} + } + + private void Frob() { } +}", + FixedCode = @" +using System; +using System.Threading; + +public class C +{ + private CancellationToken token; + + public void M() + { + token.ThrowIfCancellationRequested(); + Frob(); + } + + private void Frob() { } +}", + ExpectedDiagnostics = { CS.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task SimpleAffirmativeCheckWithElseClause_ReportedAndFixed_VB() + { + var test = new VerifyVB.Test + { + TestCode = @" +Imports System +Imports System.Threading + +Public Class C + Private token As CancellationToken + + Public Sub M() + {|#0:If token.IsCancellationRequested Then + Throw New OperationCanceledException() + Else + Frob() + End If|} + End Sub + + Private Sub Frob() + End Sub +End Class", + FixedCode = @" +Imports System +Imports System.Threading + +Public Class C + Private token As CancellationToken + + Public Sub M() + token.ThrowIfCancellationRequested() + Frob() + End Sub + + Private Sub Frob() + End Sub +End Class", + ExpectedDiagnostics = { VB.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task TriviaInIfBlock_IsPreserved_CS() + { + var test = new VerifyCS.Test + { + TestCode = @" +using System; +using System.Threading; + +public class C +{ + private CancellationToken token; + + public void M() + { + {|#0:if (token.IsCancellationRequested) + { + // Comment + throw new OperationCanceledException(); + }|} + } +}", + FixedCode = @" +using System; +using System.Threading; + +public class C +{ + private CancellationToken token; + + public void M() + { + // Comment + token.ThrowIfCancellationRequested(); + } +}", + ExpectedDiagnostics = { CS.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [MemberData(nameof(Data_NegatedCheckWithElse_ReportedAndFixed_CS))] + public Task NegatedCheckWithElse_ReportedAndFixed_CS(string operationCanceledExceptionCtor, string conditionalFormatString) + { + const string members = @" +private CancellationToken token; +private void DoSomething() { }"; + string testStatements = Markup( + FormatInvariant( + conditionalFormatString, + "!token.IsCancellationRequested", + "DoSomething();", + $"throw new {operationCanceledExceptionCtor};"), + 0); + string fixedStatements = @" +token.ThrowIfCancellationRequested(); +DoSomething();"; + + var test = new VerifyCS.Test + { + TestCode = CS.CreateBlock(testStatements, members), + FixedCode = CS.CreateBlock(fixedStatements, members), + ExpectedDiagnostics = { CS.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + public static IEnumerable Data_NegatedCheckWithElse_ReportedAndFixed_VB + { + get + { + static IEnumerable ConditionalFormatStrings() + { + return Enumerable.Repeat(@" +If {0} Then + {1} +Else + {2} +End If", 1); + } + + return CartesianProduct(OperationCanceledExceptionCtors, ConditionalFormatStrings()); + } + } + + [Theory] + [MemberData(nameof(Data_NegatedCheckWithElse_ReportedAndFixed_VB))] + public Task NegatedCheckWithElse_ReportedAndFixed_VB(string operationCanceledExceptionCtor, string conditionalFormatString) + { + const string members = @" +Private token As CancellationToken +Private Sub DoSomething() +End Sub"; + string testStatements = Markup( + FormatInvariant( + conditionalFormatString, + "Not token.IsCancellationRequested", + "DoSomething()", + $"Throw New {operationCanceledExceptionCtor}"), + 0); + string fixedStatements = @" +token.ThrowIfCancellationRequested() +DoSomething()"; + + var test = new VerifyVB.Test + { + TestCode = VB.CreateBlock(testStatements, members), + FixedCode = VB.CreateBlock(fixedStatements, members), + ExpectedDiagnostics = { VB.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task NegatedCheckWithElse_MultipleOperationsInTrueBranch_ReportedAndFixed_CS() + { + const string members = @" +private CancellationToken token; +private void Fooble() { } +private void Barble() { }"; + string testStatements = Markup(@" +if (!token.IsCancellationRequested) +{ + Fooble(); + Barble(); +} +else +{ + throw new OperationCanceledException(); +}", 0); + string fixedStatements = @" +token.ThrowIfCancellationRequested(); +Fooble(); +Barble();"; + + var test = new VerifyCS.Test + { + TestCode = CS.CreateBlock(testStatements, members), + FixedCode = CS.CreateBlock(fixedStatements, members), + ExpectedDiagnostics = { CS.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task NegatedCheckWithElse_MultpleOperationsInTrueBranch_ReportedAndFixed_VB() + { + const string members = @" +Private token As CancellationToken +Private Sub Fooble() +End Sub +Private Sub Barble() +End Sub"; + string testStatements = Markup(@" +If Not token.IsCancellationRequested Then + Fooble() + Barble() +Else + Throw New OperationCanceledException() +End If", 0); + string fixedStatements = @" +token.ThrowIfCancellationRequested() +Fooble() +Barble()"; + + var test = new VerifyVB.Test + { + TestCode = VB.CreateBlock(testStatements, members), + FixedCode = VB.CreateBlock(fixedStatements, members), + ExpectedDiagnostics = { VB.DiagnosticAt(0) }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + #endregion + + #region No Diagnostic + [Fact] + public Task MultipleConditions_NoDiagnostic_CS() + { + const string members = @" +private CancellationToken token; +private bool otherCondition;"; + const string testStatements = @" +if (token.IsCancellationRequested && otherCondition) + throw new OperationCanceledException();"; + + var test = new VerifyCS.Test + { + TestCode = CS.CreateBlock(testStatements, members), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task MultipleConditions_NoDiagnostic_VB() + { + const string members = @" +Private token As CancellationToken +Private otherCondition As Boolean"; + const string testStatements = @" +If token.IsCancellationRequested AndAlso otherCondition Then + Throw New OperationCanceledException() +End If"; + + var test = new VerifyVB.Test + { + TestCode = VB.CreateBlock(testStatements, members), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task OtherStatementsInSimpleAffirmativeCheck_NoDiagnostic_CS() + { + const string members = @" +private CancellationToken token; +private void SomeOtherAction() { }"; + const string testStatements = @" +if (token.IsCancellationRequested) +{ + SomeOtherAction(); + throw new OperationCanceledException(); +}"; + + var test = new VerifyCS.Test + { + TestCode = CS.CreateBlock(testStatements, members), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Fact] + public Task OtherStatementsInSimpleAffirmativeCheck_NoDiagnostic_VB() + { + const string members = @" +Private token As CancellationToken +Private Sub SomeOtherAction() +End Sub"; + const string testStatements = @" +If token.IsCancellationRequested Then + SomeOtherAction() + Throw New OperationCanceledException() +End If"; + + var test = new VerifyVB.Test + { + TestCode = VB.CreateBlock(testStatements, members), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + public static IEnumerable Data_OperationCanceledExceptionCtorArguments + { + get + { + yield return new[] { "text" }; + yield return new[] { "text, token" }; + yield return new[] { "text, exception" }; + yield return new[] { "text, exception, token" }; + } + } + + [Theory] + [MemberData(nameof(Data_OperationCanceledExceptionCtorArguments))] + public Task OtherExceptionCtorOverloads_SimpleAffirmativeCheck_NoDiagnostic_CS(string ctorArguments) + { + const string members = @" +private CancellationToken token; +private string text; +private Exception exception;"; + string testStatements = @" +if (token.IsCancellationRequested) + throw new OperationCanceledException(" + ctorArguments + @");"; + + var test = new VerifyCS.Test + { + TestCode = CS.CreateBlock(testStatements, members), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [MemberData(nameof(Data_OperationCanceledExceptionCtorArguments))] + public Task OtherExceptionCtorOverloads_SimpleAffirmativeCheck_NoDiagnostic_VB(string ctorArguments) + { + const string members = @" +Private token As CancellationToken +Private text As String +private exception As Exception"; + string testStatements = @" +If token.IsCancellationRequested Then + Throw New OperationCanceledException(" + ctorArguments + @") +End If"; + + var test = new VerifyVB.Test + { + TestCode = VB.CreateBlock(testStatements, members), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [MemberData(nameof(Data_OperationCanceledExceptionCtorArguments))] + public Task OtherExceptionCtorOverloads_NegatedCheckWithElse_NoDiagnostic_CS(string ctorArguments) + { + const string members = @" +private CancellationToken token; +private string text; +private Exception exception; +private void DoSomething() { }"; + string testStatements = @" +if (!token.IsCancellationRequested) + DoSomething(); +else + throw new OperationCanceledException(" + ctorArguments + @");"; + + var test = new VerifyCS.Test + { + TestCode = CS.CreateBlock(testStatements, members), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [MemberData(nameof(Data_OperationCanceledExceptionCtorArguments))] + public Task OtherExceptionCtorOverloads_NegatedCheckWithElse_NoDiagnostic_VB(string ctorArguments) + { + const string members = @" +Private token As CancellationToken +Private text As String +Private exception As Exception +Private Sub DoSomething() +End Sub"; + string testStatements = @" +If Not token.IsCancellationRequested Then + DoSomething() +Else + Throw New OperationCanceledException(" + ctorArguments + @") +End If"; + + var test = new VerifyVB.Test + { + TestCode = VB.CreateBlock(testStatements, members), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + #endregion + + #region Helpers + private static class CS + { + public const string Usings = @" +using System; +using System.Threading;"; + + public static string CreateBlock(string statements, string members) + { + return Usings + @" +public partial class Body +{ +" + IndentLines(members, " ") + @" + public void Run() + { +" + IndentLines(statements, " ") + @" + } +}"; + } + + /// + /// Creates a test class with a single private CancellationToken member called 'token'. + /// + public static string CreateBlock(string statements) => CreateBlock(statements, @"private CancellationToken token;"); + + public static DiagnosticResult DiagnosticAt(int markupKey) => VerifyCS.Diagnostic(Rule).WithLocation(markupKey); + } + + private static class VB + { + public const string Usings = @" +Imports System +Imports System.Threading"; + + public static string CreateBlock(string statements, string members) + { + return Usings + @" +Partial Public Class Body +" + IndentLines(members, " ") + @" + Public Sub Run() +" + IndentLines(statements, " ") + @" + End Sub +End Class"; + } + + public static string CreateBlock(string statements) => CreateBlock(statements, @"Private token As CancellationToken"); + + public static DiagnosticResult DiagnosticAt(int markupKey) => VerifyVB.Diagnostic(Rule).WithLocation(markupKey); + } + + private static string IndentLines(string lines, string indent) + { + return indent + lines.TrimStart().Replace(Environment.NewLine, Environment.NewLine + indent, StringComparison.Ordinal); + } + + private static string Markup(string text, int markupKey, bool removeLeadingWhitespace = true) + { + text = removeLeadingWhitespace ? text.TrimStart() : text; + return $"{{|#{markupKey}:{text}|}}"; + } + + private static DiagnosticDescriptor Rule => UseCancellationTokenThrowIfCancellationRequested.Rule; + + private static IEnumerable CartesianProduct(IEnumerable left, IEnumerable right) + { + return left.SelectMany(x => right.Select(y => new[] { x, y })); + } + + private static string FormatInvariant(string format, params object[] args) => string.Format(System.Globalization.CultureInfo.InvariantCulture, format, args); + #endregion + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 9a70474214..7b44225310 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -14,7 +14,7 @@ Globalization: CA2101, CA1300-CA1310 Mobility: CA1600-CA1601 Performance: HA, CA1800-CA1845 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5403 -Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2249 +Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2250 Naming: CA1700-CA1726 Interoperability: CA1400-CA1418 Maintainability: CA1500-CA1509 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index a50b451023..bb045df567 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -242,6 +242,7 @@ internal static class WellKnownTypeNames public const string SystemObject = "System.Object"; public const string SystemObsoleteAttribute = "System.ObsoleteAttribute"; public const string SystemOperatingSystem = "System.OperatingSystem"; + public const string SystemOperationCanceledException = "System.OperationCanceledException"; public const string SystemOutOfMemoryException = "System.OutOfMemoryException"; public const string SystemPlatformNotSupportedException = "System.PlatformNotSupportedException"; public const string SystemRandom = "System.Random";