diff --git a/CHANGELOG.md b/CHANGELOG.md index 0273032a5..7fbf1918d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ All notable changes to this project will be documented in this file. - [#2612] SVG export - handle incorrect surrogate pairs in text - [#2612] SVG export - problems with retain shape bounds setting - [#2609] JDK 25+ warnings on console about native access +- [#2473], [#2530] Always-break loops containing breaks (forward gotos) ### Changed - [#2575] dumpSWF CLI command only allows single SWF dump (no imports, etc.) @@ -4100,6 +4101,8 @@ Major version of SWF to XML export changed to 2. [#2600]: https://www.free-decompiler.com/flash/issues/2600 [#2612]: https://www.free-decompiler.com/flash/issues/2612 [#2609]: https://www.free-decompiler.com/flash/issues/2609 +[#2473]: https://www.free-decompiler.com/flash/issues/2473 +[#2530]: https://www.free-decompiler.com/flash/issues/2530 [#2556]: https://www.free-decompiler.com/flash/issues/2556 [#2536]: https://www.free-decompiler.com/flash/issues/2536 [#2537]: https://www.free-decompiler.com/flash/issues/2537 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BaseLocalData.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BaseLocalData.java index d9a9ff6cd..635619e4f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BaseLocalData.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BaseLocalData.java @@ -63,6 +63,11 @@ public abstract class BaseLocalData { */ public Reference maxTempIndex = new Reference<>(0); + /** + * Wheter goto statements were used + */ + public Reference gotosUsed = new Reference<>(false); + /** * Constructor. */ diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/AVM2LocalData.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/AVM2LocalData.java index df9a2684e..a5556cfad 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/AVM2LocalData.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/AVM2LocalData.java @@ -299,6 +299,8 @@ public class AVM2LocalData extends BaseLocalData { swfVersion = localData.swfVersion; usedDeobfuscations = localData.usedDeobfuscations; maxTempIndex = localData.maxTempIndex; + gotosUsed = localData.gotosUsed; + secondPassData = localData.secondPassData; } /** 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 c78bb3149..a683fc2d6 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 @@ -3537,18 +3537,6 @@ public class AVM2Graph extends Graph { super.makeAllCommands(commands, stack); } - /** - * Prepares second pass data. Can return null when no second pass will - * happen. - * - * @param list List of GraphTargetItems - * @return Second pass data or null - */ - @Override - protected SecondPassData prepareSecondPass(List list) { - return new SecondPassData(); - } - @Override protected GraphTargetItem getIfExpression(BaseLocalData localData, TranslateStack stack, List output) { GraphTargetItem result = stack.pop(); 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 08a0595c3..e01c21c72 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 @@ -1015,23 +1015,18 @@ public class ActionGraph extends Graph { } return ip; } - - /** - * Prepares second pass data. Can return null when no second pass will - * happen. - * - * @param list List of GraphTargetItems - * @return Second pass data or null - */ + @Override - public SecondPassData prepareSecondPass(List list) { + public SecondPassData prepareSecondPass(BaseLocalData localData, List list) { ActionSecondPassData spd = new ActionSecondPassData(); Set processedIfs = new HashSet<>(); checkSecondPassSwitches(processedIfs, list, spd.switchParts, spd.switchOnFalseParts, spd.switchCaseExpressions); - if (spd.switchParts.isEmpty()) { + + if (spd.switchParts.isEmpty() && !localData.gotosUsed.getVal() && localData.allSwitchParts.isEmpty()) { return null; //no need to second pass } + spd.allSwitchParts.addAll(localData.allSwitchParts); return spd; } 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 948220bd0..34164afb5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -60,8 +60,10 @@ import com.jpexs.helpers.Helper; import com.jpexs.helpers.Reference; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -993,20 +995,9 @@ public class Graph { List ret = printGraph(gotos, new HashMap<>(), new HashMap<>(), new HashSet<>(), localData, stack, allParts, null, heads.get(0), null, null, loops, throwStates, staticOperation, path); if (localData.secondPassData == null) { - SecondPassData secondPassData = prepareSecondPass(ret); - if (secondPassData == null) { - if (!localData.allSwitchParts.isEmpty()) { - secondPassData = new SecondPassData(); - secondPassData.allSwitchParts = localData.allSwitchParts; - throw new SecondPassException(secondPassData); - } - } else { - if (secondPassData.getClass() == SecondPassData.class && localData.allSwitchParts.isEmpty()) { - //nothing - } else { - secondPassData.allSwitchParts = localData.allSwitchParts; - throw new SecondPassException(secondPassData); - } + SecondPassData secondPassData = prepareSecondPass(localData, ret); + if (secondPassData != null) { + throw new SecondPassException(secondPassData); } } @@ -1402,11 +1393,17 @@ public class Graph { * Prepares second pass data. Can return null when no second pass will * happen. Override this method to prepare second pass data. * + * @param localData Local data * @param list List of GraphTargetItems * @return Second pass data or null */ - protected SecondPassData prepareSecondPass(List list) { - return null; + protected SecondPassData prepareSecondPass(BaseLocalData localData, List list) { + if (localData.allSwitchParts.isEmpty() && !localData.gotosUsed.getVal()) { + return null; + } + SecondPassData spd = new SecondPassData(); + spd.allSwitchParts.addAll(localData.allSwitchParts); + return spd; } /** @@ -1843,6 +1840,20 @@ public class Graph { } GraphTargetItem itemI = list.get(i); + + /* + * Fix if(true) caused by while(true) {... break;} + */ + if (itemI instanceof IfItem) { + IfItem ifi = (IfItem) itemI; + if (ifi.expression instanceof TrueItem) { + list.remove(i); + list.addAll(i, ifi.onTrue); + i--; + continue; + } + } + if (itemI instanceof ForItem) { ForItem fori = (ForItem) itemI; int exprLine = fori.getLine(); @@ -3242,6 +3253,12 @@ public class Graph { * @throws InterruptedException On interrupt */ protected final List printGraph(List foundGotos, Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List stopPartKind, List loops, List throwStates, List ret, int staticOperation, String path, int recursionLevel) throws InterruptedException { + + if (ret == null) { + ret = new GraphPartMarkedArrayList<>(); + } + List originalRet = ret; + loopPrintGraph: while (true) { if (CancellableWorker.isInterrupted()) { @@ -3257,20 +3274,16 @@ public class Graph { throw new TranslateException("printGraph max recursion level reached."); } - if (ret == null) { - ret = new GraphPartMarkedArrayList<>(); - } - //try { if (debugPrintGraph) { System.err.println("PART " + part + " nextsize:" + getNextParts(localData, part).size()); } if (part == null) { - return ret; + return originalRet; } part = checkPartWithOutput(ret, stack, localData, parent, part, allParts); if (part == null) { - return ret; + return originalRet; } //List loopContinues = getLoopsContinues(loops); @@ -3340,7 +3353,7 @@ public class Graph { } ret.add(br); - return ret; + return originalRet; } if (el.loopPreContinue == part) { if (currentLoop != null) { @@ -3351,7 +3364,7 @@ public class Graph { } makeAllCommands(ret, stack); ret.add(new ContinueItem(dialect, null, localData.lineStartInstruction, el.id)); - return ret; + return originalRet; } if (el.loopContinue == part) { if (currentLoop != null) { @@ -3362,7 +3375,7 @@ public class Graph { } makeAllCommands(ret, stack); ret.add(new ContinueItem(dialect, null, localData.lineStartInstruction, el.id)); - return ret; + return originalRet; } } @@ -3409,7 +3422,7 @@ public class Graph { if (debugPrintGraph) { System.err.println("Stopped on part " + part); } - return ret; + return originalRet; } } @@ -3418,7 +3431,7 @@ public class Graph { stack.setConnectedOutput(0, ret, localData); stack.addToOutput(new ScriptEndItem(dialect)); } - return ret; + return originalRet; } boolean vCanHandleVisited = canHandleVisited(localData, part); @@ -3447,7 +3460,8 @@ public class Graph { firstCode.add(firstCodePos, new LabelItem(dialect, null, localData.lineStartInstruction, labelName)); } ret.add(new GotoItem(dialect, null, localData.lineStartInstruction, labelName)); - return ret; + localData.gotosUsed.setVal(true); + return originalRet; } else { visited.add(part); partCodes.put(part, ret); @@ -3809,6 +3823,92 @@ public class Graph { boolean isEmpty = nps.get(0) == nps.get(1); GraphPart next = getCommonPart(localData, part, nps, loops, throwStates); + + /* + Detect forward jumps (breaks) in always-break loops. + */ + if (localData.secondPassData != null) { + if (next != null) { + Set ig = new HashSet<>(); + for (Loop el : loops) { + if (el.phase == 1) { + if (el.loopContinue != null) { + ig.add(el.loopContinue); + } + if (el.loopBreak != null) { + ig.add(el.loopBreak); + } + } + } + + Deque s = new ArrayDeque<>(); + s.push(next); + Set v = new HashSet<>(); + loops: + while (!s.isEmpty()) { + GraphPart p = s.poll(); + v.add(p); + for (GraphPart r : p.refs) { + if (r == part) { + continue; + } + if (ig.contains(r)) { + continue; + } + if (v.contains(r)) { + continue; + } + + List pn = getNextParts(localData, r); + for (GraphPart n : pn) { + if (v.contains(n)) { + continue; + } + 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("next = " + next); + + Loop el = new Loop(loops.size(), part, n2); + el.phase = 1; + loops.add(el); + + ig.add(n2); + List commands = new GraphPartMarkedArrayList<>(); + UniversalLoopItem newLoopItem = new UniversalLoopItem(dialect, null, null, el, commands); + currentRet.add(newLoopItem); + + if (currentLoop != null) { + //System.err.println("handling parent loop"); + handleLoop(loopItem, li, currentLoop, loopTypeFound, doWhileCandidate, precontinueCommands, foundGotos, partCodes, partCodePos, visited, localData, allParts, null /*??*/, stopPart, stopPartKind, loops, throwStates, ret, staticOperation, path, recursionLevel, sPreLoop); + currentLoop.phase = 1; + } + loopItem = newLoopItem; + li = loopItem; + loopTypeFound = true; + doWhileCandidate = false; + precontinueCommands = new GraphPartMarkedArrayList<>(); + sPreLoop = new TranslateStack(path); + currentLoop = el; + ret = currentRet; + currentRet = commands; + isLoop = true; + if (!ig.contains(next)) { + s.clear(); + s.push(next); + v.clear(); + continue loops; + } + } + } + } + s.push(r); + } + } + } + } + TranslateStack trueStack = (TranslateStack) stack.clone(); TranslateStack falseStack = (TranslateStack) stack.clone(); @@ -4014,224 +4114,232 @@ public class Graph { } if (isLoop && loopItem != null && currentLoop != null) { - - processIfs(loopItem.commands); - processSwitches(loopItem.commands, currentLoop.id); - processOther(loopItem.commands, currentLoop.id); - - checkContinueAtTheEnd(loopItem.commands, currentLoop); - - //DoWhile based on precontinue - if (!loopTypeFound && (!loopItem.commands.isEmpty())) { - List> continueCommands1 = new ArrayList<>(); - getContinuesCommands(loopItem.commands, continueCommands1, currentLoop.id); - if (!continueCommands1.isEmpty() && doWhileCandidate) { - int index = ret.indexOf(loopItem); - ret.remove(index); - IfItem ifi = (IfItem) precontinueCommands.remove(precontinueCommands.size() - 1); - List exprList = new ArrayList<>(precontinueCommands); - boolean invert = false; - if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem) && (((BreakItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) - && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { - invert = true; - } - - GraphTargetItem expr = ifi.expression; - if (invert) { - expr = expr.invert(null); - } - exprList.add(expr); - ret.add(index, li = new DoWhileItem(dialect, null, expr.getLineStartItem(), currentLoop, loopItem.commands, exprList)); - loopTypeFound = true; - } - } - - //Loop with condition at the beginning (While) - if (!loopTypeFound && (!loopItem.commands.isEmpty())) { - if (loopItem.commands.get(0) instanceof IfItem) { - IfItem ifi = (IfItem) loopItem.commands.get(0); - - List bodyBranch = null; - boolean inverted = false; - boolean breakpos2 = false; - BreakItem addBreakItem = null; - ContinueItem addContinueItem = null; - if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { - BreakItem bi = (BreakItem) ifi.onTrue.get(0); - if (bi.loopId == currentLoop.id) { - bodyBranch = ifi.onFalse; - inverted = true; - } - } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { - BreakItem bi = (BreakItem) ifi.onFalse.get(0); - if (bi.loopId == currentLoop.id) { - bodyBranch = ifi.onTrue; - } - } else if (loopItem.commands.size() == 2 && (loopItem.commands.get(1) instanceof BreakItem)) { - BreakItem bi = (BreakItem) loopItem.commands.get(1); - if (ifi.onTrue.isEmpty()) { - inverted = true; - } - bodyBranch = inverted ? ifi.onFalse : ifi.onTrue; - 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 ((ifi.onTrue.size() == 1) - && (ifi.onTrue.get(0) instanceof ContinueItem) - && (((ContinueItem) ifi.onTrue.get(0)).loopId != currentLoop.id)) { - addContinueItem = (ContinueItem) ifi.onTrue.get(0); - bodyBranch = ifi.onFalse; - inverted = true; - } else if ((ifi.onFalse.size() == 1) - && (ifi.onFalse.get(0) instanceof ContinueItem) - && (((ContinueItem) ifi.onFalse.get(0)).loopId != currentLoop.id)) { - addContinueItem = (ContinueItem) ifi.onFalse.get(0); - bodyBranch = ifi.onTrue; - } else if (loopItem.commands.size() == 2 - && (loopItem.commands.get(1) instanceof ContinueItem) - && (((ContinueItem) loopItem.commands.get(1)).loopId != currentLoop.id)) { - addContinueItem = (ContinueItem) loopItem.commands.get(1); - if (ifi.onTrue.isEmpty()) { - inverted = true; - } - bodyBranch = inverted ? ifi.onFalse : ifi.onTrue; - breakpos2 = true; - } - if (bodyBranch != null) { - int index = ret.indexOf(loopItem); - ret.remove(index); - List exprList = new ArrayList<>(); - GraphTargetItem expr = ifi.expression; - if (inverted) { - if (expr instanceof LogicalOpItem) { - expr = ((LogicalOpItem) expr).invert(null); - } else { - expr = new NotItem(dialect, null, expr.getLineStartItem(), expr); - } - } - exprList.add(expr); - List commands = new ArrayList<>(); - commands.addAll(bodyBranch); - loopItem.commands.remove(0); - if (breakpos2) { - loopItem.commands.remove(0); //remove that break too - } - commands.addAll(loopItem.commands); - checkContinueAtTheEnd(commands, currentLoop); - List finalComm = new ArrayList<>(); - - //findGotoTargets - comment this out: - if (!precontinueCommands.isEmpty()) { - - List> continueCommands = new ArrayList<>(); - getContinuesCommands(commands, continueCommands, currentLoop.id); - - if (continueCommands.isEmpty()) { - commands.addAll(precontinueCommands); - precontinueCommands = new ArrayList<>(); - - //Single continue and there is break/continue/return/throw at end of the commands - } else if (!commands.isEmpty() && continueCommands.size() == 1) { - GraphTargetItem lastItem = commands.get(commands.size() - 1); - if ((lastItem instanceof BreakItem) || (lastItem instanceof ContinueItem) || (lastItem instanceof ExitItem)) { - continueCommands.get(0).addAll(continueCommands.get(0).size() - 1, precontinueCommands); - precontinueCommands = new ArrayList<>(); - } - } - - finalComm.addAll(precontinueCommands); - } - if (!finalComm.isEmpty()) { - ret.add(index, li = new ForItem(dialect, expr.getSrc(), expr.getLineStartItem(), currentLoop, new ArrayList<>(), exprList.get(exprList.size() - 1), finalComm, commands)); - } else { - ret.add(index, li = new WhileItem(dialect, expr.getSrc(), expr.getLineStartItem(), currentLoop, exprList, commands)); - } - if (addBreakItem != null) { - ret.add(index + 1, addBreakItem); - } - if (addContinueItem != null) { - ret.add(index + 1, addContinueItem); - } - - loopTypeFound = true; - } - } - } - - if (!loopTypeFound && !precontinueCommands.isEmpty()) { - loopItem.commands.addAll(precontinueCommands); - } - - //Loop with condition at the end (Do..While) - if (!loopTypeFound && (!loopItem.commands.isEmpty())) { - if (loopItem.commands.get(loopItem.commands.size() - 1) instanceof IfItem) { - IfItem ifi = (IfItem) loopItem.commands.get(loopItem.commands.size() - 1); - List bodyBranch = null; - boolean inverted = false; - if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { - BreakItem bi = (BreakItem) ifi.onTrue.get(0); - if (bi.loopId == currentLoop.id) { - bodyBranch = ifi.onFalse; - inverted = true; - } - } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { - BreakItem bi = (BreakItem) ifi.onFalse.get(0); - if (bi.loopId == currentLoop.id) { - bodyBranch = ifi.onTrue; - } - } - if (bodyBranch != null) { - //Condition at the beginning - int index = ret.indexOf(loopItem); - ret.remove(index); - List exprList = new ArrayList<>(); - GraphTargetItem expr = ifi.expression; - if (inverted) { - expr = expr.invert(null); - } - - checkContinueAtTheEnd(bodyBranch, currentLoop); - - List commands = new ArrayList<>(); - - if (!bodyBranch.isEmpty()) { - ret.add(index, loopItem); - } else { - loopItem.commands.remove(loopItem.commands.size() - 1); - commands.addAll(loopItem.commands); - commands.addAll(bodyBranch); - exprList.add(expr); - checkContinueAtTheEnd(commands, currentLoop); - ret.add(index, li = new DoWhileItem(dialect, null, exprList.get(0).getLineStartItem(), currentLoop, commands, exprList)); - } - - loopTypeFound = true; - } - } - } - - if (!loopTypeFound) { - checkContinueAtTheEnd(loopItem.commands, currentLoop); - } - currentLoop.phase = 2; - - GraphTargetItem replaced = checkLoop(ret, li, localData, loops, throwStates, sPreLoop); - if (replaced != li) { - int index = ret.indexOf(li); - ret.remove(index); - if (replaced != null) { - ret.add(index, replaced); - } - } - - if (currentLoop.loopBreak != null) { - printGraph(foundGotos, partCodes, partCodePos, visited, localData, sPreLoop, allParts, part, currentLoop.loopBreak, stopPart, stopPartKind, loops, throwStates, ret, staticOperation, path, recursionLevel + 1); - } + handleLoop(loopItem, li, currentLoop, loopTypeFound, doWhileCandidate, precontinueCommands, foundGotos, partCodes, partCodePos, visited, localData, allParts, part, stopPart, stopPartKind, loops, throwStates, ret, staticOperation, path, recursionLevel, sPreLoop); } break; } - return ret; + return originalRet; + } + + private void handleLoop(UniversalLoopItem loopItem, LoopItem li, Loop currentLoop, boolean loopTypeFound, boolean doWhileCandidate, List precontinueCommands, + List foundGotos, Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, Set allParts, GraphPart part, List stopPart, List stopPartKind, List loops, List throwStates, List ret, int staticOperation, String path, int recursionLevel, + TranslateStack sPreLoop + ) throws InterruptedException { + processIfs(loopItem.commands); + processSwitches(loopItem.commands, currentLoop.id); + processOther(loopItem.commands, currentLoop.id); + + checkContinueAtTheEnd(loopItem.commands, currentLoop); + + //DoWhile based on precontinue + if (!loopTypeFound && (!loopItem.commands.isEmpty())) { + List> continueCommands1 = new ArrayList<>(); + getContinuesCommands(loopItem.commands, continueCommands1, currentLoop.id); + if (!continueCommands1.isEmpty() && doWhileCandidate) { + int index = ret.indexOf(loopItem); + ret.remove(index); + IfItem ifi = (IfItem) precontinueCommands.remove(precontinueCommands.size() - 1); + List exprList = new ArrayList<>(precontinueCommands); + boolean invert = false; + if (((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem) && (((BreakItem) ifi.onTrue.get(0)).loopId == currentLoop.id)) + && ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof ContinueItem) && (((ContinueItem) ifi.onFalse.get(0)).loopId == currentLoop.id))) { + invert = true; + } + + GraphTargetItem expr = ifi.expression; + if (invert) { + expr = expr.invert(null); + } + exprList.add(expr); + ret.add(index, li = new DoWhileItem(dialect, null, expr.getLineStartItem(), currentLoop, loopItem.commands, exprList)); + loopTypeFound = true; + } + } + + //Loop with condition at the beginning (While) + if (!loopTypeFound && (!loopItem.commands.isEmpty())) { + if (loopItem.commands.get(0) instanceof IfItem) { + IfItem ifi = (IfItem) loopItem.commands.get(0); + + List bodyBranch = null; + boolean inverted = false; + boolean breakpos2 = false; + BreakItem addBreakItem = null; + ContinueItem addContinueItem = null; + if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onTrue.get(0); + if (bi.loopId == currentLoop.id) { + bodyBranch = ifi.onFalse; + inverted = true; + } + } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onFalse.get(0); + if (bi.loopId == currentLoop.id) { + bodyBranch = ifi.onTrue; + } + } else if (loopItem.commands.size() == 2 && (loopItem.commands.get(1) instanceof BreakItem)) { + BreakItem bi = (BreakItem) loopItem.commands.get(1); + if (ifi.onTrue.isEmpty()) { + inverted = true; + } + bodyBranch = inverted ? ifi.onFalse : ifi.onTrue; + 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 ((ifi.onTrue.size() == 1) + && (ifi.onTrue.get(0) instanceof ContinueItem) + && (((ContinueItem) ifi.onTrue.get(0)).loopId != currentLoop.id)) { + addContinueItem = (ContinueItem) ifi.onTrue.get(0); + bodyBranch = ifi.onFalse; + inverted = true; + } else if ((ifi.onFalse.size() == 1) + && (ifi.onFalse.get(0) instanceof ContinueItem) + && (((ContinueItem) ifi.onFalse.get(0)).loopId != currentLoop.id)) { + addContinueItem = (ContinueItem) ifi.onFalse.get(0); + bodyBranch = ifi.onTrue; + } else if (loopItem.commands.size() == 2 + && (loopItem.commands.get(1) instanceof ContinueItem) + && (((ContinueItem) loopItem.commands.get(1)).loopId != currentLoop.id)) { + addContinueItem = (ContinueItem) loopItem.commands.get(1); + if (ifi.onTrue.isEmpty()) { + inverted = true; + } + bodyBranch = inverted ? ifi.onFalse : ifi.onTrue; + breakpos2 = true; + } + if (bodyBranch != null) { + int index = ret.indexOf(loopItem); + ret.remove(index); + List exprList = new ArrayList<>(); + GraphTargetItem expr = ifi.expression; + if (inverted) { + if (expr instanceof LogicalOpItem) { + expr = ((LogicalOpItem) expr).invert(null); + } else { + expr = new NotItem(dialect, null, expr.getLineStartItem(), expr); + } + } + exprList.add(expr); + List commands = new ArrayList<>(); + commands.addAll(bodyBranch); + loopItem.commands.remove(0); + if (breakpos2) { + loopItem.commands.remove(0); //remove that break too + } + commands.addAll(loopItem.commands); + checkContinueAtTheEnd(commands, currentLoop); + List finalComm = new ArrayList<>(); + + //findGotoTargets - comment this out: + if (!precontinueCommands.isEmpty()) { + + List> continueCommands = new ArrayList<>(); + getContinuesCommands(commands, continueCommands, currentLoop.id); + + if (continueCommands.isEmpty()) { + commands.addAll(precontinueCommands); + precontinueCommands = new ArrayList<>(); + + //Single continue and there is break/continue/return/throw at end of the commands + } else if (!commands.isEmpty() && continueCommands.size() == 1) { + GraphTargetItem lastItem = commands.get(commands.size() - 1); + if ((lastItem instanceof BreakItem) || (lastItem instanceof ContinueItem) || (lastItem instanceof ExitItem)) { + continueCommands.get(0).addAll(continueCommands.get(0).size() - 1, precontinueCommands); + precontinueCommands = new ArrayList<>(); + } + } + + finalComm.addAll(precontinueCommands); + } + if (!finalComm.isEmpty()) { + ret.add(index, li = new ForItem(dialect, expr.getSrc(), expr.getLineStartItem(), currentLoop, new ArrayList<>(), exprList.get(exprList.size() - 1), finalComm, commands)); + } else { + ret.add(index, li = new WhileItem(dialect, expr.getSrc(), expr.getLineStartItem(), currentLoop, exprList, commands)); + } + if (addBreakItem != null) { + ret.add(index + 1, addBreakItem); + } + if (addContinueItem != null) { + ret.add(index + 1, addContinueItem); + } + + loopTypeFound = true; + } + } + } + + if (!loopTypeFound && !precontinueCommands.isEmpty()) { + loopItem.commands.addAll(precontinueCommands); + } + + //Loop with condition at the end (Do..While) + if (!loopTypeFound && (!loopItem.commands.isEmpty())) { + if (loopItem.commands.get(loopItem.commands.size() - 1) instanceof IfItem) { + IfItem ifi = (IfItem) loopItem.commands.get(loopItem.commands.size() - 1); + List bodyBranch = null; + boolean inverted = false; + if ((ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onTrue.get(0); + if (bi.loopId == currentLoop.id) { + bodyBranch = ifi.onFalse; + inverted = true; + } + } else if ((ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) { + BreakItem bi = (BreakItem) ifi.onFalse.get(0); + if (bi.loopId == currentLoop.id) { + bodyBranch = ifi.onTrue; + } + } + if (bodyBranch != null) { + //Condition at the beginning + int index = ret.indexOf(loopItem); + if (index > -1) { + ret.remove(index); + List exprList = new ArrayList<>(); + GraphTargetItem expr = ifi.expression; + if (inverted) { + expr = expr.invert(null); + } + + checkContinueAtTheEnd(bodyBranch, currentLoop); + + List commands = new ArrayList<>(); + + if (!bodyBranch.isEmpty()) { + ret.add(index, loopItem); + } else { + loopItem.commands.remove(loopItem.commands.size() - 1); + commands.addAll(loopItem.commands); + commands.addAll(bodyBranch); + exprList.add(expr); + checkContinueAtTheEnd(commands, currentLoop); + ret.add(index, li = new DoWhileItem(dialect, null, exprList.get(0).getLineStartItem(), currentLoop, commands, exprList)); + } + + loopTypeFound = true; + } + } + } + } + + if (!loopTypeFound) { + checkContinueAtTheEnd(loopItem.commands, currentLoop); + } + currentLoop.phase = 2; + + GraphTargetItem replaced = checkLoop(ret, li, localData, loops, throwStates, sPreLoop); + if (replaced != li) { + int index = ret.indexOf(li); + ret.remove(index); + if (replaced != null) { + ret.add(index, replaced); + } + } + + if (currentLoop.loopBreak != null) { + printGraph(foundGotos, partCodes, partCodePos, visited, localData, sPreLoop, allParts, part, currentLoop.loopBreak, stopPart, stopPartKind, loops, throwStates, ret, staticOperation, path, recursionLevel + 1); + } } /** @@ -4798,7 +4906,7 @@ public class Graph { if (willHaveBreak) { if (!currentCaseCommands.isEmpty()) { GraphTargetItem last = currentCaseCommands.get(currentCaseCommands.size() - 1); - if (!(last instanceof ContinueItem) && !(last instanceof BreakItem) && !(last instanceof GotoItem) && !(last instanceof ExitItem) && !(last instanceof ScriptEndItem)) { + if (!(last instanceof ContinueItem) && !(last instanceof BreakItem) && !(last instanceof GotoItem) && !(last instanceof ExitItem) && !(last instanceof ScriptEndItem)) { currentCaseCommands.add(new BreakItem(dialect, null, localData.lineStartInstruction, currentLoop.id)); } } 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 d7ee60afc..7c6607f54 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript2Test.java @@ -2608,5 +2608,44 @@ public class ActionScript2Test extends ActionScript2TestBase { + "trace(\"breakDetectionTest\");\r\n" ); } + + @Test + public void frame96_doWhileTwiceTest() { + compareSrc(96, "trace(\"doWhileTwiceTest\");\r\n" + + "var a = 1;\r\n" + + "var b = 2;\r\n" + + "while(true)\r\n" + + "{\r\n" + + "while(true)\r\n" + + "{\r\n" + + "if(a)\r\n" + + "{\r\n" + + "trace(\"x\");\r\n" + + "if(b)\r\n" + + "{\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"y\");\r\n" + + "}\r\n" + + "trace(\"z\");\r\n" + + "if(false)\r\n" + + "{\r\n" + + "break;\r\n" + + "}\r\n" + + "}\r\n" + + "trace(\"g\");\r\n" + + "if(b)\r\n" + + "{\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"h\");\r\n" + + "if(false)\r\n" + + "{\r\n" + + "break;\r\n" + + "}\r\n" + + "}\r\n" + + "trace(\"finish\");\r\n" + ); + } //--FRAMES-END-- } 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 b98d6974c..d99fd1a7f 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 @@ -40,28 +40,23 @@ public class ActionScript3AssembledDecompileTest extends ActionScript3DecompileT @Test public void testAlwaysBreak() { - decompileMethod("assembled", "testAlwaysBreak", "if(true)\r\n" - + "{\r\n" - + "var v:* = 5;\r\n" + decompileMethod("assembled", "testAlwaysBreak", "var v:* = 5;\r\n" + "trace(\"a\");\r\n" + + "while(true)\r\n" + + "{\r\n" + "if(v > 4)\r\n" + "{\r\n" + "trace(\"b\");\r\n" + "if(v > 10)\r\n" + "{\r\n" + "trace(\"c\");\r\n" + + "break;\r\n" + "}\r\n" - + "else\r\n" - + "{\r\n" + "trace(\"d\");\r\n" - + "addr003e:\r\n" + + "}\r\n" + "trace(\"e\");\r\n" + + "break;\r\n" + "}\r\n" - + "§§goto(addr004e);\r\n" - + "}\r\n" - + "§§goto(addr003e);\r\n" - + "}\r\n" - + "addr004e:\r\n" + "trace(\"f\");\r\n", false); } @@ -70,23 +65,22 @@ public class ActionScript3AssembledDecompileTest extends ActionScript3DecompileT public void testAlwaysBreak2() { decompileMethod("assembled", "testAlwaysBreak2", "var v:* = 5;\r\n" + "trace(\"a\");\r\n" + + "while(true)\r\n" + + "{\r\n" + "if(v > 4)\r\n" + "{\r\n" + "trace(\"b\");\r\n" + "if(v > 10)\r\n" + "{\r\n" + "trace(\"c\");\r\n" + + "break;\r\n" + "}\r\n" - + "else\r\n" - + "{\r\n" + "trace(\"d\");\r\n" - + "addr003e:\r\n" + + "}\r\n" + "trace(\"e\");\r\n" + + "break;\r\n" + "}\r\n" - + "trace(\"f\");\r\n" - + "return;\r\n" - + "}\r\n" - + "§§goto(addr003e);\r\n", + + "trace(\"f\");\r\n", false); } @@ -206,56 +200,66 @@ public class ActionScript3AssembledDecompileTest extends ActionScript3DecompileT @Test public void testGoto() { decompileMethod("assembled", "testGoto", "var v:* = 5;\r\n" + + "loop0:\r\n" + + "while(true)\r\n" + + "{\r\n" + + "while(true)\r\n" + + "{\r\n" + "if(v > 1)\r\n" + "{\r\n" + "trace(\"a\");\r\n" + "if(v > 2)\r\n" + "{\r\n" + "trace(\"goto\");\r\n" - + "addr0052:\r\n" - + "trace(\"f\");\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"b\");\r\n" + "}\r\n" + "else\r\n" + "{\r\n" - + "trace(\"b\");\r\n" - + "addr003d:\r\n" + + "trace(\"c\");\r\n" + + "}\r\n" + "trace(\"d\");\r\n" + "if(v > 3)\r\n" + "{\r\n" + "trace(\"e\");\r\n" - + "§§goto(addr0052);\r\n" + + "break;\r\n" + "}\r\n" - + "else\r\n" - + "{\r\n" + "trace(\"g\");\r\n" + + "break loop0;\r\n" + "}\r\n" + + "trace(\"f\");\r\n" + + "break;\r\n" + "}\r\n" - + "trace(\"end\");\r\n" - + "return;\r\n" - + "}\r\n" - + "trace(\"c\");\r\n" - + "§§goto(addr003d);\r\n", + + "trace(\"end\");\r\n", false); } @Test public void testGoto2() { decompileMethod("assembled", "testGoto2", "var v:* = 5;\r\n" + + "loop0:\r\n" + + "while(true)\r\n" + + "{\r\n" + + "loop1:\r\n" + + "while(true)\r\n" + + "{\r\n" + + "while(true)\r\n" + + "{\r\n" + "if(v > 1)\r\n" + "{\r\n" + "trace(\"a\");\r\n" + "if(v > 2)\r\n" + "{\r\n" + "trace(\"goto\");\r\n" - + "addr0062:\r\n" - + "trace(\"g\");\r\n" - + "addr0069:\r\n" - + "trace(\"h\");\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"b\");\r\n" + "}\r\n" + "else\r\n" + "{\r\n" - + "trace(\"b\");\r\n" - + "addr003d:\r\n" + + "trace(\"c\");\r\n" + + "}\r\n" + "trace(\"d\");\r\n" + "if(v > 3)\r\n" + "{\r\n" @@ -263,20 +267,20 @@ public class ActionScript3AssembledDecompileTest extends ActionScript3DecompileT + "if(b > 5)\r\n" + "{\r\n" + "trace(\"f\");\r\n" - + "§§goto(addr0062);\r\n" + + "break;\r\n" + "}\r\n" - + "§§goto(addr0069);\r\n" + + "break loop1;\r\n" + "}\r\n" - + "else\r\n" - + "{\r\n" + "trace(\"i\");\r\n" + + "break loop0;\r\n" + "}\r\n" + + "trace(\"g\");\r\n" + + "break;\r\n" + "}\r\n" - + "trace(\"end\");\r\n" - + "return;\r\n" + + "trace(\"h\");\r\n" + + "break;\r\n" + "}\r\n" - + "trace(\"c\");\r\n" - + "§§goto(addr003d);\r\n", + + "trace(\"end\");\r\n", false); } 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 4206171be..4a1794344 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 @@ -45,6 +45,30 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testAlwaysBreak() { + decompileMethod("classic_air", "testAlwaysBreak", "var v:* = undefined;\r\n" + + "v = 5;\r\n" + + "trace(\"a\");\r\n" + + "while(true)\r\n" + + "{\r\n" + + "if(v > 4)\r\n" + + "{\r\n" + + "trace(\"b\");\r\n" + + "if(v > 10)\r\n" + + "{\r\n" + + "trace(\"c\");\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"d\");\r\n" + + "}\r\n" + + "trace(\"e\");\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"f\");\r\n", + false); + } + @Test public void testAndOrCoercion() { decompileMethod("classic_air", "testAndOrCoercion", "var x:TestInterface = ti || (ti = new TestClass()) && (ti = new TestClass());\r\n" @@ -552,6 +576,36 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile false); } + @Test + public void testDoWhileTwice() { + decompileMethod("classic_air", "testDoWhileTwice", "var a:int = 1;\r\n" + + "var b:int = 2;\r\n" + + "while(true)\r\n" + + "{\r\n" + + "while(true)\r\n" + + "{\r\n" + + "if(a)\r\n" + + "{\r\n" + + "trace(\"x\");\r\n" + + "if(b)\r\n" + + "{\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"y\");\r\n" + + "}\r\n" + + "trace(\"z\");\r\n" + + "}\r\n" + + "trace(\"g\");\r\n" + + "if(b)\r\n" + + "{\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"h\");\r\n" + + "}\r\n" + + "trace(\"finish\");\r\n", + false); + } + @Test public void testDotParent() { decompileMethod("classic_air", "testDotParent", "var d:* = new TestClass1();\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 e0d5b1f83..b5a44826d 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 @@ -45,6 +45,30 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testAlwaysBreak() { + decompileMethod("classic", "testAlwaysBreak", "var v:* = undefined;\r\n" + + "v = 5;\r\n" + + "trace(\"a\");\r\n" + + "while(true)\r\n" + + "{\r\n" + + "if(v > 4)\r\n" + + "{\r\n" + + "trace(\"b\");\r\n" + + "if(v > 10)\r\n" + + "{\r\n" + + "trace(\"c\");\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"d\");\r\n" + + "}\r\n" + + "trace(\"e\");\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"f\");\r\n", + false); + } + @Test public void testAndOrCoercion() { decompileMethod("classic", "testAndOrCoercion", "var x:TestInterface = this.ti || (this.ti = new TestClass()) && (this.ti = new TestClass());\r\n" @@ -553,6 +577,39 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testDoWhileTwice() { + decompileMethod("classic", "testDoWhileTwice", "var a:* = 1;\r\n" + + "var b:* = 2;\r\n" + + "do\r\n" + + "{\r\n" + + "do\r\n" + + "{\r\n" + + "if(a)\r\n" + + "{\r\n" + + "trace(\"x\");\r\n" + + "if(b)\r\n" + + "{\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"y\");\r\n" + + "}\r\n" + + "trace(\"z\");\r\n" + + "}\r\n" + + "while(true);\r\n" + + "\r\n" + + "trace(\"g\");\r\n" + + "if(b)\r\n" + + "{\r\n" + + "break;\r\n" + + "}\r\n" + + "trace(\"h\");\r\n" + + "}\r\n" + + "while(true);\r\n" + + "trace(\"finish\");\r\n", + false); + } + @Test public void testDotParent() { decompileMethod("classic", "testDotParent", "var d:* = undefined;\r\n" diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/generators/AS3Generator.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/generators/AS3Generator.java index 6313203ff..1b9a49f66 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/generators/AS3Generator.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/generators/AS3Generator.java @@ -66,7 +66,7 @@ public class AS3Generator { sortedPacks.put(pack.getClassPath().toRawString(), pack); } s.append("/*\r\n" - + " * Copyright (C) 2010-2025 JPEXS, All rights reserved.\r\n" + + " * Copyright (C) 2010-2026 JPEXS, All rights reserved.\r\n" + " * \r\n" + " * This library is free software; you can redistribute it and/or\r\n" + " * modify it under the terms of the GNU Lesser General Public\r\n" diff --git a/libsrc/ffdec_lib/testdata/as2/as2.swf b/libsrc/ffdec_lib/testdata/as2/as2.swf index 40da173c5..07ed7760f 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 102e0f73b..bedf9fb84 100644 --- a/libsrc/ffdec_lib/testdata/as2/as2/DOMDocument.xml +++ b/libsrc/ffdec_lib/testdata/as2/as2/DOMDocument.xml @@ -1,4 +1,4 @@ - + @@ -31,7 +31,7 @@ - + @@ -771,9 +771,9 @@ +!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!3156 7501[3091 7490 3054 7490!3054 7490[3050 7494 3050 7457!3050 7457[3050 7441 3159 7239"/> + + + + + @@ -3536,6 +3563,7 @@ function myFunction(item) + @@ -3555,6 +3583,5 @@ function myFunction(item) - \ 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 f6fdb389f..ec750720c 100644 --- a/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/NineSlice.xml +++ b/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/NineSlice.xml @@ -37,24 +37,27 @@ + - - - - - - - - + + + + - + + + + diff --git a/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/blue.xml b/libsrc/ffdec_lib/testdata/as2/as2/LIBRARY/blue.xml index 06719efea..f59b98d61 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 0af71eba0..40944e265 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-20T18:25:35-08:00 - 2026-01-20T18:25:35-08:00 + 2026-01-25T03:31:05-08:00 + 2026-01-25T03:31:05-08:00 @@ -22,7 +22,7 @@ xmp.did:8DD71700DC9EDF1194ADAC9B23608190 xmp.did:F0EB4FF7CAC3ED11AC9DC078F41E1AA7 - xmp.iid:D83BD77770F6F01193CE8B873E4F6ED7 + xmp.iid:54232456E1F9F01185C4F57BEF746DBF xmp.did:8DD71700DC9EDF1194ADAC9B23608190 @@ -512,6 +512,12 @@ 2010-08-03T10:48:58+02:00 Adobe Flash Professional CS6 - build 481 + + created + xmp.iid:54232456E1F9F01185C4F57BEF746DBF + 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 3a2619008..45ffbe129 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 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 e4f4252a5..4bb55f0fb 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 369e9f05a..88a889f3a 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/src/Main.as b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as index 71b0045df..e1a355ba4 100644 --- a/libsrc/ffdec_lib/testdata/as3_new/src/Main.as +++ b/libsrc/ffdec_lib/testdata/as3_new/src/Main.as @@ -17,6 +17,7 @@ package public class Main extends Sprite { TestActivationArguments; + TestAlwaysBreak; TestAndOrCoercion; TestArguments; TestBitwiseOperands; @@ -45,6 +46,7 @@ package TestDoWhile2; TestDoWhile3; TestDoWhile4; + TestDoWhileTwice; TestExecutionOrder; TestExpressions; TestFinallyZeroJump; diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestAlwaysBreak.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestAlwaysBreak.as new file mode 100644 index 000000000..c637e9cd8 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestAlwaysBreak.as @@ -0,0 +1,31 @@ +package tests +{ + + public class TestAlwaysBreak + { + public function run():* + { + while(true) + { + var v = 5; + trace("a"); + if(v > 4) + { + trace("b"); + if(v > 10) + { + trace("c"); + break; //standard "break", should lead to "f" + } + else + { + trace("d"); + } + } + trace("e"); + break; //"always break loop" + } + trace("f"); + } + } +} diff --git a/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestDoWhileTwice.as b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestDoWhileTwice.as new file mode 100644 index 000000000..c0b583e0c --- /dev/null +++ b/libsrc/ffdec_lib/testdata/as3_new/src/tests/TestDoWhileTwice.as @@ -0,0 +1,30 @@ +package tests +{ + + public class TestDoWhileTwice + { + public function run():* + { + var a = 1; + var b = 2; + do { + do { + if (a) { + trace("x"); + if (b) { + break; + } + trace("y"); + } + trace("z"); + }while(true); + trace("g"); + if (b) { + break; + } + trace("h"); + }while(true); + trace("finish"); + } + } +}