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 a1005f241..ec78e5ab7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -391,69 +391,6 @@ public class Graph { } } } - List> reachable = new ArrayList<>(); - for (GraphPart p : parts) { - LinkedHashSet r1 = new LinkedHashSet<>(); - getReachableParts(localData, p, r1, loops, gotoParts); - Set r2 = new LinkedHashSet<>(); - r2.add(p); - r2.addAll(r1); - reachable.add(r2); - } - ///List first = reachable.get(0); - int commonLevel; - Map levelMap = new HashMap<>(); - for (Set first : reachable) { - int maxclevel = 0; - Set visited = new HashSet<>(); - for (GraphPart p : first) { - if (loopContinues.contains(p)) { - break; - } - if (visited.contains(p)) { - continue; - } - visited.add(p); - boolean common = true; - commonLevel = 1; - for (Set r : reachable) { - if (r == first) { - continue; - } - if (r.contains(p)) { - commonLevel++; - } - } - if (commonLevel <= maxclevel) { - continue; - } - maxclevel = commonLevel; - if (levelMap.containsKey(p)) { - if (levelMap.get(p) > commonLevel) { - commonLevel = levelMap.get(p); - } - } - levelMap.put(p, commonLevel); - if (common) { - //return p; - } - } - } - for (int i = reachable.size() - 1; i >= 2; i--) { - for (GraphPart p : levelMap.keySet()) { - if (levelMap.get(p) == i) { - return p; - } - } - } - for (GraphPart p : levelMap.keySet()) { - if (levelMap.get(p) == parts.size()) { - return p; - } - } - return null; - /* - s List> reachable = new ArrayList<>(); Set allReachable = new LinkedHashSet<>(); for (GraphPart p : parts) { @@ -494,7 +431,6 @@ public class Graph { //System.err.println("maxclevelpart = " + maxCommonLevelPart); return maxCommonLevelPart; - */ } public GraphPart getNextNoJump(GraphPart part, BaseLocalData localData) { @@ -1449,6 +1385,19 @@ public class Graph { } } + if (i < list.size() - 1) { + if (list.get(i + 1) instanceof ContinueItem) { + if ((!onTrue.isEmpty()) && (onFalse.isEmpty())) { + if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) { + if (((ContinueItem) onTrue.get(onTrue.size() - 1)).loopId == ((ContinueItem) list.get(i + 1)).loopId) { + onTrue.remove(onTrue.size() - 1); + } + } + } + } + + } + if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) { GraphTargetItem last = onTrue.get(onTrue.size() - 1); if ((last instanceof ExitItem) || (last instanceof ContinueItem) || (last instanceof BreakItem)) { @@ -2163,6 +2112,23 @@ public class Graph { } } + private void getContinuesCommands(List commands, List> result, long loopId) { + for (GraphTargetItem ti : commands) { + if (ti instanceof ContinueItem) { + ContinueItem ci = (ContinueItem) ti; + if (ci.loopId == loopId) { + result.add(commands); + } + } + if (ti instanceof Block) { + Block bl = (Block) ti; + for (List subCommands : bl.getSubs()) { + getContinuesCommands(subCommands, result, loopId); + } + } + } + } + protected List printGraph(List foundGotos, List gotoTargets, Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List loops, List ret, int staticOperation, String path, int recursionLevel) throws InterruptedException { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); @@ -2715,6 +2681,7 @@ public class Graph { GraphPart precoBackup = currentLoop.loopPreContinue; currentLoop.loopPreContinue = null; loopItem.commands.addAll(printGraph(foundGotos, gotoTargets, partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, precoBackup, stopContPart, loops, null, staticOperation, path, recursionLevel + 1)); + checkContinueAtTheEnd(loopItem.commands, currentLoop); } } @@ -2778,9 +2745,26 @@ public class Graph { currentLoop.loopPreContinue = null; List stopPart2 = new ArrayList<>(stopPart); stopPart2.add(currentLoop.loopContinue); - finalComm = printGraph(foundGotos, gotoTargets, partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, loops, null, staticOperation, path, recursionLevel + 1); + List precoCommands = 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); + checkContinueAtTheEnd(precoCommands, currentLoop); + + List> continueCommands = new ArrayList<>(); + getContinuesCommands(commands, continueCommands, currentLoop.id); + + if (continueCommands.isEmpty()) { + commands.addAll(precoCommands); + precoCommands = new ArrayList<>(); + } //Single continue and there is break/continue/return/throw at end of the commands + else if (!commands.isEmpty() && continueCommands.size() == 1) { + GraphTargetItem lastItem = commands.get(commands.size() - 1); + if ((lastItem instanceof BreakItem) || (lastItem instanceof ContinueItem) || (lastItem instanceof ExitItem)) { + continueCommands.get(0).addAll(continueCommands.get(0).size() - 1, precoCommands); + precoCommands = new ArrayList<>(); + } + } + + finalComm.addAll(precoCommands); } 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 index fc98f0465..a8746ee1f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java @@ -28,10 +28,18 @@ import java.util.Map; import java.util.Set; /** + * + * Detects "precontinues" in Graph. A precontinue is target of continue + * statement in a for loop. For loop in this case has single backedge. + * Precontinue is predeccessor of loops backedge. Precontinue can have branches + * in it (and in some special cases like xml .() operator a while too). This + * class tries to simplify graph up to the level that precontinue is a single + * node. * * @author JPEXS */ public class GraphPrecontinueDetector { + public void detectPrecontinues(List heads, Set allParts, List loops) { boolean isSomethingTodo = false; for (Loop el : loops) { @@ -106,7 +114,12 @@ public class GraphPrecontinueDetector { //printGraph(headNodes); } - private void printGraph(List headNodes) { + /** + * Converts node graph to graphviz for easily display. + * + * @param headNodes + */ + public void printGraph(List headNodes) { Set allNodes = new LinkedHashSet<>(); for (Node headNode : headNodes) { populateNodes(headNode, allNodes); @@ -196,7 +209,7 @@ public class GraphPrecontinueDetector { whileNode.graphPart = node.graphPart; whileNode.body = bodyNode; whileNode.body.removeFromGraph(); - whileNode.prev = new NonNullList<>(node.prev); + whileNode.prev = new ArrayList<>(node.prev); node.replacePrevs(whileNode); node.replaceNexts(whileNode); whileNode.next.add(breakNode); @@ -204,7 +217,8 @@ public class GraphPrecontinueDetector { numWhile.setVal(numWhile.getVal() + 1); } - for (Node n : result.next) { + List nexts = new ArrayList<>(result.next); + for (Node n : nexts) { handleWhile(n, visited, numWhile); } return result; @@ -231,8 +245,8 @@ public class GraphPrecontinueDetector { JoinedNode joinedNode = new JoinedNode(); joinedNode.graphPart = node.graphPart; joinedNode.nodes = nodeList; - joinedNode.next = new NonNullList<>(currentNode.next); - joinedNode.prev = new NonNullList<>(node.prev); + joinedNode.next = new ArrayList<>(currentNode.next); + joinedNode.prev = new ArrayList<>(node.prev); node.replacePrevs(joinedNode); currentNode.replaceNexts(joinedNode); for (Node n : nodeList) { @@ -243,7 +257,8 @@ public class GraphPrecontinueDetector { numJoined.setVal(numJoined.getVal() + 1); } - for (Node n : result.next) { + List nexts = new ArrayList<>(result.next); + for (Node n : nexts) { joinNodes(n, visited, numJoined); } @@ -270,7 +285,7 @@ public class GraphPrecontinueDetector { ifNode.onTrue.removeFromGraph(); ifNode.onFalse = null; ifNode.graphPart = node.graphPart; - ifNode.prev = new NonNullList<>(node.prev); + ifNode.prev = new ArrayList<>(node.prev); node.replacePrevs(ifNode); Node after = node.next.get(1); node.removeFromGraph(); @@ -300,7 +315,7 @@ public class GraphPrecontinueDetector { ifNode.onFalse.parentNode = ifNode; ifNode.onFalse.removeFromGraph(); ifNode.graphPart = node.graphPart; - ifNode.prev = new NonNullList<>(node.prev); + ifNode.prev = new ArrayList<>(node.prev); node.replacePrevs(ifNode); Node after = node.next.get(0); node.removeFromGraph(); @@ -331,7 +346,7 @@ public class GraphPrecontinueDetector { ifNode.onFalse.parentNode = ifNode; ifNode.onFalse.removeFromGraph(); ifNode.graphPart = node.graphPart; - ifNode.prev = new NonNullList<>(node.prev); + ifNode.prev = new ArrayList<>(node.prev); node.replacePrevs(ifNode); Node after = node.next.get(0).next.get(0); node.removeFromGraph(); 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 index 0b054bd21..49dbe443a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/IfNode.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/IfNode.java @@ -1,3 +1,19 @@ +/* + * 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; /** 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 index ec838f13f..0f97288b0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/JoinedNode.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/JoinedNode.java @@ -1,3 +1,19 @@ +/* + * 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 java.util.ArrayList; 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 index c071bd412..842050af4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/Node.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/Node.java @@ -26,8 +26,8 @@ import java.util.List; * @author JPEXS */ public class Node { - public List next = new NonNullList<>(); - public List prev = new NonNullList(); + public List next = new ArrayList<>(); + public List prev = new ArrayList(); public GraphPart graphPart; private static int CURRENT_ID = 0; private int id; 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 deleted file mode 100644 index 08eaae890..000000000 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/NonNullList.java +++ /dev/null @@ -1,39 +0,0 @@ -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/test/com/jpexs/decompiler/flash/ActionScript2Test.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java index 736861e60..1878f602c 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java @@ -2008,8 +2008,6 @@ public class ActionScript2Test extends ActionScript2TestBase { + "case \"C\":\r\n" + "trace(\"Ret 5\");\r\n" + "return 5;\r\n" - + "default:\r\n" - + "continue;\r\n" + "}\r\n" + "}\r\n" + "trace(\"Final\");\r\n" diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3Test.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3Test.java index 7d97d3e69..ee2d50c3c 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3Test.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3Test.java @@ -251,10 +251,8 @@ public class ActionScript3Test extends ActionScriptTestBase { + "for(d = 0; d < 25; d++)\r\n" + "{\r\n" + "e = 0;\r\n" - + "if(e >= 50)\r\n" + + "if(e < 50)\r\n" + "{\r\n" - + "continue;\r\n" - + "}\r\n" + "if(e == 9)\r\n" + "{\r\n" + "break;\r\n" @@ -269,6 +267,7 @@ public class ActionScript3Test extends ActionScriptTestBase { + "}\r\n" + "break loop1;\r\n" + "}\r\n" + + "}\r\n" + "trace(\"hello\");\r\n" + "}\r\n", false); @@ -1658,6 +1657,27 @@ public class ActionScript3Test extends ActionScriptTestBase { false); } + @Test + public void testAssembledSwitch() { + decompileMethod("assembled", "testSwitch", "switch(int(somevar))\r\n" + + "{\r\n" + + "case 0:\r\n" + + "var _loc2_:String = \"X\";\r\n" + + "return;\r\n" + + "break;\r\n" + + "case 1:\r\n" + + "_loc2_ = \"A\";\r\n" + + "break;\r\n" + + "case 3:\r\n" + + "_loc2_ = \"B\";\r\n" + + "break;\r\n" + + "case 4:\r\n" + + "_loc2_ = \"C\";\r\n" + + "}\r\n" + + "_loc2_ = \"after\";\r\n", + false); + } + @Test public void testAssembledSwitchDefault() { decompileMethod("assembled", "testSwitchDefault", "switch(5)\r\n" diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/generators/AS2Generator.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/generators/AS2Generator.java index d61d4ac13..a99f4504b 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/generators/AS2Generator.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/generators/AS2Generator.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.generators; import com.jpexs.decompiler.flash.SWF; @@ -90,12 +91,10 @@ public class AS2Generator { s.append("}"); doa = null; } - /*try (PrintWriter pw = new PrintWriter("as2_teststub.java")) { - pw.println(s.toString()); - }*/ - try (FileOutputStream fos = new FileOutputStream("as2_teststub.java")) { - fos.write(Utf8Helper.getBytes(s.toString())); - } } + try (FileOutputStream fos = new FileOutputStream("as2_teststub.java")) { + fos.write(Utf8Helper.getBytes(s.toString())); + } + System.exit(0); } } diff --git a/libsrc/ffdec_lib/testdata/custom/abc/custom-0/custom-0.main.abc b/libsrc/ffdec_lib/testdata/custom/abc/custom-0/custom-0.main.abc index 1515ac50b..e1d46d642 100644 Binary files a/libsrc/ffdec_lib/testdata/custom/abc/custom-0/custom-0.main.abc and b/libsrc/ffdec_lib/testdata/custom/abc/custom-0/custom-0.main.abc differ diff --git a/libsrc/ffdec_lib/testdata/custom/abc/custom-0/custom-0.main.asasm b/libsrc/ffdec_lib/testdata/custom/abc/custom-0/custom-0.main.asasm index 803f110ed..ce430830f 100644 --- a/libsrc/ffdec_lib/testdata/custom/abc/custom-0/custom-0.main.asasm +++ b/libsrc/ffdec_lib/testdata/custom/abc/custom-0/custom-0.main.asasm @@ -15,5 +15,6 @@ program #include "tests/TestIncrement3.script.asasm" #include "tests/TestDup.script.asasm" #include "tests/TestSwitchDefault.script.asasm" + #include "tests/TestSwitch.script.asasm" ; place to add next end ; program diff --git a/libsrc/ffdec_lib/testdata/custom/abc/custom-0/tests/TestSwitch.class.asasm b/libsrc/ffdec_lib/testdata/custom/abc/custom-0/tests/TestSwitch.class.asasm new file mode 100644 index 000000000..f44c254c7 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/custom/abc/custom-0/tests/TestSwitch.class.asasm @@ -0,0 +1,83 @@ +class + refid "tests:TestSwitch" + instance QName(PackageNamespace("tests"), "TestSwitch") + extends QName(PackageNamespace(""), "Object") + flag SEALED + flag PROTECTEDNS + protectedns ProtectedNamespace("tests:TestSwitch") + iinit + refid "tests:TestSwitch/instance/init" + body + maxstack 1 + localcount 1 + initscopedepth 4 + maxscopedepth 5 + code + getlocal0 + pushscope + + getlocal0 + constructsuper 0 + + returnvoid + end ; code + end ; body + end ; method + trait method QName(PackageNamespace(""), "run") + method + refid "tests:TestSwitch/instance/run" + returns QName(PackageNamespace(""), "void") + body + maxstack 2 + localcount 4 + initscopedepth 4 + maxscopedepth 5 + code + getlocal0 + pushscope + getlex QName(PrivateNamespace("somens"),"somevar") + convert_i + lookupswitch ofs005e, [ofs002d, ofs0038, ofs005e, ofs0050, ofs0059] +ofs002d: + pushstring "X" + setlocal2 + returnvoid +ofs0038: + pushstring "A" + coerce_s + setlocal2 + jump ofs005e +ofs0050: + pushstring "B" + coerce_s + setlocal2 + jump ofs005e +ofs0059: + pushstring "C" + coerce_s + setlocal2 +ofs005e: + pushstring "after" + setlocal2 + returnvoid + end ; code + end ; body + end ; method + end ; trait + end ; instance + cinit + refid "tests:TestSwitch/class/init" + body + maxstack 1 + localcount 1 + initscopedepth 3 + maxscopedepth 4 + code + getlocal0 + pushscope + + returnvoid + end ; code + end ; body + end ; method +end ; class diff --git a/libsrc/ffdec_lib/testdata/custom/abc/custom-0/tests/TestSwitch.script.asasm b/libsrc/ffdec_lib/testdata/custom/abc/custom-0/tests/TestSwitch.script.asasm new file mode 100644 index 000000000..0d6eca7cf --- /dev/null +++ b/libsrc/ffdec_lib/testdata/custom/abc/custom-0/tests/TestSwitch.script.asasm @@ -0,0 +1,29 @@ +script + sinit + refid "tests:TestSwitch/init" + body + maxstack 2 + localcount 1 + initscopedepth 1 + maxscopedepth 3 + code + getlocal0 + pushscope + + findpropstrict Multiname("TestSwitch", [PackageNamespace("tests")]) + getlex QName(PackageNamespace(""), "Object") + pushscope + + getlex Multiname("Object", [PrivateNamespace(null, "tests:TestSwitch"), PackageNamespace(""), PackageNamespace("tests"), PackageInternalNs("tests"), Namespace("http://adobe.com/AS3/2006/builtin")]) + newclass "tests:TestSwitch" + popscope + initproperty QName(PackageNamespace("tests"), "TestSwitch") + + returnvoid + end ; code + end ; body + end ; method + trait class QName(PackageNamespace("tests"), "TestSwitch") + #include "TestSwitch.class.asasm" + end ; trait +end ; script diff --git a/libsrc/ffdec_lib/testdata/custom/bin/custom.swf b/libsrc/ffdec_lib/testdata/custom/bin/custom.swf index bcc1612f4..70b9c3471 100644 Binary files a/libsrc/ffdec_lib/testdata/custom/bin/custom.swf and b/libsrc/ffdec_lib/testdata/custom/bin/custom.swf differ