You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
847 lines
29 KiB
847 lines
29 KiB
// Copyright (c) 2016 Daniel Grunwald
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
|
|
using ICSharpCode.Decompiler.IL;
|
|
|
|
namespace ICSharpCode.Decompiler.FlowAnalysis
|
|
{
|
|
/// <summary>
|
|
/// Interface for use with DataFlowVisitor.
|
|
///
|
|
/// A mutable container for the state tracked by the data flow analysis.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// States must form a join-semilattice: https://en.wikipedia.org/wiki/Semilattice
|
|
///
|
|
/// To handle <c>try{} finally{}</c> properly, states should implement <c>MeetWith()</c> as well,
|
|
/// and thus should form a lattice.
|
|
///
|
|
/// <c>DataFlowVisitor</c> expects the state to behave like a mutable reference type.
|
|
/// It might still be a good idea to use a struct to implement it so that .NET uses static dispatch for
|
|
/// method calls on the type parameter, but that struct must consist only of a <c>readonly</c> field
|
|
/// referencing some mutable object, to ensure the type parameter behaves as it if was a mutable reference type.
|
|
/// </remarks>
|
|
public interface IDataFlowState<Self> where Self : IDataFlowState<Self>
|
|
{
|
|
/// <summary>
|
|
/// Gets whether this state is "less than" (or equal to) another state.
|
|
/// This is the partial order of the semi-lattice.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The exact meaning of this relation is up to the concrete implementation,
|
|
/// but usually "less than" means "has less information than".
|
|
/// A given position in the code starts at the "bottom state" (=no information)
|
|
/// and then adds more information as the analysis progresses.
|
|
/// After each change to the state, the old state must be less than the new state,
|
|
/// so that the analysis does not run into an infinite loop.
|
|
/// The partially ordered set must also have finite height (no infinite ascending chains s1 < s2 < ...),
|
|
/// to ensure the analysis terminates.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// The simplest possible non-trivial state, <c>bool isReachable</c>, would implement <c>LessThanOrEqual</c> as:
|
|
/// <code>return (this.isReachable ? 1 : 0) <= (otherState.isReachable ? 1 : 0);</code>
|
|
/// <para>Which can be simpified to:</para>
|
|
/// <code>return !this.isReachable || otherState.isReachable;</code>
|
|
/// </example>
|
|
bool LessThanOrEqual(Self otherState);
|
|
|
|
/// <summary>
|
|
/// Creates a new object with a copy of the state.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Mutating methods such as <c>ReplaceWith</c> or <c>JoinWith</c> modify the contents of a state object.
|
|
/// Cloning the object allows the analysis to track multiple independent states,
|
|
/// such as the
|
|
/// </remarks>
|
|
/// <example>
|
|
/// The simple state "<c>bool isReachable</c>", would implement <c>Clone</c> as:
|
|
/// <code>return new MyState(this.isReachable);</code>
|
|
/// </example>
|
|
Self Clone();
|
|
|
|
/// <summary>
|
|
/// Replace the contents of this state object with a copy of those in <paramref name="newContent"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <c>x = x.Clone(); x.ReplaceWith(newContent);</c>
|
|
/// is equivalent to
|
|
/// <c>x = newContent.Clone();</c>
|
|
///
|
|
/// ReplaceWith() is used to avoid allocating new state objects where possible.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// The simple state "<c>bool isReachable</c>", would implement <c>ReplaceWith</c> as:
|
|
/// <code>this.isReachable = newContent.isReachable;</code>
|
|
/// </example>
|
|
void ReplaceWith(Self newContent);
|
|
|
|
/// <summary>
|
|
/// Join the incomingState into this state.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Postcondition: <c>old(this).LessThanOrEqual(this) && incomingState.LessThanOrEqual(this)</c>
|
|
/// This method should set <c>this</c> to the smallest state that is greater than (or equal to)
|
|
/// both input states.
|
|
///
|
|
/// <c>JoinWith()</c> is used when multiple control flow paths are joined together.
|
|
/// For example, it is used to combine the <c>thenState</c> with the <c>elseState</c>
|
|
/// at the end of a if-else construct.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// The simple state "<c>bool isReachable</c>", would implement <c>JoinWith</c> as:
|
|
/// <code>this.isReachable |= incomingState.isReachable;</code>
|
|
/// </example>
|
|
void JoinWith(Self incomingState);
|
|
|
|
/// <summary>
|
|
/// A special operation to merge the end-state of the finally-block with the end state of
|
|
/// a branch leaving the try-block.
|
|
///
|
|
/// If either input state is unreachable, this call must result in an unreachable state.
|
|
/// </summary>
|
|
/// <example>
|
|
/// The simple state "<c>bool isReachable</c>", would implement <c>TriggerFinally</c> as:
|
|
/// <code>this.isReachable &= finallyState.isReachable;</code>
|
|
/// </example>
|
|
void TriggerFinally(Self finallyState);
|
|
|
|
/// <summary>
|
|
/// Gets whether this is the bottom state.
|
|
///
|
|
/// The bottom state represents that the data flow analysis has not yet
|
|
/// found a code path from the entry point to this state's position.
|
|
/// It thus contains no information, and is "less than" all other states.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The bottom state is the bottom element in the semi-lattice.
|
|
///
|
|
/// Initially, all code blocks not yet visited by the analysis will be in the bottom state.
|
|
/// Unreachable code will always remain in the bottom state.
|
|
/// Some analyses may also use the bottom state for reachable code after it was processed by the analysis.
|
|
/// For example, in <c>DefiniteAssignmentVisitor</c> the bottom states means
|
|
/// "either this code is unreachable, or all variables are definitely initialized".
|
|
/// </remarks>
|
|
/// <example>
|
|
/// The simple state "<c>bool isReachable</c>", would implement <c>IsBottom</c> as:
|
|
/// <code>return !this.isReachable;</code>
|
|
/// </example>
|
|
bool IsBottom { get; }
|
|
|
|
/// <summary>
|
|
/// Equivalent to <c>this.ReplaceWith(bottomState)</c>, but may be implemented more efficiently.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Since the <c>DataFlowVisitor</c> can only create states by cloning from the initial state,
|
|
/// this method is necessary for the <c>DataFlowVisitor</c> to gain access to the bottom element in
|
|
/// the first place.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// The simple state "<c>bool isReachable</c>", would implement <c>ReplaceWithBottom</c> as:
|
|
/// <code>this.isReachable = false;</code>
|
|
/// </example>
|
|
void ReplaceWithBottom();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generic base class for forward data flow analyses.
|
|
/// </summary>
|
|
/// <typeparam name="State">
|
|
/// The state type used for the data flow analysis. See <see cref="IDataFlowState{Self}"/> for details.
|
|
/// </typeparam>
|
|
public abstract class DataFlowVisitor<State> : ILVisitor
|
|
where State : IDataFlowState<State>
|
|
{
|
|
// The data flow analysis tracks a 'state'.
|
|
// There are many states (one per source code position, i.e. ILInstruction), but we don't store all of them.
|
|
// We only keep track of:
|
|
// a) the current state in the RDVisitor
|
|
// This state corresponds to the instruction currently being visited,
|
|
// and gets mutated as we traverse the ILAst.
|
|
// b) the input state for each control flow node
|
|
// These also gets mutated as the analysis learns about new control flow edges.
|
|
|
|
/// <summary>
|
|
/// The bottom state.
|
|
/// Must not be mutated.
|
|
/// </summary>
|
|
State bottomState;
|
|
|
|
/// <summary>
|
|
/// Current state.
|
|
///
|
|
/// Caution: any state object assigned to this member gets mutated as the visitor traverses the ILAst!
|
|
/// </summary>
|
|
protected State state;
|
|
|
|
/// <summary>
|
|
/// Combined state of all possible exceptional control flow paths in the current try block.
|
|
/// Serves as input state for catch blocks.
|
|
///
|
|
/// Caution: any state object assigned to this member gets mutated as the visitor encounters instructions that may throw exceptions!
|
|
///
|
|
/// Within a try block, <c>currentStateOnException == stateOnException[tryBlock.Parent]</c>.
|
|
/// </summary>
|
|
/// <seealso cref="PropagateStateOnException"/>
|
|
protected State currentStateOnException;
|
|
|
|
bool initialized;
|
|
|
|
/// <summary>
|
|
/// Initializes the DataFlowVisitor.
|
|
/// This method must be called once before any Visit()-methods can be called.
|
|
/// It must not be called more than once.
|
|
/// </summary>
|
|
/// <param name="initialState">The initial state at the entry point of the analysis.</param>
|
|
/// <remarks>
|
|
/// This is a method instead of a constructor because derived classes might need complex initialization
|
|
/// before they can construct the initial state.
|
|
/// </remarks>
|
|
protected void Initialize(State initialState)
|
|
{
|
|
Debug.Assert(!initialized);
|
|
initialized = true;
|
|
this.state = initialState.Clone();
|
|
this.bottomState = initialState.Clone();
|
|
this.bottomState.ReplaceWithBottom();
|
|
Debug.Assert(bottomState.IsBottom);
|
|
this.stateOnNullableRewrap = bottomState.Clone();
|
|
this.currentStateOnException = state.Clone();
|
|
}
|
|
|
|
#if DEBUG
|
|
// For debugging, capture the input + output state at every instruction.
|
|
readonly Dictionary<ILInstruction, State> debugInputState = new Dictionary<ILInstruction, State>();
|
|
readonly Dictionary<ILInstruction, State> debugOutputState = new Dictionary<ILInstruction, State>();
|
|
|
|
void DebugPoint(Dictionary<ILInstruction, State> debugDict, ILInstruction inst)
|
|
{
|
|
#if DEBUG
|
|
Debug.Assert(initialized, "Initialize() was not called");
|
|
|
|
if (debugDict.TryGetValue(inst, out State previousState))
|
|
{
|
|
Debug.Assert(previousState.LessThanOrEqual(state));
|
|
previousState.JoinWith(state);
|
|
}
|
|
else
|
|
{
|
|
// limit the number of tracked instructions to make memory usage in debug builds less horrible
|
|
if (debugDict.Count < 1000)
|
|
{
|
|
debugDict.Add(inst, state.Clone());
|
|
}
|
|
}
|
|
|
|
// currentStateOnException should be all states within the try block joined together
|
|
// -> state should already have been joined into currentStateOnException.
|
|
Debug.Assert(state.LessThanOrEqual(currentStateOnException));
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
[Conditional("DEBUG")]
|
|
protected void DebugStartPoint(ILInstruction inst)
|
|
{
|
|
#if DEBUG
|
|
DebugPoint(debugInputState, inst);
|
|
#endif
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
protected void DebugEndPoint(ILInstruction inst)
|
|
{
|
|
#if DEBUG
|
|
DebugPoint(debugOutputState, inst);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Derived classes may add to this set of flags to ensure they don't forget to override an interesting method.
|
|
/// </summary>
|
|
protected InstructionFlags flagsRequiringManualImpl = InstructionFlags.ControlFlow | InstructionFlags.MayBranch | InstructionFlags.MayUnwrapNull | InstructionFlags.EndPointUnreachable;
|
|
|
|
protected sealed override void Default(ILInstruction inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
// This method assumes normal control flow and no branches.
|
|
if ((inst.DirectFlags & flagsRequiringManualImpl) != 0)
|
|
{
|
|
throw new NotImplementedException(GetType().Name + " is missing implementation for " + inst.GetType().Name);
|
|
}
|
|
|
|
// Since this instruction has normal control flow, we can evaluate our children left-to-right.
|
|
foreach (var child in inst.Children)
|
|
{
|
|
child.AcceptVisitor(this);
|
|
Debug.Assert(state.IsBottom || !child.HasFlag(InstructionFlags.EndPointUnreachable),
|
|
"Unreachable code must be in the bottom state.");
|
|
}
|
|
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle control flow when the current instruction throws an exception:
|
|
/// joins the current state into the "exception state" of the current try block.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This should not only be called for instructions that may throw an exception,
|
|
/// but for all instructions (due to async exceptions like ThreadAbortException)!
|
|
///
|
|
/// To avoid redundant calls, every Visit() call may assume that the current state
|
|
/// is already propagated, and has to guarantee the same at the end.
|
|
/// This means this method should be called after every state change.
|
|
/// Alternatively, derived classes may directly modify both <c>state</c>
|
|
/// and <c>currentStateOnException</c>, so that a full <c>JoinWith()</c> call
|
|
/// is not necessary.
|
|
/// </remarks>
|
|
protected void PropagateStateOnException()
|
|
{
|
|
currentStateOnException.JoinWith(state);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replace the current state with the bottom state.
|
|
/// </summary>
|
|
protected void MarkUnreachable()
|
|
{
|
|
state.ReplaceWithBottom();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Holds the state for incoming branches.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Only used for blocks in block containers; not for inline blocks.
|
|
/// </remarks>
|
|
readonly Dictionary<Block, State> stateOnBranch = new Dictionary<Block, State>();
|
|
|
|
/// <summary>
|
|
/// Holds the state at the block container end-point. (=state for incoming 'leave' instructions)
|
|
/// </summary>
|
|
readonly Dictionary<BlockContainer, State> stateOnLeave = new Dictionary<BlockContainer, State>();
|
|
|
|
/// <summary>
|
|
/// Gets the state object that holds the state for incoming branches to the block.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Returns the a clone of the bottom state on the first call for a given block,
|
|
/// then returns the same object instance on further calls.
|
|
/// The caller is expected to mutate the returned state by calling <c>JoinWith()</c>.
|
|
/// </remarks>
|
|
State GetBlockInputState(Block block)
|
|
{
|
|
if (stateOnBranch.TryGetValue(block, out State s))
|
|
{
|
|
return s;
|
|
}
|
|
else
|
|
{
|
|
s = bottomState.Clone();
|
|
stateOnBranch.Add(block, s);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// For each block container, stores the set of blocks (via Block.ChildIndex)
|
|
/// that had their incoming state changed and were not processed yet.
|
|
/// </summary>
|
|
readonly Dictionary<BlockContainer, SortedSet<int>> workLists = new Dictionary<BlockContainer, SortedSet<int>>();
|
|
|
|
protected internal override void VisitBlockContainer(BlockContainer container)
|
|
{
|
|
DebugStartPoint(container);
|
|
SortedSet<int> worklist = new SortedSet<int>();
|
|
// register work list so that branches within this container can add to it
|
|
workLists.Add(container, worklist);
|
|
var stateOnEntry = GetBlockInputState(container.EntryPoint);
|
|
if (!state.LessThanOrEqual(stateOnEntry))
|
|
{
|
|
// If we have new information for the container's entry point,
|
|
// add the container entry point to the work list.
|
|
stateOnEntry.JoinWith(state);
|
|
worklist.Add(0);
|
|
}
|
|
|
|
// To handle loops, we need to analyze the loop body before we can know the state for the loop backedge,
|
|
// but we need to know the input state for the loop body (to which the backedge state contributes)
|
|
// before we can analyze the loop body.
|
|
// Solution: we repeat the analysis of the loop body multiple times, until the state no longer changes.
|
|
// To make it terminate reasonably quickly, we need to process the control flow nodes in the correct order:
|
|
// reverse post-order. We use a SortedSet<int> for this, and assume that the block indices used in the SortedSet
|
|
// are ordered appropriately. The caller can use BlockContainer.SortBlocks() for this.
|
|
while (worklist.Count > 0)
|
|
{
|
|
int blockIndex = worklist.Min;
|
|
worklist.Remove(blockIndex);
|
|
Block block = container.Blocks[blockIndex];
|
|
state.ReplaceWith(stateOnBranch[block]);
|
|
block.AcceptVisitor(this);
|
|
}
|
|
if (stateOnLeave.TryGetValue(container, out State stateOnExit))
|
|
{
|
|
state.ReplaceWith(stateOnExit);
|
|
}
|
|
else
|
|
{
|
|
MarkUnreachable();
|
|
}
|
|
DebugEndPoint(container);
|
|
workLists.Remove(container);
|
|
}
|
|
|
|
readonly List<(IBranchOrLeaveInstruction, State)> branchesTriggeringFinally = new List<(IBranchOrLeaveInstruction, State)>();
|
|
|
|
protected internal override void VisitBranch(Branch inst)
|
|
{
|
|
if (inst.TriggersFinallyBlock)
|
|
{
|
|
Debug.Assert(state.LessThanOrEqual(currentStateOnException));
|
|
branchesTriggeringFinally.Add((inst, state.Clone()));
|
|
}
|
|
else
|
|
{
|
|
MergeBranchStateIntoTargetBlock(inst, state);
|
|
}
|
|
MarkUnreachable();
|
|
}
|
|
|
|
void MergeBranchStateIntoTargetBlock(Branch inst, State branchState)
|
|
{
|
|
var targetBlock = inst.TargetBlock;
|
|
var targetState = GetBlockInputState(targetBlock);
|
|
if (!branchState.LessThanOrEqual(targetState))
|
|
{
|
|
targetState.JoinWith(branchState);
|
|
|
|
BlockContainer container = (BlockContainer)targetBlock.Parent;
|
|
if (workLists.TryGetValue(container, out var workList))
|
|
{
|
|
workList.Add(targetBlock.ChildIndex);
|
|
}
|
|
else
|
|
{
|
|
Debug.Fail("Failed to find target BlockContainer");
|
|
}
|
|
}
|
|
}
|
|
|
|
protected internal override void VisitLeave(Leave inst)
|
|
{
|
|
inst.Value.AcceptVisitor(this);
|
|
if (inst.TriggersFinallyBlock)
|
|
{
|
|
Debug.Assert(state.LessThanOrEqual(currentStateOnException));
|
|
branchesTriggeringFinally.Add((inst, state.Clone()));
|
|
}
|
|
else
|
|
{
|
|
MergeBranchStateIntoStateOnLeave(inst, state);
|
|
}
|
|
MarkUnreachable();
|
|
}
|
|
|
|
void MergeBranchStateIntoStateOnLeave(Leave inst, State branchState)
|
|
{
|
|
if (stateOnLeave.TryGetValue(inst.TargetContainer, out State targetState))
|
|
{
|
|
targetState.JoinWith(branchState);
|
|
}
|
|
else
|
|
{
|
|
stateOnLeave.Add(inst.TargetContainer, branchState.Clone());
|
|
}
|
|
// Note: We don't have to put the block container onto the work queue,
|
|
// because it's an ancestor of the Leave instruction, and hence
|
|
// we are currently somewhere within the VisitBlockContainer() call.
|
|
}
|
|
|
|
protected internal override void VisitThrow(Throw inst)
|
|
{
|
|
inst.Argument.AcceptVisitor(this);
|
|
MarkUnreachable();
|
|
}
|
|
|
|
protected internal override void VisitRethrow(Rethrow inst)
|
|
{
|
|
MarkUnreachable();
|
|
}
|
|
|
|
protected internal override void VisitInvalidBranch(InvalidBranch inst)
|
|
{
|
|
MarkUnreachable();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores the stateOnException per try instruction.
|
|
/// </summary>
|
|
readonly Dictionary<TryInstruction, State> stateOnException = new Dictionary<TryInstruction, State>();
|
|
|
|
/// <summary>
|
|
/// Visits the TryBlock.
|
|
///
|
|
/// Returns a new State object representing the exceptional control flow transfer out of the try block.
|
|
/// </summary>
|
|
protected State HandleTryBlock(TryInstruction inst)
|
|
{
|
|
State oldStateOnException = currentStateOnException;
|
|
if (stateOnException.TryGetValue(inst, out State newStateOnException))
|
|
{
|
|
newStateOnException.JoinWith(state);
|
|
}
|
|
else
|
|
{
|
|
newStateOnException = state.Clone();
|
|
stateOnException.Add(inst, newStateOnException);
|
|
}
|
|
|
|
currentStateOnException = newStateOnException;
|
|
inst.TryBlock.AcceptVisitor(this);
|
|
// swap back to the old object instance
|
|
currentStateOnException = oldStateOnException;
|
|
|
|
// No matter what kind of try-instruction this is, it's possible
|
|
// that an async exception is thrown immediately in the handler block,
|
|
// so propagate the state:
|
|
oldStateOnException.JoinWith(newStateOnException);
|
|
|
|
// Return a copy, so that the caller mutating the returned state
|
|
// does not influence the 'stateOnException' dict
|
|
return newStateOnException.Clone();
|
|
}
|
|
|
|
protected internal override void VisitTryCatch(TryCatch inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
State onException = HandleTryBlock(inst);
|
|
State endpoint = state.Clone();
|
|
foreach (var handler in inst.Handlers)
|
|
{
|
|
state.ReplaceWith(onException);
|
|
BeginTryCatchHandler(handler);
|
|
handler.Filter.AcceptVisitor(this);
|
|
// if the filter return false, any mutations done by the filter
|
|
// will be visible by the remaining handlers
|
|
// (but it's also possible that the filter didn't get executed at all
|
|
// because the exception type doesn't match)
|
|
onException.JoinWith(state);
|
|
|
|
handler.Body.AcceptVisitor(this);
|
|
endpoint.JoinWith(state);
|
|
}
|
|
state = endpoint;
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
protected virtual void BeginTryCatchHandler(TryCatchHandler inst)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// TryCatchHandler is handled directly in VisitTryCatch
|
|
/// </summary>
|
|
protected internal override sealed void VisitTryCatchHandler(TryCatchHandler inst)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
protected internal override void VisitTryFinally(TryFinally inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
int branchesTriggeringFinallyOldCount = branchesTriggeringFinally.Count;
|
|
// At first, handle 'try { .. } finally { .. }' like 'try { .. } catch {} .. if (?) rethrow; }'
|
|
State onException = HandleTryBlock(inst);
|
|
State onSuccess = state.Clone();
|
|
state.JoinWith(onException);
|
|
inst.FinallyBlock.AcceptVisitor(this);
|
|
//PropagateStateOnException(); // rethrow the exception after the finally block -- should be redundant
|
|
Debug.Assert(state.LessThanOrEqual(currentStateOnException));
|
|
|
|
ProcessBranchesLeavingTryFinally(inst, branchesTriggeringFinallyOldCount);
|
|
|
|
// Use TriggerFinally() to ensure points after the try-finally are reachable only if both the
|
|
// try and the finally endpoints are reachable.
|
|
onSuccess.TriggerFinally(state);
|
|
state = onSuccess;
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process branches leaving the try-finally,
|
|
/// * Calls TriggerFinally() on each branchesTriggeringFinally
|
|
/// * Removes entries from branchesTriggeringFinally if they won't trigger additional finally blocks.
|
|
/// * After all finallies are applied, the branch state is merged into the target block.
|
|
/// </summary>
|
|
void ProcessBranchesLeavingTryFinally(TryFinally tryFinally, int branchesTriggeringFinallyOldCount)
|
|
{
|
|
int outPos = branchesTriggeringFinallyOldCount;
|
|
for (int i = branchesTriggeringFinallyOldCount; i < branchesTriggeringFinally.Count; ++i)
|
|
{
|
|
var (branch, stateOnBranch) = branchesTriggeringFinally[i];
|
|
Debug.Assert(((ILInstruction)branch).IsDescendantOf(tryFinally));
|
|
Debug.Assert(tryFinally.IsDescendantOf(branch.TargetContainer));
|
|
stateOnBranch.TriggerFinally(state);
|
|
bool triggersAnotherFinally = Branch.GetExecutesFinallyBlock(tryFinally, branch.TargetContainer);
|
|
if (triggersAnotherFinally)
|
|
{
|
|
branchesTriggeringFinally[outPos++] = (branch, stateOnBranch);
|
|
}
|
|
else
|
|
{
|
|
// Merge state into target block.
|
|
if (branch is Leave leave)
|
|
{
|
|
MergeBranchStateIntoStateOnLeave((Leave)branch, stateOnBranch);
|
|
}
|
|
else
|
|
{
|
|
MergeBranchStateIntoTargetBlock((Branch)branch, stateOnBranch);
|
|
}
|
|
}
|
|
}
|
|
branchesTriggeringFinally.RemoveRange(outPos, branchesTriggeringFinally.Count - outPos);
|
|
}
|
|
|
|
protected internal override void VisitTryFault(TryFault inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
// try-fault executes fault block if an exception occurs in try,
|
|
// and always rethrows the exception at the end.
|
|
State onException = HandleTryBlock(inst);
|
|
State onSuccess = state;
|
|
state = onException;
|
|
inst.FaultBlock.AcceptVisitor(this);
|
|
//PropagateStateOnException(); // rethrow the exception after the fault block
|
|
Debug.Assert(state.LessThanOrEqual(currentStateOnException));
|
|
|
|
// try-fault exits normally only if no exception occurred
|
|
state = onSuccess;
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
protected internal override void VisitIfInstruction(IfInstruction inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
var (beforeThen, beforeElse) = EvaluateCondition(inst.Condition);
|
|
state = beforeThen;
|
|
inst.TrueInst.AcceptVisitor(this);
|
|
State afterTrueState = state;
|
|
state = beforeElse;
|
|
inst.FalseInst.AcceptVisitor(this);
|
|
state.JoinWith(afterTrueState);
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluates the condition of an if.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A pair of:
|
|
/// * The state after the condition evaluates to true
|
|
/// * The state after the condition evaluates to false
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <c>this.state</c> is invalid after this function was called, and must be overwritten
|
|
/// with one of the return values.
|
|
/// </remarks>
|
|
(State OnTrue, State OnFalse) EvaluateCondition(ILInstruction inst)
|
|
{
|
|
if (inst is IfInstruction ifInst)
|
|
{
|
|
// 'if (a?b:c)' or similar.
|
|
// This also includes conditions that are logic.not, logic.and, logic.or.
|
|
DebugStartPoint(ifInst);
|
|
var (beforeThen, beforeElse) = EvaluateCondition(ifInst.Condition);
|
|
state = beforeThen;
|
|
var (afterThenTrue, afterThenFalse) = EvaluateCondition(ifInst.TrueInst);
|
|
state = beforeElse;
|
|
var (afterElseTrue, afterElseFalse) = EvaluateCondition(ifInst.FalseInst);
|
|
|
|
var onTrue = afterThenTrue;
|
|
onTrue.JoinWith(afterElseTrue);
|
|
var onFalse = afterThenFalse;
|
|
onFalse.JoinWith(afterElseFalse);
|
|
|
|
DebugEndPoint(ifInst);
|
|
return (onTrue, onFalse);
|
|
}
|
|
else if (inst is LdcI4 constant)
|
|
{
|
|
if (constant.Value == 0)
|
|
{
|
|
return (bottomState.Clone(), state);
|
|
}
|
|
else
|
|
{
|
|
return (state, bottomState.Clone());
|
|
}
|
|
}
|
|
else if (inst is MatchInstruction match)
|
|
{
|
|
return EvaluateMatch(match);
|
|
}
|
|
else
|
|
{
|
|
// other kind of condition
|
|
inst.AcceptVisitor(this);
|
|
return (state, state.Clone());
|
|
}
|
|
}
|
|
|
|
protected internal override void VisitMatchInstruction(MatchInstruction inst)
|
|
{
|
|
var (onTrue, onFalse) = EvaluateMatch(inst);
|
|
state = onTrue;
|
|
state.JoinWith(onFalse);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluates a match instruction.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A pair of:
|
|
/// * The state after the pattern matches
|
|
/// * The state after the pattern fails to match
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <c>this.state</c> is invalid after this function was called, and must be overwritten
|
|
/// with one of the return values.
|
|
/// </remarks>
|
|
(State OnTrue, State OnFalse) EvaluateMatch(MatchInstruction inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
inst.TestedOperand.AcceptVisitor(this);
|
|
State onFalse = state.Clone();
|
|
if (!inst.CheckNotNull && !inst.CheckType)
|
|
{
|
|
onFalse.ReplaceWithBottom();
|
|
}
|
|
HandleMatchStore(inst);
|
|
foreach (var subPattern in inst.SubPatterns)
|
|
{
|
|
var (subTrue, subFalse) = EvaluateCondition(subPattern);
|
|
onFalse.JoinWith(subFalse);
|
|
state = subTrue;
|
|
}
|
|
DebugEndPoint(inst);
|
|
return (state, onFalse);
|
|
}
|
|
|
|
protected abstract void HandleMatchStore(MatchInstruction inst);
|
|
|
|
protected internal override void VisitNullCoalescingInstruction(NullCoalescingInstruction inst)
|
|
{
|
|
HandleBinaryWithOptionalEvaluation(inst, inst.ValueInst, inst.FallbackInst);
|
|
}
|
|
|
|
protected internal override void VisitDynamicLogicOperatorInstruction(DynamicLogicOperatorInstruction inst)
|
|
{
|
|
HandleBinaryWithOptionalEvaluation(inst, inst.Left, inst.Right);
|
|
}
|
|
|
|
protected internal override void VisitUserDefinedLogicOperator(UserDefinedLogicOperator inst)
|
|
{
|
|
HandleBinaryWithOptionalEvaluation(inst, inst.Left, inst.Right);
|
|
}
|
|
|
|
void HandleBinaryWithOptionalEvaluation(ILInstruction parent, ILInstruction left, ILInstruction right)
|
|
{
|
|
DebugStartPoint(parent);
|
|
left.AcceptVisitor(this);
|
|
State branchState = state.Clone();
|
|
right.AcceptVisitor(this);
|
|
state.JoinWith(branchState);
|
|
DebugEndPoint(parent);
|
|
}
|
|
|
|
State stateOnNullableRewrap;
|
|
|
|
protected internal override void VisitNullableRewrap(NullableRewrap inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
var oldState = stateOnNullableRewrap.Clone();
|
|
stateOnNullableRewrap.ReplaceWithBottom();
|
|
|
|
inst.Argument.AcceptVisitor(this);
|
|
// Join incoming control flow from the NullableUnwraps.
|
|
state.JoinWith(stateOnNullableRewrap);
|
|
|
|
stateOnNullableRewrap = oldState;
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
protected internal override void VisitNullableUnwrap(NullableUnwrap inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
inst.Argument.AcceptVisitor(this);
|
|
// Conditional control flow edge to the surrounding NullableRewrap.
|
|
stateOnNullableRewrap.JoinWith(state);
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
protected internal override void VisitSwitchInstruction(SwitchInstruction inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
inst.Value.AcceptVisitor(this);
|
|
State beforeSections = state.Clone();
|
|
inst.Sections[0].AcceptVisitor(this);
|
|
State afterSections = state.Clone();
|
|
for (int i = 1; i < inst.Sections.Count; ++i)
|
|
{
|
|
state.ReplaceWith(beforeSections);
|
|
inst.Sections[i].AcceptVisitor(this);
|
|
afterSections.JoinWith(state);
|
|
}
|
|
state = afterSections;
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
protected internal override void VisitYieldReturn(YieldReturn inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
inst.Value.AcceptVisitor(this);
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
protected internal override void VisitUsingInstruction(UsingInstruction inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
inst.ResourceExpression.AcceptVisitor(this);
|
|
inst.Body.AcceptVisitor(this);
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
protected internal override void VisitLockInstruction(LockInstruction inst)
|
|
{
|
|
DebugStartPoint(inst);
|
|
inst.OnExpression.AcceptVisitor(this);
|
|
inst.Body.AcceptVisitor(this);
|
|
DebugEndPoint(inst);
|
|
}
|
|
|
|
protected internal override void VisitILFunction(ILFunction function)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|