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