// 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 System.Diagnostics;
using System.Linq;
using Analyzer.Utilities.Extensions;
using Analyzer.Utilities.PooledObjects;
using Analyzer.Utilities.PooledObjects.Extensions;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CopyAnalysis
{
using CopyAnalysisDomain = PredicatedAnalysisDataDomain<CopyAnalysisData, CopyAbstractValue>;
using CopyAnalysisResult = DataFlowAnalysisResult<CopyBlockAnalysisResult, CopyAbstractValue>;
public partial class CopyAnalysis : ForwardDataFlowAnalysis<CopyAnalysisData, CopyAnalysisContext, CopyAnalysisResult, CopyBlockAnalysisResult, CopyAbstractValue>
{
/// <summary>
/// Operation visitor to flow the copy values across a given statement in a basic block.
/// </summary>
private sealed class CopyDataFlowOperationVisitor : PredicateAnalysisEntityDataFlowOperationVisitor<CopyAnalysisData, CopyAnalysisContext, CopyAnalysisResult, CopyAbstractValue>
{
public CopyDataFlowOperationVisitor(CopyAnalysisContext analysisContext)
: base(analysisContext)
{
var coreAnalysisDomain = new CoreCopyAnalysisDataDomain(CopyAbstractValueDomain.Default, GetDefaultCopyValue);
AnalysisDomain = new CopyAnalysisDomain(coreAnalysisDomain);
analysisContext.InterproceduralAnalysisData?.InitialAnalysisData?.AssertValidCopyAnalysisData();
}
public CopyAnalysisDomain AnalysisDomain { get; }
public override CopyAnalysisData Flow(IOperation statement, BasicBlock block, CopyAnalysisData input)
{
AssertValidCopyAnalysisData(input);
var output = base.Flow(statement, block, input);
AssertValidCopyAnalysisData(output);
return output;
}
public override (CopyAnalysisData output, bool isFeasibleBranch) FlowBranch(
BasicBlock fromBlock,
BranchWithInfo branch,
CopyAnalysisData input)
{
AssertValidCopyAnalysisData(input);
(CopyAnalysisData output, bool isFeasibleBranch) result = base.FlowBranch(fromBlock, branch, input);
AssertValidCopyAnalysisData(result.output);
return result;
}
[Conditional("DEBUG")]
private void AssertValidCopyAnalysisData(CopyAnalysisData copyAnalysisData)
{
copyAnalysisData.AssertValidCopyAnalysisData(GetDefaultCopyValue);
}
protected override void AddTrackedEntities(CopyAnalysisData analysisData, HashSet<AnalysisEntity> builder, bool forInterproceduralAnalysis)
=> analysisData.AddTrackedEntities(builder);
protected override bool HasAbstractValue(AnalysisEntity analysisEntity) => CurrentAnalysisData.HasAbstractValue(analysisEntity);
protected override bool HasAnyAbstractValue(CopyAnalysisData data) => data.HasAnyAbstractValue;
protected override void StopTrackingEntity(AnalysisEntity analysisEntity, CopyAnalysisData analysisData)
=> StopTrackingEntity(analysisEntity, analysisData, GetDefaultCopyValue);
private static void StopTrackingEntity(
AnalysisEntity analysisEntity,
CopyAnalysisData analysisData,
Func<AnalysisEntity, CopyAbstractValue> getDefaultCopyValue)
{
analysisData.AssertValidCopyAnalysisData(getDefaultCopyValue);
// First set the value to unknown so we remove the entity from existing copy sets.
// Note that we pass 'tryGetAddressSharedCopyValue = null' to ensure that
// we do not reset the entries for address shared entities.
SetAbstractValue(analysisData, analysisEntity, CopyAbstractValue.Unknown, tryGetAddressSharedCopyValue: _ => null);
analysisData.AssertValidCopyAnalysisData(tryGetDefaultCopyValue: null);
// Now it should be safe to remove the entry.
analysisData.RemoveEntries(analysisEntity);
analysisData.AssertValidCopyAnalysisData(getDefaultCopyValue);
}
protected override CopyAbstractValue GetAbstractValue(AnalysisEntity analysisEntity) => CurrentAnalysisData.TryGetValue(analysisEntity, out var value) ? value : CopyAbstractValue.Unknown;
protected override CopyAbstractValue GetCopyAbstractValue(IOperation operation) => base.GetCachedAbstractValue(operation);
protected override CopyAbstractValue GetAbstractDefaultValue(ITypeSymbol? type) => CopyAbstractValue.NotApplicable;
protected override void ResetAbstractValue(AnalysisEntity analysisEntity)
=> SetAbstractValue(analysisEntity, GetResetValue(analysisEntity));
protected override void SetAbstractValue(AnalysisEntity analysisEntity, CopyAbstractValue value)
{
SetAbstractValue(CurrentAnalysisData, analysisEntity, value, TryGetAddressSharedCopyValue);
}
protected override void SetAbstractValueForAssignment(AnalysisEntity targetAnalysisEntity, IOperation? assignedValueOperation, CopyAbstractValue assignedValue)
{
if (assignedValue.AnalysisEntities.Contains(targetAnalysisEntity))
{
// Dead assignment (assigning the same value).
return;
}
base.SetAbstractValueForAssignment(targetAnalysisEntity, assignedValueOperation, assignedValue);
CurrentAnalysisData.AssertValidCopyAnalysisData();
}
protected override void SetAbstractValueForTupleElementAssignment(AnalysisEntity tupleElementEntity, IOperation assignedValueOperation, CopyAbstractValue assignedValue)
{
// Copy value of assignedValueOperation entity might change between tuple element assignments within the same tuple.
// For example, '(a, a)'
if (!assignedValue.AnalysisEntities.IsEmpty)
{
var currentCopyValue = GetAbstractValue(assignedValue.AnalysisEntities.First());
if (currentCopyValue.Kind != CopyAbstractValueKind.Unknown)
{
assignedValue = currentCopyValue;
}
}
base.SetAbstractValueForTupleElementAssignment(tupleElementEntity, assignedValueOperation, assignedValue);
}
private static void SetAbstractValue(
CopyAnalysisData copyAnalysisData,
AnalysisEntity analysisEntity,
CopyAbstractValue value,
Func<AnalysisEntity, CopyAbstractValue?> tryGetAddressSharedCopyValue,
SetCopyAbstractValuePredicateKind? fromPredicateKind = null,
bool initializingParameters = false)
{
SetAbstractValue(sourceCopyAnalysisData: copyAnalysisData, targetCopyAnalysisData: copyAnalysisData,
analysisEntity, value, tryGetAddressSharedCopyValue, fromPredicateKind, initializingParameters);
}
private static void SetAbstractValue(
CopyAnalysisData sourceCopyAnalysisData,
CopyAnalysisData targetCopyAnalysisData,
AnalysisEntity analysisEntity,
CopyAbstractValue value,
Func<AnalysisEntity, CopyAbstractValue?> tryGetAddressSharedCopyValue,
SetCopyAbstractValuePredicateKind? fromPredicateKind,
bool initializingParameters)
{
sourceCopyAnalysisData.AssertValidCopyAnalysisData(tryGetAddressSharedCopyValue, initializingParameters);
targetCopyAnalysisData.AssertValidCopyAnalysisData(tryGetAddressSharedCopyValue, initializingParameters);
Debug.Assert(ReferenceEquals(sourceCopyAnalysisData, targetCopyAnalysisData) || fromPredicateKind.HasValue);
// Don't track entities if do not know about it's instance location.
if (analysisEntity.HasUnknownInstanceLocation)
{
return;
}
if (!value.AnalysisEntities.IsEmpty)
{
var validEntities = value.AnalysisEntities.Where(entity => !entity.HasUnknownInstanceLocation).ToImmutableHashSet();
if (validEntities.Count < value.AnalysisEntities.Count)
{
value = !validEntities.IsEmpty ? new CopyAbstractValue(validEntities, value.Kind) : CopyAbstractValue.Unknown;
}
}
// Handle updating the existing value if not setting the value from predicate analysis.
if (!fromPredicateKind.HasValue &&
sourceCopyAnalysisData.TryGetValue(analysisEntity, out var existingValue))
{
if (existingValue == value)
{
// Assigning the same value to the entity.
Debug.Assert(existingValue.AnalysisEntities.Contains(analysisEntity));
return;
}
if (existingValue.AnalysisEntities.Count > 1)
{
CopyAbstractValue? addressSharedCopyValue = tryGetAddressSharedCopyValue(analysisEntity);
if (addressSharedCopyValue == null || addressSharedCopyValue != existingValue)
{
CopyAbstractValue newValueForEntitiesInOldSet = addressSharedCopyValue != null ?
existingValue.WithEntitiesRemoved(addressSharedCopyValue.AnalysisEntities) :
existingValue.WithEntityRemoved(analysisEntity);
targetCopyAnalysisData.SetAbstactValueForEntities(newValueForEntitiesInOldSet, entityBeingAssigned: analysisEntity);
}
}
}
// Handle setting the new value.
var newAnalysisEntities = value.AnalysisEntities.Add(analysisEntity);
CopyAbstractValueKind newKind;
if (newAnalysisEntities.Count == 1)
{
newKind = analysisEntity.Type.IsValueType ?
CopyAbstractValueKind.KnownValueCopy :
CopyAbstractValueKind.KnownReferenceCopy;
}
else
{
newKind = fromPredicateKind != SetCopyAbstractValuePredicateKind.ValueCompare &&
!analysisEntity.Type.IsValueType &&
value.Kind == CopyAbstractValueKind.KnownReferenceCopy ?
CopyAbstractValueKind.KnownReferenceCopy :
CopyAbstractValueKind.KnownValueCopy;
}
if (fromPredicateKind.HasValue)
{
// Also include the existing values for the analysis entity.
if (sourceCopyAnalysisData.TryGetValue(analysisEntity, out existingValue))
{
if (existingValue.Kind == CopyAbstractValueKind.Invalid)
{
return;
}
newAnalysisEntities = newAnalysisEntities.Union(existingValue.AnalysisEntities);
newKind = newKind.MergeIfBothKnown(existingValue.Kind);
}
}
else
{
// Include address shared entities, if any.
CopyAbstractValue? addressSharedCopyValue = tryGetAddressSharedCopyValue(analysisEntity);
if (addressSharedCopyValue != null)
{
newAnalysisEntities = newAnalysisEntities.Union(addressSharedCopyValue.AnalysisEntities);
}
}
var newValue = new CopyAbstractValue(newAnalysisEntities, newKind);
targetCopyAnalysisData.SetAbstactValueForEntities(newValue, entityBeingAssigned: analysisEntity);
targetCopyAnalysisData.AssertValidCopyAnalysisData(tryGetAddressSharedCopyValue, initializingParameters);
}
protected override void SetValueForParameterOnEntry(IParameterSymbol parameter, AnalysisEntity analysisEntity, ArgumentInfo<CopyAbstractValue>? assignedValue)
{
CopyAbstractValue copyValue;
if (assignedValue != null)
{
var assignedEntities = assignedValue.Value.AnalysisEntities;
if (assignedValue.AnalysisEntity != null && !assignedEntities.Contains(assignedValue.AnalysisEntity))
{
assignedEntities = assignedEntities.Add(assignedValue.AnalysisEntity);
}
var newAnalysisEntities = assignedEntities;
CopyAbstractValueKind newKind;
if (assignedValue.Value.Kind.IsKnown())
{
newKind = assignedValue.Value.Kind;
}
else if (assignedValue.AnalysisEntity == null || assignedValue.AnalysisEntity.Type.IsValueType)
{
newKind = CopyAbstractValueKind.KnownValueCopy;
}
else
{
newKind = CopyAbstractValueKind.KnownReferenceCopy;
}
foreach (var entity in assignedEntities)
{
if (CurrentAnalysisData.TryGetValue(entity, out var existingValue))
{
newAnalysisEntities = newAnalysisEntities.Union(existingValue.AnalysisEntities);
newKind = newKind.MergeIfBothKnown(existingValue.Kind);
}
}
copyValue = assignedValue.Value.AnalysisEntities.Count == newAnalysisEntities.Count ?
assignedValue.Value :
new CopyAbstractValue(newAnalysisEntities, newKind);
}
else
{
copyValue = GetDefaultCopyValue(analysisEntity);
}
SetAbstractValue(CurrentAnalysisData, analysisEntity, copyValue, TryGetAddressSharedCopyValue, initializingParameters: true);
}
protected override void EscapeValueForParameterOnExit(IParameterSymbol parameter, AnalysisEntity analysisEntity)
{
// Do not escape the copy value for parameter at exit.
}
private CopyAbstractValue GetResetValue(AnalysisEntity analysisEntity)
=> GetResetValue(analysisEntity, GetAbstractValue(analysisEntity));
private CopyAbstractValue GetResetValue(AnalysisEntity analysisEntity, CopyAbstractValue currentValue)
=> currentValue.AnalysisEntities.Count > 1 ? GetDefaultCopyValue(analysisEntity) : currentValue;
protected override void ResetCurrentAnalysisData() => CurrentAnalysisData.Reset(GetResetValue);
protected override CopyAbstractValue ComputeAnalysisValueForReferenceOperation(IOperation operation, CopyAbstractValue defaultValue)
{
if (AnalysisEntityFactory.TryCreate(operation, out var analysisEntity))
{
return CurrentAnalysisData.TryGetValue(analysisEntity, out var value) ? value : GetDefaultCopyValue(analysisEntity);
}
else
{
return defaultValue;
}
}
protected override CopyAbstractValue ComputeAnalysisValueForEscapedRefOrOutArgument(AnalysisEntity analysisEntity, IArgumentOperation operation, CopyAbstractValue defaultValue)
{
Debug.Assert(operation.Parameter?.RefKind is RefKind.Ref or RefKind.Out);
SetAbstractValue(analysisEntity, ValueDomain.UnknownOrMayBeValue);
return GetAbstractValue(analysisEntity);
}
#region Predicate analysis
protected override PredicateValueKind SetValueForEqualsOrNotEqualsComparisonOperator(
IOperation leftOperand,
IOperation rightOperand,
bool equals,
bool isReferenceEquality,
CopyAnalysisData targetAnalysisData)
{
var predicateKind = PredicateValueKind.Unknown;
var leftCopyValue = GetCopyAbstractValue(leftOperand);
var rightCopyValue = GetCopyAbstractValue(rightOperand);
if (leftCopyValue.AnalysisEntities.IsEmpty ||
rightCopyValue.AnalysisEntities.IsEmpty)
{
return predicateKind;
}
if (leftCopyValue == rightCopyValue)
{
// We have "a == b && a == b" or "a == b && a != b"
// For both cases, condition on right is always true or always false and redundant.
if (!isReferenceEquality ||
rightCopyValue.Kind == CopyAbstractValueKind.KnownReferenceCopy)
{
predicateKind = equals ? PredicateValueKind.AlwaysTrue : PredicateValueKind.AlwaysFalse;
}
}
var setCopyValuePredicateKind = isReferenceEquality ?
SetCopyAbstractValuePredicateKind.ReferenceCompare :
SetCopyAbstractValuePredicateKind.ValueCompare;
if (predicateKind != PredicateValueKind.Unknown)
{
if (!equals)
{
// "a == b && a != b" or "a == b || a != b"
foreach (var entity in rightCopyValue.AnalysisEntities)
{
SetAbstractValue(targetAnalysisData, entity, CopyAbstractValue.Invalid, TryGetAddressSharedCopyValue, setCopyValuePredicateKind);
}
}
return predicateKind;
}
if (equals)
{
SetAbstractValue(targetAnalysisData, leftCopyValue.AnalysisEntities.First(), rightCopyValue, TryGetAddressSharedCopyValue, setCopyValuePredicateKind);
}
return PredicateValueKind.Unknown;
}
protected override PredicateValueKind SetValueForIsNullComparisonOperator(IOperation leftOperand, bool equals, CopyAnalysisData targetAnalysisData) => PredicateValueKind.Unknown;
#endregion
protected override CopyAnalysisData MergeAnalysisData(CopyAnalysisData value1, CopyAnalysisData value2)
=> AnalysisDomain.Merge(value1, value2);
protected override void UpdateValuesForAnalysisData(CopyAnalysisData targetAnalysisData)
{
// We need to trim the copy values to only include the entities that are existing keys in targetAnalysisData.
using var processedEntities = PooledHashSet<AnalysisEntity>.GetInstance();
using var builder = ArrayBuilder<AnalysisEntity>.GetInstance(targetAnalysisData.CoreAnalysisData.Count);
builder.AddRange(targetAnalysisData.CoreAnalysisData.Keys);
for (int i = 0; i < builder.Count; i++)
{
var key = builder[i];
if (!processedEntities.Add(key))
{
continue;
}
var existingValue = targetAnalysisData[key];
if (CurrentAnalysisData.TryGetValue(key, out var newValue))
{
if (newValue.AnalysisEntities.Count == 1)
{
if (existingValue.AnalysisEntities.Count == 1)
{
continue;
}
}
else if (newValue.AnalysisEntities.Count > 1)
{
var entitiesToExclude = newValue.AnalysisEntities.Where(e => !targetAnalysisData.HasAbstractValue(e));
if (entitiesToExclude.Any())
{
newValue = newValue.WithEntitiesRemoved(entitiesToExclude);
}
}
if (newValue != existingValue)
{
targetAnalysisData.SetAbstactValueForEntities(newValue, entityBeingAssigned: null);
}
processedEntities.AddRange(newValue.AnalysisEntities);
}
else
{
var entitiesToExclude = existingValue.AnalysisEntities.Where(e => !CurrentAnalysisData.HasAbstractValue(e));
var excludedCount = 0;
foreach (var entity in entitiesToExclude)
{
targetAnalysisData.RemoveEntries(entity);
processedEntities.Add(entity);
excludedCount++;
}
if (excludedCount < existingValue.AnalysisEntities.Count)
{
newValue = existingValue.WithEntitiesRemoved(entitiesToExclude);
targetAnalysisData.SetAbstactValueForEntities(newValue, entityBeingAssigned: null);
}
}
}
}
protected override void ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(CopyAnalysisData dataAtException, ThrownExceptionInfo throwBranchWithExceptionType)
{
Func<AnalysisEntity, bool>? predicate = null;
if (throwBranchWithExceptionType.IsDefaultExceptionForExceptionsPathAnalysis)
{
// Only tracking non-child analysis entities for exceptions path analysis for now.
Debug.Assert(SymbolEqualityComparer.Default.Equals(throwBranchWithExceptionType.ExceptionType, ExceptionNamedType));
predicate = e => !e.IsChildOrInstanceMember;
}
ApplyMissingCurrentAnalysisDataCore(dataAtException, predicate);
}
protected override CopyAnalysisData GetClonedAnalysisData(CopyAnalysisData analysisData)
=> (CopyAnalysisData)analysisData.Clone();
public override CopyAnalysisData GetEmptyAnalysisData()
=> new();
protected override CopyAnalysisData GetExitBlockOutputData(CopyAnalysisResult analysisResult)
=> new(analysisResult.ExitBlockOutput.Data);
protected override void AssertValidAnalysisData(CopyAnalysisData analysisData)
=> AssertValidCopyAnalysisData(analysisData);
protected override bool Equals(CopyAnalysisData value1, CopyAnalysisData value2)
=> value1.Equals(value2);
public override (CopyAbstractValue Value, PredicateValueKind PredicateValueKind)? GetReturnValueAndPredicateKind()
{
// Filter out all the local symbol and flow capture entities from the return value for interprocedural analysis.
var returnValueAndPredicateKind = base.GetReturnValueAndPredicateKind();
if (returnValueAndPredicateKind.HasValue &&
returnValueAndPredicateKind.Value.Value.Kind.IsKnown() &&
DataFlowAnalysisContext.InterproceduralAnalysisData != null)
{
using var entitiesToFilterBuilder = PooledHashSet<AnalysisEntity>.GetInstance();
var copyValue = returnValueAndPredicateKind.Value.Value;
var copyValueEntities = copyValue.AnalysisEntities;
foreach (var entity in copyValueEntities)
{
if (ShouldStopTrackingEntityAtExit(entity))
{
// Stop tracking entity that is now out of scope.
entitiesToFilterBuilder.Add(entity);
// Additionally, stop tracking all the child entities if the entity type has value copy semantics.
if (entity.Type.HasValueCopySemantics())
{
var childEntities = copyValueEntities.Where(e => IsChildAnalysisEntity(e, ancestorEntity: entity));
entitiesToFilterBuilder.AddRange(childEntities);
}
}
}
if (entitiesToFilterBuilder.Count > 0)
{
copyValue = entitiesToFilterBuilder.Count == copyValueEntities.Count ?
CopyAbstractValue.Unknown :
copyValue.WithEntitiesRemoved(entitiesToFilterBuilder);
}
return (copyValue, returnValueAndPredicateKind.Value.PredicateValueKind);
}
return returnValueAndPredicateKind;
}
#region Interprocedural analysis
protected override void ApplyInterproceduralAnalysisResultCore(CopyAnalysisData resultData)
{
using var mergedData = GetClonedAnalysisData(resultData);
ApplyMissingCurrentAnalysisDataCore(mergedData, predicate: null);
CurrentAnalysisData.CoreAnalysisData.Clear();
CurrentAnalysisData.CoreAnalysisData.AddRange(mergedData.CoreAnalysisData);
}
private void ApplyMissingCurrentAnalysisDataCore(CopyAnalysisData mergedData, Func<AnalysisEntity, bool>? predicate)
{
using var processedEntities = PooledHashSet<AnalysisEntity>.GetInstance();
foreach (var kvp in CurrentAnalysisData.CoreAnalysisData)
{
var key = kvp.Key;
var copyValue = kvp.Value;
if (mergedData.CoreAnalysisData.ContainsKey(key) ||
(predicate != null && !predicate(key)) ||
!processedEntities.Add(key))
{
continue;
}
if (predicate != null && copyValue.AnalysisEntities.Count > 1)
{
var entitiesToRemove = copyValue.AnalysisEntities.Where(entity => key != entity && !predicate(entity));
if (entitiesToRemove.Any())
{
copyValue = copyValue.WithEntitiesRemoved(entitiesToRemove);
}
}
Debug.Assert(copyValue.AnalysisEntities.Contains(key));
Debug.Assert(predicate == null || copyValue.AnalysisEntities.All(predicate));
mergedData.SetAbstactValueForEntities(copyValue, entityBeingAssigned: null);
processedEntities.AddRange(copyValue.AnalysisEntities);
}
AssertValidCopyAnalysisData(mergedData);
}
protected override CopyAnalysisData GetTrimmedCurrentAnalysisData(IEnumerable<AnalysisEntity> withEntities)
{
using var processedEntities = PooledHashSet<AnalysisEntity>.GetInstance();
var analysisData = new CopyAnalysisData();
foreach (var entity in withEntities)
{
if (processedEntities.Add(entity))
{
var copyValue = GetAbstractValue(entity);
analysisData.SetAbstactValueForEntities(copyValue, entityBeingAssigned: null);
processedEntities.AddRange(copyValue.AnalysisEntities);
}
}
AssertValidCopyAnalysisData(analysisData);
return analysisData;
}
protected override CopyAnalysisData GetInitialInterproceduralAnalysisData(
IMethodSymbol invokedMethod,
(AnalysisEntity? Instance, PointsToAbstractValue PointsToValue)? invocationInstance,
(AnalysisEntity Instance, PointsToAbstractValue PointsToValue)? thisOrMeInstanceForCaller,
ImmutableDictionary<IParameterSymbol, ArgumentInfo<CopyAbstractValue>> argumentValuesMap,
IDictionary<AnalysisEntity, PointsToAbstractValue>? pointsToValues,
IDictionary<AnalysisEntity, CopyAbstractValue>? copyValues,
IDictionary<AnalysisEntity, ValueContentAbstractValue>? valueContentValues,
bool isLambdaOrLocalFunction,
bool hasParameterWithDelegateType)
{
copyValues = CurrentAnalysisData.CoreAnalysisData;
var initialAnalysisData = base.GetInitialInterproceduralAnalysisData(invokedMethod, invocationInstance,
thisOrMeInstanceForCaller, argumentValuesMap, pointsToValues, copyValues, valueContentValues,
isLambdaOrLocalFunction, hasParameterWithDelegateType);
AssertValidCopyAnalysisData(initialAnalysisData);
return initialAnalysisData;
}
#endregion
#region Visitor overrides
public override CopyAbstractValue DefaultVisit(IOperation operation, object? argument)
{
_ = base.DefaultVisit(operation, argument);
return CopyAbstractValue.Unknown;
}
public override CopyAbstractValue VisitConversion(IConversionOperation operation, object? argument)
{
var operandValue = Visit(operation.Operand, argument);
if (TryInferConversion(operation, out var conversionInference) &&
FlowConversionOperandValue(conversionInference, operation.Type))
{
return operandValue;
}
return CopyAbstractValue.Unknown;
}
public override CopyAbstractValue GetAssignedValueForPattern(IIsPatternOperation operation, CopyAbstractValue operandValue)
{
if (TryInferConversion(operation, out var conversionInference))
{
if (FlowConversionOperandValue(conversionInference, targetType: operation.Pattern.GetPatternType()))
{
return operandValue;
}
}
return CopyAbstractValue.Unknown;
}
private static bool FlowConversionOperandValue(ConversionInference inference, ITypeSymbol? targetType)
{
Debug.Assert(!inference.AlwaysSucceed || !inference.AlwaysFail);
// Flow the copy value of the operand for boxing and unboxing conversions.
if (inference.IsBoxing || inference.IsUnboxing)
{
return true;
}
// Otherwise, flow the copy value of the operand to the converted operation if conversion may succeed.
if (!inference.AlwaysFail)
{
// For try cast, also ensure conversion always succeeds before flowing copy value.
// TODO: For direct cast, we should check if conversion is implicit.
// For now, we only flow values for reference type direct cast conversions.
if (inference.IsTryCast && inference.AlwaysSucceed ||
!inference.IsTryCast && targetType?.IsReferenceType == true)
{
return true;
}
}
return false;
}
protected override CopyAbstractValue VisitAssignmentOperation(IAssignmentOperation operation, object? argument)
{
var value = base.VisitAssignmentOperation(operation, argument);
if (AnalysisEntityFactory.TryCreate(operation.Target, out var analysisEntity))
{
return GetAbstractValue(analysisEntity);
}
return value;
}
#endregion
}
}
}