| File: Microsoft.NetCore.Analyzers\Runtime\AvoidUnreliableStreamRead.Fixer.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.NetAnalyzers; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.NetCore.Analyzers.Runtime { using static MicrosoftNetCoreAnalyzersResources; /// <summary> /// CA2022: <inheritdoc cref="AvoidUnreliableStreamReadTitle"/> /// </summary> [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] public sealed class AvoidUnreliableStreamReadFixer : SyntaxEditorBasedCodeFixProvider { private const string Async = nameof(Async); private const string ReadExactly = nameof(ReadExactly); private const string ReadExactlyAsync = nameof(ReadExactlyAsync); public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(AvoidUnreliableStreamReadAnalyzer.RuleId); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); if (GetReadInvocation(semanticModel, root.FindNode(context.Span, getInnermostNodeForTie: true), context.CancellationToken) is null) { return; } RegisterCodeFix(context, AvoidUnreliableStreamReadCodeFixTitle, nameof(AvoidUnreliableStreamReadCodeFixTitle)); } protected sealed override async Task ApplyFixAsync(Document document, Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var node = editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); if (GetReadInvocation(semanticModel, node, cancellationToken) is not IInvocationOperation invocation) { return; } var arguments = invocation.Arguments.GetArgumentsInParameterOrder(); var isAsyncInvocation = invocation.TargetMethod.Name.EndsWith(Async, StringComparison.Ordinal); var instance = invocation.Instance!.Syntax; ImmutableArray<SyntaxNode> replacementArguments = CanUseSpanOverload() ? isAsyncInvocation && arguments.Length == 4 // Stream.ReadExactlyAsync(buffer, ct) ? ImmutableArray.Create(arguments[0].Syntax, arguments[3].Syntax) // Stream.ReadExactly(buffer) and Stream.ReadExactlyAsync(buffer) : ImmutableArray.Create(arguments[0].Syntax) : invocation.Arguments.Where(a => !a.IsImplicit).Select(a => a.Syntax).ToImmutableArray(); editor.TrackNode(instance); foreach (SyntaxNode argument in replacementArguments) { editor.TrackNode(argument); } // The receiver and the arguments are carried over from inside the node being replaced, so they have // to be read back off the current node rather than off the original tree. editor.ReplaceNode(invocation.Syntax, (currentNode, generator) => { var methodExpression = generator.MemberAccessExpression( currentNode.GetCurrentNode(instance) ?? instance, isAsyncInvocation ? ReadExactlyAsync : ReadExactly); return generator.InvocationExpression(methodExpression, replacementArguments.Select(argument => currentNode.GetCurrentNode(argument) ?? argument)) .WithTriviaFrom(currentNode); }); bool CanUseSpanOverload() { return arguments.Length >= 3 && arguments[2].Value is IPropertyReferenceOperation propertyRef && propertyRef.Property.Name.Equals(WellKnownMemberNames.LengthPropertyName, StringComparison.Ordinal) && AreSameInstance(arguments[0].Value, propertyRef.Instance); } static bool AreSameInstance(IOperation? operation1, IOperation? operation2) { return (operation1, operation2) switch { (IFieldReferenceOperation fieldRef1, IFieldReferenceOperation fieldRef2) => fieldRef1.Member == fieldRef2.Member, (IPropertyReferenceOperation propRef1, IPropertyReferenceOperation propRef2) => propRef1.Member == propRef2.Member, (IParameterReferenceOperation paramRef1, IParameterReferenceOperation paramRef2) => paramRef1.Parameter == paramRef2.Parameter, (ILocalReferenceOperation localRef1, ILocalReferenceOperation localRef2) => localRef1.Local == localRef2.Local, _ => false, }; } } private static IInvocationOperation? GetReadInvocation(SemanticModel semanticModel, SyntaxNode? node, CancellationToken cancellationToken) { if (node is null || semanticModel.GetOperation(node, cancellationToken) is not IInvocationOperation invocation || invocation.Instance is null) { return null; } var streamType = semanticModel.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOStream); return streamType is not null && streamType.GetMembers(ReadExactly).OfType<IMethodSymbol>().Any() ? invocation : null; } } }