From 7d18834c819c70aa0b229ef156d28780b96be5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 14 Mar 2026 17:29:08 +0100 Subject: [PATCH] feat!: redesigned loop detector (#2542) Instead of walking code structures to get loops, the loops are populated by new faster algorithm. Also, we do not join adjacent GraphParts anymore in non-obfuscated code. For proper switch handling, the code is decompiled in two passes everytime (Previously, the second pass was used only sometimes). In first pass we do not process ifs as it may break switch detection. Second pass is executed after we know the switches position. Fixes #2542 --- .../jpexs/decompiler/flash/BaseLocalData.java | 14 +- .../src/com/jpexs/decompiler/flash/SWF.java | 2 +- .../decompiler/flash/abc/AVM2LocalData.java | 2 + .../flash/abc/avm2/graph/AVM2Graph.java | 44 +- .../jpexs/decompiler/flash/action/Action.java | 2 +- .../action/ActionClassHeaderCrippler.java | 2 +- .../decompiler/flash/action/ActionGraph.java | 137 +-- .../flash/action/ActionLocalData.java | 14 +- .../decompiler/graph/BasicPrevNextWalker.java | 37 + .../src/com/jpexs/decompiler/graph/Graph.java | 224 ++--- .../com/jpexs/decompiler/graph/GraphPart.java | 1 + .../graph/IgnoreEdgesPrevNextWalker.java | 45 + .../src/com/jpexs/decompiler/graph/Loop.java | 22 +- .../jpexs/decompiler/graph/LoopDetector.java | 899 ++++++++++++++++++ .../decompiler/graph/PrevNextWalker.java | 30 + .../decompiler/graph/SecondPassData.java | 14 +- .../decompiler/graph/ThrowPrevNextWalker.java | 75 ++ .../GraphPrecontinueDetector.java | 4 +- .../decompiler/flash/gui/FlashPlayerTest.java | 2 +- 19 files changed, 1378 insertions(+), 192 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/BasicPrevNextWalker.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/IgnoreEdgesPrevNextWalker.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/LoopDetector.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/PrevNextWalker.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/ThrowPrevNextWalker.java 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 b6fbd87d0..6924908a9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BaseLocalData.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/BaseLocalData.java @@ -20,8 +20,10 @@ import com.jpexs.decompiler.graph.GraphPart; import com.jpexs.decompiler.graph.GraphSourceItem; import com.jpexs.decompiler.graph.SecondPassData; import com.jpexs.helpers.Reference; +import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; /** @@ -59,7 +61,17 @@ public abstract class BaseLocalData { /** * Whether goto statements were used */ - public Reference gotosUsed = new Reference<>(false); + public Reference gotosUsed = new Reference<>(false); + + /** + * Switch cases + */ + public List> switchCases = new ArrayList<>(); + + /** + * Switch breaks + */ + public List switchBreaks = new ArrayList<>(); /** * Constructor. 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 c1e240dbd..fadc7beb8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -3953,7 +3953,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @throws InterruptedException On interrupt */ private static void getVariables(SWF swf, boolean insideDoInitAction, List> variables, List functions, HashMap strings, HashMap usageTypes, ActionGraphSource code, long addr, String path) throws InterruptedException { - ActionLocalData localData = new ActionLocalData(null, insideDoInitAction, new HashMap<>() /*??*/, new LinkedHashSet<>()); + ActionLocalData localData = new ActionLocalData(null, insideDoInitAction, new HashMap<>() /*??*/, new LinkedHashSet<>(), new ArrayList<>(), new ArrayList<>()); getVariables(swf, null, localData, new TranslateStack(path), new ArrayList<>(), code, code.adr2pos(addr), variables, functions, strings, new ArrayList<>(), usageTypes, path); } 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 5888256c4..db2f6da23 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 @@ -300,6 +300,8 @@ public class AVM2LocalData extends BaseLocalData { maxTempIndex = localData.maxTempIndex; gotosUsed = localData.gotosUsed; secondPassData = localData.secondPassData; + switchCases = localData.switchCases; + switchBreaks = localData.switchBreaks; } /** 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 e58c8f24f..ddda3178b 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 @@ -131,6 +131,7 @@ import com.jpexs.decompiler.graph.model.TrueItem; import com.jpexs.decompiler.graph.model.WhileItem; import com.jpexs.helpers.Reference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -1692,7 +1693,7 @@ public class AVM2Graph extends Graph { } return true; } - + @Override protected boolean checkPartOutput(Reference hasEmptyStackPops, List currentRet, List foundGotos, Map> partCodes, Map partCodePos, @@ -1707,7 +1708,7 @@ public class AVM2Graph extends Graph { AVM2LocalData aLocalData = (AVM2LocalData) localData; return checkTry(hasEmptyStackPops, currentRet, foundGotos, partCodes, partCodePos, visited, aLocalData, part, stopPart, stopPartKind, loops, throwStates, allParts, stack, staticOperation, path, recursionLevel); } - + @Override protected List check(Reference hasEmptyStackPops, List currentRet, List foundGotos, Map> partCodes, Map partCodePos, @@ -1760,14 +1761,14 @@ public class AVM2Graph extends Graph { Set ignoredParts = new LinkedHashSet<>(); for (Loop el : loops) { ignoredParts.add(el.loopContinue); - } + } while (true) { List out = new ArrayList<>(); - + if (ignoredParts.contains(part)) { break; } - + //Special: In Flex (not air) there are these blocks sometimes: // if(false) { // §§push(5); @@ -1780,16 +1781,20 @@ public class AVM2Graph extends Graph { if (part.nextParts.size() == branchCount && part.nextParts.get(branchNum).getHeight() == 2 && ((AVM2Instruction) code.get(part.nextParts.get(branchNum).start >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).start)).definition instanceof NopIns - && ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof JumpIns) { + && ((AVM2Instruction) getLastPartSourceItem(part.nextParts.get(branchNum))).definition instanceof JumpIns) { part = part.nextParts.get(branchNum); branchNum = 0; branchCount = 1; } else if (part.nextParts.size() == branchCount - && part.nextParts.get(branchNum).getHeight() > 1 - && ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof IfStrictEqIns + //&& part.nextParts.get(branchNum).getHeight() > 1 + && ((AVM2Instruction) getLastPartSourceItem(part.nextParts.get(branchNum))).definition instanceof IfStrictEqIns && ((top = translatePartGetStack(localData, part.nextParts.get(branchNum), stack, staticOperation, out)) instanceof StrictEqAVM2Item)) { cnt++; part = part.nextParts.get(branchNum); + + while (part.nextParts.size() == 1 && part.nextParts.get(0).refs.size() == 1) { + part = part.nextParts.get(0); + } caseBodyParts.add(part.nextParts.get(0)); branchNum = 1; @@ -1800,11 +1805,17 @@ public class AVM2Graph extends Graph { caseValuesMapRight.add(set.rightSide); branchCount = 2; } else if (part.nextParts.size() == branchCount - && part.nextParts.get(branchNum).getHeight() > 1 - && ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof IfStrictNeIns + //&& part.nextParts.get(branchNum).getHeight() > 1 + && ((AVM2Instruction) getLastPartSourceItem(part.nextParts.get(branchNum))).definition instanceof IfStrictNeIns && ((top = translatePartGetStack(localData, part.nextParts.get(branchNum), stack, staticOperation, out)) instanceof StrictNeqAVM2Item)) { + cnt++; part = part.nextParts.get(branchNum); + + while (part.nextParts.size() == 1 && part.nextParts.get(0).refs.size() == 1) { + part = part.nextParts.get(0); + } + caseBodyParts.add(part.nextParts.get(1)); branchNum = 0; @@ -1822,18 +1833,18 @@ public class AVM2Graph extends Graph { //ignore } List caseValuesMap = caseValuesMapLeft; - + //It's not switch, it's an If if (caseBodyParts.size() == 2) { boolean isIf = false; - for (GraphPart r : part.refs) { + for (GraphPart r : part.refs) { if (r != origPart && !origPart.leadsTo(localData, this, code, r, loops, throwStates, false)) { isIf = true; break; } } if (!isIf) { - for (GraphPart r : caseBodyParts.get(1).refs) { + for (GraphPart r : caseBodyParts.get(1).refs) { if (r != origPart && !origPart.leadsTo(localData, this, code, r, loops, throwStates, false)) { isIf = true; break; @@ -1845,7 +1856,7 @@ public class AVM2Graph extends Graph { return ret; } } - + if (caseBodyParts.size() < 2) { stack.push(firstSet); return ret; @@ -2087,7 +2098,7 @@ public class AVM2Graph extends Graph { */ @Override protected GraphTargetItem checkLoop(List output, LoopItem loopItem, BaseLocalData localData, List loops, List throwStates, TranslateStack stack) { - if (debugDoNotProcess) { + if (doNotProcessIfs) { return loopItem; } AVM2LocalData aLocalData = (AVM2LocalData) localData; @@ -2553,7 +2564,7 @@ public class AVM2Graph extends Graph { @Override protected void finalProcess(GraphTargetItem parent, List list, int level, FinalProcessLocalData localData, String path) throws InterruptedException { - if (debugDoNotProcess) { + if (doNotProcessIfs) { return; } @@ -3321,6 +3332,7 @@ public class AVM2Graph extends Graph { //Search all parts which have same or greater scope level, these all belong to catch Set catchParts = new HashSet<>(); + Reference partAfter = new Reference<>(null); if (scopePos > -1) { walkCatchParts(avm2LocalData.codeStats, part, ip, catchParts, scopePos, allParts, body.exceptions[e].isFinally()); } else { 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 66c5662ec..8753dd626 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 @@ -1256,7 +1256,7 @@ public abstract class Action implements GraphSourceItem { 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(secondPassData, insideDoInitAction, registerNames, variables, functions, graph.getUninitializedClassTraits(), usedDeobfuscations); + ActionLocalData localData = new ActionLocalData(secondPassData, insideDoInitAction, registerNames, variables, functions, graph.getUninitializedClassTraits(), usedDeobfuscations, new ArrayList<>(), new ArrayList<>()); localData.lineStartAction = lineStartActionRef.getVal(); int ip = start; boolean isWhile = false; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionClassHeaderCrippler.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionClassHeaderCrippler.java index 90422a419..25bb27c06 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionClassHeaderCrippler.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionClassHeaderCrippler.java @@ -54,7 +54,7 @@ public class ActionClassHeaderCrippler extends SWFDecompilerAdapter { public void actionListParsed(ActionList actions, SWF swf) throws InterruptedException { FastActionList list = new FastActionList(actions); int ip = 0; - BaseLocalData ld = new ActionLocalData(null, true, new HashMap<>(), new LinkedHashSet<>()); + BaseLocalData ld = new ActionLocalData(null, true, new HashMap<>(), new LinkedHashSet<>(), new ArrayList<>(), new ArrayList<>()); TranslateStack stack = new TranslateStack(""); List output = new ArrayList<>(); FastActionListIterator iterator = list.iterator(); 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 5b663c4a8..ba6ae4960 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 @@ -70,6 +70,7 @@ import com.jpexs.decompiler.graph.TranslateStack; import com.jpexs.decompiler.graph.model.BinaryOpItem; import com.jpexs.decompiler.graph.model.BreakItem; import com.jpexs.decompiler.graph.model.CommentItem; +import com.jpexs.decompiler.graph.model.DoWhileItem; import com.jpexs.decompiler.graph.model.GotoItem; import com.jpexs.decompiler.graph.model.IfItem; import com.jpexs.decompiler.graph.model.LabelItem; @@ -80,6 +81,7 @@ import com.jpexs.decompiler.graph.model.SetTemporaryItem; import com.jpexs.decompiler.graph.model.SwitchItem; import com.jpexs.decompiler.graph.model.TemporaryItem; import com.jpexs.decompiler.graph.model.TrueItem; +import com.jpexs.decompiler.graph.model.UniversalLoopItem; import com.jpexs.decompiler.graph.model.WhileItem; import com.jpexs.helpers.Helper; import com.jpexs.helpers.LinkedIdentityHashSet; @@ -91,6 +93,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; /** * ActionScript 1/2 graph @@ -108,7 +112,7 @@ public class ActionGraph extends Graph { * Inside function */ private boolean insideFunction; - + /** * Needs uninitialized class fields detection */ @@ -119,11 +123,12 @@ public class ActionGraph extends Graph { * trait */ private Map> uninitializedClassTraits; - + /** * Constructs ActionGraph * - * @param needsUninitializedClassFieldsDetection Needs uninitialized class fields detection + * @param needsUninitializedClassFieldsDetection Needs uninitialized class + * fields detection * @param uninitializedClassTraits Uninitialized class traits * @param path Path * @param insideDoInitAction Inside DoInitAction @@ -147,8 +152,7 @@ public class ActionGraph extends Graph { public boolean doesNeedUninitializedClassFieldsDetection() { return needsUninitializedClassFieldsDetection; } - - + /** * Get uninitialized class traits * @@ -168,7 +172,7 @@ public class ActionGraph extends Graph { public ActionGraphSource getGraphCode() { return (ActionGraphSource) code; } - + @Override public LinkedHashMap getSubGraphs() { LinkedHashMap subgraphs = new LinkedHashMap<>(); @@ -229,7 +233,8 @@ public class ActionGraph extends Graph { * Translates via Graph - decompiles. * * @param usedDeobfuscations Used deobfuscations - * @param needsUninitializedClassFieldsDetection Needs uninitialized class fields detection + * @param needsUninitializedClassFieldsDetection Needs uninitialized class + * fields detection * @param uninitializedClassTraits Uninitialized class traits * @param secondPassData Second pass data * @param insideDoInitAction Inside DoInitAction @@ -245,13 +250,13 @@ public class ActionGraph extends Graph { * @return List of graph target items * @throws InterruptedException On interrupt */ - public static List translateViaGraph(Set usedDeobfuscations, boolean needsUninitializedClassFieldsDetection, Map> uninitializedClassTraits, SecondPassData secondPassData, boolean insideDoInitAction, boolean insideFunction, HashMap registerNames, HashMap variables, HashMap functions, List code, int version, int staticOperation, String path, String charset, int startIp) throws InterruptedException { + public static List translateViaGraph(Set usedDeobfuscations, boolean needsUninitializedClassFieldsDetection, Map> uninitializedClassTraits, SecondPassData secondPassData, boolean insideDoInitAction, boolean insideFunction, HashMap registerNames, HashMap variables, HashMap functions, List code, int version, int staticOperation, String path, String charset, int startIp) throws InterruptedException { ActionGraph g = new ActionGraph(needsUninitializedClassFieldsDetection, uninitializedClassTraits, path, insideDoInitAction, insideFunction, code, registerNames, variables, functions, version, charset, startIp); - ActionLocalData localData = new ActionLocalData(secondPassData, insideDoInitAction, registerNames, uninitializedClassTraits, usedDeobfuscations); + ActionLocalData localData = new ActionLocalData(secondPassData, insideDoInitAction, registerNames, uninitializedClassTraits, usedDeobfuscations, new ArrayList<>(), new ArrayList<>()); g.init(localData); return g.translate(localData, staticOperation, path); } - + /** * Final process stack. Override this method to provide custom behavior. * @@ -260,7 +265,7 @@ public class ActionGraph extends Graph { * @param path Path */ @Override - public void finalProcessStack(TranslateStack stack, List output, String path) { + public void finalProcessStack(TranslateStack stack, List output, String path) { } /** @@ -289,11 +294,9 @@ public class ActionGraph extends Graph { return !isSwitch; } - - @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--) { @@ -307,8 +310,8 @@ public class ActionGraph extends Graph { } } list.addAll(0, removed); - } - + } + int targetStart; int targetEnd; GraphTargetItem targetStartItem = null; @@ -332,10 +335,10 @@ public class ActionGraph extends Graph { continue; } } - + if (pi.value instanceof FunctionActionItem) { list.set(t, pi.value); - } + } } if (it instanceof SetTemporaryItem) { SetTemporaryItem st = (SetTemporaryItem) it; @@ -552,7 +555,7 @@ public class ActionGraph extends Graph { //sti.getObject() list.remove(t - 1); t--; - + if (comparisonOp.leftSide.value instanceof TemporaryItem) { TemporaryItem ti = (TemporaryItem) comparisonOp.leftSide.value; if (t > 0) { @@ -565,7 +568,7 @@ public class ActionGraph extends Graph { } } } - + continue; } } @@ -594,8 +597,7 @@ public class ActionGraph extends Graph { } } } - - + for (int t = 0; t < list.size(); t++) { GraphTargetItem it = list.get(t); if (it instanceof PushItem) { @@ -606,7 +608,7 @@ public class ActionGraph extends Graph { t--; } } - + //Handle for loops at the end: super.finalProcess(parent, list, level, localData, path); @@ -684,7 +686,7 @@ public class ActionGraph extends Graph { } } ActionLocalData ald = (ActionLocalData) localData; - + makeDefineRegistersUp(ret, new HashSet<>(ald.regNames.keySet())); return ret; } @@ -739,14 +741,14 @@ public class ActionGraph extends Graph { if (item instanceof StoreRegisterActionItem) { StoreRegisterActionItem sr = (StoreRegisterActionItem) item; sr.define = !definedRegisters.contains(sr.register.number); - definedRegisters.add(sr.register.number); + definedRegisters.add(sr.register.number); if (sr.define && sr != ti) { list.add(ri.getVal(), new StoreRegisterActionItem(null, null, sr.register, new DirectValueActionItem(Undefined.INSTANCE), true)); - sr.define = false; + sr.define = false; ri.setVal(ri.getVal() + 1); } } - + if (item instanceof FunctionActionItem) { return false; } @@ -756,13 +758,13 @@ public class ActionGraph extends Graph { return true; } }; - + if (ti instanceof StoreRegisterActionItem) { StoreRegisterActionItem sr = (StoreRegisterActionItem) ti; sr.define = !definedRegisters.contains(sr.register.number); definedRegisters.add(sr.register.number); } - + ti.visitNoBlock(visitor); //visitor.visit(ti); //ti.visitRecursively(visitor); @@ -772,16 +774,15 @@ public class ActionGraph extends Graph { for (List items : b.getSubs()) { makeDefineRegistersUp(items, definedRegisters); } - - + for (List items : b.getSubs()) { for (int j = 0; j < items.size(); j++) { - GraphTargetItem item = items.get(j); + GraphTargetItem item = items.get(j); if (item instanceof StoreRegisterActionItem) { StoreRegisterActionItem sr = (StoreRegisterActionItem) item; if (sr.define) { list.add(ri.getVal(), new StoreRegisterActionItem(null, null, sr.register, new DirectValueActionItem(Undefined.INSTANCE), true)); - sr.define = false; + sr.define = false; if ((sr.value instanceof DirectValueActionItem) && (((DirectValueActionItem) sr.value).value == Undefined.INSTANCE)) { items.remove(j); j--; @@ -945,7 +946,7 @@ public class ActionGraph extends Graph { } return ret; } - + private int ipAfterJumps(int nip) { while (nip < code.size() && code.get(nip) instanceof ActionJump) { ActionJump j = (ActionJump) code.get(nip); @@ -953,11 +954,11 @@ public class ActionGraph extends Graph { if (nip2 == nip) { break; } - nip = nip2; + nip = nip2; } return nip; } - + /** * Checks IP and allows to modify it. * @@ -966,13 +967,13 @@ public class ActionGraph extends Graph { */ @Override protected int checkIp(int ip) { - + if (ip >= code.size()) { return ip; } - - int oldIp = ip; - + + int oldIp = ip; + //return/break in for..in /* We need to skip following: @@ -984,7 +985,7 @@ public class ActionGraph extends Graph { ... Beware: There can be obfuscation jumps anywhere on the path! - */ + */ GraphSourceItem action = code.get(ip); if ((action instanceof ActionPush) && (((ActionPush) action).values.size() == 1) && (((ActionPush) action).values.get(0) == Null.INSTANCE)) { int nip = ip; @@ -999,7 +1000,7 @@ public class ActionGraph extends Graph { nip = ipAfterJumps(nip); if (nip < code.size() && code.get(nip) instanceof ActionIf) { ActionIf aif = (ActionIf) code.get(nip); - + int jip = code.adr2pos(aif.getTargetAddress()); jip = ipAfterJumps(jip); if (jip == ip) { @@ -1008,9 +1009,9 @@ public class ActionGraph extends Graph { } } } - } + } } - + //The simple approach is not working, there may be jumps inside /* if (ip + 3 <= code.size()) { @@ -1026,7 +1027,7 @@ public class ActionGraph extends Graph { } } } - */ + */ } if (oldIp != ip) { if (ip == code.size()) { //no next checkIp call since its after code size @@ -1036,20 +1037,16 @@ public class ActionGraph extends Graph { } return ip; } - + @Override - public SecondPassData prepareSecondPass(BaseLocalData localData, List list) { + public SecondPassData prepareSecondPass(BaseLocalData localData, List loops, List throwStates, List list) { ActionSecondPassData spd = new ActionSecondPassData(); Set processedIfs = new HashSet<>(); - checkSecondPassSwitches(processedIfs, list, spd.switchParts, spd.switchOnFalseParts, spd.switchCaseExpressions); - + checkSecondPassSwitches(localData, loops, throwStates, spd.switchCases, spd.switchBreaks, processedIfs, list, spd.switchParts, spd.switchOnFalseParts, spd.switchCaseExpressions); - if (spd.switchParts.isEmpty() && !localData.gotosUsed.getVal()) { - return null; //no need to second pass - } return spd; } - + private GraphTargetItem getFirstListItem(List list) { int i = 0; while (i < list.size()) { @@ -1060,19 +1057,36 @@ public class ActionGraph extends Graph { } return item; } - return null; + return null; } /** * Checks second pass switches. * + * @param allSwitchCases SwitchCases * @param processedIfs Processed ifs * @param list List of GraphTargetItems * @param allSwitchParts All switch parts * @param allSwitchOnFalseParts All switch on false parts * @param allSwitchExpressions All switch expressions */ - private void checkSecondPassSwitches(Set processedIfs, List list, List> allSwitchParts, List> allSwitchOnFalseParts, List> allSwitchExpressions) { + private void checkSecondPassSwitches(BaseLocalData localData, List loops, List throwStates, List> allSwitchCases, List allSwitchBreaks, Set processedIfs, List list, List> allSwitchParts, List> allSwitchOnFalseParts, List> allSwitchExpressions) { + for (int i = 0; i < list.size(); i++) { + GraphTargetItem item = list.get(i); + if (item instanceof DoWhileItem) { + DoWhileItem dw = (DoWhileItem) item; + UniversalLoopItem uni = new UniversalLoopItem(dialect, null, null, dw.loop, dw.commands); + + GraphTargetItem expr = dw.expression.remove(dw.expression.size() - 1); + dw.commands.addAll(dw.expression); + + IfItem ifi = new IfItem(dialect, null, null, expr.invert(expr.getSrc()), new ArrayList<>(), new ArrayList<>()); + ifi.onTrue.add(new BreakItem(dialect, null, null, uni.loop.id)); + ifi.decisionPart = new GraphPart(Integer.MAX_VALUE, processedIfs.size()); //hack + uni.commands.add(ifi); + list.set(i, uni); + } + } for (GraphTargetItem item : list) { if (item instanceof IfItem) { IfItem ii = (IfItem) item; @@ -1084,6 +1098,7 @@ public class ActionGraph extends Graph { List switchParts = new ArrayList<>(); List switchExpressions = new ArrayList<>(); List switchOnFalseParts = new ArrayList<>(); + List switchCases = new ArrayList<>(); BinaryOpItem sneq = (BinaryOpItem) ii.expression; if (true) { /*(sneq.leftSide instanceof StoreRegisterActionItem) @@ -1103,6 +1118,7 @@ public class ActionGraph extends Graph { switchParts.add(ii.decisionPart); switchExpressions.add(sneq.rightSide); switchOnFalseParts.add(ii.onTruePart); + switchCases.add(isNeq ? ii.onTruePart : ii.onFalsePart); //the expression is inverted when creating ifItem IfItem ii2 = ii; IfItem lastOkayIi = ii; @@ -1123,6 +1139,7 @@ public class ActionGraph extends Graph { switchParts.add(ii2.decisionPart); switchOnFalseParts.add(ii2.onTruePart); switchExpressions.add(sneq.rightSide); + switchCases.add(isNeq ? ii2.onTruePart : ii2.onFalsePart); //the expression is inverted when creating ifItem lastOkayIi = ii2; } else { break; @@ -1137,6 +1154,7 @@ public class ActionGraph extends Graph { switchParts.add(ii2.decisionPart); switchOnFalseParts.add(ii2.onTruePart); switchExpressions.add(sneq.rightSide); + switchCases.add(isNeq ? ii2.onTruePart : ii2.onFalsePart); //the expression is inverted when creating ifItem lastOkayIi = ii2; } else { break; @@ -1156,6 +1174,13 @@ public class ActionGraph extends Graph { allSwitchParts.add(switchParts); allSwitchOnFalseParts.add(switchOnFalseParts); allSwitchExpressions.add(switchExpressions); + allSwitchCases.add(switchCases); + try { + allSwitchBreaks.add(getMostCommonPart(localData, switchCases, loops, throwStates, new ArrayList<>())); + } catch (InterruptedException ex) { + allSwitchBreaks.add(null); + return; + } } } } @@ -1163,7 +1188,7 @@ public class ActionGraph extends Graph { } if ((item instanceof Block)) { for (List sub : ((Block) item).getSubs()) { - checkSecondPassSwitches(processedIfs, sub, allSwitchParts, allSwitchOnFalseParts, allSwitchExpressions); + checkSecondPassSwitches(localData, loops, throwStates, allSwitchCases, allSwitchBreaks, processedIfs, sub, allSwitchParts, allSwitchOnFalseParts, allSwitchExpressions); } } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionLocalData.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionLocalData.java index 3a280aa93..3e3588920 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionLocalData.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionLocalData.java @@ -23,6 +23,7 @@ import com.jpexs.decompiler.graph.GraphSourceItem; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.decompiler.graph.SecondPassData; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -71,7 +72,7 @@ public class ActionLocalData extends BaseLocalData { * @param insideDoInitAction Is inside doInitAction * @param uninitializedClassTraits Uninitialized class traits */ - public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, Map> uninitializedClassTraits, Set usedDeobfuscations) { + public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, Map> uninitializedClassTraits, Set usedDeobfuscations, List> switchCases, List switchBreaks) { this.secondPassData = secondPassData; regNames = new HashMap<>(); variables = new HashMap<>(); @@ -79,6 +80,8 @@ public class ActionLocalData extends BaseLocalData { this.insideDoInitAction = insideDoInitAction; this.uninitializedClassTraits = uninitializedClassTraits; this.usedDeobfuscations = usedDeobfuscations; + this.switchCases = switchCases; + this.switchBreaks = switchBreaks; } /** @@ -88,8 +91,9 @@ public class ActionLocalData extends BaseLocalData { * @param insideDoInitAction Is inside doInitAction * @param regNames Register names * @param uninitializedClassTraits Uninitialized class traits + * @param switchCases Switch cases */ - public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, HashMap regNames, Map> uninitializedClassTraits, Set usedDeobfuscations) { + public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, HashMap regNames, Map> uninitializedClassTraits, Set usedDeobfuscations, List> switchCases, List switchBreaks) { this.regNames = regNames; this.secondPassData = secondPassData; variables = new HashMap<>(); @@ -97,6 +101,8 @@ public class ActionLocalData extends BaseLocalData { this.insideDoInitAction = insideDoInitAction; this.uninitializedClassTraits = uninitializedClassTraits; this.usedDeobfuscations = usedDeobfuscations; + this.switchCases = switchCases; + this.switchBreaks = switchBreaks; } /** @@ -109,7 +115,7 @@ public class ActionLocalData extends BaseLocalData { * @param functions Functions * @param uninitializedClassTraits Uninitialized class traits */ - public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, HashMap regNames, HashMap variables, HashMap functions, Map> uninitializedClassTraits, Set usedDeobfuscations) { + public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, HashMap regNames, HashMap variables, HashMap functions, Map> uninitializedClassTraits, Set usedDeobfuscations, List> switchCases, List switchBreaks) { this.regNames = regNames; this.variables = variables; this.functions = functions; @@ -117,5 +123,7 @@ public class ActionLocalData extends BaseLocalData { this.secondPassData = secondPassData; this.uninitializedClassTraits = uninitializedClassTraits; this.usedDeobfuscations = usedDeobfuscations; + this.switchCases = switchCases; + this.switchBreaks = switchBreaks; } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/BasicPrevNextWalker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/BasicPrevNextWalker.java new file mode 100644 index 000000000..422292a01 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/BasicPrevNextWalker.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.graph; + +import java.util.List; + +/** + * Basic Prev-Next walker which uses only nextParts+refs. + * @author JPEXS + */ +public class BasicPrevNextWalker implements PrevNextWalker { + + @Override + public List getPrev(GraphPart node) { + return node.refs; + } + + @Override + public List getNext(GraphPart node) { + return node.nextParts; + } + +} 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 6fc74799d..1e2478509 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.FinalProcessLocalData; import com.jpexs.decompiler.flash.action.Action; import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction; import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2; +import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.decompiler.graph.model.AndItem; import com.jpexs.decompiler.graph.model.BinaryOpItem; @@ -62,7 +63,9 @@ import com.jpexs.helpers.Reference; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.HashMap; @@ -130,9 +133,9 @@ public class Graph { */ private final boolean debugPrintGraph = false; /** - * Debug flag to not process Ifs + * Not process Ifs */ - protected boolean debugDoNotProcess = false; + protected boolean doNotProcessIfs = false; /** * Logger @@ -192,59 +195,6 @@ public class Graph { } } - /** - * Calculates time of closing the node. The node is closed when all its - * input edges are already visited (not counting back edges), then all its - * output edges are processed. - *

- * This time is useful when sorting nodes according their occurrence in - * getMostCommonPart method - used for switch detection - * - * @param loops Already calculated loops to get backedges from. - */ - private void calculateClosedTime(List loops) { - ArrayDeque openedNodes = new ArrayDeque<>(); - Set closedNodes = new HashSet<>(); - Set visitedEdges = new HashSet<>(); - for (GraphPart h : heads) { - for (GraphPart r : h.refs) { - visitedEdges.add(new LevelMapEdge(r, h)); - } - } - for (Loop el : loops) { - for (GraphPart be : el.backEdges) { - visitedEdges.add(new LevelMapEdge(be, el.loopContinue)); - } - } - - int closedTime = 1; - - for (GraphPart h : heads) { - openedNodes.add(h); - - loopopened: - while (!openedNodes.isEmpty()) { - GraphPart part = openedNodes.remove(); - if (closedNodes.contains(part)) { - continue; - } - for (GraphPart r : part.refs) { - if (!visitedEdges.contains(new LevelMapEdge(r, part))) { - continue loopopened; - } - } - for (GraphPart n : part.nextParts) { - openedNodes.add(n); - visitedEdges.add(new LevelMapEdge(part, n)); - } - closedNodes.add(part); - part.closedTime = closedTime++; - //System.err.println("part " + part + " closedTime: " + part.closedTime); - } - } - - } - /** * Edge for calculating closed time. */ @@ -970,22 +920,42 @@ public class Graph { beforeGetLoops(localData, path, allParts, throwStates); List loops = new ArrayList<>(); - getLoops(localData, heads.get(0), loops, throwStates, null); + if (debugPrintLoopList) { + getLoops(localData, heads.get(0), loops, throwStates, null); + afterGetLoops(localData, path, allParts); + //TODO: Make getPrecontinues faster + getBackEdges(localData, loops, throwStates); + } + LoopDetector detector = new LoopDetector(); + + if (debugPrintLoopList) { + detector.calculateClosedTime(heads, loops); + loops.sort(new LoopDetector.LoopComparator()); + } + List loops2 = new ArrayList<>(); + detector.detectLoops(heads, loops2, throwStates, allParts, localData.secondPassData == null ? new ArrayList<>() : localData.secondPassData.switchCases, localData.secondPassData == null ? new ArrayList<>() : localData.secondPassData.switchBreaks, localData.secondPassData != null); afterGetLoops(localData, path, allParts); + doNotProcessIfs = localData.secondPassData == null; - //TODO: Make getPrecontinues faster - getBackEdges(localData, loops, throwStates); - calculateClosedTime(loops); - - new GraphPrecontinueDetector().detectPrecontinues(heads, allParts, loops, throwStates); + //new GraphPrecontinueDetector().detectPrecontinues(heads, allParts, loops, throwStates); + //new GraphPrecontinueDetector().detectPrecontinues(heads, allParts, loops2, throwStates); if (debugPrintLoopList) { System.err.println(""); for (Loop el : loops) { System.err.println(el); } System.err.println(""); + + System.err.println(""); + for (Loop el : loops2) { + System.err.println(el); + } + System.err.println(""); + } + + loops = loops2; /*System.err.println(""); for (Loop el : loops) { System.err.println(el); @@ -993,11 +963,11 @@ public class Graph { System.err.println("");//*/ List gotos = new ArrayList<>(); - Reference hasEmptyStackPops = new Reference<>(false); + Reference hasEmptyStackPops = new Reference<>(false); List ret = printGraph(hasEmptyStackPops, 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(localData, ret); + SecondPassData secondPassData = prepareSecondPass(localData, loops, throwStates, ret); if (secondPassData != null) { throw new SecondPassException(secondPassData); } @@ -1027,17 +997,17 @@ public class Graph { propagateBreaks(ret); finalProcessStack(stack, ret, path); makeAllCommands(ret, stack); - + if (!hasEmptyStackPops.getVal()) { promotePushItemsToCommands(ret); } - + finalProcessAll(null, ret, 0, getFinalData(localData, loops, throwStates), path); //fixSwitchEnds(ret); - handleSetTemporaryDeclarations(ret); + handleSetTemporaryDeclarations(ret); return ret; } - + private void promotePushItemsToCommands(List list) { for (int i = 0; i < list.size(); i++) { GraphTargetItem ti = list.get(i); @@ -1328,7 +1298,7 @@ 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) { @@ -1348,7 +1318,7 @@ public class Graph { } } - if (found) { + if (found) { loopItem.commands.remove(loopItem.commands.size() - 1); GraphTargetItem expressionSingle = ifi.expression; if (inverted) { @@ -1361,8 +1331,7 @@ public class Graph { } } } - - + } else if (lastCommand instanceof Block) { Block blk = (Block) lastCommand; List> newTodos = new ArrayList<>(blk.getSubs()); @@ -1374,7 +1343,7 @@ public class Graph { todos.addAll(newTodos); } } - } + } if (li instanceof ForItem) { ForItem fi = (ForItem) li; List continues = fi.getContinues(); @@ -1452,14 +1421,21 @@ public class Graph { * happen. Override this method to prepare second pass data. * * @param localData Local data + * @param loops Loops + * @param throwStates Throw states * @param list List of GraphTargetItems * @return Second pass data or null */ - protected SecondPassData prepareSecondPass(BaseLocalData localData, List list) { - if (!localData.gotosUsed.getVal()) { + public SecondPassData prepareSecondPass(BaseLocalData localData, List loops, List throwStates, List list) { + /*if (!localData.gotosUsed.getVal() && localData.switchCases.isEmpty()) { return null; - } + }*/ + + //always second pass SecondPassData spd = new SecondPassData(); + spd.switchCases = localData.switchCases; + spd.switchBreaks = localData.switchBreaks; + return spd; } @@ -1791,7 +1767,7 @@ public class Graph { * @throws InterruptedException On interrupt */ private void finalProcessAll(GraphTargetItem parent, List list, int level, FinalProcessLocalData localData, String path) throws InterruptedException { - if (debugDoNotProcess) { + if (doNotProcessIfs) { return; } finalProcess(parent, list, level, localData, path); @@ -2142,7 +2118,7 @@ public class Graph { if (blk instanceof SwitchItem) { return; } - + if (blk instanceof LoopItem) { return; } @@ -2160,7 +2136,7 @@ public class Graph { * @param list List of GraphTargetItems */ protected final void processIfs(List list) { - if (debugDoNotProcess) { + if (doNotProcessIfs) { return; } for (int i = 0; i < list.size(); i++) { @@ -2475,7 +2451,8 @@ public class Graph { } /** - * Translates part and get its stack with output + * Translates part and get its stack with output. Continues to next part + * when there is only 1 next/prev. * * @param localData Local data * @param part Part @@ -2490,6 +2467,10 @@ public class Graph { stack = (TranslateStack) stack.clone(); output.clear(); translatePart(output, localData, part, stack, staticOperation, null); + while (part.nextParts.size() == 1 && part.nextParts.get(0).refs.size() == 1) { + part = part.nextParts.get(0); + translatePart(output, localData, part, stack, staticOperation, null); + } return stack.pop(); } @@ -2525,6 +2506,24 @@ public class Graph { } } + /** + * Gets source item at the end of part. While part has only single next + * part, then continues to next part. + * + * @param part Part + * @return Source item + */ + protected GraphSourceItem getLastPartSourceItem(GraphPart part) { + while (part.nextParts.size() == 1 && part.nextParts.get(0).refs.size() == 1) { + part = part.nextParts.get(0); + } + int end = part.end; + if (end >= code.size()) { + end = code.size() - 1; + } + return code.get(end); + } + /** * Checks for Continue and Break items at current part. * @@ -2602,16 +2601,16 @@ public class Graph { * Loop detection. * * @param localData Local data - * @param part Part + * @param firstPart Part * @param loops List of loops * @param throwStates List of throw states * @param stopPart Stop part * @throws InterruptedException On interrupt */ - private void getLoops(BaseLocalData localData, GraphPart part, List loops, List throwStates, List stopPart) throws InterruptedException { + private void getLoops(BaseLocalData localData, GraphPart firstPart, List loops, List throwStates, List stopPart) throws InterruptedException { clearLoops(loops); clearThrowStates(throwStates); - getLoopsWalk(localData, part, loops, throwStates, stopPart, true, new ArrayList<>(), 0); + getLoopsWalk(localData, firstPart, loops, throwStates, stopPart, true, new ArrayList<>(), 0); clearLoops(loops); clearThrowStates(throwStates); } @@ -3021,7 +3020,7 @@ public class Graph { Map count = new HashMap<>(); GraphPart winner = null; int winnerCount = 0; - int winnerNumBlock = Integer.MAX_VALUE; + int winnerNumBlock = Integer.MAX_VALUE; for (GraphPart cand : currentLoop.breakCandidates) { if (removedX.contains(cand)) { @@ -3359,7 +3358,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) { @@ -3385,7 +3384,7 @@ public class Graph { } continue; } - if (el.loopBreak == part) { + if (el.loopBreak == part) { if (currentLoop != null) { currentLoop.phase = 0; } @@ -3401,8 +3400,8 @@ public class Graph { ret.add(br); return originalRet; - } - if (el.loopPreContinue == part) { + } + if (el.loopPreContinue == part) { if (currentLoop != null) { currentLoop.phase = 0; } @@ -3429,7 +3428,7 @@ public class Graph { if (debugPrintGraph) { System.err.println("stopParts: " + pathToString(stopPart)); } - + if (stopPart.contains(part)) { boolean isRealStopPart = false; @@ -3480,7 +3479,7 @@ public class Graph { } return originalRet; } - + boolean vCanHandleVisited = canHandleVisited(localData, part); if (vCanHandleVisited) { @@ -3619,7 +3618,7 @@ public class Graph { exHappened = false; try { stack.setConnectedOutput(currentRet.size(), output, localData); - code.translatePart(output, this, part, localData, stack, ipStart, part.end, staticOperation, path); + code.translatePart(output, this, part, localData, stack, ipStart, part.end, staticOperation, path); if (stack.emptyPopCount > 0) { hasEmptyStackPops.setVal(true); } @@ -3905,9 +3904,9 @@ public class Graph { if (p == part) { continue; } - for (GraphPart r : p.refs) { + for (GraphPart r : p.refs) { // #2636 - GraphPartEdge edge = new GraphPartEdge(r, p); + GraphPartEdge edge = new GraphPartEdge(r, p); if (backEdges.contains(edge)) { continue; } @@ -3915,7 +3914,7 @@ public class Graph { if (!part.leadsTo(localData, this, code, r, loops, throwStates, true /*IMPORTANT*/)) { continue; } - + if (r == part) { continue; } @@ -3933,7 +3932,7 @@ public class Graph { } 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) { + if (n2 != null && n2.start < code.size()) { //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"); @@ -3979,7 +3978,7 @@ public class Graph { TranslateStack trueStack = (TranslateStack) stack.clone(); TranslateStack falseStack = (TranslateStack) stack.clone(); - + //hack for as1/2 for..in to get enumeration through GraphTargetItem topBsr = !stack.isEmpty() && (stack.peek() instanceof BranchStackResistant) ? stack.peek() : null; trueStack.clear(); @@ -4024,7 +4023,7 @@ public class Graph { //List out2 = new ArrayList<>(); //makeAllCommands(out2, stack); makeAllCommands(onTrue, trueStack); - makeAllCommands(onFalse, falseStack); + makeAllCommands(onFalse, falseStack); GraphTargetItem addAfterIf = null; if (!onTrue.isEmpty() && !onFalse.isEmpty() && (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) @@ -4245,11 +4244,10 @@ public class Graph { if (bi.loopId == currentLoop.id) { bodyBranch = ifi.onTrue; } - } else if (loopItem.commands.size() == 2 + } else if (loopItem.commands.size() == 2 && (loopItem.commands.get(1) instanceof BreakItem) - && ifi.onFalse.isEmpty() - && !ifi.onTrue.isEmpty() - ) { + && ifi.onFalse.isEmpty() + && !ifi.onTrue.isEmpty()) { BreakItem bi = (BreakItem) loopItem.commands.get(1); if (ifi.onTrue.isEmpty()) { inverted = true; @@ -4259,15 +4257,14 @@ public class Graph { 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) + if (!(bodyBranch.get(bodyBranch.size() - 1) instanceof ContinueItem) && !(bodyBranch.get(bodyBranch.size() - 1) instanceof BreakItem) - && !(bodyBranch.get(bodyBranch.size() - 1) instanceof ExitItem) - ) { + && !(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); @@ -4549,7 +4546,9 @@ public class Graph { e1.path = new GraphPath("e"); ret.add(makeGraph(e1, new GraphPath("e"), code, pos, pos, allBlocks, refs, visited)); } - flattenJumps(ret, allBlocks); + if (Configuration.autoDeobfuscate.get()) { + flattenJumps(ret, allBlocks); + } checkGraph(allBlocks); return ret; } @@ -4707,7 +4706,7 @@ public class Graph { int tsize = tree.size(); if (!tree.isEmpty() && (tree.get(tree.size() - 1) instanceof ScriptEndItem)) { tsize--; - } + } for (int i = 0; i < tsize; i++) { GraphTargetItem ti = tree.get(i); if (!ti.isEmpty()) { @@ -4727,7 +4726,7 @@ public class Graph { lastNewLine = true; } } - } + } return writer; } @@ -5036,6 +5035,15 @@ public class Graph { nextRef.setVal(next); + List switchCases = new ArrayList<>(); + + for (int index : valuesMapping) { + switchCases.add(caseBodies.get(index)); + } + + localData.switchCases.add(switchCases); + localData.switchBreaks.add(breakPart); + currentLoop.phase = 2; GraphTargetItem ti = checkLoop(new ArrayList<>() /*??*/, next, stopPart, loops, throwStates); tiRef.setVal(ti); 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 674af088c..8509cf0ce 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphPart.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/GraphPart.java @@ -234,6 +234,7 @@ public class GraphPart implements Serializable { l.leadsToMark = 0; } return leadsTo(localData, gr, code, null /*???*/, part, new HashSet<>(), loops, throwStates, firstCanBeLoopContinue); + //return gr.partLeadsTo(localData, this, part, code, loops, throwStates, firstCanBeLoopContinue); } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/IgnoreEdgesPrevNextWalker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/IgnoreEdgesPrevNextWalker.java new file mode 100644 index 000000000..dbf7514a8 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/IgnoreEdgesPrevNextWalker.java @@ -0,0 +1,45 @@ +package com.jpexs.decompiler.graph; + +import java.util.List; +import java.util.Set; + +/** + * + * @author JPEXS + */ +public class IgnoreEdgesPrevNextWalker implements PrevNextWalker { + + private final Set backEdges; + + private final PrevNextWalker prevNextWalker; + + public IgnoreEdgesPrevNextWalker(Set ignoredEdges, PrevNextWalker prevNextWalker) { + this.backEdges = ignoredEdges; + this.prevNextWalker = prevNextWalker; + } + + @Override + public List getPrev(GraphPart node) { + List ret = prevNextWalker.getPrev(node); + for (int i = ret.size() - 1; i >= 0; i--) { + GraphPartEdge edge = new GraphPartEdge(ret.get(i), node); + if (backEdges.contains(edge)) { + ret.remove(i); + } + } + return ret; + } + + @Override + public List getNext(GraphPart node) { + List ret = prevNextWalker.getNext(node); + for (int i = ret.size() - 1; i >= 0; i--) { + GraphPartEdge edge = new GraphPartEdge(node, ret.get(i)); + if (backEdges.contains(edge)) { + ret.remove(i); + } + } + return ret; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Loop.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Loop.java index dfeefe33d..0b2c8bd48 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Loop.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Loop.java @@ -65,7 +65,7 @@ public class Loop implements Serializable { /** * Unique id of the loop */ - public final long id; + public long id; /** * Mark for leads to method @@ -92,6 +92,26 @@ public class Loop implements Serializable { * Stop parts before entering the loop */ public List stopParts = new ArrayList<>(); + + /** + * Loop body + */ + public Set loopBody = new LinkedHashSet<>(); + + /** + * Edges outside + */ + public Set edgesOutside = new LinkedHashSet<>(); + + /** + * Order part + */ + public GraphPart orderPart; + + /** + * Parent loop + */ + public Loop parentLoop; /** * Constructs a loop diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/LoopDetector.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/LoopDetector.java new file mode 100644 index 000000000..4698ac48d --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/LoopDetector.java @@ -0,0 +1,899 @@ +package com.jpexs.decompiler.graph; + +import com.jpexs.decompiler.graph.precontinues.GraphPrecontinueDetector; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Detector of loops. + * + * @author JPEXS + */ +public class LoopDetector { + + /** + * Detects loops. + * + * @param heads + * @param loops + * @param throwStates + * @param allParts + * @param switchParts + * @throws InterruptedException + */ + public void detectLoops(List heads, List loops, List throwStates, Set allParts, List> switchParts, List switchBreaks, boolean detectBreaks) throws InterruptedException { + PrevNextWalker pnw = new ThrowPrevNextWalker(throwStates); + + List loopHeaders = new ArrayList<>(); + List loopTails = new ArrayList<>(); + List> loopBodys = new ArrayList<>(); + + Set backEdges = getBackEdges(heads.get(0), throwStates); + + for (GraphPartEdge edge : backEdges) { + loopBodys.add(buildNaturalLoop(edge.from, edge.to, pnw)); + loopHeaders.add(edge.to); + loopTails.add(edge.from); + } + + //Join loops with the same header + Map loopByHeader = new LinkedHashMap<>(); + + for (int i = 0; i < loopHeaders.size(); i++) { + GraphPart header = loopHeaders.get(i); + GraphPart tail = loopTails.get(i); + Set body = loopBodys.get(i); + + if (loopByHeader.containsKey(header)) { + Loop loop = loopByHeader.get(header); + loop.backEdges.add(tail); + loop.loopBody.addAll(body); + } else { + Loop loop = new Loop(loops.size(), header, null); + loop.orderPart = header; + loop.loopBody.addAll(body); + loop.backEdges.add(tail); + + loopByHeader.put(header, loop); + loops.add(loop); + } + } + + computeParentLoops(loops); + + //Calculate closed time + calculateClosedTime(heads, loops); + + //Find loops without header + //findLoopsWithoutHeader(loops, backEdges, allParts); + loops.sort(new LoopDetector.LoopComparator()); + + for (int i = 0; i < loops.size(); i++) { + loops.get(i).id = i; + } + + if (!detectBreaks) { + return; + } + + Set ignoredEdges = new LinkedHashSet<>(); + + Set multipleSameCase = new LinkedHashSet<>(); + + for (List switchCases : switchParts) { + for (int i = 1; i < switchCases.size(); i++) { + GraphPart case1 = switchCases.get(i - 1); + GraphPart case2 = switchCases.get(i); + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "checking case {0} and {1}", new Object[]{case1, case2}); + if (case1 == case2) { + multipleSameCase.add(case1); + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "duplicate case {0} detected", new Object[]{case1}); + continue; + } + GraphPart leadsToPrev = partLeadsToGetPrev(case1, case2, backEdges, pnw); + if (leadsToPrev != null) { + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "- leadsto, ignoring edge {0} => {1}", new Object[]{leadsToPrev, case2}); + ignoredEdges.add(new GraphPartEdge(leadsToPrev, case2)); + } + } + } + + for (GraphPart part : multipleSameCase) { + List prev = pnw.getPrev(part); + for (int r = 0; r < prev.size() - 1; r++) { + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "ignoring edge {0}->{1} detected", new Object[]{part.refs.get(r), part}); + ignoredEdges.add(new GraphPartEdge(prev.get(r), part)); + } + } + + new GraphPrecontinueDetector().detectPrecontinues(heads, allParts, loops, throwStates); + + findBreaks(loops, backEdges, throwStates, ignoredEdges, switchBreaks); + + loops.sort(new LoopDetector.LoopComparator()); + + for (int i = 0; i < loops.size(); i++) { + loops.get(i).id = i; + } + + new GraphPrecontinueDetector().detectPrecontinues(heads, allParts, loops, throwStates); + } + + public void computeParentLoops(List loops) { + int n = loops.size(); + + List> sets = new ArrayList<>(n); + for (Loop loop : loops) { + sets.add(loop.loopBody); + } + + for (int i = 0; i < n; i++) { + Set child = sets.get(i); + + int bestParentIndex = -1; + int bestParentSize = Integer.MAX_VALUE; + + for (int j = 0; j < n; j++) { + if (i == j) { + continue; + } + + Set candidate = sets.get(j); + + if (candidate.size() > child.size() && candidate.containsAll(child)) { + if (candidate.size() < bestParentSize) { + bestParentSize = candidate.size(); + bestParentIndex = j; + } + } + } + if (bestParentIndex > -1) { + loops.get(i).parentLoop = loops.get(bestParentIndex); + } + } + } + + /** + * Calculates time of closing the node. The node is closed when all its + * input edges are already visited (including back edges), then all its + * output edges are processed. + *

+ * This time is useful when sorting nodes according their occurrence in + * getMostCommonPart method - used for switch detection + * + * @param heads Heads + * @param loops Already calculated loops to get backedges from. + */ + public void calculateClosedTime(List heads, List loops) { + ArrayDeque openedNodes = new ArrayDeque<>(); + Set closedNodes = new HashSet<>(); + Set visitedEdges = new HashSet<>(); + for (GraphPart h : heads) { + for (GraphPart r : h.refs) { + visitedEdges.add(new LevelMapEdge(r, h)); + } + } + + for (Loop el : loops) { + for (GraphPart be : el.backEdges) { + visitedEdges.add(new LevelMapEdge(be, el.loopContinue)); + } + } + + int closedTime = 1; + + for (GraphPart h : heads) { + openedNodes.add(h); + + loopopened: + while (!openedNodes.isEmpty()) { + GraphPart part = openedNodes.remove(); + if (closedNodes.contains(part)) { + continue; + } + //boolean canClose = true; + for (GraphPart r : part.refs) { + LevelMapEdge e = new LevelMapEdge(r, part); + /*if (backEdges.contains(e) && !visitedEdges.contains(e)) { + canClose = false; + continue; + }*/ + if (!visitedEdges.contains(e)) { + continue loopopened; + } + } + for (GraphPart n : part.nextParts) { + openedNodes.add(n); + visitedEdges.add(new LevelMapEdge(part, n)); + } + //if (canClose) { + closedNodes.add(part); + part.closedTime = closedTime++; + //} + //System.err.println("part " + part + " closedTime: " + part.closedTime); + } + } + + } + + /** + * Edge for calculating closed time. + */ + private class LevelMapEdge { + + /** + * Source part + */ + public GraphPart from; + + /** + * Target part + */ + public GraphPart to; + + /** + * Constructs a new LevelMapEdge + * + * @param from Source part + * @param to Target part + */ + public LevelMapEdge(GraphPart from, GraphPart to) { + this.from = from; + this.to = to; + } + + /** + * Hash code + * + * @return Hash code + */ + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + Objects.hashCode(this.from); + hash = 31 * hash + Objects.hashCode(this.to); + return hash; + } + + /** + * Equals + * + * @param obj Object + * @return True if equals + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final LevelMapEdge other = (LevelMapEdge) obj; + //use == comparison, not equals, as some parts may be equal + // (the refs to throw targets have -1,-1 as start/end) + if (this.from != other.from) { + return false; + } + return this.to == other.to; + } + } + + /* + private void findLoopsWithoutHeader(List loops, Set backEdges, Set allParts) { + List sortedParts = new ArrayList<>(allParts); + sortedParts.sort(new Comparator() { + @Override + public int compare(GraphPart o1, GraphPart o2) { + return o1.closedTime - o2.closedTime; + } + }); + + PrevNextWalker pnw = new BasicPrevNextWalker(); + + Set ignored = new HashSet<>(); + + Loop currentLoop = null; + + for (GraphPart ifPart : sortedParts) { + List branches = pnw.getNext(ifPart); + + for (Loop loop : loops) { + if (loop.loopContinue != null && loop.loopContinue == ifPart) { + currentLoop = loop; + } + } + + if (branches.size() > 2) { + GraphPart breakPart = getMostCommonPart(new HashSet<>(branches), backEdges, pnw, false); + if (breakPart != null) { + Loop loop = new Loop(loops.size(), null, breakPart); + loop.orderPart = ifPart; + loops.add(loop); + ignored.add(breakPart); + } + continue; + } + + if (branches.size() != 2) { + continue; + } + GraphPart ifCommonPart = getCommonPart(new HashSet<>(branches), backEdges, pnw); + //System.err.println("IF at " + ifPart + ", commonPart " + ifCommonPart); + if (ifCommonPart == null) { + //System.err.println("- ignored"); + continue; + } + Set visited = new HashSet<>(); + + Stack stack = new Stack<>(); + stack.push(ifCommonPart); + loopstack: + while (!stack.isEmpty()) { + GraphPart part = stack.pop(); + if (part == ifPart) { + continue; + } + visited.add(part); + //System.err.println("popped " + part); + for (GraphPart r : pnw.getPrev(part)) { + //System.err.println("checking ref " + r); + if (r == part) { + //System.err.println("- is part"); + continue; + } + if (backEdges.contains(new GraphPartEdge(r, part))) { + //System.err.println("- is backedge"); + continue; + } + if (!partLeadsTo(ifPart, r, backEdges, pnw)) { + //System.err.println("- ifPart does not lead to it"); + continue; + } + if (ignored.contains(r)) { + //System.err.println("- is ignored"); + continue; + } + if (visited.contains(r)) { + //System.err.println("- already visited"); + continue; + } + //System.err.println("used ref " + r); + for (GraphPart n : pnw.getNext(r)) { + //System.err.println("- checking n = " + n); + if (visited.contains(n)) { + //System.err.println("- visited contains"); + continue; + } + if (!partLeadsTo(n, ifCommonPart, backEdges, pnw)) { + GraphPart breakPart = getCommonPart(new HashSet<>(Arrays.asList(n, ifCommonPart)), backEdges, pnw); + //System.err.println("next " +n + ", breakPart " + breakPart); + if (breakPart != null && !ignored.contains(breakPart)) { + Loop loop = new Loop(loops.size(), ifPart, breakPart); + loop.orderPart = ifPart; + loops.add(loop); + currentLoop = loop; + //} + ignored.add(breakPart); + ignored.add(currentLoop.loopContinue); + if (!ignored.contains(ifCommonPart)) { + stack.clear(); + stack.push(ifCommonPart); + visited.clear(); + continue loopstack; + } + } + } else { + //System.err.println("- n leads to commonpart, skip"); + } + } + stack.push(r); + } + } + } + } + */ + /** + * Find loop breaks. Make sure you call Graph.calculateClosedTime to loops + * before calling this method. + * + * @param loops Loops + */ + private void findBreaks(List loops, Set backEdges, List throwStates, Set ignoredEdges, List switchBreaks) { + PrevNextWalker pnw = new BasicPrevNextWalker(); + //PrevNextWalker tpnw = new ThrowPrevNextWalker(throwStates); //new BasicPrevNextWalker(); + + Set throwEdges = new LinkedHashSet<>(); + + for (ThrowState ts : throwStates) { + for (GraphPart tp : ts.throwingParts) { + throwEdges.add(new GraphPartEdge(tp, ts.targetPart)); + } + } + + Map loopWalkers = new LinkedHashMap<>(); + Map> loopThrowStates = new LinkedHashMap<>(); + + //Find outside edges + for (Loop loop : loops) { + + List subThrowStates = new ArrayList<>(); + + for (ThrowState ts : throwStates) { + boolean contains = false; + if (ts.throwingParts.contains(loop.loopContinue)) { + contains = true; + } + if (ts.catchParts.contains(loop.loopContinue)) { + //contains = true; + } + if (!contains) { + subThrowStates.add(ts); + } + } + loopThrowStates.put(loop, subThrowStates); + + PrevNextWalker walker = new ThrowPrevNextWalker(subThrowStates); + loopWalkers.put(loop, walker); + + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "Body of loop with continue {0}:", loop.loopContinue); + for (GraphPart part : loop.loopBody) { + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "- {0}", part); + for (GraphPart next : walker.getNext(part)) { + GraphPartEdge edge = new GraphPartEdge(part, next); + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "- checking next {0}", next); + if (!loop.loopBody.contains(next)) { + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "- is outside"); + loop.edgesOutside.add(new GraphPartEdge(part, next)); + } + } + } + } + + int loopIndex = -1; + //Detect breaks + for (Loop loop : loops) { + loopIndex++; + + if (loop.loopBreak != null) { + continue; + } + + Set parentContinues = new LinkedHashSet<>(); + Set parentBreaks = new LinkedHashSet<>(); + Loop parentLoop = loop.parentLoop; + while (parentLoop != null) { + if (parentLoop.loopBreak != null) { + parentBreaks.add(parentLoop.loopBreak); + } + if (parentLoop.loopContinue != null) { + parentContinues.add(parentLoop.loopContinue); + } + if (parentLoop.loopPreContinue != null) { + parentContinues.add(parentLoop.loopPreContinue); + } + parentLoop = parentLoop.parentLoop; + } + + parentBreaks.addAll(switchBreaks); //these are not real parent, but should work(?) + + PrevNextWalker walker = loopWalkers.get(loop); + List subThrowStates = loopThrowStates.get(loop); + + Set allOutReachable = new LinkedHashSet<>(); + List> allReachable = new ArrayList<>(); + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINE, "loop {0} outside:", loopIndex); + Set outParts = new LinkedHashSet<>(); + for (GraphPartEdge edge : loop.edgesOutside) { + if (ignoredEdges.contains(edge)) { + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "Ignored outside edge {0}->{1}", new Object[]{edge.from, edge.to}); + continue; + } + GraphPart outPart = edge.to; + outParts.add(outPart); + Set reachable = getReachable(outPart, backEdges, parentBreaks, walker, ignoredEdges); + + if (parentContinues.contains(outPart)) { + reachable.clear(); + reachable.add(outPart); + } + + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "Reachables of {0}:", outPart); + for (GraphPart r : reachable) { + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "- {0}:", r); + } + allReachable.add(reachable); + allOutReachable.addAll(reachable); + } + + for (ThrowState ts : subThrowStates) { + Set catchParts = ts.catchParts; + if (catchParts.isEmpty()) { + catchParts = new HashSet<>(); + catchParts.add(ts.targetPart); + //This handles only first part of the catch. We should somehow better detect the actual catchParts, + //but for now, it's better than nothing. + //The catchparts are empty usually in swftools scripts + } + + catchParts = new HashSet<>(catchParts); + + if (catchParts.contains(loop.loopContinue)) { + continue; + } + + catchParts.removeAll(parentContinues); + catchParts.removeAll(parentBreaks); + + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "removing catchparts:"); + for (GraphPart part : catchParts) { + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINEST, "- {0}", part); + } + allOutReachable.removeAll(catchParts); + } + + if (allOutReachable.isEmpty()) { + continue; + } + + + //outParts.removeAll(allSwitchCases); + + List candidateList = new ArrayList<>(); + for (GraphPart part : allOutReachable) { + int numBranches = 0; + for (Set reachable : allReachable) { + if (reachable.contains(part)) { + numBranches++; + } + } + candidateList.add(new CandidateResult(part, numBranches, outParts.contains(part), parentBreaks.contains(part))); + } + + candidateList.sort(new Comparator() { + @Override + public int compare(CandidateResult o1, CandidateResult o2) { + boolean b1 = parentBreaks.contains(o1.part); + boolean b2 = parentBreaks.contains(o2.part); + + boolean op1 = outParts.contains(o1.part); + boolean op2 = outParts.contains(o2.part); + + if (b1 != b2) { + if (b1) { + return 1; + } + return -1; + } + + boolean mb1 = o1.numBranches > 1; + boolean mb2 = o2.numBranches > 1; + + if (mb1 != mb2) { + if (mb1) { + return -1; + } + return 1; + } + + if (op1 != op2) { + if (op1) { + return -1; + } + return 1; + } + + return o1.part.closedTime - o2.part.closedTime; + } + }); + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINE, "candidates:"); + for (CandidateResult cand : candidateList) { + Logger.getLogger(LoopDetector.class.getName()).log(Level.FINE, "- {0}", cand); + } + loop.loopBreak = candidateList.get(0).part; + } + } + + private Set buildNaturalLoop(GraphPart tail, GraphPart header, PrevNextWalker pnw) { + Set loop = new LinkedHashSet<>(); + Deque stack = new ArrayDeque<>(); + + if (tail == header) { + loop.add(header); + return loop; + } + + loop.add(header); + loop.add(tail); + stack.push(tail); + + while (!stack.isEmpty()) { + GraphPart x = stack.pop(); + for (GraphPart p : pnw.getPrev(x)) { + if (!loop.contains(p)) { + loop.add(p); + stack.push(p); + } + } + } + return loop; + } + + private static class CandidateResult { + + GraphPart part; + int numBranches; + boolean inOutParts; + boolean inBreaks; + + public CandidateResult(GraphPart part, int numBranches, boolean inOutParts, boolean inBreaks) { + this.part = part; + this.numBranches = numBranches; + this.inOutParts = inOutParts; + this.inBreaks = inBreaks; + } + + @Override + public String toString() { + return "" + part + " (numBranches: " + numBranches + ", closedTime: " + part.closedTime + ", inOutParts: " + inOutParts + ", inBreaks: " + inBreaks + ")"; + } + } + + /* + private GraphPart getMostCommonPart(Set parts, Set backEdges, PrevNextWalker pnw, boolean includeTheseParts) { + + if (parts.isEmpty()) { + return null; + } + if (parts.size() == 1) { + return parts.iterator().next(); + } + + List> reachables = new ArrayList<>(); + List allPartsReachable = new ArrayList<>(); + + for (GraphPart part : parts) { + Set reachable = getReachable(part, backEdges, new HashSet<>(), pnw); + reachables.add(reachable); + allPartsReachable.addAll(reachable); + } + + if (!includeTheseParts) { + allPartsReachable.removeAll(parts); + } + + allPartsReachable.sort(new Comparator() { + @Override + public int compare(GraphPart o1, GraphPart o2) { + return o1.closedTime - o2.closedTime; + } + }); + + List candidates = new ArrayList<>(); + for (GraphPart part : allPartsReachable) { + int numBranches = 0; + for (Set reachable : reachables) { + if (reachable.contains(part)) { + numBranches++; + } + } + candidates.add(new CandidateResult(part, numBranches, 0)); + } + + candidates.sort(new Comparator() { + @Override + public int compare(CandidateResult o1, CandidateResult o2) { + int ret = o2.numBranches - o1.numBranches; + if (ret != 0) { + return ret; + } + return o1.part.closedTime - o2.part.closedTime; + } + }); + + return candidates.get(0).part; + } + + private GraphPart getCommonPart(Set parts, Set backEdges, PrevNextWalker pnw) { + List> reachables = new ArrayList<>(); + List allPartsReachable = new ArrayList<>(); + + for (GraphPart part : parts) { + Set reachable = getReachable(part, backEdges, new HashSet<>(), pnw); + reachables.add(reachable); + allPartsReachable.addAll(reachable); + } + + allPartsReachable.sort(new Comparator() { + @Override + public int compare(GraphPart o1, GraphPart o2) { + return o1.closedTime - o2.closedTime; + } + }); + + for (GraphPart part : allPartsReachable) { + boolean allContains = true; + for (Set reachable : reachables) { + if (!reachable.contains(part)) { + allContains = false; + break; + } + } + if (allContains) { + return part; + } + } + return null; + }*/ + private Set getReachable(GraphPart startPart, Set backEdges, Set ignoredParts, PrevNextWalker pnw, Set ignoredEdges) { + Set visited = new LinkedHashSet<>(); + + Queue q = new ArrayDeque<>(); + q.offer(startPart); + while (!q.isEmpty()) { + GraphPart part = q.poll(); + if (visited.contains(part)) { + continue; + } + visited.add(part); + + for (GraphPart next : pnw.getNext(part)) { + GraphPartEdge edge = new GraphPartEdge(part, next); + + if (ignoredEdges.contains(edge)) { + continue; + } + if (ignoredParts.contains(part)) { + continue; + } + if (backEdges.contains(edge)) { + visited.add(edge.to); + continue; + } + q.offer(next); + } + } + return visited; + } + + private GraphPart partLeadsToGetPrev(GraphPart part1, GraphPart part2, Set backEdges, PrevNextWalker pnw) { + if (part1 == part2) { + return null; + } + Stack stack = new Stack<>(); + stack.push(part1); + Set visited = new HashSet<>(); + while (!stack.isEmpty()) { + GraphPart part = stack.pop(); + if (visited.contains(part)) { + continue; + } + visited.add(part); + + for (GraphPart next : pnw.getNext(part)) { + GraphPartEdge edge = new GraphPartEdge(part, next); + if (backEdges.contains(edge)) { + continue; + } + + if (next == part2) { + return part; + } + + stack.push(next); + } + } + return null; + } + + /* + private boolean partLeadsTo(GraphPart part1, GraphPart part2, Set backEdges, PrevNextWalker pnw) { + if (part1 == part2) { + return false; + } + Stack stack = new Stack<>(); + stack.push(part1); + Set visited = new HashSet<>(); + while (!stack.isEmpty()) { + GraphPart part = stack.pop(); + if (visited.contains(part)) { + continue; + } + visited.add(part); + + if (part == part2) { + return true; + } + + for (GraphPart next : pnw.getNext(part)) { + GraphPartEdge edge = new GraphPartEdge(part, next); + if (backEdges.contains(edge)) { + continue; + } + stack.push(next); + } + } + return false; + } + */ + private Set getBackEdges(GraphPart firstPart, List throwStates) throws InterruptedException { + Stack stack = new Stack<>(); + stack.push(new GraphPartEdge(null, firstPart)); + Stack> pathStack = new Stack<>(); + pathStack.push(new ArrayList<>()); + Set visited = new HashSet<>(); + + Set backEdges = new LinkedHashSet<>(); + + while (!stack.isEmpty()) { + GraphPartEdge edge = stack.pop(); + List path = pathStack.pop(); + if (path.contains(edge.to)) { + backEdges.add(edge); + } + GraphPart part = edge.to; + if (visited.contains(part)) { + continue; + } + visited.add(part); + List subPath = new ArrayList<>(path); + subPath.add(part); + for (GraphPart next : part.nextParts) { + stack.push(new GraphPartEdge(part, next)); + pathStack.push(subPath); + } + for (ThrowState ts : throwStates) { + if (ts.throwingParts.contains(part)) { + stack.push(new GraphPartEdge(part, ts.targetPart)); + pathStack.push(subPath); + } + } + } + + return backEdges; + } + + public static class LoopComparator implements Comparator { + + public int compare(Loop o1, Loop o2) { + GraphPart order1 = o1.orderPart; + if (order1 == null) { + order1 = o1.loopContinue; + } + GraphPart order2 = o2.orderPart; + if (order2 == null) { + order2 = o2.loopContinue; + } + + int ret = order1.closedTime - order2.closedTime; + if (ret != 0) { + return ret; + } + ret = (o1.loopBreak == null ? 1 : 0) - (o2.loopBreak == null ? 1 : 0); + if (ret != 0) { + return ret; + } + if (o1.loopBreak == null) { + return 0; + } + return o2.loopBreak.closedTime - o1.loopBreak.closedTime; + } + + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/PrevNextWalker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/PrevNextWalker.java new file mode 100644 index 000000000..60b781af4 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/PrevNextWalker.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.graph; + +import java.util.List; + +/** + * + * @author JPEXS + */ +public interface PrevNextWalker { + + public List getPrev(GraphPart node); + + public List getNext(GraphPart node); +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/SecondPassData.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/SecondPassData.java index e635c0733..3047f4f1d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/SecondPassData.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/SecondPassData.java @@ -16,11 +16,23 @@ */ package com.jpexs.decompiler.graph; +import java.util.ArrayList; +import java.util.List; + /** * Second pass data. * * @author JPEXS */ public class SecondPassData { - + + /** + * List of cases for each switch statement + */ + public List> switchCases = new ArrayList<>(); + + /** + * List of breaks of each switch statement + */ + public List switchBreaks = new ArrayList<>(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/ThrowPrevNextWalker.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/ThrowPrevNextWalker.java new file mode 100644 index 000000000..ce549fdb4 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/ThrowPrevNextWalker.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.graph; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Prev-Next walker which also use try/catch blocks. + * @author JPEXS + */ +public class ThrowPrevNextWalker implements PrevNextWalker { + private final Map> throwMap = new LinkedHashMap<>(); + private final Map> reverseThrowMap = new LinkedHashMap<>(); + + + + public ThrowPrevNextWalker(List throwStates) { + for (ThrowState ts : throwStates) { + for (GraphPart t : ts.throwingParts) { + if (!throwMap.containsKey(t)) { + throwMap.put(t, new ArrayList<>()); + } + throwMap.get(t).add(ts.targetPart); + } + + if (!reverseThrowMap.containsKey(ts.targetPart)) { + reverseThrowMap.put(ts.targetPart, new ArrayList<>()); + } + reverseThrowMap.get(ts.targetPart).addAll(ts.throwingParts); + } + } + + @Override + public List getPrev(GraphPart node) { + List ret = new ArrayList<>(); + ret.addAll(node.refs); + if (reverseThrowMap.containsKey(node)) { + ret.addAll(reverseThrowMap.get(node)); + } + for (int i = ret.size() - 1; i >= 0; i--) { + if (ret.get(i).start < 0) { + ret.remove(i); + } + } + return ret; + } + + @Override + public List getNext(GraphPart node) { + List ret = new ArrayList<>(); + ret.addAll(node.nextParts); + if (throwMap.containsKey(node)) { + ret.addAll(throwMap.get(node)); + } + return ret; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java index c7030ba39..4b4bffde7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/precontinues/GraphPrecontinueDetector.java @@ -123,7 +123,7 @@ public class GraphPrecontinueDetector { } else { other = prev.next.get(0); } - if (other.graphPart == el.loopBreak) { + if (other.graphPart == el.loopBreak && el.loopBreak != null) { node = prev; usePreNode = true; } @@ -139,7 +139,7 @@ public class GraphPrecontinueDetector { } else { other = node.next.get(0); } - if (other.graphPart != el.loopBreak) { + if (other.graphPart != el.loopBreak && el.loopBreak != null) { hasMoreNexts = true; } } else { diff --git a/test/com/jpexs/decompiler/flash/gui/FlashPlayerTest.java b/test/com/jpexs/decompiler/flash/gui/FlashPlayerTest.java index 5e71c1c6a..85d399510 100644 --- a/test/com/jpexs/decompiler/flash/gui/FlashPlayerTest.java +++ b/test/com/jpexs/decompiler/flash/gui/FlashPlayerTest.java @@ -560,7 +560,7 @@ public class FlashPlayerTest { task.actions = newActions; List output = new ArrayList<>(); - ActionLocalData localData = new ActionLocalData(null, false, new HashMap<>(), new LinkedHashSet<>()); + ActionLocalData localData = new ActionLocalData(null, false, new HashMap<>(), new LinkedHashSet<>(), new ArrayList<>(), new ArrayList<>()); TranslateStack stack = new TranslateStack(""); for (Action a : newActions) { a.translate(localData, stack, output, 0, "");