Skip to content

[DAM analyzer] Add basic dataflow analysis #2360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// 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 ILLink.RoslynAnalyzer.TrimAnalysis;
using ILLink.Shared.DataFlow;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.FlowAnalysis;
using Microsoft.CodeAnalysis.Operations;

namespace ILLink.RoslynAnalyzer.DataFlow
{
// Visitor which tracks the values of locals in a block. It provides extension points that get called
// whenever a value that comes from a tracked local reference flows into one of the following:
// - field
// - parameter
// - method return
public abstract class LocalDataFlowVisitor<TValue, TValueLattice> : OperationVisitor<LocalState<TValue>, TValue>,
ITransfer<BlockProxy, LocalState<TValue>, LocalStateLattice<TValue, TValueLattice>>
where TValue : IEquatable<TValue>
where TValueLattice : ILattice<TValue>
{
protected readonly LocalStateLattice<TValue, TValueLattice> LocalStateLattice;

protected readonly OperationBlockAnalysisContext Context;

protected TValue TopValue => LocalStateLattice.Lattice.ValueLattice.Top;

public LocalDataFlowVisitor (LocalStateLattice<TValue, TValueLattice> lattice, OperationBlockAnalysisContext context) =>
(LocalStateLattice, Context) = (lattice, context);

public void Transfer (BlockProxy block, LocalState<TValue> state)
{
foreach (IOperation operation in block.Block.Operations)
Visit (operation, state);

// Blocks may end with a BranchValue computation. Visit the BranchValue operation after all others.
IOperation? branchValueOperation = block.Block.BranchValue;
if (branchValueOperation != null) {
var branchValue = Visit (branchValueOperation, state);

// BranchValue may represent a value used in a conditional branch to the ConditionalSuccessor - if so, we are done.

// If not, the BranchValue represents a return or throw value associated with the FallThroughSuccessor of this block.
// (ConditionalSuccessor == null iff ConditionKind == None).
if (block.Block.ConditionKind == ControlFlowConditionKind.None) {
// This means it's a return value or throw value associated with the fall-through successor.

// Return statements with return values are represented in the control flow graph as
// a branch value operation that computes the return value. The return operation itself is the parent
// of the branch value operation.

// Get the actual "return Foo;" operation (not the branch value operation "Foo").
// This should be used as the location of the warning. This is important to provide the right
// warning location and because warnings are disambiguated based on the operation.
var parentSyntax = branchValueOperation.Syntax.Parent;
if (parentSyntax == null)
throw new InvalidOperationException ();

var parentOperation = Context.Compilation.GetSemanticModel (branchValueOperation.Syntax.SyntaxTree).GetOperation (parentSyntax);

// Analyzer doesn't support exceptional control-flow:
// https://github.com/dotnet/linker/issues/2273
if (parentOperation is IThrowOperation)
throw new NotImplementedException ();

if (parentOperation is not IReturnOperation returnOperation)
throw new InvalidOperationException ();

HandleReturnValue (branchValue, returnOperation);
}
}
}

public abstract void HandleAssignment (TValue source, TValue target, IOperation operation);

// This is called to handle instance method invocations, where "receiver" is the
// analyzed value for the object on which the instance method is called.
public abstract void HandleReceiverArgument (TValue receiver, IInvocationOperation operation);

public abstract void HandleArgument (TValue argument, IArgumentOperation operation);

// This takes an IOperation rather than an IReturnOperation because the return value
// may (must?) come from BranchValue of an operation whose FallThroughSuccessor is the exit block.
public abstract void HandleReturnValue (TValue returnValue, IOperation operation);

// Override with a non-nullable return value to prevent some warnings.
// The interface constraint on TValue ensures that it's not a nullable type, so this is safe as long
// as no overrides return default(TValue).
public override TValue Visit (IOperation? operation, LocalState<TValue> argument) => operation != null ? operation.Accept (this, argument)! : TopValue;

internal virtual TValue VisitNoneOperation (IOperation operation, LocalState<TValue> argument) => TopValue;

// The default visitor preserves the local state. Any unimplemented operations will not
// have their effects reflected in the tracked state.
public override TValue DefaultVisit (IOperation operation, LocalState<TValue> state) => TopValue;

public override TValue VisitLocalReference (ILocalReferenceOperation operation, LocalState<TValue> state)
{
return state.Get (new LocalKey (operation.Local));
}

public override TValue VisitSimpleAssignment (ISimpleAssignmentOperation operation, LocalState<TValue> state)
{
var targetValue = Visit (operation.Target, state);
var value = Visit (operation.Value, state);
switch (operation.Target) {
case ILocalReferenceOperation localRef:
state.Set (new LocalKey (localRef.Local), value);
break;
case IFieldReferenceOperation:
case IParameterReferenceOperation:
// Extension point for assignments to "interesting" targets.
// Doesn't get called for assignments to locals, which are handled above.
HandleAssignment (value, targetValue, operation);
break;
case IPropertyReferenceOperation:
// TODO: when setting a property in an attribute, target is an IPropertyReference.
case IArrayElementReferenceOperation:
// TODO
break;
// TODO: DiscardOperation
default:
throw new NotImplementedException (operation.Target.GetType ().ToString ());
}
return value;
}

// Similar to VisitLocalReference
public override TValue VisitFlowCaptureReference (IFlowCaptureReferenceOperation operation, LocalState<TValue> state)
{
return state.Get (new LocalKey (operation.Id));
}

// Similar to VisitSimpleAssignment when assigning to a local, but for values which are captured without a
// corresponding local variable. The "flow capture" is like a local assignment, and the "flow capture reference"
// is like a local reference.
public override TValue VisitFlowCapture (IFlowCaptureOperation operation, LocalState<TValue> state)
{
TValue value = Visit (operation.Value, state);
state.Set (new LocalKey (operation.Id), value);
return value;
}

public override TValue VisitExpressionStatement (IExpressionStatementOperation operation, LocalState<TValue> state)
{
Visit (operation.Operation, state);
return TopValue;
}

public override TValue VisitInvocation (IInvocationOperation operation, LocalState<TValue> state)
{
if (operation.Instance != null) {
var instanceValue = Visit (operation.Instance, state);
HandleReceiverArgument (instanceValue, operation);
}

foreach (var argument in operation.Arguments)
VisitArgument (argument, state);

return TopValue;
}

public override TValue VisitArgument (IArgumentOperation operation, LocalState<TValue> state)
{
var value = Visit (operation.Value, state);
HandleArgument (value, operation);
return value;
}

public override TValue VisitReturn (IReturnOperation operation, LocalState<TValue> state)
{
if (operation.ReturnedValue != null) {
var value = Visit (operation.ReturnedValue, state);
HandleReturnValue (value, operation);
return value;
}

return TopValue;
}

public override TValue VisitConversion (IConversionOperation operation, LocalState<TValue> state)
{
var operandValue = Visit (operation.Operand, state);
return operation.OperatorMethod == null ? operandValue : TopValue;
}
}
}
68 changes: 68 additions & 0 deletions src/ILLink.RoslynAnalyzer/DataFlow/LocalStateLattice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 ILLink.Shared.DataFlow;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis;

