File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\FlowAnalysis\SymbolUsageAnalysis\SymbolUsageAnalysis.DataFlowAnalyzer.FlowGraphAnalysisData.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;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FlowAnalysis.SymbolUsageAnalysis;
 
internal static partial class SymbolUsageAnalysis
{
    private sealed partial class DataFlowAnalyzer : DataFlowAnalyzer<BasicBlockAnalysisData>
    {
        private sealed class FlowGraphAnalysisData : AnalysisData
        {
            private readonly ImmutableArray<IParameterSymbol> _parameters;
 
            /// <summary>
            /// Map from basic block to current <see cref="BasicBlockAnalysisData"/> for dataflow analysis.
            /// </summary>
            private readonly PooledDictionary<BasicBlock, BasicBlockAnalysisData> _analysisDataByBasicBlockMap;
 
            /// <summary>
            /// Callback to analyze lambda/local function invocations and return new block analysis data.
            /// </summary>
            private readonly Func<IMethodSymbol, ControlFlowGraph, AnalysisData, CancellationToken, BasicBlockAnalysisData> _analyzeLocalFunctionOrLambdaInvocation;
 
            /// <summary>
            /// Map from flow capture ID to set of captured symbol addresses along all possible control flow paths.
            /// </summary>
            private readonly PooledDictionary<CaptureId, PooledHashSet<(ISymbol, IOperation)>> _lValueFlowCapturesMap;
 
            /// <summary>
            /// Map from operations to potential delegate creation targets that could be invoked via delegate invocation
            /// on the operation.
            /// Used to analyze delegate creations/invocations of lambdas and local/functions defined in a method.
            /// </summary>
            private readonly PooledDictionary<IOperation, PooledHashSet<IOperation>> _reachingDelegateCreationTargets;
 
            /// <summary>
            /// Map from local functions to the <see cref="ControlFlowGraph"/> where the local function was accessed
            /// to create an invocable delegate. This control flow graph is required to lazily get or create the
            /// control flow graph for this local function at delegate invocation callsite.
            /// </summary>
            private readonly PooledDictionary<IMethodSymbol, ControlFlowGraph> _localFunctionTargetsToAccessingCfgMap;
 
            /// <summary>
            /// Map from lambdas to the <see cref="ControlFlowGraph"/> where the lambda was defined
            /// to create an invocable delegate. This control flow graph is required to lazily get or create the
            /// control flow graph for this lambda at delegate invocation callsite.
            /// </summary>
            private readonly PooledDictionary<IFlowAnonymousFunctionOperation, ControlFlowGraph> _lambdaTargetsToAccessingCfgMap;
 
            /// <summary>
            /// Map from basic block range to set of writes within this block range.
            /// Used for try-catch-finally analysis, where start of catch/finally blocks should
            /// consider all writes in the corresponding try block as reachable.
            /// </summary>
            private readonly PooledDictionary<(int firstBlockOrdinal, int lastBlockOrdinal), PooledHashSet<(ISymbol, IOperation)>> _symbolWritesInsideBlockRangeMap;
 
