diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs index 156feb48d..460903996 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs @@ -20,6 +20,7 @@ using System; using System.Diagnostics; using ICSharpCode.Decompiler.IL.Transforms; using System.Threading; +using System.Collections.Generic; namespace ICSharpCode.Decompiler.IL.ControlFlow { @@ -48,8 +49,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// public class DetectExitPoints : ILVisitor, IILTransform { - static readonly Nop ExitNotYetDetermined = new Nop(); - static readonly Nop NoExit = new Nop(); + static readonly Nop ExitNotYetDetermined = new Nop { Comment = "ExitNotYetDetermined" }; + static readonly Nop NoExit = new Nop { Comment = "NoExit" }; bool canIntroduceExitForReturn; @@ -106,12 +107,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// /// 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 + /// ExitNotYetDetermined 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; + /// + /// If currentExit==ExitNotYetDetermined, holds the list of potential exit instructions. + /// After the currentContainer was visited completely, one of these will be selected as exit instruction. + /// + List potentialExits; + public void Run(ILFunction function, ILTransformContext context) { cancellationToken = context.CancellationToken; @@ -129,12 +136,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow { var oldExit = currentExit; var oldContainer = currentContainer; + var oldPotentialExits = potentialExits; var thisExit = GetExit(container); currentExit = thisExit; currentContainer = container; + potentialExits = (thisExit == ExitNotYetDetermined ? new List() : null); base.VisitBlockContainer(container); - if (thisExit == ExitNotYetDetermined && currentExit != ExitNotYetDetermined) { + if (thisExit == ExitNotYetDetermined && potentialExits.Count > 0) { // This transform determined an exit point. + currentExit = ChooseExit(potentialExits); + foreach (var exit in potentialExits) { + if (CompatibleExitInstruction(currentExit, exit)) { + exit.ReplaceWith(new Leave(currentContainer) { ILRange = exit.ILRange }); + } + } Debug.Assert(!currentExit.MatchLeave(currentContainer)); ILInstruction inst = container; // traverse up to the block (we'll always find one because GetExit @@ -148,6 +163,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } currentExit = oldExit; currentContainer = oldContainer; + potentialExits = oldPotentialExits; + } + + static ILInstruction ChooseExit(List potentialExits) + { + ILInstruction first = potentialExits[0]; + if (first is Leave l && l.IsLeavingFunction) { + for (int i = 1; i < potentialExits.Count; i++) { + var exit = potentialExits[i]; + if (!(exit is Leave l2 && l2.IsLeavingFunction)) + return exit; + } + } + return first; } protected internal override void VisitBlock(Block block) @@ -162,8 +191,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow void HandleExit(ILInstruction inst) { if (currentExit == ExitNotYetDetermined && CanIntroduceAsExit(inst)) { - currentExit = inst; - inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange }); + potentialExits.Add(inst); } else if (CompatibleExitInstruction(inst, currentExit)) { inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange }); } @@ -198,44 +226,4 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow HandleExit(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) - { - Debug.Assert(block.Parent == context.Container); - currentContainer = context.Container; - exitPoint = DetectExitPoints.GetExit(context.Container); - Visit(block); - } - - void Visit(ILInstruction inst) - { - switch (inst.OpCode) { - 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); - } - } - } - */ }