Browse Source

Fix bugs in data flow analysis.

pull/728/head
Daniel Grunwald 9 years ago
parent
commit
1aaf7b3dc0
  1. 180
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  2. 12
      ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
  3. 13
      ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs
  4. 1
      ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
  5. 22
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  6. 29
      ICSharpCode.Decompiler/Util/BitSet.cs

180
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -29,14 +29,16 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// A mutable container for the state tracked by the data flow analysis. /// A mutable container for the state tracked by the data flow analysis.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// To ensure the data flow analysis terminates, states must form a partially ordered set (poset) /// States must form a join-semilattice: https://en.wikipedia.org/wiki/Semilattice
/// with finite height. ///
/// To handle <c>try{} finally{}</c> properly, states should implement <c>MeetWith()</c> as well,
/// and thus should form a lattice.
/// </remarks> /// </remarks>
public interface IDataFlowState<Self> where Self: IDataFlowState<Self> public interface IDataFlowState<Self> where Self: IDataFlowState<Self>
{ {
/// <summary> /// <summary>
/// Gets whether this state is "less than" (or equal to) another state. /// Gets whether this state is "less than" (or equal to) another state.
/// This is the partial order of the poset. /// This is the partial order of the semi-lattice.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The exact meaning of this relation is up to the concrete implementation, /// The exact meaning of this relation is up to the concrete implementation,
@ -45,9 +47,15 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// and then adds more information as the analysis progresses. /// 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, /// 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. /// so that the analysis does not run into an infinite loop.
/// The poset must also have finite height (no infinite ascending chains s1 &lt; s2 &lt; ...), /// The partially ordered set must also have finite height (no infinite ascending chains s1 &lt; s2 &lt; ...),
/// to ensure the analysis terminates. /// to ensure the analysis terminates.
/// </remarks> /// </remarks>
/// <example>
/// The simplest possible state, <c>bool isReachable</c>, would implement <c>LessThanOrEqual</c> as:
/// <code>(this.isReachable ? 1 : 0) &lt;= (otherState.isReachable ? 1 : 0)</code>
/// <para>Which can be simpified to:</para>
/// <code>!this.isReachable || otherState.isReachable</code>
/// </example>
bool LessThanOrEqual(Self otherState); bool LessThanOrEqual(Self otherState);
/// <summary> /// <summary>
@ -71,19 +79,41 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// Join the incomingState into this state. /// Join the incomingState into this state.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Postcondition: <c>old(this).LessThan(this) &amp;&amp; incomingState.LessThan(this)</c> /// Postcondition: <c>old(this).LessThanOrEqual(this) &amp;&amp; incomingState.LessThanOrEqual(this)</c>
/// This method generally sets <c>this</c> to the smallest state that is greater than (or equal to) /// This method generally sets <c>this</c> to the smallest state that is greater than (or equal to)
/// both input states. /// both input states.
/// </remarks> /// </remarks>
/// <example>
/// The simplest possible state, <c>bool isReachable</c>, would implement <c>JoinWith</c> as:
/// <code>this.isReachable |= incomingState.isReachable;</code>
/// </example>
void JoinWith(Self incomingState); void JoinWith(Self incomingState);
/// <summary>
/// The meet operation.
///
/// If possible, this method sets <c>this</c> to the greatest state that is smaller than (or equal to)
/// both input states.
/// At a minimum, meeting with an unreachable state must result in an unreachable state.
/// </summary>
/// <remarks>
/// MeetWith() is used when control flow passes out of a try-finally construct: the endpoint of the try-finally
/// is reachable only if both the endpoint of the <c>try</c> and the endpoint of the <c>finally</c> blocks are reachable.
/// </remarks>
/// <example>
/// The simplest possible state, <c>bool isReachable</c>, would implement <c>MeetWith</c> as:
/// <code>this.isReachable &amp;= incomingState.isReachable;</code>
/// </example>
void MeetWith(Self incomingState);
/// <summary> /// <summary>
/// Gets whether this is the "unreachable" state. /// Gets whether this is the "unreachable" state.
/// The unreachable state represents that the data flow analysis has not yet /// 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. /// found a code path from the entry point to this state's position.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The unreachable state is the bottom element in the poset: the unreachable state is "less than" all other states. /// The unreachable state is the bottom element in the semi-lattice:
/// the unreachable state is "less than" all other states.
/// </remarks> /// </remarks>
bool IsUnreachable { get; } bool IsUnreachable { get; }
@ -125,8 +155,10 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// <summary> /// <summary>
/// Combined state of all possible exceptional control flow paths in the current try block. /// Combined state of all possible exceptional control flow paths in the current try block.
/// Serves as input state for catch blocks. /// Serves as input state for catch blocks.
///
/// Within a try block, <c>currentStateOnException == stateOnException[tryBlock.Parent]</c>.
/// </summary> /// </summary>
State stateOnException; State currentStateOnException;
/// <summary> /// <summary>
/// Current state. /// Current state.
@ -144,11 +176,49 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
this.unreachableState = initialState.Clone(); this.unreachableState = initialState.Clone();
this.unreachableState.MarkUnreachable(); this.unreachableState.MarkUnreachable();
Debug.Assert(unreachableState.IsUnreachable); Debug.Assert(unreachableState.IsUnreachable);
this.stateOnException = unreachableState.Clone(); this.currentStateOnException = unreachableState.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
State previousOutputState;
if (debugDict.TryGetValue(inst, out previousOutputState)) {
Debug.Assert(previousOutputState.LessThanOrEqual(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());
}
}
#endif
}
#endif
[Conditional("DEBUG")]
void DebugStartPoint(ILInstruction inst)
{
#if DEBUG
DebugPoint(debugInputState, inst);
#endif
}
[Conditional("DEBUG")]
void DebugEndPoint(ILInstruction inst)
{
#if DEBUG
DebugPoint(debugOutputState, inst);
#endif
} }
protected sealed override void Default(ILInstruction inst) protected sealed override void Default(ILInstruction inst)
{ {
DebugStartPoint(inst);
// This method assumes normal control flow and no branches. // This method assumes normal control flow and no branches.
if ((inst.DirectFlags & (InstructionFlags.ControlFlow | InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable)) != 0) { if ((inst.DirectFlags & (InstructionFlags.ControlFlow | InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable)) != 0) {
throw new NotImplementedException("RDVisitor is missing implementation for " + inst.GetType().Name); throw new NotImplementedException("RDVisitor is missing implementation for " + inst.GetType().Name);
@ -164,6 +234,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
if ((inst.DirectFlags & InstructionFlags.MayThrow) != 0) { if ((inst.DirectFlags & InstructionFlags.MayThrow) != 0) {
MayThrow(); MayThrow();
} }
DebugEndPoint(inst);
} }
/// <summary> /// <summary>
@ -172,7 +243,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// </summary> /// </summary>
protected void MayThrow() protected void MayThrow()
{ {
stateOnException.JoinWith(state); currentStateOnException.JoinWith(state);
} }
/// <summary> /// <summary>
@ -208,6 +279,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
protected internal override void VisitBlockContainer(BlockContainer container) protected internal override void VisitBlockContainer(BlockContainer container)
{ {
DebugStartPoint(container);
SortedSet<int> worklist = new SortedSet<int>(); SortedSet<int> worklist = new SortedSet<int>();
// register work list so that branches within this container can add to it // register work list so that branches within this container can add to it
workLists.Add(container, worklist); workLists.Add(container, worklist);
@ -239,6 +311,8 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
} else { } else {
state.MarkUnreachable(); state.MarkUnreachable();
} }
DebugEndPoint(container);
workLists.Remove(container);
} }
protected internal override void VisitBranch(Branch inst) protected internal override void VisitBranch(Branch inst)
@ -288,6 +362,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
state.MarkUnreachable(); state.MarkUnreachable();
} }
/// <summary>
/// Stores the stateOnException per try instruction.
/// </summary>
readonly Dictionary<TryInstruction, State> stateOnException = new Dictionary<TryInstruction, State>();
/// <summary> /// <summary>
/// Visits the TryBlock. /// Visits the TryBlock.
/// ///
@ -295,36 +374,42 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// </summary> /// </summary>
protected State HandleTryBlock(TryInstruction inst) protected State HandleTryBlock(TryInstruction inst)
{ {
State oldStateOnException = stateOnException; State oldStateOnException = currentStateOnException;
State newStateOnException = unreachableState.Clone(); State newStateOnException;
if (!stateOnException.TryGetValue(inst, out newStateOnException)) {
newStateOnException = unreachableState.Clone();
stateOnException.Add(inst, newStateOnException);
}
stateOnException = newStateOnException; currentStateOnException = newStateOnException;
inst.TryBlock.AcceptVisitor(this); inst.TryBlock.AcceptVisitor(this);
stateOnException = oldStateOnException; currentStateOnException = oldStateOnException;
return newStateOnException; return newStateOnException;
} }
protected internal override void VisitTryCatch(TryCatch inst) protected internal override void VisitTryCatch(TryCatch inst)
{ {
State caughtState = HandleTryBlock(inst); DebugStartPoint(inst);
State endpointState = state.Clone(); State onException = HandleTryBlock(inst);
State endpoint = state.Clone();
// The exception might get propagated if no handler matches the type: // The exception might get propagated if no handler matches the type:
stateOnException.JoinWith(caughtState); currentStateOnException.JoinWith(onException);
foreach (var handler in inst.Handlers) { foreach (var handler in inst.Handlers) {
state.ReplaceWith(caughtState); state.ReplaceWith(onException);
BeginTryCatchHandler(handler); BeginTryCatchHandler(handler);
handler.Filter.AcceptVisitor(this); handler.Filter.AcceptVisitor(this);
// if the filter return false, any mutations done by the filter // if the filter return false, any mutations done by the filter
// will be visible by the remaining handlers // will be visible by the remaining handlers
// (but it's also possible that the filter didn't get executed at all // (but it's also possible that the filter didn't get executed at all
// because the exception type doesn't match) // because the exception type doesn't match)
caughtState.JoinWith(state); onException.JoinWith(state);
handler.Body.AcceptVisitor(this); handler.Body.AcceptVisitor(this);
endpointState.JoinWith(state); endpoint.JoinWith(state);
} }
state = endpointState; state = endpoint;
DebugEndPoint(inst);
} }
protected virtual void BeginTryCatchHandler(TryCatchHandler inst) protected virtual void BeginTryCatchHandler(TryCatchHandler inst)
@ -341,58 +426,38 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
protected internal override void VisitTryFinally(TryFinally inst) protected internal override void VisitTryFinally(TryFinally inst)
{ {
// I don't think there's a good way to track dataflow across finally blocks DebugStartPoint(inst);
// without duplicating the whole finally block. // At first, handle 'try { .. } finally { .. }' like 'try { .. } catch {} .. if (?) rethrow; }'
// We'll just approximate 'try { .. } finally { .. }' as 'try { .. } catch {} .. if (?) rethrow; }' State onException = HandleTryBlock(inst);
State caughtState = HandleTryBlock(inst);
State onSuccess = state.Clone(); State onSuccess = state.Clone();
state.JoinWith(caughtState); state.JoinWith(onException);
inst.FinallyBlock.AcceptVisitor(this); inst.FinallyBlock.AcceptVisitor(this);
MayThrow(); MayThrow();
// Our approximation allows the impossible code path where the try block wasn't fully executed // Use MeetWith() to ensure points after the try-finally are reachable only if both the
// and the finally block did not rethrow the exception. // try and the finally endpoints are reachable.
// We can't fix up the data flow state in general, but specific analyses may be able to do so. state.MeetWith(onSuccess);
ExitTryFinally(inst, onSuccess); DebugEndPoint(inst);
}
/// <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) protected internal override void VisitTryFault(TryFault inst)
{ {
DebugStartPoint(inst);
// try-fault executes fault block if an exception occurs in try, // try-fault executes fault block if an exception occurs in try,
// and always rethrows the exception at the end. // and always rethrows the exception at the end.
State caughtState = HandleTryBlock(inst); State onException = HandleTryBlock(inst);
State noException = state; State onSuccess = state;
state = caughtState; state = onException;
inst.FaultBlock.AcceptVisitor(this); inst.FaultBlock.AcceptVisitor(this);
MayThrow(); // rethrow the exception after the fault block MayThrow(); // rethrow the exception after the fault block
// try-fault exits normally only if no exception occurred // try-fault exits normally only if no exception occurred
state = noException; state = onSuccess;
DebugEndPoint(inst);
} }
protected internal override void VisitIfInstruction(IfInstruction inst) protected internal override void VisitIfInstruction(IfInstruction inst)
{ {
DebugStartPoint(inst);
inst.Condition.AcceptVisitor(this); inst.Condition.AcceptVisitor(this);
State branchState = state.Clone(); State branchState = state.Clone();
inst.TrueInst.AcceptVisitor(this); inst.TrueInst.AcceptVisitor(this);
@ -400,10 +465,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
state = branchState; state = branchState;
inst.FalseInst.AcceptVisitor(this); inst.FalseInst.AcceptVisitor(this);
state.JoinWith(afterTrueState); state.JoinWith(afterTrueState);
DebugEndPoint(inst);
} }
protected internal override void VisitSwitchInstruction(SwitchInstruction inst) protected internal override void VisitSwitchInstruction(SwitchInstruction inst)
{ {
DebugStartPoint(inst);
inst.Value.AcceptVisitor(this); inst.Value.AcceptVisitor(this);
State beforeSections = state.Clone(); State beforeSections = state.Clone();
State afterSections = unreachableState.Clone(); State afterSections = unreachableState.Clone();
@ -413,6 +480,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
afterSections.JoinWith(state); afterSections.JoinWith(state);
} }
state = afterSections; state = afterSections;
DebugEndPoint(inst);
} }
} }
} }

