diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c0274ddc..bc2c27f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. ### Added - AS2 classes: maintain order of variables, and methods (place variables before methods) - AS1/2: displaying script path in the error log when jump to invalid address - +- AS1/2: Try..catch with Error types - decompilation and direct editation ### Changed - AS1/2 direct editation - generated constantpool is sorted according to ActionPush position diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/model/clauses/TryActionItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/model/clauses/TryActionItem.java index 42dcc5ddb..fa6fc486c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/model/clauses/TryActionItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/model/clauses/TryActionItem.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.action.model.clauses; import com.jpexs.decompiler.flash.SWF; @@ -20,9 +21,21 @@ import com.jpexs.decompiler.flash.SourceGeneratorLocalData; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.model.ActionItem; import com.jpexs.decompiler.flash.action.model.ConstantPool; +import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; import com.jpexs.decompiler.flash.action.parser.script.ActionSourceGenerator; +import com.jpexs.decompiler.flash.action.swf4.ActionIf; import com.jpexs.decompiler.flash.action.swf4.ActionJump; +import com.jpexs.decompiler.flash.action.swf4.ActionPop; +import com.jpexs.decompiler.flash.action.swf4.ActionPush; +import com.jpexs.decompiler.flash.action.swf4.RegisterNumber; +import com.jpexs.decompiler.flash.action.swf5.ActionDefineLocal; +import com.jpexs.decompiler.flash.action.swf5.ActionEquals2; +import com.jpexs.decompiler.flash.action.swf5.ActionPushDuplicate; +import com.jpexs.decompiler.flash.action.swf5.ActionStackSwap; +import com.jpexs.decompiler.flash.action.swf7.ActionCastOp; +import com.jpexs.decompiler.flash.action.swf7.ActionThrow; import com.jpexs.decompiler.flash.action.swf7.ActionTry; +import com.jpexs.decompiler.flash.ecma.Null; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.decompiler.graph.Block; import com.jpexs.decompiler.graph.CompilationException; @@ -33,6 +46,8 @@ import com.jpexs.decompiler.graph.model.ContinueItem; import com.jpexs.decompiler.graph.model.LocalData; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -42,7 +57,9 @@ public class TryActionItem extends ActionItem implements Block { public List tryCommands; - public List catchExceptions; + public List catchExceptionNames; + + public List catchExceptionTypes; public List> catchCommands; @@ -61,10 +78,11 @@ public class TryActionItem extends ActionItem implements Block { return ret; } - public TryActionItem(List tryCommands, List catchExceptions, List> catchCommands, List finallyCommands) { + public TryActionItem(List tryCommands, List catchExceptionNames, List catchExceptionTypes, List> catchCommands, List finallyCommands) { super(null, null, NOPRECEDENCE); this.tryCommands = tryCommands; - this.catchExceptions = catchExceptions; + this.catchExceptionNames = catchExceptionNames; + this.catchExceptionTypes = catchExceptionTypes; this.catchCommands = catchCommands; this.finallyCommands = finallyCommands; } @@ -73,19 +91,23 @@ public class TryActionItem extends ActionItem implements Block { public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException { writer.append("try"); appendBlock(null, writer, localData, tryCommands); - for (int e = 0; e < catchExceptions.size(); e++) { + for (int e = 0; e < catchExceptionNames.size(); e++) { writer.newLine(); writer.append("catch"); if (writer.getFormatting().spaceBeforeParenthesesCatchParentheses) { writer.append(" "); } writer.append("("); - catchExceptions.get(e).toStringNoQuotes(writer, localData); + catchExceptionNames.get(e).toStringNoQuotes(writer, localData); + if (catchExceptionTypes.get(e) != null) { + writer.append(":"); + catchExceptionTypes.get(e).toStringNoQuotes(writer, localData); + } writer.append(")"); List commands = catchCommands.get(e); appendBlock(null, writer, localData, commands); } - if (catchExceptions.isEmpty() || finallyCommands.size() > 0) { + if (catchExceptionNames.isEmpty() || finallyCommands.size() > 0) { writer.newLine(); writer.append("finally"); appendBlock(null, writer, localData, finallyCommands); @@ -140,15 +162,82 @@ public class TryActionItem extends ActionItem implements Block { List finallyCommandsA = finallyCommands == null ? null : asGenerator.toActionList(asGenerator.generate(localData, finallyCommands)); List catchCommandsA = null; String catchName = null; - if (catchExceptions != null) { - if (!catchExceptions.isEmpty()) { - catchName = catchExceptions.get(0).toStringNoQuotes(LocalData.create(new ConstantPool(asGenerator.getConstantPool()))); - } - - } int catchSize = 0; + int catchRegister = 0; + boolean catchInRegisterFlag = false; if (catchCommands != null && !catchCommands.isEmpty()) { - catchCommandsA = asGenerator.toActionList(asGenerator.generate(localData, catchCommands.get(0))); + + List fullCatchBody = new ArrayList<>(); + + if (catchExceptionNames.size() == 1 && catchExceptionTypes.get(0) == null) { //catch everything without any type + GraphTargetItem ename = catchExceptionNames.get(0); + if (ename instanceof DirectValueActionItem) { + catchName = ((DirectValueActionItem) ename).getAsString(); + } else { + Logger.getLogger(TryActionItem.class.getName()).log(Level.SEVERE, "Invalid catchName, string expected"); + } + catchInRegisterFlag = false; + fullCatchBody = GraphTargetItem.toSourceMerge(localData, generator, catchCommands.get(0)); + } else { + catchInRegisterFlag = true; + catchRegister = asGenerator.getTempRegister(localData); + boolean allCatched = false; + for (int i = catchExceptionNames.size() - 1; i >= 0; i--) { + GraphTargetItem etype = catchExceptionTypes.get(i); + if (etype == null) { + allCatched = true; + break; + } + } + + if (!allCatched) { + fullCatchBody.addAll(0, GraphTargetItem.toSourceMerge(localData, generator, + new ActionPush(new RegisterNumber(catchRegister)), + new ActionThrow() + )); + } + + for (int i = catchExceptionNames.size() - 1; i >= 0; i--) { + GraphTargetItem ename = catchExceptionNames.get(i); + GraphTargetItem etype = catchExceptionTypes.get(i); + List ebody = catchCommands.get(i); + if (etype == null) { + fullCatchBody.addAll(0, GraphTargetItem.toSourceMerge(localData, generator, + new DirectValueActionItem(new RegisterNumber(catchRegister)), + ename, + new ActionStackSwap(), + new ActionDefineLocal(), + ebody + )); + } else { + List ifBody = GraphTargetItem.toSourceMerge(localData, generator, + ename, + new ActionStackSwap(), + new ActionDefineLocal(), + ebody); + fullCatchBody.add(0, new ActionPop()); + int toFinishSize = Action.actionsToBytes(asGenerator.toActionList(fullCatchBody), false, SWF.DEFAULT_VERSION).length; + ActionJump finishJump = new ActionJump(toFinishSize); + ifBody.add(finishJump); + List ifBodyA = asGenerator.toActionList(ifBody); + int ifBodySize = Action.actionsToBytes(ifBodyA, false, SWF.DEFAULT_VERSION).length; + fullCatchBody.addAll(0, ifBody); + fullCatchBody.addAll(0, + GraphTargetItem.toSourceMerge(localData, generator, + etype, + new ActionPush(new RegisterNumber(catchRegister)), + new ActionCastOp(), + new ActionPushDuplicate(), + new ActionPush(Null.INSTANCE), + new ActionEquals2(), + new ActionIf(ifBodySize) + )); + + } + } + asGenerator.releaseTempRegister(localData, catchRegister); + } + catchCommandsA = asGenerator.toActionList(fullCatchBody); catchSize = Action.actionsToBytes(catchCommandsA, false, SWF.DEFAULT_VERSION).length; tryCommandsA.add(new ActionJump(catchSize)); } @@ -157,7 +246,7 @@ public class TryActionItem extends ActionItem implements Block { finallySize = Action.actionsToBytes(finallyCommandsA, false, SWF.DEFAULT_VERSION).length; } int trySize = Action.actionsToBytes(tryCommandsA, false, SWF.DEFAULT_VERSION).length; - ret.add(new ActionTry(false, finallyCommands != null, catchCommands != null, catchName, 0, trySize, catchSize, finallySize, SWF.DEFAULT_VERSION)); + ret.add(new ActionTry(catchInRegisterFlag, finallyCommands != null, catchCommands != null, catchName, catchRegister, trySize, catchSize, finallySize, SWF.DEFAULT_VERSION)); ret.addAll(tryCommandsA); if (catchCommandsA != null) { ret.addAll(catchCommandsA); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2Parser.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2Parser.java index 3b41a5fac..9e506a13a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2Parser.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2Parser.java @@ -1152,14 +1152,25 @@ public class ActionScript2Parser { s = lex(); boolean found = false; List> catchCommands = null; - List catchExceptions = new ArrayList<>(); - if (s.type == SymbolType.CATCH) { + List catchExceptionNames = new ArrayList<>(); + List catchExceptionTypes = new ArrayList<>(); + + while (s.type == SymbolType.CATCH) { expectedType(SymbolType.PARENT_OPEN); s = lex(); expected(s, lexer.yyline(), SymbolType.IDENTIFIER, SymbolType.STRING); - catchExceptions.add(pushConst((String) s.value)); + catchExceptionNames.add(pushConst((String) s.value)); + s = lex(); + if (s.type == SymbolType.COLON) { + catchExceptionTypes.add(type(variables)); + } else { + catchExceptionTypes.add(null); + lexer.pushback(s); + } expectedType(SymbolType.PARENT_CLOSE); - catchCommands = new ArrayList<>(); + if (catchCommands == null) { + catchCommands = new ArrayList<>(); + } List cc = new ArrayList<>(); cc.add(command(inFunction, inMethod, forinlevel, true, variables, functions)); catchCommands.add(cc); @@ -1177,7 +1188,7 @@ public class ActionScript2Parser { expected(s, lexer.yyline(), SymbolType.CATCH, SymbolType.FINALLY); } lexer.pushback(s); - ret = new TryActionItem(tryCommands, catchExceptions, catchCommands, finallyCommands); + ret = new TryActionItem(tryCommands, catchExceptionNames, catchExceptionTypes, catchCommands, finallyCommands); break; case THROW: ret = new ThrowActionItem(null, null, expression(inFunction, inMethod, true, variables, functions)); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/swf7/ActionTry.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/swf7/ActionTry.java index f5cdc4667..f10f44463 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/swf7/ActionTry.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/swf7/ActionTry.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.action.swf7; import com.jpexs.decompiler.flash.SWFInputStream; @@ -21,12 +22,16 @@ import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.ActionList; import com.jpexs.decompiler.flash.action.LocalDataArea; import com.jpexs.decompiler.flash.action.model.ActionItem; +import com.jpexs.decompiler.flash.action.model.CastOpActionItem; +import com.jpexs.decompiler.flash.action.model.DefineLocalActionItem; import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; +import com.jpexs.decompiler.flash.action.model.ThrowActionItem; import com.jpexs.decompiler.flash.action.model.clauses.TryActionItem; import com.jpexs.decompiler.flash.action.parser.ActionParseException; import com.jpexs.decompiler.flash.action.parser.pcode.ASMParsedSymbol; import com.jpexs.decompiler.flash.action.parser.pcode.FlasmLexer; import com.jpexs.decompiler.flash.action.swf4.RegisterNumber; +import com.jpexs.decompiler.flash.ecma.Null; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.types.annotations.Reserved; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; @@ -34,6 +39,9 @@ import com.jpexs.decompiler.graph.GraphSourceItem; import com.jpexs.decompiler.graph.GraphSourceItemContainer; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.decompiler.graph.TranslateStack; +import com.jpexs.decompiler.graph.model.IfItem; +import com.jpexs.decompiler.graph.model.PopItem; +import com.jpexs.decompiler.graph.model.PushItem; import com.jpexs.helpers.Helper; import com.jpexs.helpers.utf8.Utf8Helper; import java.io.IOException; @@ -274,23 +282,89 @@ public class ActionTry extends Action implements GraphSourceItemContainer { @Override public void translateContainer(List> contents, GraphSourceItem lineStartItem, TranslateStack stack, List output, HashMap regNames, HashMap variables, HashMap functions) { List tryCommands = contents.get(0); - ActionItem catchName; - if (catchInRegisterFlag) { - catchName = new DirectValueActionItem(this, lineStartItem, -1, new RegisterNumber(this.catchRegister), new ArrayList<>()); - } else { - catchName = new DirectValueActionItem(this, lineStartItem, -1, this.catchName, new ArrayList<>()); - } - List catchExceptions = new ArrayList<>(); - if (catchBlockFlag) { - catchExceptions.add(catchName); - } + + List catchExceptionNames = new ArrayList<>(); + List catchExceptionTypes = new ArrayList<>(); List> catchCommands = new ArrayList<>(); + if (catchBlockFlag) { - catchCommands.add(contents.get(1)); + List body = contents.get(1); + if (catchInRegisterFlag) { + //catchName = new DirectValueActionItem(this, lineStartItem, -1, new RegisterNumber(this.catchRegister), new ArrayList<>()); + if (body.size() >= 2) { + int pos = 0; + loopex: + while (body.get(pos) instanceof PushItem) { + PushItem pi = (PushItem) body.get(pos); + if (pi.value instanceof CastOpActionItem) { + CastOpActionItem co = (CastOpActionItem) pi.value; + if ((co.object instanceof DirectValueActionItem) && (((DirectValueActionItem) co.object).value instanceof RegisterNumber)) { + RegisterNumber rn = (RegisterNumber) ((DirectValueActionItem) co.object).value; + if (rn.number == catchRegister) { + catchExceptionTypes.add(co.constructor); + if (body.get(pos + 1) instanceof IfItem) { + IfItem ifi = (IfItem) body.get(pos + 1); + if (!ifi.onTrue.isEmpty()) { + if (ifi.onTrue.get(0) instanceof DefineLocalActionItem) { + DefineLocalActionItem dl = (DefineLocalActionItem) ifi.onTrue.get(0); + catchExceptionNames.add(dl.name); + List catchBody = new ArrayList<>(ifi.onTrue); + catchBody.remove(0); + catchCommands.add(catchBody); + if (!ifi.onFalse.isEmpty()) { + if (ifi.onFalse.get(0) instanceof PopItem) { + pos = 1; + body = ifi.onFalse; + continue loopex; + } else { + break; + } + } else { + break; + } + /*if (body.size() == pos + 4) { + if (body.get(pos + 2) instanceof PopItem) { + if (body.get(pos + 3) instanceof ThrowActionItem) { + ThrowActionItem ta = (ThrowActionItem) body.get(pos + 3); + if (ta.value instanceof DirectValueActionItem) { + if (((DirectValueActionItem) ta.value).value instanceof RegisterNumber) { + RegisterNumber rn2 = (RegisterNumber) ((DirectValueActionItem) ta.value).value; + if (rn2.number == catchRegister) { + break; + } + } + } + } + } + }else{ + + }*/ + } + } + } + } + } + + } + } + if (body.get(pos) instanceof DefineLocalActionItem) { + DefineLocalActionItem dl = (DefineLocalActionItem) body.get(pos); + catchExceptionNames.add(dl.name); + catchExceptionTypes.add(null); + List catchBody = new ArrayList<>(body); + catchBody.remove(0); //pop + catchBody.remove(0); //definelocal + catchCommands.add(catchBody); + } + } + } else { + catchExceptionNames.add(new DirectValueActionItem(this, lineStartItem, -1, this.catchName, new ArrayList<>())); + catchExceptionTypes.add(null); + catchCommands.add(body); + } } List finallyCommands = contents.get(2); - output.add(new TryActionItem(tryCommands, catchExceptions, catchCommands, finallyCommands)); - + output.add(new TryActionItem(tryCommands, catchExceptionNames, catchExceptionTypes, catchCommands, finallyCommands)); } @Override 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 73652164e..c557b9895 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java @@ -1873,4 +1873,66 @@ public class ActionScript2Test extends ActionScript2TestBase { + "trace(\"switchVariantsTest\");\r\n" ); } + + @Test + public void frame68_tryTypeTest() { + compareSrc(68, "trace(\"tryTypeTest\");\r\n" + + "var a = 5;\r\n" + + "try\r\n" + + "{\r\n" + + "a = a / 0;\r\n" + + "}\r\n" + + "catch(e)\r\n" + + "{\r\n" + + "trace(\"err:\" + e);\r\n" + + "}\r\n" + + "try\r\n" + + "{\r\n" + + "a = a / 0;\r\n" + + "}\r\n" + + "catch(e)\r\n" + + "{\r\n" + + "if(a == 0)\r\n" + + "{\r\n" + + "throw e;\r\n" + + "}\r\n" + + "else\r\n" + + "{\r\n" + + "trace(\"err:\" + e);\r\n" + + "}\r\n" + + "}\r\n" + + "try\r\n" + + "{\r\n" + + "a = a / 0;\r\n" + + "}\r\n" + + "catch(e:MyError)\r\n" + + "{\r\n" + + "trace(\"err:\" + e);\r\n" + + "}\r\n" + + "try\r\n" + + "{\r\n" + + "a = a / 0;\r\n" + + "}\r\n" + + "catch(e1:MyError)\r\n" + + "{\r\n" + + "trace(\"err:\" + e1);\r\n" + + "}\r\n" + + "catch(e2)\r\n" + + "{\r\n" + + "trace(\"err:\" + e2);\r\n" + + "}\r\n" + + "try\r\n" + + "{\r\n" + + "a = a / 0;\r\n" + + "}\r\n" + + "catch(e:MyError)\r\n" + + "{\r\n" + + "trace(\"err:\" + e);\r\n" + + "}\r\n" + + "catch(e2:Error)\r\n" + + "{\r\n" + + "trace(\"err:\" + e2);\r\n" + + "}\r\n" + ); + } } diff --git a/libsrc/ffdec_lib/testdata/as2/MyError.as b/libsrc/ffdec_lib/testdata/as2/MyError.as new file mode 100644 index 000000000..17155b011 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as2/MyError.as @@ -0,0 +1,4 @@ +class MyError extends Error +{ + var message = "My custom error occurred"; +} \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/as2/as2.fla b/libsrc/ffdec_lib/testdata/as2/as2.fla index cad3e071e..a84106d38 100644 Binary files a/libsrc/ffdec_lib/testdata/as2/as2.fla and b/libsrc/ffdec_lib/testdata/as2/as2.fla differ diff --git a/libsrc/ffdec_lib/testdata/as2/as2.swf b/libsrc/ffdec_lib/testdata/as2/as2.swf index c9832c61c..58a2056d8 100644 Binary files a/libsrc/ffdec_lib/testdata/as2/as2.swf and b/libsrc/ffdec_lib/testdata/as2/as2.swf differ