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
This commit is contained in:
Jindra Petřík
2026-03-14 17:29:08 +01:00
parent a52126472a
commit 7d18834c81
19 changed files with 1378 additions and 192 deletions

View File

@@ -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<Boolean> gotosUsed = new Reference<>(false);
public Reference<Boolean> gotosUsed = new Reference<>(false);
/**
* Switch cases
*/
public List<List<GraphPart>> switchCases = new ArrayList<>();
/**
* Switch breaks
*/
public List<GraphPart> switchBreaks = new ArrayList<>();
/**
* Constructor.

View File

@@ -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<MyEntry<DirectValueActionItem, ConstantPool>> variables, List<GraphSourceItem> functions, HashMap<DirectValueActionItem, ConstantPool> strings, HashMap<DirectValueActionItem, String> 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);
}

View File

@@ -300,6 +300,8 @@ public class AVM2LocalData extends BaseLocalData {
maxTempIndex = localData.maxTempIndex;
gotosUsed = localData.gotosUsed;
secondPassData = localData.secondPassData;
switchCases = localData.switchCases;
switchBreaks = localData.switchBreaks;
}
/**

View File

@@ -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<Boolean> hasEmptyStackPops, List<GraphTargetItem> currentRet, List<GotoItem> foundGotos,
Map<GraphPart, List<GraphTargetItem>> partCodes, Map<GraphPart, Integer> 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<GraphTargetItem> check(Reference<Boolean> hasEmptyStackPops, List<GraphTargetItem> currentRet, List<GotoItem> foundGotos,
Map<GraphPart, List<GraphTargetItem>> partCodes, Map<GraphPart, Integer> partCodePos,
@@ -1760,14 +1761,14 @@ public class AVM2Graph extends Graph {
Set<GraphPart> ignoredParts = new LinkedHashSet<>();
for (Loop el : loops) {
ignoredParts.add(el.loopContinue);
}
}
while (true) {
List<GraphTargetItem> 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<GraphTargetItem> 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<GraphTargetItem> output, LoopItem loopItem, BaseLocalData localData, List<Loop> loops, List<ThrowState> 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<GraphTargetItem> 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<GraphPart> catchParts = new HashSet<>();
Reference<GraphPart> partAfter = new Reference<>(null);
if (scopePos > -1) {
walkCatchParts(avm2LocalData.codeStats, part, ip, catchParts, scopePos, allParts, body.exceptions[e].isFinally());
} else {

View File

@@ -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;

View File

@@ -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<GraphTargetItem> output = new ArrayList<>();
FastActionListIterator iterator = list.iterator();

View File

@@ -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<String, Map<String, Trait>> 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<String, Graph> getSubGraphs() {
LinkedHashMap<String, Graph> 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<GraphTargetItem> translateViaGraph(Set<String> usedDeobfuscations, boolean needsUninitializedClassFieldsDetection, Map<String, Map<String, Trait>> uninitializedClassTraits, SecondPassData secondPassData, boolean insideDoInitAction, boolean insideFunction, HashMap<Integer, String> registerNames, HashMap<String, GraphTargetItem> variables, HashMap<String, GraphTargetItem> functions, List<Action> code, int version, int staticOperation, String path, String charset, int startIp) throws InterruptedException {
public static List<GraphTargetItem> translateViaGraph(Set<String> usedDeobfuscations, boolean needsUninitializedClassFieldsDetection, Map<String, Map<String, Trait>> uninitializedClassTraits, SecondPassData secondPassData, boolean insideDoInitAction, boolean insideFunction, HashMap<Integer, String> registerNames, HashMap<String, GraphTargetItem> variables, HashMap<String, GraphTargetItem> functions, List<Action> 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<GraphTargetItem> output, String path) {
public void finalProcessStack(TranslateStack stack, List<GraphTargetItem> output, String path) {
}
/**
@@ -289,11 +294,9 @@ public class ActionGraph extends Graph {
return !isSwitch;
}
@Override
protected void finalProcess(GraphTargetItem parent, List<GraphTargetItem> list, int level, FinalProcessLocalData localData, String path) throws InterruptedException {
if (level == 0) {
List<GraphTargetItem> 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<GraphTargetItem> items : b.getSubs()) {
makeDefineRegistersUp(items, definedRegisters);
}
for (List<GraphTargetItem> 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<GraphTargetItem> list) {
public SecondPassData prepareSecondPass(BaseLocalData localData, List<Loop> loops, List<ThrowState> throwStates, List<GraphTargetItem> list) {
ActionSecondPassData spd = new ActionSecondPassData();
Set<GraphPart> 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<GraphTargetItem> 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<GraphPart> processedIfs, List<GraphTargetItem> list, List<List<GraphPart>> allSwitchParts, List<List<GraphPart>> allSwitchOnFalseParts, List<List<GraphTargetItem>> allSwitchExpressions) {
private void checkSecondPassSwitches(BaseLocalData localData, List<Loop> loops, List<ThrowState> throwStates, List<List<GraphPart>> allSwitchCases, List<GraphPart> allSwitchBreaks, Set<GraphPart> processedIfs, List<GraphTargetItem> list, List<List<GraphPart>> allSwitchParts, List<List<GraphPart>> allSwitchOnFalseParts, List<List<GraphTargetItem>> 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<GraphPart> switchParts = new ArrayList<>();
List<GraphTargetItem> switchExpressions = new ArrayList<>();
List<GraphPart> switchOnFalseParts = new ArrayList<>();
List<GraphPart> 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<GraphTargetItem> sub : ((Block) item).getSubs()) {
checkSecondPassSwitches(processedIfs, sub, allSwitchParts, allSwitchOnFalseParts, allSwitchExpressions);
checkSecondPassSwitches(localData, loops, throwStates, allSwitchCases, allSwitchBreaks, processedIfs, sub, allSwitchParts, allSwitchOnFalseParts, allSwitchExpressions);
}
}
}

View File

@@ -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<String, Map<String, Trait>> uninitializedClassTraits, Set<String> usedDeobfuscations) {
public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, Map<String, Map<String, Trait>> uninitializedClassTraits, Set<String> usedDeobfuscations, List<List<GraphPart>> switchCases, List<GraphPart> 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<Integer, String> regNames, Map<String, Map<String, Trait>> uninitializedClassTraits, Set<String> usedDeobfuscations) {
public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, HashMap<Integer, String> regNames, Map<String, Map<String, Trait>> uninitializedClassTraits, Set<String> usedDeobfuscations, List<List<GraphPart>> switchCases, List<GraphPart> 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<Integer, String> regNames, HashMap<String, GraphTargetItem> variables, HashMap<String, GraphTargetItem> functions, Map<String, Map<String, Trait>> uninitializedClassTraits, Set<String> usedDeobfuscations) {
public ActionLocalData(SecondPassData secondPassData, boolean insideDoInitAction, HashMap<Integer, String> regNames, HashMap<String, GraphTargetItem> variables, HashMap<String, GraphTargetItem> functions, Map<String, Map<String, Trait>> uninitializedClassTraits, Set<String> usedDeobfuscations, List<List<GraphPart>> switchCases, List<GraphPart> 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;
}
}

View File

@@ -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<? extends GraphPart> getPrev(GraphPart node) {
return node.refs;
}
@Override
public List<? extends GraphPart> getNext(GraphPart node) {
return node.nextParts;
}
}

View File

@@ -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.
* <p>
* 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<Loop> loops) {
ArrayDeque<GraphPart> openedNodes = new ArrayDeque<>();
Set<GraphPart> closedNodes = new HashSet<>();
Set<LevelMapEdge> 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<Loop> 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<Loop> 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("<loops>");
for (Loop el : loops) {
System.err.println(el);
}
System.err.println("</loops>");
System.err.println("<loops2>");
for (Loop el : loops2) {
System.err.println(el);
}
System.err.println("</loops2>");
}
loops = loops2;
/*System.err.println("<loopspre>");
for (Loop el : loops) {
System.err.println(el);
@@ -993,11 +963,11 @@ public class Graph {
System.err.println("</loopspre>");//*/
List<GotoItem> gotos = new ArrayList<>();
Reference<Boolean> hasEmptyStackPops = new Reference<>(false);
Reference<Boolean> hasEmptyStackPops = new Reference<>(false);
List<GraphTargetItem> 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<GraphTargetItem> 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<List<GraphTargetItem>> newTodos = new ArrayList<>(blk.getSubs());
@@ -1374,7 +1343,7 @@ public class Graph {
todos.addAll(newTodos);
}
}
}
}
if (li instanceof ForItem) {
ForItem fi = (ForItem) li;
List<ContinueItem> 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<GraphTargetItem> list) {
if (!localData.gotosUsed.getVal()) {
public SecondPassData prepareSecondPass(BaseLocalData localData, List<Loop> loops, List<ThrowState> throwStates, List<GraphTargetItem> 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<GraphTargetItem> 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<GraphTargetItem> 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<Loop> loops, List<ThrowState> throwStates, List<GraphPart> stopPart) throws InterruptedException {
private void getLoops(BaseLocalData localData, GraphPart firstPart, List<Loop> loops, List<ThrowState> throwStates, List<GraphPart> 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<GraphPart, Integer> 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<GraphTargetItem> 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<GraphPart> 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);

View File

@@ -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);
}
/**

View File

@@ -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<GraphPartEdge> backEdges;
private final PrevNextWalker prevNextWalker;
public IgnoreEdgesPrevNextWalker(Set<GraphPartEdge> ignoredEdges, PrevNextWalker prevNextWalker) {
this.backEdges = ignoredEdges;
this.prevNextWalker = prevNextWalker;
}
@Override
public List<? extends GraphPart> getPrev(GraphPart node) {
List<? extends GraphPart> 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<? extends GraphPart> getNext(GraphPart node) {
List<? extends GraphPart> 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;
}
}

View File

@@ -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<GraphPart> stopParts = new ArrayList<>();
/**
* Loop body
*/
public Set<GraphPart> loopBody = new LinkedHashSet<>();
/**
* Edges outside
*/
public Set<GraphPartEdge> edgesOutside = new LinkedHashSet<>();
/**
* Order part
*/
public GraphPart orderPart;
/**
* Parent loop
*/
public Loop parentLoop;
/**
* Constructs a loop

View File

@@ -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<GraphPart> heads, List<Loop> loops, List<ThrowState> throwStates, Set<GraphPart> allParts, List<List<GraphPart>> switchParts, List<GraphPart> switchBreaks, boolean detectBreaks) throws InterruptedException {
PrevNextWalker pnw = new ThrowPrevNextWalker(throwStates);
List<GraphPart> loopHeaders = new ArrayList<>();
List<GraphPart> loopTails = new ArrayList<>();
List<Set<GraphPart>> loopBodys = new ArrayList<>();
Set<GraphPartEdge> 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<GraphPart, Loop> loopByHeader = new LinkedHashMap<>();
for (int i = 0; i < loopHeaders.size(); i++) {
GraphPart header = loopHeaders.get(i);
GraphPart tail = loopTails.get(i);
Set<GraphPart> 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<GraphPartEdge> ignoredEdges = new LinkedHashSet<>();
Set<GraphPart> multipleSameCase = new LinkedHashSet<>();
for (List<GraphPart> 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<? extends GraphPart> 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<Loop> loops) {
int n = loops.size();
List<Set<GraphPart>> sets = new ArrayList<>(n);
for (Loop loop : loops) {
sets.add(loop.loopBody);
}
for (int i = 0; i < n; i++) {
Set<GraphPart> child = sets.get(i);
int bestParentIndex = -1;
int bestParentSize = Integer.MAX_VALUE;
for (int j = 0; j < n; j++) {
if (i == j) {
continue;
}
Set<GraphPart> 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.
* <p>
* 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<GraphPart> heads, List<Loop> loops) {
ArrayDeque<GraphPart> openedNodes = new ArrayDeque<>();
Set<GraphPart> closedNodes = new HashSet<>();
Set<LevelMapEdge> 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<Loop> loops, Set<GraphPartEdge> backEdges, Set<GraphPart> allParts) {
List<GraphPart> sortedParts = new ArrayList<>(allParts);
sortedParts.sort(new Comparator<GraphPart>() {
@Override
public int compare(GraphPart o1, GraphPart o2) {
return o1.closedTime - o2.closedTime;
}
});
PrevNextWalker pnw = new BasicPrevNextWalker();
Set<GraphPart> ignored = new HashSet<>();
Loop currentLoop = null;
for (GraphPart ifPart : sortedParts) {
List<? extends GraphPart> 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<GraphPart> visited = new HashSet<>();
Stack<GraphPart> 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<Loop> loops, Set<GraphPartEdge> backEdges, List<ThrowState> throwStates, Set<GraphPartEdge> ignoredEdges, List<GraphPart> switchBreaks) {
PrevNextWalker pnw = new BasicPrevNextWalker();
//PrevNextWalker tpnw = new ThrowPrevNextWalker(throwStates); //new BasicPrevNextWalker();
Set<GraphPartEdge> throwEdges = new LinkedHashSet<>();
for (ThrowState ts : throwStates) {
for (GraphPart tp : ts.throwingParts) {
throwEdges.add(new GraphPartEdge(tp, ts.targetPart));
}
}
Map<Loop, PrevNextWalker> loopWalkers = new LinkedHashMap<>();
Map<Loop, List<ThrowState>> loopThrowStates = new LinkedHashMap<>();
//Find outside edges
for (Loop loop : loops) {
List<ThrowState> 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<GraphPart> parentContinues = new LinkedHashSet<>();
Set<GraphPart> 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<ThrowState> subThrowStates = loopThrowStates.get(loop);
Set<GraphPart> allOutReachable = new LinkedHashSet<>();
List<Set<GraphPart>> allReachable = new ArrayList<>();
Logger.getLogger(LoopDetector.class.getName()).log(Level.FINE, "loop {0} outside:", loopIndex);
Set<GraphPart> 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<GraphPart> 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<GraphPart> 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<CandidateResult> candidateList = new ArrayList<>();
for (GraphPart part : allOutReachable) {
int numBranches = 0;
for (Set<GraphPart> reachable : allReachable) {
if (reachable.contains(part)) {
numBranches++;
}
}
candidateList.add(new CandidateResult(part, numBranches, outParts.contains(part), parentBreaks.contains(part)));
}
candidateList.sort(new Comparator<CandidateResult>() {
@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<GraphPart> buildNaturalLoop(GraphPart tail, GraphPart header, PrevNextWalker pnw) {
Set<GraphPart> loop = new LinkedHashSet<>();
Deque<GraphPart> 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<GraphPart> parts, Set<GraphPartEdge> backEdges, PrevNextWalker pnw, boolean includeTheseParts) {
if (parts.isEmpty()) {
return null;
}
if (parts.size() == 1) {
return parts.iterator().next();
}
List<Set<GraphPart>> reachables = new ArrayList<>();
List<GraphPart> allPartsReachable = new ArrayList<>();
for (GraphPart part : parts) {
Set<GraphPart> reachable = getReachable(part, backEdges, new HashSet<>(), pnw);
reachables.add(reachable);
allPartsReachable.addAll(reachable);
}
if (!includeTheseParts) {
allPartsReachable.removeAll(parts);
}
allPartsReachable.sort(new Comparator<GraphPart>() {
@Override
public int compare(GraphPart o1, GraphPart o2) {
return o1.closedTime - o2.closedTime;
}
});
List<CandidateResult> candidates = new ArrayList<>();
for (GraphPart part : allPartsReachable) {
int numBranches = 0;
for (Set<GraphPart> reachable : reachables) {
if (reachable.contains(part)) {
numBranches++;
}
}
candidates.add(new CandidateResult(part, numBranches, 0));
}
candidates.sort(new Comparator<CandidateResult>() {
@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<GraphPart> parts, Set<GraphPartEdge> backEdges, PrevNextWalker pnw) {
List<Set<GraphPart>> reachables = new ArrayList<>();
List<GraphPart> allPartsReachable = new ArrayList<>();
for (GraphPart part : parts) {
Set<GraphPart> reachable = getReachable(part, backEdges, new HashSet<>(), pnw);
reachables.add(reachable);
allPartsReachable.addAll(reachable);
}
allPartsReachable.sort(new Comparator<GraphPart>() {
@Override
public int compare(GraphPart o1, GraphPart o2) {
return o1.closedTime - o2.closedTime;
}
});
for (GraphPart part : allPartsReachable) {
boolean allContains = true;
for (Set<GraphPart> reachable : reachables) {
if (!reachable.contains(part)) {
allContains = false;
break;
}
}
if (allContains) {
return part;
}
}
return null;
}*/
private Set<GraphPart> getReachable(GraphPart startPart, Set<GraphPartEdge> backEdges, Set<GraphPart> ignoredParts, PrevNextWalker pnw, Set<GraphPartEdge> ignoredEdges) {
Set<GraphPart> visited = new LinkedHashSet<>();
Queue<GraphPart> 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<GraphPartEdge> backEdges, PrevNextWalker pnw) {
if (part1 == part2) {
return null;
}
Stack<GraphPart> stack = new Stack<>();
stack.push(part1);
Set<GraphPart> 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<GraphPartEdge> backEdges, PrevNextWalker pnw) {
if (part1 == part2) {
return false;
}
Stack<GraphPart> stack = new Stack<>();
stack.push(part1);
Set<GraphPart> 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<GraphPartEdge> getBackEdges(GraphPart firstPart, List<ThrowState> throwStates) throws InterruptedException {
Stack<GraphPartEdge> stack = new Stack<>();
stack.push(new GraphPartEdge(null, firstPart));
Stack<List<GraphPart>> pathStack = new Stack<>();
pathStack.push(new ArrayList<>());
Set<GraphPart> visited = new HashSet<>();
Set<GraphPartEdge> backEdges = new LinkedHashSet<>();
while (!stack.isEmpty()) {
GraphPartEdge edge = stack.pop();
List<GraphPart> path = pathStack.pop();
if (path.contains(edge.to)) {
backEdges.add(edge);
}
GraphPart part = edge.to;
if (visited.contains(part)) {
continue;
}
visited.add(part);
List<GraphPart> 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<Loop> {
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;
}
}
}

View File

@@ -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<? extends GraphPart> getPrev(GraphPart node);
public List<? extends GraphPart> getNext(GraphPart node);
}

View File

@@ -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<List<GraphPart>> switchCases = new ArrayList<>();
/**
* List of breaks of each switch statement
*/
public List<GraphPart> switchBreaks = new ArrayList<>();
}

View File

@@ -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<GraphPart, List<GraphPart>> throwMap = new LinkedHashMap<>();
private final Map<GraphPart, List<GraphPart>> reverseThrowMap = new LinkedHashMap<>();
public ThrowPrevNextWalker(List<ThrowState> 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<? extends GraphPart> getPrev(GraphPart node) {
List<GraphPart> 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<? extends GraphPart> getNext(GraphPart node) {
List<GraphPart> ret = new ArrayList<>();
ret.addAll(node.nextParts);
if (throwMap.containsKey(node)) {
ret.addAll(throwMap.get(node));
}
return ret;
}
}

View File

@@ -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 {

View File

@@ -560,7 +560,7 @@ public class FlashPlayerTest {
task.actions = newActions;
List<GraphTargetItem> 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, "");