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