File: src\RoslynAnalyzers\Utilities\FlowAnalysis\FlowAnalysis\Analysis\PointsToAnalysis\PointsToAbstractValue.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.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Analyzer.Utilities;
 
namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis
{
    /// <summary>
    /// Abstract PointsTo value for an <see cref="AnalysisEntity"/>/<see cref="IOperation"/> tracked by <see cref="PointsToAnalysis"/>.
    /// It contains the set of possible <see cref="AbstractLocation"/>s that the entity or the operation can point to and the <see cref="Kind"/> of the location(s).
    /// </summary>
    public class PointsToAbstractValue : CacheBasedEquatable<PointsToAbstractValue>
    {
        // An upper bound on number of underlying locations contained in the tracked PointsTo value.
        // This is required to prevent infinite analysis from interprocedural calls within a loop
        // The chosen constant value is just an approximate heuristic, which can be fine tuned in future.
        private const int LocationThreshold = 20;
 
        public static PointsToAbstractValue Undefined { get; } = new PointsToAbstractValue(PointsToAbstractValueKind.Undefined, NullAbstractValue.Undefined);
        public static PointsToAbstractValue Invalid { get; } = new PointsToAbstractValue(PointsToAbstractValueKind.Invalid, NullAbstractValue.Invalid);
        public static PointsToAbstractValue Unknown { get; } = new PointsToAbstractValue(PointsToAbstractValueKind.Unknown, NullAbstractValue.MaybeNull);
        public static PointsToAbstractValue UnknownNull { get; } = new PointsToAbstractValue(PointsToAbstractValueKind.UnknownNull, NullAbstractValue.Null);
        public static PointsToAbstractValue UnknownNotNull { get; } = new PointsToAbstractValue(PointsToAbstractValueKind.UnknownNotNull, NullAbstractValue.NotNull);
        public static PointsToAbstractValue NoLocation { get; } = new PointsToAbstractValue(ImmutableHashSet.Create(AbstractLocation.NoLocation), NullAbstractValue.NotNull);
        public static PointsToAbstractValue NullLocation { get; } = new PointsToAbstractValue(ImmutableHashSet.Create(AbstractLocation.Null), NullAbstractValue.Null);
 
        private PointsToAbstractValue(ImmutableHashSet<AbstractLocation> locations, NullAbstractValue nullState)
        {
            Debug.Assert(!locations.IsEmpty);
            Debug.Assert(locations.All(location => !location.IsNull) || nullState != NullAbstractValue.NotNull);
            Debug.Assert(nullState != NullAbstractValue.Undefined);
            Debug.Assert(nullState != NullAbstractValue.Invalid);
            Debug.Assert(!locations.Any(l => l.IsAnalysisEntityDefaultLocation && l.AnalysisEntity!.HasUnknownInstanceLocation));
            Debug.Assert(locations.Count <= LocationThreshold);
 
            Locations = locations;
            LValueCapturedOperations = ImmutableHashSet<IOperation>.Empty;
            Kind = PointsToAbstractValueKind.KnownLocations;
            NullState = nullState;
        }
 
        private PointsToAbstractValue(ImmutableHashSet<IOperation> lValueCapturedOperations)
        {
            Debug.Assert(!lValueCapturedOperations.IsEmpty);
 
            LValueCapturedOperations = lValueCapturedOperations;
            Locations = ImmutableHashSet<AbstractLocation>.Empty;
            Kind = PointsToAbstractValueKind.KnownLValueCaptures;
            NullState = NullAbstractValue.NotNull;
        }
 
        private PointsToAbstractValue(PointsToAbstractValueKind kind, NullAbstractValue nullState)
        {
            Debug.Assert(kind != PointsToAbstractValueKind.KnownLocations);
            Debug.Assert(kind != PointsToAbstractValueKind.KnownLValueCaptures);
 
            Locations = ImmutableHashSet<AbstractLocation>.Empty;
            LValueCapturedOperations = ImmutableHashSet<IOperation>.Empty;
            Kind = kind;
            NullState = nullState;
        }
 
        internal static PointsToAbstractValue Create(AbstractLocation location, bool mayBeNull)
        {
            Debug.Assert(!location.IsNull, "Use 'PointsToAbstractValue.NullLocation' singleton");
            Debug.Assert(!location.IsNoLocation, "Use 'PointsToAbstractValue.NoLocation' singleton");
 
            return new PointsToAbstractValue(ImmutableHashSet.Create(location), mayBeNull ? NullAbstractValue.MaybeNull : NullAbstractValue.NotNull);
        }
 
        internal static PointsToAbstractValue Create(IOperation lValueCapturedOperation)
        {
            return new PointsToAbstractValue(ImmutableHashSet.Create(lValueCapturedOperation));
        }
 
        internal static PointsToAbstractValue Create(ImmutableHashSet<AbstractLocation> locations, NullAbstractValue nullState)
        {
            Debug.Assert(!locations.IsEmpty);
 
            if (locations.Count == 1)
            {
                var location = locations.Single();
                if (location.IsNull)
                {
                    return NullLocation;
                }
 
                if (location.IsNoLocation)
                {
                    return NoLocation;
                }
            }
            else if (locations.Count > LocationThreshold)
            {
                return nullState switch
                {
                    NullAbstractValue.Null => UnknownNull,
 
                    NullAbstractValue.NotNull => UnknownNotNull,
 
                    _ => Unknown,
                };
            }
 
            return new PointsToAbstractValue(locations, nullState);
        }
 
        internal static PointsToAbstractValue Create(ImmutableHashSet<IOperation> lValueCapturedOperations)
        {
            Debug.Assert(!lValueCapturedOperations.IsEmpty);
            return new PointsToAbstractValue(lValueCapturedOperations);
        }
 
        internal PointsToAbstractValue MakeNonNull()
        {
            Debug.Assert(Kind != PointsToAbstractValueKind.KnownLValueCaptures);
 
            if (NullState == NullAbstractValue.NotNull)
            {
                return this;
            }
 
            if (Locations.IsEmpty)
            {
                return UnknownNotNull;
            }
 
            var locations = Locations.Where(location => !location.IsNull).ToImmutableHashSet();
            if (locations.IsEmpty)
            {
                return UnknownNotNull;
            }
            else if (locations.Count == Locations.Count)
            {
                locations = Locations;
            }
 
            return new PointsToAbstractValue(locations, NullAbstractValue.NotNull);
        }
 
        internal PointsToAbstractValue MakeNull()
        {
            Debug.Assert(Kind != PointsToAbstractValueKind.KnownLValueCaptures);
 
            if (NullState == NullAbstractValue.Null)
            {
                return this;
            }
 
            if (Locations.IsEmpty)
            {
                return UnknownNull;
            }
 
            return new PointsToAbstractValue(Locations, NullAbstractValue.Null);
        }
 
        internal PointsToAbstractValue MakeMayBeNull()
        {
            Debug.Assert(Kind != PointsToAbstractValueKind.KnownLValueCaptures);
            Debug.Assert(NullState != NullAbstractValue.Null);
 
            if (NullState == NullAbstractValue.MaybeNull)
            {
                return this;
            }
 
            if (Locations.IsEmpty)
            {
                return Unknown;
            }
 
            Debug.Assert(Locations.All(location => !location.IsNull));
            return new PointsToAbstractValue(Locations, NullAbstractValue.MaybeNull);
        }
 
        public ImmutableHashSet<AbstractLocation> Locations { get; }
        public ImmutableHashSet<IOperation> LValueCapturedOperations { get; }
        public PointsToAbstractValueKind Kind { get; }
        public NullAbstractValue NullState { get; }
 
        protected override void ComputeHashCodeParts(ref RoslynHashCode hashCode)
        {
            hashCode.Add(HashUtilities.Combine(Locations));
            hashCode.Add(HashUtilities.Combine(LValueCapturedOperations));
            hashCode.Add(((int)Kind).GetHashCode());
            hashCode.Add(((int)NullState).GetHashCode());
        }
 
        protected override bool ComputeEqualsByHashCodeParts(CacheBasedEquatable<PointsToAbstractValue> obj)
        {
            var other = (PointsToAbstractValue)obj;
            return HashUtilities.Combine(Locations) == HashUtilities.Combine(other.Locations)
                && HashUtilities.Combine(LValueCapturedOperations) == HashUtilities.Combine(other.LValueCapturedOperations)
                && ((int)Kind).GetHashCode() == ((int)other.Kind).GetHashCode()
                && ((int)NullState).GetHashCode() == ((int)other.NullState).GetHashCode();
        }
    }
}