diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a77d063..284f22b98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,8 @@ All notable changes to this project will be documented in this file. - [#2153] FLA Export - sound streams were limited to first stream block - [#2163] FLA Export - maintain sound export settings for streams - [#2162] Debugger - ignore (warn) invalid jumps when injecting debug info +- AS3 - extra newlines on methods which use activation +- [#2162] AS3 switch inside foreach ### Changed - [#2120] Exported assets no longer take names from assigned classes if there is more than 1 assigned class diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/StoreNewActivationAVM2Item.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/StoreNewActivationAVM2Item.java index 58998485d..a8cd67de0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/StoreNewActivationAVM2Item.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/StoreNewActivationAVM2Item.java @@ -65,5 +65,9 @@ public class StoreNewActivationAVM2Item extends AVM2Item { public GraphTargetItem returnType() { return TypeItem.UNBOUNDED; } - + + @Override + public boolean isEmpty() { + return true; + } } 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 b730f5f26..1881338b6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -286,7 +286,7 @@ public class Graph { stack.add(queue); stacknext: while (!stack.isEmpty()) { - + queue = stack.peek(); if (!queue.isEmpty()) { part = queue.remove(); @@ -472,9 +472,9 @@ public class Graph { } Comparator comparator = new Comparator() { @Override - public int compare(PartCommon o1, PartCommon o2) { + public int compare(PartCommon o1, PartCommon o2) { int levelCompare = o2.level - o1.level; - if (levelCompare == 0) { + if (levelCompare == 0) { try { if (o1.part.leadsTo(localData, Graph.this, code, o2.part, loops, throwStates, false)) { return -1; @@ -637,7 +637,7 @@ public class Graph { } @Override - public int compareTo(PartCommon o) { + public int compareTo(PartCommon o) { int ret = o.level - level; if (ret == 0) { ret = part.closedTime - o.part.closedTime; @@ -886,7 +886,7 @@ public class Graph { BreakItem br = (BreakItem) it; if (br.loopId == swi.loop.id) { breakCount++; - if (breakCount > 1) { + if (breakCount > 2) { continue loopi; } } @@ -894,11 +894,11 @@ public class Graph { } if (!swi.caseCommands.isEmpty()) { List lastCommands = swi.caseCommands.get(swi.caseCommands.size() - 1); - if (lastCommands.isEmpty() && breakCount == 1) { + if (lastCommands.isEmpty() && breakCount > 0) { continue loopi; } - if (breakCount == 1 && !(lastCommands.get(lastCommands.size() - 1) instanceof ContinueItem) + if (breakCount > 0 && !(lastCommands.get(lastCommands.size() - 1) instanceof ContinueItem) && !(lastCommands.get(lastCommands.size() - 1) instanceof ExitItem)) { continue loopi; } @@ -909,10 +909,12 @@ public class Graph { 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 (commands.size() == 1) { + BreakItem br = (BreakItem) commands.get(commands.size() - 1); + if (br.loopId == swi.loop.id) { + breakCaseIndex = c; + break; + } } } } @@ -928,6 +930,24 @@ public class Graph { } } } + + if (breakCount == 2) { + if (breakCaseIndex == 0) { + continue loopi; + } + if (swi.caseCommands.get(breakCaseIndex - 1).isEmpty()) { + continue loopi; + } + GraphTargetItem ti = swi.caseCommands.get(breakCaseIndex - 1).get(swi.caseCommands.get(breakCaseIndex - 1).size() - 1); + if (!(ti instanceof BreakItem)) { + continue loopi; + } + BreakItem br = (BreakItem) ti; + if (br.loopId != swi.loop.id) { + continue loopi; + } + swi.caseCommands.get(breakCaseIndex - 1).remove(swi.caseCommands.get(breakCaseIndex - 1).size() - 1); + } boolean hasContinues = false; for (int c = 0; c < swi.caseCommands.size(); c++) { @@ -962,6 +982,9 @@ public class Graph { targetCommands.add(new BreakItem(null, null, swi.loop.id)); } } + } else if (item instanceof IfItem) { + processSwitches(((IfItem) item).onTrue, lastLoopId); + processSwitches(((IfItem) item).onFalse, lastLoopId); } } } @@ -1468,6 +1491,41 @@ public class Graph { } } } + + /* + if (xx) { + A; + break/continue x; + } + break/continue x; + + => + + if (xx) { + A; + } + break/continue x; + + */ + if (i + 1 < list.size()) { + GraphTargetItem nextItem = list.get(i + 1); + if (onFalse.isEmpty() && !onTrue.isEmpty()) { + if ((onTrue.get(onTrue.size() - 1) instanceof ContinueItem) && (nextItem instanceof ContinueItem)) { + ContinueItem cntOnTrue = (ContinueItem) onTrue.get(onTrue.size() - 1); + ContinueItem cntNext = (ContinueItem) nextItem; + if (cntOnTrue.loopId == cntNext.loopId) { + onTrue.remove(onTrue.size() - 1); + } + } + if ((onTrue.get(onTrue.size() - 1) instanceof BreakItem) && (nextItem instanceof BreakItem)) { + BreakItem brkOnTrue = (BreakItem) onTrue.get(onTrue.size() - 1); + BreakItem brkNext = (BreakItem) nextItem; + if (brkOnTrue.loopId == brkNext.loopId) { + onTrue.remove(onTrue.size() - 1); + } + } + } + } } } @@ -3357,7 +3415,7 @@ public class Graph { } } } - + protected SwitchItem handleSwitch(GraphTargetItem switchedObject, GraphSourceItem switchStartItem, List foundGotos, Map> partCodes, Map partCodePos, Set visited, Set allParts, TranslateStack stack, List stopPart, List stopPartKind, List loops, List throwStates, BaseLocalData localData, int staticOperation, String path, List caseValuesMap, GraphPart defaultPart, List caseBodyParts, Reference nextRef, Reference tiRef) throws InterruptedException { @@ -3561,7 +3619,7 @@ public class Graph { } } } - + //If the lastone is default empty and alone, remove it if (!caseCommands.isEmpty()) { List lastc = caseCommands.get(caseCommands.size() - 1); 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 4067edc1e..0796c6277 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 @@ -730,6 +730,45 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testForEachSwitch() { + decompileMethod("classic_air", "testForEachSwitch", "var a:Boolean = true;\r\n" + + "var b:Boolean = true;\r\n" + + "var c:Boolean = true;\r\n" + + "var s:int = 5;\r\n" + + "var obj:Object = {};\r\n" + + "for each(var name in obj)\r\n" + + "{\r\n" + + "if(a)\r\n" + + "{\r\n" + + "switch(s - 1)\r\n" + + "{\r\n" + + "case 0:\r\n" + + "trace(\"1\");\r\n" + + "if(b)\r\n" + + "{\r\n" + + "trace(\"1b\");\r\n" + + "}\r\n" + + "case 1:\r\n" + + "trace(\"2\");\r\n" + + "break;\r\n" + + "case 2:\r\n" + + "trace(\"3\");\r\n" + + "break;\r\n" + + "case 3:\r\n" + + "trace(\"4\");\r\n" + + "break;\r\n" + + "}\r\n" + + "if(c)\r\n" + + "{\r\n" + + "trace(\"2c\");\r\n" + + "}\r\n" + + "}\r\n" + + "trace(\"before_continue\");\r\n" + + "}\r\n", + false); + } + @Test public void testForGoto() { decompileMethod("classic_air", "testForGoto", "var i:* = 0;\r\n" @@ -1820,7 +1859,6 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile + "if(a)\r\n" + "{\r\n" + "trace(\"A\");\r\n" - + "break;\r\n" + "}\r\n" + "break;\r\n" + "}\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 828555ca1..4276692c1 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 @@ -727,6 +727,46 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testForEachSwitch() { + decompileMethod("classic", "testForEachSwitch", "var name:String = null;\r\n" + + "var a:Boolean = true;\r\n" + + "var b:Boolean = true;\r\n" + + "var c:Boolean = true;\r\n" + + "var s:int = 5;\r\n" + + "var obj:Object = {};\r\n" + + "for each(name in obj)\r\n" + + "{\r\n" + + "if(a)\r\n" + + "{\r\n" + + "switch(s)\r\n" + + "{\r\n" + + "case 1:\r\n" + + "trace(\"1\");\r\n" + + "if(b)\r\n" + + "{\r\n" + + "trace(\"1b\");\r\n" + + "}\r\n" + + "case 2:\r\n" + + "trace(\"2\");\r\n" + + "break;\r\n" + + "case 3:\r\n" + + "trace(\"3\");\r\n" + + "break;\r\n" + + "case 4:\r\n" + + "trace(\"4\");\r\n" + + "break;\r\n" + + "}\r\n" + + "if(c)\r\n" + + "{\r\n" + + "trace(\"2c\");\r\n" + + "}\r\n" + + "}\r\n" + + "trace(\"before_continue\");\r\n" + + "}\r\n", + false); + } + @Test public void testForGoto() { decompileMethod("classic", "testForGoto", "var c:int = 0;\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 715834ae0..625bf09bf 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 e300420bd..79a683d3c 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/build_flex_debug.bat b/libsrc/ffdec_lib/testdata/as3_new/build_flex_debug.bat index 69d826549..89088bc66 100644 --- a/libsrc/ffdec_lib/testdata/as3_new/build_flex_debug.bat +++ b/libsrc/ffdec_lib/testdata/as3_new/build_flex_debug.bat @@ -1,5 +1,5 @@ @echo off set COMPILERKIND=flex set SWFNAME=as3_new -c:\flex\bin\mxmlc.exe -debug=true -output bin/%SWFNAME%.%COMPILERKIND%.swf src/Main.as 1> buildlog.%COMPILERKIND%.txt 2>&1 -rem -warnings=false \ No newline at end of file +call c:\flex\bin\mxmlc.bat -debug=true -output bin/%SWFNAME%.%COMPILERKIND%.swf src/Main.as 1> buildlog.%COMPILERKIND%.txt 2>&1 +rem -warnings=false diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as index 3f4c131e2..865a5ad33 100644 --- a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as +++ b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as @@ -50,6 +50,7 @@ package TestForEach; TestForEachObjectArray; TestForEachObjectAttribute; + TestForEachSwitch; TestForEachReturn; TestForEachReturn2; TestForGoto; diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForEachSwitch.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForEachSwitch.as new file mode 100644 index 000000000..0a5af6d80 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestForEachSwitch.as @@ -0,0 +1,39 @@ +package tests { + + public class TestForEachSwitch { + public function run() : *{ + var a:Boolean = true; + var b:Boolean = true; + var c:Boolean = true; + var s:int = 5; + var obj:Object = {}; + + for each(var name:String in obj) { + if (a) { + switch (s) { + case 1: + trace("1"); + if (b) { + trace("1b"); + } + case 2: + trace("2"); + if (c) { + trace("2c"); + } + break; + case 3: + trace("3"); + break; + case 4: + trace("4"); + break; + default: + break; + } + } + trace("before_continue"); + } + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/TestNoTraits.java b/src/com/jpexs/decompiler/flash/gui/TestNoTraits.java new file mode 100644 index 000000000..de0c87a95 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/TestNoTraits.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; +import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.Tag; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * + * @author JPEXS + */ +public class TestNoTraits { + public static void main(String[] args) throws IOException, InterruptedException { + SWF swf = new SWF(new FileInputStream("c:\\FlashRelated\\2162\\test_no_traits.swf"), false, false); + ABCContainerTag cnt = swf.getAbcList().get(0); + ABC abc = cnt.getABC(); + Trait t = abc.script_info.get(2).traits.traits.remove(0); + ((TraitClass)t).slot_id = 2; + //abc.script_info.get(1).traits.traits.add(0, t); + TraitSlotConst tsc = new TraitSlotConst(); + tsc.kindType = TraitSlotConst.TRAIT_SLOT; + tsc.slot_id = 2; + tsc.name_index = t.name_index; + abc.script_info.get(1).traits.traits.add(0, tsc); + //abc.script_info.get(1).traits.traits.clear(); + ((Tag) cnt).setModified(true); + try(FileOutputStream fos = new FileOutputStream("c:\\FlashRelated\\2162\\test_no_traits_mod.swf")) { + swf.saveTo(fos); + } + + } +}