// 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(); } } } } } |