File: src\RoslynAnalyzers\Utilities\FlowAnalysis\FlowAnalysis\Framework\DataFlow\AnalysisEntityBasedPredicateAnalysisData.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.Diagnostics.CodeAnalysis;
using System.Linq;
using Analyzer.Utilities.PooledObjects;
 
namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow
{
    /// <summary>
    /// Base class for all the aggregate analysis data with predicated analysis data,
    /// whose <see cref="CoreAnalysisData"/> is keyed by an <see cref="AnalysisEntity"/>.
    /// </summary>
    public abstract class AnalysisEntityBasedPredicateAnalysisData<TValue> : PredicatedAnalysisData<AnalysisEntity, TValue>
    {
        protected AnalysisEntityBasedPredicateAnalysisData()
        {
            CoreAnalysisData = [];
        }
 
        protected AnalysisEntityBasedPredicateAnalysisData(IDictionary<AnalysisEntity, TValue> fromData)
        {
            CoreAnalysisData = [.. fromData];
        }
 
        protected AnalysisEntityBasedPredicateAnalysisData(AnalysisEntityBasedPredicateAnalysisData<TValue> fromData)
            : base(fromData)
        {
            CoreAnalysisData = [.. fromData.CoreAnalysisData];
        }
 
        protected AnalysisEntityBasedPredicateAnalysisData(
            AnalysisEntityBasedPredicateAnalysisData<TValue> data1,
            AnalysisEntityBasedPredicateAnalysisData<TValue> data2,
            MapAbstractDomain<AnalysisEntity, TValue> coreDataAnalysisDomain)
            : base(data1, data2, data1.CoreAnalysisData,
                  data2.CoreAnalysisData, data1.IsReachableBlockData, coreDataAnalysisDomain)
        {
            Debug.Assert(data1.IsReachableBlockData == data2.IsReachableBlockData);
 
            CoreAnalysisData = coreDataAnalysisDomain.Merge(data1.CoreAnalysisData, data2.CoreAnalysisData);
        }
 
        protected AnalysisEntityBasedPredicateAnalysisData(
            DictionaryAnalysisData<AnalysisEntity, TValue> mergedCoreAnalysisData,
            PredicatedAnalysisData<AnalysisEntity, TValue> predicatedData1,
            PredicatedAnalysisData<AnalysisEntity, TValue> predicatedData2,
            bool isReachableData,
            MapAbstractDomain<AnalysisEntity, TValue> coreDataAnalysisDomain)
            : base(predicatedData1, predicatedData2, mergedCoreAnalysisData,
                  mergedCoreAnalysisData, isReachableData, coreDataAnalysisDomain)
        {
            CoreAnalysisData = mergedCoreAnalysisData;
        }
 
        public DictionaryAnalysisData<AnalysisEntity, TValue> CoreAnalysisData { get; }
 
        public virtual bool HasAnyAbstractValue => CoreAnalysisData.Count > 0 || HasPredicatedData;
 
        protected abstract AbstractValueDomain<TValue> ValueDomain { get; }
        public abstract AnalysisEntityBasedPredicateAnalysisData<TValue> Clone();
        public abstract AnalysisEntityBasedPredicateAnalysisData<TValue> WithMergedData(AnalysisEntityBasedPredicateAnalysisData<TValue> data, MapAbstractDomain<AnalysisEntity, TValue> coreDataAnalysisDomain);
        public abstract int Compare(AnalysisEntityBasedPredicateAnalysisData<TValue> other, MapAbstractDomain<AnalysisEntity, TValue> coreDataAnalysisDomain);
 
        protected int BaseCompareHelper(AnalysisEntityBasedPredicateAnalysisData<TValue> newData, MapAbstractDomain<AnalysisEntity, TValue> coreDataAnalysisDomain)
        {
            var baseCompareResult = base.BaseCompareHelper(newData);
            if (baseCompareResult != 0)
            {
                Debug.Assert(baseCompareResult < 0, "Non-monotonic Merge function");
                return baseCompareResult;
            }
 
            var coreAnalysisDataCompareResult = coreDataAnalysisDomain.Compare(CoreAnalysisData, newData.CoreAnalysisData);
            Debug.Assert(coreAnalysisDataCompareResult <= 0, "Non-monotonic Merge function");
            return coreAnalysisDataCompareResult;
        }
 
        public bool HasAbstractValue(AnalysisEntity analysisEntity) => CoreAnalysisData.ContainsKey(analysisEntity);
 
        public bool TryGetValue(AnalysisEntity key, [MaybeNullWhen(false)] out TValue value) => CoreAnalysisData.TryGetValue(key, out value);
 
#pragma warning disable CA1043 // Use Integral Or String Argument For Indexers
        public TValue this[AnalysisEntity key] => CoreAnalysisData[key];
#pragma warning restore CA1043 // Use Integral Or String Argument For Indexers
 
        [Conditional("DEBUG")]
        private void AssertValidAnalysisData()
        {
            Debug.Assert(!CoreAnalysisData.IsDisposed);
            AssertValidPredicatedAnalysisData(map => Debug.Assert(!map.IsDisposed));
        }
 
        public virtual void SetAbstractValue(AnalysisEntity key, TValue value)
        {
            AssertValidAnalysisData();
            if (HasPredicatedData)
            {
                RemoveEntriesInPredicatedData(key);
            }
 
            CoreAnalysisData[key] = value;
 
            ClearOverlappingAnalysisDataForIndexedEntity(key, value);
        }
 
        public void RemoveEntries(AnalysisEntity key)
        {
            AssertValidAnalysisData();
 
            CoreAnalysisData.Remove(key);
            if (HasPredicatedData)
            {
                RemoveEntriesInPredicatedData(key);
            }
        }
 
        public bool Equals(AnalysisEntityBasedPredicateAnalysisData<TValue> other)
        {
            AssertValidAnalysisData();
 
            return base.Equals(other) &&
                EqualsHelper(CoreAnalysisData, other.CoreAnalysisData);
        }
 
        public virtual void Reset(Func<AnalysisEntity, TValue, TValue> getResetValue)
        {
            AssertValidAnalysisData();
 
            // 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.
            if (CoreAnalysisData.Count > 0)
            {
                var keys = CoreAnalysisData.Keys.ToImmutableArray();
                foreach (var key in keys)
                {
                    CoreAnalysisData[key] = getResetValue(key, CoreAnalysisData[key]);
                }
            }
 
            ResetPredicatedData();
            AssertValidAnalysisData();
        }
 
        public void StartTrackingPredicatedData(AnalysisEntity predicatedEntity, AnalysisEntityBasedPredicateAnalysisData<TValue>? truePredicateData, AnalysisEntityBasedPredicateAnalysisData<TValue>? falsePredicateData)
        {
            AssertValidAnalysisData();
 
            StartTrackingPredicatedData(predicatedEntity, truePredicateData?.CoreAnalysisData, falsePredicateData?.CoreAnalysisData);
            AssertValidAnalysisData();
        }
 
        public PredicateValueKind ApplyPredicatedDataForEntity(AnalysisEntity predicatedEntity, bool trueData)
        {
            AssertValidAnalysisData();
 
            var result = ApplyPredicatedDataForEntity(CoreAnalysisData, predicatedEntity, trueData);
            AssertValidAnalysisData();
            return result;
        }
 
        public void AddTrackedEntities(HashSet<AnalysisEntity> builder) => builder.UnionWith(CoreAnalysisData.Keys);
 
        private void ClearOverlappingAnalysisDataForIndexedEntity(AnalysisEntity analysisEntity, TValue value)
        {
            if (!analysisEntity.Indices.Any(index => !index.IsConstant()))
            {
                return;
            }
 
            // Collect all the overlapping indexed entities whose value needs to be updated into a builder.
            // Ensure that we perform these state updates after the foreach loop to avoid modifying the
            // underlying CoreAnalysisData within the loop.
            // See https://github.com/dotnet/roslyn-analyzers/issues/6929 for more details.
            using var builder = ArrayBuilder<(AnalysisEntity, TValue)>.GetInstance(CoreAnalysisData.Count);
            foreach (var entity in CoreAnalysisData.Keys)
            {
                if (entity.Indices.Length != analysisEntity.Indices.Length ||
                    entity == analysisEntity)
                {
                    continue;
                }
 
                var canOverlap = true;
                for (var i = 0; i < entity.Indices.Length; i++)
                {
                    if (entity.Indices[i].IsConstant() &&
                        analysisEntity.Indices[i].IsConstant() &&
                        !entity.Indices[i].Equals(analysisEntity.Indices[i]))
                    {
                        canOverlap = false;
                        break;
                    }
                }
 
                if (canOverlap &&
                    entity.WithIndices(analysisEntity.Indices).Equals(analysisEntity) &&
                    CoreAnalysisData.TryGetValue(entity, out var existingValue))
                {
                    var mergedValue = ValueDomain.Merge(value, existingValue);
                    if (!existingValue!.Equals(mergedValue))
                    {
                        builder.Add((entity, mergedValue));
                    }
                }
            }
 
            foreach (var (entity, newValue) in builder.AsEnumerable())
            {
                SetAbstractValue(entity, newValue);
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            if (IsDisposed)
            {
                return;
            }
 
            if (disposing)
            {
                CoreAnalysisData.Dispose();
            }
 
            base.Dispose(disposing);
        }
    }
}