diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 85b00f76c..ced0883cc 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -57,6 +57,7 @@ namespace ICSharpCode.Decompiler.CSharp
new SplitVariables(),
new ILInlining(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
+ new DetectExitPoints(),
new BlockILTransform {
PostOrderTransforms = {
new ExpressionTransforms() // for RemoveDeadVariableInit
@@ -67,9 +68,9 @@ namespace ICSharpCode.Decompiler.CSharp
new RemoveDeadVariableInit(),
new SwitchDetection(),
new LoopDetection(),
- new IntroduceExitPoints(),
new BlockILTransform { // per-block transforms
PostOrderTransforms = {
+ //new UseExitPoints(),
new ConditionDetection(),
// CachedDelegateInitialization must run after ConditionDetection and before/in LoopingBlockTransform.
new CachedDelegateInitialization(),
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
index 2e53ddf93..b6240a4c0 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
@@ -289,6 +289,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
var vds = new VariableDeclarationStatement(type, v.Name, initializer);
vds.Variables.Single().AddAnnotation(new ILVariableResolveResult(p.Key, p.Key.Type));
+ Debug.Assert(v.InsertionPoint.nextNode.Role == BlockStatement.StatementRole);
v.InsertionPoint.nextNode.Parent.InsertChildBefore(
v.InsertionPoint.nextNode,
vds,
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 50a9e071f..053577c15 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -286,7 +286,7 @@
-
+
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
index 59e76a5a7..eed2d8f5c 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
@@ -112,7 +112,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& targetBlock.Instructions[0].MatchIfInstruction(out nestedCondition, out nestedTrueInst))
{
nestedTrueInst = UnpackBlockContainingOnlyBranch(nestedTrueInst);
- if (CompatibleExitInstruction(exitInst, nestedTrueInst)) {
+ if (DetectExitPoints.CompatibleExitInstruction(exitInst, nestedTrueInst)) {
// "if (...) { if (nestedCondition) goto exitPoint; ... } goto exitPoint;"
// -> "if (... && !nestedCondition) { ... } goto exitPoint;"
context.Step("Combine 'if (cond1 && !cond2)' in then-branch", ifInst);
@@ -130,7 +130,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
trueExitInst = targetBlock.Instructions.LastOrDefault();
- if (CompatibleExitInstruction(exitInst, trueExitInst)) {
+ if (DetectExitPoints.CompatibleExitInstruction(exitInst, trueExitInst)) {
// "if (...) { ...; goto exitPoint } goto exitPoint;"
// -> "if (...) { ... } goto exitPoint;"
context.Step("Remove redundant 'goto exitPoint;' in then-branch", ifInst);
@@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (IsUsableBranchToChild(cfgNode, exitInst)) {
var targetBlock = ((Branch)exitInst).TargetBlock;
var falseExitInst = targetBlock.Instructions.LastOrDefault();
- if (CompatibleExitInstruction(trueExitInst, falseExitInst)) {
+ if (DetectExitPoints.CompatibleExitInstruction(trueExitInst, falseExitInst)) {
// if (...) { ...; goto exitPoint; } goto nextBlock; nextBlock: ...; goto exitPoint;
// -> if (...) { ... } else { ... } goto exitPoint;
context.Step("Inline block as else-branch", ifInst);
@@ -246,28 +246,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& targetBlock.IncomingEdgeCount == 1 && targetBlock.FinalInstruction.OpCode == OpCode.Nop;
}
- internal static bool CompatibleExitInstruction(ILInstruction exit1, ILInstruction exit2)
- {
- if (exit1 == null || exit2 == null || exit1.OpCode != exit2.OpCode)
- return false;
- switch (exit1.OpCode) {
- case OpCode.Branch:
- Branch br1 = (Branch)exit1;
- Branch br2 = (Branch)exit2;
- return br1.TargetBlock == br2.TargetBlock;
- case OpCode.Leave:
- Leave leave1 = (Leave)exit1;
- Leave leave2 = (Leave)exit2;
- return leave1.TargetContainer == leave2.TargetContainer;
- case OpCode.Return:
- Return ret1 = (Return)exit1;
- Return ret2 = (Return)exit2;
- return ret1.ReturnValue == null && ret2.ReturnValue == null;
- default:
- return false;
- }
- }
-
private void HandleSwitchInstruction(ControlFlowNode cfgNode, Block block, SwitchInstruction sw, ref ILInstruction exitInst)
{
Debug.Assert(sw.DefaultBody is Nop);
@@ -302,7 +280,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
exitInst = sectionBlock.Instructions.Last();
sectionBlock.Instructions.RemoveAt(sectionBlock.Instructions.Count - 1);
block.Instructions.Add(exitInst);
- } else if (sectionBlock != null && CompatibleExitInstruction(exitInst, sectionBlock.Instructions.Last())) {
+ } else if (sectionBlock != null && DetectExitPoints.CompatibleExitInstruction(exitInst, sectionBlock.Instructions.Last())) {
sectionBlock.Instructions.RemoveAt(sectionBlock.Instructions.Count - 1);
}
}
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs
new file mode 100644
index 000000000..cfa8599cf
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs
@@ -0,0 +1,230 @@
+// Copyright (c) 2014 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.Diagnostics;
+using ICSharpCode.Decompiler.IL.Transforms;
+
+namespace ICSharpCode.Decompiler.IL.ControlFlow
+{
+ ///
+ /// Detect suitable exit points for BlockContainers.
+ ///
+ /// An "exit point" is an instruction that causes control flow
+ /// to leave the container (a branch, leave or value-less return instruction).
+ ///
+ /// If an "exit point" instruction is placed immediately following a
+ /// block container, each equivalent exit point within the container
+ /// can be replaced with a "leave container" instruction.
+ ///
+ /// This transform performs this replacement: any exit points
+ /// equivalent to the exit point following the container are
+ /// replaced with a leave instruction.
+ /// Additionally, if the container is not yet followed by an exit point,
+ /// but has room to introduce such an exit point (i.e. iff the container's
+ /// end point is currently unreachable), we pick one of the non-return
+ /// exit points within the container, move it to the position following the
+ /// container, and replace all instances within the container with a leave
+ /// instruction.
+ ///
+ /// This makes it easier for the following transforms to construct
+ /// control flow that falls out of blocks instead of using goto/break statements.
+ ///
+ public class DetectExitPoints : ILVisitor, IILTransform
+ {
+ static readonly Nop ExitNotYetDetermined = new Nop();
+ static readonly Nop NoExit = new Nop();
+ // The special ReturnExit value (indicating fall-through out of a void method)
+ // is considered a compatible exit point with all normal return instructions.
+ static readonly Return ReturnExit = new Return();
+
+ ///
+ /// Gets the next instruction after is executed.
+ /// Returns NoExit when the next instruction cannot be identified;
+ /// returns null when the end of a Block is reached (so that we could insert an arbitrary instruction)
+ ///
+ internal static ILInstruction GetExit(ILInstruction inst)
+ {
+ SlotInfo slot = inst.SlotInfo;
+ if (slot == Block.InstructionSlot) {
+ Block block = (Block)inst.Parent;
+ return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1) ?? ExitNotYetDetermined;
+ } else if (slot == TryInstruction.TryBlockSlot
+ || slot == TryCatchHandler.BodySlot
+ || slot == TryCatch.HandlerSlot
+ || slot == PinnedRegion.BodySlot)
+ {
+ return GetExit(inst.Parent);
+ } else if (slot == ILFunction.BodySlot) {
+ return ReturnExit;
+ }
+ return NoExit;
+ }
+
+ ///
+ /// Returns true iff exit1 and exit2 are both exit instructions
+ /// (branch, leave, or return) and both represent the same exit.
+ ///
+ internal static bool CompatibleExitInstruction(ILInstruction exit1, ILInstruction exit2)
+ {
+ if (exit1 == null || exit2 == null || exit1.OpCode != exit2.OpCode)
+ return false;
+ switch (exit1.OpCode) {
+ case OpCode.Branch:
+ Branch br1 = (Branch)exit1;
+ Branch br2 = (Branch)exit2;
+ return br1.TargetBlock == br2.TargetBlock;
+ case OpCode.Leave:
+ Leave leave1 = (Leave)exit1;
+ Leave leave2 = (Leave)exit2;
+ return leave1.TargetContainer == leave2.TargetContainer;
+ case OpCode.Return:
+ Return ret1 = (Return)exit1;
+ Return ret2 = (Return)exit2;
+ return ret1.ReturnValue == null && ret2.ReturnValue == null;
+ default:
+ return false;
+ }
+ }
+
+ BlockContainer currentContainer;
+
+ ///
+ /// The instruction that will be executed next after leaving the currentContainer.
+ /// null means the container is last in its parent block, and thus does not
+ /// yet have any leave instructions. This means we can move any exit instruction of
+ /// our choice our of the container and replace it with a leave instruction.
+ ///
+ ILInstruction currentExit;
+
+ public void Run(ILFunction function, ILTransformContext context)
+ {
+ currentExit = NoExit;
+ function.AcceptVisitor(this);
+ }
+
+ protected override void Default(ILInstruction inst)
+ {
+ foreach (var child in inst.Children)
+ child.AcceptVisitor(this);
+ }
+
+ protected internal override void VisitBlockContainer(BlockContainer container)
+ {
+ var oldExit = currentExit;
+ var oldContainer = currentContainer;
+ var thisExit = GetExit(container);
+ currentExit = thisExit;
+ currentContainer = container;
+ base.VisitBlockContainer(container);
+ if (thisExit == ExitNotYetDetermined && currentExit != ExitNotYetDetermined) {
+ // This transform determined an exit point.
+ Debug.Assert(!currentExit.MatchLeave(currentContainer));
+ ILInstruction inst = container;
+ // traverse up to the block (we'll always find one because GetExit
+ // only returns ExitNotYetDetermined if there's a block)
+ while (inst.Parent.OpCode != OpCode.Block)
+ inst = inst.Parent;
+ Block block = (Block)inst.Parent;
+ block.Instructions.Add(currentExit);
+ } else {
+ Debug.Assert(thisExit == currentExit);
+ }
+ currentExit = oldExit;
+ currentContainer = oldContainer;
+ }
+
+ protected internal override void VisitBlock(Block block)
+ {
+ // Don't use foreach loop, because the children might add to the block
+ for (int i = 0; i < block.Instructions.Count; i++) {
+ block.Instructions[i].AcceptVisitor(this);
+ }
+ }
+
+ void HandleExit(ILInstruction inst)
+ {
+ if (currentExit == ExitNotYetDetermined && !(inst is Return)) {
+ currentExit = inst;
+ inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
+ } else if (CompatibleExitInstruction(inst, currentExit)) {
+ inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
+ }
+ }
+
+ protected internal override void VisitBranch(Branch inst)
+ {
+ if (!inst.TargetBlock.IsDescendantOf(currentContainer)) {
+ HandleExit(inst);
+ }
+ }
+
+ protected internal override void VisitLeave(Leave inst)
+ {
+ HandleExit(inst);
+ }
+
+ protected internal override void VisitReturn(Return inst)
+ {
+ if (inst.ReturnValue == null) {
+ // only void returns are considered exit points
+ HandleExit(inst);
+ } else {
+ base.VisitReturn(inst);
+ }
+ }
+ }
+
+ ///
+ /// Like DetectExitPoints, but only uses existing exit points
+ /// (by replacing compatible instructions with Leave),
+ /// without introducing any exit points even if it were possible.
+ ///
+ class UseExitPoints : IBlockTransform
+ {
+ BlockContainer currentContainer;
+ ILInstruction exitPoint;
+
+ public void Run(Block block, BlockTransformContext context)
+ {
+ currentContainer = context.Container;
+ exitPoint = DetectExitPoints.GetExit(context.Container);
+ Visit(block);
+ }
+
+ void Visit(ILInstruction inst)
+ {
+ switch (inst.OpCode) {
+ case OpCode.Return:
+ case OpCode.Leave:
+ case OpCode.Branch:
+ if (DetectExitPoints.CompatibleExitInstruction(inst, exitPoint)) {
+ inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
+ }
+ break;
+ case OpCode.Block:
+ // This is a post-order block transform; skip over nested blocks
+ // as those are already processed.
+ return;
+ }
+ foreach (var c in inst.Children) {
+ Visit(c);
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/IntroduceExitPoints.cs b/ICSharpCode.Decompiler/IL/ControlFlow/IntroduceExitPoints.cs
deleted file mode 100644
index 4a8cbb79a..000000000
--- a/ICSharpCode.Decompiler/IL/ControlFlow/IntroduceExitPoints.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2014 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.Diagnostics;
-using ICSharpCode.Decompiler.IL.Transforms;
-
-namespace ICSharpCode.Decompiler.IL.ControlFlow
-{
- ///
- /// Control flow transform: use 'leave' instructions instead of 'br' where possible.
- ///
- public class IntroduceExitPoints : ILVisitor, IILTransform
- {
- public void Run(ILFunction function, ILTransformContext context)
- {
- function.AcceptVisitor(this);
- }
-
- static readonly Nop NoExit = new Nop();
- static readonly Return ReturnExit = new Return();
-
- protected override void Default(ILInstruction inst)
- {
- foreach (var child in inst.Children)
- child.AcceptVisitor(this);
- }
-
- BlockContainer currentContainer;
- ILInstruction currentExit = NoExit;
-
- protected internal override void VisitBlockContainer(BlockContainer container)
- {
- var oldExit = currentExit;
- var oldContainer = currentContainer;
- var thisExit = GetExit(container);
- currentExit = thisExit;
- currentContainer = container;
- base.VisitBlockContainer(container);
- if (thisExit == null && currentExit != null) {
- Debug.Assert(!currentExit.MatchLeave(currentContainer));
- ILInstruction inst = container;
- // traverse up to the block (we'll always find one because GetExit only returns null if there's a block)
- while (inst.Parent.OpCode != OpCode.Block)
- inst = inst.Parent;
- Block block = (Block)inst.Parent;
- block.Instructions.Add(currentExit);
- } else {
- Debug.Assert(thisExit == currentExit);
- }
- currentExit = oldExit;
- currentContainer = oldContainer;
- }
-
- ///
- /// Gets the next instruction after is executed.
- /// Returns NoExit when the next instruction cannot be identified;
- /// returns null when the end of a Block is reached (so that we could insert an arbitrary instruction)
- ///
- ILInstruction GetExit(ILInstruction inst)
- {
- SlotInfo slot = inst.SlotInfo;
- if (slot == Block.InstructionSlot) {
- Block block = (Block)inst.Parent;
- return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1);
- } else if (slot == TryInstruction.TryBlockSlot || slot == TryCatchHandler.BodySlot || slot == TryCatch.HandlerSlot || slot == PinnedRegion.BodySlot) {
- return GetExit(inst.Parent);
- } else if (slot == ILFunction.BodySlot) {
- return ReturnExit;
- }
- return NoExit;
- }
-
- protected internal override void VisitBlock(Block block)
- {
- // Don't use foreach loop, because the children might add to the block
- for (int i = 0; i < block.Instructions.Count; i++) {
- block.Instructions[i].AcceptVisitor(this);
- }
- }
-
- void HandleExit(ILInstruction inst)
- {
- if (currentExit == null) {
- currentExit = inst;
- inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
- } else if (ConditionDetection.CompatibleExitInstruction(inst, currentExit)) {
- inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
- }
- }
-
- protected internal override void VisitBranch(Branch inst)
- {
- if (!inst.TargetBlock.IsDescendantOf(currentContainer)) {
- HandleExit(inst);
- }
- }
-
- protected internal override void VisitLeave(Leave inst)
- {
- HandleExit(inst);
- }
-
- protected internal override void VisitReturn(Return inst)
- {
- if (inst.ReturnValue == null) {
- HandleExit(inst);
- } else {
- base.VisitReturn(inst);
- }
- }
- }
-}
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
index b2ecd7e3f..ed3b5b5ca 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
@@ -153,9 +153,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
if (loop != null) {
+ var headBlock = (Block)h.UserData;
+ context.Step($"Construct loop with head {headBlock.Label}", headBlock);
// loop now is the union of all natural loops with loop head h.
// Try to extend the loop to reduce the number of exit points:
- ExtendLoop(h, loop);
+ ExtendLoop(h, loop, out var exitPoint);
// Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
// (if the loop doesn't contain nested loops, this is a topological sort)
@@ -165,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
node.Visited = false; // reset visited flag so that we can find nested loops
Debug.Assert(h.Dominates(node), "The loop body must be dominated by the loop head");
}
- ConstructLoop(loop);
+ ConstructLoop(loop, exitPoint);
}
// Recurse into the dominator tree to find other possible loop heads
foreach (var child in h.DominatorTreeChildren) {
@@ -221,7 +223,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// Observations:
/// * If a node is in-loop, so are all its ancestors in the dominator tree (up to the loop entry point)
/// * If there are no exits reachable from a node (i.e. all paths from that node lead to a return/throw instruction),
- /// it is valid to put the dominator tree rooted at that node into either partition independently of
+ /// it is valid to put the group of nodes dominated by that node into either partition independently of
/// any other nodes except for the ancestors in the dominator tree.
/// (exception: the loop head itself must always be in-loop)
///
@@ -255,20 +257,24 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
///
/// Precondition: Requires that a node is marked as visited iff it is contained in the loop.
///
- void ExtendLoop(ControlFlowNode loopHead, List loop)
+ void ExtendLoop(ControlFlowNode loopHead, List loop, out ControlFlowNode exitPoint)
{
ComputeNodesWithReachableExits();
- ControlFlowNode exitPoint = FindExitPoint(loopHead, loop);
+ exitPoint = FindExitPoint(loopHead, loop);
Debug.Assert(!loop.Contains(exitPoint), "Cannot pick an exit point that is part of the natural loop");
if (exitPoint != null) {
- foreach (var node in TreeTraversal.PreOrder(loopHead, n => (n != exitPoint) ? n.DominatorTreeChildren : null)) {
- // TODO: did FindExitPoint really not touch the visited flag?
+ // Either we are in case 1 and just picked an exit that maximizes the amount of code
+ // outside the loop, or we are in case 2 and found an exit point via post-dominance.
+ var ep = exitPoint;
+ foreach (var node in TreeTraversal.PreOrder(loopHead, n => (n != ep) ? n.DominatorTreeChildren : null)) {
if (node != exitPoint && !node.Visited) {
loop.Add(node);
}
}
} else {
- // TODO: did FindExitPoint really not touch the visited flag?
+ // We are in case 2, but could not find a suitable exit point.
+ // Heuristically try to minimize the number of exit points
+ // (but we'll always end up with more than 1 exit and will require goto statements).
ExtendLoopHeuristic(loopHead, loop, loopHead);
}
}
@@ -314,7 +320,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
return false;
}
-
+
+ ///
+ /// Finds a suitable single exit point for the specified loop.
+ ///
+ /// This method must not write to the Visited flags on the CFG.
ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList naturalLoop)
{
if (!nodeHasReachableExit[loopHead.UserIndex]) {
@@ -365,6 +375,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// over the dominator tree and pick the node with the maximum amount of code.
///
/// Code amount in and its dominated nodes.
+ /// This method must not write to the Visited flags on the CFG.
int PickExitPoint(ControlFlowNode node, ref ControlFlowNode exitPoint, ref int exitPointCodeAmount)
{
int codeAmount = ((Block)node.UserData).Children.Count;
@@ -474,10 +485,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
///
/// Move the blocks associated with the loop into a new block container.
///
- void ConstructLoop(List loop)
+ void ConstructLoop(List loop, ControlFlowNode exitPoint)
{
Block oldEntryPoint = (Block)loop[0].UserData;
-
+ Block exitTargetBlock = (Block)exitPoint?.UserData;
+
BlockContainer loopContainer = new BlockContainer();
Block newEntryPoint = new Block();
loopContainer.Blocks.Add(newEntryPoint);
@@ -487,6 +499,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
newEntryPoint.FinalInstruction = oldEntryPoint.FinalInstruction;
newEntryPoint.ILRange = oldEntryPoint.ILRange;
oldEntryPoint.Instructions.ReplaceList(new[] { loopContainer });
+ if (exitTargetBlock != null)
+ oldEntryPoint.Instructions.Add(new Branch(exitTargetBlock));
oldEntryPoint.FinalInstruction = new Nop();
// Move other blocks into the loop body: they're all dominated by the loop header,
@@ -502,8 +516,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Rewrite branches within the loop from oldEntryPoint to newEntryPoint:
foreach (var branch in loopContainer.Descendants.OfType()) {
- if (branch.TargetBlock == oldEntryPoint)
+ if (branch.TargetBlock == oldEntryPoint) {
branch.TargetBlock = newEntryPoint;
+ } else if (branch.TargetBlock == exitTargetBlock) {
+ branch.ReplaceWith(new Leave(loopContainer) { ILRange = branch.ILRange });
+ }
}
}
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs
index 3ecbf5ff9..a4c027173 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs
@@ -71,7 +71,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
var blockContext = new BlockTransformContext(context);
blockContext.Function = function;
- foreach (var container in function.Descendants.OfType()) {
+ foreach (var container in function.Descendants.OfType().ToList()) {
context.CancellationToken.ThrowIfCancellationRequested();
var cfg = LoopDetection.BuildCFG(container);
Dominance.ComputeDominance(cfg[0], context.CancellationToken);
diff --git a/ICSharpCode.Decompiler/IL/Transforms/Stepper.cs b/ICSharpCode.Decompiler/IL/Transforms/Stepper.cs
index eef39e433..87175e5c2 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/Stepper.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/Stepper.cs
@@ -94,7 +94,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
private Node StepInternal(string description, ILInstruction near)
{
- if (step >= StepLimit) {
+ if (step == StepLimit) {
if (IsDebug)
Debugger.Break();
else
diff --git a/ICSharpCode.Decompiler/Tests/TestCases/Correctness/ControlFlow.cs b/ICSharpCode.Decompiler/Tests/TestCases/Correctness/ControlFlow.cs
index 0015fc119..c2b77e7b9 100644
--- a/ICSharpCode.Decompiler/Tests/TestCases/Correctness/ControlFlow.cs
+++ b/ICSharpCode.Decompiler/Tests/TestCases/Correctness/ControlFlow.cs
@@ -87,4 +87,19 @@ class ControlFlow
Console.WriteLine("else");
}
}
+
+ int Dim2Search(int arg)
+ {
+ var tens = new[] { 10, 20, 30 };
+ var ones = new[] { 1, 2, 3 };
+
+ for (int i = 0; i < tens.Length; i++) {
+ for (int j = 0; j < ones.Length; j++) {
+ if (tens[i] + ones[j] == arg)
+ return i;
+ }
+ }
+
+ return -1;
+ }
}
diff --git a/ILSpy/ILSpyTraceListener.cs b/ILSpy/ILSpyTraceListener.cs
index 162a3d093..3009419cc 100644
--- a/ILSpy/ILSpyTraceListener.cs
+++ b/ILSpy/ILSpyTraceListener.cs
@@ -70,7 +70,7 @@ namespace ICSharpCode.ILSpy
thread.Start();
thread.Join();
if (result == 0) { // throw
- throw new Exception(message);
+ throw new AssertionFailedException(message);
} else if (result == 1) { // debug
Debugger.Break();
} else if (result == 2) { // ignore
@@ -96,4 +96,9 @@ namespace ICSharpCode.ILSpy
}
}
}
+
+ class AssertionFailedException : Exception
+ {
+ public AssertionFailedException(string message) : base(message) { }
+ }
}
diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs
index 7ab2d80e0..a3ce6787d 100644
--- a/ILSpy/Languages/ILAstLanguage.cs
+++ b/ILSpy/Languages/ILAstLanguage.cs
@@ -161,14 +161,14 @@ namespace ICSharpCode.ILSpy
try {
il.RunTransforms(transforms, context);
} catch (StepLimitReachedException) {
- il.WriteTo(output);
- return;
+ } finally {
+ // update stepper even if a transform crashed unexpectedly
+ if (options.StepLimit == int.MaxValue) {
+ Stepper = context.Stepper;
+ OnStepperUpdated(new EventArgs());
+ }
}
il.WriteTo(output);
- if (options.StepLimit == int.MaxValue) {
- Stepper = context.Stepper;
- OnStepperUpdated(new EventArgs());
- }
}
}
}