            private FlowGraphAnalysisData(
                ControlFlowGraph controlFlowGraph,
                ISymbol owningSymbol,
                ImmutableArray<IParameterSymbol> parameters,
                ImmutableHashSet<ILocalSymbol> capturedLocals,
                PooledDictionary<BasicBlock, BasicBlockAnalysisData> analysisDataByBasicBlockMap,
                PooledDictionary<(ISymbol symbol, IOperation operation), bool> symbolsWriteMap,
                PooledHashSet<ISymbol> symbolsRead,
                PooledHashSet<IMethodSymbol> lambdaOrLocalFunctionsBeingAnalyzed,
                Func<IMethodSymbol, ControlFlowGraph, AnalysisData, CancellationToken, BasicBlockAnalysisData> analyzeLocalFunctionOrLambdaInvocation,
                PooledDictionary<IOperation, PooledHashSet<IOperation>> reachingDelegateCreationTargets,
                PooledDictionary<IMethodSymbol, ControlFlowGraph> localFunctionTargetsToAccessingCfgMap,
                PooledDictionary<IFlowAnonymousFunctionOperation, ControlFlowGraph> lambdaTargetsToAccessingCfgMap)
            {
                ControlFlowGraph = controlFlowGraph;
                OwningSymbol = owningSymbol;
                _parameters = parameters;
                CapturedLocals = capturedLocals;
                _analysisDataByBasicBlockMap = analysisDataByBasicBlockMap;
                _analyzeLocalFunctionOrLambdaInvocation = analyzeLocalFunctionOrLambdaInvocation;
                _reachingDelegateCreationTargets = reachingDelegateCreationTargets;
                _localFunctionTargetsToAccessingCfgMap = localFunctionTargetsToAccessingCfgMap;
                _lambdaTargetsToAccessingCfgMap = lambdaTargetsToAccessingCfgMap;
 
                SymbolsWriteBuilder = symbolsWriteMap;
                SymbolsReadBuilder = symbolsRead;
                LambdaOrLocalFunctionsBeingAnalyzed = lambdaOrLocalFunctionsBeingAnalyzed;
 
                _lValueFlowCapturesMap = PooledDictionary<CaptureId, PooledHashSet<(ISymbol, IOperation)>>.GetInstance();
                LValueFlowCapturesInGraph = LValueFlowCapturesProvider.CreateLValueFlowCaptures(controlFlowGraph);
                Debug.Assert(LValueFlowCapturesInGraph.Values.All(kind => kind is FlowCaptureKind.LValueCapture or FlowCaptureKind.LValueAndRValueCapture));
 
                _symbolWritesInsideBlockRangeMap = PooledDictionary<(int firstBlockOrdinal, int lastBlockOrdinal), PooledHashSet<(ISymbol, IOperation)>>.GetInstance();
            }
 
            public ISymbol OwningSymbol { get; }
 
            protected override PooledHashSet<ISymbol> SymbolsReadBuilder { get; }
 
            protected override PooledDictionary<(ISymbol symbol, IOperation operation), bool> SymbolsWriteBuilder { get; }
 
            protected override PooledHashSet<IMethodSymbol> LambdaOrLocalFunctionsBeingAnalyzed { get; }
 
            public ImmutableHashSet<ILocalSymbol> CapturedLocals { get; }
 
            public static FlowGraphAnalysisData Create(
                ControlFlowGraph cfg,
                ISymbol owningSymbol,
                Func<IMethodSymbol, ControlFlowGraph, AnalysisData, CancellationToken, BasicBlockAnalysisData> analyzeLocalFunctionOrLambdaInvocation)
            {
                Debug.Assert(cfg.Parent == null);
 
                var parameters = owningSymbol.GetParameters();
                return new FlowGraphAnalysisData(
                    cfg,
                    owningSymbol,
                    parameters,
                    capturedLocals: GetCapturedLocals(cfg),
                    analysisDataByBasicBlockMap: CreateAnalysisDataByBasicBlockMap(cfg),
                    symbolsWriteMap: CreateSymbolsWriteMap(parameters),
                    symbolsRead: PooledHashSet<ISymbol>.GetInstance(),
                    lambdaOrLocalFunctionsBeingAnalyzed: PooledHashSet<IMethodSymbol>.GetInstance(),
                    analyzeLocalFunctionOrLambdaInvocation,
                    reachingDelegateCreationTargets: PooledDictionary<IOperation, PooledHashSet<IOperation>>.GetInstance(),
                    localFunctionTargetsToAccessingCfgMap: PooledDictionary<IMethodSymbol, ControlFlowGraph>.GetInstance(),
                    lambdaTargetsToAccessingCfgMap: PooledDictionary<IFlowAnonymousFunctionOperation, ControlFlowGraph>.GetInstance());
            }
 
