File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\FlowAnalysis\SymbolUsageAnalysis\SymbolUsageAnalysis.DataFlowAnalyzer.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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.
 
#nullable disable
 
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FlowAnalysis.SymbolUsageAnalysis;
 
internal static partial class SymbolUsageAnalysis
{
    /// <summary>
    /// Dataflow analysis to compute symbol usage information (i.e. reads/writes) for locals/parameters
    /// in a given control flow graph, along with the information of whether or not the writes
    /// may be read on some control flow path.
    /// </summary>
    private sealed partial class DataFlowAnalyzer : DataFlowAnalyzer<BasicBlockAnalysisData>
    {
        private readonly FlowGraphAnalysisData _analysisData;
 
        private DataFlowAnalyzer(ControlFlowGraph cfg, ISymbol owningSymbol)
            => _analysisData = FlowGraphAnalysisData.Create(cfg, owningSymbol, AnalyzeLocalFunctionOrLambdaInvocation);
 
        private DataFlowAnalyzer(
            ControlFlowGraph cfg,
            IMethodSymbol lambdaOrLocalFunction,
            FlowGraphAnalysisData parentAnalysisData)
        {
            _analysisData = FlowGraphAnalysisData.Create(cfg, lambdaOrLocalFunction, parentAnalysisData);
 
            var entryBlockAnalysisData = GetEmptyAnalysisData();
            entryBlockAnalysisData.SetAnalysisDataFrom(parentAnalysisData.CurrentBlockAnalysisData);
            _analysisData.SetBlockAnalysisData(cfg.EntryBlock(), entryBlockAnalysisData);
        }
 
        public static SymbolUsageResult RunAnalysis(ControlFlowGraph cfg, ISymbol owningSymbol, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            using var analyzer = new DataFlowAnalyzer(cfg, owningSymbol);
            _ = CustomDataFlowAnalysis<BasicBlockAnalysisData>.Run(cfg, analyzer, cancellationToken);
            return analyzer._analysisData.ToResult();
        }
 
        public override void Dispose()
            => _analysisData.Dispose();
 
        private static BasicBlockAnalysisData AnalyzeLocalFunctionOrLambdaInvocation(
            IMethodSymbol localFunctionOrLambda,
            ControlFlowGraph cfg,
            AnalysisData parentAnalysisData,
            CancellationToken cancellationToken)
        {
            Debug.Assert(localFunctionOrLambda.IsLocalFunction() || localFunctionOrLambda.IsAnonymousFunction());
 
            cancellationToken.ThrowIfCancellationRequested();
            using var analyzer = new DataFlowAnalyzer(cfg, localFunctionOrLambda, (FlowGraphAnalysisData)parentAnalysisData);
 
            var resultBlockAnalysisData = CustomDataFlowAnalysis<BasicBlockAnalysisData>.Run(cfg, analyzer, cancellationToken);
            if (resultBlockAnalysisData == null)
            {
                // Unreachable exit block from lambda/local.
                // So use our current analysis data.
                return parentAnalysisData.CurrentBlockAnalysisData;
            }
 
            // We need to return a cloned basic block analysis data as disposing the DataFlowAnalyzer
            // created above will dispose all basic block analysis data instances allocated by it.
            var clonedBasicBlockData = parentAnalysisData.CreateBlockAnalysisData();
            clonedBasicBlockData.SetAnalysisDataFrom(resultBlockAnalysisData);
            return clonedBasicBlockData;
        }
 
        // Don't analyze blocks which are unreachable, as any write
        // in such a block which has a read outside will be marked redundant, which will just be noise for users.
        // For example,
        //      int x;
        //      if (true)
        //          x = 0;
        //      else
        //          x = 1; // This will be marked redundant if "AnalyzeUnreachableBlocks = true"
        //      return x;
        public override bool AnalyzeUnreachableBlocks => false;
 
        public override BasicBlockAnalysisData AnalyzeBlock(BasicBlock basicBlock, CancellationToken cancellationToken)
        {
            BeforeBlockAnalysis();
            Walker.AnalyzeOperationsAndUpdateData(_analysisData.OwningSymbol, basicBlock.Operations, _analysisData, cancellationToken);
            AfterBlockAnalysis();
            return _analysisData.CurrentBlockAnalysisData;
 
            // Local functions.
            void BeforeBlockAnalysis()
            {
                // Initialize current block analysis data.
                _analysisData.SetCurrentBlockAnalysisDataFrom(basicBlock, cancellationToken);
 
                // At start of entry block, handle parameter definitions from method declaration.
                if (basicBlock.Kind == BasicBlockKind.Entry)
                {
                    _analysisData.SetAnalysisDataOnEntryBlockStart();
                }
            }
 
            void AfterBlockAnalysis()
            {
                // If we are exiting the control flow graph, handle ref/out parameter definitions from method declaration.
                if (basicBlock.FallThroughSuccessor?.Destination == null &&
                    basicBlock.ConditionalSuccessor?.Destination == null)
                {
                    _analysisData.SetAnalysisDataOnMethodExit();
                }
            }
        }
 
