From fe28187a970603e79fc9105f02e7d7fca5e4c4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Tue, 17 Feb 2026 18:12:02 +0100 Subject: [PATCH] Fixed: #2636 ActionScript - Incorrect always-break detection causing insertion of while(true) Fixed: ActionScript - newline after do..while --- CHANGELOG.md | 3 + .../src/com/jpexs/decompiler/flash/SWF.java | 2 +- .../flash/abc/avm2/graph/AVM2Graph.java | 5 ++ .../decompiler/flash/action/ActionGraph.java | 2 +- .../src/com/jpexs/decompiler/graph/Graph.java | 87 ++++++++++++++++--- .../com/jpexs/decompiler/graph/GraphPart.java | 12 ++- .../decompiler/graph/model/DoWhileItem.java | 9 +- .../flash/ActionScript2AssemblerTest.java | 18 ++-- 8 files changed, 107 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f3b5528..9414a674a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ All notable changes to this project will be documented in this file. - Metadata formatting - [#2632] TTF export - non-zero italic angle - Improper standalone layer termination, not cleaning temp files +- [#2636] ActionScript - Incorrect always-break detection causing insertion of while(true) +- ActionScript - newline after do..while ## [25.0.0] - 2026-02-10 ### Added @@ -4124,6 +4126,7 @@ Major version of SWF to XML export changed to 2. [#1850]: https://www.free-decompiler.com/flash/issues/1850 [#2619]: https://www.free-decompiler.com/flash/issues/2619 [#2632]: https://www.free-decompiler.com/flash/issues/2632 +[#2636]: https://www.free-decompiler.com/flash/issues/2636 [#2610]: https://www.free-decompiler.com/flash/issues/2610 [#2603]: https://www.free-decompiler.com/flash/issues/2603 [#2626]: https://www.free-decompiler.com/flash/issues/2626 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index 5199fe7d7..867a9d042 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -701,7 +701,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { /** * SHA 256 hash of original data */ - private String hashSha256 = null; + private String hashSha256 = "0000000000000000000000000000000000000000000000000000000000000000"; public String getHashSha256() { return hashSha256; 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 a683fc2d6..ce871126d 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 @@ -1896,6 +1896,11 @@ public class AVM2Graph extends Graph { return ret; } } + + if (caseBodyParts.size() < 2) { + stack.push(firstSet); + return ret; + } //determine whether local register are on left or on right side of === operator // -1 = there's no register, diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java index e01c21c72..cfc6705af 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java @@ -304,7 +304,7 @@ public class ActionGraph extends Graph { @Override protected void finalProcess(GraphTargetItem parent, List list, int level, FinalProcessLocalData localData, String path) throws InterruptedException { - + if (level == 0) { List removed = new ArrayList<>(); for (int i = list.size() - 1; i >= 0; i--) { 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 34164afb5..f62a71fec 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -1305,6 +1305,41 @@ public class Graph { LoopItem innerLoop = (LoopItem) lastCommand; Block blk = (Block) lastCommand; changeContinueToBreak(blk, li.loop.id, innerLoop.loop.id); + + if (innerLoop instanceof UniversalLoopItem) { + UniversalLoopItem loopItem = (UniversalLoopItem) innerLoop; + if (!loopItem.commands.isEmpty() && loopItem.commands.get(loopItem.commands.size() - 1) instanceof IfItem) { + IfItem ifi = (IfItem) loopItem.commands.get(loopItem.commands.size() - 1); + boolean inverted = false; + boolean found = false; + if (ifi.onFalse.isEmpty() && (ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onTrue.get(0); + if (bi.loopId == loopItem.loop.id) { + found = true; + inverted = true; + } + } else if (ifi.onTrue.isEmpty() && (ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onFalse.get(0); + if (bi.loopId == loopItem.loop.id) { + found = true; + } + } + + if (found) { + loopItem.commands.remove(loopItem.commands.size() - 1); + GraphTargetItem expressionSingle = ifi.expression; + if (inverted) { + expressionSingle = expressionSingle.invert(null); + } + List expression = new ArrayList<>(); + expression.add(expressionSingle); + DoWhileItem doWhile = new DoWhileItem(dialect, loopItem.getSrc(), loopItem.getLineStartItem(), loopItem.loop, loopItem.commands, expression); + currentList.set(currentList.size() - 1, doWhile); + } + } + } + + } else if (lastCommand instanceof Block) { Block blk = (Block) lastCommand; List> newTodos = new ArrayList<>(blk.getSubs()); @@ -1316,7 +1351,7 @@ public class Graph { todos.addAll(newTodos); } } - } + } if (li instanceof ForItem) { ForItem fi = (ForItem) li; List continues = fi.getContinues(); @@ -1823,7 +1858,7 @@ public class Graph { /** * Final process. Override this method to provide custom behavior. * - * @param parent Paren item + * @param parent Parent item * @param list List of GraphTargetItems * @param level Level * @param localData Local data @@ -2085,6 +2120,10 @@ public class Graph { if (blk instanceof SwitchItem) { return; } + + if (blk instanceof LoopItem) { + return; + } for (List sub : blk.getSubs()) { processScriptEnd(sub); @@ -3312,7 +3351,7 @@ public class Graph { } if (debugPrintGraph) { System.err.println("loopsize:" + loops.size()); - } + } for (int l = loops.size() - 1; l >= 0; l--) { Loop el = loops.get(l); if (el == ignoredLoop) { @@ -3338,7 +3377,7 @@ public class Graph { } continue; } - if (el.loopBreak == part) { + if (el.loopBreak == part) { if (currentLoop != null) { currentLoop.phase = 0; } @@ -3354,8 +3393,8 @@ public class Graph { ret.add(br); return originalRet; - } - if (el.loopPreContinue == part) { + } + if (el.loopPreContinue == part) { if (currentLoop != null) { currentLoop.phase = 0; } @@ -3382,7 +3421,7 @@ public class Graph { if (debugPrintGraph) { System.err.println("stopParts: " + pathToString(stopPart)); } - + if (stopPart.contains(part)) { boolean isRealStopPart = false; @@ -3433,7 +3472,7 @@ public class Graph { } return originalRet; } - + boolean vCanHandleVisited = canHandleVisited(localData, part); if (vCanHandleVisited) { @@ -3826,6 +3865,7 @@ public class Graph { /* Detect forward jumps (breaks) in always-break loops. + FIXME!!! */ if (localData.secondPassData != null) { if (next != null) { @@ -3848,7 +3888,15 @@ public class Graph { while (!s.isEmpty()) { GraphPart p = s.poll(); v.add(p); + if (p == part) { + continue; + } for (GraphPart r : p.refs) { + // #2636, it has no test + if (!part.leadsTo(localData, this, code, r, loops, throwStates)) { + //System.err.println("Part " + part + " do not lead"); + continue; + } if (r == part) { continue; } @@ -3867,8 +3915,9 @@ public class Graph { if (!n.leadsTo(localData, this, code, next, loops, throwStates)) { GraphPart n2 = getCommonPart(localData, r, Arrays.asList(next, n), loops, throwStates); if (n2 != null) { - //System.err.println("Found block: start = " + part + ", break = " + n2+", exit = " + r); + //System.err.println("Found block: start = " + part + ", break = " + n2 + ", exit = " + r); //System.err.println("next = " + next); + //System.err.println("n = " + n + " does not lead to next"); Loop el = new Loop(loops.size(), part, n2); el.phase = 1; @@ -4177,7 +4226,11 @@ public class Graph { if (bi.loopId == currentLoop.id) { bodyBranch = ifi.onTrue; } - } else if (loopItem.commands.size() == 2 && (loopItem.commands.get(1) instanceof BreakItem)) { + } else if (loopItem.commands.size() == 2 + && (loopItem.commands.get(1) instanceof BreakItem) + && ifi.onFalse.isEmpty() + && !ifi.onTrue.isEmpty() + ) { BreakItem bi = (BreakItem) loopItem.commands.get(1); if (ifi.onTrue.isEmpty()) { inverted = true; @@ -4186,8 +4239,16 @@ public class Graph { breakpos2 = true; if (bi.loopId != currentLoop.id) { //it's break of another parent loop addBreakItem = bi; //we must add it after the loop + } else { + if ( + !(bodyBranch.get(bodyBranch.size() - 1) instanceof ContinueItem) + && !(bodyBranch.get(bodyBranch.size() - 1) instanceof BreakItem) + && !(bodyBranch.get(bodyBranch.size() - 1) instanceof ExitItem) + ) { + bodyBranch.add(loopItem.commands.get(1)); + } } - } else if ((ifi.onTrue.size() == 1) + } /*else if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onTrue.get(0)).loopId != currentLoop.id)) { addContinueItem = (ContinueItem) ifi.onTrue.get(0); @@ -4207,8 +4268,8 @@ public class Graph { } bodyBranch = inverted ? ifi.onFalse : ifi.onTrue; breakpos2 = true; - } - if (bodyBranch != null) { + }*/ + if (bodyBranch != null) { //FIXME int index = ret.indexOf(loopItem); ret.remove(index); List exprList = new ArrayList<>(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphPart.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphPart.java index 0b7cf121d..45407b126 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphPart.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphPart.java @@ -150,6 +150,7 @@ public class GraphPart implements Serializable { Stack todo = new Stack<>(); todo.push(this); + boolean first = true; looptodo: while (!todo.isEmpty()) { @@ -164,18 +165,23 @@ public class GraphPart implements Serializable { continue; } for (Loop l : loops) { - if (l.phase == 1) { + if (l.phase == 1) { if (l.loopContinue == thisPart) { - continue looptodo; + if (!first) { + continue looptodo; + } } if (l.loopPreContinue == thisPart) { - continue looptodo; + if (!first) { + continue looptodo; + } } if (l.loopBreak == thisPart) { //return false; //? } } } + first = false; if (visited.contains(thisPart)) { continue; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/DoWhileItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/DoWhileItem.java index f8ab1d1cb..9e1b9939a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/DoWhileItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/DoWhileItem.java @@ -53,12 +53,7 @@ public class DoWhileItem extends LoopItem implements Block { * Label used */ private boolean labelUsed; - - @Override - public boolean needsSemicolon() { - return false; - } - + @Override public List> getSubs() { List> ret = new ArrayList<>(); @@ -137,7 +132,7 @@ public class DoWhileItem extends LoopItem implements Block { } - writer.append(");").newLine(); + writer.append(")"); if (writer instanceof NulWriter) { LoopWithType loopOjb = ((NulWriter) writer).endLoop(loop.id); labelUsed = loopOjb.used; diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java index c988bccc1..4fa02559f 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java @@ -556,19 +556,26 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { assertEquals(res, "function f()\n" + "{\n" + "var a = [1,2,3];\n" + + "loop0:\n" + "for(v in a)\n" + "{\n" + "trace(v);\n" + "var b = 0;\n" - + "while(b < 10)\n" + + "while(true)\n" + + "{\n" + + "if(b < 10)\n" + "{\n" + "if(b == 4)\n" + "{\n" - + "trace(\"ret\");\n" - + "return;\n" //critical - no level2 break, but return + + "break;\n" + "}\n" + "b++;\n" + + "continue;\n" + "}\n" + + "continue loop0;\n" + + "}\n" + + "trace(\"ret\");\n" + + "break;\n" + "}\n" + "}"); } @@ -587,7 +594,7 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { res = cleanPCode(res); assertEquals(res, "this.myVar++;"); } - + @Test public void testMemberCompoundDup() { String res = decompilePcode("Push \"this\"\n" @@ -604,7 +611,6 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { assertEquals(res, "this.myVar += 1;"); } - @Test public void testVariableIncrementDup() { String res = decompilePcode("Push \"c\"\n" @@ -615,7 +621,7 @@ public class ActionScript2AssemblerTest extends ActionScript2TestBase { res = cleanPCode(res); assertEquals(res, "c++;"); } - + @Test public void testVariableCompoundDup() { String res = decompilePcode("Push \"c\"\n"