            public static FlowGraphAnalysisData Create(
                ControlFlowGraph cfg,
                IMethodSymbol lambdaOrLocalFunction,
                FlowGraphAnalysisData parentAnalysisData)
            {
                Debug.Assert(cfg.Parent != null);
                Debug.Assert(lambdaOrLocalFunction.IsAnonymousFunction() || lambdaOrLocalFunction.IsLocalFunction());
                Debug.Assert(parentAnalysisData != null);
 
                var parameters = lambdaOrLocalFunction.GetParameters();
                return new FlowGraphAnalysisData(
                    cfg,
                    lambdaOrLocalFunction,
                    parameters,
                    capturedLocals: parentAnalysisData.CapturedLocals,
                    analysisDataByBasicBlockMap: CreateAnalysisDataByBasicBlockMap(cfg),
                    symbolsWriteMap: UpdateSymbolsWriteMap(parentAnalysisData.SymbolsWriteBuilder, parameters),
                    symbolsRead: parentAnalysisData.SymbolsReadBuilder,
                    lambdaOrLocalFunctionsBeingAnalyzed: parentAnalysisData.LambdaOrLocalFunctionsBeingAnalyzed,
                    analyzeLocalFunctionOrLambdaInvocation: parentAnalysisData._analyzeLocalFunctionOrLambdaInvocation,
                    reachingDelegateCreationTargets: parentAnalysisData._reachingDelegateCreationTargets,
                    localFunctionTargetsToAccessingCfgMap: parentAnalysisData._localFunctionTargetsToAccessingCfgMap,
                    lambdaTargetsToAccessingCfgMap: parentAnalysisData._lambdaTargetsToAccessingCfgMap);
            }
 
            private static PooledDictionary<BasicBlock, BasicBlockAnalysisData> CreateAnalysisDataByBasicBlockMap(
                ControlFlowGraph cfg)
            {
                var builder = PooledDictionary<BasicBlock, BasicBlockAnalysisData>.GetInstance();
                foreach (var block in cfg.Blocks)
                {
                    builder.Add(block, null);
                }
 
                return builder;
            }
 
            public ControlFlowGraph ControlFlowGraph { get; }
 
            /// <summary>
            /// Flow captures for l-value or address captures.
            /// </summary>
            public ImmutableDictionary<CaptureId, FlowCaptureKind> LValueFlowCapturesInGraph { get; }
 
            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            private static ImmutableHashSet<ILocalSymbol> GetCapturedLocals(ControlFlowGraph cfg)
            {
                using var _ = PooledHashSet<ILocalSymbol>.GetInstance(out var builder);
                foreach (var operation in cfg.OriginalOperation.Descendants())
                {
                    if (operation.Kind is OperationKind.LocalFunction or OperationKind.AnonymousFunction)
                    {
                        var dataFlow = operation.SemanticModel.AnalyzeDataFlow(operation.Syntax);
                        builder.AddRange(dataFlow.Captured.OfType<ILocalSymbol>());
                    }
                }
 
                return [.. builder];
            }
 
            public BasicBlockAnalysisData GetBlockAnalysisData(BasicBlock basicBlock)
                => _analysisDataByBasicBlockMap[basicBlock];
 
            public BasicBlockAnalysisData GetOrCreateBlockAnalysisData(BasicBlock basicBlock, CancellationToken cancellationToken)
            {
                if (_analysisDataByBasicBlockMap[basicBlock] == null)
                {
                    _analysisDataByBasicBlockMap[basicBlock] = CreateBlockAnalysisData();
                }
 
                HandleCatchOrFilterOrFinallyInitialization(basicBlock, cancellationToken);
                return _analysisDataByBasicBlockMap[basicBlock];
            }
 
            private PooledHashSet<(ISymbol, IOperation)> GetOrCreateSymbolWritesInBlockRange(int firstBlockOrdinal, int lastBlockOrdinal, CancellationToken cancellationToken)
            {
                if (!_symbolWritesInsideBlockRangeMap.TryGetValue((firstBlockOrdinal, lastBlockOrdinal), out var writesInBlockRange))
                {
                    // Compute all descendant operations in basic block range.
                    var operations = PooledHashSet<IOperation>.GetInstance();
                    AddDescendantOperationsInRange(ControlFlowGraph, firstBlockOrdinal, lastBlockOrdinal, operations, cancellationToken);
 
                    // Filter down the operations to writes within this block range.
                    writesInBlockRange = PooledHashSet<(ISymbol, IOperation)>.GetInstance();
                    foreach (var (symbol, write) in SymbolsWriteBuilder.Where(kvp => !kvp.Value).Select(kvp => kvp.Key).ToArray())
                    {
                        if (write != null && operations.Contains(write))
                        {
                            writesInBlockRange.Add((symbol, write));
                        }
                    }
                }
 
                return writesInBlockRange;
            }
 
