Skip to content

Commit

Permalink
Merge pull request #6776 from mavasani/ArrayAccess
Browse files Browse the repository at this point in the history
Improve handling of indexed array access in flow analysis
  • Loading branch information
mavasani authored Jul 18, 2023
2 parents 55eb2c2 + 2a58a22 commit 20690b4
Show file tree
Hide file tree
Showing 14 changed files with 164 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
abstract Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.AbstractDataFlowAnalysisContext<TAnalysisData, TAnalysisContext, TAnalysisResult, TAbstractAnalysisValue>.ComputeEqualsByHashCodeParts(Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.AbstractDataFlowAnalysisContext<TAnalysisData!, TAnalysisContext!, TAnalysisResult!, TAbstractAnalysisValue>! obj) -> bool
abstract Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.AbstractDataFlowAnalysisContext<TAnalysisData, TAnalysisContext, TAnalysisResult, TAbstractAnalysisValue>.ComputeHashCodePartsSpecific(ref Analyzer.Utilities.RoslynHashCode hashCode) -> void
abstract Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.AnalysisEntityBasedPredicateAnalysisData<TValue>.ValueDomain.get -> Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.AbstractValueDomain<TValue>!
abstract Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CacheBasedEquatable<T>.ComputeEqualsByHashCodeParts(Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CacheBasedEquatable<T!>! obj) -> bool
abstract Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CacheBasedEquatable<T>.ComputeHashCodeParts(ref Analyzer.Utilities.RoslynHashCode hashCode) -> void
abstract Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.DataFlowOperationVisitor<TAnalysisData, TAnalysisContext, TAnalysisResult, TAbstractAnalysisValue>.GetAbstractDefaultValue(Microsoft.CodeAnalysis.ITypeSymbol? type) -> TAbstractAnalysisValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6762,6 +6762,103 @@ public void M(S[] arguments)
}");
}

[Trait(Traits.DataflowAnalysis, Traits.Dataflow.NullAnalysis)]
[Trait(Traits.DataflowAnalysis, Traits.Dataflow.PointsToAnalysis)]
[Fact, WorkItem(6616, "https://github.com/dotnet/roslyn-analyzers/issues/6616")]
public async Task IndexedArrayAccessWithConstantsAndNonConstants_NoDiagnosticsAsync()
{
await new VerifyCS.Test
{
TestCode = @"
#nullable enable
using System;
namespace Test
{
public static class Test
{
public static void Method1()
{
// default
var objPlayers = new object?[] { null };
for (var idx = 0; idx < 1; idx++)
{
objPlayers[idx] = $""{idx}"";
}
// validate
if (objPlayers[0] is null) // CA1508: Always true (should probably be unknown)
{
}
}
public static void Method2(object?[] objPlayers, int idx)
{
objPlayers[0] = null;
objPlayers[idx] = 0;
if (objPlayers[0] is null) // CA1508: Always true (should probably be unknown)
{
}
}
}
}",
LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8,
}.RunAsync();
}

[Trait(Traits.DataflowAnalysis, Traits.Dataflow.NullAnalysis)]
[Trait(Traits.DataflowAnalysis, Traits.Dataflow.PointsToAnalysis)]
[Fact, WorkItem(6616, "https://github.com/dotnet/roslyn-analyzers/issues/6616")]
public async Task IndexedArrayAccessWithConstantsAndNonConstants_DiagnosticsAsync()
{
await new VerifyCS.Test
{
TestCode = @"
#nullable enable
using System;
namespace Test
{
public static class Test
{
public static void Method1(object?[][] objPlayers, int idx)
{
objPlayers[0][0] = null;
for (idx = 0; idx < 1; idx++)
{
objPlayers[1][idx] = 0;
}
if (objPlayers[0][0] is null) // CA1508: Always true
{
}
}
public static void Method2(object?[][] objPlayers, int idx)
{
objPlayers[0][0] = null;
objPlayers[1][idx] = 0;
if (objPlayers[0][0] is null) // CA1508: Always true
{
}
}
}
}",
LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8,
ExpectedDiagnostics =
{
// /0/Test0.cs(19,17): warning CA1508: 'objPlayers[0][0] is null' is always 'true'. Remove or refactor the condition(s) to avoid dead code.
GetCSharpResultAt(19, 17, "objPlayers[0][0] is null", "true"),
// /0/Test0.cs(28,17): warning CA1508: 'objPlayers[0][0] is null' is always 'true'. Remove or refactor the condition(s) to avoid dead code.
GetCSharpResultAt(28, 17, "objPlayers[0][0] is null", "true"),
},
}.RunAsync();
}

