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()); - } } } }