            private void AddDescendantOperationsInRange(
                ControlFlowGraph cfg,
                int firstBlockOrdinal,
                int lastBlockOrdinal,
                PooledHashSet<IOperation> operationsBuilder,
                CancellationToken cancellationToken)
            {
                // Compute all descendant operations in basic block range.
                for (var i = firstBlockOrdinal; i <= lastBlockOrdinal; i++)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    foreach (var operation in cfg.Blocks[i].DescendantOperations())
                    {
                        var added = operationsBuilder.Add(operation);
                        if (added && operation is IInvocationOperation invocation)
                        {
                            if (invocation.Instance != null &&
                                _reachingDelegateCreationTargets.TryGetValue(invocation.Instance, out var targets))
                            {
                                AddDescendantOperationsFromDelegateCreationTargets(targets);
                            }
                            else if (invocation.TargetMethod.IsLocalFunction())
                            {
                                var localFunctionGraph = cfg.GetLocalFunctionControlFlowGraphInScope(invocation.TargetMethod.OriginalDefinition, cancellationToken);
                                if (localFunctionGraph != null)
                                {
                                    AddDescendantOperationsInLambdaOrLocalFunctionGraph(localFunctionGraph);
                                }
                            }
                        }
                    }
                }
 
                return;
 
                // Local functions.
                void AddDescendantOperationsFromDelegateCreationTargets(PooledHashSet<IOperation> targets)
                {
                    foreach (var target in targets)
                    {
                        ControlFlowGraph lambdaOrLocalFunctionCfgOpt = null;
                        switch (target)
                        {
                            case IFlowAnonymousFunctionOperation flowAnonymousFunctionOperation:
                                lambdaOrLocalFunctionCfgOpt = TryGetAnonymousFunctionControlFlowGraphInScope(flowAnonymousFunctionOperation);
                                break;
 
                            case ILocalFunctionOperation localFunctionOperation:
                                lambdaOrLocalFunctionCfgOpt = TryGetLocalFunctionControlFlowGraphInScope(localFunctionOperation.Symbol);
                                break;
 
                            case IMethodReferenceOperation methodReferenceOperation when (methodReferenceOperation.Method.IsLocalFunction()):
                                lambdaOrLocalFunctionCfgOpt = TryGetLocalFunctionControlFlowGraphInScope(methodReferenceOperation.Method);
                                break;
                        }
 
                        if (lambdaOrLocalFunctionCfgOpt != null &&
                            operationsBuilder.Add(target))
                        {
                            AddDescendantOperationsInLambdaOrLocalFunctionGraph(lambdaOrLocalFunctionCfgOpt);
                        }
                    }
                }
 
                void AddDescendantOperationsInLambdaOrLocalFunctionGraph(ControlFlowGraph lambdaOrLocalFunctionCfg)
                {
                    Debug.Assert(lambdaOrLocalFunctionCfg != null);
                    AddDescendantOperationsInRange(lambdaOrLocalFunctionCfg, firstBlockOrdinal: 0,
                        lastBlockOrdinal: lambdaOrLocalFunctionCfg.Blocks.Length - 1, operationsBuilder, cancellationToken);
                }
 
                ControlFlowGraph TryGetAnonymousFunctionControlFlowGraphInScope(IFlowAnonymousFunctionOperation flowAnonymousFunctionOperation)
                {
                    if (_lambdaTargetsToAccessingCfgMap.TryGetValue(flowAnonymousFunctionOperation, out var lambdaAccessingCfg))
                    {
                        var anonymousFunctionCfg = lambdaAccessingCfg.GetAnonymousFunctionControlFlowGraphInScope(flowAnonymousFunctionOperation, cancellationToken);
                        Debug.Assert(anonymousFunctionCfg != null);
                        return anonymousFunctionCfg;
                    }
 
                    return null;
                }
 
                ControlFlowGraph TryGetLocalFunctionControlFlowGraphInScope(IMethodSymbol localFunction)
                {
                    Debug.Assert(localFunction.IsLocalFunction());
 
                    // Use the original definition of the local function for flow analysis.
                    localFunction = localFunction.OriginalDefinition;
 
                    if (_localFunctionTargetsToAccessingCfgMap.TryGetValue(localFunction, out var localFunctionAccessingCfg))
                    {
                        var localFunctionCfg = localFunctionAccessingCfg.GetLocalFunctionControlFlowGraphInScope(localFunction, cancellationToken);
                        Debug.Assert(localFunctionCfg != null);
                        return localFunctionCfg;
                    }
 
                    return null;
                }
            }
 
