// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. 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 } } }