diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java index f8b352d21..a1005f241 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -54,6 +54,7 @@ import com.jpexs.decompiler.graph.model.TernarOpItem; import com.jpexs.decompiler.graph.model.TrueItem; import com.jpexs.decompiler.graph.model.UniversalLoopItem; import com.jpexs.decompiler.graph.model.WhileItem; +import com.jpexs.decompiler.graph.precontinues.GraphPrecontinueDetector; import com.jpexs.helpers.Reference; import java.util.ArrayList; import java.util.Collection; @@ -577,11 +578,14 @@ public class Graph { //TODO: Make getPrecontinues faster getBackEdges(localData, loops, new ArrayList<>()); + + new GraphPrecontinueDetector().detectPrecontinues(heads, allParts, loops); + //getPrecontinues(path, localData, null, heads.get(0), allParts, loops, null); //getPrecontinues2(path, localData, null, heads.get(0), allParts, loops, null); List gotoTargets = new ArrayList<>(); - findGotoTargets(localData, path, heads.get(0), allParts, loops, gotoTargets); + //findGotoTargets(localData, path, heads.get(0), allParts, loops, gotoTargets); /*System.err.println(""); for (Loop el : loops) { @@ -2769,7 +2773,7 @@ public class Graph { List finalComm = new ArrayList<>(); //findGotoTargets - comment this out: - /*if (currentLoop.loopPreContinue != null) { + if (currentLoop.loopPreContinue != null) { GraphPart backup = currentLoop.loopPreContinue; currentLoop.loopPreContinue = null; List stopPart2 = new ArrayList<>(stopPart); @@ -2777,7 +2781,7 @@ public class Graph { finalComm = printGraph(foundGotos, gotoTargets, partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, loops, null, staticOperation, path, recursionLevel + 1); currentLoop.loopPreContinue = backup; checkContinueAtTheEnd(finalComm, currentLoop); - }*/ + } if (currentLoop.precontinueCommands != null) { finalComm.addAll(currentLoop.precontinueCommands); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java new file mode 100644 index 000000000..fc98f0465 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2010-2018 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.graph.precontinues; + +import com.jpexs.decompiler.graph.GraphPart; +import com.jpexs.decompiler.graph.Loop; +import com.jpexs.helpers.Reference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + * @author JPEXS + */ +public class GraphPrecontinueDetector { + public void detectPrecontinues(List heads, Set allParts, List loops) { + boolean isSomethingTodo = false; + for (Loop el : loops) { + if (el.backEdges.size() == 1) { + isSomethingTodo = true; + } + } + if (!isSomethingTodo) { + return; + } + + Map partToNode = new HashMap<>(); + for (GraphPart part : allParts) { + Node node = new Node(); + node.graphPart = part; + partToNode.put(part, node); + } + for (GraphPart part : allParts) { + Node node = partToNode.get(part); + for (GraphPart prev : part.refs) { + if (prev.start < 0) { + continue; + } + Node prevNode = partToNode.get(prev); + node.prev.add(prevNode); + } + for (GraphPart next : part.nextParts) { + Node nextNode = partToNode.get(next); + node.next.add(nextNode); + } + } + List headNodes = new ArrayList<>(); + for (GraphPart head : heads) { + headNodes.add(partToNode.get(head)); + } + + + boolean changed = true; + while (changed) { + changed = false; + if (joinNodes(headNodes)) { + changed = true; + } + if (checkIfs(headNodes)) { + changed = true; + } + if (handleWhile(headNodes)) { + changed = true; + } + } + + for (Loop el : loops) { + if (el.backEdges.size() == 1) { + //System.err.println("loop " + el.loopContinue); + GraphPart backEdgePart = el.backEdges.iterator().next(); + Node node = partToNode.get(backEdgePart); + //System.err.println("backedge:" + backEdgePart); + boolean wholeLoop = false; + while (node.parentNode != null) { + node = node.parentNode; + //System.err.println("- parent " + node.graphPart); + if (node.graphPart.equals(el.loopContinue)) { + wholeLoop = true; + break; + } + } + if (!wholeLoop) { + el.loopPreContinue = node.graphPart; + } + } + } + //printGraph(headNodes); + } + + private void printGraph(List headNodes) { + Set allNodes = new LinkedHashSet<>(); + for (Node headNode : headNodes) { + populateNodes(headNode, allNodes); + } + + StringBuilder sb = new StringBuilder(); + sb.append("digraph mygraph {\r\n"); + for (Node node : allNodes) { + sb.append("node" + node.getId() + "[label=\"" + node.toString() + "\"];\r\n"); + for (Node n : node.next) { + sb.append("node" + node.getId() + "->node" + n.getId() + ";\r\n"); + } + } + sb.append("}\r\n"); + System.err.println(sb.toString()); + } + + private void populateNodes(Node node, Set populated) { + if (populated.contains(node)) { + return; + } + populated.add(node); + for (Node n : node.next) { + populateNodes(n, populated); + } + } + + private boolean checkIfs(List headNodes) { + Set visited = new HashSet<>(); + Reference numChanged = new Reference<>(0); + for (int h = 0; h < headNodes.size(); h++) { + Node newHeadNode; + newHeadNode = checkIfs(headNodes.get(h), visited, numChanged); + headNodes.set(h, newHeadNode); + } + return numChanged.getVal() > 0; + } + + private boolean joinNodes(List headNodes) { + Set visited = new HashSet<>(); + Reference numChanged = new Reference<>(0); + for (int h = 0; h < headNodes.size(); h++) { + Node newHeadNode; + newHeadNode = joinNodes(headNodes.get(h), visited, numChanged); + headNodes.set(h, newHeadNode); + } + return numChanged.getVal() > 0; + } + + private boolean handleWhile(List headNodes) { + Set visited = new HashSet<>(); + Reference numChanged = new Reference<>(0); + for (int h = 0; h < headNodes.size(); h++) { + Node newHeadNode; + newHeadNode = handleWhile(headNodes.get(h), visited, numChanged); + headNodes.set(h, newHeadNode); + } + return numChanged.getVal() > 0; + } + + private Node handleWhile(Node node, Set visited, Reference numWhile) { + if (visited.contains(node)) { + return node; + } + visited.add(node); + + Node result = node; + + Node bodyNode = null; + Node breakNode = null; + if (node.next.size() == 2 + && node.next.get(0).next.size() == 1 + && node.next.get(0).next.get(0) == node) { + breakNode = node.next.get(1); + bodyNode = node.next.get(0); + } else if (node.next.size() == 2 + && node.next.get(1).next.size() == 1 + && node.next.get(1).next.get(0) == node) { + breakNode = node.next.get(0); + bodyNode = node.next.get(1); + } + + if (bodyNode != null) { + + WhileNode whileNode = new WhileNode(); + bodyNode.parentNode = whileNode; + whileNode.graphPart = node.graphPart; + whileNode.body = bodyNode; + whileNode.body.removeFromGraph(); + whileNode.prev = new NonNullList<>(node.prev); + node.replacePrevs(whileNode); + node.replaceNexts(whileNode); + whileNode.next.add(breakNode); + result = whileNode; + numWhile.setVal(numWhile.getVal() + 1); + } + + for (Node n : result.next) { + handleWhile(n, visited, numWhile); + } + return result; + } + + private Node joinNodes(Node node, Set visited, Reference numJoined) { + if (visited.contains(node)) { + return node; + } + visited.add(node); + Node currentNode = node; + List nodeList = new ArrayList<>(); + nodeList.add(currentNode); + while (currentNode.next.size() == 1 + && currentNode.next.get(0).prev.size() == 1 + && !visited.contains(currentNode.next.get(0))) { + currentNode = currentNode.next.get(0); + visited.add(currentNode); + nodeList.add(currentNode); + } + + Node result = node; + if (nodeList.size() > 1) { + JoinedNode joinedNode = new JoinedNode(); + joinedNode.graphPart = node.graphPart; + joinedNode.nodes = nodeList; + joinedNode.next = new NonNullList<>(currentNode.next); + joinedNode.prev = new NonNullList<>(node.prev); + node.replacePrevs(joinedNode); + currentNode.replaceNexts(joinedNode); + for (Node n : nodeList) { + n.parentNode = joinedNode; + n.removeFromGraph(); + } + result = joinedNode; + numJoined.setVal(numJoined.getVal() + 1); + } + + for (Node n : result.next) { + joinNodes(n, visited, numJoined); + } + + return result; + } + + private Node checkIfs(Node node, Set visited, Reference numIfs) { + if (visited.contains(node)) { + return node; + } + visited.add(node); + /* + if(a) + { + onTrue + } + */ + if (node.next.size() == 2 + && node.next.get(0).next.size() == 1 + && node.next.get(0).next.get(0) == node.next.get(1)) { + IfNode ifNode = new IfNode(); + ifNode.onTrue = node.next.get(0); + ifNode.onTrue.parentNode = ifNode; + ifNode.onTrue.removeFromGraph(); + ifNode.onFalse = null; + ifNode.graphPart = node.graphPart; + ifNode.prev = new NonNullList<>(node.prev); + node.replacePrevs(ifNode); + Node after = node.next.get(1); + node.removeFromGraph(); + ifNode.next.add(after); + after.prev.add(ifNode); + numIfs.setVal(numIfs.getVal() + 1); + checkIfs(after, visited, numIfs); + return ifNode; + } + + /* + if(a) + { + + } + else + { + onFalse + } + */ + if (node.next.size() == 2 + && node.next.get(1).next.size() == 1 + && node.next.get(1).next.get(0) == node.next.get(0)) { + IfNode ifNode = new IfNode(); + ifNode.onTrue = null; + ifNode.onFalse = node.next.get(1); + ifNode.onFalse.parentNode = ifNode; + ifNode.onFalse.removeFromGraph(); + ifNode.graphPart = node.graphPart; + ifNode.prev = new NonNullList<>(node.prev); + node.replacePrevs(ifNode); + Node after = node.next.get(0); + node.removeFromGraph(); + ifNode.next.add(after); + after.prev.add(ifNode); + numIfs.setVal(numIfs.getVal() + 1); + checkIfs(after, visited, numIfs); + return ifNode; + } + /* + if(a) + { + onTrue + } + else + { + onFalse + } + */ + if (node.next.size() == 2 + && node.next.get(0).next.size() == 1 + && node.next.get(1).next.size() == 1 + && node.next.get(0).next.get(0) == node.next.get(1).next.get(0)) { + IfNode ifNode = new IfNode(); + ifNode.onTrue = node.next.get(0); + ifNode.onTrue.parentNode = ifNode; + ifNode.onFalse = node.next.get(1); + ifNode.onFalse.parentNode = ifNode; + ifNode.onFalse.removeFromGraph(); + ifNode.graphPart = node.graphPart; + ifNode.prev = new NonNullList<>(node.prev); + node.replacePrevs(ifNode); + Node after = node.next.get(0).next.get(0); + node.removeFromGraph(); + ifNode.next.add(after); + after.prev.add(ifNode); + numIfs.setVal(numIfs.getVal() + 1); + checkIfs(after, visited, numIfs); + return ifNode; + } + + for (Node n : node.next) { + checkIfs(n, visited, numIfs); + } + return node; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/IfNode.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/IfNode.java new file mode 100644 index 000000000..0b054bd21 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/IfNode.java @@ -0,0 +1,16 @@ +package com.jpexs.decompiler.graph.precontinues; + +/** + * + * @author JPEXS + */ +public class IfNode extends Node { + public Node onTrue; + public Node onFalse; + + @Override + public String toString() { + return "if" + super.toString(); + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/JoinedNode.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/JoinedNode.java new file mode 100644 index 000000000..ec838f13f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/JoinedNode.java @@ -0,0 +1,18 @@ +package com.jpexs.decompiler.graph.precontinues; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class JoinedNode extends Node { + public List nodes = new ArrayList<>(); + + @Override + public String toString() { + return "join" + super.toString(); + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/Node.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/Node.java new file mode 100644 index 000000000..c071bd412 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/Node.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2010-2018 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.graph.precontinues; + +import com.jpexs.decompiler.graph.GraphPart; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class Node { + public List next = new NonNullList<>(); + public List prev = new NonNullList(); + public GraphPart graphPart; + private static int CURRENT_ID = 0; + private int id; + + public Node parentNode; + + public Node() { + this.id = ++CURRENT_ID; + } + + public int getId() { + return id; + } + + @Override + public String toString() { + return "node" + id + ":" + (graphPart == null ? "null" : graphPart.toString()); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + this.id; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Node other = (Node) obj; + if (this.id != other.id) { + return false; + } + return true; + } + + + public void replacePrevs(Node newNode) { + for (Node p : this.prev) { + for (int i = 0; i < p.next.size(); i++) { + if (p.next.get(i) == this) { + p.next.set(i, newNode); + } + } + } + } + + public void replaceNexts(Node newNode) { + for (Node n : this.next) { + for (int i = 0; i < n.prev.size(); i++) { + if (n.prev.get(i) == this) { + n.prev.set(i, newNode); + } + } + } + } + + public void removeFromGraph() { + for (Node p : this.prev) { + for (int i = p.next.size() - 1; i >= 0; i--) { + if (p.next.get(i) == this) { + p.next.remove(i); + } + } + } + for (Node n : this.next) { + for (int i = n.prev.size() - 1; i >= 0; i--) { + if (n.prev.get(i) == this) { + n.prev.remove(i); + } + } + } + next.clear(); + prev.clear(); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/NonNullList.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/NonNullList.java new file mode 100644 index 000000000..08eaae890 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/NonNullList.java @@ -0,0 +1,39 @@ +package com.jpexs.decompiler.graph.precontinues; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * + * @author JPEXS + */ +class NonNullList extends ArrayList { + + public NonNullList(Collection col) { + super(col); + } + + public NonNullList() { + } + + + @Override + public boolean add(T item) { + if (item == null) { + throw new NullPointerException("The collection does not support null values"); + } else { + return super.add(item); + } + } + + @Override + public boolean addAll(Collection items) { + if (items.contains(null)) { + throw new NullPointerException("The collection does not support null values"); + } else { + return super.addAll(items); + } + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/WhileNode.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/WhileNode.java new file mode 100644 index 000000000..ac61b347d --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/WhileNode.java @@ -0,0 +1,16 @@ +package com.jpexs.decompiler.graph.precontinues; + +/** + * + * @author JPEXS + */ +public class WhileNode extends Node { + public Node body; + + @Override + public String toString() { + return "while" + super.toString(); + } + + +}