            /// <summary>
            /// Special handling to ensure that at start of catch/filter/finally region analysis,
            /// we mark all symbol writes from the corresponding try region as reachable in the
            /// catch/filter/finally region.
            /// </summary>
            /// <param name="basicBlock"></param>
            private void HandleCatchOrFilterOrFinallyInitialization(BasicBlock basicBlock, CancellationToken cancellationToken)
            {
                Debug.Assert(_analysisDataByBasicBlockMap[basicBlock] != null);
 
                // Ensure we are processing a basic block with following properties:
                //  1. It has no predecessors
                //  2. It is not the entry block
                //  3. It is the first block of its enclosing region.
                if (!basicBlock.Predecessors.IsEmpty ||
                    basicBlock.Kind == BasicBlockKind.Entry ||
                    basicBlock.EnclosingRegion.FirstBlockOrdinal != basicBlock.Ordinal)
                {
                    return;
                }
 
                // Find the outermost region for which this block is the first block.
                var outermostEnclosingRegionStartingBlock = basicBlock.EnclosingRegion;
                while (outermostEnclosingRegionStartingBlock.EnclosingRegion?.FirstBlockOrdinal == basicBlock.Ordinal)
                {
                    outermostEnclosingRegionStartingBlock = outermostEnclosingRegionStartingBlock.EnclosingRegion;
                }
 
                // Check if we are at start of catch or filter or finally.
                switch (outermostEnclosingRegionStartingBlock.Kind)
                {
                    case ControlFlowRegionKind.Catch:
                    case ControlFlowRegionKind.Filter:
                    case ControlFlowRegionKind.FilterAndHandler:
                    case ControlFlowRegionKind.Finally:
                        break;
 
                    default:
                        return;
                }
 
                // Find the outer try/catch or try/finally for this region.
                ControlFlowRegion containingTryCatchFinallyRegion = null;
                var currentRegion = outermostEnclosingRegionStartingBlock;
                do
                {
                    switch (currentRegion.Kind)
                    {
                        case ControlFlowRegionKind.TryAndCatch:
                        case ControlFlowRegionKind.TryAndFinally:
                            containingTryCatchFinallyRegion = currentRegion;
                            break;
                    }
 
                    currentRegion = currentRegion.EnclosingRegion;
                }
                while (containingTryCatchFinallyRegion == null);
 
                // All symbol writes reachable at start of try region are considered reachable at start of catch/finally region.
                var firstBasicBlockInOutermostRegion = ControlFlowGraph.Blocks[containingTryCatchFinallyRegion.FirstBlockOrdinal];
                var mergedAnalysisData = _analysisDataByBasicBlockMap[basicBlock];
                mergedAnalysisData.SetAnalysisDataFrom(GetBlockAnalysisData(firstBasicBlockInOutermostRegion));
 
                // All symbol writes within the try region are considered reachable at start of catch/finally region.
                foreach (var (symbol, write) in GetOrCreateSymbolWritesInBlockRange(containingTryCatchFinallyRegion.FirstBlockOrdinal, basicBlock.Ordinal - 1, cancellationToken))
                {
                    mergedAnalysisData.OnWriteReferenceFound(symbol, write, maybeWritten: true);
                    SymbolsWriteBuilder[(symbol, write)] = true;
                    SymbolsReadBuilder.Add(symbol);
                }
 
                SetBlockAnalysisData(basicBlock, mergedAnalysisData);
            }
 
            public void SetCurrentBlockAnalysisDataFrom(BasicBlock basicBlock, CancellationToken cancellationToken)
                => SetCurrentBlockAnalysisDataFrom(GetOrCreateBlockAnalysisData(basicBlock, cancellationToken));
 
            public void SetAnalysisDataOnEntryBlockStart()
            {
                foreach (var parameter in _parameters)
                {
                    SymbolsWriteBuilder[(parameter, null)] = false;
                    CurrentBlockAnalysisData.OnWriteReferenceFound(parameter, operation: null, maybeWritten: false);
                }
            }
 
            public void SetBlockAnalysisData(BasicBlock basicBlock, BasicBlockAnalysisData data)
                => _analysisDataByBasicBlockMap[basicBlock] = data;
 
            public void SetBlockAnalysisDataFrom(BasicBlock basicBlock, BasicBlockAnalysisData data, CancellationToken cancellationToken)
                => GetOrCreateBlockAnalysisData(basicBlock, cancellationToken).SetAnalysisDataFrom(data);
 
