diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs index e8e4f371d..e01477e83 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs @@ -303,7 +303,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // We need to pick our exit point so that all paths from the loop head // to the reachable exits run through that exit point. var cfg = context.ControlFlowGraph.cfg; - var revCfg = PrepareReverseCFG(loopHead, treatBackEdgesAsExits); + var revCfg = PrepareReverseCFG(loopHead, treatBackEdgesAsExits, out int exitNodeArity); //ControlFlowNode.ExportGraph(cfg).Show("cfg"); //ControlFlowNode.ExportGraph(revCfg).Show("rev"); ControlFlowNode commonAncestor = revCfg[loopHead.UserIndex]; @@ -330,7 +330,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow commonAncestor = commonAncestor.ImmediateDominator; } // least common post-dominator is the artificial exit node - return null; + // This means we're in one of two cases: + // * The loop might have multiple exit points. + // -> we should return null + // * The loop has a single exit point that wasn't considered during post-dominance analysis. + // (which means the single exit isn't dominated by the loop head) + // -> we should return NoExitPoint so that all code dominated by the loop head is included into the loop + if (exitNodeArity > 1) { + return null; + } else { + // If exitNodeArity == 0, we should maybe look test if our exits out of the block container are all compatible? + // but I don't think it hurts to have a bit too much code inside the loop in this rare case. + return NoExitPoint; + } } } @@ -407,14 +419,33 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow PickExitPoint(child, ref exitPoint, ref exitPointILOffset); } } - - ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead, bool treatBackEdgesAsExits) + + /// + /// Constructs a new control flow graph. + /// Each node cfg[i] has a corresponding node rev[i]. + /// Edges are only created for nodes dominated by loopHead, and are in reverse from their direction + /// in the primary CFG. + /// An artificial exit node is used for edges that leave the set of nodes dominated by loopHead, + /// or that leave the block Container. + /// + /// Entry point of the loop. + /// Whether to treat loop back edges as exit points. + /// out: The number of different CFG nodes. + /// Possible values: + /// 0 = no CFG nodes used as exit nodes (although edges leaving the block container might still be exits); + /// 1 = a single CFG node (not dominated by loopHead) was used as an exit node; + /// 2 = more than one CFG node (not dominated by loopHead) was used as an exit node. + /// + /// + ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead, bool treatBackEdgesAsExits, out int exitNodeArity) { ControlFlowNode[] cfg = context.ControlFlowGraph.cfg; ControlFlowNode[] rev = new ControlFlowNode[cfg.Length + 1]; for (int i = 0; i < cfg.Length; i++) { rev[i] = new ControlFlowNode { UserIndex = i, UserData = cfg[i].UserData }; } + ControlFlowNode nodeTreatedAsExitNode = null; + bool multipleNodesTreatedAsExitNodes = false; ControlFlowNode exitNode = new ControlFlowNode { UserIndex = -1 }; rev[cfg.Length] = exitNode; for (int i = 0; i < cfg.Length; i++) { @@ -425,6 +456,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (loopHead.Dominates(succ) && (!treatBackEdgesAsExits || loopHead != succ)) { rev[succ.UserIndex].AddEdgeTo(rev[i]); } else { + if (nodeTreatedAsExitNode == null) + nodeTreatedAsExitNode = succ; + if (nodeTreatedAsExitNode != succ) + multipleNodesTreatedAsExitNodes = true; exitNode.AddEdgeTo(rev[i]); } } @@ -432,6 +467,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow exitNode.AddEdgeTo(rev[i]); } } + if (multipleNodesTreatedAsExitNodes) + exitNodeArity = 2; // more than 1 + else if (nodeTreatedAsExitNode != null) + exitNodeArity = 1; + else + exitNodeArity = 0; Dominance.ComputeDominance(exitNode, context.CancellationToken); return rev; }