File: Microsoft.NetCore.Analyzers\Runtime\BufferBlockCopyLengthAnalyzer.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.Linq;
using System.Threading;
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>
    /// CA2018: <inheritdoc cref="BufferBlockCopyLengthTitle"/>
    /// Check for the intended use of .Length on arrays passed into Buffer.BlockCopy
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public sealed class BufferBlockCopyLengthAnalyzer : DiagnosticAnalyzer
    {
        internal const string RuleId = "CA2018";

        internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                      CreateLocalizableResourceString(nameof(BufferBlockCopyLengthTitle)),
                                                                                      CreateLocalizableResourceString(nameof(BufferBlockCopyLengthMessage)),
                                                                                      DiagnosticCategory.Reliability,
                                                                                      RuleLevel.BuildWarning,
                                                                                      CreateLocalizableResourceString(nameof(BufferBlockCopyDescription)),
                                                                                      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(context =>
            {
                if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemBuffer, out INamedTypeSymbol? bufferType))
                {
                    return;
                }

                INamedTypeSymbol arrayType = context.Compilation.GetSpecialType(SpecialType.System_Array);
                INamedTypeSymbol int32Type = context.Compilation.GetSpecialType(SpecialType.System_Int32);

                // Buffer.BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count)
                ParameterInfo[] blockCopyParameterInfo = {
                    ParameterInfo.GetParameterInfo(arrayType, false, 1, false),
                    ParameterInfo.GetParameterInfo(int32Type, false, 1, false),
                    ParameterInfo.GetParameterInfo(arrayType, false, 1, false),
                    ParameterInfo.GetParameterInfo(int32Type, false, 1, false),
                    ParameterInfo.GetParameterInfo(int32Type, false, 1, false)};

                IMethodSymbol? blockCopyMethod = bufferType
                   .GetMembers("BlockCopy")
                   .OfType<IMethodSymbol>()
                   .GetFirstOrDefaultMemberWithParameterInfos(blockCopyParameterInfo);

                if (blockCopyMethod is null)
                {
                    return;
                }

                ISymbol? arrayLengthProperty = arrayType
                    .GetMembers("Length")
                    .FirstOrDefault(p => p is IPropertySymbol);

                context.RegisterOperationAction(context =>
                {
                    var invocationOperation = (IInvocationOperation)context.Operation;

                    if (!invocationOperation.TargetMethod.Equals(blockCopyMethod, SymbolEqualityComparer.Default))
                    {
                        return;
                    }

                    ImmutableArray<IArgumentOperation> arguments = IOperationExtensions.GetArgumentsInParameterOrder(invocationOperation.Arguments);

                    IArgumentOperation sourceArgument = arguments[0];
                    IArgumentOperation destinationArgument = arguments[2];
                    IArgumentOperation countArgument = arguments[4];

                    bool CheckArgumentArrayType(IArgumentOperation targetArgument, IPropertyReferenceOperation lengthPropertyArgument)
                    {
                        if (targetArgument.Value is IConversionOperation targetArgumentValue)
                        {
                            if (lengthPropertyArgument.Instance.GetReferencedMemberOrLocalOrParameter() == targetArgumentValue.Operand.GetReferencedMemberOrLocalOrParameter())
                            {
                                IArrayTypeSymbol countArgumentArrayTypeSymbol = (IArrayTypeSymbol)lengthPropertyArgument.Instance!.Type!;
                                if (countArgumentArrayTypeSymbol.ElementType.SpecialType is not SpecialType.System_Byte and
                                not SpecialType.System_SByte and
                                not SpecialType.System_Boolean)
                                {
                                    return true;
                                }
                            }
                        }

                        return false;
                    }

                    bool CheckLengthProperty(IPropertyReferenceOperation countArgument)
                    {
                        if (countArgument.Property.Equals(arrayLengthProperty))
                        {
                            return CheckArgumentArrayType(sourceArgument, countArgument) || CheckArgumentArrayType(destinationArgument, countArgument);
                        }

                        return false;
                    }

                    if (countArgument.Value is IPropertyReferenceOperation countArgumentValue && CheckLengthProperty(countArgumentValue))
                    {
                        context.ReportDiagnostic(countArgument.Value.CreateDiagnostic(Rule));
                    }
                    else
                    {
                        if (countArgument.Value is not ILocalReferenceOperation localReferenceOperation)
                        {
                            return;
                        }

                        SemanticModel semanticModel = countArgument.SemanticModel!;
                        CancellationToken cancellationToken = context.CancellationToken;

                        ILocalSymbol localArgumentDeclaration = localReferenceOperation.Local;

                        SyntaxReference? declaringSyntaxReference = localArgumentDeclaration.DeclaringSyntaxReferences.FirstOrDefault();
                        if (declaringSyntaxReference is null)
                        {
                            return;
                        }

                        if (semanticModel.GetOperationWalkingUpParentChain(declaringSyntaxReference.GetSyntax(cancellationToken), cancellationToken) is not IVariableDeclaratorOperation variableDeclaratorOperation)
                        {
                            return;
                        }

                        IVariableInitializerOperation? variableInitializer = variableDeclaratorOperation.Initializer;

                        if (variableInitializer is not null && variableInitializer.Value is IPropertyReferenceOperation variableInitializerPropertyReference &&
                        CheckLengthProperty(variableInitializerPropertyReference))
                        {
                            context.ReportDiagnostic(countArgument.Value.CreateDiagnostic(Rule));
                        }
                        else if (variableDeclaratorOperation.Parent is IVariableDeclarationOperation variableDeclarationOperation &&
                        variableDeclarationOperation.Initializer is not null &&
                        variableDeclarationOperation.Initializer.Value is IPropertyReferenceOperation variableInitializerPropertyReferenceVB &&
                        CheckLengthProperty(variableInitializerPropertyReferenceVB))
                        {
                            context.ReportDiagnostic(countArgument.Value.CreateDiagnostic(Rule));
                        }
                    }
                },
                OperationKind.Invocation);
            });
        }
    }
}