static ControlFlowGraph ReduceBreak(ControlFlowGraph graph, CfgLoop loop, LoopPart part) { // Func<string> vis = () => graph.ToVis().AddPointer("part", part.Index).ToString(); // For VS Code debug visualisation // Break = needs to exit the loop to be a break if (!part.Break) { return(graph); } // Outside entry = non-structured code, so give up if (part.OutsideEntry) { return(graph); } var children = graph.Children(part.Index); if (children.Length != 2) { throw new ControlFlowGraphException($"Break at {part.Index} has unexpected child count ({children.Length})", graph); } bool isfirstChildInLoop = loop.Body.Any(x => x.Index == children[0]) || children[0] == loop.Header.Index; var exitTarget = isfirstChildInLoop ? children[1] : children[0]; // Add LoopSuccessor edge if this is the last link to the MainExit. var remainingLoopChildren = loop.Body .Where(x => x.Index != part.Index) .Aggregate( (IEnumerable <int>)graph.Children(loop.Header.Index), (current, x) => current.Union(graph.Children(x.Index))); if (loop.MainExit.HasValue && !remainingLoopChildren.Contains(loop.MainExit.Value)) { graph = graph.AddEdge(loop.Header.Index, loop.MainExit.Value, CfgEdge.LoopSuccessor); } if (exitTarget != loop.MainExit) { var targetChildren = graph.Children(exitTarget); if (targetChildren.Length != 1 || targetChildren[0] != loop.MainExit) { return(graph); } var condition = graph.Nodes[part.Index]; if (graph.GetEdgeLabel(part.Index, exitTarget) == CfgEdge.False) { condition = Emit.Negation(condition); } var ifNode = Emit.If(condition, Emit.Seq(graph.Nodes[exitTarget], Emit.Break())); return(graph .RemoveNode(exitTarget) .ReplaceNode(part.Index, ifNode)); } return(ReplaceLoopBranch(graph, part.Index, exitTarget, Emit.Break())); }
public static (ControlFlowGraph, string) Decompile(ControlFlowGraph graph) { var(reachability, reachableCount) = graph.Reverse().GetReachability(graph.ExitIndex); if (reachableCount == graph.ActiveNodeCount) { return(graph, null); } var acyclic = graph.RemoveBackEdges(); var distances = acyclic.GetLongestPaths(acyclic.EntryIndex); int longestDistance = 0; int winner = -1; for (int i = 0; i < graph.NodeCount; i++) { if (graph.Nodes[i] == null || reachability[i]) // Only consider nodes that can't reach the exit { continue; } if (distances[i] <= longestDistance) { continue; } longestDistance = distances[i]; winner = i; } if (winner == -1) { return(graph, null); } foreach (var backEdge in graph.GetBackEdges().Where(x => x.start == winner)) { return(graph.AddEdge(backEdge.end, graph.ExitIndex, CfgEdge.LoopSuccessor), Description); } return(graph.AddEdge(winner, graph.ExitIndex, CfgEdge.DisjointGraphFixup), Description); }
public static void DoSplitBlocksIntoAssignmentsAndPredicates(ControlFlowGraph cfg) { cfg.Vertices.ForEach(v => { if (v.BalancedCode.IsEmpty() || v.Residue.IsEmpty()) { return; } var ass = (v.BalancedCode.Last() is Assign) ? v.BalancedCode.Last().AssertCast <Assign>() : null; var lhs = ass == null ? null : ass.Lhs.AssertCast <Ref>().Sym; var @ref = v.Residue.AssertSingle() is Ref ? v.Residue.AssertSingle().AssertCast <Ref>().Sym : null; if (lhs != null && @ref != null && lhs.ProtoId == @ref.ProtoId) { // todo. this introduces a nasty bug if assigned variable is reused later v.BalancedCode.RemoveLast(); v.Residue.SetElements(ass.Rhs); } if (v.BalancedCode.IsEmpty() || v.Residue.IsEmpty()) { return; } var v_test = new ControlFlowBlock(); v_test.Residue.Add(v.Residue.AssertSingle()); v.Residue.RemoveElements(); var outEdges = cfg.Vedges(v, null); cfg.RemoveEdges(outEdges); cfg.AddVertex(v_test); cfg.AddEdge(new ControlFlowEdge(v, v_test)); outEdges.ForEach(e => cfg.AddEdge(new ControlFlowEdge(v_test, e.Target, e.Tag))); }); }
public static void DoRemoveReturnThunk(ControlFlowGraph cfg) { var preRets = cfg.Vedges(null, cfg.Finish); if (preRets.Count() > 1) { return; } var wannabe = preRets.AssertSingle().Source; if (wannabe.BalancedCode.IsEmpty() && wannabe.Residue.SingleOrDefault() is Ref) { var retThunk = wannabe; retThunk.BalancedCode.AssertEmpty(); var auxLocal = retThunk.Residue.AssertSingle().AssertCast <Ref>().Sym; var rets = cfg.Vedges(null, retThunk); rets.AssertEach(ret => ret.Tag == null); cfg.RemoveVertex(retThunk); rets.ForEach(ret => { var src = ret.Source; src.Residue.AssertEmpty(); var ass = src.BalancedCode.Last().AssertCast <Assign>(); var lhs = ass.Lhs.AssertCast <Ref>(); var rhs = ass.Rhs.AssertCast <Expression>(); (lhs.Sym.ProtoId == auxLocal.ProtoId).AssertTrue(); cfg.AddEdge(new ControlFlowEdge(src, cfg.Finish)); src.BalancedCode.RemoveLast(); src.Residue.Add(rhs); }); } }
public static ControlFlowGraph DoCreateCarcass(IMethodBody cil, out ReadOnlyDictionary <ControlFlowBlock, ReadOnlyCollection <IILOp> > blocks2parts) { // create the control flow graph var cfg = new ControlFlowGraph(); // partition the code into blocks with continuous control flow // todo. support switches and protected regions var targets = new HashSet <IILOp>(cil.OfType <Branch>().Select(br => br.Target)); var l_partitions = new List <ReadOnlyCollection <IILOp> >(); var l_partition = new List <IILOp>(); Action qualifyPartition = () => { if (l_partition.IsNotEmpty()) { l_partitions.Add(l_partition.ToReadOnly()); l_partition = new List <IILOp>(); } }; foreach (var op in cil) { if (op is Branch || op is Ret) { qualifyPartition(); } else { if (targets.Contains(op)) { qualifyPartition(); } l_partition.Add(op); if (op is Throw) { qualifyPartition(); } } } qualifyPartition(); var partitions = l_partitions.ToReadOnly(); // create blocks and map those to ops and partitions blocks2parts = partitions.ToDictionary(p => new ControlFlowBlock(), p => p).ToReadOnly(); blocks2parts.ForEach(kvp => cfg.AddVertex(kvp.Key)); var op2blocks = new Dictionary <IILOp, ControlFlowBlock>(); blocks2parts.ForEach(kvp => kvp.Value.ForEach(op => op2blocks.Add(op, kvp.Key))); cil.ForEach(op => { if (!op2blocks.ContainsKey(op)) { op2blocks.Add(op, null); } }); // prepare to link the blocks Action <IILOp, IILOp, CilPredicateType?> link = (op1, op2, cil_pred) => { var source = op1 == null ? cfg.Start : op2blocks[op1]; var target = op2 == null ? cfg.Finish : op2blocks[op2]; var hir_pred = cil_pred == null ? (HirPredicateType?)null : (HirPredicateType)Enum.Parse(typeof(HirPredicateType), cil_pred.Value.ToString()); cfg.AddEdge(new ControlFlowEdge(source, target, hir_pred)); }; // link the blocks (down from 300+ LOC to this simple loop =)) if (cil.IsEmpty()) { link(null, null, null); } foreach (var op in cil) { // todo. support switches here if (op is Switch) { throw AssertionHelper.Fail(); } // todo. support general case of control flow // n0te. throw needs something on stack, so br > throw is impossible Func <IILOp, bool> isJmp = op1 => op1 is Ret || op1 is Branch; if (isJmp(op) && isJmp(op.Prev)) { continue; } if (isJmp(op)) { Func <IILOp, CilPredicateType?> pred = op1 => op1 is Ret ? null : op1 is Branch ? ((Branch)op1).PredicateType : ((Func <CilPredicateType?>)(() => { throw AssertionHelper.Fail(); }))(); Func <IILOp, bool> uncond = op1 => isJmp(op1) && pred(op1) == null; Func <IILOp, bool> cond = op1 => isJmp(op1) && pred(op1) != null; Func <IILOp, IILOp> target = null; target = op1 => op1 is Ret ? null : op1 is Branch?target(((Branch)op1).Target) : op1; (target(op) is Branch).AssertFalse(); if (target(op) is Ret) { link(op.Prev, null, pred(op)); } else { link(op.Prev, target(op), pred(op)); } isJmp(op.Next).AssertImplies(uncond(op.Next)); if (cond(op)) { link(op.Prev, target(op.Next), pred(op).Negate()); } } else if (op is Throw) { // do nothing - throw doesn't create links } else { if (op.Prev == null) { link(null, op, null); } if (isJmp(op.Next)) { continue; } var blk = op2blocks.GetOrDefault(op); var blk_next = op2blocks.GetOrDefault(op.Next); if (blk != blk_next) { link(op, op.Next, null); } } } // yield control to the next step of the pipeline return(cfg); }
public static void DoDecompileComplexConditions(ControlFlowGraph cfg) { var flow = cfg.Cflow(cfg.Start); while (true) { var vs = flow.FirstOrDefault(v => v.Residue.Count() == 1 && cfg.Vedges(v, null).Count() == 2 && cfg.Vedges(v, null).All(e => e.Target.BalancedCode.IsEmpty() && e.Target.Residue.Count() == 1)); if (vs == null) { break; } var conv = cfg.ConvStrict(vs); var ass = conv.BalancedCode.AssertFirst().AssertCast <Assign>(); (ass.Lhs is Ref && ass.Rhs is Loophole).AssertTrue(); var parts = cfg.Cflow(vs, conv); var innerEdges = cfg.Edges(parts, parts).ToList(); cfg.Edges(null, parts).Except(innerEdges).AssertEach(e => e.Target == vs); cfg.Edges(parts, null).Except(innerEdges).AssertEach(e => e.Source == vs || e.Source == conv); while (true) { var somethingWasChanged = false; foreach (var pivot in parts) { var pivot_inEdges = cfg.Vedges(null, pivot); if (pivot_inEdges.Count() != 1) { continue; } var e_pred2pivot = pivot_inEdges.AssertSingle(); var pred = e_pred2pivot.Source; var pred_outEdges = cfg.Vedges(pred, null); if (pred_outEdges.Count() != 2) { continue; } var e_pred2target1 = pred_outEdges.AssertSingle(e => e.Target != pivot); var target1 = e_pred2target1.Target; var e_pivot2target1 = cfg.Vedge(pivot, target1); if (e_pivot2target1 == null) { continue; } var pivot_outEdges = cfg.Vedges(pivot, null); if (pivot_outEdges.Count() != 2) { continue; } var e_pivot2target2 = pivot_outEdges.AssertSingle(e => e.Target != target1); var target2 = e_pivot2target2.Target; var @operator = e_pred2target1.Condition == PredicateType.IsTrue ? OperatorType.OrElse : e_pred2target1.Condition == PredicateType.IsFalse ? OperatorType.AndAlso : ((Func <OperatorType>)(() => { throw AssertionHelper.Fail(); }))(); var clause_left = pred.Residue.AssertSingle(); var clause_right = pivot.Residue.AssertSingle(); var negate_rhs = e_pred2target1.Condition != e_pivot2target1.Condition; if (negate_rhs) { clause_right = Operator.Not(clause_right); } var junction = Operator.Create(@operator, clause_left, clause_right); cfg.RemoveVertex(pivot); cfg.AddEdge(new ControlFlowEdge(pred, target2, e_pred2target1.Condition.Negate())); pred.Residue.SetElements(junction); somethingWasChanged |= true; } if (!somethingWasChanged) { break; } } parts = cfg.Cflow(vs, conv); (parts.Count() == 4).AssertTrue(); var @const = parts.Except(vs, conv).AssertSingle(v => v.Residue.AssertSingle() is Const); var vnext = parts.Except(vs, conv, @const).AssertSingle(); (cfg.Vedge(@const, vnext) == null && cfg.Vedge(vnext, @const) == null).AssertTrue(); cfg.Vedge(vs, vnext).IsConditional.AssertTrue(); cfg.Vedge(vs, @const).IsConditional.AssertTrue(); cfg.Vedge(vnext, conv).IsUnconditional.AssertTrue(); cfg.Vedge(@const, conv).IsUnconditional.AssertTrue(); var estart = vs.Residue.AssertSingle(); var enext = vnext.Residue.AssertSingle(); var cond_const = @const.Residue.AssertSingle().AssertCast <Const>().Value.AssertCast <int>(); var cond_edge = cfg.Vedge(vs, @const).Condition; var val_const = cond_const == 1 ? true : cond_const == 0 ? false : ((Func <bool>)(() => { throw AssertionHelper.Fail(); }))(); var val_edge = cond_edge == PredicateType.IsTrue ? true : cond_edge == PredicateType.IsFalse ? false : ((Func <bool>)(() => { throw AssertionHelper.Fail(); }))(); var operator1 = val_const ? OperatorType.OrElse : OperatorType.AndAlso; var clause_left1 = val_edge && val_const ? estart : Operator.Not(estart); var clause_right1 = !val_edge && !val_const?Operator.Not(enext) : enext; var junction1 = Operator.Create(operator1, clause_left1, clause_right1); var conv_outEdges = cfg.Vedges(conv, null); var conv_inEdges = cfg.Vedges(null, conv).Except(cfg.Vedges(parts, conv)); cfg.RemoveVertices(@const, vnext, conv); conv_outEdges.ForEach(e => cfg.AddEdge(new ControlFlowEdge(vs, e.Target, e.Tag))); conv_inEdges.ForEach(e => cfg.AddEdge(new ControlFlowEdge(e.Source, vs, e.Tag))); vs.BalancedCode.Add(new Assign(ass.Lhs, junction1)); vs.BalancedCode.AddElements(conv.BalancedCode.Skip(1)); vs.Residue.SetElements(conv.Residue); } cfg.Edges().AssertEach(e => e.Tag.Arity() <= 1); cfg.Vertices.Where(v => v.Residue.IsNotEmpty()).AssertEach(v => v.Residue.Count() == 1); cfg.Vertices.AssertNone(v => v.Residue.IsNotEmpty() && cfg.Vedges(v, null).Any(e => e.IsUnconditional && e.Target != cfg.Finish)); }