File: Microsoft.CodeQuality.Analyzers\QualityGuidelines\DoNotCallOverridableMethodsInConstructors.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 Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.CodeQuality.Analyzers.QualityGuidelines
{
    using static MicrosoftCodeQualityAnalyzersResources;

    /// <summary>
    /// CA2214: <inheritdoc cref="DoNotCallOverridableMethodsInConstructors"/>
    ///
    /// Cause: The constructor of an unsealed type calls a virtual method defined in its class.
    ///
    /// Description: When a virtual method is called, the actual type that executes the method is not selected
    /// until run time. When a constructor calls a virtual method, it is possible that the constructor for the
    /// instance that invokes the method has not executed.
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public sealed class DoNotCallOverridableMethodsInConstructorsAnalyzer : DiagnosticAnalyzer
    {
        public const string RuleId = "CA2214";
        private static readonly LocalizableString s_localizableMessageAndTitle = CreateLocalizableResourceString(nameof(DoNotCallOverridableMethodsInConstructors));

        public static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_localizableMessageAndTitle,
            s_localizableMessageAndTitle,
            DiagnosticCategory.Usage,
            RuleLevel.Disabled,
            description: CreateLocalizableResourceString(nameof(DoNotCallOverridableMethodsInConstructorsDescription)),
            isPortedFxCopRule: true,
            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(compilationContext =>
            {
                INamedTypeSymbol? webUiControlType = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebUIControl);
                INamedTypeSymbol? componentModelComponentType = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemComponentModelComponent);

                compilationContext.RegisterOperationBlockStartAction(context =>
                {
                    if (ShouldOmitThisDiagnostic(context.OwningSymbol, webUiControlType, componentModelComponentType))
                    {
                        return;
                    }

                    context.RegisterOperationAction(oc => AnalyzeOperation(oc, context.OwningSymbol.ContainingType), OperationKind.Invocation);
                });
            });
        }

        private static void AnalyzeOperation(OperationAnalysisContext context, INamedTypeSymbol containingType)
        {
            var operation = (IInvocationOperation)context.Operation;
            IMethodSymbol method = operation.TargetMethod;
            if (operation.Instance?.Kind == OperationKind.InstanceReference &&
                method != null &&
                (method.IsAbstract || method.IsVirtual) &&
                Equals(method.ContainingType, containingType) &&
                !operation.IsWithinLambdaOrLocalFunction(out _))
            {
                context.ReportDiagnostic(operation.Syntax.CreateDiagnostic(Rule));
            }
        }

        private static bool ShouldOmitThisDiagnostic(ISymbol symbol, INamedTypeSymbol? webUiControlType, INamedTypeSymbol? componentModelComponentType)
        {
            // This diagnostic is only relevant in constructors.
            // TODO: should this apply to instance field initializers for VB?
            if (symbol is not IMethodSymbol m || m.MethodKind != MethodKind.Constructor)
            {
                return true;
            }

            INamedTypeSymbol containingType = m.ContainingType;
            if (containingType == null)
            {
                return true;
            }

            // special case ASP.NET and Components constructors
            if (containingType.Inherits(webUiControlType))
            {
                return true;
            }

            if (containingType.Inherits(componentModelComponentType))
            {
                return true;
            }

            return false;
        }
    }
}