            public void SetAnalysisDataOnMethodExit()
            {
                if (SymbolsWriteBuilder.Count == 0)
                {
                    return;
                }
 
                // Mark all reachable definitions for ref/out parameters at end of exit block as used.
                foreach (var parameter in _parameters)
                {
                    if (parameter.RefKind is RefKind.Ref or RefKind.Out)
                    {
                        CurrentBlockAnalysisData.ForEachCurrentWrite(
                            parameter,
                            static (write, arg) =>
                            {
                                if (write != null)
                                {
                                    arg.self.SymbolsWriteBuilder[(arg.parameter, write)] = true;
                                }
                            },
                            (parameter, self: this));
                    }
                }
            }
 
            public override bool IsLValueFlowCapture(CaptureId captureId)
                => LValueFlowCapturesInGraph.ContainsKey(captureId);
 
            public override bool IsRValueFlowCapture(CaptureId captureId)
                => !LValueFlowCapturesInGraph.TryGetValue(captureId, out var captureKind) || captureKind != FlowCaptureKind.LValueCapture;
 
            public override void OnLValueCaptureFound(ISymbol symbol, IOperation operation, CaptureId captureId)
            {
                if (!_lValueFlowCapturesMap.TryGetValue(captureId, out var captures))
                {
                    captures = PooledHashSet<(ISymbol, IOperation)>.GetInstance();
                    _lValueFlowCapturesMap.Add(captureId, captures);
                }
 
                captures.Add((symbol, operation));
            }
 
            public override void OnLValueDereferenceFound(CaptureId captureId)
            {
                if (_lValueFlowCapturesMap.TryGetValue(captureId, out var captures))
                {
                    var mayBeWritten = captures.Count > 1;
                    foreach (var (symbol, write) in captures)
                    {
                        OnWriteReferenceFound(symbol, write, mayBeWritten, isRef: false);
                    }
                }
            }
 
            protected override BasicBlockAnalysisData AnalyzeLocalFunctionInvocationCore(IMethodSymbol localFunction, CancellationToken cancellationToken)
            {
                Debug.Assert(localFunction.IsLocalFunction());
                Debug.Assert(localFunction.Equals(localFunction.OriginalDefinition));
 
                cancellationToken.ThrowIfCancellationRequested();
                if (!_localFunctionTargetsToAccessingCfgMap.TryGetValue(localFunction, out var accessingCfg))
                {
                    accessingCfg = ControlFlowGraph;
                }
 
                var localFunctionCfg = accessingCfg.GetLocalFunctionControlFlowGraphInScope(localFunction, cancellationToken);
                return _analyzeLocalFunctionOrLambdaInvocation(localFunction, localFunctionCfg, this, cancellationToken);
            }
 
            protected override BasicBlockAnalysisData AnalyzeLambdaInvocationCore(IFlowAnonymousFunctionOperation lambda, CancellationToken cancellationToken)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (!_lambdaTargetsToAccessingCfgMap.TryGetValue(lambda, out var accessingCfg))
                {
                    accessingCfg = ControlFlowGraph;
                }
 
                var lambdaCfg = accessingCfg.GetAnonymousFunctionControlFlowGraphInScope(lambda, cancellationToken);
                return _analyzeLocalFunctionOrLambdaInvocation(lambda.Symbol, lambdaCfg, this, cancellationToken);
            }
 
            public override bool IsTrackingDelegateCreationTargets => true;
 
            public override void SetTargetsFromSymbolForDelegate(IOperation write, ISymbol symbol)
            {
                // Transfer reaching delegate creation targets when assigning from a local/parameter symbol
                // that has known set of potential delegate creation targets. For example, this method will be called
                // for definition 'y' from symbol 'x' for below code:
                //      Action x = () => { };
                //      Action y = x;
                //
                var targetsBuilder = PooledHashSet<IOperation>.GetInstance();
                var completedVisit = CurrentBlockAnalysisData.ForEachCurrentWrite(
                    symbol,
                    static (symbolWrite, arg) =>
                    {
                        if (symbolWrite == null)
                        {
                            // Continue with the iteration
                            return true;
                        }
 
                        if (!arg.self._reachingDelegateCreationTargets.TryGetValue(symbolWrite, out var targetsBuilderForSymbolWrite))
                        {
                            // Unable to find delegate creation targets for this symbol write.
                            // Bail out without setting targets.
                            arg.targetsBuilder.Free();
 
                            // Stop iterating here, even if early
                            return false;
                        }
                        else
                        {
                            foreach (var target in targetsBuilderForSymbolWrite)
                            {
                                arg.targetsBuilder.Add(target);
                            }
                        }
 
                        // Continue with the iteration
                        return true;
                    },
                    (targetsBuilder, self: this));
 
                if (!completedVisit)
                    return;
 
                _reachingDelegateCreationTargets[write] = targetsBuilder;
            }
 