12
ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs

@ -79,7 +79,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
bits.UnionWith(incomingState.bits); bits.UnionWith(incomingState.bits);
} }
public void IntersectWith(State incomingState) public void MeetWith(State incomingState)
{ {
bits.IntersectWith(incomingState.bits); bits.IntersectWith(incomingState.bits);
} }
@ -157,15 +157,5 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
base.VisitLdLoca(inst); base.VisitLdLoca(inst);
EnsureInitialized(inst.Variable); EnsureInitialized(inst.Variable);
} }
protected override void ExitTryFinally(TryFinally inst, State onSuccess)
{
// We can simply intersect the onSuccess bits with the state after the finally block.
// This works because the try-finally endpoint is reached (=control flow continues after the try-finally construct)
// without writing to variable i,
// only if the endpoint of the try was reached with writing to variable i,
// and the endpoint of the finally block was also reached without writing to variable i.
state.IntersectWith(onSuccess);
}
} }
} }

13
ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs

@ -72,6 +72,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
[DebuggerDisplay("{bits}")] [DebuggerDisplay("{bits}")]
struct State : IDataFlowState<State> struct State : IDataFlowState<State>
{ {
/// <summary>
/// bit 0: This state's position is reachable from the entry point.
/// bit i+1: There is a code path from the entry point to this state's position
/// that passes through through <c>allStores[i]</c> and does not pass through another
/// store to <c>allStores[i].Variable</c>.
/// </summary>
readonly internal BitSet bits; readonly internal BitSet bits;
public State(BitSet bits) public State(BitSet bits)
@ -99,6 +105,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
bits.UnionWith(incomingState.bits); bits.UnionWith(incomingState.bits);
} }
public void MeetWith(State incomingState)
{
bits.IntersectWith(incomingState.bits);
}
public void MarkUnreachable() public void MarkUnreachable()
{ {
bits.ClearAll(); bits.ClearAll();
@ -137,7 +148,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// All stores for all variables in the scope. /// All stores for all variables in the scope.
/// ///
/// <c>state[storeIndex]</c> is true iff <c>allStores[storeIndex]</c> is a reaching definition. /// <c>state[storeIndex]</c> is true iff <c>allStores[storeIndex]</c> is a reaching definition.
/// Invariant: <c>state.Length == allStores.Length</c>. /// Invariant: <c>state.bits.Length == allStores.Length</c>.
/// </summary> /// </summary>
readonly ILInstruction[] allStores; readonly ILInstruction[] allStores;

1
ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs

@ -145,6 +145,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(EntryPoint == Blocks[0]); Debug.Assert(EntryPoint == Blocks[0]);
Debug.Assert(!IsConnected || EntryPoint.IncomingEdgeCount >= 1); Debug.Assert(!IsConnected || EntryPoint.IncomingEdgeCount >= 1);
Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable))); Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable)));
Debug.Assert(Blocks.All(b => b.FinalInstruction.OpCode == OpCode.Nop));
} }
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()

