diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 99d0e66b9..a742391f7 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -658,6 +658,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs index 79b79d70c..7db8f7f61 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs @@ -279,7 +279,7 @@ namespace ICSharpCode.Decompiler.IL // Visit blocks in post-order BitSet visited = new BitSet(Blocks.Count); List postOrder = new List(); - Visit(EntryPoint); + GraphTraversal.DepthFirstSearch(new[] { EntryPoint }, Successors, postOrder.Add, MarkAsVisited, reverseSuccessors: true); postOrder.Reverse(); if (!deleteUnreachableBlocks) { @@ -291,24 +291,30 @@ namespace ICSharpCode.Decompiler.IL } return postOrder; - void Visit(Block block) + bool MarkAsVisited(Block block) { Debug.Assert(block.Parent == this); if (!visited[block.ChildIndex]) { visited[block.ChildIndex] = true; + return true; + } + else + { + return false; + } + } - foreach (var branch in block.Descendants.OfType()) + IEnumerable Successors(Block block) + { + foreach (var branch in block.Descendants.OfType()) + { + if (branch.TargetBlock.Parent == this) { - if (branch.TargetBlock.Parent == this) - { - Visit(branch.TargetBlock); - } + yield return branch.TargetBlock; } - - postOrder.Add(block); } - }; + } } /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs index fdfa34975..9ab6740fe 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs @@ -5,6 +5,7 @@ using System.Linq; using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.IL.ControlFlow; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { @@ -105,29 +106,36 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - void VisitBlock(ControlFlowNode cfgNode, BlockTransformContext context) + /// + /// Walks the dominator tree rooted at entryNode, calling the transforms on each block. + /// + void VisitBlock(ControlFlowNode entryNode, BlockTransformContext context) { - Block block = (Block)cfgNode.UserData; - context.StepStartGroup(block.Label, block); - - context.ControlFlowNode = cfgNode; - context.Block = block; - context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; - block.RunTransforms(PreOrderTransforms, context); - - // First, process the children in the dominator tree. - // The ConditionDetection transform requires dominated blocks to - // be already processed. - foreach (var child in cfgNode.DominatorTreeChildren) + IEnumerable Preorder(ControlFlowNode cfgNode) { - VisitBlock(child, context); + // preorder processing: + Block block = (Block)cfgNode.UserData; + context.StepStartGroup(block.Label, block); + + context.ControlFlowNode = cfgNode; + context.Block = block; + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + block.RunTransforms(PreOrderTransforms, context); + + // process the children + return cfgNode.DominatorTreeChildren; } - context.ControlFlowNode = cfgNode; - context.Block = block; - context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; - block.RunTransforms(PostOrderTransforms, context); - context.StepEndGroup(); + foreach (var cfgNode in TreeTraversal.PostOrder(entryNode, Preorder)) + { + // in post-order: + Block block = (Block)cfgNode.UserData; + context.ControlFlowNode = cfgNode; + context.Block = block; + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + block.RunTransforms(PostOrderTransforms, context); + context.StepEndGroup(); + } } } } diff --git a/ICSharpCode.Decompiler/Util/GraphTraversal.cs b/ICSharpCode.Decompiler/Util/GraphTraversal.cs new file mode 100644 index 000000000..f11310bf7 --- /dev/null +++ b/ICSharpCode.Decompiler/Util/GraphTraversal.cs @@ -0,0 +1,115 @@ +// Copyright (c) 2023 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. + +#nullable enable + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.Decompiler.Util; + +static class GraphTraversal +{ + /// + /// Depth-first-search of an graph data structure. + /// The two callbacks (successorFunc + postorderAction) will be called exactly once for each node reachable from startNodes. + /// + /// The start nodes. + /// Called multiple times per node. The first call should return true, subsequent calls must return false. + /// If this function is not provided, normal Equals/GetHashCode will be used to compare nodes. + /// The function that gets the successors of an element. Called in pre-order. + /// Called in post-order. + /// + /// With reverse_successors=True, the start_nodes and each list of successors will be handled in reverse order. + /// This is useful if the post-order will be reversed later (e.g. for a topological sort) + /// so that blocks which could be output in either order (e.g. then-block and else-block of an if) + /// will maintain the order of the edges (then-block before else-block). + /// + public static void DepthFirstSearch(IEnumerable startNodes, Func?> successorFunc, Action postorderAction, Func? visitedFunc = null, bool reverseSuccessors = false) + { + /* + Pseudocode: + def dfs_walk(start_nodes, successor_func, visited, postorder_func, reverse_successors): + if reverse_successors: + start_nodes = reversed(start_nodes) + for node in start_nodes: + if node in visited: continue + visited.insert(node) + children = successor_func(node) + dfs_walk(children, successor_func, visited, postorder_action, reverse_successors) + postorder_action(node) + + The actual implementation here is equivalent but does not use recursion, + so that we don't blow the stack on large graphs. + A single stack holds the "continuations" of work that needs to be done. + These can be either "visit continuations" (=execute the body of the pseudocode + loop for the given node) or "postorder continuations" (=execute postorder_action) + */ + // Use a List as stack (but allowing for the Reverse() usage) + var worklist = new List<(T node, bool isPostOrderContinuation)>(); + visitedFunc ??= new HashSet().Add; + foreach (T node in startNodes) + { + worklist.Add((node, false)); + } + if (!reverseSuccessors) + { + // Our use of a stack will reverse the order of the nodes. + // If that's not desired, restore original order by reversing twice. + worklist.Reverse(); + } + // Process outstanding continuations: + while (worklist.Count > 0) + { + var (node, isPostOrderContinuation) = worklist.Last(); + if (isPostOrderContinuation) + { + // Execute postorder_action + postorderAction(node); + worklist.RemoveAt(worklist.Count - 1); + continue; + } + // Execute body of loop + if (!visitedFunc(node)) + { + // Already visited + worklist.RemoveAt(worklist.Count - 1); + continue; + } + // foreach-loop-iteration will end with postorder_func call, + // so switch the type of continuation for this node + int oldWorkListSize = worklist.Count; + worklist[oldWorkListSize - 1] = (node, true); + // Create "visit continuations" for all successor nodes: + IEnumerable? children = successorFunc(node); + if (children != null) + { + foreach (T child in children) + { + worklist.Add((child, false)); + } + } + // Our use of a stack will reverse the order of the nodes. + // If that's not desired, restore original order by reversing twice. + if (!reverseSuccessors) + { + worklist.Reverse(oldWorkListSize, worklist.Count - oldWorkListSize); + } + } + } +}