File: Microsoft.NetCore.Analyzers\Runtime\PreventNumericIntPtrUIntPtrBehavioralChanges.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>
    /// CA2020: <inheritdoc cref="PreventNumericIntPtrUIntPtrBehavioralChangesTitle"/>
    /// Detects Behavioral Changes introduced by new Numeric IntPtr UIntPtr feature
    /// </summary>
    public abstract class PreventNumericIntPtrUIntPtrBehavioralChanges : DiagnosticAnalyzer
    {
        internal const string RuleId = "CA2020";
        private static readonly LocalizableResourceString s_titleResource = CreateLocalizableResourceString(nameof(PreventNumericIntPtrUIntPtrBehavioralChangesTitle));
        private static readonly LocalizableResourceString s_descriptionResource = CreateLocalizableResourceString(nameof(PreventNumericIntPtrUIntPtrBehavioralChangesDescription));

        internal static readonly DiagnosticDescriptor OperatorThrowsRule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_titleResource,
            CreateLocalizableResourceString(nameof(PreventNumericIntPtrUIntPtrBehavioralChangesOperatorThrowsMessage)),
            DiagnosticCategory.Reliability,
            RuleLevel.IdeSuggestion,
            s_descriptionResource,
            isPortedFxCopRule: false,
            isDataflowRule: false);

        internal static readonly DiagnosticDescriptor ConversionThrowsRule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_titleResource,
            CreateLocalizableResourceString(nameof(PreventNumericIntPtrUIntPtrBehavioralChangesConversionThrowsMessage)),
            DiagnosticCategory.Reliability,
            RuleLevel.IdeSuggestion,
            s_descriptionResource,
            isPortedFxCopRule: false,
            isDataflowRule: false);

        internal static readonly DiagnosticDescriptor ConversionNotThrowRule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_titleResource,
            CreateLocalizableResourceString(nameof(PreventNumericIntPtrUIntPtrBehavioralChangesConversionNotThrowMessage)),
            DiagnosticCategory.Reliability,
            RuleLevel.IdeSuggestion,
            s_descriptionResource,
            isPortedFxCopRule: false,
            isDataflowRule: false);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(OperatorThrowsRule, ConversionThrowsRule, ConversionNotThrowRule);

        protected abstract bool IsWithinCheckedContext(IOperation operation);

        protected abstract bool IsAliasUsed(ISymbol? symbol);

        protected abstract bool IsAliasUsed(SyntaxNode syntax);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterCompilationStartAction(context =>
            {
                if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesRuntimeFeature, out var runtimeFeatureType) ||
                    !runtimeFeatureType.GetMembers("NumericIntPtr").OfType<IFieldSymbol>().Any())
                {
                    // Numeric IntPtr feature not available
                    return;
                }

                context.RegisterOperationAction(context =>
                {
                    if (context.Operation is IBinaryOperation binaryOperation &&
                        binaryOperation.IsAdditionOrSubstractionOperation(out var binaryOperator) &&
                        binaryOperation.IsChecked)
                    {
                        if ((binaryOperation.LeftOperand.Type?.SpecialType == SpecialType.System_IntPtr ||
                             binaryOperation.LeftOperand.Type?.SpecialType == SpecialType.System_UIntPtr) &&
                             IsConversionFromInt32(binaryOperation.RightOperand) &&
                             !IsAliasUsed(GetSymbol(binaryOperation.LeftOperand)))
                        {
                            context.ReportDiagnostic(binaryOperation.CreateDiagnostic(OperatorThrowsRule, binaryOperator));
                        }
                    }
                    else if (context.Operation is IConversionOperation conversionOperation)
                    {
                        var operation = conversionOperation.WalkDownConversion(c => c.IsImplicit); // get innermost conversion
                        if (operation is IConversionOperation explicitConversion &&
                            explicitConversion.OperatorMethod == null) // Built in conversion
                        {
                            if (explicitConversion.IsChecked ||
                                IsWithinCheckedContext(explicitConversion))
                            {
                                if (IsIntPtrToOrFromPtrConversion(explicitConversion.Type, explicitConversion.Operand.Type) &&
                                    !IsAliasUsed(GetSymbol(explicitConversion.Operand)))
                                {
                                    context.ReportDiagnostic(explicitConversion.CreateDiagnostic(ConversionThrowsRule,
                                        PopulateConversionString(explicitConversion.Type, explicitConversion.Operand.Type)));
                                }
                                else if (IsIntPtrToOrFromPtrConversion(explicitConversion.Operand.Type, explicitConversion.Type) &&
                                         !IsAliasUsed(explicitConversion.Syntax))
                                {
                                    context.ReportDiagnostic(explicitConversion.CreateDiagnostic(ConversionThrowsRule,
                                        PopulateConversionString(explicitConversion.Type, explicitConversion.Operand.Type)));
                                }
                            }
                            else // unchecked context
                            {
                                if ((IsLongToIntPtrConversion(explicitConversion.Type, explicitConversion.Operand.Type) ||
                                     IsULongToUIntPtrConversion(explicitConversion.Type, explicitConversion.Operand.Type)) &&
                                    !IsAliasUsed(explicitConversion.Syntax))
                                {
                                    context.ReportDiagnostic(explicitConversion.CreateDiagnostic(ConversionNotThrowRule,
                                        PopulateConversionString(explicitConversion.Type, explicitConversion.Operand.Type)));
                                }
                                else if ((IsIntPtrToIntConversion(explicitConversion.Type, explicitConversion.Operand.Type) ||
                                          IsUIntPtrToUIntConversion(explicitConversion.Type, explicitConversion.Operand.Type)) &&
                                        !IsAliasUsed(GetSymbol(explicitConversion.Operand)))
                                {
                                    context.ReportDiagnostic(explicitConversion.CreateDiagnostic(ConversionNotThrowRule,
                                        PopulateConversionString(explicitConversion.Type, explicitConversion.Operand.Type)));
                                }
                            }
                        }
                    }
                },
                OperationKind.Binary, OperationKind.Conversion);
            });

            static string PopulateConversionString(ITypeSymbol type, ITypeSymbol operand)
            {
                string typeName = type.Name;
                string operandName = operand.Name;

                if (type is IPointerTypeSymbol)
                {
                    typeName = type.ToString();
                }

                if (operand is IPointerTypeSymbol)
                {
                    operandName = operand.ToString();
                }

                return $"({typeName}){operandName}";
            }

            static ISymbol? GetSymbol(IOperation operation) =>
                operation switch
                {
                    IFieldReferenceOperation fieldReference => fieldReference.Field,
                    IParameterReferenceOperation parameter => parameter.Parameter,
                    ILocalReferenceOperation local => local.Local,
                    _ => null,
                };

            static bool IsConversionFromInt32(IOperation operation) =>
                operation is IConversionOperation conversion &&
                conversion.Operand.Type?.SpecialType == SpecialType.System_Int32;

            static bool IsIntPtrToOrFromPtrConversion([NotNullWhen(true)] ITypeSymbol? pointerType, [NotNullWhen(true)] ITypeSymbol? intPtrType) =>
                intPtrType?.SpecialType == SpecialType.System_IntPtr &&
                pointerType is IPointerTypeSymbol pointer;

            static bool IsLongToIntPtrConversion([NotNullWhen(true)] ITypeSymbol? convertingType, [NotNullWhen(true)] ITypeSymbol? operandType) =>
                convertingType?.SpecialType == SpecialType.System_IntPtr &&
                operandType?.SpecialType == SpecialType.System_Int64;

            static bool IsIntPtrToIntConversion([NotNullWhen(true)] ITypeSymbol? convertingType, [NotNullWhen(true)] ITypeSymbol? operandType) =>
                convertingType?.SpecialType == SpecialType.System_Int32 &&
                operandType?.SpecialType == SpecialType.System_IntPtr;

            static bool IsULongToUIntPtrConversion([NotNullWhen(true)] ITypeSymbol? convertingType, [NotNullWhen(true)] ITypeSymbol? operandType) =>
                convertingType?.SpecialType == SpecialType.System_UIntPtr &&
                operandType?.SpecialType == SpecialType.System_UInt64;

            static bool IsUIntPtrToUIntConversion([NotNullWhen(true)] ITypeSymbol? convertingType, [NotNullWhen(true)] ITypeSymbol? operandType) =>
                convertingType?.SpecialType == SpecialType.System_UInt32 &&
                operandType?.SpecialType == SpecialType.System_UIntPtr;
        }
    }
}