namespace ILLink.RoslynAnalyzer.DataFlow
{
public readonly struct LocalKey : IEquatable<LocalKey>
{
readonly ILocalSymbol? Local;

readonly CaptureId? CaptureId;

public LocalKey (ILocalSymbol symbol) => (Local, CaptureId) = (symbol, null);

public LocalKey (CaptureId captureId) => (Local, CaptureId) = (null, captureId);

public bool Equals (LocalKey other) => SymbolEqualityComparer.Default.Equals (Local, other.Local) &&
(CaptureId?.Equals (other.CaptureId) ?? other.CaptureId == null);

public override string ToString ()
{
if (Local != null)
return Local.ToString ();
return $"capture {CaptureId.GetHashCode ().ToString ().Substring (0, 3)}";
}
}

// Wrapper class exists purely to substitute a concrete LocalKey for TKey of DefaultValueDictionary
// This is a class because it is passed to the transfer functions and expected to be modified in a
// way that is visible to the caller.
public class LocalState<TValue> : IEquatable<LocalState<TValue>>
where TValue : IEquatable<TValue>
{
public DefaultValueDictionary<LocalKey, TValue> Dictionary;

public LocalState (DefaultValueDictionary<LocalKey, TValue> dictionary) => Dictionary = dictionary;

public bool Equals (LocalState<TValue> other) => Dictionary.Equals (other.Dictionary);

public TValue Get (LocalKey key) => Dictionary.Get (key);

public void Set (LocalKey key, TValue value) => Dictionary.Set (key, value);

public override string ToString () => Dictionary.ToString ();
}

// Wrapper struct exists purely to substitute a concrete LocalKey for TKey of DictionaryLattice
public readonly struct LocalStateLattice<TValue, TValueLattice> : ILattice<LocalState<TValue>>
where TValue : IEquatable<TValue>
where TValueLattice : ILattice<TValue>
{
public readonly DictionaryLattice<LocalKey, TValue, TValueLattice> Lattice;

public LocalStateLattice (TValueLattice valueLattice)
{
Lattice = new DictionaryLattice<LocalKey, TValue, TValueLattice> (valueLattice);
Top = new (Lattice.Top);
}

public LocalState<TValue> Top { get; }

public LocalState<TValue> Meet (LocalState<TValue> left, LocalState<TValue> right) => new (Lattice.Meet (left.Dictionary, right.Dictionary));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace System.Diagnostics.CodeAnalysis
/// bitwise combination of its member values.
/// </summary>
[Flags]
internal enum DynamicallyAccessedMemberTypes
public enum DynamicallyAccessedMemberTypes
{
/// <summary>
/// Specifies no members.
Expand Down
Loading