From 90a4d56b889405872a45ffdf9f6fc06ead25be54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 10 Jan 2021 19:00:00 +0100 Subject: [PATCH] Try for new getprecontinues method for for detection --- .../script/PcodeGraphVizExporter.java | 1 + .../src/com/jpexs/decompiler/graph/Graph.java | 663 ++++++++++++------ .../src/com/jpexs/decompiler/graph/Loop.java | 13 +- 3 files changed, 456 insertions(+), 221 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/PcodeGraphVizExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/PcodeGraphVizExporter.java index 466b75160..6159ea6e5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/PcodeGraphVizExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/script/PcodeGraphVizExporter.java @@ -140,6 +140,7 @@ public class PcodeGraphVizExporter { blkStyle = "shape=\"circle\""; labelStr = "FINISH"; } + labelStr = part.toString() + ":\\l" + labelStr; writer.append(partBlockName + " [" + blkStyle + " label=\"" + labelStr + "\"];\r\n"); for (int n = 0; n < part.nextParts.size(); n++) { GraphPart next = part.nextParts.get(n); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java index a6ffa1f82..5202090bb 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -54,14 +54,17 @@ import com.jpexs.decompiler.graph.model.TrueItem; import com.jpexs.decompiler.graph.model.UniversalLoopItem; import com.jpexs.decompiler.graph.model.WhileItem; import java.util.ArrayList; +import java.util.Collection; 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.logging.Logger; /** * @@ -86,6 +89,8 @@ public class Graph { private boolean debugGetLoops = false; private boolean debugPrintGraph = false; + private static Logger logger = Logger.getLogger(Graph.class.getName()); + public GraphSource getGraphCode() { return code; } @@ -680,7 +685,10 @@ public class Graph { } //TODO: Make getPrecontinues faster - getPrecontinues(path, localData, null, heads.get(0), allParts, loops, null); + getBackEdges(localData, loops); + //getPrecontinues(path, localData, null, heads.get(0), allParts, loops, null); + //getPrecontinues2(path, localData, null, heads.get(0), allParts, loops, null); + getPrecontinues3(path, localData, null, heads.get(0), allParts, loops); /*System.err.println(""); for (Loop el : loops) { @@ -694,6 +702,431 @@ public class Graph { return ret; } + private static class Edge { + + public GraphPart from; + public GraphPart to; + + public Edge(GraphPart from, GraphPart to) { + this.from = from; + this.to = to; + } + + @Override + public String toString() { + return from.toString() + " -> " + to.toString(); + } + + + @Override + public int hashCode() { + int hash = 7; + hash = 37 * hash + Objects.hashCode(this.from); + hash = 37 * hash + Objects.hashCode(this.to); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Edge other = (Edge) obj; + if (!Objects.equals(this.from, other.from)) { + return false; + } + if (!Objects.equals(this.to, other.to)) { + return false; + } + return true; + } + + } + + private List getCommonPrefix(List> listOfLists) { + List result = new ArrayList<>(); + if (listOfLists.isEmpty()) { + return result; + } + + int maxlen = Integer.MAX_VALUE; + for (int j = 0; j < listOfLists.size(); j++) { + if (listOfLists.get(j).size() < maxlen) { + maxlen = listOfLists.get(j).size(); + } + } + for (int i = 0; i < maxlen; i++) { + List firstList = listOfLists.get(0); + for (int j = 1; j < listOfLists.size(); j++) { + if (!listOfLists.get(j).get(i).equals(firstList.get(i))) { + return result; + } + } + result.add(firstList.get(i)); + } + return result; + } + + private GraphPart getDominator(GraphPart graphStart, GraphPart targetPart, List loops) { + Set refs = new LinkedHashSet<>(); + List parts = targetPart.refs; + + List> allPredecessorsLists = new ArrayList<>(); + for (GraphPart part : parts) { + List allPredecessors = new ArrayList<>(); + getAllPredecessors(part, allPredecessors, loops, null); + //allRefs.addAll(allPredecessors); + logger.info("predecessors of part " + part + " are: " + pathToString(allPredecessors)); + allPredecessorsLists.add(allPredecessors); + } + List firstList = allPredecessorsLists.get(0); + GraphPart commonPart = null; + looppart: + for (GraphPart part : firstList) { + for (int i = 1; i < allPredecessorsLists.size(); i++) { + if (!allPredecessorsLists.get(i).contains(part)) { + continue looppart; + } + } + logger.info("checking predeccessor " + part); + for (GraphPart part2 : parts) { + List allPredecessors = new ArrayList<>(); + getAllPredecessors(part2, allPredecessors, loops, part); + logger.info("predecessors of " + part2 + ": " + allPredecessors); + if (allPredecessors.contains(graphStart)) { + logger.info("graphstart(" + graphStart + ") found, " + part + " will not be correct"); + continue looppart; + } + } + + commonPart = part; + + break; + } + + return commonPart; + } + + private void getAllPredecessors(GraphPart part, List ret, List loops, GraphPart ignored) { + List toprocess = new ArrayList<>(); + loopref: + for (GraphPart ref : part.refs) { + for (Loop el : loops) { + if (part.equals(el.loopContinue) && el.backEdges.contains(ref)) { + continue loopref; + } + } + if (ref == ignored) { + continue; + } + if (!ret.contains(ref)) { + ret.add(0, ref); + toprocess.add(ref); + } + } + for (GraphPart ref : toprocess) { + getAllPredecessors(ref, ret, loops, ignored); + } + } + + private void getPrecontinues3(String path, BaseLocalData localData, GraphPart parent, GraphPart startPart, Set allParts, List loops) { + if (!path.equals("classes.tests/ForTest1.test")) { + //return; + } + logger.info("GETTING precontinues of " + path + " ================="); + Set opened = new HashSet<>(); + Set closed = new HashSet<>(); + Set closedBranches = new HashSet<>(); + Set exitEdges = new HashSet<>(); + Set backEdges = new HashSet<>(); + for (Loop el : loops) { + for (GraphPart g : el.backEdges) { + backEdges.add(new Edge(g, el.loopContinue)); + } + } + Map> edgeToBranches = new HashMap<>(); + opened.add(startPart); + GraphPart start = startPart; + Stack walkStack = new Stack<>(); + walkStack.push(new Edge(startPart, startPart)); + loopwalk: + while (!walkStack.isEmpty()) { + Edge e = walkStack.pop(); + GraphPart p = e.to; + if (closed.contains(p)) { + logger.fine("part " + p + " is already closed, skipping"); + continue; + } + logger.fine("processing " + p); + if (!edgeToBranches.containsKey(e)) { + edgeToBranches.put(e, new ArrayList<>()); + } + List branches = edgeToBranches.get(e); + if (p != start) { + List refs = getUnicatePartList(p.refs); + + List> comparedPaths = new ArrayList<>(); + List comparedPathsEdges = new ArrayList<>(); + for (GraphPart r : refs) { + Edge re = new Edge(r, p); + if (backEdges.contains(re)) { + logger.fine("ref edge " + re + " is backedge, ignored"); + continue; + } + if (!edgeToBranches.containsKey(re)) { + //edge not yet processed + logger.fine("ref edge " + re + " NOT yet processed"); + continue loopwalk; + } else { + logger.fine("ref edge " + re + " already processed"); + } + comparedPaths.add(edgeToBranches.get(re)); + comparedPathsEdges.add(re); + + } + loopi: + for (int i = 0; i < comparedPaths.size(); i++) { + for (int j = 0; j < comparedPaths.size(); j++) { + if (i == j) { + continue; + } + if (comparedPaths.get(i).equals(comparedPaths.get(j))) { + //logger.info("paths match: " + comparedPaths.get(i)); + if (comparedPaths.get(i).isEmpty()) { + //logger.info("path isempty, removing it "); + comparedPaths.remove(i); + comparedPathsEdges.remove(i); + i--; + continue loopi; + } + //remove last path component + int last = i > j ? i : j; + int first = i < j ? i : j; + + comparedPaths.get(first).remove(comparedPaths.get(first).size() - 1); + comparedPaths.remove(last); + comparedPathsEdges.remove(last); + i = -1; + continue loopi; + } + } + } + for (List cp : comparedPaths) { + logger.fine("- branches:" + pathToString(cp)); + } + if (comparedPaths.size() > 1) { + logger.fine("not a single path"); + List prefix = getCommonPrefix(comparedPaths); + Set partsToClose = new HashSet<>(); + boolean isEndOfBlock = false; + for (int i = 0; i < comparedPaths.size(); i++) { + for (int j = prefix.size(); j < comparedPaths.get(i).size(); j++) { + GraphPart partToClose = comparedPaths.get(i).get(j); + Edge edgeToClose = comparedPathsEdges.get(i); + if (!closedBranches.contains(partToClose)) { + logger.info("on part " + p); + logger.info("closing branch " + partToClose); + partsToClose.add(partToClose); + } else { + logger.fine("probably break edge: " + edgeToClose); + isEndOfBlock = true; + } + } + } + closedBranches.addAll(partsToClose); + branches = prefix; + if (!branches.isEmpty()) { + branches.remove(branches.size() - 1); + } + + if (isEndOfBlock) { + //GraphPart blockStartPart = getDominator(startPart, p, loops); + logger.info("found breaks to to " + p); + } + + } + + } + + //filter out backedges + List nexts = new ArrayList<>(); + for (GraphPart n : p.nextParts) { + Edge ne = new Edge(p, n); + if (!backEdges.contains(ne)) { + nexts.add(n); + } else { + logger.fine("next edge " + ne + " is backedge, ignored"); + } + } + + closed.add(p); + + logger.fine("processing nextparts of " + p); + for (GraphPart n : nexts) { + Edge ne = new Edge(p, n); + List subBranches = branches; + if (nexts.size() > 1) { + subBranches = new ArrayList<>(branches); + subBranches.add(p); + } + edgeToBranches.put(ne, subBranches); + walkStack.push(ne); + } + + } + } + + private void getPrecontinues2(String path, BaseLocalData localData, GraphPart parent, GraphPart part, Set allParts, List loops, List stopPart) throws InterruptedException { + for (Loop el : loops) { + if (el.backEdges.size() == 1) { //for statement has single real continue + GraphPart backEdge = (GraphPart) el.backEdges.toArray()[0]; + + GraphPart g = backEdge; + Set openedParts = new HashSet(); + openedParts.addAll(g.nextParts); + List level0Parts = new ArrayList(); + getPrecontinues2Walk(new HashMap(), level0Parts, new HashMap>>(), openedParts, g, loops, el, 0, new HashMap>(), new ArrayList<>()); + if (!level0Parts.isEmpty()) { + GraphPart lastPart = level0Parts.get(level0Parts.size() - 1); + el.loopPreContinue = lastPart; + logger.info("Found precontinue: " + lastPart); + } + + } + } + } + + private String pathToString(Collection list) { + List strs = new ArrayList<>(); + for (GraphPart p : list) { + strs.add(p.toString()); + } + return "[" + String.join(", ", strs) + "]"; + } + + private List getUnicatePartList(List list) { + List result = new ArrayList<>(); + for (GraphPart p : list) { + if (!result.contains(p)) { + result.add(p); + } + } + return result; + } + + private List getUsableRefs(GraphPart g, List loops) { + List ret = getUnicatePartList(g.refs); + for (Loop el : loops) { + for (GraphPart be : el.backEdges) { + if (ret.contains(be)) { + ret.remove(be); + } + } + } + return ret; + } + + private void getPrecontinues2Walk(Map numRefsRemaining, List level0Parts, Map>> joinedPaths, Set openedParts, GraphPart g, List loops, Loop currentLoop, int partId, Map> partPaths, List path) { + logger.fine("start walk " + g); + if (g == currentLoop.loopContinue) { + return; + } + if (openedParts.contains(g)) { + logger.fine("- already walked - terminate"); + return; //already walked + } + + if (!joinedPaths.containsKey(g)) { + joinedPaths.put(g, new ArrayList<>()); + } + joinedPaths.get(g).add(path); + + boolean checkNext = true; + for (Loop el : loops) { + if (el.loopContinue == g) { + checkNext = false; + logger.fine("do not check next as current is loopcontinue"); + break; + } + } + logger.fine("opened parts: " + pathToString(openedParts)); + + if (checkNext) { + //Do not proceed up before handling all descendants + for (GraphPart next : g.nextParts) { + if (!openedParts.contains(next)) { + logger.fine("- waiting for next descendats - terminate"); + return; + } + } + } + + List jp1 = joinedPaths.get(g).get(0); + for (List jp : joinedPaths.get(g)) { + if (!jp.equals(jp1)) { + //paths in both branches do not match, do not continue up + logger.fine("- Paths mismatch - terminate"); + logger.fine("path " + pathToString(jp) + " does not match " + pathToString(jp1)); + return; + } + } + + if (!path.isEmpty() && g.nextParts.size() > 1) { + GraphPart lastPathComponent = path.get(path.size() - 1); + logger.fine("numrefs before = " + numRefsRemaining.get(lastPathComponent)); + numRefsRemaining.put(lastPathComponent, numRefsRemaining.get(lastPathComponent) - 1); + logger.fine("numrefs = " + numRefsRemaining.get(lastPathComponent)); + if (numRefsRemaining.get(lastPathComponent) == 1) { + logger.fine("numrefs is zero, removing last item of path " + pathToString(path)); + path.remove(path.size() - 1); + } + } + if (path.isEmpty()) { + logger.fine("path is empty, adding current item"); + level0Parts.add(g); + } + + logger.fine("added to openedParts"); + openedParts.add(g); + + List refs = getUsableRefs(g, loops); + numRefsRemaining.put(g, refs.size()); + logger.fine("setting numrefs to " + refs.size()); + logger.fine("all refs = " + pathToString(refs)); + for (GraphPart ref : refs) { + logger.fine("going to ref " + ref + "..."); + List subPath = new ArrayList(path); + if (refs.size() > 1) { + subPath.add(g); + } + getPrecontinues2Walk(numRefsRemaining, level0Parts, joinedPaths, openedParts, ref, loops, currentLoop, partId, partPaths, subPath); + } + logger.fine("/All refs of " + g + " processed"); + } + + /**/ + //if (ref.nextParts) + private void getBackEdges(BaseLocalData localData, List loops) throws InterruptedException { + clearLoops(loops); + for (Loop el : loops) { + el.backEdges.clear(); + Set uniqueRefs = new HashSet<>(el.loopContinue.refs); + for (GraphPart r : uniqueRefs) { + if (el.loopContinue.leadsTo(localData, this, code, r, loops)) { + el.backEdges.add(r); + } + } + el.phase = 1; + } + clearLoops(loops); + } + public void finalProcessStack(TranslateStack stack, List output, String path) { } @@ -845,6 +1278,9 @@ public class Graph { private void processIfs(List list) { + if (true) { + return; //FIXMe + } for (int i = 0; i < list.size(); i++) { GraphTargetItem item = list.get(i); if ((item instanceof LoopItem) && (item instanceof Block)) { @@ -1081,224 +1517,6 @@ public class Graph { return loopItem; } - //TODO: Make this faster!!! - private void getPrecontinues(String path, BaseLocalData localData, GraphPart parent, GraphPart part, Set allParts, List loops, List stopPart) throws InterruptedException { - try { - markLevels(path, localData, part, allParts, loops); - } catch (ThreadDeath | InterruptedException ex) { - throw ex; - } catch (Throwable ex) { - //It is unusual code so markLevels failed, nevermind, it can still work - } - //Note: this also marks part as precontinue when there is if - /* - while(k<10){ - if(k==7){ - trace(a); - }else{ - trace(b); - } - //precontinue - k++; - } - - */ - looploops: - for (Loop l : loops) { - if (l.loopContinue != null) { - Set uniqueRefs = new HashSet<>(); - uniqueRefs.addAll(l.loopContinue.refs); - if (uniqueRefs.size() == 2) { //only one path - from precontinue - List uniqueRefsList = new ArrayList<>(uniqueRefs); - if (uniqueRefsList.get(0).discoveredTime > uniqueRefsList.get(1).discoveredTime) { //latch node is discovered later - part = uniqueRefsList.get(0); - } else { - part = uniqueRefsList.get(1); - } - if (part == l.loopContinue) { - continue looploops; - } - - while (part.refs.size() == 1) { - if (part.refs.get(0).nextParts.size() != 1) { - continue looploops; - } - - part = part.refs.get(0); - if (part == l.loopContinue) { - break; - } - } - if (part.level == 0 && part != l.loopContinue) { - l.loopPreContinue = part; - } - } - } - } - /*clearLoops(loops); - getPrecontinues(parent, part, loops, stopPart, 0, new ArrayList()); - clearLoops(loops);*/ - } - - private void markLevels(String path, BaseLocalData localData, GraphPart part, Set allParts, List loops) throws InterruptedException { - clearLoops(loops); - markLevels(path, localData, part, allParts, loops, new ArrayList<>(), 1, new HashSet<>(), 0); - clearLoops(loops); - } - - private void markLevels(String path, BaseLocalData localData, GraphPart part, Set allParts, List loops, List stopPart, int level, Set visited, int recursionLevel) throws InterruptedException { - if (stopPart == null) { - stopPart = new ArrayList<>(); - } - if (recursionLevel > allParts.size() + 1) { - throw new RuntimeException(path + ": markLevels max recursion level reached"); - } - - if (stopPart.contains(part)) { - //System.err.println("/stopped part " + part); - return; - } - for (Loop el : loops) { - if ((el.phase == 2) && (el.loopContinue == part)) { - return; - } - if (el.phase != 1) { - continue; - } - if (el.loopContinue == part) { - return; - } - if (el.loopPreContinue == part) { - return; - } - if (el.loopBreak == part) { - return; - } - } - - if (visited.contains(part)) { - part.level = 0; - //System.err.println("set level " + part + " to zero"); - } else { - visited.add(part); - part.level = level; - //System.err.println("set level " + part + " to " + level); - } - - boolean isLoop = false; - Loop currentLoop = null; - for (Loop el : loops) { - if ((el.phase == 0) && (el.loopContinue == part)) { - isLoop = true; - currentLoop = el; - el.phase = 1; - break; - } - } - - List nextParts = checkPrecoNextParts(part); - if (nextParts == null) { - nextParts = part.nextParts; - } - nextParts = new ArrayList<>(nextParts); - - if (nextParts.size() == 2 && stopPart.contains(nextParts.get(1))) { - nextParts.remove(1); - } - if (nextParts.size() >= 1 && stopPart.contains(nextParts.get(0))) { - nextParts.remove(0); - } - - if (nextParts.size() == 2) { - GraphPart next = getCommonPart(localData, nextParts, loops);//part.getNextPartPath(new ArrayList()); - //System.err.println("- common part of " + nextParts.get(0) + " and " + nextParts.get(1) + " is " + next); - List stopParts2 = new ArrayList<>(); //stopPart); - if (next != null) { - stopParts2.add(next); - } else if (!stopPart.isEmpty()) { - stopParts2.add(stopPart.get(stopPart.size() - 1)); - } - if (next != nextParts.get(0)) { - // System.err.println("- going to branch 0 nextpart from " + part + " to " + nextParts.get(0)); - markLevels(path, localData, nextParts.get(0), allParts, loops, next == null ? stopPart : stopParts2, level + 1, visited, recursionLevel + 1); - } else { - //System.err.println("- branch 0 of " + part + " is skipped (=next)"); - } - - if (next != nextParts.get(1)) { - //System.err.println("- going to branch 1 nextpart from " + part + " to " + nextParts.get(1)); - markLevels(path, localData, nextParts.get(1), allParts, loops, next == null ? stopPart : stopParts2, level + 1, visited, recursionLevel + 1); - } else { - //System.err.println("- branch 1 of " + part + " is skipped (=next)"); - } - if (next != null) { - //System.err.println("- going to next from " + part + " to " + next); - markLevels(path, localData, next, allParts, loops, stopPart, level, visited, recursionLevel + 1); - } - } - - if (nextParts.size() > 2) { - GraphPart next = getMostCommonPart(localData, nextParts, loops); - List vis = new ArrayList<>(); - for (GraphPart p : nextParts) { - if (vis.contains(p)) { - continue; - } - List stopPart2 = new ArrayList<>(); //(stopPart); - if (next != null) { - stopPart2.add(next); - } else if (!stopPart.isEmpty()) { - stopPart2.add(stopPart.get(stopPart.size() - 1)); - } - for (GraphPart p2 : nextParts) { - if (p2 == p) { - continue; - } - if (!stopPart2.contains(p2)) { - stopPart2.add(p2); - } - } - if (next != p) { - markLevels(path, localData, p, allParts, loops, stopPart2, level + 1, visited, recursionLevel + 1); - vis.add(p); - } - } - if (next != null) { - markLevels(path, localData, next, allParts, loops, stopPart, level, visited, recursionLevel + 1); - } - } - - if (nextParts.size() == 1) { - //System.err.println("going to one nexpart from " + part + " to " + nextParts.get(0)); - markLevels(path, localData, nextParts.get(0), allParts, loops, stopPart, level, visited, recursionLevel + 1); - } - - for (GraphPart t : part.throwParts) { - if (!visited.contains(t)) { - List stopPart2 = new ArrayList<>(); - List cmn = new ArrayList<>(); - cmn.add(part); - cmn.add(t); - GraphPart next = getCommonPart(localData, cmn, loops); - if (next != null) { - stopPart2.add(next); - } else { - stopPart2 = stopPart; - } - - markLevels(path, localData, t, allParts, loops, stopPart2, level, visited, recursionLevel + 1); - } - } - - if (isLoop) { - if (currentLoop != null && currentLoop.loopBreak != null) { - currentLoop.phase = 2; - //System.err.println("- going to break of loop " + currentLoop.loopBreak); - markLevels(path, localData, currentLoop.loopBreak, allParts, loops, stopPart, level, visited, recursionLevel + 1); - } - } - } - private void clearLoops(List loops) { for (Loop l : loops) { l.phase = 0; @@ -1696,6 +1914,10 @@ public class Graph { } } + if (debugPrintGraph) { + System.err.println("stopParts: " + pathToString(stopPart)); + } + if (stopPart.contains(part)) { if (currentLoop != null) { currentLoop.phase = 0; @@ -1712,6 +1934,9 @@ public class Graph { } if (visited.contains(part)) { + if (debugPrintGraph) { + System.err.println("Already visited part " + part + ", adding goto"); + } String labelName = "addr" + part.start; List firstCode = partCodes.get(part); int firstCodePos = partCodePos.get(part); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Loop.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Loop.java index e8d4603e1..435f9cd20 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Loop.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Loop.java @@ -12,12 +12,15 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.graph; import java.io.Serializable; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * @@ -31,6 +34,8 @@ public class Loop implements Serializable { public GraphPart loopPreContinue; + public Set backEdges = new HashSet<>(); + public List breakCandidates = new ArrayList<>(); public List breakCandidatesLevels = new ArrayList<>(); @@ -53,7 +58,11 @@ public class Loop implements Serializable { @Override public String toString() { - return "loop(id:" + id + (loopPreContinue != null ? ",precontinue:" + loopPreContinue : "") + ",continue:" + loopContinue + ", break:" + loopBreak + ", phase:" + phase + ")"; + Set edgesAsStr = new HashSet<>(); + for (GraphPart p : backEdges) { + edgesAsStr.add(p.toString()); + } + return "loop(id:" + id + (loopPreContinue != null ? ",precontinue:" + loopPreContinue : "") + ",continue:" + loopContinue + ", break:" + loopBreak + ", phase:" + phase + ", backedges: " + String.join(",", edgesAsStr) + ")"; } @Override