File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\FlowAnalysis\SymbolUsageAnalysis\SymbolUsageAnalysis.AnalysisData.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Operations;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.FlowAnalysis.SymbolUsageAnalysis;
 
internal static partial class SymbolUsageAnalysis
{
    /// <summary>
    /// Core analysis data to drive the operation <see cref="Walker"/>
    /// for operation tree based analysis OR control flow graph based analysis.
    /// </summary>
    private abstract class AnalysisData : IDisposable
    {
        /// <summary>
        /// Pooled <see cref="BasicBlockAnalysisData"/> allocated during analysis with the
        /// current <see cref="AnalysisData"/> instance, which will be freed during <see cref="Dispose"/>.
        /// </summary>
        private readonly ArrayBuilder<BasicBlockAnalysisData> _allocatedBasicBlockAnalysisDatas;
 
        /// <summary>
        /// Set of locals/parameters which are passed by reference to other method calls.
        /// </summary>
        private readonly PooledHashSet<ISymbol> _referenceTakenSymbolsBuilder;
 
        protected AnalysisData()
        {
            _allocatedBasicBlockAnalysisDatas = ArrayBuilder<BasicBlockAnalysisData>.GetInstance();
            _referenceTakenSymbolsBuilder = PooledHashSet<ISymbol>.GetInstance();
            CurrentBlockAnalysisData = CreateBlockAnalysisData();
            AdditionalConditionalBranchAnalysisData = CreateBlockAnalysisData();
        }
 
        /// <summary>
        /// Map from each (symbol, write) to a boolean indicating if the value assigned
        /// at the write is read on some control flow path.
        /// For example, consider the following code:
        /// <code>
        ///     int x = 0;
        ///     x = 1;
        ///     Console.WriteLine(x);
        /// </code>
        /// This map will have two entries for 'x':
        ///     1. Key = (symbol: x, write: 'int x = 0')
        ///        Value = 'false', because value assigned to 'x' here **is never** read. 
        ///     2. Key = (symbol: x, write: 'x = 1')
        ///        Value = 'true', because value assigned to 'x' here **may be** read on
        ///        some control flow path.
        /// </summary>
        protected abstract PooledDictionary<(ISymbol symbol, IOperation operation), bool> SymbolsWriteBuilder { get; }
 
        /// <summary>
        /// Set of locals/parameters that are read at least once.
        /// </summary>
        protected abstract PooledHashSet<ISymbol> SymbolsReadBuilder { get; }
 
        /// <summary>
        /// Set of lambda/local functions whose invocations are currently being analyzed to prevent
        /// infinite recursion for analyzing code with recursive lambda/local function calls.
        /// </summary>
        protected abstract PooledHashSet<IMethodSymbol> LambdaOrLocalFunctionsBeingAnalyzed { get; }
 
        /// <summary>
        /// Current block analysis data used for analysis.
        /// </summary>
        public BasicBlockAnalysisData CurrentBlockAnalysisData { get; }
 
        /// <summary>
        /// Block analysis data used for an additional conditional branch.
        /// </summary>
        public BasicBlockAnalysisData AdditionalConditionalBranchAnalysisData { get; }
 
        /// <summary>
        /// Creates an immutable <see cref="SymbolUsageResult"/> for the current analysis data.
        /// </summary>
        public SymbolUsageResult ToResult()
            => new(SymbolsWriteBuilder.ToImmutableDictionary(),
                                     [.. SymbolsReadBuilder]);
 
        public BasicBlockAnalysisData AnalyzeLocalFunctionInvocation(IMethodSymbol localFunction, CancellationToken cancellationToken)
        {
            Debug.Assert(localFunction.IsLocalFunction());
 
            // Use the original definition of the local function for flow analysis.
            localFunction = localFunction.OriginalDefinition;
 
            if (!LambdaOrLocalFunctionsBeingAnalyzed.Add(localFunction))
            {
                ResetState();
                return CurrentBlockAnalysisData;
            }
            else
            {
                var result = AnalyzeLocalFunctionInvocationCore(localFunction, cancellationToken);
                LambdaOrLocalFunctionsBeingAnalyzed.Remove(localFunction);
                return result;
            }
        }
 
        public BasicBlockAnalysisData AnalyzeLambdaInvocation(IFlowAnonymousFunctionOperation lambda, CancellationToken cancellationToken)
        {
            if (!LambdaOrLocalFunctionsBeingAnalyzed.Add(lambda.Symbol))
            {
                ResetState();
                return CurrentBlockAnalysisData;
            }
            else
            {
                var result = AnalyzeLambdaInvocationCore(lambda, cancellationToken);
                LambdaOrLocalFunctionsBeingAnalyzed.Remove(lambda.Symbol);
                return result;
            }
        }
 
        protected abstract BasicBlockAnalysisData AnalyzeLocalFunctionInvocationCore(IMethodSymbol localFunction, CancellationToken cancellationToken);
        protected abstract BasicBlockAnalysisData AnalyzeLambdaInvocationCore(IFlowAnonymousFunctionOperation lambda, CancellationToken cancellationToken);
 
