File: src\RoslynAnalyzers\Utilities\FlowAnalysis\FlowAnalysis\Framework\DataFlow\AnalysisEntity.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;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow
{
    /// <summary>
    /// <para>
    /// Primary entity for which analysis data is tracked by <see cref="DataFlowAnalysis"/>.
    /// </para>
    /// <para>
    /// The entity is based on one or more of the following:
    ///     1. An <see cref="ISymbol"/>.
    ///     2. One or more <see cref="AbstractIndex"/> indices to index into the parent key.
    ///     3. "this" or "Me" instance.
    ///     4. An allocation or an object creation.
    /// </para>
    /// <para>
    /// Each entity has:
    ///     1. An associated non-null <see cref="Type"/> and
    ///     2. A non-null <see cref="InstanceLocation"/> indicating the abstract location at which the entity is located and
    ///     3. An optional parent key if this key has the same <see cref="InstanceLocation"/> as the parent (i.e. parent is a value type).
    ///     4. An optional entity for reference typed <see cref="InstanceLocation"/> if the points to value for the instance location has more than one possible locations.
    /// </para>
    /// </summary>
    public sealed class AnalysisEntity : CacheBasedEquatable<AnalysisEntity>
    {
        private readonly int _ignoringLocationHashCode;
 
        private AnalysisEntity(
            ISymbol? symbol,
            ImmutableArray<AbstractIndex> indices,
            SyntaxNode? instanceReferenceOperationSyntax,
            InterproceduralCaptureId? captureId,
            PointsToAbstractValue location,
            ITypeSymbol type,
            AnalysisEntity? parent,
            AnalysisEntity? entityForInstanceLocation,
            bool isThisOrMeInstance)
        {
            Debug.Assert(!indices.IsDefault);
            Debug.Assert(symbol != null || !indices.IsEmpty || instanceReferenceOperationSyntax != null || captureId.HasValue);
            Debug.Assert(parent == null || parent.Type.HasValueCopySemantics() || !indices.IsEmpty);
            Debug.Assert(entityForInstanceLocation == null || parent == null);
            Debug.Assert(entityForInstanceLocation == null || location.Kind == PointsToAbstractValueKind.KnownLocations);
 
            Symbol = symbol;
            Indices = indices;
            InstanceReferenceOperationSyntax = instanceReferenceOperationSyntax;
            CaptureId = captureId;
            InstanceLocation = location;
            Type = type;
            Parent = parent;
            EntityForInstanceLocation = entityForInstanceLocation;
            IsThisOrMeInstance = isThisOrMeInstance;
 
            _ignoringLocationHashCode = ComputeIgnoringLocationHashCode();
            EqualsIgnoringInstanceLocationId = _ignoringLocationHashCode;
        }
 
        private AnalysisEntity(ISymbol? symbol, ImmutableArray<AbstractIndex> indices, PointsToAbstractValue location, ITypeSymbol type, AnalysisEntity? parent, AnalysisEntity? entityForInstanceLocation)
            : this(symbol, indices, instanceReferenceOperationSyntax: null, captureId: null, location: location, type: type, parent: parent, entityForInstanceLocation: entityForInstanceLocation, isThisOrMeInstance: false)
        {
            Debug.Assert(symbol != null || !indices.IsEmpty);
        }
 
        private AnalysisEntity(IInstanceReferenceOperation instanceReferenceOperation, PointsToAbstractValue location)
            : this(symbol: null, indices: ImmutableArray<AbstractIndex>.Empty, instanceReferenceOperationSyntax: instanceReferenceOperation.Syntax,
                  captureId: null, location: location, type: instanceReferenceOperation.Type!, parent: null, entityForInstanceLocation: null, isThisOrMeInstance: false)
        {
            Debug.Assert(instanceReferenceOperation != null);
        }
 
        private AnalysisEntity(InterproceduralCaptureId captureId, ITypeSymbol capturedType, PointsToAbstractValue location)
            : this(symbol: null, indices: ImmutableArray<AbstractIndex>.Empty, instanceReferenceOperationSyntax: null,
                  captureId: captureId, location: location, type: capturedType, parent: null, entityForInstanceLocation: null, isThisOrMeInstance: false)
        {
        }
 
        private AnalysisEntity(INamedTypeSymbol namedType, PointsToAbstractValue location, bool isThisOrMeInstance)
            : this(symbol: namedType, indices: ImmutableArray<AbstractIndex>.Empty, instanceReferenceOperationSyntax: null,
                  captureId: null, location: location, type: namedType, parent: null, entityForInstanceLocation: null, isThisOrMeInstance: isThisOrMeInstance)
        {
        }
 
        public static AnalysisEntity Create(ISymbol? symbol, ImmutableArray<AbstractIndex> indices,
            ITypeSymbol type, PointsToAbstractValue instanceLocation, AnalysisEntity? parent, AnalysisEntity? entityForInstanceLocation)
        {
            Debug.Assert(symbol != null || !indices.IsEmpty);
            Debug.Assert(parent == null || parent.InstanceLocation == instanceLocation);
            Debug.Assert(entityForInstanceLocation == null || instanceLocation.Kind == PointsToAbstractValueKind.KnownLocations);
 
            return new AnalysisEntity(symbol, indices, instanceLocation, type, parent, entityForInstanceLocation);
        }
 
        public static AnalysisEntity Create(IInstanceReferenceOperation instanceReferenceOperation, PointsToAbstractValue instanceLocation)
        {
            return new AnalysisEntity(instanceReferenceOperation, instanceLocation);
        }
 
        public static AnalysisEntity Create(
            InterproceduralCaptureId interproceduralCaptureId,
            ITypeSymbol type,
            PointsToAbstractValue instanceLocation)
        {
            return new AnalysisEntity(interproceduralCaptureId, type, instanceLocation);
        }
 
        public static AnalysisEntity CreateThisOrMeInstance(INamedTypeSymbol typeSymbol, PointsToAbstractValue instanceLocation)
        {
            Debug.Assert(instanceLocation.Locations.Count == 1);
            Debug.Assert(instanceLocation.Locations.Single().Creation == null);
            Debug.Assert(Equals(instanceLocation.Locations.Single().Symbol, typeSymbol));
 
            return new AnalysisEntity(typeSymbol, instanceLocation, isThisOrMeInstance: true);
        }
 
        public AnalysisEntity WithMergedInstanceLocation(AnalysisEntity analysisEntityToMerge)
        {
            Debug.Assert(EqualsIgnoringInstanceLocation(analysisEntityToMerge));
            Debug.Assert(!InstanceLocation.Equals(analysisEntityToMerge.InstanceLocation));
 
            var mergedInstanceLocation = PointsToAnalysis.PointsToAnalysis.ValueDomainInstance.Merge(InstanceLocation, analysisEntityToMerge.InstanceLocation);
            return new AnalysisEntity(Symbol, Indices, InstanceReferenceOperationSyntax, CaptureId, mergedInstanceLocation, Type, Parent, EntityForInstanceLocation, IsThisOrMeInstance);
        }
 
        public bool IsChildOrInstanceMember
        {
            get
            {
                if (IsThisOrMeInstance)
                {
                    return false;
                }
 
                bool result;
                if (Symbol != null)
                {
                    result = Symbol.Kind != SymbolKind.Parameter &&
                        Symbol.Kind != SymbolKind.Local &&
                        !Symbol.IsStatic;
                }
                else if (!Indices.IsEmpty)
                {
                    result = true;
                }
                else
                {
                    result = false;
                }
 
                Debug.Assert(Parent == null || result);
                return result;
            }
        }
 
        internal bool ShouldBeTrackedForPointsToAnalysis(PointsToAnalysisKind pointsToAnalysisKind)
            => ShouldBeTrackedForAnalysis(pointsToAnalysisKind == PointsToAnalysisKind.Complete);
 
        internal bool ShouldBeTrackedForAnalysis(bool hasCompletePointsToAnalysisResult)
        {
            if (!IsChildOrInstanceMember)
            {
                // We can always perform analysis for entities which are not child or instance members.
                return true;
            }
 
            // If we have complete points to analysis result, and this child or instance member has a
            // known instance location, we can perform analysis for this entity.
            if (hasCompletePointsToAnalysisResult && !HasUnknownInstanceLocation)
            {
                return true;
            }
 
            // PERF: This is the core performance optimization when we have partial points to analysis result.
            // We avoid tracking analysis values for all entities that are child or instance members,
            // except when they are fields or members of a value type (for example, tuple elements or struct members).
            return Parent != null && Parent.Type.HasValueCopySemantics();
        }
 
        public bool HasConstantValue => Symbol switch
        {
            IFieldSymbol fieldSymbol => fieldSymbol.HasConstantValue,
            ILocalSymbol localSymbol => localSymbol.HasConstantValue,
            _ => false,
        };
 
        public ISymbol? Symbol { get; }
        public ImmutableArray<AbstractIndex> Indices { get; }
        public SyntaxNode? InstanceReferenceOperationSyntax { get; }
        public InterproceduralCaptureId? CaptureId { get; }
        public PointsToAbstractValue InstanceLocation { get; }
        public AnalysisEntity? EntityForInstanceLocation { get; }
        public ITypeSymbol Type { get; }
        public AnalysisEntity? Parent { get; }
        public bool IsThisOrMeInstance { get; }
 
        public bool HasUnknownInstanceLocation => InstanceLocation.Kind switch
        {
            PointsToAbstractValueKind.Unknown
            or PointsToAbstractValueKind.UnknownNull
            or PointsToAbstractValueKind.UnknownNotNull => true,
            _ => false,
        };
 
        public bool IsLValueFlowCaptureEntity => CaptureId.HasValue && CaptureId.Value.IsLValueFlowCapture;
 
        internal AnalysisEntity WithIndices(ImmutableArray<AbstractIndex> indices)
            => new(Symbol, indices, InstanceReferenceOperationSyntax, CaptureId, InstanceLocation, Type, Parent, EntityForInstanceLocation, IsThisOrMeInstance);
 
        public bool EqualsIgnoringInstanceLocation(AnalysisEntity? other)
        {
            // Perform fast equality checks first.
            if (ReferenceEquals(this, other))
            {
                return true;
            }
 
            if (other == null ||
                EqualsIgnoringInstanceLocationId != other.EqualsIgnoringInstanceLocationId)
            {
                return false;
            }
 
            // Now perform slow check that compares individual hash code parts sequences.
            return Symbol.GetHashCodeOrDefault() == other.Symbol.GetHashCodeOrDefault()
                && HashUtilities.Combine(Indices) == HashUtilities.Combine(other.Indices)
                && InstanceReferenceOperationSyntax.GetHashCodeOrDefault() == other.InstanceReferenceOperationSyntax.GetHashCodeOrDefault()
                && CaptureId.GetHashCodeOrDefault() == other.CaptureId.GetHashCodeOrDefault()
                && Type.GetHashCodeOrDefault() == other.Type.GetHashCodeOrDefault()
                && Parent.GetHashCodeOrDefault() == other.Parent.GetHashCodeOrDefault()
                && EntityForInstanceLocation.GetHashCodeOrDefault() == other.EntityForInstanceLocation.GetHashCodeOrDefault()
                && IsThisOrMeInstance.GetHashCode() == other.IsThisOrMeInstance.GetHashCode();
        }
 
        public int EqualsIgnoringInstanceLocationId { get; private set; }
 
        protected override void ComputeHashCodeParts(ref RoslynHashCode hashCode)
        {
            hashCode.Add(InstanceLocation.GetHashCode());
            ComputeHashCodePartsIgnoringLocation(ref hashCode);
        }
 
        protected override bool ComputeEqualsByHashCodeParts(CacheBasedEquatable<AnalysisEntity> obj)
        {
            var other = (AnalysisEntity)obj;
            return InstanceLocation.GetHashCode() == other.InstanceLocation.GetHashCode()
                && EqualsIgnoringInstanceLocation(other);
        }
 
        private void ComputeHashCodePartsIgnoringLocation(ref RoslynHashCode hashCode)
        {
            hashCode.Add(Symbol.GetHashCodeOrDefault());
            hashCode.Add(HashUtilities.Combine(Indices));
            hashCode.Add(InstanceReferenceOperationSyntax.GetHashCodeOrDefault());
            hashCode.Add(CaptureId.GetHashCodeOrDefault());
            hashCode.Add(Type.GetHashCode());
            hashCode.Add(Parent.GetHashCodeOrDefault());
            hashCode.Add(EntityForInstanceLocation.GetHashCodeOrDefault());
            hashCode.Add(IsThisOrMeInstance.GetHashCode());
        }
 
        private int ComputeIgnoringLocationHashCode()
        {
            var hashCode = new RoslynHashCode();
            ComputeHashCodePartsIgnoringLocation(ref hashCode);
            return hashCode.ToHashCode();
        }
 
        public bool HasAncestor(AnalysisEntity ancestor)
        {
            AnalysisEntity? current = this.Parent;
            while (current != null)
            {
                if (current == ancestor)
                {
                    return true;
                }
 
                current = current.Parent;
            }
 
            return false;
        }
 
        internal bool IsCandidatePredicateEntity()
            => Type.SpecialType == SpecialType.System_Boolean ||
               Type.IsNullableOfBoolean() ||
               Type.Language == LanguageNames.VisualBasic && Type.SpecialType == SpecialType.System_Object;
    }
}