diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/controlflow/SccForwardExecution.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/controlflow/SccForwardExecution.java index 768500ce6..16b3e2a44 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/controlflow/SccForwardExecution.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/controlflow/SccForwardExecution.java @@ -3,6 +3,7 @@ import de.peeeq.datastructures.GraphInterpreter; import de.peeeq.wurstscript.ast.AstElementWithBody; import de.peeeq.wurstscript.ast.WStatement; +import de.peeeq.wurstscript.utils.Utils; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -62,7 +63,13 @@ protected Collection getIncidentNodes(WStatement t) { // 4. Analyze each SCC in topological order for (List scc : sccs) { - analyzeComponent(scc); + // Nodes are popped from the SCC stack in reverse discovery order. + // Restore forward CFG order so facts propagate through the component + // without requiring a separate pass for nearly every statement. + Collections.reverse(scc); + if (!analyzeComponent(scc)) { + return; + } } @@ -72,19 +79,25 @@ protected Collection getIncidentNodes(WStatement t) { method.checkFinal(finalState); } - private void analyzeComponent(List scc) { + private boolean analyzeComponent(List scc) { Queue worklist = new ArrayDeque<>(scc); + Set queued = new ObjectOpenHashSet<>(scc); + Set componentMembers = new ObjectOpenHashSet<>(scc); int iterations = 0; int maxIterations = scc.size() * scc.size() + 100; // Heuristic limit to prevent infinite loops while (!worklist.isEmpty()) { if (iterations++ > maxIterations) { - // This should ideally not happen in a correct CFG with a monotonic transfer function - throw new RuntimeException("Dataflow analysis did not converge. Possible infinite loop in component."); + f.addError("Internal compiler error: " + method.getClass().getSimpleName() + + " did not converge while analyzing " + Utils.printElement(f) + + " (component size " + scc.size() + ", " + iterations + + " worklist iterations). Further dataflow checks for this body were skipped."); + return false; } WStatement s = worklist.poll(); + queued.remove(s); // Merge states from all predecessors Collection predecessorStates = get(s.attrPreviousStatements()); @@ -98,12 +111,13 @@ private void analyzeComponent(List scc) { // If the value changed, add successors within the same SCC to the worklist for (WStatement succ : s.attrNextStatements()) { - if (scc.contains(succ)) { + if (componentMembers.contains(succ) && queued.add(succ)) { worklist.add(succ); } } } } + return true; } private List getAllStatements() { diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/FlowAnalysisTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/FlowAnalysisTests.java index 45b93e28f..779327240 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/FlowAnalysisTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/FlowAnalysisTests.java @@ -132,4 +132,45 @@ public void destroyThisDataflowTest() { ); } + @Test + public void dataflowConvergesForManyMutatedLoopLocals() { + testAssertOkLines(false, + "package test", + "function probe()", + " int a01 = 0", + " int a02 = 0", + " int a03 = 0", + " int a04 = 0", + " int a05 = 0", + " int a06 = 0", + " int a07 = 0", + " int a08 = 0", + " int a09 = 0", + " int a10 = 0", + " int a11 = 0", + " int a12 = 0", + " int a13 = 0", + " int a14 = 0", + " int a15 = 0", + " int a16 = 0", + " while true", + " a01++", + " a02++", + " a03++", + " a04++", + " a05++", + " a06++", + " a07++", + " a08++", + " a09++", + " a10++", + " a11++", + " a12++", + " a13++", + " a14++", + " a15++", + " a16++" + ); + } + }