diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e1f87cd6..82f2fabf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. - [#1488] SVG Export - EmptyStackException when clipping used - [#1584] SVG Import - paths with horizontal/vertical lines and rotation - [#1572] SVG Export - clipping must not use groups +- [#270] AS decompilation - switch in loop ## [14.2.0] - 2021-03-12 ### Added @@ -2116,6 +2117,7 @@ All notable changes to this project will be documented in this file. [#1488]: https://www.free-decompiler.com/flash/issues/1488 [#1584]: https://www.free-decompiler.com/flash/issues/1584 [#1572]: https://www.free-decompiler.com/flash/issues/1572 +[#270]: https://www.free-decompiler.com/flash/issues/270 [#1645]: https://www.free-decompiler.com/flash/issues/1645 [#1639]: https://www.free-decompiler.com/flash/issues/1639 [#1371]: https://www.free-decompiler.com/flash/issues/1371 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 222f79ae6..48552c574 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -556,6 +556,7 @@ public class Graph { } } expandGotos(ret); + //processSwitches(ret); processIfs(ret); finalProcessStack(stack, ret, path); makeAllCommands(ret, stack); @@ -563,6 +564,139 @@ public class Graph { return ret; } + protected void processSwitches(List list) { + processSwitches(list, -1); + } + + /* + + while(something){ + switch(xx){ + case 1: + trace("1"); + continue; + case 2: + trace("2"); + continue; + case 3: + break; + default: + continue; + } + item + } + + => + + while(something){ + switch(xx){ + case 1: + trace("1"); + break; + case 2: + trace("2"); + break; + case 3: + item + break; + default: + break; + } + } + + This will fix precontinue handler which detects multiple continues + */ + protected void processSwitches(List list, long lastLoopId) { + loopi: + for (int i = 0; i < list.size(); i++) { + GraphTargetItem item = list.get(i); + if (item instanceof SwitchItem) { + SwitchItem swi = (SwitchItem) item; + + Set allItems = swi.getAllSubItemsRecursively(); + int breakCount = 0; + for (GraphTargetItem it : allItems) { + if (it instanceof BreakItem) { + BreakItem br = (BreakItem) it; + if (br.loopId == swi.loop.id) { + breakCount++; + if (breakCount > 1) { + continue loopi; + } + } + } + } + if (!swi.caseCommands.isEmpty()) { + List lastCommands = swi.caseCommands.get(swi.caseCommands.size() - 1); + if (lastCommands.isEmpty() && breakCount == 1) { + continue loopi; + } + + if (breakCount == 1 && !(lastCommands.get(lastCommands.size() - 1) instanceof ContinueItem) + && !(lastCommands.get(lastCommands.size() - 1) instanceof ExitItem)) { + continue loopi; + } + } + + int breakCaseIndex = -1; + for (int c = 0; c < swi.caseCommands.size(); c++) { + List commands = swi.caseCommands.get(c); + if (!commands.isEmpty()) { + if (commands.get(commands.size() - 1) instanceof BreakItem) { + BreakItem br = (BreakItem) commands.get(commands.size() - 1); + if (br.loopId == swi.loop.id) { + breakCaseIndex = c; + break; + } + } + } + if (c == swi.caseCommands.size() - 1) { + if (commands.isEmpty()) { + breakCaseIndex = c; + break; + } + if (!(commands.get(commands.size() - 1) instanceof ContinueItem) + && !(commands.get(commands.size() - 1) instanceof ExitItem)) { + breakCaseIndex = c; + break; + } + } + } + + boolean hasContinues = false; + for (int c = 0; c < swi.caseCommands.size(); c++) { + List commands = swi.caseCommands.get(c); + if (!commands.isEmpty()) { + if (commands.get(commands.size() - 1) instanceof ContinueItem) { + ContinueItem cnt = (ContinueItem) commands.get(commands.size() - 1); + if (cnt.loopId == lastLoopId) { + hasContinues = true; + commands.set(commands.size() - 1, new BreakItem(null, null, swi.loop.id)); + if (c == swi.caseCommands.size() - 1) { + if (commands.size() == 1) { + commands.remove(0); + } + } + } + } + } + } + if (hasContinues && breakCaseIndex > -1 && i + 1 < list.size() && (list.get(list.size() - 1) instanceof ContinueItem)) { + List toAdd = new ArrayList<>(); + for (int j = i + 1; j < list.size() - 1; j++) { + toAdd.add(list.remove(i + 1)); + } + List targetCommands = swi.caseCommands.get(breakCaseIndex); + if (!targetCommands.isEmpty() && (targetCommands.get(targetCommands.size() - 1) instanceof BreakItem)) { + targetCommands.remove(targetCommands.size() - 1); + } + targetCommands.addAll(toAdd); + targetCommands.add(new BreakItem(null, null, swi.loop.id)); + } + } + } + } + protected FinalProcessLocalData getFinalData(BaseLocalData localData, List loops, List throwStates) { return new FinalProcessLocalData(loops); } @@ -844,6 +978,7 @@ public class Graph { } alreadyProcessedBlocks.add(list); } + /** * if (xxx) { y ; goto a } else { z ; goto a } * @@ -1005,6 +1140,21 @@ public class Graph { } } } + + if (i < list.size() - 1) { + if ((list.get(list.size() - 1) instanceof ExitItem) || (list.get(list.size() - 1) instanceof BreakItem)) { + if (onFalse.isEmpty() && !onTrue.isEmpty() && (onTrue.get(onTrue.size() - 1) instanceof ContinueItem)) { + ifi.expression = ifi.expression.invert(null); + List onTrueItems = new ArrayList<>(); + for (int j = i + 1; j < list.size(); j++) { + onTrueItems.add(list.remove(i + 1)); + } + list.addAll(i + 1, ifi.onTrue); + ifi.onTrue.clear(); + ifi.onTrue.addAll(onTrueItems); + } + } + } } } @@ -2263,6 +2413,8 @@ public class Graph { boolean hasContinue = false; processIfs(loopItem.commands); + processSwitches(loopItem.commands, currentLoop.id); + checkContinueAtTheEnd(loopItem.commands, currentLoop); List continues = loopItem.getContinues(); for (ContinueItem c : continues) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/SwitchItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/SwitchItem.java index 4cd440d6d..f8597817f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/SwitchItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/SwitchItem.java @@ -99,6 +99,26 @@ public class SwitchItem extends LoopItem implements Block { switchedObject.toString(writer, localData); writer.append(")").startBlock(); for (int i = 0; i < caseCommands.size(); i++) { + + //if last is default and is empty, ignore it + if (i == caseCommands.size() - 1) { + if (caseCommands.get(i).isEmpty()) { + boolean hasDefault = false; + boolean hasNonDefault = false; + for (int k = 0; k < valuesMapping.size(); k++) { + if (valuesMapping.get(k) == i) { + if (caseValues.get(k) instanceof DefaultItem) { + hasDefault = true; + } else { + hasNonDefault = true; + } + } + } + if (hasDefault && !hasNonDefault) { + continue; + } + } + } for (int k = 0; k < valuesMapping.size(); k++) { if (valuesMapping.get(k) == i) { if (!(caseValues.get(k) instanceof DefaultItem)) { 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 0220cc856..ca6cb6f98 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java @@ -644,16 +644,15 @@ public class ActionScript2Test extends ActionScript2TestBase { + "var _loc1_ = 5;\r\n" + "while(_loc1_ < 10)\r\n" + "{\r\n" - + "if(_loc1_ == 5)\r\n" + + "if(_loc1_ != 5)\r\n" + "{\r\n" + + "return false;\r\n" + + "}\r\n" + "if(_loc1_ == 6)\r\n" + "{\r\n" + "return true;\r\n" + "}\r\n" + "_loc1_ = _loc1_ + 1;\r\n" - + "continue;\r\n" - + "}\r\n" - + "return false;\r\n" + "}\r\n" + "}\r\n" + "trace(\"function4Test\");\r\n" @@ -2002,8 +2001,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/testdata/as3_new/bin/as3_new.air.swf b/libsrc/ffdec_lib/testdata/as3_new/bin/as3_new.air.swf index 14f33202d..14345fd9b 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 cc3291a17..d4460a11b 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 15bb30d96..361386385 100644 --- a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as +++ b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as @@ -43,7 +43,9 @@ package TestForEachReturn; TestForGoto; TestForIn; + TestForInIf; TestForInReturn; + TestForInSwitch; TestForXml; TestGotos; TestGotos2; @@ -56,6 +58,7 @@ package TestIf; TestIfElse; TestIfInIf; + TestIfTry; TestIgnoreAndOr; TestImportedVar; TestInc2; diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForInIf.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForInIf.as new file mode 100644 index 000000000..f9e69a4a2 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForInIf.as @@ -0,0 +1,24 @@ +package tests +{ + + public class TestForInIf + { + public function run():* + { + var arr:Array = ["a", "b", "c"]; + var b:int = 5; + + for (var a:String in arr){ + + if (b == 5){ + if (b > 7){ + trace("b>7"); + }else{ + return; + } + } + trace("forend"); + } + } + } +} \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForInSwitch.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForInSwitch.as new file mode 100644 index 000000000..bb63c957c --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForInSwitch.as @@ -0,0 +1,27 @@ +package tests +{ + + public class TestForInSwitch + { + public function run():* + { + var arr:Array = ["a", "b", "c"]; + + for (var a:String in arr){ + + switch(a){ + case "a": + trace("val a"); + break; + case "b": + trace("val b"); + break; + case "c": + trace("val c"); + //break; + } + trace("final"); + } + } + } +} \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestHello.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestHello.as index 08793c51d..14adab912 100644 --- a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestHello.as +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestHello.as @@ -5,7 +5,7 @@ package tests { public function run():* { - trace("hello"); + trace("hello"); } } } \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestIfTry.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestIfTry.as new file mode 100644 index 000000000..b4eca8966 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestIfTry.as @@ -0,0 +1,28 @@ +package tests +{ + + public class TestIfTry + { + public function run():* + { + var b:Boolean = true; + + if (b) + { + var c:int = 5; + for (var i:int = 0; i < c; i++) + { + trace("xx"); + } + } + try + { + trace("in try"); + } + catch (e:Error) + { + trace("in catch"); + } + } + } +} \ No newline at end of file