diff --git a/CHANGELOG.md b/CHANGELOG.md index d0b520f4b..04d2a9b35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file. - AS3 goto definition missing some namespaces - [#2514] AS3 direct editation - problems with namespaces - [#2649] SVG export - blur scaling (on supported browsers) +- [#2651] Loop breaks detection ### Changed - Icon of Debug listen action from ear to phone receiver @@ -4190,6 +4191,7 @@ Major version of SWF to XML export changed to 2. [#2645]: https://www.free-decompiler.com/flash/issues/2645 [#2514]: https://www.free-decompiler.com/flash/issues/2514 [#2649]: https://www.free-decompiler.com/flash/issues/2649 +[#2651]: https://www.free-decompiler.com/flash/issues/2651 [#2639]: https://www.free-decompiler.com/flash/issues/2639 [#2642]: https://www.free-decompiler.com/flash/issues/2642 [#2636]: https://www.free-decompiler.com/flash/issues/2636 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 7cd6aee75..c7466ce5e 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 @@ -1878,14 +1878,14 @@ public class AVM2Graph extends Graph { if (caseBodyParts.size() == 2) { boolean isIf = false; for (GraphPart r : part.refs) { - if (r != origPart && !origPart.leadsTo(localData, this, code, r, loops, throwStates)) { + if (r != origPart && !origPart.leadsTo(localData, this, code, r, loops, throwStates, false)) { isIf = true; break; } } if (!isIf) { for (GraphPart r : caseBodyParts.get(1).refs) { - if (r != origPart && !origPart.leadsTo(localData, this, code, r, loops, throwStates)) { + if (r != origPart && !origPart.leadsTo(localData, this, code, r, loops, throwStates, false)) { isIf = true; break; } 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 53b78a90d..2dad65688 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -581,7 +581,7 @@ public class Graph { if (q == p) { continue; } - if (!q.leadsTo(localData, this, code, p, loops, throwStates)) { + if (!q.leadsTo(localData, this, code, p, loops, throwStates, false)) { common = false; break; } @@ -666,10 +666,10 @@ public class Graph { int levelCompare = o2.level - o1.level; if (levelCompare == 0) { try { - if (o1.part.leadsTo(localData, Graph.this, code, o2.part, loops, throwStates)) { + if (o1.part.leadsTo(localData, Graph.this, code, o2.part, loops, throwStates, false)) { return -1; } - if (o2.part.leadsTo(localData, Graph.this, code, o1.part, loops, throwStates)) { + if (o2.part.leadsTo(localData, Graph.this, code, o1.part, loops, throwStates, false)) { return 1; } return 0; @@ -732,7 +732,7 @@ public class Graph { if (visited.contains(n)) { continue; } - if (!n.leadsTo(localData, this, code, r, loops, throwStates)) { + if (!n.leadsTo(localData, this, code, r, loops, throwStates, false)) { common = false; break loopprocess; } @@ -1738,7 +1738,7 @@ public class Graph { } } } - if (el.loopContinue.leadsTo(localData, this, code, r, loops, throwStates)) { + if (el.loopContinue.leadsTo(localData, this, code, r, loops, throwStates, false)) { el.backEdges.add(r); } } @@ -2713,7 +2713,7 @@ public class Graph { } try { - if (!part.leadsTo(localData, this, code, lastP1.loopContinue, loops2, throwStates)) { + if (!part.leadsTo(localData, this, code, lastP1.loopContinue, loops2, throwStates, false)) { if (lastP1.breakCandidatesLocked == 0) { if (debugGetLoops) { System.err.println("added breakCandidate " + part + " to " + lastP1); @@ -2746,7 +2746,7 @@ public class Graph { } part.level = level; - isLoop = part.leadsTo(localData, this, code, part, loops, throwStates); + isLoop = part.leadsTo(localData, this, code, part, loops, throwStates, false); currentLoop = null; if (isLoop) { currentLoop = new Loop(loops.size(), part, null); @@ -2906,7 +2906,7 @@ public class Graph { for (int c = 0; c < currentLoop.breakCandidates.size(); c++) { GraphPart cand = currentLoop.breakCandidates.get(c); GraphPart sp = currentLoop.stopParts.get(currentLoop.stopParts.size() - 1); - if (cand == sp || cand.leadsTo(localData, this, code, sp, new ArrayList<>() /*ignore existing loop states*/, throwStates)) { + if (cand == sp || cand.leadsTo(localData, this, code, sp, new ArrayList<>() /*ignore existing loop states*/, throwStates, false)) { breakCandidatesLeft.add(c); } } @@ -2932,7 +2932,7 @@ public class Graph { if (cand == cand2) { continue; } - if (cand.leadsTo(localData, this, code, cand2, loops, throwStates)) { + if (cand.leadsTo(localData, this, code, cand2, loops, throwStates, false)) { int curLevl = currentLoop.breakCandidatesLevels.get(c1); int curLev2 = currentLoop.breakCandidatesLevels.get(c2); @@ -3882,7 +3882,7 @@ public class Graph { continue; } // also #2636 - if (!part.leadsTo(localData, this, code, r, loops, throwStates)) { + if (!part.leadsTo(localData, this, code, r, loops, throwStates, true /*IMPORTANT*/)) { continue; } @@ -3901,7 +3901,7 @@ public class Graph { if (v.contains(n)) { continue; } - if (!n.leadsTo(localData, this, code, next, loops, throwStates)) { + if (!n.leadsTo(localData, this, code, next, loops, throwStates, true /*IMPORTANT*/)) { 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); @@ -4834,7 +4834,7 @@ public class Graph { */ //must go backwards to hit case 2, not case 1 for (int i = caseBodyParts.size() - 1; i >= 0; i--) { - if (caseBodyParts.get(i).leadsTo(localData, this, code, defaultPart, loops, throwStates)) { + if (caseBodyParts.get(i).leadsTo(localData, this, code, defaultPart, loops, throwStates, false)) { DefaultItem di = new DefaultItem(dialect); caseValuesMap.add(i + 1, di); caseBodyParts.add(i + 1, defaultPart); @@ -4855,7 +4855,7 @@ public class Graph { trace("2"); */ for (int i = 0; i < caseBodyParts.size(); i++) { - if (defaultPart.leadsTo(localData, this, code, caseBodyParts.get(i), loops, throwStates)) { + if (defaultPart.leadsTo(localData, this, code, caseBodyParts.get(i), loops, throwStates, false)) { DefaultItem di = new DefaultItem(dialect); caseValuesMap.add(i, di); caseBodyParts.add(i, defaultPart); @@ -4905,8 +4905,8 @@ public class Graph { GraphPart b = caseBodies.get(i); for (int j = i + 1; j < caseBodies.size(); j++) { GraphPart b2 = caseBodies.get(j); - if (b2.leadsTo(localData, this, code, b, loops, throwStates)) { - if (b.leadsTo(localData, this, code, b2, loops, throwStates)) { //unstructured code + if (b2.leadsTo(localData, this, code, b, loops, throwStates, false)) { + if (b.leadsTo(localData, this, code, b2, loops, throwStates, false)) { //unstructured code continue; } caseBodies.remove(j); @@ -4914,7 +4914,7 @@ public class Graph { i--; continue loopi; } else if (j > i + 1) { - if (b.leadsTo(localData, this, code, b2, loops, throwStates)) { + if (b.leadsTo(localData, this, code, b2, loops, throwStates, false)) { caseBodies.remove(j); caseBodies.add(i + 1, b2); continue loopi; @@ -4932,7 +4932,7 @@ public class Graph { List currentCaseCommands = new ArrayList<>(); boolean willHaveBreak = false; if (i < caseBodies.size() - 1) { - if (!caseBodies.get(i).leadsTo(localData, this, code, caseBodies.get(i + 1), loops, throwStates)) { + if (!caseBodies.get(i).leadsTo(localData, this, code, caseBodies.get(i + 1), loops, throwStates, false)) { willHaveBreak = true; } } 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 45407b126..674af088c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphPart.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphPart.java @@ -140,17 +140,18 @@ public class GraphPart implements Serializable { * @param visited Visited parts * @param loops Loops * @param throwStates Throw states + * @param firstCanBeLoopContinue Can entry point be loop continue? * @return True if this part leads to the other part * @throws InterruptedException On interrupt */ - private boolean leadsTo(BaseLocalData localData, Graph gr, GraphSource code, GraphPart prev, GraphPart part, HashSet visited, List loops, List throwStates) throws InterruptedException { + private boolean leadsTo(BaseLocalData localData, Graph gr, GraphSource code, GraphPart prev, GraphPart part, HashSet visited, List loops, List throwStates, boolean firstCanBeLoopContinue) throws InterruptedException { if (CancellableWorker.isInterrupted()) { throw new InterruptedException(); } Stack todo = new Stack<>(); todo.push(this); - boolean first = true; + boolean first = firstCanBeLoopContinue; //This is big HACK of how to get always-break working looptodo: while (!todo.isEmpty()) { @@ -224,14 +225,15 @@ public class GraphPart implements Serializable { * @param part Part to check * @param loops Loops * @param throwStates Throw states + * @param firstCanBeLoopContinue Can entry point be loop continue? * @return True if this part leads to the other part * @throws InterruptedException On interrupt */ - public boolean leadsTo(BaseLocalData localData, Graph gr, GraphSource code, GraphPart part, List loops, List throwStates) throws InterruptedException { + public boolean leadsTo(BaseLocalData localData, Graph gr, GraphSource code, GraphPart part, List loops, List throwStates, boolean firstCanBeLoopContinue) throws InterruptedException { for (Loop l : loops) { l.leadsToMark = 0; } - return leadsTo(localData, gr, code, null /*???*/, part, new HashSet<>(), loops, throwStates); + return leadsTo(localData, gr, code, null /*???*/, part, new HashSet<>(), loops, throwStates, firstCanBeLoopContinue); } /** 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 4fa02559f..78dccdf2e 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2AssemblerTest.java @@ -556,26 +556,19 @@ 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(true)\n" - + "{\n" - + "if(b < 10)\n" + + "while(b < 10)\n" + "{\n" + "if(b == 4)\n" + "{\n" - + "break;\n" + + "trace(\"ret\");\n" + + "return;\n" + "}\n" + "b++;\n" - + "continue;\n" + "}\n" - + "continue loop0;\n" - + "}\n" - + "trace(\"ret\");\n" - + "break;\n" + "}\n" + "}"); } 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 7c6607f54..51593a241 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java @@ -2647,5 +2647,27 @@ public class ActionScript2Test extends ActionScript2TestBase { + "trace(\"finish\");\r\n" ); } + + @Test + public void frame97_twoInTest() { + compareSrc(97, "trace(\"twoInTest\");\r\n" + + "var o = {a:{}};\r\n" + + "for(var n in o)\r\n" + + "{\r\n" + + "var c = 5;\r\n" + + "for(var i in o.a)\r\n" + + "{\r\n" + + "if(i == c)\r\n" + + "{\r\n" + + "if(i == 0)\r\n" + + "{\r\n" + + "trace(\"xx\");\r\n" + + "break;\r\n" + + "}\r\n" + + "}\r\n" + + "}\r\n" + + "}\r\n" + ); + } //--FRAMES-END-- } diff --git a/libsrc/ffdec_lib/testdata/as2/as2.swf b/libsrc/ffdec_lib/testdata/as2/as2.swf index 07ed7760f..2cfa3bec1 100644 Binary files a/libsrc/ffdec_lib/testdata/as2/as2.swf and b/libsrc/ffdec_lib/testdata/as2/as2.swf differ diff --git a/libsrc/ffdec_lib/testdata/as2/as2/DOMDocument.xml b/libsrc/ffdec_lib/testdata/as2/as2/DOMDocument.xml index bedf9fb84..a1eedcc4a 100644 --- a/libsrc/ffdec_lib/testdata/as2/as2/DOMDocument.xml +++ b/libsrc/ffdec_lib/testdata/as2/as2/DOMDocument.xml @@ -31,7 +31,7 @@ - + @@ -771,9 +771,9 @@ +!3156 7501[3091 7490 3054 7490!3054 7490[3050 7494 3050 7457!3050 7457[3050 7441 3159 7239!3159 7239[3270 7033 3327 6947!3327 6947[3353 6908 3412 6794!3412 6794[3470 6682 3500 6639!3500 6639[3602 6491 3649 6692!3649 6692|3713 6670!3713 + 6670[3760 6655 3782 6655!3782 6655[3942 6655 3985 6781!3985 6781[4007 6844 3996 6914!3996 6914[3996 6948 3999 6995!3999 6995[4000 7031 3993 7054!3993 7054[3976 7106 3860 7224!3860 7224[3847 7237 3741 7292!3741 7292[3638 7346 3616 7372 +!3616 7372[3594 7396 3511 7442!3511 7442[3435 7483 3427 7483!3427 7483[3409 7483 3407 7468!3407 7468[3405 7453 3386 7453!3386 7453[3361 7468 3330 7483!3330 7483[3270 7512 3235 7512!3235 7512[3220 7512 3156 7501"/> + + + + + + @@ -3563,6 +3587,7 @@ trace("finish");]]> + @@ -3582,6 +3607,5 @@ trace("finish");]]> - \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/NineSlice.xml b/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/NineSlice.xml index ec750720c..c36681108 100644 --- a/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/NineSlice.xml +++ b/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/NineSlice.xml @@ -37,27 +37,30 @@ - - - - - - + - + + + + + + + + - - - + diff --git a/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/blue.xml b/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/blue.xml index f59b98d61..960671973 100644 --- a/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/blue.xml +++ b/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/blue.xml @@ -36,8 +36,8 @@ trace("init_blue"); - + diff --git a/libsrc/ffdec_lib/testdata/as2/as2/META-INF/metadata.xml b/libsrc/ffdec_lib/testdata/as2/as2/META-INF/metadata.xml index 40944e265..3d3b457ee 100644 --- a/libsrc/ffdec_lib/testdata/as2/as2/META-INF/metadata.xml +++ b/libsrc/ffdec_lib/testdata/as2/as2/META-INF/metadata.xml @@ -5,8 +5,8 @@ xmlns:xmp="http://ns.adobe.com/xap/1.0/"> Adobe Flash CS4 Professional 2010-08-03T10:48:58+02:00 - 2026-01-25T03:31:05-08:00 - 2026-01-25T03:31:05-08:00 + 2026-03-03T23:15:56-08:00 + 2026-03-03T23:15:56-08:00 @@ -22,7 +22,7 @@ xmp.did:8DD71700DC9EDF1194ADAC9B23608190 xmp.did:F0EB4FF7CAC3ED11AC9DC078F41E1AA7 - xmp.iid:54232456E1F9F01185C4F57BEF746DBF + xmp.iid:B144D2FC9917F111A74DE617D61B86A4 xmp.did:8DD71700DC9EDF1194ADAC9B23608190 @@ -518,6 +518,12 @@ 2010-08-03T10:48:58+02:00 Adobe Flash Professional CS6 - build 481 + + created + xmp.iid:B144D2FC9917F111A74DE617D61B86A4 + 2010-08-03T10:48:58+02:00 + Adobe Flash Professional CS6 - build 481 + diff --git a/libsrc/ffdec_lib/testdata/as2/as2/bin/SymDepend.cache b/libsrc/ffdec_lib/testdata/as2/as2/bin/SymDepend.cache index 45ffbe129..34d34824b 100644 Binary files a/libsrc/ffdec_lib/testdata/as2/as2/bin/SymDepend.cache and b/libsrc/ffdec_lib/testdata/as2/as2/bin/SymDepend.cache differ