File: src\RoslynAnalyzers\Utilities\FlowAnalysis\FlowAnalysis\Framework\DataFlow\AnalysisEntityDataFlowOperationVisitor.cs
Web Access
Project: src\src\RoslynAnalyzers\Microsoft.CodeAnalysis.AnalyzerUtilities\Microsoft.CodeAnalysis.AnalyzerUtilities.csproj (Microsoft.CodeAnalysis.AnalyzerUtilities)
// 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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CopyAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.PooledObjects;
 
namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow
{
    /// <summary>
    /// Operation visitor to flow the abstract dataflow analysis values for <see cref="AnalysisEntity"/> instances across a given statement in a basic block.
    /// </summary>
    public abstract class AnalysisEntityDataFlowOperationVisitor<TAnalysisData, TAnalysisContext, TAnalysisResult, TAbstractAnalysisValue>
        : DataFlowOperationVisitor<TAnalysisData, TAnalysisContext, TAnalysisResult, TAbstractAnalysisValue>
        where TAnalysisData : AbstractAnalysisData
        where TAnalysisContext : AbstractDataFlowAnalysisContext<TAnalysisData, TAnalysisContext, TAnalysisResult, TAbstractAnalysisValue>
        where TAnalysisResult : class, IDataFlowAnalysisResult<TAbstractAnalysisValue>
        where TAbstractAnalysisValue : IEquatable<TAbstractAnalysisValue>
    {
        protected AnalysisEntityDataFlowOperationVisitor(TAnalysisContext analysisContext)
            : base(analysisContext)
        {
            Debug.Assert(!analysisContext.PredicateAnalysis || SupportsPredicateAnalysis);
        }
 
        protected virtual bool SupportsPredicateAnalysis => false;
        protected void AddTrackedEntities(HashSet<AnalysisEntity> builder, bool forInterproceduralAnalysis = false)
            => AddTrackedEntities(CurrentAnalysisData, builder, forInterproceduralAnalysis);
        protected abstract void AddTrackedEntities(TAnalysisData analysisData, HashSet<AnalysisEntity> builder, bool forInterproceduralAnalysis = false);
        protected abstract void SetAbstractValue(AnalysisEntity analysisEntity, TAbstractAnalysisValue value);
        protected abstract void ResetAbstractValue(AnalysisEntity analysisEntity);
        protected abstract TAbstractAnalysisValue GetAbstractValue(AnalysisEntity analysisEntity);
        protected abstract bool HasAbstractValue(AnalysisEntity analysisEntity);
        protected abstract void StopTrackingEntity(AnalysisEntity analysisEntity, TAnalysisData analysisData);
 
        protected override TAbstractAnalysisValue ComputeAnalysisValueForReferenceOperation(IOperation operation, TAbstractAnalysisValue defaultValue)
        {
            if (AnalysisEntityFactory.TryCreate(operation, out var analysisEntity))
            {
                if (!HasAbstractValue(analysisEntity))
                {
                    SetAbstractValue(analysisEntity, defaultValue);
                }
 
                return GetAbstractValue(analysisEntity);
            }
            else
            {
                return defaultValue;
            }
        }
 
        protected sealed override TAbstractAnalysisValue ComputeAnalysisValueForEscapedRefOrOutArgument(IArgumentOperation operation, TAbstractAnalysisValue defaultValue)
        {
            Debug.Assert(operation.Parameter!.RefKind is RefKind.Ref or RefKind.Out);
 
            if (AnalysisEntityFactory.TryCreate(operation, out var analysisEntity))
            {
                var value = ComputeAnalysisValueForEscapedRefOrOutArgument(analysisEntity, operation, defaultValue);
                SetAbstractValueForAssignment(analysisEntity, operation, value);
                return GetAbstractValue(analysisEntity);
            }
            else
            {
                return defaultValue;
            }
        }
 
        protected virtual TAbstractAnalysisValue ComputeAnalysisValueForEscapedRefOrOutArgument(AnalysisEntity analysisEntity, IArgumentOperation operation, TAbstractAnalysisValue defaultValue)
        {
            Debug.Assert(operation.Parameter!.RefKind is RefKind.Ref or RefKind.Out);
 
            return defaultValue;
        }
 
        /// <summary>
        /// Helper method to reset analysis data for analysis entities.
        /// </summary>
        protected void ResetAnalysisData(DictionaryAnalysisData<AnalysisEntity, TAbstractAnalysisValue> currentAnalysisData)
        {
            // Reset the current analysis data, while ensuring that we don't violate the monotonicity, i.e. we cannot remove any existing key from currentAnalysisData.
            // Just set the values for existing keys to ValueDomain.UnknownOrMayBeValue.
            var keys = currentAnalysisData.Keys.ToImmutableArray();
            foreach (var key in keys)
            {
                ResetAbstractValue(key);
            }
        }
 
        protected override void ProcessOutOfScopeLocalsAndFlowCaptures(IEnumerable<ILocalSymbol> locals, IEnumerable<CaptureId> flowCaptures)
        {
            Debug.Assert(locals.Any() || flowCaptures.Any());
 
            base.ProcessOutOfScopeLocalsAndFlowCaptures(locals, flowCaptures);
 
            using var _ = PooledHashSet<AnalysisEntity>.GetInstance(out var allEntities);
            AddTrackedEntities(allEntities);
 
            // Stop tracking entities for locals and capture Ids that are now out of scope.
            foreach (var local in locals)
            {
                if (AnalysisEntityFactory.TryCreateForSymbolDeclaration(local, out var analysisEntity))
                {
                    StopTrackingDataForEntity(analysisEntity, allEntities);
                }
                else
                {
                    Debug.Fail("TryCreateForSymbolDeclaration failed");
                }
            }
 
            foreach (var captureId in flowCaptures)
            {
                if (AnalysisEntityFactory.TryGetForFlowCapture(captureId, out var analysisEntity))
                {
                    StopTrackingDataForEntity(analysisEntity, allEntities);
                }
            }
        }
 
        private void StopTrackingDataForEntity(AnalysisEntity analysisEntity, PooledHashSet<AnalysisEntity> allEntities)
            => StopTrackingDataForEntity(analysisEntity, CurrentAnalysisData, allEntities);
 
        private void StopTrackingDataForEntity(AnalysisEntity analysisEntity, TAnalysisData analysisData, PooledHashSet<AnalysisEntity> allEntities)
        {
            if (!allEntities.Contains(analysisEntity))
            {
                return;
            }
 
            // Stop tracking entity that is now out of scope.
            StopTrackingEntity(analysisEntity, analysisData);
 
            // Additionally, stop tracking all the child entities if the entity type has value copy semantics.
            if (analysisEntity.Type.HasValueCopySemantics())
            {
                foreach (var childEntity in GetChildAnalysisEntities(analysisEntity, allEntities))
                {
                    StopTrackingEntity(childEntity, analysisData);
                }
            }
        }
 
        private void StopTrackingDataForParamArrayParameterIndices(AnalysisEntity analysisEntity, TAnalysisData analysisData, PooledHashSet<AnalysisEntity> allEntities)
        {
            Debug.Assert(analysisEntity.Symbol is IParameterSymbol parameter && parameter.IsParams);
 
            foreach (var entity in allEntities)
            {
                if (!entity.Indices.IsEmpty &&
                    entity.InstanceLocation.Equals(analysisEntity.InstanceLocation))
                {
                    StopTrackingEntity(entity, analysisData);
                }
            }
        }
 
        protected sealed override void StopTrackingDataForParameter(IParameterSymbol parameter, AnalysisEntity analysisEntity)
            => throw new InvalidOperationException("Unreachable");
 
        protected sealed override void StopTrackingDataForParameters(ImmutableDictionary<IParameterSymbol, AnalysisEntity> parameterEntities)
        {
            if (!parameterEntities.IsEmpty)
            {
                using var _ = PooledHashSet<AnalysisEntity>.GetInstance(out var allEntities);
                AddTrackedEntities(allEntities);
 
                foreach (var (parameter, parameterEntity) in parameterEntities)
                {
                    StopTrackingDataForEntity(parameterEntity, CurrentAnalysisData, allEntities);
 
                    if (parameter.IsParams)
                    {
                        StopTrackingDataForParamArrayParameterIndices(parameterEntity, CurrentAnalysisData, allEntities);
                    }
                }
            }
        }
 
        protected override TAnalysisData GetMergedAnalysisDataForPossibleThrowingOperation(TAnalysisData? existingData, IOperation operation)
        {
            // Get tracked entities.
            using var _ = PooledHashSet<AnalysisEntity>.GetInstance(out var entitiesBuilder);
            AddTrackedEntities(entitiesBuilder);
 
            // Only non-child entities are tracked for now.
            var resultAnalysisData = GetTrimmedCurrentAnalysisData(entitiesBuilder.Where(e => !e.IsChildOrInstanceMember && HasAbstractValue(e)));
            if (existingData != null)
            {
                var mergedAnalysisData = MergeAnalysisData(resultAnalysisData, existingData);
                resultAnalysisData.Dispose();
                resultAnalysisData = mergedAnalysisData;
            }
 
            return resultAnalysisData;
        }
 
        #region Helper methods to handle initialization/assignment operations
        protected override void SetAbstractValueForArrayElementInitializer(IArrayCreationOperation arrayCreation, ImmutableArray<AbstractIndex> indices, ITypeSymbol elementType, IOperation initializer, TAbstractAnalysisValue value)
        {
            if (AnalysisEntityFactory.TryCreateForArrayElementInitializer(arrayCreation, indices, elementType, out var analysisEntity))
            {
                SetAbstractValueForAssignment(analysisEntity, initializer, value);
            }
        }
 
        protected override void SetAbstractValueForAssignment(IOperation target, IOperation? assignedValueOperation, TAbstractAnalysisValue assignedValue, bool mayBeAssignment = false)
        {
            if (AnalysisEntityFactory.TryCreate(target, out var targetAnalysisEntity))
            {
                if (!targetAnalysisEntity.ShouldBeTrackedForAnalysis(HasCompletePointsToAnalysisResult))
                {
                    // We are not tracking points to values for fields and properties.
                    // So, it is not possible to accurately track value changes to target entity which is a member.
                    // Conservatively assume that the entity is assigned an unknown value.
                    assignedValue = ValueDomain.UnknownOrMayBeValue;
                }
                else if (mayBeAssignment)
                {
                    assignedValue = ValueDomain.Merge(GetAbstractValue(targetAnalysisEntity), assignedValue);
                }
 
                SetAbstractValueForAssignment(targetAnalysisEntity, assignedValueOperation, assignedValue);
            }
        }
 
        protected override void SetAbstractValueForTupleElementAssignment(AnalysisEntity tupleElementEntity, IOperation assignedValueOperation, TAbstractAnalysisValue assignedValue)
        {
            if (tupleElementEntity.Type.IsTupleType)
            {
                // For nested tuple entity, we only want to flow the value for the tuple, not it's children.
                // Children of nested tuples should have already been assigned when visiting the nested tuple operation in the base dataflow visitor.
                SetAbstractValue(tupleElementEntity, assignedValue);
            }
            else
            {
                SetAbstractValueForAssignment(tupleElementEntity, assignedValueOperation, assignedValue);
            }
        }
 
        protected virtual void SetAbstractValueForAssignment(AnalysisEntity targetAnalysisEntity, IOperation? assignedValueOperation, TAbstractAnalysisValue assignedValue)
        {
            AnalysisEntity? assignedValueEntity = null;
            if (assignedValueOperation != null)
            {
                var success = AnalysisEntityFactory.TryCreate(assignedValueOperation, out assignedValueEntity);
                Debug.Assert(success || assignedValueEntity == null);
            }
 
            SetAbstractValueForAssignment(targetAnalysisEntity, assignedValueEntity, assignedValueOperation, assignedValue);
        }
 
        private void SetAbstractValueForAssignment(AnalysisEntity targetAnalysisEntity, AnalysisEntity? assignedValueEntity, IOperation? assignedValueOperation, TAbstractAnalysisValue assignedValue)
        {
            // Value type and string type assignment has copy semantics.
            if (HasPointsToAnalysisResult &&
                targetAnalysisEntity.Type.HasValueCopySemantics())
            {
                // Reset the analysis values for analysis entities within the target instance.
                ResetValueTypeInstanceAnalysisData(targetAnalysisEntity);
 
                // Transfer the values of symbols from the assigned instance to the analysis entities in the target instance.
                TransferValueTypeInstanceAnalysisDataForAssignment(targetAnalysisEntity, assignedValueEntity, assignedValueOperation);
            }
 
            var addressSharedCopyValue = TryGetAddressSharedCopyValue(targetAnalysisEntity);
            if (addressSharedCopyValue != null)
            {
                Debug.Assert(addressSharedCopyValue.AnalysisEntities.Contains(targetAnalysisEntity));
                foreach (var entity in addressSharedCopyValue.AnalysisEntities)
                {
                    SetAbstractValue(entity, assignedValue);
                }
            }
            else
            {
                SetAbstractValue(targetAnalysisEntity, assignedValue);
            }
        }
 
        protected override void SetValueForParameterOnEntry(IParameterSymbol parameter, AnalysisEntity analysisEntity, ArgumentInfo<TAbstractAnalysisValue>? assignedValue)
        {
            Debug.Assert(SymbolEqualityComparer.Default.Equals(analysisEntity.Symbol, parameter));
            if (assignedValue != null)
            {
                SetAbstractValueForAssignment(analysisEntity, assignedValue.Operation, assignedValue.Value);
            }
            else
            {
                SetAbstractValue(analysisEntity, GetDefaultValueForParameterOnEntry(parameter, analysisEntity));
            }
        }
 
        protected override void EscapeValueForParameterOnExit(IParameterSymbol parameter, AnalysisEntity analysisEntity)
        {
            Debug.Assert(Equals(analysisEntity.Symbol, parameter));
            if (parameter.RefKind != RefKind.None)
            {
                SetAbstractValue(analysisEntity, GetDefaultValueForParameterOnExit(analysisEntity.Type));
            }
        }
 
        protected virtual TAbstractAnalysisValue GetDefaultValueForParameterOnEntry(IParameterSymbol parameter, AnalysisEntity analysisEntity) => ValueDomain.UnknownOrMayBeValue;
        protected virtual TAbstractAnalysisValue GetDefaultValueForParameterOnExit(ITypeSymbol parameterType) => ValueDomain.UnknownOrMayBeValue;
 
        #endregion
 
        #region Helper methods for reseting/transfer instance analysis data when PointsTo analysis results are available
 
        /// <summary>
        /// Resets all the analysis data for all <see cref="AnalysisEntity"/> instances that share the same <see cref="AnalysisEntity.InstanceLocation"/>
        /// as the given <paramref name="analysisEntity"/>.
        /// </summary>
        /// <param name="analysisEntity"></param>
        protected override void ResetValueTypeInstanceAnalysisData(AnalysisEntity analysisEntity)
        {
            Debug.Assert(HasPointsToAnalysisResult);
            Debug.Assert(analysisEntity.Type.HasValueCopySemantics());
 
            IEnumerable<AnalysisEntity> dependantAnalysisEntities = GetChildAnalysisEntities(analysisEntity);
            ResetInstanceAnalysisDataCore(dependantAnalysisEntities.Concat(analysisEntity));
        }
 
        protected override void ResetReferenceTypeInstanceAnalysisData(PointsToAbstractValue pointsToAbstractValue)
        {
            Debug.Assert(HasPointsToAnalysisResult);
 
            IEnumerable<AnalysisEntity> dependantAnalysisEntities = GetChildAnalysisEntities(pointsToAbstractValue);
            ResetInstanceAnalysisDataCore(dependantAnalysisEntities);
        }
 
        /// <summary>
        /// Resets the analysis data for the given <paramref name="dependantAnalysisEntities"/>.
        /// </summary>
        /// <param name="dependantAnalysisEntities"></param>
        private void ResetInstanceAnalysisDataCore(IEnumerable<AnalysisEntity> dependantAnalysisEntities)
        {
            foreach (var dependentAnalysisEntity in dependantAnalysisEntities)
            {
                ResetAbstractValue(dependentAnalysisEntity);
            }
        }
 
        /// <summary>
        /// Transfers the analysis data rooted from <paramref name="valueAnalysisEntity"/> or <paramref name="assignedValueOperation"/> to <paramref name="targetAnalysisEntity"/>, for a value type assignment operation.
        /// This involves transfer of data for of all <see cref="AnalysisEntity"/> instances that share the same <see cref="AnalysisEntity.InstanceLocation"/> as <paramref name="valueAnalysisEntity"/> or allocation for the <paramref name="assignedValueOperation"/>
        /// to all <see cref="AnalysisEntity"/> instances that share the same <see cref="AnalysisEntity.InstanceLocation"/> as <paramref name="targetAnalysisEntity"/>.
        /// </summary>
        private void TransferValueTypeInstanceAnalysisDataForAssignment(AnalysisEntity targetAnalysisEntity, AnalysisEntity? valueAnalysisEntity, IOperation? assignedValueOperation)
        {
            Debug.Assert(HasPointsToAnalysisResult);
            Debug.Assert(targetAnalysisEntity.Type.HasValueCopySemantics());
 
            IEnumerable<AnalysisEntity> dependentAnalysisEntities;
            if (valueAnalysisEntity != null)
            {
                if (!valueAnalysisEntity.Type.HasValueCopySemantics())
                {
                    // Unboxing conversion from assigned value (reference type) to target (value copy semantics).
                    // We do not need to transfer any data for such a case as there is no entity for unboxed value.
                    return;
                }
 
                dependentAnalysisEntities = GetChildAnalysisEntities(valueAnalysisEntity);
            }
            else if (assignedValueOperation != null)
            {
                // For allocations.
                PointsToAbstractValue newValueLocation = GetPointsToAbstractValue(assignedValueOperation);
                dependentAnalysisEntities = GetChildAnalysisEntities(newValueLocation);
            }
            else
            {
                return;
            }
 
            foreach (AnalysisEntity dependentInstance in dependentAnalysisEntities)
            {
                // Clone the dependent instance but with target as the root.
                AnalysisEntity newAnalysisEntity = AnalysisEntityFactory.CreateWithNewInstanceRoot(dependentInstance, targetAnalysisEntity);
                var dependentValue = GetAbstractValue(dependentInstance);
                SetAbstractValue(newAnalysisEntity, dependentValue);
            }
        }
 
        private ImmutableHashSet<AnalysisEntity> GetChildAnalysisEntities(AnalysisEntity analysisEntity)
        {
            // PERF: If we do not have complete points to analysis data, then there cannot be any
            // child entities for reference type entities.
            if (!HasCompletePointsToAnalysisResult && analysisEntity.Type.IsReferenceType)
            {
                return ImmutableHashSet<AnalysisEntity>.Empty;
            }
 
            return GetChildAnalysisEntities(analysisEntity.InstanceLocation, entity => IsChildAnalysisEntity(entity, analysisEntity));
        }
 
        private static IEnumerable<AnalysisEntity> GetChildAnalysisEntities(AnalysisEntity analysisEntity, HashSet<AnalysisEntity> allEntities)
        {
            Debug.Assert(analysisEntity.Type.HasValueCopySemantics());
 
            foreach (var entity in allEntities)
            {
                if (IsChildAnalysisEntity(entity, ancestorEntity: analysisEntity))
                {
                    yield return entity;
                }
            }
        }
 
        protected static bool IsChildAnalysisEntity(AnalysisEntity entity, AnalysisEntity ancestorEntity)
        {
            return (!ancestorEntity.Type.HasValueCopySemantics() || entity.HasAncestor(ancestorEntity)) &&
                IsChildAnalysisEntity(entity, ancestorEntity.InstanceLocation);
        }
 
        protected ImmutableHashSet<AnalysisEntity> GetChildAnalysisEntities(PointsToAbstractValue? instanceLocation)
        {
            return GetChildAnalysisEntities(instanceLocation, predicate: null);
        }
 
        private ImmutableHashSet<AnalysisEntity> GetChildAnalysisEntities(PointsToAbstractValue? instanceLocation, Func<AnalysisEntity, bool>? predicate)
        {
            // We are interested only in dependent child/member infos, not the root info.
            if (instanceLocation == null || instanceLocation.Kind == PointsToAbstractValueKind.Unknown)
            {
                return ImmutableHashSet<AnalysisEntity>.Empty;
            }
 
            predicate ??= entity => IsChildAnalysisEntity(entity, instanceLocation);
 
            return GetChildAnalysisEntities(predicate);
        }
 
        protected static bool IsChildAnalysisEntity(AnalysisEntity entity, PointsToAbstractValue instanceLocation)
        {
            return instanceLocation != PointsToAbstractValue.NoLocation &&
                entity.InstanceLocation.Equals(instanceLocation) &&
                entity.IsChildOrInstanceMember;
        }
 
        private ImmutableHashSet<AnalysisEntity> GetChildAnalysisEntities(Func<AnalysisEntity, bool> predicate)
        {
            var trackedEntitiesBuilder = PooledHashSet<AnalysisEntity>.GetInstance();
            AddTrackedEntities(trackedEntitiesBuilder);
            trackedEntitiesBuilder.RemoveWhere(entity => !predicate(entity));
            return trackedEntitiesBuilder.ToImmutableAndFree();
        }
 
        #endregion
 
        #region Interprocedural analysis
        protected override TAnalysisData GetInitialInterproceduralAnalysisData(
            IMethodSymbol invokedMethod,
            (AnalysisEntity? Instance, PointsToAbstractValue PointsToValue)? invocationInstance,
            (AnalysisEntity Instance, PointsToAbstractValue PointsToValue)? thisOrMeInstanceForCaller,
            ImmutableDictionary<IParameterSymbol, ArgumentInfo<TAbstractAnalysisValue>> argumentValuesMap,
            IDictionary<AnalysisEntity, PointsToAbstractValue>? pointsToValues,
            IDictionary<AnalysisEntity, CopyAbstractValue>? copyValues,
            IDictionary<AnalysisEntity, ValueContentAbstractValue>? valueContentValues,
            bool isLambdaOrLocalFunction,
            bool hasParameterWithDelegateType)
        {
            // PERF: For non-lambda and local functions + presence of points to values, we trim down
            // the initial analysis data passed as input to interprocedural analysis.
            // We retain the analysis entities for the invocation instance, arguments and this or me instance.
            // Additionally, we also retain the transitive closure of analysis entities reachable from these
            // entities via the PointsTo values chain (i.e., recursively compute child analysis entities).
            // All the remaining entities are not accessible in the callee and are excluded from the initial
            // interprocedural analysis data.
 
            if (isLambdaOrLocalFunction || hasParameterWithDelegateType || pointsToValues == null)
            {
                return base.GetInitialInterproceduralAnalysisData(invokedMethod, invocationInstance,
                    thisOrMeInstanceForCaller, argumentValuesMap, pointsToValues, copyValues, valueContentValues,
                    isLambdaOrLocalFunction, hasParameterWithDelegateType);
            }
 
            using var _1 = PooledHashSet<AnalysisEntity>.GetInstance(out var candidateEntitiesBuilder);
            using var _2 = PooledHashSet<AnalysisEntity>.GetInstance(out var interproceduralEntitiesToRetainBuilder);
            using var _3 = PooledHashSet<AnalysisEntity>.GetInstance(out var worklistEntities);
            using var _4 = PooledHashSet<PointsToAbstractValue>.GetInstance(out var worklistPointsToValues);
            using var _5 = PooledHashSet<PointsToAbstractValue>.GetInstance(out var processedPointsToValues);
            using var _6 = PooledHashSet<AnalysisEntity>.GetInstance(out var childWorklistEntities);
 
            // All tracked entities are candidates to be retained for initial interprocedural
            // analysis data.
            AddTrackedEntities(candidateEntitiesBuilder, forInterproceduralAnalysis: true);
            var candidateEntitiesCount = candidateEntitiesBuilder.Count;
 
            // Add entities and PointsTo values for invocation instance, this or me instance
            // and argument values to the initial worklist
 
            if (invocationInstance.HasValue)
            {
                AddWorklistEntityAndPointsToValue(invocationInstance.Value.Instance);
                AddWorklistPointsToValue(invocationInstance.Value.PointsToValue);
            }
 
            if (thisOrMeInstanceForCaller.HasValue)
            {
                AddWorklistEntityAndPointsToValue(thisOrMeInstanceForCaller.Value.Instance);
                AddWorklistPointsToValue(thisOrMeInstanceForCaller.Value.PointsToValue);
            }
 
            foreach (var argument in argumentValuesMap.Values)
            {
                if (!AddWorklistEntityAndPointsToValue(argument.AnalysisEntity))
                {
                    // For allocations passed as arguments.
                    AddWorklistPointsToValue(argument.InstanceLocation);
                }
            }
 
            // Worklist based algorithm to compute the transitive closure of analysis entities
            // that are accessible in the callee via the PointsTo value chain.
            while (worklistEntities.Count > 0 || worklistPointsToValues.Count > 0)
            {
                if (worklistEntities.Count > 0)
                {
                    // Add all the worklistEntities to interproceduralEntitiesBuilder
                    // to ensure these entities are retained.
                    interproceduralEntitiesToRetainBuilder.AddRange(worklistEntities);
 
                    // Remove the worklistEntities from tracked candidate entities.
                    candidateEntitiesBuilder.ExceptWith(worklistEntities);
 
                    // Add child entities of worklistEntities to childWorklistEntities.
                    // PERF: We cannot have any child entities for PointsToAnalysis if we
                    // not computing complete PointsToAnalysis data.
                    if (HasCompletePointsToAnalysisResult || !IsPointsToAnalysis)
                    {
                        foreach (var candidateEntity in candidateEntitiesBuilder)
                        {
                            foreach (var ancestorEntity in worklistEntities)
                            {
                                if (IsChildAnalysisEntity(candidateEntity, ancestorEntity))
                                {
                                    childWorklistEntities.Add(candidateEntity);
                                    break;
                                }
                            }
                        }
                    }
 
                    worklistEntities.Clear();
                }
 
                if (worklistPointsToValues.Count > 0)
                {
                    // Add child entities which are accessible from PointsTo chain to childWorklistEntities.
                    // PERF: We cannot have any child entities for PointsToAnalysis if we
                    // not computing complete PointsToAnalysis data.
                    if (HasCompletePointsToAnalysisResult || !IsPointsToAnalysis)
                    {
                        foreach (var candidateEntity in candidateEntitiesBuilder)
                        {
                            foreach (var pointsToValue in worklistPointsToValues)
                            {
                                Debug.Assert(ShouldProcessPointsToValue(pointsToValue));
                                if (IsChildAnalysisEntity(candidateEntity, pointsToValue))
                                {
                                    childWorklistEntities.Add(candidateEntity);
                                    break;
                                }
                            }
                        }
                    }
 
                    worklistPointsToValues.Clear();
                }
 
                // Move all the child work list entities and their PointsTo values to the worklist.
                foreach (var childEntity in childWorklistEntities)
                {
                    AddWorklistEntityAndPointsToValue(childEntity);
                }
 
                childWorklistEntities.Clear();
            }
 
            // If all candidates being retained, just retain the cloned current analysis data.
            if (interproceduralEntitiesToRetainBuilder.Count == candidateEntitiesCount)
            {
                return GetClonedCurrentAnalysisData();
            }
 
            // Otherwise, return cloned current analysis data with trimmed keys.
            return GetTrimmedCurrentAnalysisData(interproceduralEntitiesToRetainBuilder);
 
            // Local functions.
            bool AddWorklistEntityAndPointsToValue(AnalysisEntity? analysisEntity)
            {
                RoslynDebug.Assert(pointsToValues != null);
 
                if (analysisEntity != null && candidateEntitiesBuilder.Contains(analysisEntity))
                {
                    worklistEntities.Add(analysisEntity);
 
                    if (pointsToValues.TryGetValue(analysisEntity, out var pointsToValue))
                    {
                        AddWorklistPointsToValue(pointsToValue);
                    }
 
                    return true;
                }
 
                return false;
            }
 
            void AddWorklistPointsToValue(PointsToAbstractValue pointsToValue)
            {
                if (ShouldProcessPointsToValue(pointsToValue) &&
                    processedPointsToValues.Add(pointsToValue))
                {
                    worklistPointsToValues.Add(pointsToValue);
                }
            }
 
            static bool ShouldProcessPointsToValue(PointsToAbstractValue pointsToValue)
                => pointsToValue.Kind == PointsToAbstractValueKind.KnownLocations &&
                   pointsToValue != PointsToAbstractValue.NoLocation;
        }
 
        /// <summary>
        /// Returns a cloned CurrentAnalysisData, trimmed down to only have key-value pairs for the given <paramref name="withEntities"/>.
        /// </summary>
        protected abstract TAnalysisData GetTrimmedCurrentAnalysisData(IEnumerable<AnalysisEntity> withEntities);
 
        protected TAnalysisData GetTrimmedCurrentAnalysisDataHelper(
            IEnumerable<AnalysisEntity> withEntities,
            IDictionary<AnalysisEntity, TAbstractAnalysisValue> existingValues,
            Action<TAnalysisData, AnalysisEntity, TAbstractAnalysisValue> setAbstractValue)
        {
            var initialAnalysisData = GetEmptyAnalysisData();
            foreach (var entity in withEntities)
            {
                setAbstractValue(initialAnalysisData, entity, existingValues[entity]);
            }
 
            return initialAnalysisData;
        }
 
        protected abstract void ApplyInterproceduralAnalysisResultCore(TAnalysisData resultData);
 
        protected sealed override void ApplyInterproceduralAnalysisResult(
            TAnalysisData resultData,
            bool isLambdaOrLocalFunction,
            bool hasDelegateTypeArgument,
            TAnalysisResult analysisResult)
        {
            if (isLambdaOrLocalFunction || hasDelegateTypeArgument)
            {
                base.ApplyInterproceduralAnalysisResult(resultData, isLambdaOrLocalFunction, hasDelegateTypeArgument, analysisResult);
                return;
            }
 
            ApplyInterproceduralAnalysisResultCore(resultData);
        }
 
        protected void ApplyInterproceduralAnalysisResultHelper(IDictionary<AnalysisEntity, TAbstractAnalysisValue> resultToApply)
        {
            foreach (var kvp in resultToApply)
            {
                var entity = kvp.Key;
                var newValue = kvp.Value;
                var currentValue = GetAbstractValue(entity);
                if (!currentValue.Equals(newValue))
                {
                    SetAbstractValue(entity, newValue);
                }
            }
        }
 
        internal bool ShouldStopTrackingEntityAtExit(AnalysisEntity entity)
        {
            Debug.Assert(DataFlowAnalysisContext.InterproceduralAnalysisData != null);
 
            // Filter out all the parameter, local symbol and flow capture entities from the analysis data.
            return IsParameterEntityForCurrentMethod(entity) ||
                entity.Symbol?.Kind == SymbolKind.Local &&
                entity.Symbol.ContainingSymbol.Equals(DataFlowAnalysisContext.OwningSymbol) ||
                entity.CaptureId.HasValue &&
                entity.CaptureId.Value.ControlFlowGraph == DataFlowAnalysisContext.ControlFlowGraph;
        }
 
        public override TAnalysisData? GetMergedDataForUnhandledThrowOperations()
        {
            // For interprocedural analysis, prune analysis data for unhandled exceptions
            // to remove analysis entities that are only valid in the callee.
            if (DataFlowAnalysisContext.InterproceduralAnalysisData != null &&
                AnalysisDataForUnhandledThrowOperations != null &&
                AnalysisDataForUnhandledThrowOperations.Values.Any(HasAnyAbstractValue))
            {
                using var _ = PooledHashSet<AnalysisEntity>.GetInstance(out var allAnalysisEntities);
 
                foreach (var dataAtException in AnalysisDataForUnhandledThrowOperations.Values)
                {
                    AddTrackedEntities(dataAtException, allAnalysisEntities, forInterproceduralAnalysis: true);
                }
 
                foreach (var entity in allAnalysisEntities)
                {
                    if (ShouldStopTrackingEntityAtExit(entity))
                    {
                        foreach (var dataAtException in AnalysisDataForUnhandledThrowOperations.Values)
                        {
                            StopTrackingDataForEntity(entity, dataAtException, allAnalysisEntities);
                        }
                    }
                }
            }
 
            return base.GetMergedDataForUnhandledThrowOperations();
        }
 
        #endregion
 
        protected DictionaryAnalysisData<AnalysisEntity, TAbstractAnalysisValue> GetClonedAnalysisDataHelper(IDictionary<AnalysisEntity, TAbstractAnalysisValue> analysisData)
            => [.. analysisData];
 
        protected void ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(
            DictionaryAnalysisData<AnalysisEntity, TAbstractAnalysisValue> coreDataAtException,
            DictionaryAnalysisData<AnalysisEntity, TAbstractAnalysisValue> coreCurrentAnalysisData,
            ThrownExceptionInfo throwBranchWithExceptionType)
        {
            Func<AnalysisEntity, bool>? predicate = null;
            if (throwBranchWithExceptionType.IsDefaultExceptionForExceptionsPathAnalysis)
            {
                // Only tracking non-child analysis entities for exceptions path analysis for now.
                Debug.Assert(throwBranchWithExceptionType.ExceptionType.Equals(ExceptionNamedType));
                predicate = e => !e.IsChildOrInstanceMember;
            }
 
            base.ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(coreDataAtException, coreCurrentAnalysisData, predicate);
        }
 
        #region Visitor methods
 
        public override TAbstractAnalysisValue VisitDeconstructionAssignment(IDeconstructionAssignmentOperation operation, object? argument)
        {
            var value = base.VisitDeconstructionAssignment(operation, argument);
            var assignedInstance = GetPointsToAbstractValue(operation.Value);
            HandleDeconstructionAssignment(operation.Target, GetChildAnalysisEntities(assignedInstance));
            return value;
        }
 
        private void HandleDeconstructionAssignment(IOperation target, ImmutableHashSet<AnalysisEntity> childEntities)
        {
            if (target is IDeclarationExpressionOperation declarationExpressionOperation)
            {
                target = declarationExpressionOperation.Expression;
            }
 
            if (target is ITupleOperation tupleOperation &&
                AnalysisEntityFactory.TryCreateForTupleElements(tupleOperation, out var tupleElementEntities))
            {
                Debug.Assert(tupleOperation.Elements.Length == tupleElementEntities.Length);
                for (int i = 0; i < tupleOperation.Elements.Length; i++)
                {
                    var element = tupleOperation.Elements[i];
                    var tupleElementEntity = tupleElementEntities[i];
                    if (element is ITupleOperation tupleElement)
                    {
                        Debug.Assert(tupleElementEntity.Symbol is IFieldSymbol field);
                        HandleDeconstructionAssignment(tupleElement, childEntities);
                    }
                    else if (AnalysisEntityFactory.TryCreate(element, out var elementEntity))
                    {
                        AnalysisEntity? assignedValueEntity = childEntities.FirstOrDefault(c => IsMatchingAssignedEntity(tupleElementEntity, c));
                        var assignedValue = assignedValueEntity != null ? GetAbstractValue(assignedValueEntity) : ValueDomain.UnknownOrMayBeValue;
                        SetAbstractValueForAssignment(elementEntity, assignedValueEntity, assignedValueOperation: null, assignedValue);
                    }
                }
            }
 
            return;
 
            // Local function
            static bool IsMatchingAssignedEntity(AnalysisEntity tupleElementEntity, AnalysisEntity? childEntity)
            {
                if (childEntity == null)
                {
                    return false;
                }
 
                if (tupleElementEntity.Parent == null)
                {
                    // Root tuple entity, compare the underlying tuple types.
                    return childEntity.Parent == null &&
                        SymbolEqualityComparer.Default.Equals(tupleElementEntity.Type.OriginalDefinition, childEntity.Type.OriginalDefinition);
                }
 
                // Must be a tuple element field entity.
                return tupleElementEntity.Symbol is IFieldSymbol tupleElementField &&
                    childEntity.Symbol is IFieldSymbol childEntityField &&
                    SymbolEqualityComparer.Default.Equals(tupleElementField.OriginalDefinition, childEntityField.OriginalDefinition) &&
                    IsMatchingAssignedEntity(tupleElementEntity.Parent, childEntity.Parent);
            }
        }
 
        #endregion
    }
}