            public override void SetLambdaTargetForDelegate(IOperation write, IFlowAnonymousFunctionOperation lambdaTarget)
            {
                // Sets a lambda delegate target for the current write.
                // For example, this method will be called for the definition 'x' below with assigned lambda.
                //      Action x = () => { };
                //
                SetReachingDelegateTargetCore(write, lambdaTarget);
                _lambdaTargetsToAccessingCfgMap[lambdaTarget] = ControlFlowGraph;
            }
 
            public override void SetLocalFunctionTargetForDelegate(IOperation write, IMethodReferenceOperation localFunctionTarget)
            {
                // Sets a local function delegate target for the current write.
                // For example, this method will be called for the definition 'x' below with assigned LocalFunction delegate.
                //      Action x = LocalFunction;
                //      void LocalFunction() { }
                //
                Debug.Assert(localFunctionTarget.Method.IsLocalFunction());
                SetReachingDelegateTargetCore(write, localFunctionTarget);
                _localFunctionTargetsToAccessingCfgMap[localFunctionTarget.Method.OriginalDefinition] = ControlFlowGraph;
            }
 
            public override void SetEmptyInvocationTargetsForDelegate(IOperation write)
                => SetReachingDelegateTargetCore(write, targetOpt: null);
 
            private void SetReachingDelegateTargetCore(IOperation write, IOperation targetOpt)
            {
                var targetsBuilder = PooledHashSet<IOperation>.GetInstance();
                if (targetOpt != null)
                {
                    targetsBuilder.Add(targetOpt);
                }
 
                _reachingDelegateCreationTargets[write] = targetsBuilder;
            }
 
            public override bool TryGetDelegateInvocationTargets(IOperation write, out ImmutableHashSet<IOperation> targets)
            {
                // Attempts to return potential lamba/local function delegate invocation targets for the given write.
                if (_reachingDelegateCreationTargets.TryGetValue(write, out var targetsBuilder))
                {
                    targets = [.. targetsBuilder];
                    return true;
                }
 
                targets = [];
                return false;
            }
 
            public override void Dispose()
            {
                // We share the base analysis data structures between primary method's flow graph analysis
                // and it's inner lambda/local function flow graph analysis.
                // Dispose the base data structures only for primary method's flow analysis data.
                if (ControlFlowGraph.Parent == null)
                {
                    DisposeForNonLocalFunctionOrLambdaAnalysis();
                }
 
                DisposeCommon();
 
                base.Dispose();
 
                return;
 
                // Local functions.
                void DisposeForNonLocalFunctionOrLambdaAnalysis()
                {
                    foreach (var creations in _reachingDelegateCreationTargets.Values)
                    {
                        creations.Free();
                    }
 
                    _reachingDelegateCreationTargets.Free();
 
                    _localFunctionTargetsToAccessingCfgMap.Free();
                    _lambdaTargetsToAccessingCfgMap.Free();
 
                    SymbolsWriteBuilder.Free();
                    SymbolsReadBuilder.Free();
                    LambdaOrLocalFunctionsBeingAnalyzed.Free();
                }
 
                void DisposeCommon()
                {
                    // Note the base type already disposes the BasicBlockAnalysisData values
                    // allocated by us, so we only need to free the map.
                    _analysisDataByBasicBlockMap.Free();
 
                    foreach (var captures in _lValueFlowCapturesMap.Values)
                    {
                        captures.Free();
                    }
 
                    _lValueFlowCapturesMap.Free();
 
                    foreach (var operations in _symbolWritesInsideBlockRangeMap.Values)
                    {
                        operations.Free();
                    }
 
                    _symbolWritesInsideBlockRangeMap.Free();
                }
            }
        }
    }
}