diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 564870fe3..d87f57d48 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -32,6 +32,7 @@ using ICSharpCode.NRefactory.Utils; using Mono.Cecil; using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.IL.Transforms; namespace ICSharpCode.Decompiler.CSharp { @@ -39,6 +40,12 @@ namespace ICSharpCode.Decompiler.CSharp { readonly DecompilerTypeSystem typeSystem; + List ilTransforms = new List { + new LoopDetection(), + new TransformingVisitor(), + new TransformStackIntoVariables() + }; + List astTransforms = new List { //new PushNegation(), //new DelegateConstruction(context), @@ -56,13 +63,15 @@ namespace ICSharpCode.Decompiler.CSharp //new FlattenSwitchBlocks(), }; - List ilTransforms = new List { - new TransformingVisitor(), - new TransformStackIntoVariables() - }; - public CancellationToken CancellationToken { get; set; } + /// + /// IL transforms. + /// + public IList ILTransforms { + get { return ilTransforms; } + } + /// /// C# AST transforms. /// diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index b68a6ad88..efe7b0a43 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -73,6 +73,11 @@ namespace ICSharpCode.Decompiler.CSharp var falseStatement = inst.FalseInst.OpCode == OpCode.Nop ? null : Convert(inst.FalseInst); return new IfElseStatement(condition, trueStatement, falseStatement); } + + protected internal override Statement VisitLoop(Loop inst) + { + return new WhileStatement(new PrimitiveExpression(true), Convert(inst.Body)); + } protected internal override Statement VisitBranch(Branch inst) { diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowEdge.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowEdge.cs deleted file mode 100644 index bfab810cf..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowEdge.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Describes the type of a control flow egde. - /// - public enum JumpType - { - /// - /// A regular control flow edge. - /// - Normal, - /// - /// Jump to exception handler (an exception occurred) - /// - JumpToExceptionHandler, - /// - /// Jump from try block to leave target: - /// This is not a real jump, as the finally handler is executed first! - /// - LeaveTry, - /// - /// Jump at endfinally (to any of the potential leave targets). - /// For any leave-instruction, control flow enters the finally block - the edge to the leave target (LeaveTry) is not a real control flow edge. - /// EndFinally edges are inserted at the end of the finally block, jumping to any of the targets of the leave instruction. - /// This edge type is only used when copying of finally blocks is disabled (with copying, a normal deterministic edge is used at each copy of the endfinally node). - /// - EndFinally - } - - /// - /// Represents an edge in the control flow graph, pointing from Source to Target. - /// - public sealed class ControlFlowEdge - { - public readonly ControlFlowNode Source; - public readonly ControlFlowNode Target; - public readonly JumpType Type; - - public ControlFlowEdge(ControlFlowNode source, ControlFlowNode target, JumpType type) - { - this.Source = source; - this.Target = target; - this.Type = type; - } - - public override string ToString() - { - switch (Type) { - case JumpType.Normal: - return "#" + Target.BlockIndex; - case JumpType.JumpToExceptionHandler: - return "e:#" + Target.BlockIndex; - default: - return Type.ToString() + ":#" + Target.BlockIndex; - } - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs deleted file mode 100644 index 7cc815a60..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; -using System.Threading; - -using ICSharpCode.NRefactory.Utils; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Contains the control flow graph. - /// - /// Use ControlFlowGraph builder to create instances of the ControlFlowGraph. - public sealed class ControlFlowGraph - { - readonly ReadOnlyCollection nodes; - - public ControlFlowNode EntryPoint { - get { return nodes[0]; } - } - - public ControlFlowNode RegularExit { - get { return nodes[1]; } - } - - public ControlFlowNode ExceptionalExit { - get { return nodes[2]; } - } - - public ReadOnlyCollection Nodes { - get { return nodes; } - } - - internal ControlFlowGraph(ControlFlowNode[] nodes) - { - this.nodes = new ReadOnlyCollection(nodes); - Debug.Assert(EntryPoint.NodeType == ControlFlowNodeType.EntryPoint); - Debug.Assert(RegularExit.NodeType == ControlFlowNodeType.RegularExit); - Debug.Assert(ExceptionalExit.NodeType == ControlFlowNodeType.ExceptionalExit); - } - - public GraphVizGraph ExportGraph() - { - GraphVizGraph graph = new GraphVizGraph(); - foreach (ControlFlowNode node in nodes) { - graph.AddNode(new GraphVizNode(node.BlockIndex) { label = node.ToString(), shape = "box" }); - } - foreach (ControlFlowNode node in nodes) { - foreach (ControlFlowEdge edge in node.Outgoing) { - GraphVizEdge e = new GraphVizEdge(edge.Source.BlockIndex, edge.Target.BlockIndex); - switch (edge.Type) { - case JumpType.Normal: - break; - case JumpType.LeaveTry: - case JumpType.EndFinally: - e.color = "red"; - break; - default: - e.color = "gray"; - //e.constraint = false; - break; - } - graph.AddEdge(e); - } - if (node.ImmediateDominator != null) { - graph.AddEdge(new GraphVizEdge(node.ImmediateDominator.BlockIndex, node.BlockIndex) { color = "green", constraint = false }); - } - } - return graph; - } - - /// - /// Resets "Visited" to false for all nodes in this graph. - /// - public void ResetVisited() - { - foreach (ControlFlowNode node in nodes) { - node.Visited = false; - } - } - - /// - /// Computes the dominator tree. - /// - public void ComputeDominance(CancellationToken cancellationToken = default(CancellationToken)) - { - // A Simple, Fast Dominance Algorithm - // Keith D. Cooper, Timothy J. Harvey and Ken Kennedy - - EntryPoint.ImmediateDominator = EntryPoint; - bool changed = true; - while (changed) { - changed = false; - ResetVisited(); - - cancellationToken.ThrowIfCancellationRequested(); - - // for all nodes b except the entry point - EntryPoint.TraversePreOrder( - b => b.Successors, - b => { - if (b != EntryPoint) { - ControlFlowNode newIdom = b.Predecessors.First(block => block.Visited && block != b); - // for all other predecessors p of b - foreach (ControlFlowNode p in b.Predecessors) { - if (p != b && p.ImmediateDominator != null) { - newIdom = FindCommonDominator(p, newIdom); - } - } - if (b.ImmediateDominator != newIdom) { - b.ImmediateDominator = newIdom; - changed = true; - } - } - }); - } - EntryPoint.ImmediateDominator = null; - foreach (ControlFlowNode node in nodes) { - if (node.ImmediateDominator != null) - node.ImmediateDominator.DominatorTreeChildren.Add(node); - } - } - - static ControlFlowNode FindCommonDominator(ControlFlowNode b1, ControlFlowNode b2) - { - // Here we could use the postorder numbers to get rid of the hashset, see "A Simple, Fast Dominance Algorithm" - HashSet path1 = new HashSet(); - while (b1 != null && path1.Add(b1)) - b1 = b1.ImmediateDominator; - while (b2 != null) { - if (path1.Contains(b2)) - return b2; - else - b2 = b2.ImmediateDominator; - } - throw new Exception("No common dominator found!"); - } - - /// - /// Computes dominance frontiers. - /// This method requires that the dominator tree is already computed! - /// - public void ComputeDominanceFrontier() - { - ResetVisited(); - - EntryPoint.TraversePostOrder( - b => b.DominatorTreeChildren, - n => { - //logger.WriteLine("Calculating dominance frontier for " + n.Name); - n.DominanceFrontier = new HashSet(); - // DF_local computation - foreach (ControlFlowNode succ in n.Successors) { - if (succ.ImmediateDominator != n) { - //logger.WriteLine(" local: " + succ.Name); - n.DominanceFrontier.Add(succ); - } - } - // DF_up computation - foreach (ControlFlowNode child in n.DominatorTreeChildren) { - foreach (ControlFlowNode p in child.DominanceFrontier) { - if (p.ImmediateDominator != n) { - //logger.WriteLine(" DF_up: " + p.Name + " (child=" + child.Name); - n.DominanceFrontier.Add(p); - } - } - } - }); - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs deleted file mode 100644 index 4b0c434d4..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Constructs the Control Flow Graph from a Cecil method body. - /// - public sealed class ControlFlowGraphBuilder - { - public static ControlFlowGraph Build(MethodBody methodBody) - { - return new ControlFlowGraphBuilder(methodBody).Build(); - } - - // This option controls how finally blocks are handled: - // false means that the endfinally instruction will jump to any of the leave targets (EndFinally edge type). - // true means that a copy of the whole finally block is created for each leave target. In this case, each endfinally node will be connected with the leave - // target using a normal edge. - bool copyFinallyBlocks = false; - - MethodBody methodBody; - int[] offsets; // array index = instruction index; value = IL offset - bool[] hasIncomingJumps; // array index = instruction index - List nodes = new List(); - ControlFlowNode entryPoint; - ControlFlowNode regularExit; - ControlFlowNode exceptionalExit; - - private ControlFlowGraphBuilder(MethodBody methodBody) - { - this.methodBody = methodBody; - offsets = methodBody.Instructions.Select(i => i.Offset).ToArray(); - hasIncomingJumps = new bool[methodBody.Instructions.Count]; - - entryPoint = new ControlFlowNode(0, 0, ControlFlowNodeType.EntryPoint); - nodes.Add(entryPoint); - regularExit = new ControlFlowNode(1, -1, ControlFlowNodeType.RegularExit); - nodes.Add(regularExit); - exceptionalExit = new ControlFlowNode(2, -1, ControlFlowNodeType.ExceptionalExit); - nodes.Add(exceptionalExit); - Debug.Assert(nodes.Count == 3); - } - - /// - /// Determines the index of the instruction (for use with the hasIncomingJumps array) - /// - int GetInstructionIndex(Instruction inst) - { - int index = Array.BinarySearch(offsets, inst.Offset); - Debug.Assert(index >= 0); - return index; - } - - /// - /// Builds the ControlFlowGraph. - /// - public ControlFlowGraph Build() - { - CalculateHasIncomingJumps(); - CreateNodes(); - CreateRegularControlFlow(); - CreateExceptionalControlFlow(); - if (copyFinallyBlocks) - CopyFinallyBlocksIntoLeaveEdges(); - else - TransformLeaveEdges(); - return new ControlFlowGraph(nodes.ToArray()); - } - - #region Step 1: calculate which instructions are the targets of jump instructions. - void CalculateHasIncomingJumps() - { - foreach (Instruction inst in methodBody.Instructions) { - if (inst.OpCode.OperandType == OperandType.InlineBrTarget || inst.OpCode.OperandType == OperandType.ShortInlineBrTarget) { - hasIncomingJumps[GetInstructionIndex((Instruction)inst.Operand)] = true; - } else if (inst.OpCode.OperandType == OperandType.InlineSwitch) { - foreach (Instruction i in (Instruction[])inst.Operand) - hasIncomingJumps[GetInstructionIndex(i)] = true; - } - } - foreach (ExceptionHandler eh in methodBody.ExceptionHandlers) { - if (eh.FilterStart != null) { - hasIncomingJumps[GetInstructionIndex(eh.FilterStart)] = true; - } - hasIncomingJumps[GetInstructionIndex(eh.HandlerStart)] = true; - } - } - #endregion - - #region Step 2: create nodes - void CreateNodes() - { - // Step 2a: find basic blocks and create nodes for them - for (int i = 0; i < methodBody.Instructions.Count; i++) { - Instruction blockStart = methodBody.Instructions[i]; - ExceptionHandler blockStartEH = FindInnermostExceptionHandler(blockStart.Offset); - // try and see how big we can make that block: - for (; i + 1 < methodBody.Instructions.Count; i++) { - Instruction inst = methodBody.Instructions[i]; - if (IsBranch(inst.OpCode) || CanThrowException(inst.OpCode)) - break; - if (hasIncomingJumps[i + 1]) - break; - if (inst.Next != null) { - // ensure that blocks never contain instructions from different try blocks - ExceptionHandler instEH = FindInnermostExceptionHandler(inst.Next.Offset); - if (instEH != blockStartEH) - break; - } - } - - nodes.Add(new ControlFlowNode(nodes.Count, blockStart, methodBody.Instructions[i])); - } - // Step 2b: Create special nodes for the exception handling constructs - foreach (ExceptionHandler handler in methodBody.ExceptionHandlers) { - if (handler.HandlerType == ExceptionHandlerType.Filter) - throw new NotSupportedException(); - ControlFlowNode endFinallyOrFaultNode = null; - if (handler.HandlerType == ExceptionHandlerType.Finally || handler.HandlerType == ExceptionHandlerType.Fault) { - endFinallyOrFaultNode = new ControlFlowNode(nodes.Count, handler.HandlerEnd.Offset, ControlFlowNodeType.EndFinallyOrFault); - nodes.Add(endFinallyOrFaultNode); - } - nodes.Add(new ControlFlowNode(nodes.Count, handler, endFinallyOrFaultNode)); - } - } - #endregion - - #region Step 3: create edges for the normal flow of control (assuming no exceptions thrown) - void CreateRegularControlFlow() - { - CreateEdge(entryPoint, methodBody.Instructions[0], JumpType.Normal); - foreach (ControlFlowNode node in nodes) { - if (node.End != null) { - // create normal edges from one instruction to the next - if (!OpCodeInfo.IsUnconditionalBranch(node.End.OpCode)) - CreateEdge(node, node.End.Next, JumpType.Normal); - - // create edges for branch instructions - if (node.End.OpCode.OperandType == OperandType.InlineBrTarget || node.End.OpCode.OperandType == OperandType.ShortInlineBrTarget) { - if (node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S) { - var handlerBlock = FindInnermostHandlerBlock(node.End.Offset); - if (handlerBlock.NodeType == ControlFlowNodeType.FinallyOrFaultHandler) - CreateEdge(node, (Instruction)node.End.Operand, JumpType.LeaveTry); - else - CreateEdge(node, (Instruction)node.End.Operand, JumpType.Normal); - } else { - CreateEdge(node, (Instruction)node.End.Operand, JumpType.Normal); - } - } else if (node.End.OpCode.OperandType == OperandType.InlineSwitch) { - foreach (Instruction i in (Instruction[])node.End.Operand) - CreateEdge(node, i, JumpType.Normal); - } - - // create edges for return instructions - if (node.End.OpCode.FlowControl == FlowControl.Return) { - switch (node.End.OpCode.Code) { - case Code.Ret: - CreateEdge(node, regularExit, JumpType.Normal); - break; - case Code.Endfinally: - ControlFlowNode handlerBlock = FindInnermostHandlerBlock(node.End.Offset); - if (handlerBlock.EndFinallyOrFaultNode == null) - throw new InvalidProgramException("Found endfinally in block " + handlerBlock); - CreateEdge(node, handlerBlock.EndFinallyOrFaultNode, JumpType.Normal); - break; - default: - throw new NotSupportedException(node.End.OpCode.ToString()); - } - } - } - } - } - #endregion - - #region Step 4: create edges for the exceptional control flow (from instructions that might throw, to the innermost containing exception handler) - void CreateExceptionalControlFlow() - { - foreach (ControlFlowNode node in nodes) { - if (node.End != null && CanThrowException(node.End.OpCode)) { - CreateEdge(node, FindInnermostExceptionHandlerNode(node.End.Offset), JumpType.JumpToExceptionHandler); - } - if (node.ExceptionHandler != null) { - if (node.EndFinallyOrFaultNode != null) { - // For Fault and Finally blocks, create edge from "EndFinally" to next exception handler. - // This represents the exception bubbling up after finally block was executed. - CreateEdge(node.EndFinallyOrFaultNode, FindParentExceptionHandlerNode(node), JumpType.JumpToExceptionHandler); - } else { - // For Catch blocks, create edge from "CatchHandler" block (at beginning) to next exception handler. - // This represents the exception bubbling up because it did not match the type of the catch block. - CreateEdge(node, FindParentExceptionHandlerNode(node), JumpType.JumpToExceptionHandler); - } - CreateEdge(node, node.ExceptionHandler.HandlerStart, JumpType.Normal); - } - } - } - - ExceptionHandler FindInnermostExceptionHandler(int instructionOffsetInTryBlock) - { - foreach (ExceptionHandler h in methodBody.ExceptionHandlers) { - if (h.TryStart.Offset <= instructionOffsetInTryBlock && instructionOffsetInTryBlock < h.TryEnd.Offset) { - return h; - } - } - return null; - } - - ControlFlowNode FindInnermostExceptionHandlerNode(int instructionOffsetInTryBlock) - { - ExceptionHandler h = FindInnermostExceptionHandler(instructionOffsetInTryBlock); - if (h != null) - return nodes.Single(n => n.ExceptionHandler == h && n.CopyFrom == null); - else - return exceptionalExit; - } - - ControlFlowNode FindInnermostHandlerBlock(int instructionOffset) - { - foreach (ExceptionHandler h in methodBody.ExceptionHandlers) { - if (h.TryStart.Offset <= instructionOffset && instructionOffset < h.TryEnd.Offset - || h.HandlerStart.Offset <= instructionOffset && instructionOffset < h.HandlerEnd.Offset) - { - return nodes.Single(n => n.ExceptionHandler == h && n.CopyFrom == null); - } - } - return exceptionalExit; - } - - ControlFlowNode FindParentExceptionHandlerNode(ControlFlowNode exceptionHandler) - { - Debug.Assert(exceptionHandler.NodeType == ControlFlowNodeType.CatchHandler - || exceptionHandler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); - int offset = exceptionHandler.ExceptionHandler.TryStart.Offset; - for (int i = exceptionHandler.BlockIndex + 1; i < nodes.Count; i++) { - ExceptionHandler h = nodes[i].ExceptionHandler; - if (h != null && h.TryStart.Offset <= offset && offset < h.TryEnd.Offset) - return nodes[i]; - } - return exceptionalExit; - } - #endregion - - #region Step 5a: replace LeaveTry edges with EndFinally edges - // this is used only for copyFinallyBlocks==false; see Step 5b otherwise - void TransformLeaveEdges() - { - for (int i = nodes.Count - 1; i >= 0; i--) { - ControlFlowNode node = nodes[i]; - if (node.End != null && node.Outgoing.Count == 1 && node.Outgoing[0].Type == JumpType.LeaveTry) { - Debug.Assert(node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S); - - ControlFlowNode target = node.Outgoing[0].Target; - // remove the edge - target.Incoming.Remove(node.Outgoing[0]); - node.Outgoing.Clear(); - - ControlFlowNode handler = FindInnermostExceptionHandlerNode(node.End.Offset); - Debug.Assert(handler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); - - CreateEdge(node, handler, JumpType.Normal); - CreateEdge(handler.EndFinallyOrFaultNode, target, JumpType.EndFinally); - } - } - } - #endregion - - #region Step 5b: copy finally blocks into the LeaveTry edges - void CopyFinallyBlocksIntoLeaveEdges() - { - // We need to process try-finally blocks inside-out. - // We'll do that by going through all instructions in reverse order - for (int i = nodes.Count - 1; i >= 0; i--) { - ControlFlowNode node = nodes[i]; - if (node.End != null && node.Outgoing.Count == 1 && node.Outgoing[0].Type == JumpType.LeaveTry) { - Debug.Assert(node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S); - - ControlFlowNode target = node.Outgoing[0].Target; - // remove the edge - target.Incoming.Remove(node.Outgoing[0]); - node.Outgoing.Clear(); - - ControlFlowNode handler = FindInnermostExceptionHandlerNode(node.End.Offset); - Debug.Assert(handler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); - - ControlFlowNode copy = CopyFinallySubGraph(handler, handler.EndFinallyOrFaultNode, target); - CreateEdge(node, copy, JumpType.Normal); - } - } - } - - /// - /// Creates a copy of all nodes pointing to 'end' and replaces those references with references to 'newEnd'. - /// Nodes pointing to the copied node are copied recursively to update those references, too. - /// This recursion stops at 'start'. The modified version of start is returned. - /// - ControlFlowNode CopyFinallySubGraph(ControlFlowNode start, ControlFlowNode end, ControlFlowNode newEnd) - { - return new CopyFinallySubGraphLogic(this, start, end, newEnd).CopyFinallySubGraph(); - } - - class CopyFinallySubGraphLogic - { - readonly ControlFlowGraphBuilder builder; - readonly Dictionary oldToNew = new Dictionary(); - readonly ControlFlowNode start; - readonly ControlFlowNode end; - readonly ControlFlowNode newEnd; - - public CopyFinallySubGraphLogic(ControlFlowGraphBuilder builder, ControlFlowNode start, ControlFlowNode end, ControlFlowNode newEnd) - { - this.builder = builder; - this.start = start; - this.end = end; - this.newEnd = newEnd; - } - - internal ControlFlowNode CopyFinallySubGraph() - { - foreach (ControlFlowNode n in end.Predecessors) { - CollectNodes(n); - } - foreach (var pair in oldToNew) - ReconstructEdges(pair.Key, pair.Value); - return GetNew(start); - } - - void CollectNodes(ControlFlowNode node) - { - if (node == end || node == newEnd) - throw new InvalidOperationException("unexpected cycle involving finally construct"); - if (!oldToNew.ContainsKey(node)) { - int newBlockIndex = builder.nodes.Count; - ControlFlowNode copy; - switch (node.NodeType) { - case ControlFlowNodeType.Normal: - copy = new ControlFlowNode(newBlockIndex, node.Start, node.End); - break; - case ControlFlowNodeType.FinallyOrFaultHandler: - copy = new ControlFlowNode(newBlockIndex, node.ExceptionHandler, node.EndFinallyOrFaultNode); - break; - default: - // other nodes shouldn't occur when copying finally blocks - throw new NotSupportedException(node.NodeType.ToString()); - } - copy.CopyFrom = node; - builder.nodes.Add(copy); - oldToNew.Add(node, copy); - - if (node != start) { - foreach (ControlFlowNode n in node.Predecessors) { - CollectNodes(n); - } - } - } - } - - void ReconstructEdges(ControlFlowNode oldNode, ControlFlowNode newNode) - { - foreach (ControlFlowEdge oldEdge in oldNode.Outgoing) { - builder.CreateEdge(newNode, GetNew(oldEdge.Target), oldEdge.Type); - } - } - - ControlFlowNode GetNew(ControlFlowNode oldNode) - { - if (oldNode == end) - return newEnd; - ControlFlowNode newNode; - if (oldToNew.TryGetValue(oldNode, out newNode)) - return newNode; - return oldNode; - } - } - #endregion - - #region CreateEdge methods - void CreateEdge(ControlFlowNode fromNode, Instruction toInstruction, JumpType type) - { - CreateEdge(fromNode, nodes.Single(n => n.Start == toInstruction), type); - } - - void CreateEdge(ControlFlowNode fromNode, ControlFlowNode toNode, JumpType type) - { - ControlFlowEdge edge = new ControlFlowEdge(fromNode, toNode, type); - fromNode.Outgoing.Add(edge); - toNode.Incoming.Add(edge); - } - #endregion - - #region OpCode info - static bool CanThrowException(OpCode opcode) - { - if (opcode.OpCodeType == OpCodeType.Prefix) - return false; - return OpCodeInfo.Get(opcode).CanThrow; - } - - static bool IsBranch(OpCode opcode) - { - if (opcode.OpCodeType == OpCodeType.Prefix) - return false; - switch (opcode.FlowControl) { - case FlowControl.Cond_Branch: - case FlowControl.Branch: - case FlowControl.Throw: - case FlowControl.Return: - return true; - case FlowControl.Next: - case FlowControl.Call: - return false; - default: - throw new NotSupportedException(opcode.FlowControl.ToString()); - } - } - #endregion - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs index cab507bc9..5a9dac0ed 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs @@ -22,95 +22,37 @@ using System.Diagnostics; using System.IO; using System.Linq; +using ICSharpCode.NRefactory.Utils; using ICSharpCode.Decompiler.Disassembler; using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.FlowAnalysis { - /// - /// Type of the control flow node - /// - public enum ControlFlowNodeType - { - /// - /// A normal node represents a basic block. - /// - Normal, - /// - /// The entry point of the method. - /// - EntryPoint, - /// - /// The exit point of the method (every ret instruction branches to this node) - /// - RegularExit, - /// - /// This node represents leaving a method irregularly by throwing an exception. - /// - ExceptionalExit, - /// - /// This node is used as a header for exception handler blocks. - /// - CatchHandler, - /// - /// This node is used as a header for finally blocks and fault blocks. - /// Every leave instruction in the try block leads to the handler of the containing finally block; - /// and exceptional control flow also leads to this handler. - /// - FinallyOrFaultHandler, - /// - /// This node is used as footer for finally blocks and fault blocks. - /// Depending on the "copyFinallyBlocks" option used when creating the graph, it is connected with all leave targets using - /// EndFinally edges (when not copying); or with a specific leave target using a normal edge (when copying). - /// For fault blocks, an exception edge is used to represent the "re-throwing" of the exception. - /// - EndFinallyOrFault - } - /// /// Represents a block in the control flow graph. /// - public sealed class ControlFlowNode + public class ControlFlowNode { - /// - /// Index of this node in the ControlFlowGraph.Nodes collection. - /// - public readonly int BlockIndex; - - /// - /// Gets the IL offset of this node. - /// - public readonly int Offset; - - /// - /// Type of the node. - /// - public readonly ControlFlowNodeType NodeType; + public object UserData; /// - /// If this node is a FinallyOrFaultHandler node, this field points to the corresponding EndFinallyOrFault node. - /// Otherwise, this field is null. + /// Visited flag, used in various algorithms. /// - public readonly ControlFlowNode EndFinallyOrFaultNode; + public bool Visited; /// - /// Visited flag, used in various algorithms. - /// Before using it in your algorithm, reset it to false by calling ControlFlowGraph.ResetVisited(); + /// Gets the node index in a post-order traversal of the control flow graph, starting at the + /// entry point. This field gets computed by dominance analysis. /// - public bool Visited; + public int PostOrderNumber; /// /// Gets whether this node is reachable. Requires that dominance is computed! /// public bool IsReachable { - get { return ImmediateDominator != null || NodeType == ControlFlowNodeType.EntryPoint; } + get { return DominatorTreeChildren != null; } } - /// - /// Signalizes that this node is a copy of another node. - /// - public ControlFlowNode CopyFrom { get; internal set; } - /// /// Gets the immediate dominator (the parent in the dominator tree). /// Null if dominance has not been calculated; or if the node is unreachable. @@ -119,113 +61,24 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// /// List of children in the dominator tree. + /// Null if dominance has not been calculated; or if the node is unreachable. /// - public readonly List DominatorTreeChildren = new List(); - - /// - /// The dominance frontier of this node. - /// This is the set of nodes for which this node dominates a predecessor, but which are not strictly dominated by this node. - /// - /// - /// b.DominanceFrontier = { y in CFG; (exists p in predecessors(y): b dominates p) and not (b strictly dominates y)} - /// - public HashSet DominanceFrontier; - - /// - /// Start of code block represented by this node. Only set for nodetype == Normal. - /// - public readonly Instruction Start; - - /// - /// End of the code block represented by this node. Only set for nodetype == Normal. - /// The end is exclusive, the end instruction itself does not belong to this block. - /// - public readonly Instruction End; - - /// - /// Gets the exception handler associated with this node. - /// Only set for nodetype == CatchHandler or nodetype == FinallyOrFaultHandler. - /// - public readonly ExceptionHandler ExceptionHandler; + public List DominatorTreeChildren { get; internal set; } /// /// List of incoming control flow edges. /// - public readonly List Incoming = new List(); + public readonly List Predecessors = new List(); /// /// List of outgoing control flow edges. /// - public readonly List Outgoing = new List(); - - /// - /// Any user data - /// - public object UserData; - - internal ControlFlowNode(int blockIndex, int offset, ControlFlowNodeType nodeType) - { - this.BlockIndex = blockIndex; - this.Offset = offset; - this.NodeType = nodeType; - } - - internal ControlFlowNode(int blockIndex, Instruction start, Instruction end) - { - if (start == null) - throw new ArgumentNullException("start"); - if (end == null) - throw new ArgumentNullException("end"); - this.BlockIndex = blockIndex; - this.NodeType = ControlFlowNodeType.Normal; - this.Start = start; - this.End = end; - this.Offset = start.Offset; - } + public readonly List Successors = new List(); - internal ControlFlowNode(int blockIndex, ExceptionHandler exceptionHandler, ControlFlowNode endFinallyOrFaultNode) + public void AddEdgeTo(ControlFlowNode target) { - this.BlockIndex = blockIndex; - this.NodeType = endFinallyOrFaultNode != null ? ControlFlowNodeType.FinallyOrFaultHandler : ControlFlowNodeType.CatchHandler; - this.ExceptionHandler = exceptionHandler; - this.EndFinallyOrFaultNode = endFinallyOrFaultNode; - Debug.Assert((exceptionHandler.HandlerType == ExceptionHandlerType.Finally || exceptionHandler.HandlerType == ExceptionHandlerType.Fault) == (endFinallyOrFaultNode != null)); - this.Offset = exceptionHandler.HandlerStart.Offset; - } - - /// - /// Gets all predecessors (=sources of incoming edges) - /// - public IEnumerable Predecessors { - get { - return Incoming.Select(e => e.Source); - } - } - - /// - /// Gets all successors (=targets of outgoing edges) - /// - public IEnumerable Successors { - get { - return Outgoing.Select(e => e.Target); - } - } - - /// - /// Gets all instructions in this node. - /// Returns an empty list for special nodes that don't have any instructions. - /// - public IEnumerable Instructions { - get { - Instruction inst = Start; - if (inst != null) { - yield return inst; - while (inst != End) { - inst = inst.Next; - yield return inst; - } - } - } + this.Successors.Add(target); + target.Predecessors.Add(this); } public void TraversePreOrder(Func> children, Action visitAction) @@ -248,45 +101,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis visitAction(this); } - public override string ToString() - { - StringWriter writer = new StringWriter(); - switch (NodeType) { - case ControlFlowNodeType.Normal: - writer.Write("Block #{0}", BlockIndex); - if (Start != null) - writer.Write(": IL_{0:x4}", Start.Offset); - if (End != null) - writer.Write(" to IL_{0:x4}", End.GetEndOffset()); - break; - case ControlFlowNodeType.CatchHandler: - case ControlFlowNodeType.FinallyOrFaultHandler: - writer.Write("Block #{0}: {1}: ", BlockIndex, NodeType); - Disassembler.DisassemblerHelpers.WriteTo(ExceptionHandler, new PlainTextOutput(writer)); - break; - default: - writer.Write("Block #{0}: {1}", BlockIndex, NodeType); - break; - } -// if (ImmediateDominator != null) { -// writer.WriteLine(); -// writer.Write("ImmediateDominator: #{0}", ImmediateDominator.BlockIndex); -// } - if (DominanceFrontier != null && DominanceFrontier.Any()) { - writer.WriteLine(); - writer.Write("DominanceFrontier: " + string.Join(",", DominanceFrontier.OrderBy(d => d.BlockIndex).Select(d => d.BlockIndex.ToString()))); - } - foreach (Instruction inst in this.Instructions) { - writer.WriteLine(); - Disassembler.DisassemblerHelpers.WriteTo(inst, new PlainTextOutput(writer)); - } - if (UserData != null) { - writer.WriteLine(); - writer.Write(UserData.ToString()); - } - return writer.ToString(); - } - /// /// Gets whether this dominates . /// diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlStructureDetector.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlStructureDetector.cs deleted file mode 100644 index d00f82a8e..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlStructureDetector.cs +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Detects the structure of the control flow (exception blocks and loops). - /// - public class ControlStructureDetector - { - public static ControlStructure DetectStructure(ControlFlowGraph g, IEnumerable exceptionHandlers, CancellationToken cancellationToken) - { - ControlStructure root = new ControlStructure(new HashSet(g.Nodes), g.EntryPoint, ControlStructureType.Root); - // First build a structure tree out of the exception table - DetectExceptionHandling(root, g, exceptionHandlers); - // Then run the loop detection. - DetectLoops(g, root, cancellationToken); - return root; - } - - #region Exception Handling - static void DetectExceptionHandling(ControlStructure current, ControlFlowGraph g, IEnumerable exceptionHandlers) - { - // We rely on the fact that the exception handlers are sorted so that the innermost come first. - // For each exception handler, we determine the nodes and substructures inside that handler, and move them into a new substructure. - // This is always possible because exception handlers are guaranteed (by the CLR spec) to be properly nested and non-overlapping; - // so they directly form the tree that we need. - foreach (ExceptionHandler eh in exceptionHandlers) { - var tryNodes = FindNodes(current, eh.TryStart, eh.TryEnd); - current.Nodes.ExceptWith(tryNodes); - ControlStructure tryBlock = new ControlStructure( - tryNodes, - g.Nodes.Single(n => n.Start == eh.TryStart), - ControlStructureType.Try); - tryBlock.ExceptionHandler = eh; - MoveControlStructures(current, tryBlock, eh.TryStart, eh.TryEnd); - current.Children.Add(tryBlock); - - if (eh.FilterStart != null) { - throw new NotSupportedException(); - } - - var handlerNodes = FindNodes(current, eh.HandlerStart, eh.HandlerEnd); - var handlerNode = current.Nodes.Single(n => n.ExceptionHandler == eh); - handlerNodes.Add(handlerNode); - if (handlerNode.EndFinallyOrFaultNode != null) - handlerNodes.Add(handlerNode.EndFinallyOrFaultNode); - current.Nodes.ExceptWith(handlerNodes); - ControlStructure handlerBlock = new ControlStructure( - handlerNodes, handlerNode, ControlStructureType.Handler); - handlerBlock.ExceptionHandler = eh; - MoveControlStructures(current, handlerBlock, eh.HandlerStart, eh.HandlerEnd); - current.Children.Add(handlerBlock); - } - } - - /// - /// Removes all nodes from start to end (exclusive) from this ControlStructure and moves them to the target structure. - /// - static HashSet FindNodes(ControlStructure current, Instruction startInst, Instruction endInst) - { - HashSet result = new HashSet(); - int start = startInst.Offset; - int end = endInst.Offset; - foreach (var node in current.Nodes.ToArray()) { - if (node.Start != null && start <= node.Start.Offset && node.Start.Offset < end) { - result.Add(node); - } - } - return result; - } - - static void MoveControlStructures(ControlStructure current, ControlStructure target, Instruction startInst, Instruction endInst) - { - for (int i = 0; i < current.Children.Count; i++) { - var child = current.Children[i]; - if (startInst.Offset <= child.EntryPoint.Offset && child.EntryPoint.Offset < endInst.Offset) { - current.Children.RemoveAt(i--); - target.Children.Add(child); - target.AllNodes.UnionWith(child.AllNodes); - } - } - } - #endregion - - #region Loop Detection - // Loop detection works like this: - // We find a top-level loop by looking for its entry point, which is characterized by a node dominating its own predecessor. - // Then we determine all other nodes that belong to such a loop (all nodes which lead to the entry point, and are dominated by it). - // Finally, we check whether our result conforms with potential existing exception structures, and create the substructure for the loop if successful. - - // This algorithm is applied recursively for any substructures (both detected loops and exception blocks) - - // But maybe we should get rid of this complex stuff and instead treat every backward jump as a loop? - // That should still work with the IL produced by compilers, and has the advantage that the detected loop bodies are consecutive IL regions. - - static void DetectLoops(ControlFlowGraph g, ControlStructure current, CancellationToken cancellationToken) - { - if (!current.EntryPoint.IsReachable) - return; - g.ResetVisited(); - cancellationToken.ThrowIfCancellationRequested(); - FindLoops(current, current.EntryPoint); - foreach (ControlStructure loop in current.Children) - DetectLoops(g, loop, cancellationToken); - } - - static void FindLoops(ControlStructure current, ControlFlowNode node) - { - if (node.Visited) - return; - node.Visited = true; - if (current.Nodes.Contains(node) - && node.DominanceFrontier.Contains(node) - && !(node == current.EntryPoint && current.Type == ControlStructureType.Loop)) - { - HashSet loopContents = new HashSet(); - FindLoopContents(current, loopContents, node, node); - List containedChildStructures = new List(); - bool invalidNesting = false; - foreach (ControlStructure childStructure in current.Children) { - if (childStructure.AllNodes.IsSubsetOf(loopContents)) { - containedChildStructures.Add(childStructure); - } else if (childStructure.AllNodes.Intersect(loopContents).Any()) { - invalidNesting = true; - } - } - if (!invalidNesting) { - current.Nodes.ExceptWith(loopContents); - ControlStructure ctl = new ControlStructure(loopContents, node, ControlStructureType.Loop); - foreach (ControlStructure childStructure in containedChildStructures) { - ctl.Children.Add(childStructure); - current.Children.Remove(childStructure); - ctl.Nodes.ExceptWith(childStructure.AllNodes); - } - current.Children.Add(ctl); - } - } - foreach (var edge in node.Outgoing) { - FindLoops(current, edge.Target); - } - } - - static void FindLoopContents(ControlStructure current, HashSet loopContents, ControlFlowNode loopHead, ControlFlowNode node) - { - if (current.AllNodes.Contains(node) && loopHead.Dominates(node) && loopContents.Add(node)) { - foreach (var edge in node.Incoming) { - FindLoopContents(current, loopContents, loopHead, edge.Source); - } - } - } - #endregion - } - - public enum ControlStructureType - { - /// - /// The root block of the method - /// - Root, - /// - /// A nested control structure representing a loop. - /// - Loop, - /// - /// A nested control structure representing a try block. - /// - Try, - /// - /// A nested control structure representing a catch, finally, or fault block. - /// - Handler, - /// - /// A nested control structure representing an exception filter block. - /// - Filter - } - - /// - /// Represents the structure detected by the . - /// - /// This is a tree of ControlStructure nodes. Each node contains a set of CFG nodes, and every CFG node is contained in exactly one ControlStructure node. - /// - public class ControlStructure - { - public readonly ControlStructureType Type; - public readonly List Children = new List(); - - /// - /// The nodes in this control structure. - /// - public readonly HashSet Nodes; - - /// - /// The nodes in this control structure and in all child control structures. - /// - public readonly HashSet AllNodes; - - /// - /// The entry point of this control structure. - /// - public readonly ControlFlowNode EntryPoint; - - /// - /// The exception handler associated with this Try,Handler or Finally structure. - /// - public ExceptionHandler ExceptionHandler; - - public ControlStructure(HashSet nodes, ControlFlowNode entryPoint, ControlStructureType type) - { - if (nodes == null) - throw new ArgumentNullException("nodes"); - this.Nodes = nodes; - this.EntryPoint = entryPoint; - this.Type = type; - this.AllNodes = new HashSet(nodes); - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs b/ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs new file mode 100644 index 000000000..d9165e2af --- /dev/null +++ b/ICSharpCode.Decompiler/FlowAnalysis/Dominance.cs @@ -0,0 +1,116 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace ICSharpCode.Decompiler.FlowAnalysis +{ + /// + /// Description of Dominance. + /// + public static class Dominance + { + /// + /// Computes the dominator tree. + /// + /// + /// Precondition: the dominance tree is not already computed for some nodes reachable from entryPoint + /// (i.e. ImmediateDominator and DominatorTreeChildren are both null), + /// and the visited flag is false for any nodes reachable from entryPoint. + /// + /// Postcondition: a dominator tree is constructed for all nodes reachable from entryPoint, + /// and the visited flag remains false. + /// + public static void ComputeDominance(ControlFlowNode entryPoint, CancellationToken cancellationToken = default(CancellationToken)) + { + // A Simple, Fast Dominance Algorithm + // Keith D. Cooper, Timothy J. Harvey and Ken Kennedy + + var nodes = new List(); + entryPoint.TraversePostOrder(n => n.Successors, nodes.Add); + Debug.Assert(nodes.Last() == entryPoint); + for (int i = 0; i < nodes.Count; i++) { + nodes[i].PostOrderNumber = i; + } + + // For the purpose of this algorithm, make the entry point its own dominator. + // We'll reset it back to null at the end of this function. + entryPoint.ImmediateDominator = entryPoint; + bool changed; + do { + changed = false; + + cancellationToken.ThrowIfCancellationRequested(); + + // For all nodes b except the entry point (in reverse post-order) + for (int i = nodes.Count - 2; i >= 0; i--) { + ControlFlowNode b = nodes[i]; + // Compute new immediate dominator: + ControlFlowNode newIdom = null; + foreach (var p in b.Predecessors) { + // Ignore predecessors that were not processed yet + if (p.ImmediateDominator != null) { + if (newIdom == null) + newIdom = p; + else + newIdom = FindCommonDominator(p, newIdom); + } + } + // The reverse post-order ensures at least one of our predecessors was processed. + Debug.Assert(newIdom != null); + if (newIdom != b.ImmediateDominator) { + b.ImmediateDominator = newIdom; + changed = true; + } + } + } while(changed); + // Create dominator tree for all reachable nodes: + foreach (ControlFlowNode node in nodes) { + if (node.ImmediateDominator != null) + node.DominatorTreeChildren = new List(); + } + entryPoint.ImmediateDominator = null; + foreach (ControlFlowNode node in nodes) { + // Create list of children in dominator tree + if (node.ImmediateDominator != null) + node.ImmediateDominator.DominatorTreeChildren.Add(node); + // Also reset the visited flag + node.Visited = false; + } + } + + /// + /// Returns the common ancestor of a and b in the dominator tree. + /// + /// Precondition: a and b are part of the same dominator tree. + /// + public static ControlFlowNode FindCommonDominator(ControlFlowNode a, ControlFlowNode b) + { + while (a != b) { + while (a.PostOrderNumber < b.PostOrderNumber) + a = a.ImmediateDominator; + while (b.PostOrderNumber < a.PostOrderNumber) + b = b.ImmediateDominator; + } + return a; + } + } +} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/OpCodeInfo.cs b/ICSharpCode.Decompiler/FlowAnalysis/OpCodeInfo.cs deleted file mode 100644 index e26447ebc..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/OpCodeInfo.cs +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using System.Linq; - -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Additional info about opcodes. - /// - sealed class OpCodeInfo - { - public static bool IsUnconditionalBranch(OpCode opcode) - { - if (opcode.OpCodeType == OpCodeType.Prefix) - return false; - switch (opcode.FlowControl) { - case FlowControl.Branch: - case FlowControl.Throw: - case FlowControl.Return: - return true; - case FlowControl.Next: - case FlowControl.Call: - case FlowControl.Cond_Branch: - return false; - default: - throw new NotSupportedException(opcode.FlowControl.ToString()); - } - } - - static readonly OpCodeInfo[] knownOpCodes = { - #region Base Instructions - new OpCodeInfo(OpCodes.Add) { CanThrow = false }, - new OpCodeInfo(OpCodes.Add_Ovf) { CanThrow = true }, - new OpCodeInfo(OpCodes.Add_Ovf_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.And) { CanThrow = false }, - new OpCodeInfo(OpCodes.Arglist) { CanThrow = false }, - new OpCodeInfo(OpCodes.Beq) { CanThrow = false }, - new OpCodeInfo(OpCodes.Beq_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bge) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bge_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bge_Un) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bge_Un_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bgt) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bgt_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bgt_Un) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bgt_Un_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ble) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ble_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ble_Un) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ble_Un_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Blt) { CanThrow = false }, - new OpCodeInfo(OpCodes.Blt_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Blt_Un) { CanThrow = false }, - new OpCodeInfo(OpCodes.Blt_Un_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bne_Un) { CanThrow = false }, - new OpCodeInfo(OpCodes.Bne_Un_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Br) { CanThrow = false }, - new OpCodeInfo(OpCodes.Br_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Break) { CanThrow = true }, - new OpCodeInfo(OpCodes.Brfalse) { CanThrow = false }, - new OpCodeInfo(OpCodes.Brfalse_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Brtrue) { CanThrow = false }, - new OpCodeInfo(OpCodes.Brtrue_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Call) { CanThrow = true }, - new OpCodeInfo(OpCodes.Calli) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ceq) { CanThrow = false }, - new OpCodeInfo(OpCodes.Cgt) { CanThrow = false }, - new OpCodeInfo(OpCodes.Cgt_Un) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ckfinite) { CanThrow = true }, - new OpCodeInfo(OpCodes.Clt) { CanThrow = false }, - new OpCodeInfo(OpCodes.Clt_Un) { CanThrow = false }, - // conv. - new OpCodeInfo(OpCodes.Conv_I1) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_I2) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_I4) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_I8) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_R4) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_R8) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_U1) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_U2) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_U4) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_U8) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_I) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_U) { CanThrow = false }, - new OpCodeInfo(OpCodes.Conv_R_Un) { CanThrow = false }, - // conv.ovf. - new OpCodeInfo(OpCodes.Conv_Ovf_I1) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_I2) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_I4) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_I8) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_U1) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_U2) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_U4) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_U8) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_I) { CanThrow = true}, - new OpCodeInfo(OpCodes.Conv_Ovf_U) { CanThrow = true}, - // conv.ovf..un - new OpCodeInfo(OpCodes.Conv_Ovf_I1_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_I2_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_I4_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_I8_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_U1_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_U2_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_U4_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_U8_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_I_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Conv_Ovf_U_Un) { CanThrow = true }, - - //new OpCodeInfo(OpCodes.Cpblk) { CanThrow = true }, - no idea whether this might cause trouble for the type system, C# shouldn't use it so I'll disable it - new OpCodeInfo(OpCodes.Div) { CanThrow = true }, - new OpCodeInfo(OpCodes.Div_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Dup) { CanThrow = true, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Endfilter) { CanThrow = false }, - new OpCodeInfo(OpCodes.Endfinally) { CanThrow = false }, - //new OpCodeInfo(OpCodes.Initblk) { CanThrow = true }, - no idea whether this might cause trouble for the type system, C# shouldn't use it so I'll disable it - //new OpCodeInfo(OpCodes.Jmp) { CanThrow = true } - We don't support non-local control transfers. - new OpCodeInfo(OpCodes.Ldarg) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldarg_0) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldarg_1) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldarg_2) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldarg_3) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldarg_S) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldarga) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldarga_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_M1) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_0) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_1) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_2) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_3) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_4) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_5) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_6) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_7) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_8) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I4_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_I8) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_R4) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldc_R8) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldftn) { CanThrow = false }, - // ldind. - new OpCodeInfo(OpCodes.Ldind_I1) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_I2) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_I4) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_I8) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_U1) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_U2) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_U4) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_R4) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_R8) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_I) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ldind_Ref) { CanThrow = true }, - // the ldloc exceptions described in the spec can only occur on methods without .localsinit - but csc always sets that flag - new OpCodeInfo(OpCodes.Ldloc) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldloc_0) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldloc_1) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldloc_2) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldloc_3) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldloc_S) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Ldloca) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldloca_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldnull) { CanThrow = false }, - new OpCodeInfo(OpCodes.Leave) { CanThrow = false }, - new OpCodeInfo(OpCodes.Leave_S) { CanThrow = false }, - new OpCodeInfo(OpCodes.Localloc) { CanThrow = true }, - new OpCodeInfo(OpCodes.Mul) { CanThrow = false }, - new OpCodeInfo(OpCodes.Mul_Ovf) { CanThrow = true }, - new OpCodeInfo(OpCodes.Mul_Ovf_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Neg) { CanThrow = false }, - new OpCodeInfo(OpCodes.Nop) { CanThrow = false }, - new OpCodeInfo(OpCodes.Not) { CanThrow = false }, - new OpCodeInfo(OpCodes.Or) { CanThrow = false }, - new OpCodeInfo(OpCodes.Pop) { CanThrow = false }, - new OpCodeInfo(OpCodes.Rem) { CanThrow = true }, - new OpCodeInfo(OpCodes.Rem_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Ret) { CanThrow = false }, - new OpCodeInfo(OpCodes.Shl) { CanThrow = false }, - new OpCodeInfo(OpCodes.Shr) { CanThrow = false }, - new OpCodeInfo(OpCodes.Shr_Un) { CanThrow = false }, - new OpCodeInfo(OpCodes.Starg) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Starg_S) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Stind_I1) { CanThrow = true }, - new OpCodeInfo(OpCodes.Stind_I2) { CanThrow = true }, - new OpCodeInfo(OpCodes.Stind_I4) { CanThrow = true }, - new OpCodeInfo(OpCodes.Stind_I8) { CanThrow = true }, - new OpCodeInfo(OpCodes.Stind_R4) { CanThrow = true }, - new OpCodeInfo(OpCodes.Stind_R8) { CanThrow = true }, - new OpCodeInfo(OpCodes.Stind_I) { CanThrow = true }, - new OpCodeInfo(OpCodes.Stind_Ref) { CanThrow = true }, - new OpCodeInfo(OpCodes.Stloc) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Stloc_0) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Stloc_1) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Stloc_2) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Stloc_3) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Stloc_S) { CanThrow = false, IsMoveInstruction = true }, - new OpCodeInfo(OpCodes.Sub) { CanThrow = false }, - new OpCodeInfo(OpCodes.Sub_Ovf) { CanThrow = true }, - new OpCodeInfo(OpCodes.Sub_Ovf_Un) { CanThrow = true }, - new OpCodeInfo(OpCodes.Switch) { CanThrow = false }, - new OpCodeInfo(OpCodes.Xor) { CanThrow = false }, - #endregion - #region Object model instructions - // CanThrow is true by default - most OO instructions can throw, so we don't specify CanThrow all of the time - new OpCodeInfo(OpCodes.Box), - new OpCodeInfo(OpCodes.Callvirt), - new OpCodeInfo(OpCodes.Castclass), - new OpCodeInfo(OpCodes.Cpobj), - new OpCodeInfo(OpCodes.Initobj) { CanThrow = false }, - new OpCodeInfo(OpCodes.Isinst) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldelem_Any), - // ldelem. - new OpCodeInfo(OpCodes.Ldelem_I) , - new OpCodeInfo(OpCodes.Ldelem_I1), - new OpCodeInfo(OpCodes.Ldelem_I2), - new OpCodeInfo(OpCodes.Ldelem_I4), - new OpCodeInfo(OpCodes.Ldelem_I8), - new OpCodeInfo(OpCodes.Ldelem_R4), - new OpCodeInfo(OpCodes.Ldelem_R8), - new OpCodeInfo(OpCodes.Ldelem_Ref), - new OpCodeInfo(OpCodes.Ldelem_U1), - new OpCodeInfo(OpCodes.Ldelem_U2), - new OpCodeInfo(OpCodes.Ldelem_U4), - new OpCodeInfo(OpCodes.Ldelema) , - new OpCodeInfo(OpCodes.Ldfld) , - new OpCodeInfo(OpCodes.Ldflda), - new OpCodeInfo(OpCodes.Ldlen) , - new OpCodeInfo(OpCodes.Ldobj) , - new OpCodeInfo(OpCodes.Ldsfld), - new OpCodeInfo(OpCodes.Ldsflda), - new OpCodeInfo(OpCodes.Ldstr) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldtoken) { CanThrow = false }, - new OpCodeInfo(OpCodes.Ldvirtftn), - new OpCodeInfo(OpCodes.Mkrefany), - new OpCodeInfo(OpCodes.Newarr), - new OpCodeInfo(OpCodes.Newobj), - new OpCodeInfo(OpCodes.Refanytype) { CanThrow = false }, - new OpCodeInfo(OpCodes.Refanyval), - new OpCodeInfo(OpCodes.Rethrow), - new OpCodeInfo(OpCodes.Sizeof) { CanThrow = false }, - new OpCodeInfo(OpCodes.Stelem_Any), - new OpCodeInfo(OpCodes.Stelem_I1), - new OpCodeInfo(OpCodes.Stelem_I2), - new OpCodeInfo(OpCodes.Stelem_I4), - new OpCodeInfo(OpCodes.Stelem_I8), - new OpCodeInfo(OpCodes.Stelem_R4), - new OpCodeInfo(OpCodes.Stelem_R8), - new OpCodeInfo(OpCodes.Stelem_Ref), - new OpCodeInfo(OpCodes.Stfld), - new OpCodeInfo(OpCodes.Stobj), - new OpCodeInfo(OpCodes.Stsfld), - new OpCodeInfo(OpCodes.Throw), - new OpCodeInfo(OpCodes.Unbox), - new OpCodeInfo(OpCodes.Unbox_Any), - #endregion - }; - static readonly Dictionary knownOpCodeDict = knownOpCodes.ToDictionary(info => info.OpCode.Code); - - public static OpCodeInfo Get(OpCode opCode) - { - return Get(opCode.Code); - } - - public static OpCodeInfo Get(Code code) - { - OpCodeInfo info; - if (knownOpCodeDict.TryGetValue(code, out info)) - return info; - else - throw new NotSupportedException(code.ToString()); - } - - OpCode opcode; - - private OpCodeInfo(OpCode opcode) - { - this.opcode = opcode; - this.CanThrow = true; - } - - public OpCode OpCode { get { return opcode; } } - - /// - /// 'Move' kind of instructions have one input (may be stack or local variable) and copy that value to all outputs (again stack or local variable). - /// - public bool IsMoveInstruction { get; private set; } - - /// - /// Specifies whether this opcode is capable of throwing exceptions. - /// - public bool CanThrow { get; private set; } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SimplifyByRefCalls.cs b/ICSharpCode.Decompiler/FlowAnalysis/SimplifyByRefCalls.cs deleted file mode 100644 index 6b0e03950..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/SimplifyByRefCalls.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using System.Diagnostics; - -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// This is a transformation working on SSA form. - /// It removes ldloca instructions and replaces them with SpecialOpCode.PrepareByOutCall or SpecialOpCode.PrepareByRefCall. - /// This then allows the variable that had its address taken to also be transformed into SSA. - /// - sealed class SimplifyByRefCalls - { - public static bool MakeByRefCallsSimple(SsaForm ssaForm) - { - SimplifyByRefCalls instance = new SimplifyByRefCalls(ssaForm); - foreach (SsaBlock block in ssaForm.Blocks) { - for (int i = 0; i < block.Instructions.Count; i++) { - SsaInstruction inst = block.Instructions[i]; - if (inst.Instruction != null) { - switch (inst.Instruction.OpCode.Code) { - case Code.Call: - case Code.Callvirt: - instance.MakeByRefCallSimple(block, ref i, (IMethodSignature)inst.Instruction.Operand); - break; - case Code.Initobj: - instance.MakeInitObjCallSimple(block, ref i); - break; - case Code.Ldfld: - instance.MakeLoadFieldCallSimple(block, ref i); - break; - } - } - } - } - instance.RemoveRedundantInstructions(); - if (instance.couldSimplifySomething) - ssaForm.ComputeVariableUsage(); - return instance.couldSimplifySomething; - } - - readonly SsaForm ssaForm; - - bool couldSimplifySomething; - - // the list of ldloca instructions we will remove - readonly List redundantLoadAddressInstructions = new List(); - - private SimplifyByRefCalls(SsaForm ssaForm) - { - this.ssaForm = ssaForm; - } - - void MakeByRefCallSimple(SsaBlock block, ref int instructionIndexInBlock, IMethodSignature targetMethod) - { - SsaInstruction inst = block.Instructions[instructionIndexInBlock]; - for (int i = 0; i < inst.Operands.Length; i++) { - SsaVariable operand = inst.Operands[i]; - if (operand.IsSingleAssignment && operand.Usage.Count == 1 && IsLoadAddress(operand.Definition)) { - // address is used for this method call only - - Instruction loadAddressInstruction = operand.Definition.Instruction; - - // find target parameter type: - bool isOut; - if (i == 0 && targetMethod.HasThis) { - isOut = false; - } else { - ParameterDefinition parameter = targetMethod.Parameters[i - (targetMethod.HasThis ? 1 : 0)]; - isOut = parameter.IsOut; - } - - SsaVariable addressTakenOf = GetVariableFromLoadAddressInstruction(loadAddressInstruction); - - // insert "Prepare" instruction on front - SpecialOpCode loadOpCode = isOut ? SpecialOpCode.PrepareByOutCall : SpecialOpCode.PrepareByRefCall; - block.Instructions.Insert(instructionIndexInBlock++, new SsaInstruction( - block, null, operand, new SsaVariable[] { addressTakenOf }, specialOpCode: loadOpCode)); - - // insert "WriteAfterByRefOrOutCall" instruction after call - block.Instructions.Insert(instructionIndexInBlock + 1, new SsaInstruction( - block, null, addressTakenOf, new SsaVariable[] { operand }, specialOpCode: SpecialOpCode.WriteAfterByRefOrOutCall)); - - couldSimplifySomething = true; - - // remove the loadAddressInstruction later - // (later because it might be defined in the current block and we don't want instructionIndex to become invalid) - redundantLoadAddressInstructions.Add(operand.Definition); - } - } - } - - SsaVariable GetVariableFromLoadAddressInstruction(Instruction loadAddressInstruction) - { - if (loadAddressInstruction.OpCode == OpCodes.Ldloca) { - return ssaForm.GetOriginalVariable((VariableReference)loadAddressInstruction.Operand); - } else { - Debug.Assert(loadAddressInstruction.OpCode == OpCodes.Ldarga); - return ssaForm.GetOriginalVariable((ParameterReference)loadAddressInstruction.Operand); - } - } - - static bool IsLoadAddress(SsaInstruction inst) - { - return inst.Instruction != null && (inst.Instruction.OpCode == OpCodes.Ldloca || inst.Instruction.OpCode == OpCodes.Ldarga); - } - - void MakeInitObjCallSimple(SsaBlock block, ref int instructionIndexInBlock) - { - SsaInstruction inst = block.Instructions[instructionIndexInBlock]; - Debug.Assert(inst.Operands.Length == 1); - SsaVariable operand = inst.Operands[0]; - if (operand.IsSingleAssignment && operand.Usage.Count == 1 && IsLoadAddress(operand.Definition)) { - // replace instruction with special "InitObj" instruction - block.Instructions[instructionIndexInBlock] = new SsaInstruction( - inst.ParentBlock, null, GetVariableFromLoadAddressInstruction(operand.Definition.Instruction), null, - specialOpCode: SpecialOpCode.InitObj, - typeOperand: (TypeReference)inst.Instruction.Operand); - - couldSimplifySomething = true; - - // remove the loadAddressInstruction later - redundantLoadAddressInstructions.Add(operand.Definition); - } - } - - void MakeLoadFieldCallSimple(SsaBlock block, ref int instructionIndexInBlock) - { - SsaInstruction inst = block.Instructions[instructionIndexInBlock]; - Debug.Assert(inst.Operands.Length == 1); - SsaVariable operand = inst.Operands[0]; - if (operand.IsSingleAssignment && operand.Usage.Count == 1 && IsLoadAddress(operand.Definition)) { - // insert special "PrepareForFieldAccess" instruction in front - block.Instructions.Insert(instructionIndexInBlock++, new SsaInstruction( - inst.ParentBlock, null, operand, - new SsaVariable[] { GetVariableFromLoadAddressInstruction(operand.Definition.Instruction) }, - specialOpCode: SpecialOpCode.PrepareForFieldAccess)); - - couldSimplifySomething = true; - - // remove the loadAddressInstruction later - redundantLoadAddressInstructions.Add(operand.Definition); - } - } - - void RemoveRedundantInstructions() - { - foreach (SsaInstruction inst in redundantLoadAddressInstructions) { - inst.ParentBlock.Instructions.Remove(inst); - } - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaBlock.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaBlock.cs deleted file mode 100644 index 44022e30e..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaBlock.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using System.IO; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// A block in a control flow graph; with instructions represented by "SsaInstructions" (instructions use variables, no evaluation stack). - /// Usually these variables are in SSA form to make analysis easier. - /// - public sealed class SsaBlock - { - public readonly List Successors = new List(); - public readonly List Predecessors = new List(); - public readonly ControlFlowNodeType NodeType; - public readonly List Instructions = new List(); - - /// - /// The block index in the control flow graph. - /// This correspons to the node index in ControlFlowGraph.Nodes, so it can be used to retrieve the original CFG node and look - /// up additional information (e.g. dominance). - /// - public readonly int BlockIndex; - - internal SsaBlock(ControlFlowNode node) - { - this.NodeType = node.NodeType; - this.BlockIndex = node.BlockIndex; - } - - public override string ToString() - { - StringWriter writer = new StringWriter(); - writer.Write("Block #{0} ({1})", BlockIndex, NodeType); - foreach (SsaInstruction inst in Instructions) { - writer.WriteLine(); - inst.WriteTo(writer); - } - return writer.ToString(); - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs deleted file mode 100644 index 1a85e76a7..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2010 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.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; - -using ICSharpCode.NRefactory.Utils; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Represents a graph of SsaBlocks. - /// - public sealed class SsaForm - { - readonly SsaVariable[] parameters; - readonly SsaVariable[] locals; - public readonly ReadOnlyCollection OriginalVariables; - public readonly ReadOnlyCollection Blocks; - readonly bool methodHasThis; - - public SsaBlock EntryPoint { - get { return this.Blocks[0]; } - } - - public SsaBlock RegularExit { - get { return this.Blocks[1]; } - } - - public SsaBlock ExceptionalExit { - get { return this.Blocks[2]; } - } - - internal SsaForm(SsaBlock[] blocks, SsaVariable[] parameters, SsaVariable[] locals, SsaVariable[] stackLocations, bool methodHasThis) - { - this.parameters = parameters; - this.locals = locals; - this.Blocks = new ReadOnlyCollection(blocks); - this.OriginalVariables = new ReadOnlyCollection(parameters.Concat(locals).Concat(stackLocations).ToList()); - this.methodHasThis = methodHasThis; - - Debug.Assert(EntryPoint.NodeType == ControlFlowNodeType.EntryPoint); - Debug.Assert(RegularExit.NodeType == ControlFlowNodeType.RegularExit); - Debug.Assert(ExceptionalExit.NodeType == ControlFlowNodeType.ExceptionalExit); - for (int i = 0; i < this.OriginalVariables.Count; i++) { - this.OriginalVariables[i].OriginalVariableIndex = i; - } - } - - public GraphVizGraph ExportBlockGraph(Func labelProvider = null) - { - if (labelProvider == null) - labelProvider = b => b.ToString(); - GraphVizGraph graph = new GraphVizGraph(); - foreach (SsaBlock block in this.Blocks) { - graph.AddNode(new GraphVizNode(block.BlockIndex) { label = labelProvider(block), shape = "box" }); - } - foreach (SsaBlock block in this.Blocks) { - foreach (SsaBlock s in block.Successors) { - graph.AddEdge(new GraphVizEdge(block.BlockIndex, s.BlockIndex)); - } - } - return graph; - } - - public GraphVizGraph ExportVariableGraph(Func labelProvider = null) - { - if (labelProvider == null) - labelProvider = v => v.ToString(); - GraphVizGraph graph = new GraphVizGraph(); - foreach (SsaVariable v in this.AllVariables) { - graph.AddNode(new GraphVizNode(v.Name) { label = labelProvider(v) }); - } - int instructionIndex = 0; - foreach (SsaBlock block in this.Blocks) { - foreach (SsaInstruction inst in block.Instructions) { - if (inst.Operands.Length == 0 && inst.Target == null) - continue; - string id = "instruction" + (++instructionIndex); - graph.AddNode(new GraphVizNode(id) { label = inst.ToString(), shape = "box" }); - foreach (SsaVariable op in inst.Operands) - graph.AddEdge(new GraphVizEdge(op.Name, id)); - if (inst.Target != null) - graph.AddEdge(new GraphVizEdge(id, inst.Target.Name)); - } - } - return graph; - } - - public SsaVariable GetOriginalVariable(ParameterReference parameter) - { - if (methodHasThis) - return parameters[parameter.Index + 1]; - else - return parameters[parameter.Index]; - } - - public SsaVariable GetOriginalVariable(VariableReference variable) - { - return locals[variable.Index]; - } - - #region ComputeVariableUsage - public void ComputeVariableUsage() - { - // clear data from previous runs - foreach (SsaBlock block in this.Blocks) { - foreach (SsaInstruction inst in block.Instructions) { - foreach (SsaVariable v in inst.Operands) { - if (v.Usage != null) - v.Usage.Clear(); - } - if (inst.Target != null && inst.Target.Usage != null) - inst.Target.Usage.Clear(); - } - } - foreach (SsaBlock block in this.Blocks) { - foreach (SsaInstruction inst in block.Instructions) { - foreach (SsaVariable v in inst.Operands) { - if (v.Usage == null) - v.Usage = new List(); - v.Usage.Add(inst); - } - if (inst.Target != null && inst.Target.Usage == null) - inst.Target.Usage = new List(); - } - } - } - #endregion - - public IEnumerable AllVariables { - get { - return ( - from block in this.Blocks - from instruction in block.Instructions - where instruction.Target != null - select instruction.Target - ).Distinct(); - } - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaFormBuilder.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaFormBuilder.cs deleted file mode 100644 index 9596b52a5..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaFormBuilder.cs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) 2010 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.Collections.Generic; -using System.Diagnostics; - -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Constructs "SsaForm" graph for a CFG. - /// This class transforms the method from stack-based IL to a register-based IL language. - /// Then it calls into TransformToSsa to convert the resulting graph to static single assignment form. - /// - public sealed class SsaFormBuilder - { - public static SsaForm Build(MethodDefinition method) - { - if (method == null) - throw new ArgumentNullException("method"); - var cfg = ControlFlowGraphBuilder.Build(method.Body); - cfg.ComputeDominance(); - cfg.ComputeDominanceFrontier(); - var ssa = BuildRegisterIL(method, cfg); - TransformToSsa.Transform(cfg, ssa); - return ssa; - } - - public static SsaForm BuildRegisterIL(MethodDefinition method, ControlFlowGraph cfg) - { - if (method == null) - throw new ArgumentNullException("method"); - if (cfg == null) - throw new ArgumentNullException("cfg"); - return new SsaFormBuilder(method, cfg).Build(); - } - - readonly MethodDefinition method; - readonly ControlFlowGraph cfg; - - readonly SsaBlock[] blocks; // array index = block index - readonly int[] stackSizeAtBlockStart; // array index = block index - - readonly SsaVariable[] parameters; // array index = parameter number - readonly SsaVariable[] locals; // array index = local number - readonly SsaVariable[] stackLocations; // array index = position on the IL evaluation stack - SsaForm ssaForm; - - private SsaFormBuilder(MethodDefinition method, ControlFlowGraph cfg) - { - this.method = method; - this.cfg = cfg; - - this.blocks = new SsaBlock[cfg.Nodes.Count]; - this.stackSizeAtBlockStart = new int[cfg.Nodes.Count]; - for (int i = 0; i < stackSizeAtBlockStart.Length; i++) { - stackSizeAtBlockStart[i] = -1; - } - stackSizeAtBlockStart[cfg.EntryPoint.BlockIndex] = 0; - - this.parameters = new SsaVariable[method.Parameters.Count + (method.HasThis ? 1 : 0)]; - if (method.HasThis) - parameters[0] = new SsaVariable(method.Body.ThisParameter); - for (int i = 0; i < method.Parameters.Count; i++) - parameters[i + (method.HasThis ? 1 : 0)] = new SsaVariable(method.Parameters[i]); - - this.locals = new SsaVariable[method.Body.Variables.Count]; - for (int i = 0; i < locals.Length; i++) - locals[i] = new SsaVariable(method.Body.Variables[i]); - - this.stackLocations = new SsaVariable[method.Body.MaxStackSize]; - for (int i = 0; i < stackLocations.Length; i++) { - stackLocations[i] = new SsaVariable(i); - } - } - - internal SsaForm Build() - { - CreateGraphStructure(); - this.ssaForm = new SsaForm(blocks, parameters, locals, stackLocations, method.HasThis); - CreateInstructions(cfg.EntryPoint.BlockIndex); - CreateSpecialInstructions(); - return ssaForm; - } - - void CreateGraphStructure() - { - for (int i = 0; i < blocks.Length; i++) { - blocks[i] = new SsaBlock(cfg.Nodes[i]); - } - for (int i = 0; i < blocks.Length; i++) { - foreach (ControlFlowNode node in cfg.Nodes[i].Successors) { - blocks[i].Successors.Add(blocks[node.BlockIndex]); - blocks[node.BlockIndex].Predecessors.Add(blocks[i]); - } - } - } - - void CreateInstructions(int blockIndex) - { - ControlFlowNode cfgNode = cfg.Nodes[blockIndex]; - SsaBlock block = blocks[blockIndex]; - - int stackSize = stackSizeAtBlockStart[blockIndex]; - Debug.Assert(stackSize >= 0); - - List prefixes = new List(); - foreach (Instruction inst in cfgNode.Instructions) { - if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { - prefixes.Add(inst); - continue; - } - - int popCount = inst.GetPopDelta(method) ?? stackSize; - stackSize -= popCount; - if (stackSize < 0) - throw new InvalidProgramException("IL stack underflow"); - - int pushCount = inst.GetPushDelta(); - if (stackSize + pushCount > stackLocations.Length) - throw new InvalidProgramException("IL stack overflow"); - - SsaVariable target; - SsaVariable[] operands; - DetermineOperands(stackSize, inst, popCount, pushCount, out target, out operands); - - Instruction[] prefixArray = prefixes.Count > 0 ? prefixes.ToArray() : null; - prefixes.Clear(); - - // ignore NOP instructions - if (!(inst.OpCode == OpCodes.Nop || inst.OpCode == OpCodes.Pop)) { - block.Instructions.Add(new SsaInstruction(block, inst, target, operands, prefixArray)); - } - stackSize += pushCount; - } - - foreach (ControlFlowEdge edge in cfgNode.Outgoing) { - int newStackSize; - switch (edge.Type) { - case JumpType.Normal: - newStackSize = stackSize; - break; - case JumpType.EndFinally: - if (stackSize != 0) - throw new NotSupportedException("stacksize must be 0 in endfinally edge"); - newStackSize = 0; - break; - case JumpType.JumpToExceptionHandler: - switch (edge.Target.NodeType) { - case ControlFlowNodeType.FinallyOrFaultHandler: - newStackSize = 0; - break; - case ControlFlowNodeType.ExceptionalExit: - case ControlFlowNodeType.CatchHandler: - newStackSize = 1; - break; - default: - throw new NotSupportedException("unsupported target node type: " + edge.Target.NodeType); - } - break; - default: - throw new NotSupportedException("unsupported jump type: " + edge.Type); - } - - int nextStackSize = stackSizeAtBlockStart[edge.Target.BlockIndex]; - if (nextStackSize == -1) { - stackSizeAtBlockStart[edge.Target.BlockIndex] = newStackSize; - CreateInstructions(edge.Target.BlockIndex); - } else if (nextStackSize != newStackSize) { - throw new InvalidProgramException("Stack size doesn't match"); - } - } - } - - void DetermineOperands(int stackSize, Instruction inst, int popCount, int pushCount, out SsaVariable target, out SsaVariable[] operands) - { - switch (inst.OpCode.Code) { - case Code.Ldarg: - operands = new SsaVariable[] { ssaForm.GetOriginalVariable((ParameterReference)inst.Operand) }; - target = stackLocations[stackSize]; - break; - case Code.Starg: - operands = new SsaVariable[] { stackLocations[stackSize] }; - target = ssaForm.GetOriginalVariable((ParameterReference)inst.Operand); - break; - case Code.Ldloc: - operands = new SsaVariable[] { ssaForm.GetOriginalVariable((VariableReference)inst.Operand) }; - target = stackLocations[stackSize]; - break; - case Code.Stloc: - operands = new SsaVariable[] { stackLocations[stackSize] }; - target = ssaForm.GetOriginalVariable((VariableReference)inst.Operand); - break; - case Code.Dup: - operands = new SsaVariable[] { stackLocations[stackSize] }; - target = stackLocations[stackSize + 1]; - break; - default: - operands = new SsaVariable[popCount]; - for (int i = 0; i < popCount; i++) { - operands[i] = stackLocations[stackSize + i]; - } - - switch (pushCount) { - case 0: - target = null; - break; - case 1: - target = stackLocations[stackSize]; - break; - default: - throw new NotSupportedException("unsupported pushCount=" + pushCount); - } - break; - } - } - - void CreateSpecialInstructions() - { - // Everything needs an initial write for the SSA transformation to work correctly. - foreach (SsaVariable v in parameters) { - ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Parameter)); - } - foreach (SsaVariable v in locals) { - ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Uninitialized)); - } - foreach (SsaVariable v in stackLocations) { - ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Uninitialized)); - } - foreach (SsaBlock b in blocks) { - if (b.NodeType == ControlFlowNodeType.CatchHandler) { - b.Instructions.Add(new SsaInstruction(b, null, stackLocations[0], null, - specialOpCode: SpecialOpCode.Exception, - typeOperand: cfg.Nodes[b.BlockIndex].ExceptionHandler.CatchType)); - } - } - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaInstruction.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaInstruction.cs deleted file mode 100644 index 077a329ce..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaInstruction.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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 System.IO; - -using ICSharpCode.Decompiler.Disassembler; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - public enum SpecialOpCode - { - /// - /// No special op code: SsaInstruction has a normal IL instruction - /// - None, - /// - /// Φ function: chooses the appropriate variable based on which CFG edge was used to enter this block - /// - Phi, - /// - /// Variable is read from before passing it by ref. - /// This instruction constructs a managed reference to the variable. - /// - PrepareByRefCall, - /// - /// This instruction constructs a managed reference to the variable. - /// The variable is not really read from. - /// - PrepareByOutCall, - /// - /// This instruction constructs a managed reference to the variable. - /// The reference is used for a field access on a value type. - /// - PrepareForFieldAccess, - /// - /// Variable is written to after passing it by ref or out. - /// - WriteAfterByRefOrOutCall, - /// - /// Variable is not initialized. - /// - Uninitialized, - /// - /// Value is passed in as parameter - /// - Parameter, - /// - /// Value is a caught exception. - /// TypeOperand is set to the exception type. - /// - Exception, - /// - /// Initialize a value type. Unlike the real initobj instruction, this one does not take an address - /// but assigns to the target variable. - /// TypeOperand is set to the type being created. - /// - InitObj - } - - public sealed class SsaInstruction - { - public readonly SsaBlock ParentBlock; - public readonly SpecialOpCode SpecialOpCode; - - /// - /// The original IL instruction. - /// May be null for "invented" instructions (SpecialOpCode != None). - /// - public readonly Instruction Instruction; - - /// - /// Prefixes in front of the IL instruction. - /// - public readonly Instruction[] Prefixes; - - /// - /// Gets the type operand. This is used only in combination with some special opcodes. - /// - public readonly TypeReference TypeOperand; - - public SsaVariable Target; - public SsaVariable[] Operands; - - static readonly SsaVariable[] emptyVariableArray = {}; - static readonly Instruction[] emptyInstructionArray = {}; - - public SsaInstruction(SsaBlock parentBlock, Instruction instruction, SsaVariable target, SsaVariable[] operands, - Instruction[] prefixes = null, SpecialOpCode specialOpCode = SpecialOpCode.None, - TypeReference typeOperand = null) - { - this.ParentBlock = parentBlock; - this.Instruction = instruction; - this.Prefixes = prefixes ?? emptyInstructionArray; - this.Target = target; - this.Operands = operands ?? emptyVariableArray; - this.SpecialOpCode = specialOpCode; - this.TypeOperand = typeOperand; - Debug.Assert((typeOperand != null) == (specialOpCode == SpecialOpCode.Exception || specialOpCode == SpecialOpCode.InitObj)); - } - - /// - /// Gets whether this instruction is a simple assignment from one variable to another. - /// - public bool IsMoveInstruction { - get { - return Target != null && Operands.Length == 1 && Instruction != null && OpCodeInfo.Get(Instruction.OpCode).IsMoveInstruction; - } - } - - public void ReplaceVariableInOperands(SsaVariable oldVar, SsaVariable newVar) - { - for (int i = 0; i < this.Operands.Length; i++) { - if (this.Operands[i] == oldVar) - this.Operands[i] = newVar; - } - } - - public override string ToString() - { - StringWriter w = new StringWriter(); - WriteTo(w); - return w.ToString(); - } - - public void WriteTo(TextWriter writer) - { - foreach (Instruction prefix in this.Prefixes) { - Disassembler.DisassemblerHelpers.WriteTo(prefix, new PlainTextOutput(writer)); - writer.WriteLine(); - } - if (Instruction != null && Instruction.Offset >= 0) { - writer.Write(CecilExtensions.OffsetToString(Instruction.Offset)); - writer.Write(": "); - } - if (Target != null) { - writer.Write(Target.ToString()); - writer.Write(" = "); - } - if (IsMoveInstruction) { - writer.Write(Operands[0].ToString()); - if (Instruction != null) { - writer.Write(" (" + Instruction.OpCode.Name + ")"); - } - } else { - if (Instruction == null) { - writer.Write(SpecialOpCode.ToString()); - } else { - writer.Write(Instruction.OpCode.Name); - if(null != Instruction.Operand) { - writer.Write(' '); - Disassembler.DisassemblerHelpers.WriteOperand(new PlainTextOutput(writer), Instruction.Operand); - writer.Write(' '); - } - } - if (TypeOperand != null) { - writer.Write(' '); - writer.Write(TypeOperand.ToString()); - writer.Write(' '); - } - if (Operands.Length > 0) { - writer.Write('('); - for (int i = 0; i < Operands.Length; i++) { - if (i > 0) - writer.Write(", "); - writer.Write(Operands[i].ToString()); - } - writer.Write(')'); - } - } - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaOptimization.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaOptimization.cs deleted file mode 100644 index 5466bf462..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaOptimization.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using System.Diagnostics; - -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Contains some very simple optimizations that work on the SSA form. - /// - static class SsaOptimization - { - public static void Optimize(SsaForm ssaForm) - { - DirectlyStoreToVariables(ssaForm); - SimpleCopyPropagation(ssaForm); - RemoveDeadAssignments(ssaForm); - } - - /// - /// When any instructions stores its result in a stack location that's used only once in a 'stloc' or 'starg' instruction, - /// we optimize this to directly store in the target location. - /// As optimization this is redundant (does the same as copy propagation), but it'll make us keep the variables named - /// after locals instead of keeping the temps as using only the simple copy propagation would do. - /// - public static void DirectlyStoreToVariables(SsaForm ssaForm) - { - foreach (SsaBlock block in ssaForm.Blocks) { - block.Instructions.RemoveAll( - inst => { - if (inst.Instruction != null && (inst.Instruction.OpCode == OpCodes.Stloc || inst.Instruction.OpCode == OpCodes.Starg)) { - SsaVariable target = inst.Target; - SsaVariable temp = inst.Operands[0]; - if (target.IsSingleAssignment && temp.IsSingleAssignment && temp.Usage.Count == 1 && temp.IsStackLocation) { - temp.Definition.Target = target; - return true; - } - } - return false; - }); - } - ssaForm.ComputeVariableUsage(); // update usage after we modified stuff - } - - public static void SimpleCopyPropagation(SsaForm ssaForm, bool onlyForStackLocations = true) - { - foreach (SsaBlock block in ssaForm.Blocks) { - foreach (SsaInstruction inst in block.Instructions) { - if (inst.IsMoveInstruction && inst.Target.IsSingleAssignment && inst.Operands[0].IsSingleAssignment) { - if (inst.Target.IsStackLocation || !onlyForStackLocations) { - // replace all uses of 'target' with 'operands[0]'. - foreach (SsaInstruction useInstruction in inst.Target.Usage) { - useInstruction.ReplaceVariableInOperands(inst.Target, inst.Operands[0]); - } - } - } - } - } - ssaForm.ComputeVariableUsage(); // update usage after we modified stuff - } - - public static void RemoveDeadAssignments(SsaForm ssaForm) - { - HashSet liveVariables = new HashSet(); - // find variables that are used directly - foreach (SsaBlock block in ssaForm.Blocks) { - foreach (SsaInstruction inst in block.Instructions) { - if (!CanRemoveAsDeadCode(inst)) { - if (inst.Target != null) - liveVariables.Add(inst.Target); - foreach (SsaVariable op in inst.Operands) { - liveVariables.Add(op); - } - } - } - } - Queue queue = new Queue(liveVariables); - // find variables that are used indirectly - while (queue.Count > 0) { - SsaVariable v = queue.Dequeue(); - if (v.IsSingleAssignment) { - foreach (SsaVariable op in v.Definition.Operands) { - if (liveVariables.Add(op)) - queue.Enqueue(op); - } - } - } - // remove assignments to all unused variables - foreach (SsaBlock block in ssaForm.Blocks) { - block.Instructions.RemoveAll( - inst => { - if (inst.Target != null && !liveVariables.Contains(inst.Target)) { - Debug.Assert(inst.Target.IsSingleAssignment); - return true; - } - return false; - }); - } - ssaForm.ComputeVariableUsage(); // update usage after we modified stuff - } - - static bool CanRemoveAsDeadCode(SsaInstruction inst) - { - if (inst.Target != null && !inst.Target.IsSingleAssignment) - return false; - switch (inst.SpecialOpCode) { - case SpecialOpCode.Phi: - case SpecialOpCode.Exception: - case SpecialOpCode.Parameter: - case SpecialOpCode.Uninitialized: - return true; - case SpecialOpCode.None: - return inst.IsMoveInstruction; - default: - return false; - } - } - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaVariable.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaVariable.cs deleted file mode 100644 index a4c16f2a1..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaVariable.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Represents a variable used with the SsaInstruction register-based instructions. - /// Despite what the name suggests, the variable is not necessarily in single-assignment form - take a look at "bool IsSingleAssignment". - /// - public sealed class SsaVariable - { - public int OriginalVariableIndex; - public readonly string Name; - public readonly bool IsStackLocation; - - public readonly ParameterDefinition Parameter; - public readonly VariableDefinition Variable; - - public SsaVariable(ParameterDefinition p) - { - this.Name = string.IsNullOrEmpty(p.Name) ? "param" + p.Index : p.Name; - this.Parameter = p; - } - - public SsaVariable(VariableDefinition v) - { - this.Name = string.IsNullOrEmpty(v.Name) ? "V_" + v.Index : v.Name; - this.Variable = v; - } - - public SsaVariable(int stackLocation) - { - this.Name = "stack" + stackLocation; - this.IsStackLocation = true; - } - - public SsaVariable(SsaVariable original, string newName) - { - this.Name = newName; - this.IsStackLocation = original.IsStackLocation; - this.OriginalVariableIndex = original.OriginalVariableIndex; - this.Parameter = original.Parameter; - this.Variable = original.Variable; - } - - public override string ToString() - { - return Name; - } - - /// - /// Gets whether this variable has only a single assignment. - /// This field is initialized in TransformToSsa step. - /// - /// Not all variables can be transformed to single assignment form: variables that have their address taken - /// cannot be represented in SSA (although SimplifyByRefCalls will get rid of the address-taking instruction in almost all cases) - public bool IsSingleAssignment; - - /// - /// Gets the instruction defining the variable. - /// This field is initialized in TransformToSsa step. It is only set for variables with a single assignment. - /// - public SsaInstruction Definition; - - /// - /// Gets the places where a variable is used. - /// If a single instruction reads a variable 2 times (e.g. adding to itself), then it must be included 2 times in this list! - /// - public List Usage; - } -} diff --git a/ICSharpCode.Decompiler/FlowAnalysis/TransformToSsa.cs b/ICSharpCode.Decompiler/FlowAnalysis/TransformToSsa.cs deleted file mode 100644 index 9fbe366e0..000000000 --- a/ICSharpCode.Decompiler/FlowAnalysis/TransformToSsa.cs +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// 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.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; - -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace ICSharpCode.Decompiler.FlowAnalysis -{ - /// - /// Convers a method to static single assignment form. - /// - sealed class TransformToSsa - { - public static void Transform(ControlFlowGraph cfg, SsaForm ssa, bool optimize = true) - { - TransformToSsa transform = new TransformToSsa(cfg, ssa); - transform.ConvertVariablesToSsa(); - SsaOptimization.RemoveDeadAssignments(ssa); // required so that 'MakeByRefCallsSimple' can detect more cases - if (SimplifyByRefCalls.MakeByRefCallsSimple(ssa)) { - transform.ConvertVariablesToSsa(); - } - if (optimize) - SsaOptimization.Optimize(ssa); - } - - readonly ControlFlowGraph cfg; - readonly SsaForm ssaForm; - readonly List[] writeToOriginalVariables; // array index -> SsaVariable OriginalVariableIndex - readonly bool[] addressTaken; // array index -> SsaVariable OriginalVariableIndex; value = whether ldloca instruction was used with variable - - private TransformToSsa(ControlFlowGraph cfg, SsaForm ssaForm) - { - this.cfg = cfg; - this.ssaForm = ssaForm; - this.writeToOriginalVariables = new List[ssaForm.OriginalVariables.Count]; - this.addressTaken = new bool[ssaForm.OriginalVariables.Count]; - } - - #region CollectInformationAboutOriginalVariableUse - void CollectInformationAboutOriginalVariableUse() - { - Debug.Assert(addressTaken.Length == writeToOriginalVariables.Length); - for (int i = 0; i < writeToOriginalVariables.Length; i++) { - Debug.Assert(ssaForm.OriginalVariables[i].OriginalVariableIndex == i); - - addressTaken[i] = false; - // writeToOriginalVariables is only used when placing phi functions - // we don't need to do that anymore for variables that are already in SSA form - if (ssaForm.OriginalVariables[i].IsSingleAssignment) - writeToOriginalVariables[i] = null; - else - writeToOriginalVariables[i] = new List(); - } - foreach (SsaBlock block in ssaForm.Blocks) { - foreach (SsaInstruction inst in block.Instructions) { - if (inst.Target != null ) { - var list = writeToOriginalVariables[inst.Target.OriginalVariableIndex]; - if (list != null) - list.Add(inst); - } - if (inst.Instruction != null) { - if (inst.Instruction.OpCode == OpCodes.Ldloca) { - addressTaken[ssaForm.GetOriginalVariable((VariableDefinition)inst.Instruction.Operand).OriginalVariableIndex] = true; - } else if (inst.Instruction.OpCode == OpCodes.Ldarga) { - addressTaken[ssaForm.GetOriginalVariable((ParameterDefinition)inst.Instruction.Operand).OriginalVariableIndex] = true; - } - } - } - } - } - #endregion - - #region ConvertToSsa - void ConvertVariablesToSsa() - { - CollectInformationAboutOriginalVariableUse(); - bool[] processVariable = new bool[ssaForm.OriginalVariables.Count]; - foreach (SsaVariable variable in ssaForm.OriginalVariables) { - if (!variable.IsSingleAssignment && !addressTaken[variable.OriginalVariableIndex]) { - PlacePhiFunctions(variable); - processVariable[variable.OriginalVariableIndex] = true; - } - } - RenameVariables(processVariable); - foreach (SsaVariable variable in ssaForm.OriginalVariables) { - if (!addressTaken[variable.OriginalVariableIndex]) { - Debug.Assert(variable.IsSingleAssignment && variable.Definition != null); - } - } - ssaForm.ComputeVariableUsage(); - } - #endregion - - #region PlacePhiFunctions - void PlacePhiFunctions(SsaVariable variable) - { - cfg.ResetVisited(); - HashSet blocksWithPhi = new HashSet(); - Queue worklist = new Queue(); - foreach (SsaInstruction writeInstruction in writeToOriginalVariables[variable.OriginalVariableIndex]) { - ControlFlowNode cfgNode = cfg.Nodes[writeInstruction.ParentBlock.BlockIndex]; - if (!cfgNode.Visited) { - cfgNode.Visited = true; - worklist.Enqueue(cfgNode); - } - } - while (worklist.Count > 0) { - ControlFlowNode cfgNode = worklist.Dequeue(); - foreach (ControlFlowNode dfNode in cfgNode.DominanceFrontier) { - // we don't need phi functions in the exit node - if (dfNode.NodeType == ControlFlowNodeType.RegularExit || dfNode.NodeType == ControlFlowNodeType.ExceptionalExit) - continue; - SsaBlock y = ssaForm.Blocks[dfNode.BlockIndex]; - if (blocksWithPhi.Add(y)) { - // add a phi instruction in y - SsaVariable[] operands = Enumerable.Repeat(variable, dfNode.Incoming.Count).ToArray(); - y.Instructions.Insert(0, new SsaInstruction(y, null, variable, operands, specialOpCode: SpecialOpCode.Phi)); - if (!dfNode.Visited) { - dfNode.Visited = true; - worklist.Enqueue(dfNode); - } - } - } - } - } - #endregion - - #region RenameVariable - int tempVariableCounter = 1; - - void RenameVariables(bool[] processVariable) - { - VariableRenamer r = new VariableRenamer(this, processVariable); - r.Visit(ssaForm.EntryPoint); - } - - sealed class VariableRenamer - { - readonly TransformToSsa transform; - readonly ReadOnlyCollection inputVariables; - internal readonly Stack[] versionStacks; - int[] versionCounters; // specifies for each input variable the next version number - - // processVariable = specifies for each input variable whether we should rename it - public VariableRenamer(TransformToSsa transform, bool[] processVariable) - { - this.transform = transform; - this.inputVariables = transform.ssaForm.OriginalVariables; - Debug.Assert(inputVariables.Count == processVariable.Length); - this.versionCounters = new int[inputVariables.Count]; - this.versionStacks = new Stack[inputVariables.Count]; - for (int i = 0; i < versionStacks.Length; i++) { - if (processVariable[i]) { - Debug.Assert(inputVariables[i].IsSingleAssignment == false); - // only create version stacks for the variables that we need to process and that weren't already processed earlier - versionStacks[i] = new Stack(); - versionStacks[i].Push(inputVariables[i]); - } - } - } - - SsaVariable MakeNewVersion(int variableIndex) - { - int versionCounter = ++versionCounters[variableIndex]; - SsaVariable x = inputVariables[variableIndex]; - if (versionCounter == 1) { - return x; - } else { - if (x.IsStackLocation) { - return new SsaVariable(x, "temp" + (transform.tempVariableCounter++)); - } else { - return new SsaVariable(x, x.Name + "_" + versionCounter); - } - } - } - - internal void Visit(SsaBlock block) - { - // duplicate top of all stacks - foreach (var stack in versionStacks) { - if (stack != null) - stack.Push(stack.Peek()); - } - - foreach (SsaInstruction s in block.Instructions) { - // replace all uses of variables being processed with their current version. - if (s.SpecialOpCode != SpecialOpCode.Phi) { - for (int i = 0; i < s.Operands.Length; i++) { - var stack = versionStacks[s.Operands[i].OriginalVariableIndex]; - if (stack != null) - s.Operands[i] = stack.Peek(); - } - } - // if we're writing to a variable we should process: - if (s.Target != null) { - int targetIndex = s.Target.OriginalVariableIndex; - if (versionStacks[targetIndex] != null) { - s.Target = MakeNewVersion(targetIndex); - s.Target.IsSingleAssignment = true; - s.Target.Definition = s; - - // we already pushed our entry for this SsaBlock at the beginning (where we duplicated all stacks), - // so now replace the top element - versionStacks[targetIndex].Pop(); - versionStacks[targetIndex].Push(s.Target); - } - } - } - - foreach (SsaBlock succ in block.Successors) { - int j = succ.Predecessors.IndexOf(block); - Debug.Assert(j >= 0); - foreach (SsaInstruction f in succ.Instructions) { - if (f.SpecialOpCode == SpecialOpCode.Phi) { - var stack = versionStacks[f.Target.OriginalVariableIndex]; - if (stack != null) { - f.Operands[j] = stack.Peek(); - } - } - } - } - foreach (ControlFlowNode child in transform.cfg.Nodes[block.BlockIndex].DominatorTreeChildren) - Visit(transform.ssaForm.Blocks[child.BlockIndex]); - // restore stacks: - foreach (var stack in versionStacks) { - if (stack != null) - stack.Pop(); - } - } - } - #endregion - } -} diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 1d3ee647a..b26e19ae1 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -76,6 +76,8 @@ + + True True @@ -93,17 +95,19 @@ + - - - + + + + @@ -169,6 +173,9 @@ + + + diff --git a/ICSharpCode.Decompiler/IL/BlockBuilder.cs b/ICSharpCode.Decompiler/IL/BlockBuilder.cs index e9f98c111..15ce70e4f 100644 --- a/ICSharpCode.Decompiler/IL/BlockBuilder.cs +++ b/ICSharpCode.Decompiler/IL/BlockBuilder.cs @@ -172,7 +172,6 @@ namespace ICSharpCode.Decompiler.IL case OpCode.BlockContainer: var container = (BlockContainer)inst; containerStack.Push(container); - container.EntryPoint.IncomingEdgeCount++; // count the entry edge foreach (var block in container.Blocks) ConnectBranches(block); containerStack.Pop(); diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 23a765234..be3599b03 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -70,6 +70,8 @@ namespace ICSharpCode.Decompiler.IL EndFinally, /// If statement / conditional expression. if (condition) trueExpr else falseExpr IfInstruction, + /// Infinite loop + Loop, /// Try-catch statement. TryCatch, /// Catch handler within a try-catch statement. @@ -606,6 +608,37 @@ namespace ICSharpCode.Decompiler.IL } } + /// Infinite loop + public sealed partial class Loop : ILInstruction + { + public Loop(ILInstruction body) : base(OpCode.Loop) + { + this.Body = body; + } + ILInstruction body; + public ILInstruction Body { + get { return this.body; } + set { + ValidateChild(value); + SetChildInstruction(ref this.body, value); + } + } + public override IEnumerable Children { + get { + yield return this.body; + } + } + public override void TransformChildren(ILVisitor visitor) + { + this.Body = this.body.AcceptVisitor(visitor); + } + public override StackType ResultType { get { return StackType.Void; } } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitLoop(this); + } + } + /// Try-catch statement. public sealed partial class TryCatch : TryInstruction { @@ -2104,6 +2137,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } + protected internal virtual T VisitLoop(Loop inst) + { + return Default(inst); + } protected internal virtual T VisitTryCatch(TryCatch inst) { return Default(inst); @@ -2382,6 +2419,7 @@ namespace ICSharpCode.Decompiler.IL "br", "endfinally", "if", + "loop", "try.catch", "try.catch.handler", "try.finally", diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index f3e7b059f..fe7b41d26 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -71,6 +71,11 @@ new ChildInfo("trueInst"), new ChildInfo("falseInst"), }), CustomConstructor, CustomComputeFlags, CustomWriteTo), + new OpCode("loop", "Infinite loop", + CustomChildren(new []{ + new ChildInfo("body") + }), + CustomComputeFlags, CustomWriteTo, ResultType("Void")), new OpCode("try.catch", "Try-catch statement.", BaseClass("TryInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo), new OpCode("try.catch.handler", "Catch handler within a try-catch statement.", diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index 54dc0f049..2bd4fd1a9 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Text; @@ -59,7 +60,37 @@ namespace ICSharpCode.Decompiler.IL { public readonly InstructionCollection Instructions; ILInstruction finalInstruction; + + /// + /// For blocks in a block container, this field holds + /// the number of incoming control flow edges to this block. + /// + /// + /// This variable is automatically updated when adding/removing branch instructions from the ILAst, + /// or when adding the block as an entry point to a BlockContainer. + /// + public int IncomingEdgeCount; + /// + /// Returns the index of the block in the parent BlockContainer's block list. + /// Returns 0 if the block is not in a BlockContainer. + /// + public int Index { + get { + // TODO: we can offer this in O(1) by making the + // parent BlockContainer store this in the blocks, + // but I'm not sure if it's worth the complexity. + // We'll have to see if the Index is useful in more than a few places. + // (otherwise those few places could use a Dictionary) + var bc = Parent as BlockContainer; + if (bc != null) { + return bc.Blocks.IndexOf(this); + } else { + return 0; + } + } + } + public ILInstruction FinalInstruction { get { return finalInstruction; @@ -70,7 +101,6 @@ namespace ICSharpCode.Decompiler.IL } } - public int IncomingEdgeCount; public Block() : base(OpCode.Block) { @@ -178,9 +208,24 @@ namespace ICSharpCode.Decompiler.IL inst = new Void(new StLoc(inst, variable)); } Instructions[i] = inst; + if (inst.HasFlag(InstructionFlags.EndPointUnreachable)) + return; } FinalInstruction = FinalInstruction.Inline(InstructionFlags.None, state); FinalInstruction.TransformStackIntoVariables(state); + if (FinalInstruction.HasFlag(InstructionFlags.EndPointUnreachable)) + return; + var bc = Parent as BlockContainer; + if (bc != null) { + // If this block allows us to fall out of the container, + // remember the variable stack in state.FinalVariables. + ImmutableArray variables; + if (state.FinalVariables.TryGetValue(bc, out variables)) { + state.MergeVariables(state.Variables, variables.ToStack()); + } else { + state.FinalVariables.Add(bc, state.Variables.ToImmutableArray()); + } + } } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs index de6ff6923..9a55466c2 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs @@ -40,10 +40,16 @@ namespace ICSharpCode.Decompiler.IL partial class BlockContainer : ILInstruction { public readonly InstructionCollection Blocks; + Block entryPoint; public Block EntryPoint { - get { - return Blocks[0]; + get { return entryPoint; } + private set { + if (entryPoint != null && IsConnected) + entryPoint.IncomingEdgeCount--; + entryPoint = value; + if (entryPoint != null && IsConnected) + entryPoint.IncomingEdgeCount++; } } @@ -52,6 +58,26 @@ namespace ICSharpCode.Decompiler.IL this.Blocks = new InstructionCollection(this); } + protected internal override void InstructionCollectionUpdateComplete() + { + base.InstructionCollectionUpdateComplete(); + this.EntryPoint = this.Blocks.FirstOrDefault(); + } + + protected override void Connected() + { + base.Connected(); + if (entryPoint != null) + entryPoint.IncomingEdgeCount++; + } + + protected override void Disconnected() + { + base.Disconnected(); + if (entryPoint != null) + entryPoint.IncomingEdgeCount--; + } + public override void WriteTo(ITextOutput output) { output.WriteLine("BlockContainer {"); @@ -81,7 +107,8 @@ namespace ICSharpCode.Decompiler.IL internal override void CheckInvariant() { base.CheckInvariant(); - Debug.Assert(Blocks.Count >= 1); + Debug.Assert(EntryPoint == Blocks[0]); + Debug.Assert(!IsConnected || EntryPoint.IncomingEdgeCount >= 1); } protected override InstructionFlags ComputeFlags() @@ -107,6 +134,11 @@ namespace ICSharpCode.Decompiler.IL internal override void TransformStackIntoVariables(TransformStackIntoVariablesState state) { EntryPoint.TransformStackIntoVariables(state); + ImmutableArray variables; + if (state.FinalVariables.TryGetValue(this, out variables)) + state.Variables = variables.ToStack(); + else + state.Variables.Clear(); } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/Branch.cs b/ICSharpCode.Decompiler/IL/Instructions/Branch.cs index bb30a0a55..98f6500b2 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Branch.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Branch.cs @@ -49,6 +49,14 @@ namespace ICSharpCode.Decompiler.IL this.targetILOffset = targetILOffset; } + public Branch(Block targetBlock) : base(OpCode.Branch) + { + if (targetBlock == null) + throw new ArgumentNullException("targetBlock"); + this.targetBlock = targetBlock; + this.targetILOffset = targetBlock.ILRange.Start; + } + protected override InstructionFlags ComputeFlags() { var flags = InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable; diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs index ad289cb2d..9a39ac502 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs @@ -22,6 +22,7 @@ using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; +using ICSharpCode.NRefactory.Utils; using ICSharpCode.Decompiler.CSharp; namespace ICSharpCode.Decompiler.IL @@ -152,6 +153,15 @@ namespace ICSharpCode.Decompiler.IL /// public abstract void TransformChildren(ILVisitor visitor); + /// + /// Returns all descendants of the ILInstruction. + /// + public IEnumerable Descendants { + get { + return TreeTraversal.PreOrder(Children, inst => inst.Children); + } + } + /// /// Attempts inlining from the inline context into this instruction. /// @@ -237,18 +247,30 @@ namespace ICSharpCode.Decompiler.IL InvalidateFlags(); } - protected internal void AddChildInstruction(ILInstruction newChild) + /// + /// Called when a new child is added to a InstructionCollection. + /// + protected internal void InstructionCollectionAdded(ILInstruction newChild) { if (refCount > 0) newChild.AddRef(); newChild.parent = this; - InvalidateFlags(); } - protected internal void RemoveChildInstruction(ILInstruction newChild) + /// + /// Called when a child is removed from a InstructionCollection. + /// + protected internal void InstructionCollectionRemoved(ILInstruction newChild) { if (refCount > 0) newChild.ReleaseRef(); + } + + /// + /// Called when a series of add/remove operations on the InstructionCollection is complete. + /// + protected internal virtual void InstructionCollectionUpdateComplete() + { InvalidateFlags(); } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs b/ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs index bc77848da..8bfc2327b 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs @@ -35,22 +35,25 @@ namespace ICSharpCode.Decompiler.IL protected override void ClearItems() { foreach (var child in this) - parentInstruction.RemoveChildInstruction(child); + parentInstruction.InstructionCollectionRemoved(child); base.ClearItems(); + parentInstruction.InstructionCollectionUpdateComplete(); } protected override void InsertItem(int index, T item) { if (item == null) throw new ArgumentNullException("item"); - parentInstruction.AddChildInstruction(item); + parentInstruction.InstructionCollectionAdded(item); base.InsertItem(index, item); + parentInstruction.InstructionCollectionUpdateComplete(); } protected override void RemoveItem(int index) { - parentInstruction.RemoveChildInstruction(this[index]); + parentInstruction.InstructionCollectionRemoved(this[index]); base.RemoveItem(index); + parentInstruction.InstructionCollectionUpdateComplete(); } protected override void SetItem(int index, T item) @@ -59,13 +62,15 @@ namespace ICSharpCode.Decompiler.IL throw new ArgumentNullException("item"); if (this[index] == item) return; - parentInstruction.RemoveChildInstruction(this[index]); - parentInstruction.AddChildInstruction(item); + parentInstruction.InstructionCollectionRemoved(this[index]); + parentInstruction.InstructionCollectionAdded(item); base.SetItem(index, item); + parentInstruction.InstructionCollectionUpdateComplete(); } public int RemoveAll(Predicate predicate) { + // TODO: optimize int removed = 0; for (int i = 0; i < this.Count;) { if (predicate(this[i])) { diff --git a/ICSharpCode.Decompiler/IL/Instructions/Loop.cs b/ICSharpCode.Decompiler/IL/Instructions/Loop.cs new file mode 100644 index 000000000..4eb485aac --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Instructions/Loop.cs @@ -0,0 +1,50 @@ +// 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; + +namespace ICSharpCode.Decompiler.IL +{ + partial class Loop + { + public override void WriteTo(ITextOutput output) + { + output.Write("loop "); + body.WriteTo(output); + } + + protected override InstructionFlags ComputeFlags() + { + return Block.Phase1Boundary(body.Flags) + | InstructionFlags.EndPointUnreachable + | InstructionFlags.SideEffect; // an infinite loop is a side effect + } + + internal override ILInstruction Inline(InstructionFlags flagsBefore, IInlineContext context) + { + return this; + } + + internal override void TransformStackIntoVariables(TransformStackIntoVariablesState state) + { + var initialVariables = state.Variables.Clone(); + Body = Body.Inline(InstructionFlags.None, state); + Body.TransformStackIntoVariables(state); + state.MergeVariables(initialVariables, state.Variables); + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/LoopDetection.cs b/ICSharpCode.Decompiler/IL/Transforms/LoopDetection.cs new file mode 100644 index 000000000..9d4c37ead --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/LoopDetection.cs @@ -0,0 +1,206 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.Decompiler.FlowAnalysis; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Detect loops in IL AST. + /// + public class LoopDetection : IILTransform + { + #region Construct Control Flow Graph + /// + /// Constructs a control flow graph for the blocks in the given block container. + /// The graph nodes will have the same indices as the blocks in the block container. + /// An additional exit node is used to signal when a block potentially falls through + /// to the endpoint of the BlockContainer. + /// Return statements, exceptions, or branches leaving the block container are not + /// modeled by the control flow graph. + /// + static ControlFlowNode BuildCFG(BlockContainer bc) + { + ControlFlowNode[] nodes = new ControlFlowNode[bc.Blocks.Count]; + for (int i = 0; i < nodes.Length; i++) { + nodes[i] = new ControlFlowNode { UserData = bc.Blocks[i] }; + } + ControlFlowNode exit = new ControlFlowNode(); + + // Create edges: + for (int i = 0; i < bc.Blocks.Count; i++) { + var block = bc.Blocks[i]; + var sourceNode = nodes[i]; + foreach (var branch in block.Descendants.OfType()) { + if (branch.TargetBlock.Parent == bc) { + sourceNode.AddEdgeTo(nodes[branch.TargetBlock.Index]); + } else { + // Note: edges into different block containers are ignored: + // Either they point to a nested block container in the source block, + // in which case we can ignore them for control flow purposes; + // or they jump to a parent block container, in which case they act + // like a return statement or exceptional exit. + } + } + if (!block.HasFlag(InstructionFlags.EndPointUnreachable)) + sourceNode.AddEdgeTo(exit); + } + + if (nodes[0].Predecessors.Count != 0) { + // Create artificial entry point without predecessors: + ControlFlowNode entry = new ControlFlowNode(); + entry.AddEdgeTo(nodes[0]); + return entry; + } else { + return nodes[0]; + } + } + #endregion + + /// + /// Run loop detection for all block containers in the function (including nested lambda functions). + /// + public void Run(ILFunction function, ILTransformContext context) + { + foreach (var blockContainer in function.Descendants.OfType().ToArray()) { + Run(blockContainer, context); + } + } + + /// + /// Run loop detection for blocks in the block container. + /// + public void Run(BlockContainer blockContainer, ILTransformContext context) + { + var entryPoint = BuildCFG(blockContainer); + Dominance.ComputeDominance(entryPoint, context.CancellationToken); + FindLoops(entryPoint); + } + + /// + /// Recurse into the dominator tree and find back edges/natural loops. + /// + /// + /// A back edge is an edge t->h so that h dominates t. + /// The natural loop of the back edge is the smallest set of nodes that includes the back edge + /// and has no predecessors outside the set except for the predecessor of the header. + /// + /// Preconditions: + /// * dominance was computed for h + /// * all blocks in the dominator subtree starting at h are in the same BlockContainer + /// * the visited flag is set to false + /// + void FindLoops(ControlFlowNode h) + { + List loop = null; + foreach (var t in h.Predecessors) { + if (h.Dominates(t)) { + // h->t is a back edge, and h is a loop header + // Add the natural loop of t->h to the loop. + if (loop == null) { + loop = new List(); + loop.Add(h); + // Mark loop header as visited so that the pre-order traversal + // stops at the loop header. + h.Visited = true; + } + t.TraversePreOrder(n => n.Predecessors, loop.Add); + } + } + if (loop != null) { + // loop now is the union of all natural loops with loop head h + Debug.Assert(loop[0] == h); + foreach (var node in loop) { + 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); + } + // Recurse into the dominator tree to find other possible loop heads + foreach (var child in h.DominatorTreeChildren) { + FindLoops(child); + } + } + + /// + /// Move the blocks associated with the loop into a new block container. + /// + void ConstructLoop(List loop) + { + Block oldEntryPoint = (Block)loop[0].UserData; + BlockContainer oldContainer = (BlockContainer)oldEntryPoint.Parent; + + BlockContainer body = new BlockContainer(); + Block newEntryPoint = new Block(); + body.Blocks.Add(newEntryPoint); + // Move contents of oldEntryPoint to newEntryPoint + // (we can't move the block itself because it might be the target of branch instructions outside the loop) + newEntryPoint.Instructions.ReplaceList(oldEntryPoint.Instructions); + newEntryPoint.FinalInstruction = oldEntryPoint.FinalInstruction; + newEntryPoint.ILRange = oldEntryPoint.ILRange; + oldEntryPoint.Instructions.ReplaceList(new[] { new Loop(body) }); + oldEntryPoint.FinalInstruction = new Nop(); + + // Move other blocks into the loop body: they're all dominated by the loop header, + // and thus cannot be the target of branch instructions outside the loop. + for (int i = 1; i < loop.Count; i++) { + Block block = (Block)loop[i].UserData; + Debug.Assert(block.Parent == oldContainer); + body.Blocks.Add(block); + } + // Remove all blocks that were moved into the body from the old container + oldContainer.Blocks.RemoveAll(b => b.Parent != oldContainer); + + // Rewrite branches within the loop from oldEntryPoint to newEntryPoint: + foreach (var branch in body.Descendants.OfType()) { + if (branch.TargetBlock == oldEntryPoint) + branch.TargetBlock = newEntryPoint; + } + + // Because we use extended basic blocks, not just basic blocks, it's possible + // that we moved too many instructions into the loop body. + // In cases where those additional instructions branch, that's not really a problem + // (and is more readable with the instructions being inside the loop, anyways) + // But in cases where those additional instructions fall through to the end of + // the block, they previously had the semantics of falling out of the oldContainer, + // thus leaving the loop. + // Now they just fall out of the body block container, and thus get executed repeatedly. + // To fix up this semantic difference, we'll patch up these blocks to branch to + // a new empty block in the old container. + Block fallthrough_helper_block = null; + foreach (var block in body.Blocks) { + if (!block.HasFlag(InstructionFlags.EndPointUnreachable)) { + if (block.FinalInstruction.OpCode != OpCode.Nop) { + // move final instruction into the block + block.Instructions.Add(new Void(block.FinalInstruction)); + block.FinalInstruction = new Nop(); + } + if (fallthrough_helper_block == null) { + fallthrough_helper_block = new Block(); + oldContainer.Blocks.Add(fallthrough_helper_block); + } + block.Instructions.Add(new Branch(fallthrough_helper_block)); + } + Debug.Assert(block.HasFlag(InstructionFlags.EndPointUnreachable)); + } + } + } +} diff --git a/ICSharpCode.Decompiler/IL/TransformStackIntoVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformStackIntoVariables.cs similarity index 97% rename from ICSharpCode.Decompiler/IL/TransformStackIntoVariables.cs rename to ICSharpCode.Decompiler/IL/Transforms/TransformStackIntoVariables.cs index 42283e4e5..977ca1428 100644 --- a/ICSharpCode.Decompiler/IL/TransformStackIntoVariables.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformStackIntoVariables.cs @@ -19,12 +19,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace ICSharpCode.Decompiler.IL { public class ILTransformContext { public IDecompilerTypeSystem TypeSystem { get; set; } + public CancellationToken CancellationToken { get; set; } } public interface IILTransform diff --git a/ICSharpCode.Decompiler/IL/Instructions/TransformStackIntoVariablesState.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformStackIntoVariablesState.cs similarity index 93% rename from ICSharpCode.Decompiler/IL/Instructions/TransformStackIntoVariablesState.cs rename to ICSharpCode.Decompiler/IL/Transforms/TransformStackIntoVariablesState.cs index 3c6b008b6..da6c718c3 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/TransformStackIntoVariablesState.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformStackIntoVariablesState.cs @@ -29,6 +29,7 @@ namespace ICSharpCode.Decompiler.IL public Stack Variables { get; set; } public UnionFind UnionFind { get; set; } public Dictionary> InitialVariables { get; set; } + public Dictionary> FinalVariables { get; set; } public IDecompilerTypeSystem TypeSystem { get; set; } public TransformStackIntoVariablesState() @@ -36,6 +37,7 @@ namespace ICSharpCode.Decompiler.IL Variables = new Stack(); UnionFind = new UnionFind(); InitialVariables = new Dictionary>(); + FinalVariables = new Dictionary>(); } public void MergeVariables(Stack a, Stack b) diff --git a/ICSharpCode.Decompiler/IL/TransformingVisitor.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformingVisitor.cs similarity index 100% rename from ICSharpCode.Decompiler/IL/TransformingVisitor.cs rename to ICSharpCode.Decompiler/IL/Transforms/TransformingVisitor.cs diff --git a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj index ad7c18b66..0b3242b46 100644 --- a/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj @@ -100,7 +100,8 @@ - + + diff --git a/ICSharpCode.Decompiler/Tests/ILTransforms/Inlining.cs b/ICSharpCode.Decompiler/Tests/ILTransforms/InliningTests.cs similarity index 99% rename from ICSharpCode.Decompiler/Tests/ILTransforms/Inlining.cs rename to ICSharpCode.Decompiler/Tests/ILTransforms/InliningTests.cs index 5fc1add6b..9f688beea 100644 --- a/ICSharpCode.Decompiler/Tests/ILTransforms/Inlining.cs +++ b/ICSharpCode.Decompiler/Tests/ILTransforms/InliningTests.cs @@ -26,7 +26,7 @@ namespace ICSharpCode.Decompiler.Tests.ILTransforms /// /// IL Inlining unit tests. /// - public class Inlining + public class InliningTests { static ILFunction MakeFunction(ILInstruction body) { diff --git a/ICSharpCode.Decompiler/Tests/Loops.cs b/ICSharpCode.Decompiler/Tests/Loops.cs index 03427a8bd..2731d50c1 100644 --- a/ICSharpCode.Decompiler/Tests/Loops.cs +++ b/ICSharpCode.Decompiler/Tests/Loops.cs @@ -70,5 +70,18 @@ public class Loops array[i].ToLower(); } } + + public void NestedLoops() + { + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) { + for (int j = 0; j < 5; j++) { + Console.WriteLine("Y"); + } + } else { + Console.WriteLine("X"); + } + } + } } diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs index f3540ce5b..62bdca0c7 100644 --- a/ILSpy/Languages/ILAstLanguage.cs +++ b/ILSpy/Languages/ILAstLanguage.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.Disassembler; using ICSharpCode.Decompiler.IL; using Mono.Cecil; @@ -88,14 +89,10 @@ namespace ICSharpCode.ILSpy internal static IEnumerable GetDebugLanguages() { yield return new TypedIL(); - yield return new BlockIL(null); - yield return new BlockIL(new TransformingVisitor()); - //yield return new ILAstLanguage { name = "ILAst (unoptimized)", inlineVariables = false }; - //string nextName = "ILAst (variable splitting)"; - //foreach (ILAstOptimizationStep step in Enum.GetValues(typeof(ILAstOptimizationStep))) { - // yield return new ILAstLanguage { name = nextName, abortBeforeStep = step }; - // nextName = "ILAst (after " + step + ")"; - //} + CSharpDecompiler decompiler = new CSharpDecompiler(ModuleDefinition.CreateModule("Dummy", ModuleKind.Dll)); + for (int i = 0; i <= decompiler.ILTransforms.Count; i++) { + yield return new BlockIL(decompiler.ILTransforms.Take(i).ToList()); + } } public override string FileExtension { @@ -136,11 +133,11 @@ namespace ICSharpCode.ILSpy class BlockIL : ILAstLanguage { - readonly ILVisitor transformer; + readonly IReadOnlyList transforms; - public BlockIL(ILVisitor transformer) : base(transformer == null ? "ILAst (blocks)" : "ILAst (" + transformer.ToString() + ")") + public BlockIL(IReadOnlyList transforms) : base(transforms.Count == 0 ? "ILAst (blocks)" : "ILAst (" + transforms.Last().GetType().Name + ")") { - this.transformer = transformer; + this.transforms = transforms; } public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) @@ -150,9 +147,9 @@ namespace ICSharpCode.ILSpy return; var typeSystem = new DecompilerTypeSystem(method.Module); ILReader reader = new ILReader(typeSystem); - ILInstruction il = reader.ReadIL(method.Body, options.CancellationToken); - if (transformer != null) - il = il.AcceptVisitor(transformer); + ILFunction il = reader.ReadIL(method.Body, options.CancellationToken); + foreach (var transform in transforms) + transform.Run(il, new ILTransformContext { TypeSystem = typeSystem }); il.WriteTo(output); } }