File: Microsoft.NetCore.Analyzers\Runtime\DoNotUseReferenceEqualsWithValueTypes.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 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>
    /// CA2013: <inheritdoc cref="DoNotUseReferenceEqualsWithValueTypesTitle"/>
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public sealed class DoNotUseReferenceEqualsWithValueTypesAnalyzer : DiagnosticAnalyzer
    {
        internal const string RuleId = "CA2013";

        private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(DoNotUseReferenceEqualsWithValueTypesTitle));
        private static readonly LocalizableString s_localizableDescription = CreateLocalizableResourceString(nameof(DoNotUseReferenceEqualsWithValueTypesDescription));

        internal static readonly DiagnosticDescriptor ComparerRule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_localizableTitle,
            CreateLocalizableResourceString(nameof(DoNotUseReferenceEqualsWithValueTypesComparerMessage)),
            DiagnosticCategory.Reliability,
            RuleLevel.BuildWarning,
            description: s_localizableDescription,
            isPortedFxCopRule: false,
            isDataflowRule: false);

        internal static readonly DiagnosticDescriptor MethodRule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_localizableTitle,
            CreateLocalizableResourceString(nameof(DoNotUseReferenceEqualsWithValueTypesMethodMessage)),
            DiagnosticCategory.Reliability,
            RuleLevel.BuildWarning,
            description: s_localizableDescription,
            isPortedFxCopRule: false,
            isDataflowRule: false);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(MethodRule, ComparerRule);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

            context.RegisterCompilationStartAction(compilationStartContext =>
            {
                var objectType = compilationStartContext.Compilation.GetSpecialType(SpecialType.System_Object);

                var objectObjectParameters = new[]
                {
                    ParameterInfo.GetParameterInfo(objectType),
                    ParameterInfo.GetParameterInfo(objectType)
                };

                var referenceEqualsMethodGroup = objectType.GetMembers("ReferenceEquals").OfType<IMethodSymbol>();
                var referenceEqualsMethod = referenceEqualsMethodGroup.GetFirstOrDefaultMemberWithParameterInfos(
                    objectObjectParameters);

                if (referenceEqualsMethod == null)
                {
                    return;
                }

                var typeProvider = WellKnownTypeProvider.GetOrCreate(compilationStartContext.Compilation);
                var referenceEqualityComparer =
                    typeProvider.GetOrCreateTypeByMetadataName("System.Collections.Generic.ReferenceEqualityComparer");

                IMethodSymbol? comparerEqualsMethod = null;

                if (referenceEqualityComparer != null)
                {
                    var equalsMethodGroup = referenceEqualityComparer.GetMembers("Equals").OfType<IMethodSymbol>();
                    comparerEqualsMethod = equalsMethodGroup.GetFirstOrDefaultMemberWithParameterInfos(
                        objectObjectParameters);
                }

                compilationStartContext.RegisterOperationAction(operationContext =>
                {
                    var invocationExpression = (IInvocationOperation)operationContext.Operation;
                    var targetMethod = invocationExpression.TargetMethod;
                    DiagnosticDescriptor rule;

                    if (targetMethod == null)
                    {
                        return;
                    }

                    if (referenceEqualsMethod.Equals(targetMethod))
                    {
                        rule = MethodRule;
                    }
                    else if (comparerEqualsMethod != null && comparerEqualsMethod.Equals(targetMethod))
                    {
                        rule = ComparerRule;
                    }
                    else
                    {
                        return;
                    }

                    foreach (var argument in invocationExpression.Arguments)
                    {
                        var val = argument.Value;

                        // Only check through one level of conversion,
                        // which will be either the boxing conversion to object,
                        // or a reference type implicit conversion to object.
                        if (val is IConversionOperation conversion)
                        {
                            val = conversion.Operand;
                        }

                        if (val.Type?.IsValueType == true)
                        {
                            operationContext.ReportDiagnostic(
                                val.CreateDiagnostic(
                                    rule,
                                    val.Type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
                        }
                    }
                },
                OperationKind.Invocation);
            });
        }
    }
}