        // Methods specific to flow capture analysis for CFG based dataflow analysis.
        public abstract bool IsLValueFlowCapture(CaptureId captureId);
        public abstract bool IsRValueFlowCapture(CaptureId captureId);
        public abstract void OnLValueCaptureFound(ISymbol symbol, IOperation operation, CaptureId captureId);
        public abstract void OnLValueDereferenceFound(CaptureId captureId);
 
        // Methods specific to delegate analysis to track potential delegate invocation targets for CFG based dataflow analysis.
        public abstract bool IsTrackingDelegateCreationTargets { get; }
        public abstract void SetTargetsFromSymbolForDelegate(IOperation write, ISymbol symbol);
        public abstract void SetLambdaTargetForDelegate(IOperation write, IFlowAnonymousFunctionOperation lambdaTarget);
        public abstract void SetLocalFunctionTargetForDelegate(IOperation write, IMethodReferenceOperation localFunctionTarget);
        public abstract void SetEmptyInvocationTargetsForDelegate(IOperation write);
        public abstract bool TryGetDelegateInvocationTargets(IOperation write, out ImmutableHashSet<IOperation> targets);
 
        protected static PooledDictionary<(ISymbol Symbol, IOperation Write), bool> CreateSymbolsWriteMap(
            ImmutableArray<IParameterSymbol> parameters)
        {
            var symbolsWriteMap = PooledDictionary<(ISymbol Symbol, IOperation Write), bool>.GetInstance();
            return UpdateSymbolsWriteMap(symbolsWriteMap, parameters);
        }
 
        protected static PooledDictionary<(ISymbol Symbol, IOperation Write), bool> UpdateSymbolsWriteMap(
            PooledDictionary<(ISymbol Symbol, IOperation Write), bool> symbolsWriteMap,
            ImmutableArray<IParameterSymbol> parameters)
        {
            // Mark parameters as being written from the value provided at the call site.
            // Note that the write operation is "null" as there is no corresponding IOperation for parameter definition.
            foreach (var parameter in parameters)
            {
                (ISymbol, IOperation) key = (parameter, null);
                if (!symbolsWriteMap.ContainsKey(key))
                {
                    symbolsWriteMap.Add(key, false);
                }
            }
 
            return symbolsWriteMap;
        }
 
        public BasicBlockAnalysisData CreateBlockAnalysisData()
        {
            var instance = BasicBlockAnalysisData.GetInstance();
            TrackAllocatedBlockAnalysisData(instance);
            return instance;
        }
 
        public void TrackAllocatedBlockAnalysisData(BasicBlockAnalysisData allocatedData)
            => _allocatedBasicBlockAnalysisDatas.Add(allocatedData);
 
        public void OnReadReferenceFound(ISymbol symbol)
        {
            if (symbol.Kind == SymbolKind.Discard)
            {
                return;
            }
 
            // Mark all the current reaching writes of symbol as read.
            if (SymbolsWriteBuilder.Count != 0)
            {
                CurrentBlockAnalysisData.ForEachCurrentWrite(
                    symbol,
                    static (write, arg) =>
                    {
                        arg.self.SymbolsWriteBuilder[(arg.symbol, write)] = true;
                    },
                    (symbol, self: this));
            }
 
            // Mark the current symbol as read.
            SymbolsReadBuilder.Add(symbol);
        }
 
        public void OnWriteReferenceFound(ISymbol symbol, IOperation operation, bool maybeWritten, bool isRef)
        {
            var symbolAndWrite = (symbol, operation);
            if (symbol.Kind == SymbolKind.Discard)
            {
                // Skip discard symbols and also for already processed writes (back edge from loops).
                return;
            }
 
            if (_referenceTakenSymbolsBuilder.Contains(symbol))
            {
                // Skip tracking writes for reference taken symbols as the written value may be read from a different variable.
                return;
            }
 
            // Add a new write for the given symbol at the given operation.
            CurrentBlockAnalysisData.OnWriteReferenceFound(symbol, operation, maybeWritten);
 
            if (isRef)
            {
                _referenceTakenSymbolsBuilder.Add(symbol);
            }
 
            // Only mark as unused write if we are processing it for the first time (not from back edge for loops)
            if (!SymbolsWriteBuilder.ContainsKey(symbolAndWrite) &&
                !maybeWritten)
            {
                SymbolsWriteBuilder.Add((symbol, operation), false);
            }
        }
 
        /// <summary>
        /// Resets all the currently tracked symbol writes to be conservatively marked as read.
        /// </summary>
        public void ResetState()
        {
            foreach (var symbol in SymbolsWriteBuilder.Keys.Select(d => d.symbol).ToArray())
            {
                OnReadReferenceFound(symbol);
            }
        }
 
        public void SetCurrentBlockAnalysisDataFrom(BasicBlockAnalysisData newBlockAnalysisData)
        {
            Debug.Assert(newBlockAnalysisData != null);
            CurrentBlockAnalysisData.SetAnalysisDataFrom(newBlockAnalysisData);
        }
 
        public virtual void Dispose()
        {
            foreach (var instance in _allocatedBasicBlockAnalysisDatas)
            {
                instance.Dispose();
            }
 
            _allocatedBasicBlockAnalysisDatas.Free();
            _referenceTakenSymbolsBuilder.Free();
        }
    }
}