mirror of https://github.com/icsharpcode/ILSpy.git
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.
418 lines
16 KiB
418 lines
16 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> |
|
/// To ensure the data flow analysis terminates, states must form a partially ordered set (poset) |
|
/// with finite height. |
|
/// </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 poset. |
|
/// </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 "unreachable 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 poset must also have finite height (no infinite ascending chains s1 < s2 < ...), |
|
/// to ensure the analysis terminates. |
|
/// </remarks> |
|
bool LessThanOrEqual(Self otherState); |
|
|
|
/// <summary> |
|
/// Creates a new object with a copy of the state. |
|
/// </summary> |
|
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> |
|
void ReplaceWith(Self newContent); |
|
|
|
/// <summary> |
|
/// Join the incomingState into this state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Postcondition: <c>old(this).LessThan(this) && incomingState.LessThan(this)</c> |
|
/// This method generally sets <c>this</c> to the smallest state that is greater than (or equal to) |
|
/// both input states. |
|
/// </remarks> |
|
void JoinWith(Self incomingState); |
|
|
|
/// <summary> |
|
/// Gets whether this is the "unreachable" state. |
|
/// The unreachable state represents that the data flow analysis has not yet |
|
/// found a code path from the entry point to this state's position. |
|
/// </summary> |
|
/// <remarks> |
|
/// The unreachable state is the bottom element in the poset: the unreachable state is "less than" all other states. |
|
/// </remarks> |
|
bool IsUnreachable { get; } |
|
|
|
/// <summary> |
|
/// Equivalent to <c>this.ReplaceWith(unreachableState)</c>, but may be more efficient. |
|
/// </summary> |
|
void MarkUnreachable(); |
|
} |
|
|
|
/// <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. |
|
/// |
|
/// <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. |
|
/// </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 |
|
// This also gets mutated as the analysis learns about new control flow edges. |
|
|
|
/// <summary> |
|
/// The unreachable state. |
|
/// Must not be mutated. |
|
/// </summary> |
|
readonly State unreachableState; |
|
|
|
/// <summary> |
|
/// Combined state of all possible exceptional control flow paths in the current try block. |
|
/// Serves as input state for catch blocks. |
|
/// </summary> |
|
State stateOnException; |
|
|
|
/// <summary> |
|
/// Current state. |
|
/// Gets mutated as the visitor traverses the ILAst. |
|
/// </summary> |
|
protected State state; |
|
|
|
/// <summary> |
|
/// Creates a new DataFlowVisitor. |
|
/// </summary> |
|
/// <param name="initialState">The initial state at the entry point of the analysis.</param> |
|
protected DataFlowVisitor(State initialState) |
|
{ |
|
this.state = initialState.Clone(); |
|
this.unreachableState = initialState.Clone(); |
|
this.unreachableState.MarkUnreachable(); |
|
Debug.Assert(unreachableState.IsUnreachable); |
|
this.stateOnException = unreachableState.Clone(); |
|
} |
|
|
|
protected sealed override void Default(ILInstruction inst) |
|
{ |
|
// This method assumes normal control flow and no branches. |
|
if ((inst.DirectFlags & (InstructionFlags.ControlFlow | InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable)) != 0) { |
|
throw new NotImplementedException("RDVisitor 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.IsUnreachable || !child.HasFlag(InstructionFlags.EndPointUnreachable)); |
|
} |
|
|
|
// If this instruction can throw an exception, handle the exceptional control flow edge. |
|
if ((inst.DirectFlags & InstructionFlags.MayThrow) != 0) { |
|
MayThrow(); |
|
} |
|
} |
|
|
|
/// <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> |
|
protected void MayThrow() |
|
{ |
|
stateOnException.JoinWith(state); |
|
} |
|
|
|
/// <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>(); |
|
|
|
State GetBlockInputState(Block block) |
|
{ |
|
State s; |
|
if (stateOnBranch.TryGetValue(block, out s)) { |
|
return s; |
|
} else { |
|
s = unreachableState.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) |
|
{ |
|
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); |
|
} |
|
State stateOnExit; |
|
if (stateOnLeave.TryGetValue(container, out stateOnExit)) { |
|
state.ReplaceWith(stateOnExit); |
|
} else { |
|
state.MarkUnreachable(); |
|
} |
|
} |
|
|
|
protected internal override void VisitBranch(Branch inst) |
|
{ |
|
var targetBlock = inst.TargetBlock; |
|
var targetState = GetBlockInputState(targetBlock); |
|
if (!state.LessThanOrEqual(targetState)) { |
|
targetState.JoinWith(state); |
|
|
|
BlockContainer container = (BlockContainer)targetBlock.Parent; |
|
workLists[container].Add(targetBlock.ChildIndex); |
|
} |
|
state.MarkUnreachable(); |
|
} |
|
|
|
protected internal override void VisitLeave(Leave inst) |
|
{ |
|
State targetState; |
|
if (stateOnLeave.TryGetValue(inst.TargetContainer, out targetState)) { |
|
targetState.JoinWith(state); |
|
} else { |
|
stateOnLeave.Add(inst.TargetContainer, state.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. |
|
state.MarkUnreachable(); |
|
} |
|
|
|
protected internal override void VisitReturn(Return inst) |
|
{ |
|
if (inst.ReturnValue != null) |
|
inst.ReturnValue.AcceptVisitor(this); |
|
state.MarkUnreachable(); |
|
} |
|
|
|
protected internal override void VisitThrow(Throw inst) |
|
{ |
|
inst.Argument.AcceptVisitor(this); |
|
MayThrow(); |
|
state.MarkUnreachable(); |
|
} |
|
|
|
protected internal override void VisitRethrow(Rethrow inst) |
|
{ |
|
MayThrow(); |
|
state.MarkUnreachable(); |
|
} |
|
|
|
/// <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 = stateOnException; |
|
State newStateOnException = unreachableState.Clone(); |
|
|
|
stateOnException = newStateOnException; |
|
inst.TryBlock.AcceptVisitor(this); |
|
stateOnException = oldStateOnException; |
|
|
|
return newStateOnException; |
|
} |
|
|
|
protected internal override void VisitTryCatch(TryCatch inst) |
|
{ |
|
State caughtState = HandleTryBlock(inst); |
|
State endpointState = state.Clone(); |
|
// The exception might get propagated if no handler matches the type: |
|
stateOnException.JoinWith(caughtState); |
|
foreach (var handler in inst.Handlers) { |
|
state.ReplaceWith(caughtState); |
|
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) |
|
caughtState.JoinWith(state); |
|
|
|
handler.Body.AcceptVisitor(this); |
|
endpointState.JoinWith(state); |
|
} |
|
state = endpointState; |
|
} |
|
|
|
protected virtual void BeginTryCatchHandler(TryCatchHandler inst) |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// TryCatchHandler is handled directly in VisitTryCatch |
|
/// </summary> |
|
protected internal override sealed void VisitTryCatchHandler(TryCatchHandler inst) |
|
{ |
|
throw new NotImplementedException(); |
|
} |
|
|
|
protected internal override void VisitTryFinally(TryFinally inst) |
|
{ |
|
// I don't think there's a good way to track dataflow across finally blocks |
|
// without duplicating the whole finally block. |
|
// We'll just approximate 'try { .. } finally { .. }' as 'try { .. } catch {} .. if (?) rethrow; }' |
|
State caughtState = HandleTryBlock(inst); |
|
State onSuccess = state.Clone(); |
|
state.JoinWith(caughtState); |
|
inst.FinallyBlock.AcceptVisitor(this); |
|
MayThrow(); |
|
// Our approximation allows the impossible code path where the try block wasn't fully executed |
|
// and the finally block did not rethrow the exception. |
|
// We can't fix up the data flow state in general, but specific analyses may be able to do so. |
|
ExitTryFinally(inst, onSuccess); |
|
} |
|
|
|
/// <summary> |
|
/// Called when exiting a try-finally construct. |
|
/// </summary> |
|
/// <param name="inst">The try-finally instruction.</param> |
|
/// <param name="onSuccess">The state at the endpoint of the try block.</param> |
|
/// <c>state</c>: The current state when ExitTryFinally() is called is the state at the |
|
/// endpoint of the finally block. |
|
/// After ExitTryFinally() exits, it should be the state at the endpoint |
|
/// of the whole try-finally construct. |
|
/// <remarks> |
|
/// The purpose of this method is to allow the derived class to fix up the state at the end |
|
/// of the try-finally construct: the end of the finally block can be reached when |
|
/// the the try block has throws an exception, but the endpoint of the whole try-finally |
|
/// is only reachable both the try block and the finally block execute normally. |
|
/// </remarks> |
|
protected virtual void ExitTryFinally(TryFinally inst, State onSuccess) |
|
{ |
|
if (onSuccess.IsUnreachable) { |
|
state.MarkUnreachable(); |
|
} |
|
} |
|
|
|
protected internal override void VisitTryFault(TryFault inst) |
|
{ |
|
// try-fault executes fault block if an exception occurs in try, |
|
// and always rethrows the exception at the end. |
|
State caughtState = HandleTryBlock(inst); |
|
State noException = state; |
|
state = caughtState; |
|
inst.FaultBlock.AcceptVisitor(this); |
|
MayThrow(); // rethrow the exception after the fault block |
|
|
|
// try-fault exits normally only if no exception occurred |
|
state = noException; |
|
} |
|
|
|
protected internal override void VisitIfInstruction(IfInstruction inst) |
|
{ |
|
inst.Condition.AcceptVisitor(this); |
|
State branchState = state.Clone(); |
|
inst.TrueInst.AcceptVisitor(this); |
|
State afterTrueState = state; |
|
state = branchState; |
|
inst.FalseInst.AcceptVisitor(this); |
|
state.JoinWith(afterTrueState); |
|
} |
|
|
|
protected internal override void VisitSwitchInstruction(SwitchInstruction inst) |
|
{ |
|
inst.Value.AcceptVisitor(this); |
|
State beforeSections = state.Clone(); |
|
State afterSections = unreachableState.Clone(); |
|
foreach (var section in inst.Sections) { |
|
state.ReplaceWith(beforeSections); |
|
section.AcceptVisitor(this); |
|
afterSections.JoinWith(state); |
|
} |
|
state = afterSections; |
|
} |
|
} |
|
}
|
|
|