File: src\1e7e9f16bf7f1945\ValueContentAnalysis.ValueContentDataFlowOperationVisitor.cs
Web Access
Project: src\roslyn\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 Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CopyAnalysis;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis
{
    using ValueContentAnalysisResult = DataFlowAnalysisResult<ValueContentBlockAnalysisResult, ValueContentAbstractValue>;

    public partial class ValueContentAnalysis : ForwardDataFlowAnalysis<ValueContentAnalysisData, ValueContentAnalysisContext, ValueContentAnalysisResult, ValueContentBlockAnalysisResult, ValueContentAbstractValue>
    {
        /// <summary>
        /// Operation visitor to flow the data values across a given statement in a basic block.
        /// </summary>
        private sealed class ValueContentDataFlowOperationVisitor : PredicateAnalysisEntityDataFlowOperationVisitor<ValueContentAnalysisData, ValueContentAnalysisContext, ValueContentAnalysisResult, ValueContentAbstractValue>
        {
            private readonly ValueContentAnalysisDomain _valueContentAnalysisDomain;

            public ValueContentDataFlowOperationVisitor(ValueContentAnalysisDomain valueContentAnalysisDomain, ValueContentAnalysisContext analysisContext)
                : base(analysisContext)
            {
                _valueContentAnalysisDomain = valueContentAnalysisDomain;
            }

            protected override void AddTrackedEntities(ValueContentAnalysisData analysisData, HashSet<AnalysisEntity> builder, bool forInterproceduralAnalysis)
                => analysisData.AddTrackedEntities(builder);

            protected override void ResetAbstractValue(AnalysisEntity analysisEntity)
                => SetAbstractValue(analysisEntity, ValueDomain.UnknownOrMayBeValue);

            protected override void SetAbstractValue(AnalysisEntity analysisEntity, ValueContentAbstractValue value)
                => SetAbstractValue(CurrentAnalysisData, analysisEntity, value);

            private void SetAbstractValue(ValueContentAnalysisData analysisData, AnalysisEntity analysisEntity, ValueContentAbstractValue value)
                => SetAbstractValue(analysisData, analysisEntity, value, HasCompletePointsToAnalysisResult);

            private static void SetAbstractValue(ValueContentAnalysisData analysisData, AnalysisEntity analysisEntity, ValueContentAbstractValue value, bool hasCompletePointsToAnalysisResult)
            {
                // PERF: Avoid creating an entry if the value is the default unknown value.
                if (value == ValueContentAbstractValue.MayBeContainsNonLiteralState &&
                    !analysisData.HasAbstractValue(analysisEntity))
                {
                    return;
                }

                if (analysisEntity.ShouldBeTrackedForAnalysis(hasCompletePointsToAnalysisResult))
                {
                    analysisData.SetAbstractValue(analysisEntity, value);
                }
            }

            protected override bool HasAbstractValue(AnalysisEntity analysisEntity)
                => CurrentAnalysisData.HasAbstractValue(analysisEntity);

            protected override void StopTrackingEntity(AnalysisEntity analysisEntity, ValueContentAnalysisData analysisData)
                => analysisData.RemoveEntries(analysisEntity);

            protected override ValueContentAbstractValue GetAbstractValue(AnalysisEntity analysisEntity)
                => CurrentAnalysisData.TryGetValue(analysisEntity, out var value) ? value : ValueDomain.UnknownOrMayBeValue;

            protected override ValueContentAbstractValue GetAbstractDefaultValue(ITypeSymbol? type)
                => type != null && !type.CanHoldNullValue() ?
                   ValueContentAbstractValue.DoesNotContainLiteralOrNonLiteralState :
                   ValueContentAbstractValue.ContainsNullLiteralState;

            protected override bool HasAnyAbstractValue(ValueContentAnalysisData data)
                => data.HasAnyAbstractValue;

            protected override void ResetCurrentAnalysisData()
                => CurrentAnalysisData.Reset(ValueDomain.UnknownOrMayBeValue);

            protected override CopyAbstractValue GetCopyAbstractValue(IOperation operation)
            {
                if (DataFlowAnalysisContext.CopyAnalysisResult == null &&
                    AnalysisEntityFactory.TryCreate(operation, out var entity) &&
                    entity.CaptureId.HasValue &&
                    AnalysisEntityFactory.TryGetCopyValueForFlowCapture(entity.CaptureId.Value.Id, out var copyValue) &&
                    copyValue.Kind == CopyAbstractValueKind.KnownValueCopy)
                {
                    return copyValue;
                }

                return base.GetCopyAbstractValue(operation);
            }

            #region Predicate analysis
            protected override PredicateValueKind SetValueForIsNullComparisonOperator(IOperation leftOperand, bool equals, ValueContentAnalysisData targetAnalysisData)
                => PredicateValueKind.Unknown;

            protected override PredicateValueKind SetValueForEqualsOrNotEqualsComparisonOperator(
                IOperation leftOperand,
                IOperation rightOperand,
                bool equals,
                bool isReferenceEquality,
                ValueContentAnalysisData targetAnalysisData)
            {
                var predicateValueKind = PredicateValueKind.Unknown;

                // Handle 'a == "SomeValue"' and 'a != "SomeValue"'
                SetValueForComparisonOperator(leftOperand, rightOperand, equals, ref predicateValueKind, targetAnalysisData);

                // Handle '"SomeValue" == a' and '"SomeValue" != a'
                SetValueForComparisonOperator(rightOperand, leftOperand, equals, ref predicateValueKind, targetAnalysisData);

                return predicateValueKind;
            }

            private void SetValueForComparisonOperator(IOperation target, IOperation assignedValue, bool equals, ref PredicateValueKind predicateValueKind, ValueContentAnalysisData targetAnalysisData)
            {
                ValueContentAbstractValue currentAssignedValue = GetCachedAbstractValue(assignedValue);
                if (currentAssignedValue.IsLiteralState &&
                    AnalysisEntityFactory.TryCreate(target, out var targetEntity))
                {
                    if (CurrentAnalysisData.TryGetValue(targetEntity, out var existingTargetValue) &&
                        existingTargetValue.IsLiteralState)
                    {
                        var newValue = currentAssignedValue.IntersectLiteralValues(existingTargetValue);
                        if (newValue.NonLiteralState == ValueContainsNonLiteralState.Invalid)
                        {
                            predicateValueKind = equals ? PredicateValueKind.AlwaysFalse : PredicateValueKind.AlwaysTrue;
                        }
                        else if (predicateValueKind != PredicateValueKind.AlwaysFalse &&
                            newValue.IsLiteralState &&
                            newValue.LiteralValues.Count == 1 &&
                            currentAssignedValue.LiteralValues.Count == 1 &&
                            existingTargetValue.LiteralValues.Count == 1)
                        {
                            predicateValueKind = equals ? PredicateValueKind.AlwaysTrue : PredicateValueKind.AlwaysFalse;
                        }

                        currentAssignedValue = newValue;
                    }

                    if (equals)
                    {
                        CopyAbstractValue copyValue = GetCopyAbstractValue(target);
                        if (copyValue.Kind.IsKnown())
                        {
                            // https://github.com/dotnet/roslyn-analyzers/issues/2106 tracks enabling the below assert.
                            //Debug.Assert(copyValue.AnalysisEntities.Contains(targetEntity));
                            foreach (var analysisEntity in copyValue.AnalysisEntities)
                            {
                                SetAbstractValue(targetAnalysisData, analysisEntity, currentAssignedValue);
                            }
                        }
                        else
                        {
                            SetAbstractValue(targetAnalysisData, targetEntity, currentAssignedValue);
                        }
                    }
                }
            }

            #endregion

            protected override ValueContentAnalysisData MergeAnalysisData(ValueContentAnalysisData value1, ValueContentAnalysisData value2)
                => _valueContentAnalysisDomain.Merge(value1, value2);
            protected override ValueContentAnalysisData MergeAnalysisDataForBackEdge(ValueContentAnalysisData value1, ValueContentAnalysisData value2, BasicBlock forBlock)
                => _valueContentAnalysisDomain.MergeAnalysisDataForBackEdge(value1, value2);
            protected override void UpdateValuesForAnalysisData(ValueContentAnalysisData targetAnalysisData)
                => UpdateValuesForAnalysisData(targetAnalysisData.CoreAnalysisData, CurrentAnalysisData.CoreAnalysisData);
            protected override ValueContentAnalysisData GetClonedAnalysisData(ValueContentAnalysisData analysisData)
                => (ValueContentAnalysisData)analysisData.Clone();
            public override ValueContentAnalysisData GetEmptyAnalysisData()
                => new();
            protected override ValueContentAnalysisData GetExitBlockOutputData(ValueContentAnalysisResult analysisResult)
                => new(analysisResult.ExitBlockOutput.Data);
            protected override void ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(ValueContentAnalysisData dataAtException, ThrownExceptionInfo throwBranchWithExceptionType)
                => ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(dataAtException.CoreAnalysisData, CurrentAnalysisData.CoreAnalysisData, throwBranchWithExceptionType);
            protected override bool Equals(ValueContentAnalysisData value1, ValueContentAnalysisData value2)
                => value1.Equals(value2);
            protected override void ApplyInterproceduralAnalysisResultCore(ValueContentAnalysisData resultData)
                => ApplyInterproceduralAnalysisResultHelper(resultData.CoreAnalysisData);
            protected override ValueContentAnalysisData GetTrimmedCurrentAnalysisData(IEnumerable<AnalysisEntity> withEntities)
                => GetTrimmedCurrentAnalysisDataHelper(withEntities, CurrentAnalysisData.CoreAnalysisData, SetAbstractValue);

            #region Visitor methods
            public override ValueContentAbstractValue DefaultVisit(IOperation operation, object? argument)
            {
                _ = base.DefaultVisit(operation, argument);
                if (operation.Type == null)
                {
                    return operation.Kind == OperationKind.None ?
                        ValueContentAbstractValue.MayBeContainsNonLiteralState :
                        ValueContentAbstractValue.ContainsNullLiteralState;
                }

                if (ValueContentAbstractValue.IsSupportedType(operation.Type, out var valueTypeSymbol))
                {
                    if (operation.ConstantValue.HasValue)
                    {
                        return operation.ConstantValue.Value != null ?
                            ValueContentAbstractValue.Create(operation.ConstantValue.Value, valueTypeSymbol) :
                            ValueContentAbstractValue.ContainsNullLiteralState;
                    }
                    else
                    {
                        return GetNullAbstractValue(operation) switch
                        {
                            PointsToAnalysis.NullAbstractValue.Invalid => ValueContentAbstractValue.InvalidState,

                            PointsToAnalysis.NullAbstractValue.Null => ValueContentAbstractValue.ContainsNullLiteralState,

                            _ => ValueContentAbstractValue.MayBeContainsNonLiteralState,
                        };
                    }
                }
                else if (DataFlowAnalysisContext.GetValueForAdditionalSupportedValueTypeOperation is { } getValueFunc &&
                    operation.Type is INamedTypeSymbol namedType &&
                    DataFlowAnalysisContext.AdditionalSupportedValueTypes.Contains(namedType))
                {
                    return getValueFunc(operation);
                }

                return ValueDomain.UnknownOrMayBeValue;
            }

            public override ValueContentAbstractValue VisitBinaryOperatorCore(IBinaryOperation operation, object? argument)
            {
                var leftValue = Visit(operation.LeftOperand, argument);
                var rightValue = Visit(operation.RightOperand, argument);
                return leftValue.MergeBinaryOperation(rightValue, operation.OperatorKind, operation.LeftOperand.Type, operation.RightOperand.Type, operation.Type);
            }

            public override ValueContentAbstractValue ComputeValueForCompoundAssignment(
                ICompoundAssignmentOperation operation,
                ValueContentAbstractValue targetValue,
                ValueContentAbstractValue assignedValue,
                ITypeSymbol? targetType,
                ITypeSymbol? assignedValueType)
            {
                return targetValue.MergeBinaryOperation(assignedValue, operation.OperatorKind, targetType, assignedValueType, operation.Type);
            }

            public override ValueContentAbstractValue ComputeValueForIncrementOrDecrementOperation(IIncrementOrDecrementOperation operation, ValueContentAbstractValue targetValue)
            {
                var incrementValue = ValueContentAbstractValue.ContainsOneIntergralLiteralState;
                var incrementValueType = WellKnownTypeProvider.Compilation.GetSpecialType(SpecialType.System_Int32);
                var operationKind = operation.Kind == OperationKind.Increment ? BinaryOperatorKind.Add : BinaryOperatorKind.Subtract;
                return targetValue.MergeBinaryOperation(incrementValue, operationKind, operation.Target.Type, incrementValueType, operation.Type);
            }

            public override ValueContentAbstractValue VisitObjectCreation(IObjectCreationOperation operation, object? argument)
            {
                // TODO: Analyze string constructor
                // https://github.com/dotnet/roslyn-analyzers/issues/1547
                return base.VisitObjectCreation(operation, argument);
            }

            public override ValueContentAbstractValue VisitFieldReference(IFieldReferenceOperation operation, object? argument)
            {
                var value = base.VisitFieldReference(operation, argument);

                // Handle "string.Empty"
                if (operation.Field.Name.Equals("Empty", StringComparison.Ordinal) &&
                    operation.Field.ContainingType.SpecialType == SpecialType.System_String)
                {
                    return ValueContentAbstractValue.ContainsEmptyStringLiteralState;
                }

                return value;
            }

            public override ValueContentAbstractValue VisitInvocation_NonLambdaOrDelegateOrLocalFunction(
                IMethodSymbol method,
                IOperation? visitedInstance,
                ImmutableArray<IArgumentOperation> visitedArguments,
                bool invokedAsDelegate,
                IOperation originalOperation,
                ValueContentAbstractValue defaultValue)
            {
                // TODO: Handle invocations of string methods (Format, SubString, Replace, Concat, etc.)
                // https://github.com/dotnet/roslyn-analyzers/issues/1547
                return base.VisitInvocation_NonLambdaOrDelegateOrLocalFunction(method, visitedInstance, visitedArguments, invokedAsDelegate, originalOperation, defaultValue);
            }

            public override ValueContentAbstractValue VisitInterpolatedString(IInterpolatedStringOperation operation, object? argument)
            {
                if (operation.Parts.IsEmpty)
                {
                    return ValueContentAbstractValue.ContainsEmptyStringLiteralState;
                }

                ValueContentAbstractValue mergedValue = Visit(operation.Parts[0], argument);
                for (int i = 1; i < operation.Parts.Length; i++)
                {
                    var newValue = Visit(operation.Parts[i], argument);
                    mergedValue = mergedValue.MergeBinaryOperation(newValue, BinaryOperatorKind.Add, leftType: operation.Type, rightType: operation.Type, resultType: operation.Type);
                }

                return mergedValue;
            }

            #endregion
        }
    }
}