        public override BasicBlockAnalysisData AnalyzeNonConditionalBranch(
            BasicBlock basicBlock,
            BasicBlockAnalysisData currentBlockAnalysisData,
            CancellationToken cancellationToken)
            => AnalyzeBranch(basicBlock.FallThroughSuccessor, basicBlock, currentBlockAnalysisData, cancellationToken);
 
        public override (BasicBlockAnalysisData fallThroughSuccessorData, BasicBlockAnalysisData conditionalSuccessorData) AnalyzeConditionalBranch(
            BasicBlock basicBlock,
            BasicBlockAnalysisData currentAnalysisData,
            CancellationToken cancellationToken)
        {
            // Ensure that we use distinct input BasicBlockAnalysisData instances with identical analysis data for both AnalyzeBranch invocations.
            using var savedCurrentAnalysisData = BasicBlockAnalysisData.GetInstance();
            savedCurrentAnalysisData.SetAnalysisDataFrom(currentAnalysisData);
 
            var newCurrentAnalysisData = AnalyzeBranch(basicBlock.FallThroughSuccessor, basicBlock, currentAnalysisData, cancellationToken);
 
            // Ensure that we use different instances of block analysis data for fall through successor and conditional successor.
            _analysisData.AdditionalConditionalBranchAnalysisData.SetAnalysisDataFrom(newCurrentAnalysisData);
            var fallThroughSuccessorData = _analysisData.AdditionalConditionalBranchAnalysisData;
 
            var conditionalSuccessorData = AnalyzeBranch(basicBlock.ConditionalSuccessor, basicBlock, savedCurrentAnalysisData, cancellationToken);
 
            return (fallThroughSuccessorData, conditionalSuccessorData);
        }
 
        private BasicBlockAnalysisData AnalyzeBranch(
            ControlFlowBranch branch,
            BasicBlock basicBlock,
            BasicBlockAnalysisData currentBlockAnalysisData,
            CancellationToken cancellationToken)
        {
            // Initialize current analysis data
            _analysisData.SetCurrentBlockAnalysisDataFrom(currentBlockAnalysisData);
 
            // Analyze the branch value
            var operations = SpecializedCollections.SingletonEnumerable(basicBlock.BranchValue);
            Walker.AnalyzeOperationsAndUpdateData(_analysisData.OwningSymbol, operations, _analysisData, cancellationToken);
            ProcessOutOfScopeLocals();
            return _analysisData.CurrentBlockAnalysisData;
 
            // Function added to address OOM when analyzing symbol usage analysis for locals.
            // This reduces tracking locals as they go out of scope.
            void ProcessOutOfScopeLocals()
            {
                if (branch == null)
                {
                    return;
                }
 
                if (basicBlock.EnclosingRegion.Kind == ControlFlowRegionKind.Catch &&
                    !branch.FinallyRegions.IsEmpty)
                {
                    // Bail out for branches from the catch block
                    // as the locals are still accessible in the finally region.
                    return;
                }
 
                foreach (var region in branch.LeavingRegions)
                {
                    foreach (var local in region.Locals)
                    {
                        // If locals are captured in local/anonymous functions,
                        // then they are not technically out of scope.
                        if (!_analysisData.CapturedLocals.Contains(local))
                        {
                            _analysisData.CurrentBlockAnalysisData.Clear(local);
                        }
                    }
 
                    if (region.Kind == ControlFlowRegionKind.TryAndFinally)
                    {
                        // Locals defined in the outer regions of try/finally might be used in finally region.
                        break;
                    }
                }
            }
        }
 
        public override BasicBlockAnalysisData GetCurrentAnalysisData(BasicBlock basicBlock)
            => _analysisData.GetBlockAnalysisData(basicBlock) ?? GetEmptyAnalysisData();
 
        public override BasicBlockAnalysisData GetEmptyAnalysisData()
            => _analysisData.CreateBlockAnalysisData();
 
        public override void SetCurrentAnalysisData(BasicBlock basicBlock, BasicBlockAnalysisData data, CancellationToken cancellationToken)
            => _analysisData.SetBlockAnalysisDataFrom(basicBlock, data, cancellationToken);
 
        public override bool IsEqual(BasicBlockAnalysisData analysisData1, BasicBlockAnalysisData analysisData2)
            => analysisData1 == null ? analysisData2 == null : analysisData1.Equals(analysisData2);
 
        public override BasicBlockAnalysisData Merge(
            BasicBlockAnalysisData analysisData1,
            BasicBlockAnalysisData analysisData2,
            CancellationToken cancellationToken)
            => BasicBlockAnalysisData.Merge(analysisData1, analysisData2, _analysisData.TrackAllocatedBlockAnalysisData);
    }
}