Skip to content
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

[ILLink analyzer] Add branch analysis #94123

Merged
merged 13 commits into from
Nov 7, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -1194,32 +1194,32 @@ protected virtual void HandleStoreMethodReturnValue(MethodIL method, int offset,
switch (value)
{
case FieldReferenceValue fieldReferenceValue:
dereferencedValue = MultiValue.Meet(
dereferencedValue = MultiValue.Union(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed Meet on ValueSet to Union since I also added an Intersection method. I'm thinking it's most readable if we use set operation names for ValueSet, and the lattice operation names (Meet) when using ValueSetLattice. In the case of this method, I think the fact that DereferenceValue computes a union of dereferenced values for the items in the ValueSet has more to do with the semantics of the dereference operation and ValueSet, than with ValueSetLattice, so I think it makes sense to use Union here.

dereferencedValue,
CompilerGeneratedState.IsHoistedLocal(fieldReferenceValue.FieldDefinition)
? interproceduralState.GetHoistedLocal(new HoistedLocalKey(fieldReferenceValue.FieldDefinition))
: HandleGetField(methodBody, offset, fieldReferenceValue.FieldDefinition));
break;
case ParameterReferenceValue parameterReferenceValue:
dereferencedValue = MultiValue.Meet(
dereferencedValue = MultiValue.Union(
dereferencedValue,
GetMethodParameterValue(parameterReferenceValue.Parameter));
break;
case LocalVariableReferenceValue localVariableReferenceValue:
var valueBasicBlockPair = locals[localVariableReferenceValue.LocalIndex];
if (valueBasicBlockPair.HasValue)
dereferencedValue = MultiValue.Meet(dereferencedValue, valueBasicBlockPair.Value.Value);
dereferencedValue = MultiValue.Union(dereferencedValue, valueBasicBlockPair.Value.Value);
else
dereferencedValue = MultiValue.Meet(dereferencedValue, UnknownValue.Instance);
dereferencedValue = MultiValue.Union(dereferencedValue, UnknownValue.Instance);
break;
case ReferenceValue referenceValue:
throw new NotImplementedException($"Unhandled dereference of ReferenceValue of type {referenceValue.GetType().FullName}");
// Incomplete handling for ref values
case FieldValue fieldValue:
dereferencedValue = MultiValue.Meet(dereferencedValue, fieldValue);
dereferencedValue = MultiValue.Union(dereferencedValue, fieldValue);
break;
default:
dereferencedValue = MultiValue.Meet(dereferencedValue, value);
dereferencedValue = MultiValue.Union(dereferencedValue, value);
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ILLink.Shared.DataFlow;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FlowAnalysis;

using Predecessor = ILLink.Shared.DataFlow.IControlFlowGraph<
using ControlFlowBranch = ILLink.Shared.DataFlow.IControlFlowGraph<
ILLink.RoslynAnalyzer.DataFlow.BlockProxy,
ILLink.RoslynAnalyzer.DataFlow.RegionProxy
>.Predecessor;
>.ControlFlowBranch;

namespace ILLink.RoslynAnalyzer.DataFlow
{
Expand All @@ -21,12 +23,14 @@ namespace ILLink.RoslynAnalyzer.DataFlow
// any kind of value equality for different block instances. In practice
// this should be fine as long as we consistently use block instances from
// a single ControlFlowGraph.
public readonly record struct BlockProxy (BasicBlock Block)
public readonly record struct BlockProxy (BasicBlock Block) : IBlock<BlockProxy>
{
public override string ToString ()
{
return base.ToString () + $"[{Block.Ordinal}]";
}

public ConditionKind ConditionKind => (ConditionKind) Block.ConditionKind;
}

public readonly record struct RegionProxy (ControlFlowRegion Region) : IRegion<RegionProxy>
Expand All @@ -51,25 +55,41 @@ public override string ToString ()

public BlockProxy Entry => new BlockProxy (ControlFlowGraph.EntryBlock ());

public static ControlFlowBranch? CreateProxyBranch (Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch? branch)
{
if (branch == null)
return null;

var finallyRegions = ImmutableArray.CreateBuilder<RegionProxy> ();
foreach (var region in branch.FinallyRegions) {
Debug.Assert (region != null);
if (region == null)
continue;
finallyRegions.Add (new RegionProxy (region));
}

// Destination might be null in a 'throw' branch.
return new ControlFlowBranch (
new BlockProxy (branch.Source),
branch.Destination == null ? null : new BlockProxy (branch.Destination),
finallyRegions.ToImmutable (),
branch.IsConditionalSuccessor);
}

// This is implemented by getting predecessors of the underlying Roslyn BasicBlock.
// This is fine as long as the blocks come from the correct control-flow graph.
public IEnumerable<Predecessor> GetPredecessors (BlockProxy block)
public IEnumerable<ControlFlowBranch> GetPredecessors (BlockProxy block)
{
foreach (var predecessor in block.Block.Predecessors) {
if (predecessor.FinallyRegions.IsEmpty) {
yield return new Predecessor (new BlockProxy (predecessor.Source), ImmutableArray<RegionProxy>.Empty);
continue;
}
var finallyRegions = ImmutableArray.CreateBuilder<RegionProxy> ();
foreach (var region in predecessor.FinallyRegions) {
if (region == null)
throw new InvalidOperationException ();
finallyRegions.Add (new RegionProxy (region));
}
yield return new Predecessor (new BlockProxy (predecessor.Source), finallyRegions.ToImmutable ());
if (CreateProxyBranch (predecessor) is ControlFlowBranch branch)
yield return branch;
}
}

public ControlFlowBranch? GetConditionalSuccessor (BlockProxy block) => CreateProxyBranch (block.Block.ConditionalSuccessor);

public ControlFlowBranch? GetFallThroughSuccessor (BlockProxy block) => CreateProxyBranch (block.Block.FallThroughSuccessor);

public bool TryGetEnclosingTryOrCatchOrFilter (BlockProxy block, out RegionProxy tryOrCatchOrFilterRegion)
{
return TryGetTryOrCatchOrFilter (block.Block.EnclosingRegion, out tryOrCatchOrFilterRegion);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ILLink.Shared.DataFlow;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis;
using Microsoft.CodeAnalysis.Operations;
using ILLink.Shared.TypeSystemProxy;

using StateValue = ILLink.RoslynAnalyzer.DataFlow.LocalDataFlowState<
ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>,
ILLink.RoslynAnalyzer.DataFlow.FeatureContext,
ILLink.Shared.DataFlow.ValueSetLattice<ILLink.Shared.DataFlow.SingleValue>,
ILLink.RoslynAnalyzer.DataFlow.FeatureContextLattice
>;

namespace ILLink.RoslynAnalyzer.DataFlow
{
// Visits a conditional expression to optionally produce a 'FeatureChecksValue'
// (a set features that are checked to be enabled or disabled).
// The visitor takes a LocalDataFlowState as an argument, allowing for checks that
// depend on the current dataflow state.
public class FeatureChecksVisitor : OperationVisitor<StateValue, FeatureChecksValue?>
{
DataFlowAnalyzerContext _dataFlowAnalyzerContext;

public FeatureChecksVisitor (DataFlowAnalyzerContext dataFlowAnalyzerContext)
{
_dataFlowAnalyzerContext = dataFlowAnalyzerContext;
}

public override FeatureChecksValue? VisitArgument (IArgumentOperation operation, StateValue state)
{
return Visit (operation.Value, state);
}

public override FeatureChecksValue? VisitPropertyReference (IPropertyReferenceOperation operation, StateValue state)
{
foreach (var analyzer in _dataFlowAnalyzerContext.EnabledRequiresAnalyzers) {
if (analyzer.IsRequiresCheck (_dataFlowAnalyzerContext.Compilation, operation.Property)) {
return new FeatureChecksValue (analyzer.FeatureName);
}
}

return null;
}

public override FeatureChecksValue? VisitUnaryOperator (IUnaryOperation operation, StateValue state)
{
if (operation.OperatorKind is not UnaryOperatorKind.Not)
return null;

FeatureChecksValue? context = Visit (operation.Operand, state);
if (context == null)
return null;

return context.Value.Negate ();
}

public bool? GetLiteralBool (IOperation operation)
{
if (operation is not ILiteralOperation literal)
return null;

return GetConstantBool (literal.ConstantValue);
}

static bool? GetConstantBool (Optional<object?> constantValue)
{
if (!constantValue.HasValue || constantValue.Value is not bool value)
return null;

return value;
}

public override FeatureChecksValue? VisitBinaryOperator (IBinaryOperation operation, StateValue state)
{
bool expectEqual;
switch (operation.OperatorKind) {
case BinaryOperatorKind.Equals:
expectEqual = true;
break;
case BinaryOperatorKind.NotEquals:
expectEqual = false;
break;
default:
return null;
}

if (GetLiteralBool (operation.LeftOperand) is bool leftBool) {
if (Visit (operation.RightOperand, state) is not FeatureChecksValue rightValue)
return null;
return leftBool == expectEqual
? rightValue
: rightValue.Negate ();
}

if (GetLiteralBool (operation.RightOperand) is bool rightBool) {
if (Visit (operation.LeftOperand, state) is not FeatureChecksValue leftValue)
return null;
return rightBool == expectEqual
? leftValue
: leftValue.Negate ();
}

return null;
}

public override FeatureChecksValue? VisitIsPattern (IIsPatternOperation operation, StateValue state)
{
if (GetExpectedValueFromPattern (operation.Pattern) is not bool patternValue)
return null;

if (Visit (operation.Value, state) is not FeatureChecksValue value)
return null;

return patternValue
? value
: value.Negate ();


static bool? GetExpectedValueFromPattern (IPatternOperation pattern)
{
switch (pattern) {
case IConstantPatternOperation constantPattern:
return GetConstantBool (constantPattern.Value.ConstantValue);
case INegatedPatternOperation negatedPattern:
return !GetExpectedValueFromPattern (negatedPattern.Pattern);
default:
return null;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using ILLink.Shared.DataFlow;

namespace ILLink.RoslynAnalyzer.DataFlow
{
// Represents the feature conditions checked in a conditional expression,
// such as
// Debug.Assert (RuntimeFeatures.IsDynamicCodeSupported)
// or
// if (!RuntimeFeatures.IsDynamicCodeSupported)
// For now, this is only designed to track the built-in "features"/"capabilities"
// like RuntimeFeatures.IsDynamicCodeSupported, where a true return value
// indicates that a feature/capability is available.
public record struct FeatureChecksValue : INegate<FeatureChecksValue>
{
public ValueSet<string> EnabledFeatures;
public ValueSet<string> DisabledFeatures;

public FeatureChecksValue (string enabledFeature)
{
EnabledFeatures = new ValueSet<string> (enabledFeature);
DisabledFeatures = ValueSet<string>.Empty;
}

private FeatureChecksValue (ValueSet<string> enabled, ValueSet<string> disabled)
{
EnabledFeatures = enabled;
DisabledFeatures = disabled;
}

public FeatureChecksValue And (FeatureChecksValue other)
{
return new FeatureChecksValue (
ValueSet<string>.Union (EnabledFeatures.DeepCopy (), other.EnabledFeatures.DeepCopy ()),
ValueSet<string>.Union (DisabledFeatures.DeepCopy (), other.DisabledFeatures.DeepCopy ()));
}

public FeatureChecksValue Or (FeatureChecksValue other)
{
return new FeatureChecksValue (
ValueSet<string>.Intersection (EnabledFeatures.DeepCopy (), other.EnabledFeatures.DeepCopy ()),
ValueSet<string>.Intersection (DisabledFeatures.DeepCopy (), other.DisabledFeatures.DeepCopy ()));
}

public FeatureChecksValue Negate ()
{
return new FeatureChecksValue (DisabledFeatures.DeepCopy (), EnabledFeatures.DeepCopy ());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using ILLink.Shared;
using ILLink.Shared.DataFlow;
using Microsoft.CodeAnalysis.Diagnostics;

namespace ILLink.RoslynAnalyzer.DataFlow
{
public struct FeatureContext : IEquatable<FeatureContext>, IDeepCopyValue<FeatureContext>
{
// The set of features known to be enabled in this context.
// Null represents "all possible features".
public ValueSet<string>? EnabledFeatures;

public static readonly FeatureContext All = new FeatureContext (null);

public static readonly FeatureContext None = new FeatureContext (ValueSet<string>.Empty);

public FeatureContext (ValueSet<string>? enabled)
{
EnabledFeatures = enabled;
}

public bool IsEnabled (string feature)
{
return EnabledFeatures == null || EnabledFeatures.Value.Contains (feature);
}

public bool Equals (FeatureContext other) => EnabledFeatures == other.EnabledFeatures;
public override bool Equals (object? obj) => obj is FeatureContext other && Equals (other);
public override int GetHashCode () => EnabledFeatures?.GetHashCode () ?? typeof (FeatureContext).GetHashCode ();

public static bool operator == (FeatureContext left, FeatureContext right) => left.Equals (right);
public static bool operator != (FeatureContext left, FeatureContext right) => !left.Equals (right);

public FeatureContext DeepCopy ()
{
return new FeatureContext (EnabledFeatures?.DeepCopy ());
}

public FeatureContext Intersection (FeatureContext other)
{
if (EnabledFeatures == null)
return other.DeepCopy ();
if (other.EnabledFeatures == null)
return this.DeepCopy ();
return new FeatureContext (ValueSet<string>.Intersection (EnabledFeatures.Value, other.EnabledFeatures.Value));
}

public FeatureContext Union (FeatureContext other)
{
if (EnabledFeatures == null)
return this.DeepCopy ();
if (other.EnabledFeatures == null)
return other.DeepCopy ();
return new FeatureContext (ValueSet<string>.Union (EnabledFeatures.Value, other.EnabledFeatures.Value));
}
}

public readonly struct FeatureContextLattice : ILattice<FeatureContext>
{
public FeatureContextLattice () { }

// The top value is the identity of meet (intersection), the set of all features.
public FeatureContext Top { get; } = FeatureContext.All;

// We are interested in features which are known to be enabled for all paths through the CFG,
// so the meet operator is set intersection.
public FeatureContext Meet (FeatureContext left, FeatureContext right) => left.Intersection (right);
}
}
Loading
Loading