// 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 System.Diagnostics; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Analyzer.Utilities.PooledObjects; using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CopyAnalysis; using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis { public partial class PointsToAnalysis : ForwardDataFlowAnalysis<PointsToAnalysisData, PointsToAnalysisContext, PointsToAnalysisResult, PointsToBlockAnalysisResult, PointsToAbstractValue> { /// <summary> /// Operation visitor to flow the PointsTo values across a given statement in a basic block. /// </summary> private sealed class PointsToDataFlowOperationVisitor : PredicateAnalysisEntityDataFlowOperationVisitor<PointsToAnalysisData, PointsToAnalysisContext, PointsToAnalysisResult, PointsToAbstractValue> { private readonly DefaultPointsToValueGenerator _defaultPointsToValueGenerator; private readonly PointsToAnalysisDomain _pointsToAnalysisDomain; private readonly PooledDictionary<IOperation, ImmutableHashSet<AbstractLocation>.Builder> _escapedOperationLocationsBuilder; private readonly PooledDictionary<IOperation, ImmutableHashSet<AbstractLocation>.Builder> _escapedReturnValueLocationsBuilder; private readonly PooledDictionary<AnalysisEntity, ImmutableHashSet<AbstractLocation>.Builder> _escapedEntityLocationsBuilder; public PointsToDataFlowOperationVisitor( TrackedEntitiesBuilder trackedEntitiesBuilder, DefaultPointsToValueGenerator defaultPointsToValueGenerator, PointsToAnalysisDomain pointsToAnalysisDomain, PointsToAnalysisContext analysisContext) : base(analysisContext) { TrackedEntitiesBuilder = trackedEntitiesBuilder; _defaultPointsToValueGenerator = defaultPointsToValueGenerator; _pointsToAnalysisDomain = pointsToAnalysisDomain; _escapedOperationLocationsBuilder = PooledDictionary<IOperation, ImmutableHashSet<AbstractLocation>.Builder>.GetInstance(); _escapedReturnValueLocationsBuilder = PooledDictionary<IOperation, ImmutableHashSet<AbstractLocation>.Builder>.GetInstance(); _escapedEntityLocationsBuilder = PooledDictionary<AnalysisEntity, ImmutableHashSet<AbstractLocation>.Builder>.GetInstance(); analysisContext.InterproceduralAnalysisData?.InitialAnalysisData?.AssertValidPointsToAnalysisData(); } internal TrackedEntitiesBuilder TrackedEntitiesBuilder { get; } public ImmutableDictionary<IOperation, ImmutableHashSet<AbstractLocation>> GetEscapedLocationsThroughOperationsMap() => GetEscapedAbstractLocationsMapAndFreeBuilder(_escapedOperationLocationsBuilder); public ImmutableDictionary<IOperation, ImmutableHashSet<AbstractLocation>> GetEscapedLocationsThroughReturnValuesMap() => GetEscapedAbstractLocationsMapAndFreeBuilder(_escapedReturnValueLocationsBuilder); public ImmutableDictionary<AnalysisEntity, ImmutableHashSet<AbstractLocation>> GetEscapedLocationsThroughEntitiesMap() => GetEscapedAbstractLocationsMapAndFreeBuilder(_escapedEntityLocationsBuilder); private static ImmutableDictionary<T, ImmutableHashSet<AbstractLocation>> GetEscapedAbstractLocationsMapAndFreeBuilder<T>( PooledDictionary<T, ImmutableHashSet<AbstractLocation>.Builder> escapedLocationsBuilder) where T : class { try { if (escapedLocationsBuilder.Count == 0) { return ImmutableDictionary<T, ImmutableHashSet<AbstractLocation>>.Empty; } var builder = ImmutableDictionary.CreateBuilder<T, ImmutableHashSet<AbstractLocation>>(); foreach ((var key, var valueBuilder) in escapedLocationsBuilder) { builder.Add(key, valueBuilder.ToImmutable()); } return builder.ToImmutable(); } finally { escapedLocationsBuilder.Dispose(); } } private bool ShouldBeTracked(AnalysisEntity analysisEntity) => PointsToAnalysis.ShouldBeTracked(analysisEntity, DataFlowAnalysisContext.PointsToAnalysisKind, IsDisposable); private PointsToAbstractValue GetValueForEntityThatShouldNotBeTracked(AnalysisEntity analysisEntity) { Debug.Assert(!ShouldBeTracked(analysisEntity)); Debug.Assert(!CurrentAnalysisData.TryGetValue(analysisEntity, out var existingValue) || existingValue == PointsToAbstractValue.NoLocation); return !PointsToAnalysis.ShouldBeTracked(analysisEntity.Type, IsDisposable) ? PointsToAbstractValue.NoLocation : _defaultPointsToValueGenerator.GetOrCreateDefaultValue(analysisEntity); } public override PointsToAnalysisData Flow(IOperation statement, BasicBlock block, PointsToAnalysisData input) { AssertValidPointsToAnalysisData(input); #if DEBUG if (block.Kind == BasicBlockKind.Exit) { // No flow capture entities should be alive at the end of flow graph. input.AssertNoFlowCaptureEntitiesTracked(); } #endif // Ensure PointsTo value is set for the "this" or "Me" instance. if (!HasAbstractValue(AnalysisEntityFactory.ThisOrMeInstance) && ShouldBeTracked(AnalysisEntityFactory.ThisOrMeInstance)) { input.SetAbstractValue(AnalysisEntityFactory.ThisOrMeInstance, ThisOrMePointsToAbstractValue); } var output = base.Flow(statement, block, input); AssertValidPointsToAnalysisData(output); return output; } public override (PointsToAnalysisData output, bool isFeasibleBranch) FlowBranch(BasicBlock fromBlock, BranchWithInfo branch, PointsToAnalysisData input) { AssertValidPointsToAnalysisData(input); (PointsToAnalysisData output, bool isFeasibleBranch) result = base.FlowBranch(fromBlock, branch, input); AssertValidPointsToAnalysisData(result.output); return result; } protected override void AddTrackedEntities(PointsToAnalysisData analysisData, HashSet<AnalysisEntity> builder, bool forInterproceduralAnalysis) { if (!analysisData.HasAnyAbstractValue && (forInterproceduralAnalysis || !_defaultPointsToValueGenerator.HasAnyTrackedEntity)) { return; } foreach (var entity in TrackedEntitiesBuilder.EnumerateEntities()) { if (analysisData.HasAbstractValue(entity) || !forInterproceduralAnalysis && _defaultPointsToValueGenerator.IsTrackedEntity(entity)) { builder.Add(entity); } } } internal override bool IsPointsToAnalysis => true; protected override bool HasAbstractValue(AnalysisEntity analysisEntity) => CurrentAnalysisData.HasAbstractValue(analysisEntity); protected override void StopTrackingEntity(AnalysisEntity analysisEntity, PointsToAnalysisData analysisData) => analysisData.RemoveEntries(analysisEntity); protected override PointsToAbstractValue GetAbstractValue(AnalysisEntity analysisEntity) { if (!ShouldBeTracked(analysisEntity)) { return GetValueForEntityThatShouldNotBeTracked(analysisEntity); } if (!CurrentAnalysisData.TryGetValue(analysisEntity, out var value)) { value = _defaultPointsToValueGenerator.GetOrCreateDefaultValue(analysisEntity); } return value; } protected override PointsToAbstractValue GetPointsToAbstractValue(IOperation operation) => base.GetCachedAbstractValue(operation); protected override PointsToAbstractValue GetAbstractDefaultValue(ITypeSymbol? type) => !PointsToAnalysis.ShouldBeTracked(type, IsDisposable) ? PointsToAbstractValue.NoLocation : PointsToAbstractValue.NullLocation; protected override bool HasAnyAbstractValue(PointsToAnalysisData data) => data.HasAnyAbstractValue; protected override void SetAbstractValue(AnalysisEntity analysisEntity, PointsToAbstractValue value) { Debug.Assert(ShouldBeTracked(analysisEntity) || !HasAbstractValue(analysisEntity)); if (ShouldBeTracked(analysisEntity)) { if (value.Kind == PointsToAbstractValueKind.Undefined) { Debug.Assert(value == _defaultPointsToValueGenerator.GetOrCreateDefaultValue(analysisEntity)); Debug.Assert(!CurrentAnalysisData.TryGetValue(analysisEntity, out var currentValue) || currentValue.Kind == PointsToAbstractValueKind.Unknown && analysisEntity.Symbol is IParameterSymbol { RefKind: RefKind.Out }); return; } SetAbstractValueCore(CurrentAnalysisData, analysisEntity, value); TrackedEntitiesBuilder.AddEntityAndPointsToValue(analysisEntity, value); } } 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.KnownReferenceCopy) { return copyValue; } return base.GetCopyAbstractValue(operation); } private static void SetAbstractValueCore(PointsToAnalysisData pointsToAnalysisData, AnalysisEntity analysisEntity, PointsToAbstractValue value) => pointsToAnalysisData.SetAbstractValue(analysisEntity, value); protected override void SetAbstractValueForTupleElementAssignment(AnalysisEntity tupleElementEntity, IOperation assignedValueOperation, PointsToAbstractValue assignedValue) { if (assignedValue == PointsToAbstractValue.Undefined) { return; } base.SetAbstractValueForTupleElementAssignment(tupleElementEntity, assignedValueOperation, assignedValue); } protected override void ResetAbstractValue(AnalysisEntity analysisEntity) { if (analysisEntity.IsLValueFlowCaptureEntity) { // Flow captures can never be re-assigned. return; } SetAbstractValue(analysisEntity, PointsToAbstractValue.Unknown); } private static void ResetAbstractValueIfTracked(AnalysisEntity analysisEntity, PointsToAnalysisData pointsToAnalysisData) { if (pointsToAnalysisData.TryGetValue(analysisEntity, out var currentValue)) { pointsToAnalysisData.SetAbstractValue(analysisEntity, GetResetValue(analysisEntity, currentValue)); } } // Create a dummy PointsTo value for each reference type parameter. protected override PointsToAbstractValue GetDefaultValueForParameterOnEntry(IParameterSymbol parameter, AnalysisEntity analysisEntity) => PointsToAnalysis.ShouldBeTracked(parameter.Type, IsDisposable) ? PointsToAbstractValue.Create( AbstractLocation.CreateSymbolLocation(parameter, DataFlowAnalysisContext.InterproceduralAnalysisData?.CallStack), mayBeNull: true) : PointsToAbstractValue.NoLocation; protected override void EscapeValueForParameterOnExit(IParameterSymbol parameter, AnalysisEntity analysisEntity) { // Mark PointsTo values for ref/out parameters in non-interprocedural context as escaped. if (parameter.RefKind is RefKind.Ref or RefKind.Out) { Debug.Assert(DataFlowAnalysisContext.InterproceduralAnalysisData == null); var pointsToValue = GetAbstractValue(analysisEntity); HandleEscapingLocations(analysisEntity, _escapedEntityLocationsBuilder, analysisEntity, pointsToValue); } } private static PointsToAbstractValue GetResetValue(AnalysisEntity analysisEntity, PointsToAbstractValue currentValue) { if (analysisEntity.IsLValueFlowCaptureEntity) { // LValue flow capture PointsToAbstractValue can never change. return currentValue; } return PointsToAbstractValue.Unknown; } protected override void ResetCurrentAnalysisData() => CurrentAnalysisData.Reset(GetResetValue); protected override PointsToAbstractValue ComputeAnalysisValueForReferenceOperation(IOperation operation, PointsToAbstractValue defaultValue) { if (PointsToAnalysis.ShouldBeTracked(operation.Type, IsDisposable) && AnalysisEntityFactory.TryCreate(operation, out var analysisEntity)) { return GetAbstractValue(analysisEntity); } else { Debug.Assert(operation.Type == null || !operation.Type.IsNonNullableValueType() || defaultValue == PointsToAbstractValue.NoLocation); return defaultValue; } } protected override PointsToAbstractValue ComputeAnalysisValueForEscapedRefOrOutArgument(AnalysisEntity analysisEntity, IArgumentOperation operation, PointsToAbstractValue defaultValue) { Debug.Assert(operation.Parameter!.RefKind is RefKind.Ref or RefKind.Out); if (!ShouldBeTracked(analysisEntity)) { return GetValueForEntityThatShouldNotBeTracked(analysisEntity); } var location = AbstractLocation.CreateAllocationLocation(operation, analysisEntity.Type, DataFlowAnalysisContext); return PointsToAbstractValue.Create(location, mayBeNull: true); } protected override void PostProcessArgument(IArgumentOperation operation, bool isEscaped) { base.PostProcessArgument(operation, isEscaped); if (!isEscaped) { // Update abstract value for unescaped ref or out argument (interprocedural analysis case). if ((operation.Parameter?.RefKind is RefKind.Ref or RefKind.Out) && AnalysisEntityFactory.TryCreate(operation, out var analysisEntity)) { CacheAbstractValue(operation, GetAbstractValue(analysisEntity)); if (analysisEntity.Symbol?.Kind == SymbolKind.Field) { // Ref/Out field argument is considered escaped. HandleEscapingOperation(operation, operation); } } } else if (operation.Parameter?.RefKind is RefKind.Ref or RefKind.Out) { if (operation.Parameter.RefKind == RefKind.Ref) { // Input by-ref argument passed to invoked method is considered escaped in non-interprocedural analysis case. HandleEscapingOperation(operation, operation.Value); } // Output by-ref or out argument might be escaped if assigned to a field. HandlePossibleEscapingForAssignment(target: operation.Value, value: operation, operation: operation); } } protected override void ProcessReturnValue(IOperation? returnValue) { base.ProcessReturnValue(returnValue); // Escape the return value if we are not analyzing an invoked method during interprocedural analysis. if (returnValue != null && DataFlowAnalysisContext.InterproceduralAnalysisData == null) { HandleEscapingOperation(escapingOperation: returnValue, escapedInstance: returnValue, _escapedReturnValueLocationsBuilder); } } private protected override PointsToAbstractValue GetAbstractValueForImplicitWrappingTaskCreation(IOperation returnValueOperation, PointsToAbstractValue returnValue, PointsToAbstractValue implicitTaskPointsToValue) { return implicitTaskPointsToValue; } #region Predicate analysis private static bool IsValidValueForPredicateAnalysis(NullAbstractValue value) { return value switch { NullAbstractValue.Null or NullAbstractValue.NotNull => true, _ => false, }; } protected override PredicateValueKind SetValueForEqualsOrNotEqualsComparisonOperator( IOperation leftOperand, IOperation rightOperand, bool equals, bool isReferenceEquality, PointsToAnalysisData targetAnalysisData) { var predicateValueKind = PredicateValueKind.Unknown; // Handle "a == null" and "a != null" if (SetValueForNullCompare(leftOperand, rightOperand, equals, ref predicateValueKind, targetAnalysisData)) { return predicateValueKind; } // Otherwise, handle "null == a" and "null != a" SetValueForNullCompare(rightOperand, leftOperand, equals, ref predicateValueKind, targetAnalysisData); return predicateValueKind; } protected override PredicateValueKind SetValueForIsNullComparisonOperator(IOperation leftOperand, bool equals, PointsToAnalysisData targetAnalysisData) { var predicateValueKind = PredicateValueKind.Unknown; SetValueForNullCompare(leftOperand, value: NullAbstractValue.Null, equals: equals, predicateValueKind: ref predicateValueKind, targetAnalysisData: targetAnalysisData); return predicateValueKind; } private bool SetValueForNullCompare( IOperation target, IOperation assignedValueOperation, bool equals, ref PredicateValueKind predicateValueKind, PointsToAnalysisData targetAnalysisData) { NullAbstractValue value = GetNullAbstractValue(assignedValueOperation); return SetValueForNullCompare(target, value, equals, ref predicateValueKind, targetAnalysisData); } private bool SetValueForNullCompare( IOperation target, NullAbstractValue value, bool equals, ref PredicateValueKind predicateValueKind, PointsToAnalysisData targetAnalysisData) { if (IsValidValueForPredicateAnalysis(value) && AnalysisEntityFactory.TryCreate(target, out var targetEntity) && ShouldBeTracked(targetEntity)) { // Comparison with a non-null value guarantees that we can infer result in only one of the branches. // For example, predicate "a == c", where we know 'c' is non-null, guarantees 'a' is non-null in CurrentAnalysisData, bool inferInTargetAnalysisData = !(value == NullAbstractValue.NotNull && !equals); CopyAbstractValue copyValue = GetCopyAbstractValue(target); if (copyValue.Kind.IsKnown()) { foreach (var analysisEntity in copyValue.AnalysisEntities) { SetValueForNullCompareFromPredicate(analysisEntity, value, targetEntity.Type, equals, inferInTargetAnalysisData, ref predicateValueKind, _defaultPointsToValueGenerator, WellKnownTypeProvider.Compilation, IsDisposable, sourceAnalysisData: CurrentAnalysisData, targetAnalysisData: targetAnalysisData); } } else { SetValueForNullCompareFromPredicate(targetEntity, value, targetEntity.Type, equals, inferInTargetAnalysisData, ref predicateValueKind, _defaultPointsToValueGenerator, WellKnownTypeProvider.Compilation, IsDisposable, sourceAnalysisData: CurrentAnalysisData, targetAnalysisData: targetAnalysisData); } return true; } return false; } private static void SetValueForNullCompareFromPredicate( AnalysisEntity key, NullAbstractValue value, ITypeSymbol targetType, bool equals, bool inferInTargetAnalysisData, ref PredicateValueKind predicateValueKind, DefaultPointsToValueGenerator defaultPointsToValueGenerator, Compilation compilation, Func<ITypeSymbol?, bool> isDisposable, PointsToAnalysisData sourceAnalysisData, PointsToAnalysisData targetAnalysisData) { if (!PointsToAnalysis.ShouldBeTracked(key, defaultPointsToValueGenerator.PointsToAnalysisKind, isDisposable)) { Debug.Assert(!targetAnalysisData.HasAbstractValue(key)); Debug.Assert(!sourceAnalysisData.HasAbstractValue(key)); return; } // Compute the negated value. NullAbstractValue negatedValue = NegatePredicateValue(value); // Check if the key already has an existing "Null" or "NotNull" NullState that would make the condition always true or false. // If so, set the predicateValueKind to always true/false, set the value in branch that can never be taken to NullAbstractValue.Invalid // and turn off value inference in one of the branch. if (sourceAnalysisData.TryGetValue(key, out var existingPointsToValue)) { NullAbstractValue existingNullValue = existingPointsToValue.NullState; if (IsValidValueForPredicateAnalysis(existingNullValue) && (existingNullValue == NullAbstractValue.Null || value == NullAbstractValue.Null)) { if (value == existingNullValue && equals || negatedValue == existingNullValue && !equals) { predicateValueKind = PredicateValueKind.AlwaysTrue; negatedValue = NullAbstractValue.Invalid; inferInTargetAnalysisData = false; } if (negatedValue == existingNullValue && equals || value == existingNullValue && !equals) { predicateValueKind = PredicateValueKind.AlwaysFalse; value = NullAbstractValue.Invalid; } } } // Swap value and negatedValue if we are processing not-equals operator. if (!equals) { if (value != NullAbstractValue.Invalid && negatedValue != NullAbstractValue.Invalid) { value = negatedValue; } } if (inferInTargetAnalysisData) { // Set value for the CurrentAnalysisData. SetAbstractValueFromPredicate(key, value, targetType, defaultPointsToValueGenerator, compilation, sourceAnalysisData, targetAnalysisData); } return; static void SetAbstractValueFromPredicate( AnalysisEntity analysisEntity, NullAbstractValue nullState, ITypeSymbol targetType, DefaultPointsToValueGenerator defaultPointsToValueGenerator, Compilation compilation, PointsToAnalysisData sourceAnalysisData, PointsToAnalysisData targetAnalysisData) { AssertValidPointsToAnalysisData(sourceAnalysisData); AssertValidPointsToAnalysisData(targetAnalysisData); Debug.Assert(IsValidValueForPredicateAnalysis(nullState) || nullState == NullAbstractValue.Invalid); // Ensure that the predicated 'value' can be flowed from the target type to the analysis entity. if (!SymbolEqualityComparer.Default.Equals(targetType, analysisEntity.Type)) { var conversion = compilation.ClassifyCommonConversion(targetType, analysisEntity.Type); if (!conversion.Exists) { // No conversion exists, so we bail out from flowing the predicated value. return; } if (!conversion.IsIdentity && !conversion.IsNumeric && !conversion.IsNullable) { // For 'Null' predicated value, there needs to be an explicit conversion // from targetType to the analysisEntity's type to flow the predicated value. // For 'NotNull' predicated value, there needs to be an implicit conversion // from targetType to the analysisEntity's type to flow the predicated value. if (nullState == NullAbstractValue.Null && conversion.IsImplicit || nullState == NullAbstractValue.NotNull && !conversion.IsImplicit) { return; } } } if (!sourceAnalysisData.TryGetValue(analysisEntity, out var existingValue)) { existingValue = defaultPointsToValueGenerator.GetOrCreateDefaultValue(analysisEntity); } var newPointsToValue = nullState switch { NullAbstractValue.Null => existingValue.MakeNull(), NullAbstractValue.NotNull => existingValue.MakeNonNull(), NullAbstractValue.Invalid => PointsToAbstractValue.Invalid, _ => throw new InvalidProgramException(), }; targetAnalysisData.SetAbstractValue(analysisEntity, newPointsToValue); AssertValidPointsToAnalysisData(targetAnalysisData); } } private static NullAbstractValue NegatePredicateValue(NullAbstractValue value) { Debug.Assert(IsValidValueForPredicateAnalysis(value)); return value switch { NullAbstractValue.Null => NullAbstractValue.NotNull, NullAbstractValue.NotNull => NullAbstractValue.Null, _ => throw new InvalidProgramException(), }; } #endregion protected override PointsToAnalysisData MergeAnalysisData(PointsToAnalysisData value1, PointsToAnalysisData value2) => _pointsToAnalysisDomain.Merge(value1, value2); protected override PointsToAnalysisData MergeAnalysisDataForBackEdge(PointsToAnalysisData value1, PointsToAnalysisData value2, BasicBlock forBlock) => _pointsToAnalysisDomain.MergeAnalysisDataForBackEdge(value1, value2, GetChildAnalysisEntities, ResetAbstractValueIfTracked, IsDisposable); protected override void UpdateValuesForAnalysisData(PointsToAnalysisData targetAnalysisData) => UpdateValuesForAnalysisData(targetAnalysisData.CoreAnalysisData, CurrentAnalysisData.CoreAnalysisData); protected override PointsToAnalysisData GetClonedAnalysisData(PointsToAnalysisData analysisData) => (PointsToAnalysisData)analysisData.Clone(); public override PointsToAnalysisData GetEmptyAnalysisData() => new(IsDisposable); protected override PointsToAnalysisData GetExitBlockOutputData(PointsToAnalysisResult analysisResult) => new(analysisResult.ExitBlockOutput.Data, IsDisposable); protected override void ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(PointsToAnalysisData dataAtException, ThrownExceptionInfo throwBranchWithExceptionType) => ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(dataAtException.CoreAnalysisData, CurrentAnalysisData.CoreAnalysisData, throwBranchWithExceptionType); protected override void AssertValidAnalysisData(PointsToAnalysisData analysisData) => AssertValidPointsToAnalysisData(analysisData); protected override bool Equals(PointsToAnalysisData value1, PointsToAnalysisData value2) => value1.Equals(value2); protected override void ApplyInterproceduralAnalysisResultCore(PointsToAnalysisData resultData) { ApplyInterproceduralAnalysisResultHelper(resultData.CoreAnalysisData); AssertValidPointsToAnalysisData(CurrentAnalysisData); } protected override PointsToAnalysisData GetTrimmedCurrentAnalysisData(IEnumerable<AnalysisEntity> withEntities) { var trimmedData = GetTrimmedCurrentAnalysisDataHelper(withEntities, CurrentAnalysisData.CoreAnalysisData, SetAbstractValueCore); AssertValidPointsToAnalysisData(trimmedData); return trimmedData; } protected override PointsToAnalysisData GetInitialInterproceduralAnalysisData( IMethodSymbol invokedMethod, (AnalysisEntity? Instance, PointsToAbstractValue PointsToValue)? invocationInstance, (AnalysisEntity Instance, PointsToAbstractValue PointsToValue)? thisOrMeInstanceForCaller, ImmutableDictionary<IParameterSymbol, ArgumentInfo<PointsToAbstractValue>> argumentValuesMap, IDictionary<AnalysisEntity, PointsToAbstractValue>? pointsToValues, IDictionary<AnalysisEntity, CopyAbstractValue>? copyValues, IDictionary<AnalysisEntity, ValueContentAbstractValue>? valueContentValues, bool isLambdaOrLocalFunction, bool hasParameterWithDelegateType) { pointsToValues = CurrentAnalysisData.CoreAnalysisData; var initialAnalysisData = base.GetInitialInterproceduralAnalysisData(invokedMethod, invocationInstance, thisOrMeInstanceForCaller, argumentValuesMap, pointsToValues, copyValues, valueContentValues, isLambdaOrLocalFunction, hasParameterWithDelegateType); AssertValidPointsToAnalysisData(initialAnalysisData); return initialAnalysisData; } private void HandleEscapingOperation(IOperation escapingOperation, IOperation escapedInstance) { HandleEscapingOperation(escapingOperation, escapedInstance, _escapedOperationLocationsBuilder); } private void HandleEscapingOperation(IOperation escapingOperation, IOperation escapedInstance, PooledDictionary<IOperation, ImmutableHashSet<AbstractLocation>.Builder> builder) { PointsToAbstractValue escapedInstancePointsToValue = GetPointsToAbstractValue(escapedInstance); if (escapedInstancePointsToValue.Kind == PointsToAbstractValueKind.KnownLValueCaptures) { foreach (var capturedOperation in escapedInstancePointsToValue.LValueCapturedOperations) { HandleEscapingOperation(escapingOperation, capturedOperation, builder); } return; } AnalysisEntityFactory.TryCreate(escapedInstance, out var escapedEntityOpt); HandleEscapingLocations(escapingOperation, builder, escapedEntityOpt, escapedInstancePointsToValue); } private void HandleEscapingLocations<TKey>( TKey key, PooledDictionary<TKey, ImmutableHashSet<AbstractLocation>.Builder> escapedLocationsBuilder, AnalysisEntity? escapedEntity, PointsToAbstractValue escapedInstancePointsToValue) where TKey : class { // Start by clearing escaped locations from previous flow analysis iterations. if (escapedLocationsBuilder.TryGetValue(key, out var builder)) { builder.Clear(); } HandleEscapingLocations(key, escapedLocationsBuilder, escapedInstancePointsToValue); // For value type entities, we also need to handle escaping the locations for child entities. if (escapedEntity?.Type.HasValueCopySemantics() == true) { HandleEscapingLocations(key, escapedLocationsBuilder, escapedEntity.InstanceLocation); } } private void HandleEscapingLocations<TKey>( TKey key, PooledDictionary<TKey, ImmutableHashSet<AbstractLocation>.Builder> escapedLocationsBuilder, PointsToAbstractValue pointsToValueOfEscapedInstance) where TKey : class { if (pointsToValueOfEscapedInstance.Locations.IsEmpty || pointsToValueOfEscapedInstance == PointsToAbstractValue.NoLocation || pointsToValueOfEscapedInstance == PointsToAbstractValue.NullLocation) { return; } if (!escapedLocationsBuilder.TryGetValue(key, out var builder)) { builder = ImmutableHashSet.CreateBuilder<AbstractLocation>(); escapedLocationsBuilder.Add(key, builder); } HandleEscapingLocations(pointsToValueOfEscapedInstance, builder); foreach (var childEntity in GetChildAnalysisEntities(pointsToValueOfEscapedInstance)) { var pointsToValueOfEscapedChild = GetAbstractValue(childEntity); HandleEscapingLocations(pointsToValueOfEscapedChild, builder); } if (TryGetTaskWrappedValue(pointsToValueOfEscapedInstance, out var wrappedValue)) { HandleEscapingLocations(key, escapedLocationsBuilder, wrappedValue); } } private void HandleEscapingLocations(PointsToAbstractValue pointsToValueOfEscapedInstance, ImmutableHashSet<AbstractLocation>.Builder builder) { foreach (var escapedLocation in pointsToValueOfEscapedInstance.Locations) { // Only escape locations associated with creations. // We can expand this for more cases in future if need arises. if (escapedLocation.Creation != null) { builder.Add(escapedLocation); } } MarkEscapedLambdasAndLocalFunctions(pointsToValueOfEscapedInstance); } private void HandlePossibleEscapingForAssignment(IOperation target, IOperation value, IOperation operation) { // FxCop compat: The object assigned to a field or a property or an array element is considered escaped. // TODO: Perform better analysis for array element assignments as we already track element locations. // https://github.com/dotnet/roslyn-analyzers/issues/1577 if (target is IMemberReferenceOperation || target.Kind == OperationKind.ArrayElementReference) { HandleEscapingOperation(operation, value); } } protected override void SetAbstractValueForAssignment(IOperation target, IOperation? assignedValueOperation, PointsToAbstractValue assignedValue, bool mayBeAssignment = false) { base.SetAbstractValueForAssignment(target, assignedValueOperation, assignedValue, mayBeAssignment); if (assignedValueOperation != null) { HandlePossibleEscapingForAssignment(target, assignedValueOperation, assignedValueOperation); } } protected override void SetAbstractValueForArrayElementInitializer(IArrayCreationOperation arrayCreation, ImmutableArray<AbstractIndex> indices, ITypeSymbol elementType, IOperation initializer, PointsToAbstractValue value) { base.SetAbstractValueForArrayElementInitializer(arrayCreation, indices, elementType, initializer, value); // We use the array initializer as the escaping operation instead of arrayCreation // to ensure we have a unique escaping operation key for each initializer. HandleEscapingOperation(initializer, initializer); } #region Visitor methods public override PointsToAbstractValue DefaultVisit(IOperation operation, object? argument) { _ = base.DefaultVisit(operation, argument); // Special handling for: // 1. Null value: NullLocation // 2. Constants and value types do not point to any location. if (operation.ConstantValue.HasValue) { if (operation.Type == null || operation.ConstantValue.Value == null) { return PointsToAbstractValue.NullLocation; } else { return PointsToAbstractValue.NoLocation; } } else if (operation.Type.IsNonNullableValueType()) { return PointsToAbstractValue.NoLocation; } return ValueDomain.UnknownOrMayBeValue; } public override PointsToAbstractValue VisitIsType(IIsTypeOperation operation, object? argument) { _ = base.VisitIsType(operation, argument); return PointsToAbstractValue.NoLocation; } public override PointsToAbstractValue VisitInstanceReference(IInstanceReferenceOperation operation, object? argument) { _ = base.VisitInstanceReference(operation, argument); IOperation? currentInstanceOperation = operation.GetInstance(IsInsideAnonymousObjectInitializer); var value = currentInstanceOperation != null ? GetCachedAbstractValue(currentInstanceOperation) : ThisOrMePointsToAbstractValue; Debug.Assert(value.NullState == NullAbstractValue.NotNull || DataFlowAnalysisContext.InterproceduralAnalysisData != null); return value; } private PointsToAbstractValue VisitTypeCreationWithArgumentsAndInitializer<TOperation>( TOperation operation, object? argument, Func<TOperation, object?, PointsToAbstractValue?> baseVisit) where TOperation : IOperation { AbstractLocation location = AbstractLocation.CreateAllocationLocation(operation, operation.Type!, DataFlowAnalysisContext); var pointsToAbstractValue = PointsToAbstractValue.Create(location, mayBeNull: false); CacheAbstractValue(operation, pointsToAbstractValue); _ = baseVisit(operation, argument); return pointsToAbstractValue; } public override PointsToAbstractValue VisitObjectCreation(IObjectCreationOperation operation, object? argument) { return VisitTypeCreationWithArgumentsAndInitializer(operation, argument, base.VisitObjectCreation); } public override PointsToAbstractValue VisitDynamicObjectCreation(IDynamicObjectCreationOperation operation, object? argument) { return VisitTypeCreationWithArgumentsAndInitializer(operation, argument, base.VisitDynamicObjectCreation); } public override PointsToAbstractValue VisitAnonymousObjectCreation(IAnonymousObjectCreationOperation operation, object? argument) { return VisitTypeCreationWithArgumentsAndInitializer(operation, argument, base.VisitAnonymousObjectCreation); } public override PointsToAbstractValue VisitReDimClause(IReDimClauseOperation operation, object? argument) { if (operation.Operand.Type == null) { return base.VisitReDimClause(operation, argument)!; } AbstractLocation location = AbstractLocation.CreateAllocationLocation(operation, operation.Operand.Type, DataFlowAnalysisContext); var pointsToAbstractValue = PointsToAbstractValue.Create(location, mayBeNull: false); CacheAbstractValue(operation, pointsToAbstractValue); _ = base.VisitReDimClause(operation, argument); return pointsToAbstractValue; } public override PointsToAbstractValue VisitTuple(ITupleOperation operation, object? argument) { PointsToAbstractValue pointsToAbstractValue; if (operation.Type.GetUnderlyingValueTupleTypeOrThis() is { } type) { AbstractLocation location = AbstractLocation.CreateAllocationLocation(operation, type, DataFlowAnalysisContext); pointsToAbstractValue = PointsToAbstractValue.Create(location, mayBeNull: false); } else { pointsToAbstractValue = PointsToAbstractValue.Unknown; } CacheAbstractValue(operation, pointsToAbstractValue); _ = base.VisitTuple(operation, argument); return pointsToAbstractValue; } public override PointsToAbstractValue VisitDelegateCreation(IDelegateCreationOperation operation, object? argument) { _ = base.VisitDelegateCreation(operation, argument); if (operation.Type is null) return PointsToAbstractValue.Unknown; AbstractLocation location = AbstractLocation.CreateAllocationLocation(operation, operation.Type, DataFlowAnalysisContext); return PointsToAbstractValue.Create(location, mayBeNull: false); } public override PointsToAbstractValue VisitTypeParameterObjectCreation(ITypeParameterObjectCreationOperation operation, object? argument) { return VisitTypeCreationWithArgumentsAndInitializer(operation, argument, base.VisitTypeParameterObjectCreation); } public override PointsToAbstractValue VisitArrayInitializer(IArrayInitializerOperation operation, object? argument) { _ = base.VisitArrayInitializer(operation, argument); // We should have created a new PointsTo value for the associated array creation operation in non-error code. // Bail out otherwise. var arrayCreation = operation.GetAncestor<IArrayCreationOperation>(OperationKind.ArrayCreation); return arrayCreation != null ? GetCachedAbstractValue(arrayCreation) : ValueDomain.UnknownOrMayBeValue; } public override PointsToAbstractValue VisitArrayCreation(IArrayCreationOperation operation, object? argument) { var pointsToAbstractValue = operation.Type != null ? PointsToAbstractValue.Create(AbstractLocation.CreateAllocationLocation(operation, operation.Type, DataFlowAnalysisContext), mayBeNull: false) : PointsToAbstractValue.Unknown; CacheAbstractValue(operation, pointsToAbstractValue); _ = VisitArray(operation.DimensionSizes, argument); var initializerValue = Visit(operation.Initializer, argument); Debug.Assert(operation.Initializer == null || initializerValue == pointsToAbstractValue); return pointsToAbstractValue; } public override PointsToAbstractValue VisitInterpolatedString(IInterpolatedStringOperation operation, object? argument) { _ = base.VisitInterpolatedString(operation, argument); return PointsToAbstractValue.NoLocation; } public override PointsToAbstractValue VisitBinaryOperatorCore(IBinaryOperation operation, object? argument) { _ = base.VisitBinaryOperatorCore(operation, argument); return PointsToAbstractValue.Unknown; } public override PointsToAbstractValue VisitSizeOf(ISizeOfOperation operation, object? argument) { _ = base.VisitSizeOf(operation, argument); return PointsToAbstractValue.NoLocation; } public override PointsToAbstractValue VisitTypeOf(ITypeOfOperation operation, object? argument) { _ = base.VisitTypeOf(operation, argument); return PointsToAbstractValue.NoLocation; } private PointsToAbstractValue VisitInvocationCommon(IOperation operation, IOperation? instance) { if (PointsToAnalysis.ShouldBeTracked(operation.Type, IsDisposable)) { if (TryGetInterproceduralAnalysisResult(operation, out var interproceduralResult)) { return interproceduralResult.ReturnValueAndPredicateKind!.Value.Value; } AbstractLocation location = AbstractLocation.CreateAllocationLocation(operation, operation.Type, DataFlowAnalysisContext); var pointsToAbstractValue = PointsToAbstractValue.Create(location, mayBeNull: true); return GetValueBasedOnInstanceOrReferenceValue(referenceOrInstance: instance, operation: operation, defaultValue: pointsToAbstractValue); } else { return PointsToAbstractValue.NoLocation; } } public override PointsToAbstractValue VisitInvocation_NonLambdaOrDelegateOrLocalFunction( IMethodSymbol method, IOperation? visitedInstance, ImmutableArray<IArgumentOperation> visitedArguments, bool invokedAsDelegate, IOperation originalOperation, PointsToAbstractValue defaultValue) { _ = base.VisitInvocation_NonLambdaOrDelegateOrLocalFunction(method, visitedInstance, visitedArguments, invokedAsDelegate, originalOperation, defaultValue); if (!visitedArguments.IsEmpty && method.IsCollectionAddMethod(CollectionNamedTypes)) { // FxCop compat: The object added to a collection is considered escaped. foreach (var argument in visitedArguments) { HandleEscapingOperation(argument, argument.Value); } } var value = VisitInvocationCommon(originalOperation, visitedInstance); if (IsSpecialMethodReturningNonNullValue(method, DataFlowAnalysisContext.WellKnownTypeProvider) && !TryGetInterproceduralAnalysisResult(originalOperation, out _)) { return value.MakeNonNull(); } return value; } private static bool IsSpecialMethodReturningNonNullValue(IMethodSymbol method, WellKnownTypeProvider wellKnownTypeProvider) => IsSpecialFactoryMethodReturningNonNullValue(method, wellKnownTypeProvider) || IsSpecialEmptyMember(method); /// <summary> /// Returns true if this special static factory method whose name starts with "Create", such that /// method's return type is not nullable, i.e. 'type?', and /// method's containing type is static OR a special type OR derives from or is same as the type of the field/property/method return. /// For example: class SomeType { static SomeType CreateXXX(...); } /// </summary> private static bool IsSpecialFactoryMethodReturningNonNullValue(IMethodSymbol method, WellKnownTypeProvider wellKnownTypeProvider) { if (!method.IsStatic || !method.Name.StartsWith("Create", StringComparison.Ordinal) || method.ReturnType.NullableAnnotation == NullableAnnotation.Annotated) { return false; } // 'Activator.CreateInstance' can return 'null'. // Even though it is nullable annotated to return 'object?', // the NullableAnnotation check above fails for VB, so we special case it here. if (method.Name.Equals("CreateInstance", StringComparison.Ordinal) && wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemActivator) is { } activatorType && activatorType.Equals(method.ContainingType)) { return false; } return method.ContainingType.IsStatic || method.ContainingType.SpecialType != SpecialType.None || method.ReturnType is INamedTypeSymbol namedType && method.ContainingType.DerivesFromOrImplementsAnyConstructionOf(namedType.OriginalDefinition); } /// <summary> /// Returns true if this special member symbol named "Empty", such that one of the following is true: /// 1. It is a static method with no parameters or /// 2. It is a static readonly property or /// 3. It is static readonly field /// and symbol's containing type is a special type or derives from or is same as the type of the field/property/method return. /// For example: /// 1. class SomeType { static readonly SomeType Empty; } /// 2. class SomeType { static readonly SomeType Empty { get; } } /// 3. class SomeType { static SomeType Empty(); } /// </summary> private static bool IsSpecialEmptyMember(ISymbol symbol) { return symbol.IsStatic && symbol.Name.Equals("Empty", StringComparison.Ordinal) && (symbol.IsReadOnlyFieldOrProperty() || symbol.Kind == SymbolKind.Method) && (symbol.ContainingType.SpecialType != SpecialType.None || symbol.GetMemberType() is INamedTypeSymbol namedType && symbol.ContainingType.DerivesFromOrImplementsAnyConstructionOf(namedType.OriginalDefinition)); } public override PointsToAbstractValue VisitInvocation_LocalFunction( IMethodSymbol localFunction, ImmutableArray<IArgumentOperation> visitedArguments, IOperation originalOperation, PointsToAbstractValue defaultValue) { _ = base.VisitInvocation_LocalFunction(localFunction, visitedArguments, originalOperation, defaultValue); return VisitInvocationCommon(originalOperation, instance: null); } public override PointsToAbstractValue VisitInvocation_Lambda( IFlowAnonymousFunctionOperation lambda, ImmutableArray<IArgumentOperation> visitedArguments, IOperation originalOperation, PointsToAbstractValue defaultValue) { _ = base.VisitInvocation_Lambda(lambda, visitedArguments, originalOperation, defaultValue); return VisitInvocationCommon(originalOperation, instance: null); } public override PointsToAbstractValue VisitDynamicInvocation(IDynamicInvocationOperation operation, object? argument) { _ = base.VisitDynamicInvocation(operation, argument); return VisitInvocationCommon(operation, operation.Operation); } private NullAbstractValue GetNullStateBasedOnInstanceOrReferenceValue(IOperation? referenceOrInstance, ITypeSymbol? operationType, NullAbstractValue defaultValue) { if (operationType.IsNonNullableValueType()) { return NullAbstractValue.NotNull; } NullAbstractValue referenceOrInstanceValue = referenceOrInstance != null ? GetNullAbstractValue(referenceOrInstance) : NullAbstractValue.NotNull; return referenceOrInstanceValue switch { NullAbstractValue.Invalid or NullAbstractValue.Null => referenceOrInstanceValue, _ => defaultValue, }; } private PointsToAbstractValue GetValueBasedOnInstanceOrReferenceValue(IOperation? referenceOrInstance, IOperation operation, PointsToAbstractValue defaultValue) { NullAbstractValue nullState = GetNullStateBasedOnInstanceOrReferenceValue(referenceOrInstance, operation.Type, defaultValue.NullState); return nullState switch { NullAbstractValue.NotNull => defaultValue.MakeNonNull(), NullAbstractValue.Null => defaultValue.MakeNull(), NullAbstractValue.Invalid => PointsToAbstractValue.Invalid, _ => defaultValue, }; } public override PointsToAbstractValue VisitFieldReference(IFieldReferenceOperation operation, object? argument) { var value = base.VisitFieldReference(operation, argument); // "class SomeType { static readonly SomeType Empty; }" if (IsSpecialEmptyMember(operation.Field) && value.NullState != NullAbstractValue.Null) { return value.MakeNonNull(); } return GetValueBasedOnInstanceOrReferenceValue(operation.Instance, operation, value); } public override PointsToAbstractValue VisitPropertyReference(IPropertyReferenceOperation operation, object? argument) { var value = base.VisitPropertyReference(operation, argument); // "class SomeType { static SomeType Empty { get; } }" if (IsSpecialEmptyMember(operation.Property) && value.NullState != NullAbstractValue.Null) { return value.MakeNonNull(); } return GetValueBasedOnInstanceOrReferenceValue(operation.Instance, operation, value); } public override PointsToAbstractValue VisitDynamicMemberReference(IDynamicMemberReferenceOperation operation, object? argument) { var value = base.VisitDynamicMemberReference(operation, argument); return GetValueBasedOnInstanceOrReferenceValue(operation.Instance, operation, value); } public override PointsToAbstractValue VisitMethodReference(IMethodReferenceOperation operation, object? argument) { var value = base.VisitMethodReference(operation, argument); return GetValueBasedOnInstanceOrReferenceValue(operation.Instance, operation, value); } public override PointsToAbstractValue VisitEventReference(IEventReferenceOperation operation, object? argument) { var value = base.VisitEventReference(operation, argument); return GetValueBasedOnInstanceOrReferenceValue(operation.Instance, operation, value); } public override PointsToAbstractValue VisitArrayElementReference(IArrayElementReferenceOperation operation, object? argument) { var value = base.VisitArrayElementReference(operation, argument); return GetValueBasedOnInstanceOrReferenceValue(operation.ArrayReference, operation, value); } public override PointsToAbstractValue VisitDynamicIndexerAccess(IDynamicIndexerAccessOperation operation, object? argument) { var value = base.VisitDynamicIndexerAccess(operation, argument)!; return GetValueBasedOnInstanceOrReferenceValue(operation.Operation, operation, value); } public override PointsToAbstractValue VisitConversion(IConversionOperation operation, object? argument) { var value = base.VisitConversion(operation, argument); if (operation.OperatorMethod != null) { // Conservatively handle user defined conversions as escaping operations. HandleEscapingOperation(operation, operation.Operand); return value; } ConversionInference? inference = null; if (value.NullState == NullAbstractValue.NotNull) { if (TryInferConversion(operation, out var conversionInference)) { inference = conversionInference; value = InferConversionCommon(conversionInference, value); } else { value = value.MakeMayBeNull(); } } return HandleBoxingUnboxing(value, operation, inference ?? ConversionInference.Create(operation)); } public override PointsToAbstractValue GetAssignedValueForPattern(IIsPatternOperation operation, PointsToAbstractValue operandValue) { var value = base.GetAssignedValueForPattern(operation, operandValue); ConversionInference? inference = null; if (operandValue.NullState == NullAbstractValue.NotNull && PointsToAnalysis.ShouldBeTracked(operation.Value.Type, IsDisposable)) { if (TryInferConversion(operation, out var conversionInference)) { inference = conversionInference; value = InferConversionCommon(conversionInference, operandValue); } else { value = operandValue.MakeMayBeNull(); } } return HandleBoxingUnboxing(value, operation, inference ?? ConversionInference.Create(operation)); } private static PointsToAbstractValue InferConversionCommon(ConversionInference inference, PointsToAbstractValue operandValue) { Debug.Assert(!inference.AlwaysSucceed || !inference.AlwaysFail); if (inference.AlwaysFail) { return operandValue.MakeNull(); } else if (inference.IsTryCast && !inference.AlwaysSucceed) { // TryCast which may or may not succeed. return operandValue.MakeMayBeNull(); } return operandValue; } private PointsToAbstractValue HandleBoxingUnboxing( PointsToAbstractValue value, IOperation operation, ConversionInference inference) { if (inference.IsBoxing && operation.Type != null) { Debug.Assert(!inference.IsUnboxing); var location = AbstractLocation.CreateAllocationLocation(operation, operation.Type, DataFlowAnalysisContext); return PointsToAbstractValue.Create(location, mayBeNull: false); } else if (inference.IsUnboxing && operation.Type.IsNonNullableValueType()) { return PointsToAbstractValue.NoLocation; } else { return value; } } public override PointsToAbstractValue VisitFlowCapture(IFlowCaptureOperation operation, object? argument) { var value = base.VisitFlowCapture(operation, argument); if (IsLValueFlowCapture(operation) && AnalysisEntityFactory.TryCreate(operation, out var flowCaptureEntity)) { value = PointsToAbstractValue.Create(operation.Value); SetAbstractValue(flowCaptureEntity, value); } return value; } public override PointsToAbstractValue VisitFlowCaptureReference(IFlowCaptureReferenceOperation operation, object? argument) { var value = base.VisitFlowCaptureReference(operation, argument); if (IsLValueFlowCaptureReference(operation) && AnalysisEntityFactory.TryCreate(operation, out var flowCaptureEntity)) { return GetAbstractValue(flowCaptureEntity); } return value; } public override PointsToAbstractValue ComputeValueForCompoundAssignment(ICompoundAssignmentOperation operation, PointsToAbstractValue targetValue, PointsToAbstractValue assignedValue, ITypeSymbol? targetType, ITypeSymbol? assignedValueType) { if (targetValue.Kind == PointsToAbstractValueKind.KnownLValueCaptures) { // Flow captures can never be re-assigned, so reuse the target value. return targetValue; } return base.ComputeValueForCompoundAssignment(operation, targetValue, assignedValue, targetType, assignedValueType); } protected override PointsToAbstractValue VisitAssignmentOperation(IAssignmentOperation operation, object? argument) { var value = base.VisitAssignmentOperation(operation, argument); HandlePossibleEscapingForAssignment(operation.Target, operation.Value, operation); return value; } public override PointsToAbstractValue VisitDeclarationExpression(IDeclarationExpressionOperation operation, object? argument) { return Visit(operation.Expression, argument); } public override PointsToAbstractValue VisitCaughtException(ICaughtExceptionOperation operation, object? argument) { _ = base.VisitCaughtException(operation, argument); return PointsToAbstractValue.UnknownNotNull; } #endregion } } } |