diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
index 85381422c..1c82e49a2 100644
--- a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
+++ b/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.
///
///
- /// To ensure the data flow analysis terminates, states must form a partially ordered set (poset)
- /// with finite height.
+ /// States must form a join-semilattice: https://en.wikipedia.org/wiki/Semilattice
+ ///
+ /// To handle try{} finally{} properly, states should implement MeetWith() as well,
+ /// and thus should form a lattice.
///
public interface IDataFlowState where Self: IDataFlowState
{
///
/// 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.
///
///
/// 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.
/// 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 < ...),
+ /// The partially ordered set must also have finite height (no infinite ascending chains s1 < s2 < ...),
/// to ensure the analysis terminates.
///
+ ///
+ /// The simplest possible state, bool isReachable, would implement LessThanOrEqual as:
+ /// (this.isReachable ? 1 : 0) <= (otherState.isReachable ? 1 : 0)
+ /// Which can be simpified to:
+ /// !this.isReachable || otherState.isReachable
+ ///
bool LessThanOrEqual(Self otherState);
///
@@ -71,19 +79,41 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// Join the incomingState into this state.
///
///
- /// Postcondition: old(this).LessThan(this) && incomingState.LessThan(this)
+ /// Postcondition: old(this).LessThanOrEqual(this) && incomingState.LessThanOrEqual(this)
/// This method generally sets this to the smallest state that is greater than (or equal to)
/// both input states.
///
+ ///
+ /// The simplest possible state, bool isReachable, would implement JoinWith as:
+ /// this.isReachable |= incomingState.isReachable;
+ ///
void JoinWith(Self incomingState);
+ ///
+ /// The meet operation.
+ ///
+ /// If possible, this method sets this 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.
+ ///
+ ///
+ /// 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 try and the endpoint of the finally blocks are reachable.
+ ///
+ ///
+ /// The simplest possible state, bool isReachable, would implement MeetWith as:
+ /// this.isReachable &= incomingState.isReachable;
+ ///
+ void MeetWith(Self incomingState);
+
///
/// 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.
///
///
- /// 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.
///
bool IsUnreachable { get; }
@@ -125,8 +155,10 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
///
/// Combined state of all possible exceptional control flow paths in the current try block.
/// Serves as input state for catch blocks.
+ ///
+ /// Within a try block, currentStateOnException == stateOnException[tryBlock.Parent].
///
- State stateOnException;
+ State currentStateOnException;
///
/// Current state.
@@ -144,11 +176,49 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
this.unreachableState = initialState.Clone();
this.unreachableState.MarkUnreachable();
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 debugInputState = new Dictionary();
+ readonly Dictionary debugOutputState = new Dictionary();
+
+ void DebugPoint(Dictionary 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)
{
+ DebugStartPoint(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);
@@ -164,6 +234,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
if ((inst.DirectFlags & InstructionFlags.MayThrow) != 0) {
MayThrow();
}
+ DebugEndPoint(inst);
}
///
@@ -172,7 +243,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
///
protected void MayThrow()
{
- stateOnException.JoinWith(state);
+ currentStateOnException.JoinWith(state);
}
///
@@ -208,6 +279,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
protected internal override void VisitBlockContainer(BlockContainer container)
{
+ DebugStartPoint(container);
SortedSet worklist = new SortedSet();
// register work list so that branches within this container can add to it
workLists.Add(container, worklist);
@@ -239,6 +311,8 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
} else {
state.MarkUnreachable();
}
+ DebugEndPoint(container);
+ workLists.Remove(container);
}
protected internal override void VisitBranch(Branch inst)
@@ -288,6 +362,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
state.MarkUnreachable();
}
+ ///
+ /// Stores the stateOnException per try instruction.
+ ///
+ readonly Dictionary stateOnException = new Dictionary();
+
///
/// Visits the TryBlock.
///
@@ -295,36 +374,42 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
///
protected State HandleTryBlock(TryInstruction inst)
{
- State oldStateOnException = stateOnException;
- State newStateOnException = unreachableState.Clone();
+ State oldStateOnException = currentStateOnException;
+ State newStateOnException;
+ if (!stateOnException.TryGetValue(inst, out newStateOnException)) {
+ newStateOnException = unreachableState.Clone();
+ stateOnException.Add(inst, newStateOnException);
+ }
- stateOnException = newStateOnException;
+ currentStateOnException = newStateOnException;
inst.TryBlock.AcceptVisitor(this);
- stateOnException = oldStateOnException;
+ currentStateOnException = oldStateOnException;
return newStateOnException;
}
protected internal override void VisitTryCatch(TryCatch inst)
{
- State caughtState = HandleTryBlock(inst);
- State endpointState = state.Clone();
+ DebugStartPoint(inst);
+ State onException = HandleTryBlock(inst);
+ State endpoint = state.Clone();
// The exception might get propagated if no handler matches the type:
- stateOnException.JoinWith(caughtState);
+ currentStateOnException.JoinWith(onException);
foreach (var handler in inst.Handlers) {
- state.ReplaceWith(caughtState);
+ 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)
- caughtState.JoinWith(state);
+ onException.JoinWith(state);
handler.Body.AcceptVisitor(this);
- endpointState.JoinWith(state);
+ endpoint.JoinWith(state);
}
- state = endpointState;
+ state = endpoint;
+ DebugEndPoint(inst);
}
protected virtual void BeginTryCatchHandler(TryCatchHandler inst)
@@ -341,58 +426,38 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
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);
+ DebugStartPoint(inst);
+ // At first, handle 'try { .. } finally { .. }' like 'try { .. } catch {} .. if (?) rethrow; }'
+ State onException = HandleTryBlock(inst);
State onSuccess = state.Clone();
- state.JoinWith(caughtState);
+ state.JoinWith(onException);
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);
- }
-
- ///
- /// Called when exiting a try-finally construct.
- ///
- /// The try-finally instruction.
- /// The state at the endpoint of the try block.
- /// state: 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.
- ///
- /// 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.
- ///
- protected virtual void ExitTryFinally(TryFinally inst, State onSuccess)
- {
- if (onSuccess.IsUnreachable) {
- state.MarkUnreachable();
- }
+ // Use MeetWith() to ensure points after the try-finally are reachable only if both the
+ // try and the finally endpoints are reachable.
+ state.MeetWith(onSuccess);
+ DebugEndPoint(inst);
}
-
+
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 caughtState = HandleTryBlock(inst);
- State noException = state;
- state = caughtState;
+ State onException = HandleTryBlock(inst);
+ State onSuccess = state;
+ state = onException;
inst.FaultBlock.AcceptVisitor(this);
MayThrow(); // rethrow the exception after the fault block
// try-fault exits normally only if no exception occurred
- state = noException;
+ state = onSuccess;
+ DebugEndPoint(inst);
}
protected internal override void VisitIfInstruction(IfInstruction inst)
{
+ DebugStartPoint(inst);
inst.Condition.AcceptVisitor(this);
State branchState = state.Clone();
inst.TrueInst.AcceptVisitor(this);
@@ -400,10 +465,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
state = branchState;
inst.FalseInst.AcceptVisitor(this);
state.JoinWith(afterTrueState);
+ DebugEndPoint(inst);
}
protected internal override void VisitSwitchInstruction(SwitchInstruction inst)
{
+ DebugStartPoint(inst);
inst.Value.AcceptVisitor(this);
State beforeSections = state.Clone();
State afterSections = unreachableState.Clone();
@@ -413,6 +480,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
afterSections.JoinWith(state);
}
state = afterSections;
+ DebugEndPoint(inst);
}
}
}
diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
index ac3d92c55..d0f58cf85 100644
--- a/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
+++ b/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
@@ -79,7 +79,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
bits.UnionWith(incomingState.bits);
}
- public void IntersectWith(State incomingState)
+ public void MeetWith(State incomingState)
{
bits.IntersectWith(incomingState.bits);
}
@@ -157,15 +157,5 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
base.VisitLdLoca(inst);
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);
- }
}
}
diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs b/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs
index 7ab5f1f56..d0c62e62c 100644
--- a/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs
+++ b/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs
@@ -72,6 +72,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
[DebuggerDisplay("{bits}")]
struct State : IDataFlowState
{
+ ///
+ /// 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 allStores[i] and does not pass through another
+ /// store to allStores[i].Variable.
+ ///
readonly internal BitSet bits;
public State(BitSet bits)
@@ -99,6 +105,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
bits.UnionWith(incomingState.bits);
}
+ public void MeetWith(State incomingState)
+ {
+ bits.IntersectWith(incomingState.bits);
+ }
+
public void MarkUnreachable()
{
bits.ClearAll();
@@ -137,7 +148,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// All stores for all variables in the scope.
///
/// state[storeIndex] is true iff allStores[storeIndex] is a reaching definition.
- /// Invariant: state.Length == allStores.Length.
+ /// Invariant: state.bits.Length == allStores.Length.
///
readonly ILInstruction[] allStores;
diff --git a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
index feb5852be..b97ff6e07 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
@@ -145,6 +145,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(EntryPoint == Blocks[0]);
Debug.Assert(!IsConnected || EntryPoint.IncomingEdgeCount >= 1);
Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable)));
+ Debug.Assert(Blocks.All(b => b.FinalInstruction.OpCode == OpCode.Nop));
}
protected override InstructionFlags ComputeFlags()
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index 070e96183..8404fce31 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
@@ -85,15 +85,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var newObj = new NewObj(inst.Method);
newObj.ILRange = inst.ILRange;
newObj.Arguments.AddRange(inst.Arguments.Skip(1));
- LdcDecimal decimalConstant;
- ILInstruction result;
- if (TransformDecimalCtorToConstant(newObj, out decimalConstant)) {
- result = decimalConstant;
- } else {
- result = newObj;
- }
- var expr = new StObj(inst.Arguments[0], result, inst.Method.DeclaringType);
+ var expr = new StObj(inst.Arguments[0], newObj, inst.Method.DeclaringType);
inst.ReplaceWith(expr);
+ // Both the StObj and the NewObj may trigger further rules, so continue visiting the replacement:
VisitStObj(expr);
} else {
base.VisitCall(inst);
@@ -139,11 +133,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// This transform is required because ILInlining only works with stloc/ldloc
protected internal override void VisitStObj(StObj inst)
{
+ base.VisitStObj(inst);
ILVariable v;
- if (inst.Target.MatchLdLoca(out v) && TypeUtils.IsCompatibleTypeForMemoryAccess(new ByReferenceType(v.Type), inst.Type) && inst.UnalignedPrefix == 0 && !inst.IsVolatile) {
- inst.ReplaceWith(new StLoc(v, inst.Value.Clone()));
+ if (inst.Target.MatchLdLoca(out v)
+ && 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);
}
}
}
diff --git a/ICSharpCode.Decompiler/Util/BitSet.cs b/ICSharpCode.Decompiler/Util/BitSet.cs
index 351c19dd0..e71191f38 100644
--- a/ICSharpCode.Decompiler/Util/BitSet.cs
+++ b/ICSharpCode.Decompiler/Util/BitSet.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
@@ -75,6 +76,34 @@ namespace ICSharpCode.Decompiler
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)
{
bits.Or(other.bits);