/*
* Copyright (C) 2010-2026 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 com.jpexs.decompiler.flash.BaseLocalData;
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.helpers.GraphTextWriter;
import com.jpexs.decompiler.graph.model.AndItem;
import com.jpexs.decompiler.graph.model.BinaryOpItem;
import com.jpexs.decompiler.graph.model.BranchStackResistant;
import com.jpexs.decompiler.graph.model.BreakItem;
import com.jpexs.decompiler.graph.model.CommaExpressionItem;
import com.jpexs.decompiler.graph.model.ContinueItem;
import com.jpexs.decompiler.graph.model.DefaultItem;
import com.jpexs.decompiler.graph.model.DoWhileItem;
import com.jpexs.decompiler.graph.model.DuplicateItem;
import com.jpexs.decompiler.graph.model.DuplicateSourceItem;
import com.jpexs.decompiler.graph.model.ExitItem;
import com.jpexs.decompiler.graph.model.FalseItem;
import com.jpexs.decompiler.graph.model.ForItem;
import com.jpexs.decompiler.graph.model.GotoItem;
import com.jpexs.decompiler.graph.model.IfItem;
import com.jpexs.decompiler.graph.model.IntegerValueItem;
import com.jpexs.decompiler.graph.model.IntegerValueTypeItem;
import com.jpexs.decompiler.graph.model.LabelItem;
import com.jpexs.decompiler.graph.model.LocalData;
import com.jpexs.decompiler.graph.model.LogicalOpItem;
import com.jpexs.decompiler.graph.model.LoopItem;
import com.jpexs.decompiler.graph.model.NotItem;
import com.jpexs.decompiler.graph.model.OrItem;
import com.jpexs.decompiler.graph.model.PopItem;
import com.jpexs.decompiler.graph.model.PushItem;
import com.jpexs.decompiler.graph.model.ScriptEndItem;
import com.jpexs.decompiler.graph.model.SetTemporaryItem;
import com.jpexs.decompiler.graph.model.SwitchItem;
import com.jpexs.decompiler.graph.model.TernarOpItem;
import com.jpexs.decompiler.graph.model.TrueItem;
import com.jpexs.decompiler.graph.model.UniversalLoopItem;
import com.jpexs.decompiler.graph.model.WhileItem;
import com.jpexs.decompiler.graph.precontinues.GraphPrecontinueDetector;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Reference;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Graph class. This is the main class where decompilation process is done. It
* translates GraphSourceItems to GraphTargetItems.
*
* Subclasses of Graph are used for different types of decompilation.
*
* @author JPEXS
*/
public class Graph {
/**
* Graph entry points
*/
public List heads;
/**
* Graph target dialect
*/
protected GraphTargetDialect dialect;
/**
* Graph source code
*/
protected GraphSource code;
/**
* Exceptions in the graph
*/
private final List exceptions;
/**
* Graph startIp
*/
protected int startIp = 0;
/**
* Debug flag to print all parts
*/
private final boolean debugPrintAllParts = false;
/**
* Debug flag to print loop list
*/
private final boolean debugPrintLoopList = false;
/**
* Debug flag to print getLoops
*/
private final boolean debugGetLoops = false;
/**
* Debug flag to print decompilation progress (printGraph method)
*/
private final boolean debugPrintGraph = false;
/**
* Debug flag to not process Ifs
*/
protected boolean debugDoNotProcess = false;
/**
* Logger
*/
private static final Logger logger = Logger.getLogger(Graph.class.getName());
/**
* Gets the graphSource
*
* @return GraphSource
*/
public GraphSource getGraphCode() {
return code;
}
/**
* Gets sub-graphs
*
* @return Sub-graphs
*/
public LinkedHashMap getSubGraphs() {
return new LinkedHashMap<>();
}
/**
* Constructs a new Graph.
*
* @param dialect Dialect
* @param code Graph source
* @param exceptions Exceptions in the graph
* @param startIp Start ip
*/
public Graph(GraphTargetDialect dialect, GraphSource code, List exceptions, int startIp) {
this.dialect = dialect;
this.code = code;
this.exceptions = exceptions;
this.startIp = startIp;
}
/**
* Initializes the graph.
*
* @param localData Local data
* @throws InterruptedException On interrupt
*/
public void init(BaseLocalData localData) throws InterruptedException {
if (heads != null) {
return;
}
heads = makeGraph(code, new ArrayList<>(), exceptions);
int time = 1;
List ordered = new ArrayList<>();
List visited = new ArrayList<>();
for (GraphPart head : heads) {
time = head.setTime(time, ordered, visited);
head.setNumblocks(1);
}
}
/**
* 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.
*/
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;
}
}
/**
* Gets graph exceptions.
*
* @return List of exceptions
*/
public List getExceptions() {
return exceptions;
}
/**
* Populates all parts available from the part.
*
* @param part Source part
* @param allParts Result
*/
protected static void populateParts(GraphPart part, Set allParts) {
if (allParts.contains(part)) {
return;
}
allParts.add(part);
for (GraphPart p : part.nextParts) {
populateParts(p, allParts);
}
}
/**
* Deep copies GraphPart.
*
* @param part Source part
* @return Deep copy of the part
*/
public GraphPart deepCopy(GraphPart part) {
return deepCopy(part, new HashMap<>());
}
/**
* Deep copies GraphPart.
*
* @param part Source part
* @param copies Already copied parts
* @return Deep copy of the part
*/
private GraphPart deepCopy(GraphPart part, Map copies) {
GraphPart copy = copies.get(part);
if (copy != null) {
return copy;
}
copy = new GraphPart(part.start, part.end);
copy.path = part.path;
copies.put(part, copy);
copy.nextParts = new ArrayList<>();
for (int i = 0; i < part.nextParts.size(); i++) {
copy.nextParts.add(deepCopy(part.nextParts.get(i), copies));
}
for (int i = 0; i < part.refs.size(); i++) {
copy.refs.add(deepCopy(part.refs.get(i), copies));
}
return copy;
}
/**
* Resets the graph.
*
* @param part Part to reset
* @param visited Visited parts
*/
public void resetGraph(GraphPart part, Set visited) {
if (visited.contains(part)) {
return;
}
visited.add(part);
int pos = 0;
for (GraphPart p : part.nextParts) {
if (!visited.contains(p)) {
p.path = part.path.sub(pos, p.end);
}
resetGraph(p, visited);
pos++;
}
}
/**
* Gets reachable parts from the part.
*
* @param localData Local data
* @param part Source part
* @param ret Result
* @param loops Loops
* @param throwStates Throw states
*/
protected void getReachableParts(BaseLocalData localData, GraphPart part, LinkedHashSet ret, List loops, List throwStates) {
// use LinkedHashSet to preserve order
getReachableParts(localData, part, ret, loops, throwStates, true);
}
/**
* Gets reachable parts from the part.
*
* @param localData Local data
* @param part Source part
* @param ret Result
* @param loops Loops
* @param throwStates Throw states
* @param first First call
*/
private void getReachableParts(BaseLocalData localData, GraphPart part, LinkedHashSet ret, List loops, List throwStates, boolean first) {
// todo: honfika: why call with first = true parameter always?
Stack stack = new Stack<>();
GraphPartQueue queue = new GraphPartQueue();
queue.add(part);
stack.add(queue);
stacknext:
while (!stack.isEmpty()) {
queue = stack.peek();
if (!queue.isEmpty()) {
part = queue.remove();
} else if (queue.currentLoop != null) {
Loop cLoop = queue.currentLoop;
part = cLoop.loopBreak;
queue.currentLoop = null;
if (ret.contains(part)) {
continue;
}
ret.add(part);
cLoop.reachableMark = 2;
} else {
stack.pop();
continue;
}
for (Loop l : loops) {
l.reachableMark = 0;
}
Loop currentLoop = null;
for (Loop l : loops) {
if ((l.phase == 1) || (l.reachableMark == 1)) {
if (l.loopContinue == part) {
continue stacknext;
}
if (l.loopBreak == part) {
continue stacknext;
}
if (l.loopPreContinue == part) {
continue stacknext;
}
}
if (l.reachableMark == 0) {
if (l.loopContinue == part) {
l.reachableMark = 1;
currentLoop = l;
}
}
}
GraphPartQueue newParts = new GraphPartQueue();
List nextParts = new ArrayList<>(getNextParts(localData, part));
for (ThrowState ts : throwStates) {
if (ts.state != 1) {
if (ts.throwingParts.contains(part)) {
newParts.add(ts.targetPart);
}
}
}
loopnext:
for (GraphPart nextRaw : nextParts) {
GraphPart next = checkPart(null, localData, part, nextRaw, null);
if (next == null) {
continue;
}
if (next.start >= code.size()) {
continue;
}
for (Loop l : loops) {
if ((l.phase == 1) || (l.reachableMark == 1)) {
if (l.loopContinue == next) {
continue loopnext;
}
if (l.loopBreak == next) {
continue loopnext;
}
if (l.loopPreContinue == next) {
continue loopnext;
}
}
}
if (!ret.contains(next)) {
newParts.add(next);
}
}
ret.addAll(newParts);
if (currentLoop != null && currentLoop.loopBreak != null) {
newParts.currentLoop = currentLoop;
}
if (!newParts.isEmpty() || newParts.currentLoop != null) {
stack.add(newParts);
}
}
}
/**
* Gets common successor of the next parts of the part.
*
* @param localData Local data
* @param part Part
* @param loops Loops
* @param throwStates Throw states
* @return Common successor
* @throws InterruptedException On interrupt
*/
public GraphPart getNextCommonPart(BaseLocalData localData, GraphPart part, List loops, List throwStates) throws InterruptedException {
return getCommonPart(localData, part, getNextParts(localData, part), loops, throwStates);
}
/**
* Gets common successor of the parts.
*
* TODO: Make this faster!
*
* @param localData Local data
* @param prev Previous part
* @param parts Parts
* @param loops Loops
* @param throwStates Throw states
* @return Common successor
* @throws InterruptedException On interrupt
*/
public GraphPart getCommonPart(BaseLocalData localData, GraphPart prev, List parts, List loops, List throwStates) throws InterruptedException {
if (parts.isEmpty()) {
return null;
}
List loopContinues = new ArrayList<>();
List loopBreaks = new ArrayList<>();
for (Loop l : loops) {
if (l.phase == 1) {
loopContinues.add(l.loopContinue);
if (l.loopPreContinue != null) {
loopContinues.add(l.loopPreContinue);
}
if (l.loopBreak != null) {
loopBreaks.add(l.loopBreak);
}
}
}
for (GraphPart p : parts) {
if (loopContinues.contains(p)) {
break;
}
if (loopBreaks.contains(p)) {
break;
}
boolean common = true;
for (GraphPart q : parts) {
if (q == p) {
continue;
}
if (!q.leadsTo(localData, this, code, p, loops, throwStates)) {
common = false;
break;
}
}
if (common) {
return p;
}
}
List> reachable = new ArrayList<>();
for (GraphPart p : parts) {
LinkedHashSet r1 = new LinkedHashSet<>();
getReachableParts(localData, p, r1, loops, throwStates);
r1.add(p);
reachable.add(r1);
}
Set first = reachable.get(0);
for (GraphPart p : first) {
boolean common = true;
for (Set r : reachable) {
if (!r.contains(p)) {
common = false;
break;
}
}
if (common) {
if (loopContinues.contains(p)) {
return null;
}
if (loopBreaks.contains(p)) {
return null;
}
return p;
}
}
return null;
}
/**
* Gets common successor of most of the nextparts of the part.
*
* This is used mostly in switch detection.
*
* @param localData Local data
* @param parts Parts
* @param loops Loops
* @param throwStates Throw states
* @param stopPart Stop part
* @return Most common successor
* @throws InterruptedException On interrupt
*/
public GraphPart getMostCommonPart(BaseLocalData localData, List parts, List loops, List throwStates, List stopPart) throws InterruptedException {
if (parts.isEmpty()) {
return null;
}
Set s = new HashSet<>(parts); //unique
parts = new ArrayList<>(s); //make local copy
List loopContinues = new ArrayList<>();
for (Loop l : loops) {
if (l.phase == 1) {
loopContinues.add(l.loopContinue);
loopContinues.add(l.loopPreContinue);
}
}
Map> reachable = new HashMap<>();
Set allReachable = new LinkedHashSet<>();
for (GraphPart p : parts) {
LinkedHashSet r1 = new LinkedHashSet<>();
getReachableParts(localData, p, r1, loops, throwStates);
Set r2 = new LinkedHashSet<>();
r2.add(p);
r2.addAll(r1);
reachable.put(p, r2);
allReachable.add(p);
allReachable.addAll(r1);
}
Comparator comparator = new Comparator() {
@Override
public int compare(PartCommon o1, PartCommon o2) {
int levelCompare = o2.level - o1.level;
if (levelCompare == 0) {
try {
if (o1.part.leadsTo(localData, Graph.this, code, o2.part, loops, throwStates)) {
return -1;
}
if (o2.part.leadsTo(localData, Graph.this, code, o1.part, loops, throwStates)) {
return 1;
}
return 0;
} catch (InterruptedException ex) {
//ignore
return 0;
}
//return o1.part.discoveredTime - o2.part.discoveredTime;
} else {
return levelCompare;
}
}
};
Set commonSet = new TreeSet<>();
for (GraphPart r : allReachable) {
if (loopContinues.contains(r)) {
continue;
}
boolean common = true;
int commonLevel = 0;
for (GraphPart p : parts) {
if (p == r) {
commonLevel++;
continue;
}
if (!reachable.get(p).contains(r)) {
common = false;
} else {
commonLevel++;
}
}
if (common) {
Stack toProcess = new Stack<>();
Set visited = new HashSet<>();
toProcess.addAll(parts);
loopprocess:
while (!toProcess.isEmpty()) {
GraphPart p = toProcess.pop();
if (p == r) {
continue;
}
if (loopContinues.contains(p)) {
continue;
}
if (visited.contains(p)) {
continue;
}
visited.add(p);
for (GraphPart n : p.nextParts) {
if (n == r) {
continue;
}
if (loopContinues.contains(n)) {
continue;
}
if (visited.contains(n)) {
continue;
}
if (!n.leadsTo(localData, this, code, r, loops, throwStates)) {
common = false;
break loopprocess;
}
}
toProcess.addAll(p.nextParts);
}
if (common) {
if (debugPrintLoopList) {
System.err.println("all time common: " + r);
}
return r;
}
}
/*if (commonLevel > maxCommonLevel) {
maxCommonPart = r;
maxCommonLevel = commonLevel;
commonSet.add(e)
}*/
commonSet.add(new PartCommon(r, commonLevel));
}
/*Set partsLeadingToStopPart = new LinkedHashSet<>();
if (stopPart != null) {
for (GraphPart p : parts) {
for (GraphPart sp : stopPart) {
if (sp == p || p.leadsTo(localData, this, code, sp, new ArrayList(), throwStates, false)) {
partsLeadingToStopPart.add(p);
}
}
}
}
if (debugPrintLoopList) {
System.err.println("commonset:");
for (PartCommon pc : commonSet) {
System.err.println("- " + pc);
}
System.err.println("parts:");
for (GraphPart p : parts) {
System.err.println("- " + p);
}
if (stopPart != null) {
System.err.println("stopparts:");
for (GraphPart p : stopPart) {
System.err.println("- " + p);
}
}
System.err.println("partsLeadingToStopPart:");
for (GraphPart p : partsLeadingToStopPart) {
System.err.println("- " + p);
}
}*/
loopc:
for (PartCommon pc : commonSet) {
/*for (GraphPart p : partsLeadingToStopPart) {
if (p != pc.part && !p.leadsTo(localData, this, code, pc.part, loops, throwStates, false)) {
if (debugPrintLoopList) {
System.err.println("ignoring " + pc.part + ", " + p + " does not lead to it");
}
continue loopc;
}
}*/
if (pc.level <= 1) {
return null;
}
if (debugPrintLoopList) {
StringBuilder sb = new StringBuilder();
for (GraphPart p : parts) {
sb.append(" ");
sb.append(p.toString());
}
System.err.println("most common part of" + sb.toString() + " is " + pc.part);
}
return pc.part;
}
return null;
}
/**
* Common part
*/
private class PartCommon implements Comparable {
/**
* Part
*/
public GraphPart part;
/**
* Level - how many parts are common
*/
public int level;
/**
* Constructs a new PartCommon
*
* @param part Part
* @param level Level
*/
public PartCommon(GraphPart part, int level) {
this.part = part;
this.level = level;
}
/**
* Compares to another PartCommon
*
* @param o Other PartCommon
* @return Comparison result
*/
@Override
public int compareTo(PartCommon o) {
int ret = o.level - level;
if (ret == 0) {
ret = part.closedTime - o.part.closedTime;
if (ret == 0) { //some nodes may be split in half and thus have same closedTime - like in try..catch
return part.start - o.part.start;
}
}
return ret;
}
/**
* To string
*
* @return String representation
*/
@Override
public String toString() {
return "" + part.toString() + " (level=" + level + ")";
}
/**
* Hash code
*
* @return Hash code
*/
@Override
public int hashCode() {
int hash = 5;
hash = 71 * hash + Objects.hashCode(this.part);
hash = 71 * hash + this.level;
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 PartCommon other = (PartCommon) obj;
if (this.level != other.level) {
return false;
}
return Objects.equals(this.part, other.part);
}
}
/**
* Method called after populating all parts.
*
* @param allParts All parts
*/
protected void afterPopulateAllParts(Set allParts) {
}
/**
* Gets throw states. Override this method to get throw states.
*
* @param localData Local data
* @param allParts All parts
* @return List of ThrowStates
*/
protected List getThrowStates(BaseLocalData localData, Set allParts) {
return new ArrayList<>();
}
/**
* Translates the graph - decompiles.
*
* @param localData Local data
* @param staticOperation Unused
* @param path Path
* @return List of GraphTargetItems
* @throws InterruptedException On interrupt
*/
public List translate(BaseLocalData localData, int staticOperation, String path) throws InterruptedException {
Set allParts = new HashSet<>();
for (GraphPart head : heads) {
populateParts(head, allParts);
}
afterPopulateAllParts(allParts);
if (debugPrintAllParts) {
System.err.println("parts:");
for (GraphPart p : allParts) {
System.err.print(p);
if (!getNextParts(localData, p).isEmpty()) {
System.err.print(", next: ");
}
boolean first = true;
for (GraphPart n : getNextParts(localData, p)) {
if (!first) {
System.err.print(",");
}
System.err.print(n);
first = false;
}
System.err.println("");
}
System.err.println("/parts");
}
TranslateStack stack = new TranslateStack(path);
List throwStates = getThrowStates(localData, allParts);
beforeGetLoops(localData, path, allParts, throwStates);
List loops = new ArrayList<>();
getLoops(localData, heads.get(0), loops, throwStates, null);
afterGetLoops(localData, path, allParts);
//TODO: Make getPrecontinues faster
getBackEdges(localData, loops, throwStates);
calculateClosedTime(loops);
new GraphPrecontinueDetector().detectPrecontinues(heads, allParts, loops, throwStates);
if (debugPrintLoopList) {
System.err.println("");
for (Loop el : loops) {
System.err.println(el);
}
System.err.println("");
}
/*System.err.println("");
for (Loop el : loops) {
System.err.println(el);
}
System.err.println("");//*/
List gotos = new ArrayList<>();
List ret = printGraph(gotos, new HashMap<>(), new HashMap<>(), new HashSet<>(), localData, stack, allParts, null, heads.get(0), null, null, loops, throwStates, staticOperation, path);
if (localData.secondPassData == null) {
SecondPassData secondPassData = prepareSecondPass(localData, ret);
if (secondPassData != null) {
throw new SecondPassException(secondPassData);
}
}
processIfGotos2(new ArrayList<>(), gotos, ret, ret);
processIfGotos(gotos, ret, ret);
processScriptEnd(ret);
Map usages = new HashMap<>();
Map lastUsage = new HashMap<>();
for (GotoItem gi : gotos) {
if (!usages.containsKey(gi.labelName)) {
usages.put(gi.labelName, 0);
}
usages.put(gi.labelName, usages.get(gi.labelName) + 1);
lastUsage.put(gi.labelName, gi);
}
for (String labelName : usages.keySet()) {
logger.log(Level.FINE, "usage - {0}: {1}", new Object[]{labelName, usages.get(labelName)});
if (usages.get(labelName) == 1) {
lastUsage.get(labelName).labelName = null;
}
}
expandGotos(ret);
processIfs(ret);
propagateBreaks(ret);
finalProcessStack(stack, ret, path);
makeAllCommands(ret, stack);
finalProcessAll(null, ret, 0, getFinalData(localData, loops, throwStates), path);
//fixSwitchEnds(ret);
handleSetTemporaryDeclarations(ret);
return ret;
}
private void handleSetTemporaryDeclarations(List items) {
for (int i = 0; i < items.size(); i++) {
GraphTargetItem item = items.get(i);
if (item instanceof SetTemporaryItem) {
SetTemporaryItem s = (SetTemporaryItem) item;
s.declaration = true;
}
/*if (item instanceof DuplicateSourceItem) {
DuplicateSourceItem s = (DuplicateSourceItem) item;
s.declaration = true;
}*/
Reference iRef = new Reference<>(i);
item.visitRecursivelyNoBlock(new AbstractGraphTargetRecursiveVisitor() {
@Override
public void visit(GraphTargetItem item, Stack parentStack) {
if (item instanceof SetTemporaryItem) {
SetTemporaryItem st = (SetTemporaryItem) item;
SetTemporaryItem dec = new SetTemporaryItem(dialect, null, null, null, st.tempIndex, st.getSuffix(), st.refCount);
dec.declaration = true;
items.add(iRef.getVal(), dec);
iRef.setVal(iRef.getVal() + 1);
}
/*if (item instanceof DuplicateSourceItem) {
DuplicateSourceItem st = (DuplicateSourceItem) item;
SetTemporaryItem dec = new SetTemporaryItem(dialect, null, null, null, st.tempIndex, "");
dec.declaration = true;
items.add(iRef.getVal(), dec);
iRef.setVal(iRef.getVal() + 1);
}*/
}
});
i = iRef.getVal();
if (item instanceof Block) {
Block blk = (Block) item;
for (List sub : blk.getSubs()) {
handleSetTemporaryDeclarations(sub);
}
}
}
}
/*
private void fixSwitchEnds(List items) {
for (GraphTargetItem item : items) {
if (item instanceof SwitchItem) {
fixSwitchEnd((SwitchItem) item);
}
if (item instanceof Block) {
Block blk = (Block) item;
for (List sub : blk.getSubs()) {
fixSwitchEnds(sub);
}
}
}
}
*/
/**
* This is needed to avoid loop/switch identifiers in AS1/2. AS3 supports
* them, but AS1/2 not.
*
*
* loop1: switch(a) { //has loop identifier
* case 1:
* trace("1");
* break;
* case 2: //last case
* trace("2");
* switch(b) { //last command is switch case 3:
* case 4:
* trace("4");
* break loop1; //breaks parent loop
* case 5:
* trace("5");
* break loop1;
* case 6:
* trace("6");
* }
* }
*
* ==>
*
* switch(a) {
* case 1:
* trace("1");
* break;
* case 2:
* trace("2");
* switch(b) {
* case 3:
* case 4:
* trace("4");
* break;
* case 5:
* trace("5");
* break;
* case 6:
* trace("6");
* }
* }
*
* It also does similar thing to loops and continues.
*
*
*
*
* @param list Items
*/
protected void propagateBreaks(List list) {
for (int i = 0; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
if (item instanceof Block) {
Block bl = (Block) item;
for (List subList : bl.getSubs()) {
propagateBreaks(subList);
}
}
if (item instanceof SwitchItem) {
SwitchItem sw = (SwitchItem) item;
if (sw.caseCommands.isEmpty()) {
continue;
}
List lastCase = sw.caseCommands.get(sw.caseCommands.size() - 1);
if (lastCase.isEmpty()) {
continue;
}
//Replace breaks in lastCommands loops
for (int h = 0; h < sw.caseCommands.size(); h++) {
List com = sw.caseCommands.get(h);
List com2 = com;
if (com.isEmpty()) {
continue;
}
boolean isLastCase = h == sw.caseCommands.size() - 1;
int last = com.size() - 1;
boolean hasBreak = ((com.get(last) instanceof BreakItem) && (((BreakItem) com.get(last)).loopId == sw.loop.id));
if (!isLastCase && !hasBreak) {
continue;
}
com2 = new ArrayList<>(com);
if (hasBreak) {
com2.remove(com2.size() - 1);
}
List> todos = new ArrayList<>();
todos.add(com2);
for (int j = 0; j < todos.size(); j++) {
List currentList = todos.get(j);
if (currentList.isEmpty()) {
continue;
}
GraphTargetItem lastCommand = currentList.get(currentList.size() - 1);
if (lastCommand instanceof LoopItem) {
if (lastCommand instanceof SwitchItem) {
SwitchItem innerSwitch = (SwitchItem) lastCommand;
List> subs = innerSwitch.getSubs();
for (int k = 0; k < subs.size(); k++) {
List caseCommands = subs.get(k);
if (caseCommands.isEmpty()) {
continue;
}
lastCommand = caseCommands.get(caseCommands.size() - 1);
if ((lastCommand instanceof BreakItem) || (lastCommand instanceof ContinueItem) || (lastCommand instanceof ExitItem)) {
changeBreakToBreak(caseCommands, sw.loop.id, innerSwitch.loop.id);
} else if (k == subs.size() - 1) {
changeBreakToBreak(caseCommands, sw.loop.id, innerSwitch.loop.id);
}
}
fixSwitchEnd(innerSwitch);
} else {
LoopItem innerLoop = (LoopItem) lastCommand;
changeBreakToBreak(innerLoop.getBaseBodyCommands(), sw.loop.id, innerLoop.loop.id);
//Detect While
if (innerLoop instanceof UniversalLoopItem) {
List body = innerLoop.getBaseBodyCommands();
if (!body.isEmpty()) {
if (body.get(0) instanceof IfItem) {
IfItem ifi = (IfItem) body.get(0);
if (ifi.onFalse.isEmpty() && ifi.onTrue.size() == 1) {
if (ifi.onTrue.get(0) instanceof BreakItem) {
BreakItem br = (BreakItem) ifi.onTrue.get(0);
if (br.loopId == innerLoop.loop.id) {
List expr = new ArrayList<>();
expr.add(ifi.expression.invert(null));
body.remove(0);
WhileItem wh = new WhileItem(dialect, innerLoop.getSrc(), innerLoop.getLineStartItem(), innerLoop.loop, expr, body);
if (currentList == com2) {
com.set(com.size() - 1 - (hasBreak ? 1 : 0), wh);
} else {
currentList.set(currentList.size() - 1, wh);
}
}
}
}
}
}
}
}
} else if (lastCommand instanceof Block) {
Block blk = (Block) lastCommand;
List> newTodos = new ArrayList<>(blk.getSubs());
if (!newTodos.isEmpty() && lastCommand instanceof SwitchItem) {
List> newTodos2 = new ArrayList<>();
newTodos2.add(newTodos.get(newTodos.size() - 1));
newTodos = newTodos2;
}
todos.addAll(newTodos);
}
}
}
// Remove breaks of switch in last commands of last case:
List> todos = new ArrayList<>();
todos.add(lastCase);
for (int j = 0; j < todos.size(); j++) {
List currentList = todos.get(j);
if (currentList.isEmpty()) {
continue;
}
GraphTargetItem lastCommand = currentList.get(currentList.size() - 1);
if (lastCommand instanceof BreakItem) {
BreakItem bi = (BreakItem) lastCommand;
if (bi.loopId == sw.loop.id) {
currentList.remove(currentList.size() - 1);
}
} else if (lastCommand instanceof LoopItem) {
//ignore
} else if (lastCommand instanceof Block) {
Block blk = (Block) lastCommand;
List> newTodos = new ArrayList<>(blk.getSubs());
if (!newTodos.isEmpty() && lastCommand instanceof SwitchItem) {
//empty
} else {
todos.addAll(newTodos);
}
}
}
//-----------------------
if (lastCase.isEmpty() || !(lastCase.get(lastCase.size() - 1) instanceof SwitchItem)) {
continue;
}
SwitchItem swInner = (SwitchItem) lastCase.get(lastCase.size() - 1);
List breaks = swInner.getBreaks();
for (BreakItem br : breaks) {
if (br.loopId == sw.loop.id) {
br.loopId = swInner.loop.id;
}
}
}
if (item instanceof LoopItem) {
LoopItem li = (LoopItem) item;
if (li.hasBaseBody()) {
List body = li.getBaseBodyCommands();
if (!body.isEmpty()) {
List> todos = new ArrayList<>();
todos.add(body);
for (int j = 0; j < todos.size(); j++) {
List currentList = todos.get(j);
if (currentList.isEmpty()) {
continue;
}
GraphTargetItem lastCommand = currentList.get(currentList.size() - 1);
if (lastCommand instanceof LoopItem) {
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) {
IfItem ifi = (IfItem) loopItem.commands.get(loopItem.commands.size() - 1);
boolean inverted = false;
boolean found = false;
if (ifi.onFalse.isEmpty() && (ifi.onTrue.size() == 1) && (ifi.onTrue.get(0) instanceof BreakItem)) {
BreakItem bi = (BreakItem) ifi.onTrue.get(0);
if (bi.loopId == loopItem.loop.id) {
found = true;
inverted = true;
}
} else if (ifi.onTrue.isEmpty() && (ifi.onFalse.size() == 1) && (ifi.onFalse.get(0) instanceof BreakItem)) {
BreakItem bi = (BreakItem) ifi.onFalse.get(0);
if (bi.loopId == loopItem.loop.id) {
found = true;
}
}
if (found) {
loopItem.commands.remove(loopItem.commands.size() - 1);
GraphTargetItem expressionSingle = ifi.expression;
if (inverted) {
expressionSingle = expressionSingle.invert(null);
}
List expression = new ArrayList<>();
expression.add(expressionSingle);
DoWhileItem doWhile = new DoWhileItem(dialect, loopItem.getSrc(), loopItem.getLineStartItem(), loopItem.loop, loopItem.commands, expression);
currentList.set(currentList.size() - 1, doWhile);
}
}
}
} else if (lastCommand instanceof Block) {
Block blk = (Block) lastCommand;
List> newTodos = new ArrayList<>(blk.getSubs());
if (!newTodos.isEmpty() && lastCommand instanceof SwitchItem) {
List> newTodos2 = new ArrayList<>();
newTodos2.add(newTodos.get(newTodos.size() - 1));
newTodos = newTodos2;
}
todos.addAll(newTodos);
}
}
}
if (li instanceof ForItem) {
ForItem fi = (ForItem) li;
List continues = fi.getContinues();
boolean hasContinue = false;
for (ContinueItem ci : continues) {
if (ci.loopId == fi.loop.id) {
hasContinue = true;
break;
}
}
//No continue, change for back to while
if (!hasContinue) {
List expr = new ArrayList<>();
expr.add(fi.expression);
WhileItem wh = new WhileItem(dialect, fi.getSrc(), fi.getLineStartItem(), fi.loop, expr, fi.commands);
wh.commands.addAll(fi.finalCommands);
list.set(i, wh);
for (int j = 0; j < fi.firstCommands.size(); j++) {
list.add(i, fi.firstCommands.get(j));
i++;
}
}
}
}
}
}
}
private void changeContinueToBreak(Block blk, long continueLoopId, long breakLoopId) {
for (List subItems : blk.getSubs()) {
changeContinueToBreak(subItems, continueLoopId, breakLoopId);
}
if (blk instanceof SwitchItem) {
fixSwitchEnd((SwitchItem) blk);
}
}
private void changeContinueToBreak(List items, long continueLoopId, long breakLoopId) {
for (int i = 0; i < items.size(); i++) {
GraphTargetItem item = items.get(i);
if (item instanceof Block) {
Block blk = (Block) item;
changeContinueToBreak(blk, continueLoopId, breakLoopId);
}
if (item instanceof ContinueItem) {
ContinueItem ci = (ContinueItem) item;
if (ci.loopId == continueLoopId) {
items.set(i, new BreakItem(dialect, ci.getSrc(), ci.getLineStartItem(), breakLoopId));
}
}
}
}
private void changeBreakToBreak(List items, long breakLoopIdFrom, long breakLoopIdTo) {
for (int i = 0; i < items.size(); i++) {
GraphTargetItem item = items.get(i);
if (item instanceof Block) {
Block blk = (Block) item;
for (List subItems : blk.getSubs()) {
changeBreakToBreak(subItems, breakLoopIdFrom, breakLoopIdTo);
}
}
if (item instanceof BreakItem) {
BreakItem ci = (BreakItem) item;
if (ci.loopId == breakLoopIdFrom) {
ci.loopId = breakLoopIdTo;
}
}
}
}
/**
* Prepares second pass data. Can return null when no second pass will
* happen. Override this method to prepare second pass data.
*
* @param localData Local data
* @param list List of GraphTargetItems
* @return Second pass data or null
*/
protected SecondPassData prepareSecondPass(BaseLocalData localData, List list) {
if (localData.allSwitchParts.isEmpty() && !localData.gotosUsed.getVal()) {
return null;
}
SecondPassData spd = new SecondPassData();
spd.allSwitchParts.addAll(localData.allSwitchParts);
return spd;
}
/**
* Process various items. Override this method to process items.
*
* @param list List of GraphTargetItems
* @param lastLoopId Last loop id
*/
protected void processOther(List list, long lastLoopId) {
}
/**
* Process switches.
*
* @param list List of GraphTargetItems
*/
protected final void processSwitches(List list) {
processSwitches(list, -1);
}
/*
while(something){
switch(xx){
case 1:
trace("1");
continue;
case 2:
trace("2");
continue;
case 3:
break;
default:
continue;
}
item
}
=>
while(something){
switch(xx){
case 1:
trace("1");
break;
case 2:
trace("2");
break;
case 3:
item
break;
default:
break;
}
}
This will fix precontinue handler which detects multiple continues
*/
/**
* Process switches.
*
* @param list List of GraphTargetItems
* @param lastLoopId Last loop id
*/
protected void processSwitches(List list, long lastLoopId) {
loopi:
for (int i = 0; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
if (item instanceof SwitchItem) {
SwitchItem swi = (SwitchItem) item;
Set allItems = swi.getAllSubItemsRecursively();
int breakCount = 0;
for (GraphTargetItem it : allItems) {
if (it instanceof BreakItem) {
BreakItem br = (BreakItem) it;
if (br.loopId == swi.loop.id) {
breakCount++;
if (breakCount > 2) {
continue loopi;
}
}
}
}
if (!swi.caseCommands.isEmpty()) {
List lastCommands = swi.caseCommands.get(swi.caseCommands.size() - 1);
if (lastCommands.isEmpty() && breakCount > 0) {
continue loopi;
}
if (breakCount > 0 && !(lastCommands.get(lastCommands.size() - 1) instanceof ContinueItem)
&& !(lastCommands.get(lastCommands.size() - 1) instanceof ExitItem)) {
continue loopi;
}
}
int breakCaseIndex = -1;
for (int c = 0; c < swi.caseCommands.size(); c++) {
List commands = swi.caseCommands.get(c);
if (!commands.isEmpty()) {
if (commands.get(commands.size() - 1) instanceof BreakItem) {
if (commands.size() == 1) {
BreakItem br = (BreakItem) commands.get(commands.size() - 1);
if (br.loopId == swi.loop.id) {
breakCaseIndex = c;
break;
}
}
}
}
if (c == swi.caseCommands.size() - 1) {
if (commands.isEmpty()) {
breakCaseIndex = c;
break;
}
if (!(commands.get(commands.size() - 1) instanceof ContinueItem)
&& !(commands.get(commands.size() - 1) instanceof ExitItem)) {
breakCaseIndex = c;
break;
}
}
}
if (breakCount == 2) {
if (breakCaseIndex <= 0) {
continue loopi;
}
if (swi.caseCommands.get(breakCaseIndex - 1).isEmpty()) {
continue loopi;
}
GraphTargetItem ti = swi.caseCommands.get(breakCaseIndex - 1).get(swi.caseCommands.get(breakCaseIndex - 1).size() - 1);
if (!(ti instanceof BreakItem)) {
continue loopi;
}
BreakItem br = (BreakItem) ti;
if (br.loopId != swi.loop.id) {
continue loopi;
}
swi.caseCommands.get(breakCaseIndex - 1).remove(swi.caseCommands.get(breakCaseIndex - 1).size() - 1);
}
boolean hasContinues = false;
for (int c = 0; c < swi.caseCommands.size(); c++) {
List commands = swi.caseCommands.get(c);
if (!commands.isEmpty()) {
if (commands.get(commands.size() - 1) instanceof ContinueItem) {
ContinueItem cnt = (ContinueItem) commands.get(commands.size() - 1);
if (cnt.loopId == lastLoopId) {
hasContinues = true;
commands.set(commands.size() - 1, new BreakItem(dialect, null, null, swi.loop.id));
}
}
}
}
if (hasContinues && breakCaseIndex > -1 && i + 1 < list.size()) {
List toAdd = new ArrayList<>();
boolean continueOnEnd = list.get(list.size() - 1) instanceof ContinueItem;
for (int j = i + 1; j < list.size() - (continueOnEnd ? 1 : 0); j++) {
toAdd.add(list.remove(i + 1));
}
List targetCommands = swi.caseCommands.get(breakCaseIndex);
if (!targetCommands.isEmpty() && (targetCommands.get(targetCommands.size() - 1) instanceof BreakItem)) {
targetCommands.remove(targetCommands.size() - 1);
}
targetCommands.addAll(toAdd);
if (toAdd.isEmpty() || (!((toAdd.get(toAdd.size() - 1) instanceof ExitItem) || (toAdd.get(toAdd.size() - 1) instanceof BreakItem)))) {
targetCommands.add(new BreakItem(dialect, null, null, swi.loop.id));
}
}
fixSwitchEnd(swi);
} else if (item instanceof IfItem) {
processSwitches(((IfItem) item).onTrue, lastLoopId);
processSwitches(((IfItem) item).onFalse, lastLoopId);
}
}
}
/**
* Gets data for final process. Override this method to provide data for
* final process.
*
* @param localData Local data
* @param loops Loops
* @param throwStates Throw states
* @return Final process local data
*/
protected FinalProcessLocalData getFinalData(BaseLocalData localData, List loops, List throwStates) {
return new FinalProcessLocalData(loops);
}
/**
* Method called before getting loops. Override this method to provide
* custom behavior.
*
* @param localData Local data
* @param path Path
* @param allParts All parts
* @param throwStates Throw states
* @throws InterruptedException On interrupt
*/
protected void beforeGetLoops(BaseLocalData localData, String path, Set allParts, List throwStates) throws InterruptedException {
}
/**
* Method called after getting loops. Override this method to provide custom
* behavior.
*
* @param localData Local data
* @param path Path
* @param allParts All parts
* @throws InterruptedException On interrupt
*/
protected void afterGetLoops(BaseLocalData localData, String path, Set allParts) throws InterruptedException {
}
/**
* Checks whether part is empty. Override this method to provide custom
* behavior.
*
* @param part Part
* @return True if part is empty
*/
protected boolean isPartEmpty(GraphPart part) {
return false;
}
/**
* Converts path to string
*
* @param list Collection of objects
* @return String representation of the path
*/
private String pathToString(Collection extends Object> list) {
List strs = new ArrayList<>();
for (Object p : list) {
strs.add(p.toString());
}
return "[" + String.join(", ", strs) + "]";
}
/**
* Gets unique part list.
*
* @param list List of parts
* @return Unique list of parts
*/
private List getUniquePartList(List list) {
List result = new ArrayList<>();
for (GraphPart p : list) {
if (!result.contains(p)) {
result.add(p);
}
}
return result;
}
/**
* Gets list of unique references of part without going through throw edges.
*
* @param part Part
* @param throwEdges Throw edges
* @return List of unique references
*/
private List getUniqueRefsNoThrow(GraphPart part, Set throwEdges) {
List result = new ArrayList<>();
for (GraphPart r : part.refs) {
GraphPartEdge edge = new GraphPartEdge(r, part);
if (!throwEdges.contains(edge)) {
result.add(r);
}
}
return getUniquePartList(result);
}
/**
* Gets of loop backedges
*
* @param localData Local data
* @param loops Loops
* @param throwStates Throw states
* @throws InterruptedException On interrupt
*/
private void getBackEdges(BaseLocalData localData, List loops, List throwStates) throws InterruptedException {
clearLoops(loops);
for (Loop el : loops) {
el.backEdges.clear();
Set uniqueRefs = new HashSet<>(el.loopContinue.refs);
loopR:
for (GraphPart r : uniqueRefs) {
for (Loop el2 : loops) {
if (el2.phase == 1) {
if (el2.loopContinue == r) {
continue loopR;
}
}
}
if (el.loopContinue.leadsTo(localData, this, code, r, loops, throwStates)) {
el.backEdges.add(r);
}
}
el.phase = 1;
}
clearLoops(loops);
}
/**
* Final process stack. Override this method to provide custom behavior.
*
* @param stack Translate stack
* @param output Output
* @param path Path
*/
public void finalProcessStack(TranslateStack stack, List output, String path) {
}
/**
* Final process all.
*
* @param parent Parent item
* @param list List of GraphTargetItems
* @param level Level
* @param localData Local data
* @param path Path
* @throws InterruptedException On interrupt
*/
private void finalProcessAll(GraphTargetItem parent, List list, int level, FinalProcessLocalData localData, String path) throws InterruptedException {
if (debugDoNotProcess) {
return;
}
finalProcess(parent, list, level, localData, path);
for (GraphTargetItem item : list) {
if (item instanceof Block) {
List> subs = ((Block) item).getSubs();
for (List sub : subs) {
finalProcessAll(item, sub, level + 1, localData, path);
}
}
}
finalProcessAfter(list, level, localData, path);
}
/**
* Processes sub blocks. TODO: make this clearer what it does
*
* @param b Block
* @param replacement If not null, then if last item of block is PushItem,
* then it will be replaced with this item
* @return If all blocks ends with PushItem
*/
private boolean processSubBlk(Block b, GraphTargetItem replacement) {
boolean allSubPush = true;
boolean atleastOne = false;
for (List sub : b.getSubs()) {
if (!sub.isEmpty()) {
int lastPos = sub.size() - 1;
GraphTargetItem last = sub.get(sub.size() - 1);
GraphTargetItem br = null;
if ((last instanceof BreakItem) && (sub.size() >= 2)) {
br = last;
lastPos--;
last = sub.get(lastPos);
}
if (last instanceof Block) {
if (!processSubBlk((Block) last, replacement)) {
allSubPush = false;
} else {
atleastOne = true;
}
} else if (last instanceof PushItem) {
if (replacement != null) {
GraphTargetItem e2 = (((GraphTargetItem) replacement).clone());
e2.value = last.value;
sub.set(lastPos, e2);
if (br != null) {
sub.remove(sub.size() - 1);
}
}
atleastOne = true;
} else if (!(last instanceof ExitItem)) {
allSubPush = false;
}
}
}
return allSubPush && atleastOne;
}
/**
* Final process after. Override this method to provide custom behavior.
*
* @param list List of GraphTargetItems
* @param level Level
* @param localData Local data
* @param path Path
*/
protected void finalProcessAfter(List list, int level, FinalProcessLocalData localData, String path) {
if (list.size() >= 2) {
if (list.get(list.size() - 1) instanceof ExitItem) {
ExitItem e = (ExitItem) list.get(list.size() - 1);
if (list.get(list.size() - 1).value instanceof PopItem) {
if (list.get(list.size() - 2) instanceof Block) {
Block b = (Block) list.get(list.size() - 2);
if (processSubBlk(b, (GraphTargetItem) e)) {
list.remove(list.size() - 1);
}
}
}
}
}
}
/**
* Final process. Override this method to provide custom behavior.
*
* @param parent Parent item
* @param list List of GraphTargetItems
* @param level Level
* @param localData Local data
* @param path Path
* @throws InterruptedException On interrupt
*/
protected void finalProcess(GraphTargetItem parent, List list, int level, FinalProcessLocalData localData, String path) throws InterruptedException {
//For detection based on debug line information
boolean[] toDelete = new boolean[list.size()];
for (int i = 0; i < list.size(); i++) {
if (CancellableWorker.isInterrupted()) {
throw new InterruptedException();
}
GraphTargetItem itemI = list.get(i);
/*
* Fix if(true) caused by while(true) {... break;}
*/
if (itemI instanceof IfItem) {
IfItem ifi = (IfItem) itemI;
if (ifi.expression instanceof TrueItem) {
list.remove(i);
list.addAll(i, ifi.onTrue);
i--;
continue;
}
}
if (itemI instanceof ForItem) {
ForItem fori = (ForItem) itemI;
int exprLine = fori.getLine();
if (exprLine > 0) {
List forFirstCommands = new ArrayList<>();
for (int j = i - 1; j >= 0; j--) {
if (list.get(j).getLine() == exprLine && !(list.get(j) instanceof LoopItem /*to avoid recursion and StackOverflow*/)) {
forFirstCommands.add(0, list.get(j));
toDelete[j] = true;
break; //do not allow more than 1
} else {
break;
}
}
fori.firstCommands.addAll(0, forFirstCommands);
}
}
if (itemI instanceof WhileItem) {
WhileItem whi = (WhileItem) itemI;
int whileExprLine = whi.getLine();
if (whileExprLine > 0) {
List forFirstCommands = new ArrayList<>();
List forFinalCommands = new ArrayList<>();
for (int j = i - 1; j >= 0; j--) {
GraphTargetItem itemJ = list.get(j);
if (itemJ.getLine() == whileExprLine && !(itemJ instanceof LoopItem /*to avoid recursion and StackOverflow*/)) {
forFirstCommands.add(0, itemJ);
toDelete[j] = true;
} else {
break;
}
}
for (int j = whi.commands.size() - 1; j >= 0; j--) {
if (whi.commands.get(j).getLine() == whileExprLine && !(whi.commands.get(j) instanceof LoopItem /*to avoid recursion and StackOverflow*/)) {
forFinalCommands.add(0, whi.commands.remove(j));
} else {
break;
}
}
if (!forFirstCommands.isEmpty() || !forFinalCommands.isEmpty()) {
//Do not allow more than 1 first/final command, since it can be obfuscated
if (forFirstCommands.size() > 1 || forFinalCommands.size() > 1) {
//put it back
for (int k = 0; k < forFirstCommands.size(); k++) {
toDelete[i - 1 - k] = false;
}
whi.commands.addAll(forFinalCommands); //put it back
} else if (whi.commands.isEmpty() && forFirstCommands.isEmpty()) {
//it would be for(;expr;commands) {} which looks better as while(expr){commands}
whi.commands.addAll(forFinalCommands); //put it back
} else {
GraphTargetItem lastExpr = whi.expression.remove(whi.expression.size() - 1);
forFirstCommands.addAll(whi.expression);
list.set(i, new ForItem(dialect, whi.getSrc(), whi.getLineStartItem(), whi.loop, forFirstCommands, lastExpr, forFinalCommands, whi.commands));
}
}
}
}
}
for (int i = toDelete.length - 1; i >= 0; i--) {
if (toDelete[i]) {
list.remove(i);
}
}
}
/**
* Expands gotos.
*
* @param list List of GraphTargetItems
*/
private void expandGotos(List list) {
if (!list.isEmpty() && (list.get(list.size() - 1) instanceof GotoItem)) {
GotoItem gi = (GotoItem) list.get(list.size() - 1);
if (gi.targetCommands != null) {
list.remove(gi);
if (gi.labelName != null) {
list.add(new LabelItem(dialect, null, gi.lineStartItem, gi.labelName));
}
list.addAll(gi.targetCommands);
}
}
for (int i = 0; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
if (item instanceof Block) {
List> subs = ((Block) item).getSubs();
for (List sub : subs) {
expandGotos(sub);
}
}
}
}
/**
* Processes if gotos.
*
* @param alreadyProcessedBlocks Already processed blocks
* @param allGotos All gotos
* @param list List of GraphTargetItems
* @param rootList Root list
*/
private void processIfGotos2(List> alreadyProcessedBlocks, List allGotos, List list, List rootList) {
for (int i = 0; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
if (item instanceof Block) {
List> subs = ((Block) item).getSubs();
for (List sub : subs) {
processIfGotos2(alreadyProcessedBlocks, allGotos, sub, rootList);
}
}
if (item instanceof GotoItem) {
GotoItem gi = (GotoItem) item;
loopblk:
for (List blk : alreadyProcessedBlocks) {
for (int j = 0; j < blk.size(); j++) {
GraphTargetItem ti = blk.get(j);
if (ti instanceof LabelItem) {
LabelItem label = (LabelItem) ti;
if (label.labelName.equals(gi.labelName)) {
if (blk.get(blk.size() - 1) instanceof ExitItem) {
int siz = blk.size();
for (int k = 0; k < siz - j; k++) {
list.add(i + 1 + k, blk.remove(j));
}
blk.add(j, list.remove(i));
}
break loopblk;
}
}
}
}
}
}
alreadyProcessedBlocks.add(list);
}
/**
* Processes if gotos.
*
* if (xxx) { y ; goto a } else { z ; goto a }
*
* =>
*
* if (xxx) { y } else { z } goto a
*
* @param allGotos All gotos
* @param list List of GraphTargetItems
* @param rootList Root list
*/
private void processIfGotos(List allGotos, List list, List rootList) {
for (int i = 0; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
if (item instanceof Block) {
List> subs = ((Block) item).getSubs();
for (List sub : subs) {
processIfGotos(allGotos, sub, rootList);
}
}
if (item instanceof IfItem) {
IfItem ii = (IfItem) item;
if (!ii.onTrue.isEmpty() && !ii.onFalse.isEmpty()) {
if (ii.onTrue.get(ii.onTrue.size() - 1) instanceof GotoItem) {
if (ii.onFalse.get(ii.onFalse.size() - 1) instanceof GotoItem) {
for (int j = i + 1; j < list.size(); j++) {
list.remove(i + 1);
}
}
}
}
if (!ii.onTrue.isEmpty() && !ii.onFalse.isEmpty()) {
if (ii.onTrue.get(ii.onTrue.size() - 1) instanceof GotoItem) {
if (ii.onFalse.get(ii.onFalse.size() - 1) instanceof GotoItem) {
GotoItem gotoOnTrue = (GotoItem) ii.onTrue.get(ii.onTrue.size() - 1);
GotoItem gotoOnFalse = (GotoItem) ii.onFalse.get(ii.onFalse.size() - 1);
if (gotoOnTrue.labelName.equals(gotoOnFalse.labelName)) {
String labelOnTrue = gotoOnTrue.labelName;
String labelOnFalse = gotoOnFalse.labelName;
if (labelOnTrue != null && labelOnFalse != null) {
if (labelOnTrue.equals(labelOnFalse)) {
GotoItem gotoMerged;
GotoItem gotoRemoved;
if (gotoOnTrue.targetCommands != null) {
gotoMerged = gotoOnTrue;
gotoRemoved = gotoOnFalse;
} else {
gotoMerged = gotoOnFalse;
gotoRemoved = gotoOnTrue;
}
ii.onTrue.remove(ii.onTrue.size() - 1);
ii.onFalse.remove(ii.onFalse.size() - 1);
list.add(i + 1, gotoMerged);
allGotos.remove(gotoRemoved);
}
}
}
}
}
}
if (!ii.onTrue.isEmpty() && ii.onFalse.isEmpty()) {
if (ii.onTrue.get(ii.onTrue.size() - 1) instanceof GotoItem) {
GotoItem g1 = (GotoItem) ii.onTrue.get(ii.onTrue.size() - 1);
if (i + 1 < list.size()) {
if (list.get(i + 1) instanceof GotoItem) {
GotoItem g2 = (GotoItem) list.get(i + 1);
if (g1.labelName.equals(g2.labelName)) {
ii.onTrue.remove(ii.onTrue.size() - 1);
}
}
}
}
}
}
}
}
private void processScriptEnd(List ret) {
if (!ret.isEmpty()) {
if (ret.get(ret.size() - 1) instanceof ScriptEndItem) {
ret.remove(ret.size() - 1);
processScriptEnd(ret);
return;
}
if (ret.get(ret.size() - 1) instanceof Block) {
Block blk = (Block) ret.get(ret.size() - 1);
if (blk instanceof SwitchItem) {
return;
}
if (blk instanceof LoopItem) {
return;
}
for (List sub : blk.getSubs()) {
processScriptEnd(sub);
}
}
}
}
/**
* Processes ifs.
*
* @param list List of GraphTargetItems
*/
protected final void processIfs(List list) {
if (debugDoNotProcess) {
return;
}
for (int i = 0; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
if ((item instanceof LoopItem) && (item instanceof Block)) {
List> subs = ((Block) item).getSubs();
for (List sub : subs) {
processIfs(sub);
checkContinueAtTheEnd(sub, ((LoopItem) item).loop);
}
} else if (item instanceof Block) {
List> subs = ((Block) item).getSubs();
for (List sub : subs) {
processIfs(sub);
}
}
if (item instanceof IfItem) {
IfItem ifi = (IfItem) item;
List onTrue = ifi.onTrue;
List onFalse = ifi.onFalse;
if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) {
if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) {
if (onFalse.get(onFalse.size() - 1) instanceof ContinueItem) {
if (((ContinueItem) onTrue.get(onTrue.size() - 1)).loopId == ((ContinueItem) onFalse.get(onFalse.size() - 1)).loopId) {
onTrue.remove(onTrue.size() - 1);
list.add(i + 1, onFalse.remove(onFalse.size() - 1));
}
}
}
}
if (i < list.size() - 1) {
if (list.get(i + 1) instanceof ContinueItem) {
if ((!onTrue.isEmpty()) && (onFalse.isEmpty())) {
if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) {
if (((ContinueItem) onTrue.get(onTrue.size() - 1)).loopId == ((ContinueItem) list.get(i + 1)).loopId) {
onTrue.remove(onTrue.size() - 1);
}
}
}
}
}
/*
if (xx) {
A;
continue
} else {
B;
break/exit/continue;
}
=>
if (xx) {
A;
} else {
B;
break/exit/continue;
}
continue;
*/
if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) {
if ((onFalse.get(onFalse.size() - 1) instanceof ExitItem) || (onFalse.get(onFalse.size() - 1) instanceof BreakItem)) {
if (onTrue.get(onTrue.size() - 1) instanceof ContinueItem) {
list.add(i + 1, onTrue.remove(onTrue.size() - 1));
}
}
}
/*
if (xx) {
A;
break/exit/continue;
} else {
B;
}
=>
if (xx) {
A;
break/exit/continue;
}
B;
*/
if ((!onTrue.isEmpty()) && (!onFalse.isEmpty())) {
GraphTargetItem last = onTrue.get(onTrue.size() - 1);
if ((last instanceof ExitItem) || (last instanceof ContinueItem) || (last instanceof BreakItem)) {
list.addAll(i + 1, onFalse);
onFalse.clear();
}
}
//Prefer continue/return/throw/break in onTrue rather than onFalse
if (!onFalse.isEmpty()
&& ((onFalse.get(onFalse.size() - 1) instanceof BreakItem)
|| (onFalse.get(onFalse.size() - 1) instanceof ExitItem)
|| (onFalse.get(onFalse.size() - 1) instanceof ContinueItem))
&& !(onFalse.get(onFalse.size() - 1) instanceof ScriptEndItem)
&& (onTrue.isEmpty() || !((onTrue.get(onTrue.size() - 1) instanceof BreakItem)
|| (onTrue.get(onTrue.size() - 1) instanceof ExitItem)
|| (onTrue.get(onTrue.size() - 1) instanceof ContinueItem)))) {
ifi.expression = ifi.expression.invert(null);
ifi.onTrue = onFalse;
ifi.onFalse = new ArrayList<>();
list.addAll(i + 1, onTrue);
onFalse = ifi.onFalse;
onTrue = ifi.onTrue;
}
if (i < list.size() - 1) {
if ((list.get(i + 1) instanceof BreakItem) && onFalse.isEmpty()) {
if (!onTrue.isEmpty() && (onTrue.get(onTrue.size() - 1) instanceof ContinueItem)) {
ifi.expression = ifi.expression.invert(null);
list.addAll(i + 2, ifi.onTrue);
ifi.onTrue.clear();
ifi.onTrue.add(list.remove(i + 1));
}
}
}
//Switch break onFalse and continue onTrue
if (i < list.size() - 1) {
if ((list.get(list.size() - 1) instanceof ExitItem) || (list.get(list.size() - 1) instanceof BreakItem)) {
if (onFalse.isEmpty() && !onTrue.isEmpty() && (onTrue.get(onTrue.size() - 1) instanceof ContinueItem)) {
ifi.expression = ifi.expression.invert(null);
List onTrueItems = new ArrayList<>();
for (int j = i; j < list.size(); j++) {
onTrueItems.add(list.remove(i + 1));
}
list.addAll(i + 1, ifi.onTrue);
ifi.onTrue.clear();
ifi.onTrue.addAll(onTrueItems);
}
}
}
/*
if (xx) {
A;
break/continue x;
}
break/continue x;
=>
if (xx) {
A;
}
break/continue x;
*/
if (i + 1 < list.size()) {
GraphTargetItem nextItem = list.get(i + 1);
if (onFalse.isEmpty() && !onTrue.isEmpty()) {
if ((onTrue.get(onTrue.size() - 1) instanceof ContinueItem) && (nextItem instanceof ContinueItem)) {
ContinueItem cntOnTrue = (ContinueItem) onTrue.get(onTrue.size() - 1);
ContinueItem cntNext = (ContinueItem) nextItem;
if (cntOnTrue.loopId == cntNext.loopId) {
onTrue.remove(onTrue.size() - 1);
}
}
if ((onTrue.get(onTrue.size() - 1) instanceof BreakItem) && (nextItem instanceof BreakItem)) {
BreakItem brkOnTrue = (BreakItem) onTrue.get(onTrue.size() - 1);
BreakItem brkNext = (BreakItem) nextItem;
if (brkOnTrue.loopId == brkNext.loopId) {
onTrue.remove(onTrue.size() - 1);
}
}
}
}
}
}
//Same continues in onTrue and onFalse gets continue on parent level
}
/**
* Checks continue at the end of block and remove it when its from nearest
* loop.
*
* @param commands List of GraphTargetItems
* @param loop Loop
*/
private void checkContinueAtTheEnd(List commands, Loop loop) {
if (!commands.isEmpty()) {
int i = commands.size() - 1;
for (; i >= 0; i--) {
if (commands.get(i) instanceof ContinueItem) {
continue;
}
if (commands.get(i) instanceof BreakItem) {
continue;
}
break;
}
if (i < commands.size() - 1) {
for (int k = i + 2; k < commands.size(); k++) {
commands.remove(k);
}
}
if (commands.get(commands.size() - 1) instanceof ContinueItem) {
if (((ContinueItem) commands.get(commands.size() - 1)).loopId == loop.id) {
commands.remove(commands.size() - 1);
}
}
}
}
/**
* Checks whether list of items is empty.
*
* @param output List of GraphTargetItems
* @return True if list of items is empty
*/
protected final boolean isEmpty(List output) {
if (output.isEmpty()) {
return true;
}
if (output.size() == 1) {
if (output.get(0) instanceof MarkItem) {
return true;
}
}
return false;
}
/**
* Check before decompiling next section. Override this method to provide
* custom behavior.
*
* @param currentRet Current return
* @param foundGotos Found gotos
* @param partCodes Part codes
* @param partCodePos Part code position
* @param visited Visited
* @param code Code
* @param localData Local data
* @param allParts All parts
* @param stack Stack
* @param parent Parent part
* @param part Part
* @param stopPart Stop part
* @param stopPartKind Stop part kind
* @param loops Loops
* @param throwStates Throw states
* @param output Output
* @param currentLoop Current loop
* @param staticOperation Unused
* @param path Path
* @return List of GraphTargetItems to replace current output and stop
* further processing. Null to continue.
* @throws InterruptedException On interrupt
*/
protected List check(List currentRet, List foundGotos, Map> partCodes, Map partCodePos, Set visited, GraphSource code, BaseLocalData localData, Set allParts, TranslateStack stack, GraphPart parent, GraphPart part, List stopPart, List stopPartKind, List loops, List throwStates, List output, Loop currentLoop, int staticOperation, String path) throws InterruptedException {
return null;
}
/**
* Check of Part passing output. Allows you to switch part for another. If
* not overridden, then it calls checkPart.
*
* @param output List of GraphTargetItems
* @param stack Translate stack
* @param localData Local data
* @param prev Previous part
* @param part Part
* @param allParts All parts
* @return Return same part to continue processing or return another part to
* continue to other part. Or return null to stop.
*/
protected GraphPart checkPartWithOutput(List output, TranslateStack stack, BaseLocalData localData, GraphPart prev, GraphPart part, Set allParts) {
return checkPart(stack, localData, prev, part, allParts);
}
/**
* Check of part. Allows you to switch part for another.
*
* @param stack Translate stack
* @param localData Local data
* @param prev Previous part
* @param part Part
* @param allParts All parts
* @return Return same part to continue processing or return another part to
* continue to other part. Or return null to stop.
*/
protected GraphPart checkPart(TranslateStack stack, BaseLocalData localData, GraphPart prev, GraphPart part, Set allParts) {
return part;
}
/**
* Translates part and get its stack.
*
* @param localData Local data
* @param part Part
* @param stack Translate stack
* @param staticOperation Unused
* @return Top of the stack
* @throws InterruptedException On interrupt
* @throws GraphPartChangeException On graph part change
*/
//@SuppressWarnings("unchecked")
protected final GraphTargetItem translatePartGetStack(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation) throws InterruptedException, GraphPartChangeException {
stack = (TranslateStack) stack.clone();
translatePart(new ArrayList<>(), localData, part, stack, staticOperation, null);
return stack.pop();
}
/**
* Translates part and get its stack with output
*
* @param localData Local data
* @param part Part
* @param stack Translate stack
* @param staticOperation Unused
* @return Top of the stack
* @throws InterruptedException On interrupt
* @throws GraphPartChangeException On graph part change
*/
//@SuppressWarnings("unchecked")
protected final GraphTargetItem translatePartGetStack(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, List output) throws InterruptedException, GraphPartChangeException {
stack = (TranslateStack) stack.clone();
output.clear();
translatePart(output, localData, part, stack, staticOperation, null);
return stack.pop();
}
/**
* Translates part.
*
* @param output Output
* @param localData Local data
* @param part Part
* @param stack Translate stack
* @param staticOperation Unused
* @param path Path
* @return List of GraphTargetItems
* @throws InterruptedException On interrupt
* @throws GraphPartChangeException On graph part change
*/
protected final void translatePart(List output, BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, String path) throws InterruptedException, GraphPartChangeException {
List sub = part.getSubParts();
stack.setConnectedOutput(0, output, localData);
int end;
for (GraphPart p : sub) {
if (p.end == -1) {
p.end = code.size() - 1;
}
if (p.start == code.size()) {
continue;
} else if (p.end == code.size()) {
p.end--;
}
end = p.end;
int start = p.start;
code.translatePart(output, this, part, localData, stack, start, end, staticOperation, path);
}
}
/**
* Checks for Continue and Break items at current part.
*
* @param output List of GraphTargetItems
* @param part Part
* @param stopPart Stop part
* @param loops Loops
* @param throwStates Throw states
* @return Continue or Break item or null
*/
protected final GraphTargetItem checkLoop(List output, GraphPart part, List stopPart, List loops, List throwStates) {
if (stopPart.contains(part)) {
return null;
}
GraphSourceItem firstIns = null;
if (part != null) {
if (part.start >= 0 && part.start < code.size()) {
firstIns = code.get(part.start);
}
}
for (Loop l : loops) {
if (l.phase == 2) {
continue;
}
if (l.loopContinue == part) {
return (new ContinueItem(dialect, null, firstIns, l.id));
}
if (l.loopBreak == part) {
return (new BreakItem(dialect, null, firstIns, l.id));
}
}
return null;
}
/**
* Check loop. Can be overridden to provide custom behavior.
*
* @param output List of GraphTargetItems
* @param loopItem Loop item
* @param localData Local data
* @param loops Loops
* @param throwStates Throw states
* @param stack Translate stack
* @return Return loopItem to replace current loop. Return null to continue.
*/
protected GraphTargetItem checkLoop(List output, LoopItem loopItem, BaseLocalData localData, List loops, List throwStates, TranslateStack stack) {
return loopItem;
}
/**
* Sets loop phase to 0 on loops.
*
* @param loops List of loops
*/
private void clearLoops(List loops) {
for (Loop l : loops) {
l.phase = 0;
}
}
/**
* Sets state to 0 on throw states.
*
* @param throwStates List of throw states
*/
private void clearThrowStates(List throwStates) {
for (ThrowState ts : throwStates) {
ts.state = 0;
}
}
/**
* Loop detection.
*
* @param localData Local data
* @param part 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 {
clearLoops(loops);
clearThrowStates(throwStates);
getLoopsWalk(localData, part, loops, throwStates, stopPart, true, new ArrayList<>(), 0);
clearLoops(loops);
clearThrowStates(throwStates);
}
/**
* Checks whether a part can be a break candidate. Can be overridden to
* provide custom behavior.
*
* @param localData Local data
* @param part Part
* @param throwStates List of throw states
* @return True if part can be a break candidate
*/
protected boolean canBeBreakCandidate(BaseLocalData localData, GraphPart part, List throwStates) {
return true;
}
/**
* Check part in get loops walk. Can be overridden to provide custom
* behavior.
*
* @param part Graph part
*/
protected void checkGetLoopsPart(GraphPart part) {
}
/**
* Finds parts outside try statement
*
* @param ts Throw state
* @param part Graph part
* @param ret List of Graph parts
* @param visited Set of Graph parts
*/
private void findPartsOutsideTry(ThrowState ts, GraphPart part, List ret, Set visited) {
if (visited.contains(part)) {
return;
}
visited.add(part);
if (!ts.throwingParts.contains(part)) {
ret.add(part);
return;
}
for (GraphPart n : part.nextParts) {
findPartsOutsideTry(ts, n, ret, visited);
}
}
/**
* Walks parts to detect loops.
*
* @param localData Local data
* @param part Graph part
* @param loops List of loops
* @param throwStates List of throw states
* @param stopPart Stop part
* @param first First
* @param visited Set of Graph parts
* @param level Level
* @throws InterruptedException On interrupt
*/
private void getLoopsWalk(BaseLocalData localData, GraphPart part, List loops, List throwStates, List stopPart, boolean first, List visited, int level) throws InterruptedException {
loopwalk:
while (true) {
if (part == null) {
return;
}
part = checkPart(null, localData, null, part, null);
if (part == null) {
return;
}
if (!visited.contains(part)) {
visited.add(part);
}
checkGetLoopsPart(part);
if (debugGetLoops) {
System.err.println("getloops: " + part);
}
//List loopContinues = getLoopsContinues(loops);
Loop lastP1 = null;
for (Loop el : loops) {
if ((el.phase == 1) && el.loopBreak == null) { //break not found yet
if (el.loopContinue != part) {
lastP1 = el;
} else {
lastP1 = null;
}
}
}
boolean canBeCandidate = true;
if (lastP1 != null) {
for (ThrowState ts : throwStates) {
if (!ts.catchParts.contains(lastP1.loopContinue) && ts.catchParts.contains(part)) {
canBeCandidate = false;
break;
}
}
}
boolean isLoop = false;
Loop currentLoop = null;
try {
if (lastP1 != null && canBeCandidate && canBeBreakCandidate(localData, part, throwStates)) {
if (lastP1.breakCandidates.contains(part)) {
lastP1.breakCandidates.add(part);
lastP1.breakCandidatesLevels.add(level);
return;
} else {
List loops2 = new ArrayList<>(loops);
loops2.remove(lastP1);
for (ThrowState ts : throwStates) {
if (ts.throwingParts.contains(part)) {
ts.state *= -1;
}
}
try {
if (!part.leadsTo(localData, this, code, lastP1.loopContinue, loops2, throwStates)) {
if (lastP1.breakCandidatesLocked == 0) {
if (debugGetLoops) {
System.err.println("added breakCandidate " + part + " to " + lastP1);
}
lastP1.breakCandidates.add(part);
lastP1.breakCandidatesLevels.add(level);
return;
}
}
} finally {
for (ThrowState ts : throwStates) {
if (ts.throwingParts.contains(part)) {
ts.state *= -1;
}
}
}
}
}
for (Loop el : loops) {
if (el.loopContinue == part) {
return;
}
}
if (stopPart != null && stopPart.contains(part)) {
return;
}
part.level = level;
isLoop = part.leadsTo(localData, this, code, part, loops, throwStates);
currentLoop = null;
if (isLoop) {
currentLoop = new Loop(loops.size(), part, null);
currentLoop.stopParts = stopPart == null ? new ArrayList<>() : stopPart;
currentLoop.phase = 1;
loops.add(currentLoop);
}
} finally {
for (ThrowState ts : throwStates) {
if (ts.throwingParts.contains(part)) {
ts.state = 1;
}
}
}
for (ThrowState ts : throwStates) {
if (ts.throwingParts.contains(part)) {
GraphPart t = ts.targetPart;
if (!visited.contains(t)) {
getLoopsWalk(localData, t, loops, throwStates, stopPart, false, visited, level);
}
}
}
if (part.nextParts.size() == 2 && !partIsSwitch(part)) {
List nps;
nps = part.nextParts;
GraphPart next = getCommonPart(localData, part, nps, loops, throwStates);
List stopPart2 = stopPart == null ? new ArrayList<>() : new ArrayList<>(stopPart);
if (next != null) {
stopPart2.add(next);
}
if (next != nps.get(0)) {
getLoopsWalk(localData, nps.get(0), loops, throwStates, stopPart2, false, visited, level + 1);
}
if (next != nps.get(1)) {
getLoopsWalk(localData, nps.get(1), loops, throwStates, stopPart2, false, visited, level + 1);
}
if (next != null) {
getLoopsWalk(localData, next, loops, throwStates, stopPart, false, visited, level);
}
} else if (part.nextParts.size() > 2 || partIsSwitch(part)) {
GraphPart next = getMostCommonPart(localData, part.nextParts, loops, throwStates, stopPart);
for (GraphPart p : part.nextParts) {
List stopPart2 = stopPart == null ? new ArrayList<>() : new ArrayList<>(stopPart);
if (next != null) {
stopPart2.add(next);
}
for (GraphPart p2 : part.nextParts) {
if (p2 == p) {
continue;
}
if (!stopPart2.contains(p2)) {
stopPart2.add(p2);
}
}
if (next != p) {
getLoopsWalk(localData, p, loops, throwStates, stopPart2, false, visited, level + 1);
}
}
if (next != null) {
getLoopsWalk(localData, next, loops, throwStates, stopPart, false, visited, level);
}
} else if (part.nextParts.size() == 1) {
if (!isLoop || currentLoop == null) {
part = part.nextParts.get(0);
first = false;
continue loopwalk; //to avoid recursion
} else {
getLoopsWalk(localData, part.nextParts.get(0), loops, throwStates, stopPart, false, visited, level);
}
}
if (isLoop && currentLoop != null) {
GraphPart found;
for (int i = 0; i < currentLoop.breakCandidates.size(); i++) {
GraphPart ch = checkPart(null, localData, null, currentLoop.breakCandidates.get(i), null);
if (ch == null) {
currentLoop.breakCandidates.remove(i);
currentLoop.breakCandidatesLevels.remove(i);
i--;
}
}
if (debugGetLoops) {
System.err.println("loop " + currentLoop + " break candidates:");
for (GraphPart cand : currentLoop.breakCandidates) {
System.err.println("- " + cand);
}
}
List contThrowStates = new ArrayList<>();
for (ThrowState ts : throwStates) {
if (ts.throwingParts.contains(currentLoop.loopContinue)) {
contThrowStates.add(ts.exceptionId);
}
}
Map removed = new HashMap<>();
loopcand:
for (int c = 0; c < currentLoop.breakCandidates.size(); c++) {
GraphPart cand = currentLoop.breakCandidates.get(c);
List candThrowStates = new ArrayList<>();
for (ThrowState ts : throwStates) {
if (ts.throwingParts.contains(cand) && ts.startPart != cand) {
if (contThrowStates.equals(candThrowStates)) {
//adding new ts
//this means breakcandidate is in nested try
if (debugGetLoops) {
System.err.println("candidate " + cand + " is in inner try, getting outside parts");
}
List outsideTry = new ArrayList<>();
findPartsOutsideTry(ts, cand, outsideTry, new HashSet<>());
for (int j = outsideTry.size() - 1; j >= 0; j--) {
if (!canBeBreakCandidate(localData, outsideTry.get(j), throwStates)) {
outsideTry.remove(j);
}
}
if (debugGetLoops) {
for (GraphPart op : outsideTry) {
System.err.println("- outsidepart " + op);
}
}
int bcLevel = currentLoop.breakCandidatesLevels.get(c);
currentLoop.breakCandidates.remove(c);
currentLoop.breakCandidates.addAll(c, outsideTry);
currentLoop.breakCandidatesLevels.remove(c);
removed.put(cand, bcLevel);
for (int j = 0; j < outsideTry.size(); j++) {
currentLoop.breakCandidatesLevels.add(c, bcLevel);
}
c--;
continue loopcand;
}
candThrowStates.add(ts.exceptionId);
}
}
}
//When some of breakcandidates pass through current stopPart,
//Remove other candidates.
Set removedX = new LinkedHashSet<>();
if (!currentLoop.stopParts.isEmpty()) {
List breakCandidatesLeft = new ArrayList<>();
for (int c = 0; c < currentLoop.breakCandidates.size(); c++) {
GraphPart cand = currentLoop.breakCandidates.get(c);
GraphPart sp = currentLoop.stopParts.get(currentLoop.stopParts.size() - 1);
if (cand == sp || cand.leadsTo(localData, this, code, sp, new ArrayList<>() /*ignore existing loop states*/, throwStates)) {
breakCandidatesLeft.add(c);
}
}
if (!breakCandidatesLeft.isEmpty()) {
for (int c = currentLoop.breakCandidates.size() - 1; c >= 0; c--) {
if (!breakCandidatesLeft.contains(c)) {
GraphPart cand = currentLoop.breakCandidates.get(c);
removedX.add(cand);
}
}
}
}
do {
found = null;
loopcand:
for (int c1 = 0; c1 < currentLoop.breakCandidates.size(); c1++) {
GraphPart cand = currentLoop.breakCandidates.get(c1);
for (int c2 = 0; c2 < currentLoop.breakCandidates.size(); c2++) {
GraphPart cand2 = currentLoop.breakCandidates.get(c2);
if (cand == cand2) {
continue;
}
if (cand.leadsTo(localData, this, code, cand2, loops, throwStates)) {
int curLevl = currentLoop.breakCandidatesLevels.get(c1);
int curLev2 = currentLoop.breakCandidatesLevels.get(c2);
/*
found = cand;
int lev2 = currentLoop.breakCandidatesLevels.get(c2);
currentLoop.breakCandidates.set(c1, cand2);
currentLoop.breakCandidatesLevels.set(c1, lev2);*/
int lev1 = Integer.MAX_VALUE;
int lev2 = Integer.MAX_VALUE;
for (int i = 0; i < currentLoop.breakCandidates.size(); i++) {
if (currentLoop.breakCandidates.get(i) == cand) {
if (currentLoop.breakCandidatesLevels.get(i) < lev1) {
lev1 = currentLoop.breakCandidatesLevels.get(i);
}
}
if (currentLoop.breakCandidates.get(i) == cand2) {
if (currentLoop.breakCandidatesLevels.get(i) < lev2) {
lev2 = currentLoop.breakCandidatesLevels.get(i);
}
}
}
//
GraphPart other;
int curLev;
if (lev1 <= lev2) {
found = cand2;
other = cand;
curLev = curLevl;
} else {
found = cand;
other = cand2;
curLev = curLev2;
}
currentLoop.breakCandidates.add(other);
currentLoop.breakCandidatesLevels.add(curLev);
break loopcand;
}
}
}
if (found != null) {
int maxlevel = 0;
while (currentLoop.breakCandidates.contains(found)) {
int ind = currentLoop.breakCandidates.indexOf(found);
currentLoop.breakCandidates.remove(ind);
int lev = currentLoop.breakCandidatesLevels.remove(ind);
if (lev > maxlevel) {
maxlevel = lev;
}
}
if (removed.containsKey(found)) {
if (removed.get(found) > maxlevel) {
maxlevel = removed.get(found);
}
}
removed.put(found, maxlevel);
}
} while ((found != null) && (currentLoop.breakCandidates.size() > 1));
Map count = new HashMap<>();
GraphPart winner = null;
int winnerCount = 0;
int winnerNumBlock = Integer.MAX_VALUE;
Set bannedCandidates = new HashSet<>();
if (localData.secondPassData != null) {
bannedCandidates = localData.secondPassData.allSwitchParts;
}
if (debugPrintLoopList) {
System.err.println("bannedCandidates:");
for (GraphPart p : bannedCandidates) {
System.err.println("- " + p);
}
}
for (GraphPart cand : currentLoop.breakCandidates) {
if (removedX.contains(cand)) {
if (debugPrintLoopList) {
System.err.println("cand " + cand + " is removed");
}
continue;
}
if (bannedCandidates.contains(cand)) {
if (debugPrintLoopList) {
System.err.println("cand " + cand + " is banned");
}
continue;
}
if (!count.containsKey(cand)) {
count.put(cand, 0);
}
count.put(cand, count.get(cand) + 1);
boolean otherBreakCandidate = false;
for (Loop el : loops) {
if (el == currentLoop) {
continue;
}
if (el.breakCandidates.contains(cand)) {
otherBreakCandidate = true;
break;
}
}
if (otherBreakCandidate) {
//empty
} else if (count.get(cand) > winnerCount) {
winnerCount = count.get(cand);
winner = cand;
} else if (count.get(cand) == winnerCount && winner != null) {
if (cand.path.length() < winner.path.length()) {
winner = cand;
}
}
}
for (int i = 0; i < currentLoop.breakCandidates.size(); i++) {
GraphPart cand = currentLoop.breakCandidates.get(i);
if (cand != winner) {
int lev = currentLoop.breakCandidatesLevels.get(i);
if (removed.containsKey(cand)) {
if (removed.get(cand) > lev) {
lev = removed.get(cand);
}
}
removed.put(cand, lev);
}
}
currentLoop.loopBreak = winner;
currentLoop.breakCandidates.clear();
currentLoop.phase = 2;
boolean start = false;
for (int l = 0; l < loops.size(); l++) {
Loop el = loops.get(l);
if (start) {
el.phase = 1;
}
if (el == currentLoop) {
start = true;
}
}
List removedVisited = new ArrayList<>();
for (GraphPart r : removed.keySet()) {
if (removedVisited.contains(r)) {
continue;
}
getLoopsWalk(localData, r, loops, throwStates, stopPart, false, visited, removed.get(r));
removedVisited.add(r);
}
start = false;
for (int l = 0; l < loops.size(); l++) {
Loop el = loops.get(l);
if (el == currentLoop) {
start = true;
}
if (start) {
el.phase = 2;
}
}
getLoopsWalk(localData, currentLoop.loopBreak, loops, throwStates, stopPart, false, visited, level);
}
break;
}
}
/**
* Gets all Continue commands in sub blocks.
*
* @param commands List of GraphTargetItems
* @param result Result
* @param loopId Loop id
*/
private void getContinuesCommands(List commands, List> result, long loopId) {
for (GraphTargetItem ti : commands) {
if (ti instanceof ContinueItem) {
ContinueItem ci = (ContinueItem) ti;
if (ci.loopId == loopId) {
result.add(commands);
}
}
if (ti instanceof Block) {
Block bl = (Block) ti;
for (List subCommands : bl.getSubs()) {
getContinuesCommands(subCommands, result, loopId);
}
}
}
}
/**
* Get next parts of a part. Can be overridden to provide custom behavior.
*
* @param localData Local data
* @param part Part
* @return List of GraphParts
*/
protected List getNextParts(BaseLocalData localData, GraphPart part) {
return part.nextParts;
}
/**
* Check before processing with output.
*
* @param currentRet Current return
* @param foundGotos Found gotos
* @param partCodes Part codes
* @param partCodePos Part code position
* @param visited Visited
* @param code Code
* @param localData Local data
* @param allParts All parts
* @param stack Stack
* @param parent Parent part
* @param part Part
* @param stopPart Stop part
* @param stopPartKind Stop part kind
* @param loops Loops
* @param throwStates Throw states
* @param currentLoop Current loop
* @param staticOperation Unused
* @param path Path
* @param recursionLevel Recursion level
* @return True to stop processing. False to continue.
* @throws InterruptedException On interrupt
*/
protected boolean checkPartOutput(List currentRet, List foundGotos, Map> partCodes, Map partCodePos, Set visited, GraphSource code, BaseLocalData localData, Set allParts, TranslateStack stack, GraphPart parent, GraphPart part, List stopPart, List stopPartKind, List loops, List throwStates, Loop currentLoop, int staticOperation, String path, int recursionLevel) throws InterruptedException {
return false;
}
/**
* Checks whether part is loop continue, break or precontinue.
*
* @param part Graph part
* @param loops List of loops
* @param throwStates List of throw states
* @return True if part is loop continue, break or precontinue
*/
protected boolean partIsLoopContBrePre(GraphPart part, List loops, List throwStates) {
for (Loop el : loops) {
if (el.phase == 1) {
if (el.loopBreak == part) {
return true;
}
if (el.loopContinue == part) {
return true;
}
if (el.loopPreContinue == part) {
return true;
}
}
}
return false;
}
/**
* Checks whether part can be continue of a loop. Defaults to true. Override
* this method to provide custom behavior.
*
* @param localData Local data
* @param part Graph part
* @param loops List of loops
* @param throwStates List of throw states
* @return True if part can be continue of a loop
*/
protected boolean canHandleLoop(BaseLocalData localData, GraphPart part, List loops, List throwStates) {
return true;
}
/**
* Checks whether part can be checked over visited parts list. Defaults to
* true. Can be overridden to provide custom behavior.
*
* @param localData Local data
* @param part Graph part
* @return True if part can be checked over visited parts list
*/
protected boolean canHandleVisited(BaseLocalData localData, GraphPart part) {
return true;
}
/**
* Checks whether the list of items can be converted to comma separated
* list.
*
* @param list List of GraphTargetItems
* @return True if the list of items can be converted to comma separated
* list
*/
protected final boolean canBeCommaised(List list) {
for (GraphTargetItem item : list) {
if (item instanceof Block) {
return false;
}
}
return true;
}
/**
* Gets if expression from stack. Can be overridden for custom handling
*
* @param localData Local data
* @param stack Stack
* @param output Output
* @return Expression
*/
protected GraphTargetItem getIfExpression(BaseLocalData localData, TranslateStack stack, List output) {
return stack.pop();
}
/**
* Walks graph parts and converts them to target items.
*
* @param foundGotos Found gotos
* @param partCodes Part codes
* @param partCodePos Part code position
* @param visited Visited
* @param localData Local data
* @param stack Translate stack
* @param allParts All parts
* @param parent Parent part
* @param part Part
* @param stopPart Stop part
* @param stopPartKind Stop part kind
* @param loops Loops
* @param throwStates Throw states
* @param staticOperation Unused
* @param path Path
* @return List of GraphTargetItems
* @throws InterruptedException On interrupt
*/
protected final List printGraph(List foundGotos, Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List stopPartKind, List loops, List throwStates, int staticOperation, String path) throws InterruptedException {
return printGraph(foundGotos, partCodes, partCodePos, visited, localData, stack, allParts, parent, part, stopPart, stopPartKind, loops, throwStates, null, staticOperation, path, 0);
}
/**
* Walks graph parts and converts them to target items.
*
* @param foundGotos Found gotos
* @param partCodes Part codes
* @param partCodePos Part code position
* @param visited Visited
* @param localData Local data
* @param stack Translate stack
* @param allParts All parts
* @param parent Parent part
* @param part Part
* @param stopPart Stop part
* @param stopPartKind Stop part kind
* @param loops Loops
* @param throwStates Throw states
* @param ret Return
* @param staticOperation Unused
* @param path Path
* @param recursionLevel Recursion level
* @return List of GraphTargetItems
* @throws InterruptedException On interrupt
*/
protected final List printGraph(List foundGotos, Map> partCodes, Map partCodePos, Set visited, BaseLocalData localData, TranslateStack stack, Set allParts, GraphPart parent, GraphPart part, List stopPart, List stopPartKind, List