22
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -85,15 +85,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var newObj = new NewObj(inst.Method); var newObj = new NewObj(inst.Method);
newObj.ILRange = inst.ILRange; newObj.ILRange = inst.ILRange;
newObj.Arguments.AddRange(inst.Arguments.Skip(1)); newObj.Arguments.AddRange(inst.Arguments.Skip(1));
LdcDecimal decimalConstant; var expr = new StObj(inst.Arguments[0], newObj, inst.Method.DeclaringType);
ILInstruction result;
if (TransformDecimalCtorToConstant(newObj, out decimalConstant)) {
result = decimalConstant;
} else {
result = newObj;
}
var expr = new StObj(inst.Arguments[0], result, inst.Method.DeclaringType);
inst.ReplaceWith(expr); inst.ReplaceWith(expr);
// Both the StObj and the NewObj may trigger further rules, so continue visiting the replacement:
VisitStObj(expr); VisitStObj(expr);
} else { } else {
base.VisitCall(inst); base.VisitCall(inst);
@ -139,11 +133,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// This transform is required because ILInlining only works with stloc/ldloc // This transform is required because ILInlining only works with stloc/ldloc
protected internal override void VisitStObj(StObj inst) protected internal override void VisitStObj(StObj inst)
{ {
base.VisitStObj(inst);
ILVariable v; ILVariable v;
if (inst.Target.MatchLdLoca(out v) && TypeUtils.IsCompatibleTypeForMemoryAccess(new ByReferenceType(v.Type), inst.Type) && inst.UnalignedPrefix == 0 && !inst.IsVolatile) { if (inst.Target.MatchLdLoca(out v)
inst.ReplaceWith(new StLoc(v, inst.Value.Clone())); && TypeUtils.IsCompatibleTypeForMemoryAccess(new ByReferenceType(v.Type), inst.Type)
&& inst.UnalignedPrefix == 0
&& !inst.IsVolatile)
{
// stobj(ldloca(v), ...)
// => stloc(v, ...)
inst.ReplaceWith(new StLoc(v, inst.Value));
} }
base.VisitStObj(inst);
} }
} }
} }

29
ICSharpCode.Decompiler/Util/BitSet.cs

@ -18,6 +18,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
@ -75,6 +76,34 @@ namespace ICSharpCode.Decompiler
return other.IsSubsetOf(this); return other.IsSubsetOf(this);
} }
public bool SetEquals(BitSet other)
{
for (int i = 0; i < bits.Length; i++) {
if (bits[i] != other[i])
return false;
}
return true;
}
public bool IsProperSubsetOf(BitSet other)
{
return IsSubsetOf(other) && !SetEquals(other);
}
public bool IsProperSupersetOf(BitSet other)
{
return IsSubsetOf(other) && !SetEquals(other);
}
public bool Overlaps(BitSet other)
{
for (int i = 0; i < bits.Length; i++) {
if (bits[i] && other[i])
return true;
}
return false;
}
public void UnionWith(BitSet other) public void UnionWith(BitSet other)
{ {
bits.Or(other.bits); bits.Or(other.bits);

Loading…
Cancel
Save