File: AbstractAllocationAnalyzer.cs
Web Access
Project: src\src\RoslynAnalyzers\PerformanceSensitiveAnalyzers\Core\Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.csproj (Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Immutable;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers
{
    internal abstract class AbstractAllocationAnalyzer : DiagnosticAnalyzer
    {
        protected abstract ImmutableArray<OperationKind> Operations { get; }
 
        protected abstract void AnalyzeNode(OperationAnalysisContext context, in PerformanceSensitiveInfo info);
 
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
 
            // This analyzer is triggered by an attribute, even if it appears in generated code
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
 
            if (Operations.IsEmpty)
            {
                return;
            }
 
            context.RegisterCompilationStartAction(compilationStartContext =>
            {
                var compilation = compilationStartContext.Compilation;
                var attributeSymbol = compilation.GetOrCreateTypeByMetadataName(AllocationRules.PerformanceSensitiveAttributeName);
 
                // Bail if PerformanceSensitiveAttribute is not declared in the compilation.
                if (attributeSymbol == null)
                {
                    return;
                }
 
                compilationStartContext.RegisterOperationBlockStartAction(blockStartContext =>
                {
                    var checker = new AttributeChecker(attributeSymbol);
                    RegisterOperationAnalysis(blockStartContext, checker);
                });
            });
        }
 
        private void RegisterOperationAnalysis(OperationBlockStartAnalysisContext operationBlockStartAnalysisContext, AttributeChecker performanceSensitiveAttributeChecker)
        {
            var owningSymbol = operationBlockStartAnalysisContext.OwningSymbol;
            if (!performanceSensitiveAttributeChecker.TryGetContainsPerformanceSensitiveInfo(owningSymbol, out var info))
            {
                return;
            }
 
            operationBlockStartAnalysisContext.RegisterOperationAction(
                syntaxNodeContext =>
                {
                    AnalyzeNode(syntaxNodeContext, in info);
                },
                Operations);
        }
 
        protected sealed class AttributeChecker
        {
            private INamedTypeSymbol PerfSensitiveAttributeSymbol { get; }
 
            public AttributeChecker(INamedTypeSymbol perfSensitiveAttributeSymbol)
            {
                PerfSensitiveAttributeSymbol = perfSensitiveAttributeSymbol;
            }
 
            public bool TryGetContainsPerformanceSensitiveInfo(ISymbol symbol, out PerformanceSensitiveInfo info)
            {
                if (TryGet(symbol, out info))
                {
                    return true;
                }
 
                // The attribute might be applied to a property declaration, instead of its accessor declaration.
                if (symbol is IMethodSymbol methodSymbol &&
                    (methodSymbol.MethodKind == MethodKind.PropertyGet || methodSymbol.MethodKind == MethodKind.PropertySet) &&
                    TryGet(methodSymbol.AssociatedSymbol!, out info))
                {
                    return true;
                }
 
                info = default;
                return false;
 
                bool TryGet(ISymbol s, out PerformanceSensitiveInfo i)
                {
                    if (s.GetAttribute(PerfSensitiveAttributeSymbol) is { } attribute)
                    {
                        i = CreatePerformanceSensitiveInfo(attribute);
                        return true;
                    }
 
                    i = default;
                    return false;
                }
            }
 
            private static PerformanceSensitiveInfo CreatePerformanceSensitiveInfo(AttributeData data)
            {
                var allowCaptures = true;
                var allowGenericEnumeration = true;
                var allowLocks = true;
 
                foreach (var namedArgument in data.NamedArguments)
                {
                    switch (namedArgument.Key)
                    {
                        case "AllowCaptures":
                            allowCaptures = (bool)namedArgument.Value.Value!;
                            break;
                        case "AllowGenericEnumeration":
                            allowGenericEnumeration = (bool)namedArgument.Value.Value!;
                            break;
                        case "AllowLocks":
                            allowLocks = (bool)namedArgument.Value.Value!;
                            break;
                    }
                }
 
                return new PerformanceSensitiveInfo(allowCaptures, allowGenericEnumeration, allowLocks);
            }
        }
 
#pragma warning disable CA1815 // Override equals and operator equals on value types. This type is never used for comparison
        protected readonly struct PerformanceSensitiveInfo
#pragma warning restore CA1815
        {
            public bool AllowCaptures { get; }
            public bool AllowGenericEnumeration { get; }
            public bool AllowLocks { get; }
 
            public PerformanceSensitiveInfo(
                bool allowCaptures = true,
                bool allowGenericEnumeration = true,
                bool allowLocks = true)
            {
                AllowCaptures = allowCaptures;
                AllowGenericEnumeration = allowGenericEnumeration;
                AllowLocks = allowLocks;
            }
        }
    }
}