diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b7bc82ed..9dcbd2559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ All notable changes to this project will be documented in this file. classes must be fully qualified - [#2234] AS1/2 postincrement/decrement inside DefineFunction2 - AS3 PCode - pushbyte operand docs - signed byte +- [#2226] Incorrect decompilation of continue statements in some cases ### Changed - [#2185] MochiCrypt no longer offered for auto decrypt, user needs to choose variant from "Use unpacker" menu @@ -3476,6 +3477,7 @@ Major version of SWF to XML export changed to 2. [#2239]: https://www.free-decompiler.com/flash/issues/2239 [#2237]: https://www.free-decompiler.com/flash/issues/2237 [#2234]: https://www.free-decompiler.com/flash/issues/2234 +[#2226]: https://www.free-decompiler.com/flash/issues/2226 [#2206]: https://www.free-decompiler.com/flash/issues/2206 [#2228]: https://www.free-decompiler.com/flash/issues/2228 [#2100]: https://www.free-decompiler.com/flash/issues/2100 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 cac36e452..47fcb4e03 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -2278,6 +2278,7 @@ public class Graph { //List loopContinues = getLoopsContinues(loops); boolean isLoop = false; Loop currentLoop = null; + List precontinueCommands = new ArrayList<>(); boolean vCanHandleLoop = canHandleLoop(localData, part, loops, throwStates); Loop ignoredLoop = null; @@ -2426,6 +2427,9 @@ public class Graph { List currentRet = ret; UniversalLoopItem loopItem = null; TranslateStack sPreLoop = stack; + LoopItem li = null; + boolean loopTypeFound = false; + boolean doWhileCandidate = false; if (isLoop) { //makeAllCommands(currentRet, stack); stack = (TranslateStack) stack.clone(); @@ -2440,6 +2444,52 @@ public class Graph { //loopItem.commands=printGraph(visited, localData, stack, allParts, parent, part, stopPart, loops); currentRet.add(loopItem); currentRet = loopItem.commands; + li = loopItem; + + if (currentLoop.loopPreContinue != null) { + GraphPart backup = currentLoop.loopPreContinue; + currentLoop.loopPreContinue = null; + List stopPart2 = new ArrayList<>(stopPart); + stopPart2.add(currentLoop.loopContinue); + List stopPartKind2 = new ArrayList<>(stopPartKind); + stopPartKind2.add(StopPartKind.OTHER); + Set subVisited = new HashSet<>(); + precontinueCommands = printGraph(foundGotos, partCodes, partCodePos, subVisited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, stopPartKind2, loops, throwStates, null, staticOperation, path, recursionLevel + 1); + currentLoop.loopPreContinue = backup; + checkContinueAtTheEnd(precontinueCommands, currentLoop); + + + + if (!precontinueCommands.isEmpty() && precontinueCommands.get(precontinueCommands.size() - 1) instanceof IfItem) { + IfItem ifi = (IfItem) precontinueCommands.get(precontinueCommands.size() - 1); + boolean ok = false; + boolean invert = false; + if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem) && (((BreakItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) + && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { + ok = true; + invert = true; + } + if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) + && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem) && (((BreakItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { + ok = true; + } + if (ok) { + doWhileCandidate = true; + } + } + + if (!doWhileCandidate) { + for (GraphTargetItem item : precontinueCommands) { + if (item instanceof Block) { + currentLoop.loopPreContinue = null; + break; + } + } + if (currentLoop.loopPreContinue == null) { + precontinueCommands.clear(); + } + } + } //return ret; } @@ -2888,38 +2938,40 @@ public class Graph { } if (isLoop && loopItem != null && currentLoop != null) { - - LoopItem li = loopItem; - boolean loopTypeFound = false; - - boolean hasContinue = false; + processIfs(loopItem.commands); processSwitches(loopItem.commands, currentLoop.id); processOther(loopItem.commands, currentLoop.id); checkContinueAtTheEnd(loopItem.commands, currentLoop); - List continues = loopItem.getContinues(); - for (ContinueItem c : continues) { - if (c.loopId == currentLoop.id) { - hasContinue = true; - break; - } - } - if (!hasContinue) { - if (currentLoop.loopPreContinue != null) { - List stopContPart = new ArrayList<>(); - stopContPart.add(currentLoop.loopContinue); - List stopContPartKind = new ArrayList<>(); - stopContPartKind.add(StopPartKind.OTHER); - GraphPart precoBackup = currentLoop.loopPreContinue; - currentLoop.loopPreContinue = null; - printGraph(foundGotos, partCodes, partCodePos, visited, localData, stack, allParts, null, precoBackup, stopContPart, stopContPartKind, loops, throwStates, loopItem.commands, staticOperation, path, recursionLevel + 1); - checkContinueAtTheEnd(loopItem.commands, currentLoop); - } - } - - //Loop with condition at the beginning (While) + + //DoWhile based on precontinue if (!loopTypeFound && (!loopItem.commands.isEmpty())) { + List> continueCommands1 = new ArrayList<>(); + getContinuesCommands(loopItem.commands, continueCommands1, currentLoop.id); + if (!continueCommands1.isEmpty() && doWhileCandidate) { + int index = ret.indexOf(loopItem); + ret.remove(index); + IfItem ifi = (IfItem) precontinueCommands.remove(precontinueCommands.size() - 1); + List exprList = new ArrayList<>(precontinueCommands); + boolean invert = false; + if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem) && (((BreakItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) + && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { + invert = true; + } + + GraphTargetItem expr = ifi.expression; + if (invert) { + expr = expr.invert(null); + } + exprList.add(expr); + ret.add(index, li = new DoWhileItem(null, expr.getLineStartItem(), currentLoop, loopItem.commands, exprList)); + loopTypeFound = true; + } + } + + //Loop with condition at the beginning (While) + if (!loopTypeFound && (!loopItem.commands.isEmpty())) { if (loopItem.commands.get(0) instanceof IfItem) { IfItem ifi = (IfItem) loopItem.commands.get(0); @@ -2973,49 +3025,26 @@ public class Graph { List finalComm = new ArrayList<>(); //findGotoTargets - comment this out: - if (currentLoop.loopPreContinue != null) { - GraphPart backup = currentLoop.loopPreContinue; - currentLoop.loopPreContinue = null; - List stopPart2 = new ArrayList<>(stopPart); - stopPart2.add(currentLoop.loopContinue); - List stopPartKind2 = new ArrayList<>(stopPartKind); - stopPartKind2.add(StopPartKind.OTHER); - List precoCommands = printGraph(foundGotos, partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, stopPartKind2, loops, throwStates, null, staticOperation, path, recursionLevel + 1); - currentLoop.loopPreContinue = backup; - checkContinueAtTheEnd(precoCommands, currentLoop); - + if (!precontinueCommands.isEmpty()) { + List> continueCommands = new ArrayList<>(); getContinuesCommands(commands, continueCommands, currentLoop.id); if (continueCommands.isEmpty()) { - commands.addAll(precoCommands); - precoCommands = new ArrayList<>(); + commands.addAll(precontinueCommands); + precontinueCommands = 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<>(); + continueCommands.get(0).addAll(continueCommands.get(0).size() - 1, precontinueCommands); + precontinueCommands = new ArrayList<>(); } } - boolean isAllNotBlock = true; - for (GraphTargetItem ti : precoCommands) { - if (ti instanceof Block) { - isAllNotBlock = false; - break; - } - } - if (isAllNotBlock) { - finalComm.addAll(precoCommands); - } else { - commands.addAll(precoCommands); - } - } - if (currentLoop.precontinueCommands != null) { - finalComm.addAll(currentLoop.precontinueCommands); - } + finalComm.addAll(precontinueCommands); + } if (!finalComm.isEmpty()) { ret.add(index, li = new ForItem(expr.getSrc(), expr.getLineStartItem(), currentLoop, new ArrayList<>(), exprList.get(exprList.size() - 1), finalComm, commands)); } else { @@ -3029,6 +3058,10 @@ public class Graph { } } } + + if (!loopTypeFound && !precontinueCommands.isEmpty()) { + loopItem.commands.addAll(precontinueCommands); + } //Loop with condition at the end (Do..While) if (!loopTypeFound && (!loopItem.commands.isEmpty())) { @@ -3078,50 +3111,6 @@ public class Graph { } } - if (!loopTypeFound) { - if (currentLoop.loopPreContinue != null) { - loopTypeFound = true; - GraphPart backup = currentLoop.loopPreContinue; - currentLoop.loopPreContinue = null; - List stopPart2 = new ArrayList<>(stopPart); - stopPart2.add(currentLoop.loopContinue); - List stopPartKind2 = new ArrayList<>(stopPartKind); - stopPartKind2.add(StopPartKind.OTHER); - List finalComm = printGraph(foundGotos, partCodes, partCodePos, visited, localData, new TranslateStack(path), allParts, null, backup, stopPart2, stopPartKind2, loops, throwStates, null, staticOperation, path, recursionLevel + 1); - currentLoop.loopPreContinue = backup; - checkContinueAtTheEnd(finalComm, currentLoop); - - if (!finalComm.isEmpty()) { - if (finalComm.get(finalComm.size() - 1) instanceof IfItem) { - IfItem ifi = (IfItem) finalComm.get(finalComm.size() - 1); - boolean ok = false; - boolean invert = false; - if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem) && (((BreakItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) - && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { - ok = true; - invert = true; - } - if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) - && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem) && (((BreakItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { - ok = true; - } - if (ok) { - finalComm.remove(finalComm.size() - 1); - int index = ret.indexOf(loopItem); - ret.remove(index); - List exprList = new ArrayList<>(finalComm); - GraphTargetItem expr = ifi.expression; - if (invert) { - expr = expr.invert(null); - } - exprList.add(expr); - ret.add(index, li = new DoWhileItem(null, expr.getLineStartItem(), currentLoop, loopItem.commands, exprList)); - } - } - } - } - } - if (!loopTypeFound) { checkContinueAtTheEnd(loopItem.commands, currentLoop); } 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 143dae0c0..9c73c064e 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 @@ -371,6 +371,9 @@ public class GraphPrecontinueDetector { return node; } visited.add(node); + + //Note to my future self: Do not make this twoway ifs only since it may break && and || operations in expressions + /* if(a) { diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicAirDecompileTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicAirDecompileTest.java index 9a2bb9f38..fdbf0e233 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicAirDecompileTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicAirDecompileTest.java @@ -2226,6 +2226,36 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testWhileSwitch() { + decompileMethod("classic_air", "testWhileSwitch", "var a:Boolean = true;\r\n" + + "var d:int = 5;\r\n" + + "var e:Boolean = true;\r\n" + + "var i:int = 0;\r\n" + + "while(i < 100)\r\n" + + "{\r\n" + + "trace(\"start\");\r\n" + + "if(a)\r\n" + + "{\r\n" + + "trace(\"A\");\r\n" + + "}\r\n" + + "else\r\n" + + "{\r\n" + + "switch(d - 1)\r\n" + + "{\r\n" + + "case 0:\r\n" + + "trace(\"D1\");\r\n" + + "}\r\n" + + "}\r\n" + + "if(e)\r\n" + + "{\r\n" + + "trace(\"E\");\r\n" + + "}\r\n" + + "i++;\r\n" + + "}\r\n", + false); + } + @Test public void testWhileTry() { decompileMethod("classic_air", "testWhileTry", "while(true)\r\n" diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicDecompileTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicDecompileTest.java index 5edcc7707..c872c4f6e 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicDecompileTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3ClassicDecompileTest.java @@ -2219,6 +2219,36 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testWhileSwitch() { + decompileMethod("classic", "testWhileSwitch", "var a:Boolean = true;\r\n" + + "var d:int = 5;\r\n" + + "var e:Boolean = true;\r\n" + + "var i:int = 0;\r\n" + + "while(i < 100)\r\n" + + "{\r\n" + + "trace(\"start\");\r\n" + + "if(a)\r\n" + + "{\r\n" + + "trace(\"A\");\r\n" + + "}\r\n" + + "else\r\n" + + "{\r\n" + + "switch(d)\r\n" + + "{\r\n" + + "case 1:\r\n" + + "trace(\"D1\");\r\n" + + "}\r\n" + + "}\r\n" + + "if(e)\r\n" + + "{\r\n" + + "trace(\"E\");\r\n" + + "}\r\n" + + "i++;\r\n" + + "}\r\n", + false); + } + @Test public void testWhileTry() { decompileMethod("classic", "testWhileTry", "while(true)\r\n" diff --git a/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf index 6ff7b4b7e..44b78f804 100644 Binary files a/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf and b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf differ diff --git a/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.flex.swf b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.flex.swf index 6f9000c18..44c253532 100644 Binary files a/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.flex.swf and b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.flex.swf differ diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as index 3da35cc52..21a4e0d21 100644 --- a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as +++ b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as @@ -130,6 +130,7 @@ package TestWhileBreak; TestWhileBreak2; TestWhileContinue; + TestWhileSwitch; TestWhileTry; TestWhileTry2; TestXml; diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestWhileSwitch.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestWhileSwitch.as new file mode 100644 index 000000000..4b7965d26 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestWhileSwitch.as @@ -0,0 +1,35 @@ +package tests +{ + + public class TestWhileSwitch + { + public function run():* + { + var a:Boolean = true; + var d:int = 5; + var e:Boolean = true; + var i:int = 0; + while(i < 100) + { + trace("start"); + if(a) + { + trace("A"); + } + else + { + switch(d) + { + case 1: + trace("D1"); + } + } + if(e) + { + trace("E"); + } + i++; + } + } + } +}