diff --git a/libs/UIX.DecompXml/ControlFlowAnalyzer.cs b/libs/UIX.DecompXml/ControlFlowAnalyzer.cs index 17c92a9..a16cf7a 100644 --- a/libs/UIX.DecompXml/ControlFlowAnalyzer.cs +++ b/libs/UIX.DecompXml/ControlFlowAnalyzer.cs @@ -7,7 +7,7 @@ using System.Text; namespace Microsoft.Iris.DecompXml; -public static class ControlFlowAnalyzer +public class ControlFlowAnalyzer { private static readonly ImmutableHashSet _jumpOpCodes = [ OpCode.JumpIfFalse, OpCode.JumpIfFalsePeek, OpCode.JumpIfTruePeek, @@ -18,7 +18,67 @@ public static class ControlFlowAnalyzer OpCode.ReturnValue, OpCode.ReturnVoid, OpCode.Jump, ]; - public static List CreateGraph(Instruction[] instructions) + public ControlFlowAnalyzer(Instruction[] instructions) + { + ControlBlocks = CreateGraph(instructions); + } + + public List ControlBlocks { get; } + + public IProgramBlock GetByOffset(uint offset) + { + return ControlBlocks.First(b => offset >= b.StartOffset && offset <= b.EndOffset); + } + + public IProgramBlock GetByStartOffset(uint offset) => ControlBlocks.FirstOrDefault(b => offset == b.StartOffset); + + public IProgramBlock GetByInstruction(Instruction instruction) => GetByOffset(instruction.Offset); + + public bool IsAlwaysExecuted(uint offset) + { + if (ControlBlocks.Count == 1) + return true; + + // Checks if the entry node is post-dominated by the block containing this offset. + // Essentially, do all paths through this method execute code at this offset? + + var blockOfInterest = GetByStartOffset(offset); + if (blockOfInterest is null) + return false; + + HashSet exitBlockOffsets = new(ControlBlocks + .Where(b => b.Body[^1].OpCode is OpCode.ReturnValue or OpCode.ReturnVoid) + .Select(b => b.StartOffset)); + + HashSet visitedStartOffsets = []; + Stack stack = []; + + stack.Push(ControlBlocks[0]); + + while (stack.Count > 0) + { + var current = stack.Pop(); + var currentOffset = current.StartOffset; + + if (currentOffset == offset || visitedStartOffsets.Contains(currentOffset)) + continue; + + if (exitBlockOffsets.Contains(currentOffset)) + return false; + + visitedStartOffsets.Add(current.StartOffset); + + foreach (var childOffset in current.GetChildrenStartOffsets()) + { + var child = GetByStartOffset(childOffset); + stack.Push(child); + } + } + + return true; + } + + private static List CreateGraph(Instruction[] instructions) { // See slide 15 (page 8) of https://www.cs.utexas.edu/~lin/cs380c/handout03.pdf @@ -78,62 +138,9 @@ public static class ControlFlowAnalyzer return blocks; } - public static IProgramBlock GetByOffset(this List blocks, uint offset) + public string SerializeToGraphviz() { - return blocks.First(b => offset >= b.StartOffset && offset <= b.EndOffset); - } - - public static IProgramBlock GetByStartOffset(this List blocks, uint offset) => blocks.FirstOrDefault(b => offset == b.StartOffset); - - public static IProgramBlock GetByInstruction(this List blocks, Instruction instruction) => blocks.GetByOffset(instruction.Offset); - - public static bool IsAlwaysExecuted(this List blocks, uint offset) - { - if (blocks.Count == 1) - return true; - - // Checks if the entry node is post-dominated by the block containing this offset. - // Essentially, do all paths through this method execute code at this offset? - - var blockOfInterest = blocks.GetByStartOffset(offset); - if (blockOfInterest is null) - return false; - - HashSet exitBlockOffsets = new(blocks - .Where(b => b.Body[^1].OpCode is OpCode.ReturnValue or OpCode.ReturnVoid) - .Select(b => b.StartOffset)); - - HashSet visitedStartOffsets = []; - Stack stack = []; - - stack.Push(blocks[0]); - - while (stack.Count > 0) - { - var current = stack.Pop(); - var currentOffset = current.StartOffset; - - if (currentOffset == offset || visitedStartOffsets.Contains(currentOffset)) - continue; - - if (exitBlockOffsets.Contains(currentOffset)) - return false; - - visitedStartOffsets.Add(current.StartOffset); - - foreach (var childOffset in current.GetChildrenStartOffsets()) - { - var child = blocks.GetByStartOffset(childOffset); - stack.Push(child); - } - } - - return true; - } - - public static string SerializeToGraphviz(IEnumerable blocks) - { - var sortedBlocks = blocks.OrderBy(b => b.StartOffset).ToArray(); + var sortedBlocks = ControlBlocks.OrderBy(b => b.StartOffset).ToArray(); StringBuilder sb = new(); sb.AppendLine("digraph G {"); @@ -141,7 +148,7 @@ public static class ControlFlowAnalyzer sb.AppendLine(" node [shape=none];"); var rank = 0; - foreach (var block in blocks) + foreach (var block in sortedBlocks) { var nodeId = block.StartOffset; diff --git a/libs/UIX.DecompXml/Decompiler.Script.cs b/libs/UIX.DecompXml/Decompiler.Script.cs index d05ef8f..22d8e69 100644 --- a/libs/UIX.DecompXml/Decompiler.Script.cs +++ b/libs/UIX.DecompXml/Decompiler.Script.cs @@ -30,8 +30,9 @@ partial class Decompiler { var methodBody = _context.GetMethodBody(startOffset).ToArray(); - var controlBlocks = ControlFlowAnalyzer.CreateGraph(methodBody); - var dotGraph = ControlFlowAnalyzer.SerializeToGraphviz(controlBlocks); + var cfa = new ControlFlowAnalyzer(methodBody); + var controlBlocks = cfa.ControlBlocks; + var dotGraph = cfa.SerializeToGraphviz(); Console.WriteLine(dotGraph); Stack blockStack = []; @@ -66,14 +67,14 @@ partial class Decompiler // If multiple blocks lead to this address, then we're outside of the IF clause entirely. // Otherwise, it's probably the start of an ELSE clause. - var currentControlBlock = controlBlocks.GetByInstruction(instruction); + var currentControlBlock = cfa.GetByInstruction(instruction); if (controlBlocks.Count(b => b.HasEdgeTo(currentControlBlock, controlBlocks)) <= 1) { blockStack.Push(new(instruction.Offset, uint.MaxValue, SyntaxKind.ElseClause, null)); } } - if (controlBlocks.IsAlwaysExecuted(instruction.Offset)) + if (cfa.IsAlwaysExecuted(instruction.Offset)) { while (blockStack.Count > 1) {