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, "");