File: Microsoft.NetCore.Analyzers\Runtime\AvoidUnreliableStreamRead.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.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
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.Runtime
{
    using static MicrosoftNetCoreAnalyzersResources;

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

        private const string Read = nameof(Read);
        private const string ReadAsync = nameof(ReadAsync);

        internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
            RuleId,
            CreateLocalizableResourceString(nameof(AvoidUnreliableStreamReadTitle)),
            CreateLocalizableResourceString(nameof(AvoidUnreliableStreamReadMessage)),
            DiagnosticCategory.Reliability,
            RuleLevel.BuildWarning,
            CreateLocalizableResourceString(nameof(AvoidUnreliableStreamReadDescription)),
            isPortedFxCopRule: false,
            isDataflowRule: false);

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

        public sealed override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterCompilationStartAction(OnCompilationStart);
        }

        private void OnCompilationStart(CompilationStartAnalysisContext context)
        {
            if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols))
            {
                return;
            }

            context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);

            void AnalyzeInvocation(OperationAnalysisContext context)
            {
                var invocation = (IInvocationOperation)context.Operation;
                var instanceType = invocation.GetInstanceType();

                if (instanceType is null || symbols.IsKnownReliableStreamType(instanceType))
                {
                    return;
                }

                if (symbols.IsAnyStreamReadMethod(invocation.TargetMethod))
                {
                    if (invocation.Parent is not IExpressionStatementOperation)
                    {
                        return;
                    }
                }
                else if (symbols.IsAnyStreamReadAsyncMethod(invocation.TargetMethod))
                {
                    if (invocation.Parent is not IAwaitOperation awaitOperation ||
                        awaitOperation.Parent is not IExpressionStatementOperation)
                    {
                        return;
                    }
                }
                else
                {
                    return;
                }

                context.ReportDiagnostic(invocation.CreateDiagnostic(Rule, invocation.TargetMethod.ToDisplayString()));
            }
        }

        internal sealed class RequiredSymbols
        {
            private RequiredSymbols(
                ImmutableArray<IMethodSymbol> streamReadMethods,
                ImmutableArray<IMethodSymbol> streamReadAsyncMethods,
                ImmutableHashSet<ITypeSymbol> knownReliableStreamTypes)
            {
                _streamReadMethods = streamReadMethods;
                _streamReadAsyncMethods = streamReadAsyncMethods;
                _knownReliableStreamTypes = knownReliableStreamTypes;
            }

            public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols? symbols)
            {
                symbols = default;

                var streamType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOStream);

                if (streamType is null)
                {
                    return false;
                }

                var streamReadMethods = streamType.GetMembers(Read)
                    .OfType<IMethodSymbol>()
                    .ToImmutableArray();
                var streamReadAsyncMethods = streamType.GetMembers(ReadAsync)
                    .OfType<IMethodSymbol>()
                    .ToImmutableArray();

                if (streamReadMethods.IsEmpty && streamReadAsyncMethods.IsEmpty)
                {
                    return false;
                }

                var knownReliableStreamTypesBuilder = ImmutableHashSet.CreateBuilder<ITypeSymbol>(SymbolEqualityComparer.Default);
                knownReliableStreamTypesBuilder.AddIfNotNull(compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOMemoryStream));
                knownReliableStreamTypesBuilder.AddIfNotNull(compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOUnmanagedMemoryStream));

                symbols = new RequiredSymbols(streamReadMethods, streamReadAsyncMethods, knownReliableStreamTypesBuilder.ToImmutable());

                return true;
            }

            public bool IsAnyStreamReadMethod(IMethodSymbol method)
            {
                return _streamReadMethods.Any(m =>
                    SymbolEqualityComparer.Default.Equals(method, m) || IsOverrideOf(method, m));
            }

            public bool IsAnyStreamReadAsyncMethod(IMethodSymbol method)
            {
                return _streamReadAsyncMethods.Any(m =>
                    SymbolEqualityComparer.Default.Equals(method, m) || IsOverrideOf(method, m));
            }

            public bool IsKnownReliableStreamType(ITypeSymbol type)
            {
                return _knownReliableStreamTypes.Contains(type);
            }

            private static bool IsOverrideOf(IMethodSymbol method, IMethodSymbol baseMethod)
            {
                var overriddenMethod = method.OverriddenMethod;
                while (overriddenMethod is not null)
                {
                    if (SymbolEqualityComparer.Default.Equals(overriddenMethod, baseMethod))
                    {
                        return true;
                    }

                    overriddenMethod = overriddenMethod.OverriddenMethod;
                }

                return false;
            }

            private readonly ImmutableArray<IMethodSymbol> _streamReadMethods;
            private readonly ImmutableArray<IMethodSymbol> _streamReadAsyncMethods;
            private readonly ImmutableHashSet<ITypeSymbol> _knownReliableStreamTypes;
        }
    }
}