diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb743fcf..f34345982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. - [#1572] SVG Export - clipping must not use groups - [#270] AS decompilation - switch in loop - [#270] AS decompilation - loop followed by try +- [#270] AS decompilation - comma in ternar ## [14.2.0] - 2021-03-12 ### Added 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 ccb048cd0..1901a6df9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -1825,6 +1825,15 @@ public class Graph { return true; } + protected boolean canBeCommaised(List list) { + for (GraphTargetItem item : list) { + if (item instanceof Block) { + return false; + } + } + return true; + } + protected List printGraph(List foundGotos, Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List stopPartKind, List loops, List throwStates, List ret, int staticOperation, String path, int recursionLevel) throws InterruptedException { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); @@ -2133,6 +2142,14 @@ public class Graph { caseCommaCommands.put(cpos, commaCommands); commaCommands = new ArrayList<>(); it = to.onFalse; + if (it instanceof CommaExpressionItem) { + commaCommands = new ArrayList<>(); + CommaExpressionItem ce = (CommaExpressionItem) it; + for (int f = 0; f < ce.commands.size() - 1; f++) { + commaCommands.add(ce.commands.get(f)); + } + it = ce.commands.get(ce.commands.size() - 1); + } } else { break; } @@ -2307,8 +2324,25 @@ public class Graph { List filteredOnTrue = onTrue; List filteredOnFalse = onFalse; - if (!isEmpty(filteredOnTrue) && !isEmpty(filteredOnFalse) && filteredOnTrue.size() == 1 && filteredOnFalse.size() == 1 && (filteredOnTrue.get(0) instanceof PushItem) && (filteredOnFalse.get(0) instanceof PushItem)) { - stack.push(new TernarOpItem(null, localData.lineStartInstruction, expr.invert(null), ((PushItem) filteredOnTrue.get(0)).value, ((PushItem) filteredOnFalse.get(0)).value)); + if (!isEmpty(filteredOnTrue) && !isEmpty(filteredOnFalse) + && (filteredOnTrue.get(filteredOnTrue.size() - 1) instanceof PushItem) + && (filteredOnFalse.get(filteredOnFalse.size() - 1) instanceof PushItem) + && canBeCommaised(filteredOnTrue) && canBeCommaised(filteredOnFalse)) { + GraphTargetItem ternarOnTrue; + if (filteredOnTrue.size() > 1) { + filteredOnTrue.set(filteredOnTrue.size() - 1, filteredOnTrue.get(filteredOnTrue.size() - 1).value); // replace Pushitem with its value + ternarOnTrue = new CommaExpressionItem(null, null, filteredOnTrue); + } else { + ternarOnTrue = filteredOnTrue.get(0).value; + } + GraphTargetItem ternarOnFalse; + if (filteredOnFalse.size() > 1) { + filteredOnFalse.set(filteredOnFalse.size() - 1, filteredOnFalse.get(filteredOnFalse.size() - 1).value); // replace Pushitem with its value + ternarOnFalse = new CommaExpressionItem(null, null, filteredOnFalse); + } else { + ternarOnFalse = filteredOnFalse.get(0).value; + } + stack.push(new TernarOpItem(null, localData.lineStartInstruction, expr.invert(null), ternarOnTrue, ternarOnFalse)); } else { boolean isIf = true; //If the ontrue is empty, switch ontrue and onfalse diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/CommaExpressionItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/CommaExpressionItem.java index 43c9d9fc4..49ad02e40 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/CommaExpressionItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/CommaExpressionItem.java @@ -35,7 +35,7 @@ public class CommaExpressionItem extends GraphTargetItem { public List commands; public CommaExpressionItem(GraphSourceItem src, GraphSourceItem lineStartIns, List commands) { - super(src, lineStartIns, PRECEDENCE_PRIMARY); + super(src, lineStartIns, PRECEDENCE_COMMA); this.commands = commands; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/TernarOpItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/TernarOpItem.java index 3d7dc3eb9..7611d06db 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/TernarOpItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/TernarOpItem.java @@ -53,31 +53,31 @@ public class TernarOpItem extends GraphTargetItem { @Override public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException { - if (expression.getPrecedence() >= precedence){ + if (expression.getPrecedence() >= precedence) { writer.append("("); } expression.toString(writer, localData); - if (expression.getPrecedence() >= precedence){ + if (expression.getPrecedence() >= precedence) { writer.append(")"); } writer.append(" ? "); - - if (onTrue instanceof TernarOpItem){ //ternar in ternar better in parenthesis + + if (onTrue.getPrecedence() >= precedence && onTrue.getPrecedence() != GraphTargetItem.NOPRECEDENCE) { // >= ternar in ternar better in parenthesis writer.append("("); } onTrue.toString(writer, localData); - if (onTrue instanceof TernarOpItem){ + if (onTrue.getPrecedence() >= precedence && onTrue.getPrecedence() != GraphTargetItem.NOPRECEDENCE) { writer.append(")"); - } + } writer.append(" : "); - if (onFalse instanceof TernarOpItem){ - writer.append("("); + if (onFalse.getPrecedence() >= precedence && onFalse.getPrecedence() != GraphTargetItem.NOPRECEDENCE) { + writer.append("("); } onFalse.toString(writer, localData); - if (onFalse instanceof TernarOpItem){ - writer.append(")"); + if (onFalse.getPrecedence() >= precedence && onFalse.getPrecedence() != GraphTargetItem.NOPRECEDENCE) { + writer.append(")"); } - + return writer; } 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 ca6cb6f98..046bbd207 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java @@ -1978,7 +1978,7 @@ public class ActionScript2Test extends ActionScript2TestBase { + "{\r\n" + "trace(_loc1_);\r\n" + "}\r\n" - + "_loc3_.r1 = _loc2_ + 1 + \". \" + (!_loc4_ ? _loc3_.r2 = v1[_loc2_][0] : \"unk\");\r\n" + + "_loc3_.r1 = _loc2_ + 1 + \". \" + (!_loc4_ ? (_loc3_.r2 = v1[_loc2_][0]) : \"unk\");\r\n" + "}\r\n" + "trace(\"chainedAfterForInTest\");\r\n" + "var v1 = {};\r\n" 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 c6d5127dd..0ddea2fa7 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 @@ -75,6 +75,14 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testComma() { + decompileMethod("classic_air", "testComma", "var a:int = 5;\r\n" + + "var b:int = 0;\r\n" + + "trace(a > 4 ? (b = 5, a) : 35);\r\n", + false); + } + @Test public void testComplexExpressions() { decompileMethod("classic_air", "testComplexExpressions", "var i:int = 0;\r\n" @@ -560,6 +568,25 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testForInIf() { + decompileMethod("classic_air", "testForInIf", "var arr:Array = [\"a\",\"b\",\"c\"];\r\n" + + "var b:int = 5;\r\n" + + "for(var a in arr)\r\n" + + "{\r\n" + + "if(b == 5)\r\n" + + "{\r\n" + + "if(b <= 7)\r\n" + + "{\r\n" + + "return;\r\n" + + "}\r\n" + + "trace(\"b>7\");\r\n" + + "}\r\n" + + "trace(\"forend\");\r\n" + + "}\r\n", + false); + } + @Test public void testForInReturn() { decompileMethod("classic_air", "testForInReturn", "var dic:Dictionary = null;\r\n" @@ -574,6 +601,27 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testForInSwitch() { + decompileMethod("classic_air", "testForInSwitch", "var arr:Array = [\"a\",\"b\",\"c\"];\r\n" + + "for(var a in arr)\r\n" + + "{\r\n" + + "switch(a)\r\n" + + "{\r\n" + + "case \"a\":\r\n" + + "trace(\"val a\");\r\n" + + "break;\r\n" + + "case \"b\":\r\n" + + "trace(\"val b\");\r\n" + + "break;\r\n" + + "case \"c\":\r\n" + + "trace(\"val c\");\r\n" + + "}\r\n" + + "trace(\"final\");\r\n" + + "}\r\n", + false); + } + @Test public void testForXml() { decompileMethod("classic_air", "testForXml", "var i:int = 0;\r\n" @@ -846,6 +894,31 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testIfTry() { + decompileMethod("classic_air", "testIfTry", "var c:int = 0;\r\n" + + "var i:int = 0;\r\n" + + "var b:Boolean = true;\r\n" + + "if(b)\r\n" + + "{\r\n" + + "c = 5;\r\n" + + "for(i = 0; i < c; )\r\n" + + "{\r\n" + + "trace(\"xx\");\r\n" + + "i++;\r\n" + + "}\r\n" + + "}\r\n" + + "try\r\n" + + "{\r\n" + + "trace(\"in try\");\r\n" + + "}\r\n" + + "catch(e:Error)\r\n" + + "{\r\n" + + "trace(\"in catch\");\r\n" + + "}\r\n", + false); + } + @Test public void testIgnoreAndOr() { decompileMethod("classic_air", "testIgnoreAndOr", "var k:int = Math.random();\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 36a0f2a58..2b5729dd1 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 @@ -75,6 +75,14 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testComma() { + decompileMethod("classic", "testComma", "var a:int = 5;\r\n" + + "var b:int = 0;\r\n" + + "trace(a > 4 ? (b = 5, a) : 35);\r\n", + false); + } + @Test public void testComplexExpressions() { decompileMethod("classic", "testComplexExpressions", "var i:int = 0;\r\n" @@ -554,6 +562,26 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testForInIf() { + decompileMethod("classic", "testForInIf", "var a:* = null;\r\n" + + "var arr:Array = [\"a\",\"b\",\"c\"];\r\n" + + "var b:int = 5;\r\n" + + "for(a in arr)\r\n" + + "{\r\n" + + "if(b == 5)\r\n" + + "{\r\n" + + "if(b <= 7)\r\n" + + "{\r\n" + + "return;\r\n" + + "}\r\n" + + "trace(\"b>7\");\r\n" + + "}\r\n" + + "trace(\"forend\");\r\n" + + "}\r\n", + false); + } + @Test public void testForInReturn() { decompileMethod("classic", "testForInReturn", "var dic:Dictionary = null;\r\n" @@ -568,6 +596,29 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testForInSwitch() { + decompileMethod("classic", "testForInSwitch", "var a:* = null;\r\n" + + "var arr:Array = [\"a\",\"b\",\"c\"];\r\n" + + "for(a in arr)\r\n" + + "{\r\n" + + "switch(a)\r\n" + + "{\r\n" + + "case \"a\":\r\n" + + "trace(\"val a\");\r\n" + + "break;\r\n" + + "case \"b\":\r\n" + + "trace(\"val b\");\r\n" + + "break;\r\n" + + "case \"c\":\r\n" + + "trace(\"val c\");\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"final\");\r\n" + + "}\r\n", + false); + } + @Test public void testForXml() { decompileMethod("classic", "testForXml", "var c:int = 0;\r\n" @@ -837,6 +888,30 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testIfTry() { + decompileMethod("classic", "testIfTry", "var c:int = 0;\r\n" + + "var i:int = 0;\r\n" + + "var b:Boolean = true;\r\n" + + "if(b)\r\n" + + "{\r\n" + + "c = 5;\r\n" + + "for(i = 0; i < c; i++)\r\n" + + "{\r\n" + + "trace(\"xx\");\r\n" + + "}\r\n" + + "}\r\n" + + "try\r\n" + + "{\r\n" + + "trace(\"in try\");\r\n" + + "}\r\n" + + "catch(e:Error)\r\n" + + "{\r\n" + + "trace(\"in catch\");\r\n" + + "}\r\n", + false); + } + @Test public void testIgnoreAndOr() { decompileMethod("classic", "testIgnoreAndOr", "var k:int = Math.random();\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 14345fd9b..2ee129cef 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 94800a379..5077763ec 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 361386385..9f8881045 100644 --- a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as +++ b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as @@ -21,6 +21,7 @@ package TestChainedAssignments; TestComplexExpressions; TestContinueLevels; + TestComma; TestCompoundAssignments; TestDecl2; TestDeclarations; diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestComma.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestComma.as new file mode 100644 index 000000000..8daaa4b1b --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestComma.as @@ -0,0 +1,13 @@ +package tests +{ + + public class TestComma + { + public function run():* + { + var a:int = 5; + var b:int = 0; + trace(a > 4 ? (b = 5, a) : 35); + } + } +} \ No newline at end of file