[Trait(Traits.DataflowAnalysis, Traits.Dataflow.NullAnalysis)]
[Fact]
public async Task ArrayInitializerNotParentedByArrayCreationAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CopyAnalysis
/// </summary>
public partial class CopyAnalysis : ForwardDataFlowAnalysis<CopyAnalysisData, CopyAnalysisContext, CopyAnalysisResult, CopyBlockAnalysisResult, CopyAbstractValue>
{
internal static readonly AbstractValueDomain<CopyAbstractValue> ValueDomainInstance = CopyAbstractValueDomain.Default;

private CopyAnalysis(CopyDataFlowOperationVisitor operationVisitor)
: base(operationVisitor.AnalysisDomain, operationVisitor)
{
Expand Down Expand Up @@ -43,7 +45,7 @@ private CopyAnalysis(CopyDataFlowOperationVisitor operationVisitor)
wellKnownTypeProvider, pointsToAnalysisKind, interproceduralAnalysisConfig,
interproceduralAnalysisPredicate, pessimisticAnalysis, performCopyAnalysis: false, exceptionPathsAnalysis) :
null;
var analysisContext = CopyAnalysisContext.Create(CopyAbstractValueDomain.Default, wellKnownTypeProvider,
var analysisContext = CopyAnalysisContext.Create(ValueDomainInstance, wellKnownTypeProvider,
cfg, owningSymbol, analyzerOptions, interproceduralAnalysisConfig, pessimisticAnalysis, exceptionPathsAnalysis, pointsToAnalysisResult,
TryGetOrComputeResultForAnalysisContext, interproceduralAnalysisPredicate);
return TryGetOrComputeResultForAnalysisContext(analysisContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CopyAnalysis
{
using static Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CopyAnalysis.CopyAnalysis;
using CoreCopyAnalysisData = DictionaryAnalysisData<AnalysisEntity, CopyAbstractValue>;

/// <summary>
Expand Down Expand Up @@ -38,6 +39,7 @@ private CopyAnalysisData(CopyAnalysisData data1, CopyAnalysisData data2, MapAbst
AssertValidCopyAnalysisData();
}

protected override AbstractValueDomain<CopyAbstractValue> ValueDomain => ValueDomainInstance;
public override AnalysisEntityBasedPredicateAnalysisData<CopyAbstractValue> Clone() => new CopyAnalysisData(this);

public override int Compare(AnalysisEntityBasedPredicateAnalysisData<CopyAbstractValue> other, MapAbstractDomain<AnalysisEntity, CopyAbstractValue> coreDataAnalysisDomain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public partial class PointsToAnalysis : ForwardDataFlowAnalysis<PointsToAnalysis
private sealed class PointsToAnalysisDomain : PredicatedAnalysisDataDomain<PointsToAnalysisData, PointsToAbstractValue>
{
public PointsToAnalysisDomain(DefaultPointsToValueGenerator defaultPointsToValueGenerator)
: base(new CorePointsToAnalysisDataDomain(defaultPointsToValueGenerator, PointsToAbstractValueDomainInstance))
: base(new CorePointsToAnalysisDataDomain(defaultPointsToValueGenerator, ValueDomainInstance))
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis
/// </summary>
public partial class PointsToAnalysis : ForwardDataFlowAnalysis<PointsToAnalysisData, PointsToAnalysisContext, PointsToAnalysisResult, PointsToBlockAnalysisResult, PointsToAbstractValue>
{
internal static readonly AbstractValueDomain<PointsToAbstractValue> PointsToAbstractValueDomainInstance = PointsToAbstractValueDomain.Default;
internal static readonly AbstractValueDomain<PointsToAbstractValue> ValueDomainInstance = PointsToAbstractValueDomain.Default;

private PointsToAnalysis(PointsToAnalysisDomain analysisDomain, PointsToDataFlowOperationVisitor operationVisitor)
: base(analysisDomain, operationVisitor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private PointsToAnalysisData(PointsToAnalysisData data1, PointsToAnalysisData da
AssertValidPointsToAnalysisData();
}

protected override AbstractValueDomain<PointsToAbstractValue> ValueDomain => PointsToAnalysis.ValueDomainInstance;
public override AnalysisEntityBasedPredicateAnalysisData<PointsToAbstractValue> Clone() => new PointsToAnalysisData(this);

public override int Compare(AnalysisEntityBasedPredicateAnalysisData<PointsToAbstractValue> other, MapAbstractDomain<AnalysisEntity, PointsToAbstractValue> coreDataAnalysisDomain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace Analyzer.Utilities.FlowAnalysis.Analysis.TaintedDataAnalysis

internal partial class TaintedDataAnalysis : ForwardDataFlowAnalysis<TaintedDataAnalysisData, TaintedDataAnalysisContext, TaintedDataAnalysisResult, TaintedDataBlockAnalysisResult, TaintedDataAbstractValue>
{
internal static readonly AbstractValueDomain<TaintedDataAbstractValue> ValueDomainInstance = TaintedDataAbstractValueDomain.Default;

private TaintedDataAnalysis(TaintedDataAnalysisDomain analysisDomain, TaintedDataOperationVisitor operationVisitor)
: base(analysisDomain, operationVisitor)
{
Expand Down Expand Up @@ -94,7 +96,7 @@ private TaintedDataAnalysis(TaintedDataAnalysisDomain analysisDomain, TaintedDat
}

TaintedDataAnalysisContext analysisContext = TaintedDataAnalysisContext.Create(
TaintedDataAbstractValueDomain.Default,
ValueDomainInstance,
wellKnownTypeProvider,
cfg,
containingMethod,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public TaintedDataAnalysisData(TaintedDataAnalysisData fromData, TaintedDataAnal
{
}

protected override AbstractValueDomain<TaintedDataAbstractValue> ValueDomain => TaintedDataAnalysis.ValueDomainInstance;
public override AnalysisEntityBasedPredicateAnalysisData<TaintedDataAbstractValue> Clone()
{
return new TaintedDataAnalysisData(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis
/// </summary>
public partial class ValueContentAnalysis : ForwardDataFlowAnalysis<ValueContentAnalysisData, ValueContentAnalysisContext, ValueContentAnalysisResult, ValueContentBlockAnalysisResult, ValueContentAbstractValue>
{
internal static readonly AbstractValueDomain<ValueContentAbstractValue> ValueDomainInstance = ValueContentAbstractValueDomain.Default;

private ValueContentAnalysis(ValueContentAnalysisDomain analysisDomain, ValueContentDataFlowOperationVisitor operationVisitor)
: base(analysisDomain, operationVisitor)
{
Expand Down Expand Up @@ -98,7 +100,7 @@ private ValueContentAnalysis(ValueContentAnalysisDomain analysisDomain, ValueCon
null;

var analysisContext = ValueContentAnalysisContext.Create(
ValueContentAbstractValueDomain.Default, wellKnownTypeProvider, cfg, owningSymbol, analyzerOptions,
ValueDomainInstance, wellKnownTypeProvider, cfg, owningSymbol, analyzerOptions,
interproceduralAnalysisConfig, pessimisticAnalysis, copyAnalysisResult,
pointsToAnalysisResult, TryGetOrComputeResultForAnalysisContext,
additionalSupportedValueTypes, getValueContentValueForAdditionalSupportedValueTypeOperation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal ValueContentAnalysisData(
{
}

protected override AbstractValueDomain<ValueContentAbstractValue> ValueDomain => ValueContentAnalysis.ValueDomainInstance;
public override AnalysisEntityBasedPredicateAnalysisData<ValueContentAbstractValue> Clone() => new ValueContentAnalysisData(this);

public override int Compare(AnalysisEntityBasedPredicateAnalysisData<ValueContentAbstractValue> other, MapAbstractDomain<AnalysisEntity, ValueContentAbstractValue> coreDataAnalysisDomain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public abstract partial class AbstractIndex : CacheBasedEquatable<AbstractIndex>
public static AbstractIndex Create(int index) => new ConstantValueIndex(index);
public static AbstractIndex Create(AnalysisEntity analysisEntity) => new AnalysisEntityBasedIndex(analysisEntity);
public static AbstractIndex Create(IOperation operation) => new OperationBasedIndex(operation);

internal bool IsConstant() => this is ConstantValueIndex;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public AnalysisEntity WithMergedInstanceLocation(AnalysisEntity analysisEntityTo
Debug.Assert(EqualsIgnoringInstanceLocation(analysisEntityToMerge));
Debug.Assert(!InstanceLocation.Equals(analysisEntityToMerge.InstanceLocation));

var mergedInstanceLocation = PointsToAnalysis.PointsToAnalysis.PointsToAbstractValueDomainInstance.Merge(InstanceLocation, analysisEntityToMerge.InstanceLocation);
var mergedInstanceLocation = PointsToAnalysis.PointsToAnalysis.ValueDomainInstance.Merge(InstanceLocation, analysisEntityToMerge.InstanceLocation);
return new AnalysisEntity(Symbol, Indices, InstanceReferenceOperationSyntax, CaptureId, mergedInstanceLocation, Type, Parent, IsThisOrMeInstance);
}

Expand Down Expand Up @@ -204,6 +204,9 @@ or PointsToAbstractValueKind.UnknownNull

public bool IsLValueFlowCaptureEntity => CaptureId.HasValue && CaptureId.Value.IsLValueFlowCapture;

internal AnalysisEntity WithIndices(ImmutableArray<AbstractIndex> indices)
=> new(Symbol, indices, InstanceReferenceOperationSyntax, CaptureId, InstanceLocation, Type, Parent, IsThisOrMeInstance);

public bool EqualsIgnoringInstanceLocation(AnalysisEntity? other)
{
// Perform fast equality checks first.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow
{
Expand Down Expand Up @@ -58,6 +59,7 @@ protected AnalysisEntityBasedPredicateAnalysisData(

public virtual bool HasAnyAbstractValue => CoreAnalysisData.Count > 0 || HasPredicatedData;

protected abstract AbstractValueDomain<TValue> ValueDomain { get; }
public abstract AnalysisEntityBasedPredicateAnalysisData<TValue> Clone();
public abstract AnalysisEntityBasedPredicateAnalysisData<TValue> WithMergedData(AnalysisEntityBasedPredicateAnalysisData<TValue> data, MapAbstractDomain<AnalysisEntity, TValue> coreDataAnalysisDomain);
public abstract int Compare(AnalysisEntityBasedPredicateAnalysisData<TValue> other, MapAbstractDomain<AnalysisEntity, TValue> coreDataAnalysisDomain);
Expand Down Expand Up @@ -100,6 +102,8 @@ public virtual void SetAbstractValue(AnalysisEntity key, TValue value)
}

CoreAnalysisData[key] = value;

ClearOverlappingAnalysisDataForIndexedEntity(key, value);
}

public void RemoveEntries(AnalysisEntity key)
Expand Down Expand Up @@ -159,6 +163,46 @@ public PredicateValueKind ApplyPredicatedDataForEntity(AnalysisEntity predicated

public void AddTrackedEntities(HashSet<AnalysisEntity> builder) => builder.UnionWith(CoreAnalysisData.Keys);

private void ClearOverlappingAnalysisDataForIndexedEntity(AnalysisEntity analysisEntity, TValue value)
{
if (!analysisEntity.Indices.Any(index => !index.IsConstant()))
{
return;
}

foreach (var entity in CoreAnalysisData.Keys)
{
if (entity.Indices.Length != analysisEntity.Indices.Length ||
entity == analysisEntity)
{
continue;
}

var canOverlap = true;
for (var i = 0; i < entity.Indices.Length; i++)
{
if (entity.Indices[i].IsConstant() &&
analysisEntity.Indices[i].IsConstant() &&
!entity.Indices[i].Equals(analysisEntity.Indices[i]))
{
canOverlap = false;
break;
}
}

if (canOverlap &&
entity.WithIndices(analysisEntity.Indices).Equals(analysisEntity) &&
CoreAnalysisData.TryGetValue(entity, out var existingValue))
{
var mergedValue = ValueDomain.Merge(value, existingValue);
if (!existingValue!.Equals(mergedValue))
{
SetAbstractValue(entity, mergedValue);
}
}
}
}

protected override void Dispose(bool disposing)
{
if (IsDisposed)
Expand Down

0 comments on commit 20690b4

Please sign in to comment.