From 7559970c17956e40ef403b2ad2cc00d618039e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Thu, 24 Jul 2025 21:12:24 +0200 Subject: [PATCH] =?UTF-8?q?Fixed:=20#2493=20Incorrect=20placement=20of=20?= =?UTF-8?q?=C2=A7=C2=A7push=20instructions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + .../decompiler/flash/abc/avm2/AVM2Code.java | 14 +-- .../AVM2DeobfuscatorRegisters.java | 2 +- .../AVM2DeobfuscatorRegistersOld.java | 2 +- .../flash/abc/avm2/graph/AVM2Graph.java | 36 +++++--- .../flash/abc/avm2/graph/AVM2GraphSource.java | 11 +-- .../jpexs/decompiler/flash/action/Action.java | 8 +- .../flash/action/ActionGraphSource.java | 6 +- .../src/com/jpexs/decompiler/graph/Graph.java | 25 +++-- .../jpexs/decompiler/graph/GraphSource.java | 4 +- .../decompiler/graph/GraphTargetItem.java | 7 +- .../decompiler/graph/TranslateStack.java | 17 ++++ .../ActionScript3AssembledDecompileTest.java | 38 ++++++-- .../as3_assembled-0/as3_assembled-0.main.abc | Bin 10007 -> 10225 bytes .../as3_assembled-0.main.asasm | 1 + .../tests/TestPushPlacement.class.asasm | 87 ++++++++++++++++++ .../tests/TestPushPlacement.script.asasm | 29 ++++++ .../as3_assembled/bin/as3_assembled.swf | Bin 5792 -> 5905 bytes 18 files changed, 225 insertions(+), 64 deletions(-) create mode 100644 libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/tests/TestPushPlacement.class.asasm create mode 100644 libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/tests/TestPushPlacement.script.asasm diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ea1715c1..c7350e722 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ All notable changes to this project will be documented in this file. - AS1/2 both pcode and AS editable at the same time - AS3 direct editation - Allow some special words (like `override`) as identifiers - [#2486] AS1/2 if jump to function end as return +- [#2493] Incorrect placement of §§push instructions ### Changed - Icon of "Deobfuscation options" menu from pile of pills to medkit @@ -3933,6 +3934,7 @@ Major version of SWF to XML export changed to 2. [#1277]: https://www.free-decompiler.com/flash/issues/1277 [#2483]: https://www.free-decompiler.com/flash/issues/2483 [#2486]: https://www.free-decompiler.com/flash/issues/2486 +[#2493]: https://www.free-decompiler.com/flash/issues/2493 [#2476]: https://www.free-decompiler.com/flash/issues/2476 [#2404]: https://www.free-decompiler.com/flash/issues/2404 [#1418]: https://www.free-decompiler.com/flash/issues/1418 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java index d7f107338..912c08a1d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/AVM2Code.java @@ -1826,6 +1826,7 @@ public class AVM2Code implements Cloneable { /** * Converts to source output. * + * @param output Output * @param swfVersion SWF version * @param switchParts Switch parts * @param callStack Call stack @@ -1853,11 +1854,10 @@ public class AVM2Code implements Cloneable { * @param visited Visited * @param localRegAssignmentIps Local register assignment IPs * @param bottomStackSetLocals Set locals on bottom of the stack - * @return Convert output * @throws ConvertException On convert error * @throws InterruptedException On interrupt */ - public ConvertOutput toSourceOutput(int swfVersion, Set switchParts, List callStack, AbcIndexing abcIndex, Map> setLocalPosToGetLocalPos, boolean thisHasDefaultToPrimitive, Reference lineStartItem, String path, GraphPart part, boolean processJumps, boolean isStatic, int scriptIndex, int classIndex, HashMap localRegs, TranslateStack stack, ScopeStack scopeStack, ScopeStack localScopeStack, ABC abc, MethodBody body, int start, int end, HashMap localRegNames, HashMap localRegTypes, List fullyQualifiedNames, boolean[] visited, HashMap localRegAssignmentIps, LinkedIdentityHashSet bottomStackSetLocals) throws ConvertException, InterruptedException { + public void toSourceOutput(List output, int swfVersion, Set switchParts, List callStack, AbcIndexing abcIndex, Map> setLocalPosToGetLocalPos, boolean thisHasDefaultToPrimitive, Reference lineStartItem, String path, GraphPart part, boolean processJumps, boolean isStatic, int scriptIndex, int classIndex, HashMap localRegs, TranslateStack stack, ScopeStack scopeStack, ScopeStack localScopeStack, ABC abc, MethodBody body, int start, int end, HashMap localRegNames, HashMap localRegTypes, List fullyQualifiedNames, boolean[] visited, HashMap localRegAssignmentIps, LinkedIdentityHashSet bottomStackSetLocals) throws ConvertException, InterruptedException { boolean debugMode = DEBUG_MODE; if (debugMode) { System.err.println("OPEN SubSource:" + start + "-" + end + " " + code.get(start).toString() + " to " + code.get(end).toString()); @@ -1870,7 +1870,6 @@ public class AVM2Code implements Cloneable { if (toSourceLimit > 0 && toSourceCount >= toSourceLimit) { throw new ConvertException("Limit of subs(" + toSourceLimit + ") was reached", start); } - List output = new ArrayList<>(); int ip = start; //try { //int addr; @@ -2043,14 +2042,7 @@ public class AVM2Code implements Cloneable { } if (debugMode) { System.err.println("CLOSE SubSource:" + start + "-" + end + " " + code.get(start).toString() + " to " + code.get(end).toString()); - } - /*if (hideTemporaryRegisters) { - clearTemporaryRegisters(output); - }*/ - return new ConvertOutput(stack, output); - /*} catch (ConvertException cex) { - throw cex; - }*/ + } } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java index fe9c34a4d..9ca7076cd 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegisters.java @@ -360,7 +360,7 @@ public class AVM2DeobfuscatorRegisters extends AVM2DeobfuscatorSimple { } @Override - public List translatePart(Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException { + public void translatePart(List output, Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegistersOld.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegistersOld.java index 9410430c9..77a158814 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegistersOld.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/deobfuscation/AVM2DeobfuscatorRegistersOld.java @@ -383,7 +383,7 @@ public class AVM2DeobfuscatorRegistersOld extends AVM2DeobfuscatorSimpleOld { } @Override - public List translatePart(Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException { + public void translatePart(List output, Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java index 424ec8dc3..22e1295d1 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java @@ -98,6 +98,7 @@ import com.jpexs.decompiler.graph.Graph; import com.jpexs.decompiler.graph.GraphException; import com.jpexs.decompiler.graph.GraphPart; import com.jpexs.decompiler.graph.GraphPartChangeException; +import com.jpexs.decompiler.graph.GraphPartMarkedArrayList; import com.jpexs.decompiler.graph.GraphSource; import com.jpexs.decompiler.graph.GraphSourceItem; import com.jpexs.decompiler.graph.GraphTargetItem; @@ -340,11 +341,11 @@ public class AVM2Graph extends Graph { //localData2.scopeStack = new ScopeStack(); localData2.localScopeStack = new ScopeStack(); - List targetOutput; + List targetOutput = new GraphPartMarkedArrayList<>(); try { - targetOutput = translatePart(localData2, finallyTryTargetPart, finallyTryTargetStack, 0 /*??*/, "try_target"); + translatePart(targetOutput, localData2, finallyTryTargetPart, finallyTryTargetStack, 0 /*??*/, "try_target"); } catch (GraphPartChangeException ex1) { //should not happen - targetOutput = new ArrayList<>(); + targetOutput.clear(); } int switchedReg = -1; @@ -1173,9 +1174,9 @@ public class AVM2Graph extends Graph { if (finallyIndex > -1) { parsedExceptionIds.add(finallyIndex); } - List tryCommands = new ArrayList<>(); - List> catchCommands = new ArrayList<>(); - List finallyCommands = new ArrayList<>(); + List tryCommands = new GraphPartMarkedArrayList<>(); + List> catchCommands = new GraphPartMarkedArrayList<>(); + List finallyCommands = new GraphPartMarkedArrayList<>(); GraphPart afterPart = null; @@ -1255,7 +1256,7 @@ public class AVM2Graph extends Graph { boolean inlinedFinally = false; boolean finallyAsUnnamedException = false; - List finallyTargetItems = new ArrayList<>(); + List finallyTargetItems = new GraphPartMarkedArrayList<>(); GraphPart defaultPart = null; GraphPart finallyPart = null; @@ -1289,12 +1290,12 @@ public class AVM2Graph extends Graph { AVM2LocalData localData2 = new AVM2LocalData(localData); //localData2.scopeStack = new ScopeStack(); localData2.localScopeStack = new ScopeStack(); - + try { //We are assuming Finally target has only 1 part - finallyTargetItems = translatePart(localData2, finallyTryTargetPart, st2, staticOperation, path); + translatePart(finallyTargetItems, localData2, finallyTryTargetPart, st2, staticOperation, path); } catch (GraphPartChangeException ex) { //should not happen - finallyTargetItems = new ArrayList<>(); + finallyTargetItems.clear(); } //boolean targetHasThrow = false; if (!finallyTargetItems.isEmpty() && (finallyTargetItems.get(finallyTargetItems.size() - 1) instanceof ThrowAVM2Item)) { @@ -1385,9 +1386,10 @@ public class AVM2Graph extends Graph { } if (switchPart != null) { try { - finallyCommands.addAll(translatePart(localData, switchPart, stack, staticOperation, path)); + translatePart(finallyCommands, localData, switchPart, stack, staticOperation, path); } catch (GraphPartChangeException ex) { //should not happen + finallyCommands.clear(); } stack.pop(); //value switched by lookupswitch } @@ -2249,13 +2251,21 @@ public class AVM2Graph extends Graph { } } + int firstPushPos = -1; + for (int i = output.size() - 2 /*last is loop*/; i >= 0; i--) { if (output.get(i) instanceof PushItem) { - PushItem pu = (PushItem) output.remove(i); - stack.push(pu.value); + firstPushPos = i; } else { break; } + } + if (firstPushPos > -1) { + int max = output.size() - 2; + for (int i = firstPushPos; i <= max; i++) { + PushItem pu = (PushItem) output.remove(firstPushPos); + stack.push(pu.value); + } } if (usages.isEmpty()) { output.add(filter); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2GraphSource.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2GraphSource.java index 88559a1bf..0c36b58b6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2GraphSource.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2GraphSource.java @@ -188,6 +188,7 @@ public class AVM2GraphSource extends GraphSource { /** * Translates the part of the graph source * + * @param output Output * @param graph Graph * @param part Graph part * @param localData Local data @@ -196,17 +197,13 @@ public class AVM2GraphSource extends GraphSource { * @param end End position * @param staticOperation Unused * @param path Path - * @return List of graph target items * @throws InterruptedException On interrupt */ @Override - public List translatePart(Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException { - List ret = new ArrayList<>(); + public void translatePart(List output, Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException { Reference lineStartItem = new Reference<>(localData.lineStartInstruction); - ConvertOutput co = code.toSourceOutput(localData.swfVersion, localData.allSwitchParts, ((AVM2LocalData) localData).callStack, ((AVM2LocalData) localData).abcIndex, ((AVM2LocalData) localData).setLocalPosToGetLocalPos, ((AVM2LocalData) localData).thisHasDefaultToPrimitive, lineStartItem, path, part, false, isStatic, scriptIndex, classIndex, localRegs, stack, ((AVM2LocalData) localData).scopeStack, ((AVM2LocalData) localData).localScopeStack, abc, body, start, end, localRegNames, ((AVM2LocalData) localData).localRegTypes, fullyQualifiedNames, new boolean[size()], localRegAssignmentIps, ((AVM2LocalData) localData).bottomSetLocals); - localData.lineStartInstruction = lineStartItem.getVal(); - ret.addAll(co.output); - return ret; + code.toSourceOutput(output, localData.swfVersion, localData.allSwitchParts, ((AVM2LocalData) localData).callStack, ((AVM2LocalData) localData).abcIndex, ((AVM2LocalData) localData).setLocalPosToGetLocalPos, ((AVM2LocalData) localData).thisHasDefaultToPrimitive, lineStartItem, path, part, false, isStatic, scriptIndex, classIndex, localRegs, stack, ((AVM2LocalData) localData).scopeStack, ((AVM2LocalData) localData).localScopeStack, abc, body, start, end, localRegNames, ((AVM2LocalData) localData).localRegTypes, fullyQualifiedNames, new boolean[size()], localRegAssignmentIps, ((AVM2LocalData) localData).bottomSetLocals); + localData.lineStartInstruction = lineStartItem.getVal(); } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java index e806a7219..7ba406edf 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/Action.java @@ -1225,6 +1225,7 @@ public abstract class Action implements GraphSourceItem { /** * Converts list of actions to tree. * + * @param output Output * @param graph ActionGraph * @param switchParts Switch parts * @param secondPassData Second pass data @@ -1241,17 +1242,15 @@ public abstract class Action implements GraphSourceItem { * @param staticOperation Static operation * @param path Path * @param charset Charset - * @return List of tree items * @throws InterruptedException On interrupt * @throws GraphPartChangeException On graph part change */ - public static List actionsPartToTree(ActionGraph graph, Set switchParts, SecondPassData secondPassData, boolean insideDoInitAction, Reference lineStartActionRef, HashMap registerNames, HashMap variables, HashMap functions, TranslateStack stack, List actions, int start, int end, int version, int staticOperation, String path, String charset) throws InterruptedException, GraphPartChangeException { + public static void actionsPartToTree(List output, ActionGraph graph, Set switchParts, SecondPassData secondPassData, boolean insideDoInitAction, Reference lineStartActionRef, HashMap registerNames, HashMap variables, HashMap functions, TranslateStack stack, List actions, int start, int end, int version, int staticOperation, String path, String charset) throws InterruptedException, GraphPartChangeException { if (start < actions.size() && (end > 0) && (start > 0)) { logger.log(Level.FINE, "Entering {0}-{1}{2}", new Object[]{start, end, actions.size() > 0 ? (" (" + actions.get(start).toString() + " - " + actions.get(end == actions.size() ? end - 1 : end) + ")") : ""}); } ActionLocalData localData = new ActionLocalData(switchParts, secondPassData, insideDoInitAction, registerNames, variables, functions, graph.getUninitializedClassTraits()); localData.lineStartAction = lineStartActionRef.getVal(); - List output = new ArrayList<>(); int ip = start; boolean isWhile = false; boolean isForIn = false; @@ -1389,8 +1388,7 @@ public abstract class Action implements GraphSourceItem { if (ip > end + 1) { throw new GraphPartChangeException(output, ip); } - logger.log(Level.FINE, "Leaving {0}-{1}", new Object[]{start, end}); - return output; + logger.log(Level.FINE, "Leaving {0}-{1}", new Object[]{start, end}); } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java index cb210c232..b33ab53bf 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraphSource.java @@ -197,6 +197,7 @@ public class ActionGraphSource extends GraphSource { /** * Translates the part of the graph source * + * @param output Output * @param graph Graph * @param part Graph part * @param localData Local data @@ -210,12 +211,11 @@ public class ActionGraphSource extends GraphSource { * @throws GraphPartChangeException On graph part change */ @Override - public List translatePart(Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException, GraphPartChangeException { + public void translatePart(List output, Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException, GraphPartChangeException { Reference fi = new Reference<>(localData.lineStartInstruction); - List r = Action.actionsPartToTree((ActionGraph) graph, localData.allSwitchParts, localData.secondPassData, this.insideDoInitAction, fi, registerNames, variables, functions, stack, actions, start, end, version, staticOperation, path, charset); + Action.actionsPartToTree(output, (ActionGraph) graph, localData.allSwitchParts, localData.secondPassData, this.insideDoInitAction, fi, registerNames, variables, functions, stack, actions, start, end, version, staticOperation, path, charset); localData.lineStartInstruction = fi.getVal(); - return r; } /** 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 9698b7a4d..4320541e9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -27,7 +27,6 @@ import com.jpexs.decompiler.graph.model.BinaryOpItem; import com.jpexs.decompiler.graph.model.BranchStackResistant; import com.jpexs.decompiler.graph.model.BreakItem; import com.jpexs.decompiler.graph.model.CommaExpressionItem; -import com.jpexs.decompiler.graph.model.CommentItem; import com.jpexs.decompiler.graph.model.ContinueItem; import com.jpexs.decompiler.graph.model.DefaultItem; import com.jpexs.decompiler.graph.model.DoWhileItem; @@ -2354,7 +2353,7 @@ public class Graph { //@SuppressWarnings("unchecked") protected final GraphTargetItem translatePartGetStack(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation) throws InterruptedException, GraphPartChangeException { stack = (TranslateStack) stack.clone(); - translatePart(localData, part, stack, staticOperation, null); + translatePart(new ArrayList<>(), localData, part, stack, staticOperation, null); return stack.pop(); } @@ -2373,13 +2372,14 @@ public class Graph { protected final GraphTargetItem translatePartGetStack(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, List output) throws InterruptedException, GraphPartChangeException { stack = (TranslateStack) stack.clone(); output.clear(); - output.addAll(translatePart(localData, part, stack, staticOperation, null)); + translatePart(output, localData, part, stack, staticOperation, null); return stack.pop(); } /** * Translates part. * + * @param output Output * @param localData Local data * @param part Part * @param stack Translate stack @@ -2389,9 +2389,8 @@ public class Graph { * @throws InterruptedException On interrupt * @throws GraphPartChangeException On graph part change */ - protected final List translatePart(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, String path) throws InterruptedException, GraphPartChangeException { + protected final void translatePart(List output, BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, String path) throws InterruptedException, GraphPartChangeException { List sub = part.getSubParts(); - List ret = new ArrayList<>(); int end; for (GraphPart p : sub) { if (p.end == -1) { @@ -2404,9 +2403,8 @@ public class Graph { } end = p.end; int start = p.start; - ret.addAll(code.translatePart(this, part, localData, stack, start, end, staticOperation, path)); + code.translatePart(output, this, part, localData, stack, start, end, staticOperation, path); } - return ret; } /** @@ -3469,6 +3467,7 @@ public class Graph { if (currentRet instanceof GraphPartMarkedArrayList) { ((GraphPartMarkedArrayList) currentRet).startPart(part); } + stack.setConnectedOutput(0, currentRet); if (checkPartOutput(currentRet, foundGotos, partCodes, partCodePos, visited, code, localData, allParts, stack, parent, part, stopPart, stopPartKind, loops, throwStates, currentLoop, staticOperation, path, recursionLevel)) { parseNext = false; } else { @@ -3477,9 +3476,10 @@ public class Graph { do { exHappened = false; try { - output.addAll(code.translatePart(this, part, localData, stack, ipStart, part.end, staticOperation, path)); + stack.setConnectedOutput(currentRet.size(), output); + code.translatePart(output, this, part, localData, stack, ipStart, part.end, staticOperation, path); } catch (GraphPartChangeException ex) { //Special case for ifFrameLoaded when it's over multiple parts - output.addAll(ex.getOutput()); + //output.addAll(ex.getOutput()); for (GraphPart p : allParts) { if (p.containsIP(ex.getIp())) { if (ex.getIp() == p.start) { @@ -4542,7 +4542,12 @@ public class Graph { //ASC2 leaves some function calls unpopped on stack before returning from a method commands.add(clen, p); } else { - commands.add(clen, new PushItem(p)); + int pos = 0; + if (p.outputPos < commands.size()) { + commands.add(p.outputPos, new PushItem(p)); + } else { + commands.add(clen + pos, new PushItem(p)); + } } } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSource.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSource.java index 1e010a4d1..d3d19891d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSource.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphSource.java @@ -62,6 +62,7 @@ public abstract class GraphSource implements Serializable { /** * Translates the part of the graph source * + * @param output Output * @param graph Graph * @param part Graph part * @param localData Local data @@ -70,11 +71,10 @@ public abstract class GraphSource implements Serializable { * @param end End position * @param staticOperation Unused * @param path Path - * @return List of graph target items * @throws InterruptedException On interrupt * @throws GraphPartChangeException On graph part change */ - public abstract List translatePart(Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException, GraphPartChangeException; + public abstract void translatePart(List output, Graph graph, GraphPart part, BaseLocalData localData, TranslateStack stack, int start, int end, int staticOperation, String path) throws InterruptedException, GraphPartChangeException; /** * Gets the important addresses diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphTargetItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphTargetItem.java index e2534bd61..ba2c3125d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphTargetItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphTargetItem.java @@ -128,7 +128,12 @@ public abstract class GraphTargetItem implements Serializable, Cloneable { * Dialect */ public GraphTargetDialect dialect; - + + /** + * Position in output - current list of GraphTargetItems + */ + public int outputPos = -1; + /** * Gets the line start item * diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/TranslateStack.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/TranslateStack.java index 6163ac9af..0976e92bf 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/TranslateStack.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/TranslateStack.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.graph; import com.jpexs.decompiler.graph.model.PopItem; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Stack; import java.util.logging.Level; @@ -39,9 +40,25 @@ public class TranslateStack extends Stack { * Path */ private final String path; + + private List connectedOutput = null; + + private int prevOutputSize = 0; private Map marks = new HashMap<>(); + public void setConnectedOutput(int prevOutputSize, List connectedOutput) { + this.prevOutputSize = prevOutputSize; + this.connectedOutput = connectedOutput; + } + + @Override + public GraphTargetItem push(GraphTargetItem item) { + if (connectedOutput != null && item != null) { + item.outputPos = prevOutputSize + connectedOutput.size(); + } + return super.push(item); + } /** * Sets mark. diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3AssembledDecompileTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3AssembledDecompileTest.java index 9b5192244..95b392931 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3AssembledDecompileTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/as3decompile/ActionScript3AssembledDecompileTest.java @@ -313,6 +313,24 @@ public class ActionScript3AssembledDecompileTest extends ActionScript3DecompileT false); } + @Test + public void testPushPlacement() { + decompileMethod("assembled", "testPushPlacement", "var a:int = 1;\r\n" + + "var b:* = 2;\r\n" + + "§§push(a);\r\n" + + "a += 1;\r\n" + + "if(b >= 2)\r\n" + + "{\r\n" + + "b = §§pop() + 7;\r\n" + + "trace(b);\r\n" + + "}\r\n" + + "else\r\n" + + "{\r\n" + + "§§pop();\r\n" + + "}\r\n", + false); + } + @Test public void testPushWhile() { decompileMethod("assembled", "testPushWhile", "var _loc3_:int = 5;\r\n" @@ -440,33 +458,33 @@ public class ActionScript3AssembledDecompileTest extends ActionScript3DecompileT + "case 1:\r\n" + "case 6:\r\n" + "trace(\"1-6\");\r\n" - + "addr0120:\r\n" + + "addr0121:\r\n" + "trace(\"F\");\r\n" + "break;\r\n" + "case 5:\r\n" + "trace(\"5\");\r\n" - + "addr0119:\r\n" + + "addr011a:\r\n" + "trace(\"E\");\r\n" - + "§§goto(addr0120);\r\n" + + "§§goto(addr0121);\r\n" + "case 7:\r\n" + "trace(\"7\");\r\n" - + "addr0112:\r\n" + + "addr0113:\r\n" + "trace(\"D\");\r\n" - + "§§goto(addr0119);\r\n" + + "§§goto(addr011a);\r\n" + "case 2:\r\n" + "trace(\"2\");\r\n" - + "addr010b:\r\n" + + "addr010c:\r\n" + "trace(\"C\");\r\n" - + "§§goto(addr0112);\r\n" + + "§§goto(addr0113);\r\n" + "case 8:\r\n" + "trace(\"8\");\r\n" - + "addr0104:\r\n" + + "addr0105:\r\n" + "trace(\"B\");\r\n" - + "§§goto(addr010b);\r\n" + + "§§goto(addr010c);\r\n" + "default:\r\n" + "trace(\"def\");\r\n" + "trace(\"A\");\r\n" - + "§§goto(addr0104);\r\n" + + "§§goto(addr0105);\r\n" + "}\r\n" + "trace(\"G\");\r\n" + "return null;\r\n", diff --git a/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/as3_assembled-0.main.abc b/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/as3_assembled-0.main.abc index 17a3ba11bbf88a144ddbf40f7e52fa8ab1804b9e..f5ec7afc1f4cd7287067daebcc7ffc4ef2c0b953 100644 GIT binary patch delta 3152 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 diff --git a/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/as3_assembled-0.main.asasm b/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/as3_assembled-0.main.asasm index f3fd6eeea..96276599b 100644 --- a/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/as3_assembled-0.main.asasm +++ b/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/as3_assembled-0.main.asasm @@ -39,5 +39,6 @@ program #include "tests/TestGoto2.script.asasm" #include "tests/TestAlwaysBreak.script.asasm" #include "tests/TestAlwaysBreak2.script.asasm" + #include "tests/TestPushPlacement.script.asasm" ; place to add next end ; program diff --git a/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/tests/TestPushPlacement.class.asasm b/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/tests/TestPushPlacement.class.asasm new file mode 100644 index 000000000..0582895ab --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/tests/TestPushPlacement.class.asasm @@ -0,0 +1,87 @@ +class + refid "tests:TestPushPlacement" + instance QName(PackageNamespace("tests"), "TestPushPlacement") + extends QName(PackageNamespace(""), "Object") + flag SEALED + flag PROTECTEDNS + protectedns ProtectedNamespace("tests:TestPushPlacement") + iinit + refid "tests:TestPushPlacement/instance/init" + body + maxstack 1 + localcount 1 + initscopedepth 4 + maxscopedepth 5 + code + getlocal0 + pushscope + + getlocal0 + constructsuper 0 + + returnvoid + end ; code + end ; body + end ; method + trait method QName(PackageNamespace(""), "run") + method + refid "tests:TestPushPlacement/instance/run" + returns QName(PackageNamespace(""), "void") + body + maxstack 2 + localcount 4 + initscopedepth 4 + maxscopedepth 5 + code + getlocal0 + pushscope + debug 1, "a", 0, 13 + debug 1, "b", 1, 14 + pushbyte 1 + convert_i + setlocal1 + pushbyte 2 + coerce_a + setlocal2 + getlocal1 + getlocal1 + pushbyte 1 + add + convert_i + setlocal1 + getlocal2 + pushbyte 2 + ifnge ofs0031 + pushbyte 7 + add + coerce_a + setlocal2 + findpropstrict QName(PackageNamespace(""),"trace") + getlocal2 + callpropvoid QName(PackageNamespace(""),"trace"), 1 + jump ofs0032 + ofs0031: + pop + ofs0032: + returnvoid + end ; code + end ; body + end ; method + end ; trait + end ; instance + cinit + refid "tests:TestPushPlacement/class/init" + body + maxstack 1 + localcount 1 + initscopedepth 3 + maxscopedepth 4 + code + getlocal0 + pushscope + + returnvoid + end ; code + end ; body + end ; method +end ; class diff --git a/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/tests/TestPushPlacement.script.asasm b/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/tests/TestPushPlacement.script.asasm new file mode 100644 index 000000000..9c1b48812 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_assembled/abc/as3_assembled-0/tests/TestPushPlacement.script.asasm @@ -0,0 +1,29 @@ +script + sinit + refid "tests:TestPushPlacement/init" + body + maxstack 2 + localcount 1 + initscopedepth 1 + maxscopedepth 3 + code + getlocal0 + pushscope + + findpropstrict Multiname("TestPushPlacement", [PackageNamespace("tests")]) + getlex QName(PackageNamespace(""), "Object") + pushscope + + getlex Multiname("Object", [PrivateNamespace(null, "tests:TestPushPlacement"), PackageNamespace(""), PackageNamespace("tests"), PackageInternalNs("tests"), Namespace("http://adobe.com/AS3/2006/builtin")]) + newclass "tests:TestPushPlacement" + popscope + initproperty QName(PackageNamespace("tests"), "TestPushPlacement") + + returnvoid + end ; code + end ; body + end ; method + trait class QName(PackageNamespace("tests"), "TestPushPlacement") + #include "TestPushPlacement.class.asasm" + end ; trait +end ; script diff --git a/libsrc/ffdec_lib/testdata/as3_assembled/bin/as3_assembled.swf b/libsrc/ffdec_lib/testdata/as3_assembled/bin/as3_assembled.swf index 692633add059ddcfccd1c32680b9b7dac984a9c3..7e05cf57c13a2a3c081cb5e79514ad1b0e714581 100644 GIT binary patch literal 5905 zcmV+s7w+goS5qV_DgXd@+U;6tR9i=uuKO01N`MfX5CYj?WMhN?A-rOn7_)i9*v5$! z#~7ZZC!S)VM?1ta=E#_?(W6K#jeHPE;BRJ-E;8Z!R}DFI~?xB5uMrPbWU69OlMo$ z`USv{o{eRabGf9MZsxKUHRp3j+S)cyaR5GSpYwVYWxqM+mbDJ?xSug&QF!)HX_rYvmKa(e$sE!`a67ip(=ku2wGVZVqAI0{{B_ckd?qf$5amRn^7}Jtz^26GHYa;|@ zlE`SYdJl1tE+T~q`3JC#NAK2SIf#N*KQ%RDPUXz0xSmL+lYGi4Lr={?OrA5dpsTrz z7Sjm?rUv^i4`s}`sJV3cY{r~HOmjg?CoiAW7PN`s%OfL0dhBv48O>&5mlKJ&9-Bhp za!$|Y&@)Xi*_4^fUd923gmmZv(h08X(z2?S$mtn~L&BCdF>11~00VFg1{LlN9a|p6 z*vQJ~=hN!!@`9GB6A*nIVj#7sEoYD6@?UipwWDUvtSlA7>hJ=_qcV!?VVjuCBy)O= z0Ep$13nH4(v$<^LEugMjX^vXPH6vzbSc@6eTlhg! z&){_Sf{Sd^SAl#Yy&8zQqQzISCY9+6{u&M@P$o{qzeb0%d*wbaObI>rYpGib7krzvGZ z(w^78Vn`orB?zjI({Xn1Jq!uZt0g@Msw+p%PBR-0qTmUH?* zCZjFGP{mL(JD1Xyt-Rwdkp@kyHOVxd{b6-DlQA=u;*jz&e7R6RQa^(GaW=!xsD5Um zZ!>McD4eJ|k;c5C@e{0DfEB0o<*2D;;?`jw({e`nM*6@!;}5a1J~^x0eoj+y2y-_K%qhNnG*dTy3e|%FpGNQs+ySZQ3gI-4Z6wjm{;U zF0G{;mN=uEvm0y91890j?O1jm?&A{fcH2yxtI;vTxJ>1JZ=t>aCzb2^U1afVL6%3`I5OVo;0*{ zoUcJwb{0!S!d${&SipmDm0s1CMF6BsEk0<@r*kR~m>xmH4l7UajAfr)wgk*3T*jI? zcXb8@UV?3+fQB7~!lcI)^QyM2nmJrXTLvdLXKS!BmU zP%3&cqjQ?cQJ$6X+%#;d&!y$n{f?D8T)QQmZI)JFiSpJ0+rG8;Hax~?pk$+)_hjOPp)D)@_*pRXVHGC*b*Q@ca}!zXP7%1<&t+ z=l8+$2jKY`c>WMPe*~UC2G5^>X9YZ~;JE>wH^K9#;8_FDI(Xg!&!2(k&%yH-;Q34N z{1te94xYaT&)i6xrS=n)(OTCf=$(LAMFIUC?Gh zTLj%9Xse)pL3aweOVBn!cMIAsXh6^oL4$&J3fd)Tx1b?Gdjt&&+AHWDLH7!}Ptg5> z9uV}PpnZZK67;ZehdY1V{lJi5PIw*~=8JGNa_<0?!?_;3g$@cq3Y`?XD0EW@QRtx%rqD~_UJ83C?4_`e!hQ+|C>*5FN8u2K!xWBC z=%;WSt*fVX-voACYV4v%uA?=BRGOobM5U8dI!&eXwEQHs`Dn%0fL5HQm(DOvVvUqN zB(d*-#O$oGo3i7y>K z^SAi?J<#%R0kuV`O{ca5wWTR-q%=vXNyoEvB113C)7ycnGF2t2s#N_VTJsH{cYFt^ z@&HvRbEpjL3>*x~7?d*%UvM(0WZ+^@#h{u&4TD++bqw4L>KS+#G%)ZoXk^gDz{g-4 zgY68O8MH9i!Jw6apTSNByBM@F*v+7wL4f&eY#Xrcz?y-z0NVkq6__8`PGGx$wE^1= ztQ}YYSO>5muufoIz`B8jfb{?i1M3C02iRU<`+)5Sb^zExV12+20Xq!r2(W%&w*iwB zCaLT(klB~;8JF2_f%I-5@*pXEK#mhTl$VpOADNKBP<1`rQ({O1zwHY?oMyY~$>9uf(Rw4$ii=N-RNkaki~dVj2l> zwxd&GCh6jAcTi$!66S1gy~HNTKF;nBODsqFID4p9V)LY*v$u6i>>hHAvx5y1n<68e z9o;LjMRJm}r+OroC1*H0jzxn^$QO@-{WAbSB1a|rXOV$B2FL|aJQBM?F7knqE{V;N zyEuD!kHi+p6lbT~Bz8B6aW-BnvAf6&XN?Yt&62B}P3@GJK}^ohg(Q|CS(?nA&v78Qb^M>>{E#E0q`qi+rt3c6|(&?fM$g><1*AKq~%!v zw?cNj2(U{btvuBN!P`{mpUbA*TDlw-mYAb*B{X@mpgzgfiiBAHm5@%RrO>n< zn$|+ovCwohG(8xaejh}LPkfAO9IN7*}z>MW;R^MMy9T^*Y1N`HsekDcWO zv`-1N`H18*T2Nn6M01v)RdTf6l~7Ae^^iAhXOzl$oL2u#SxwY2Wcw6e?h=6M{GW0H=_88@Y_n@w{!Tiiy?nFaAg<)j}sWSAy9Jx z=!*jA-U#&>?Kr-r2(+UFsFedX;tNNk>c~;JHWLWM9Er&nqK?GvFO1*GquF1;{HVaZ zFJku@Uexa_V(u!zY!k+)Vb&EXEfp)okggw5~ywtbpoN(MlT++7PJO;3-yBT zBbZQ$Kfdm2#fGfAny`U&7sWq%6vvA>U)Y7gi9>w zYD2CE)h=LsfQjje)h&137q@^yT9Phqw%i`gEE zl>3Yz>h~6B+gCE%{*A%xSGA>#BIVVvnm(S-gI6K<1b)p5=`Q|Qs~2td0IlSdLq zMlO--vw*rWu5)?#ysM#0B(NR`b%>5p-r=)Ld_nwi_l*NSUKFo|8MSS z!KiNtqpm}w(r5JI+yh0Uj+GcS$k*j{Ni57YzA&!atreNzyb}a}f%E@r>F?tFuPlC& z^J^A=k@G*c_$ki6X7SGm&<|q(9$deHY4%O8jR&f0h6ku6fa?;cmLR#tc(7W6>^jN= z*b)?1FH&j=s%sY?Yza1315&#sP#%Xa&e>grQ@h-_l+W8k`8@tREdCwO=P%^*)A@WV zpT|Blv&!1xYOATSnz+@>Sk2v5lftGM5Asl9tzQ>w{Rl){KBF6#bhxYFI~2x!<(Y^b4JzUc-_IeNKO}!za@|# zgGjZ{*oy#TMdULj$m0rL%$|ak(62$}+rD>4ocO{W#r3~fvi^}m9dfMu)wLT~j6VI+=VCJU z+*w$G4~rFe0wQ%j!-E}{idc7*U|r@|KLUQw_%)0WJ8mhkUxOq${AzY>*TQ<+!dtF$ zR>(dgLe}*HN;j%;quzX9)uVVSF@~^H7JYWXOPt466}oBb?MJP*&!fb%!D>wL<28(* z?w!Qd&U3|v1g=J&1vVtfRe{x;Uo&^gE{RjS()h!CUe4z859jl*=kqA=>(FGu_pb=w zpM;3pXLMo5m7?!cCB9E{-_KjQTZiC-v$6}nQ8P@(QZ+9Zc%{<;B{^~Ttjyc0U*bu1 z-;&6$9BMNTjba){AD;zLOl%mAbjWg^ADOw1D5PPBr3-xT(}3nKMCV-K1UE!r0= zu`kZ;8xSX04#onLr#nubCV}axj^N)xht}5#EKuzurll9AepO8U6hu5eqYXO}#i?gXrZ)K0o?k;fclIJ)XhW0xSGE0<2mB80z$lUS z^Bdv;A~{I1fc<`f{WL@xd`2xo++D=JT7sS8*gm{@7$XB}+huu;3b>QD2)?2Q}l<6Np^B1aL!O1Go13z7Noy8W-#l*Rq z_&(sbnmeQFhQ(-#e{MqpelGlyU2R;;=cS+J^Ka(!$T99Kzc#A;$f4=3VGi^?4%D*= zv<>O-CJ=|cXG4+%tc< z?@tYGdrffq7+kAaTTOfy5?OwoTrPlpTYx6RmSzAOZs)^L zF!U3`&=(=H-Dhmap6gJw^bsgA^-*E!WAF~P+R#?prmZ%#)dsHpf|WQ+1*;;9O@$aL zI|D*VQJVe^UO=2qAv>)>$E@b#P<(yFQVn3keRyoshffM0-VKpvpV5pxk8kneiBcb) z6h2(ET&Q&Q^Uy~>CRKDmMXMAiej!0g5|Tr>qoUy|@MMi_1+v&suE8kSRrLeBN2_SH zDvWy9rcoadM!f`)7N1dvJx^^h>giIWo)Jdf@!v7Z{Wha`R(Yeure`;8`k=7sJrLR9 zGu+to+!mXjFSY3f*!cAZzjak$iN=kF?3!P?zP^gP{&E&?0#aoqzbwCeeSH#c@CzT_ ndw2nOadX1YiwWNgkyf9vi~nk-hX3`S_+vo+o9cfD14m^yp_GA5 literal 5792 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