mirror of
https://github.com/konsoletyper/teavm.git
synced 2024-11-21 01:00:54 +08:00
Fix bug in nullness analysis when variable graph has irreducible loops
This commit is contained in:
parent
1fabe4c5b9
commit
3c8184c3b7
@ -169,7 +169,7 @@ class NullnessInformationBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
assignmentGraph = GraphUtils.removeLoops(builder.build());
|
||||
assignmentGraph = removeLoops(builder.build());
|
||||
|
||||
notNullPredecessorsLeft = new int[assignmentGraph.size()];
|
||||
for (int i = 0; i < assignmentGraph.size(); ++i) {
|
||||
@ -177,6 +177,46 @@ class NullnessInformationBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private Graph removeLoops(Graph graph) {
|
||||
int[][] sccs = GraphUtils.findStronglyConnectedComponents(graph);
|
||||
if (sccs.length == 0) {
|
||||
return graph;
|
||||
}
|
||||
|
||||
int[] backMap = new int[graph.size()];
|
||||
for (int i = 0; i < backMap.length; ++i) {
|
||||
backMap[i] = i;
|
||||
}
|
||||
boolean hasNonTrivialSccs = false;
|
||||
GraphBuilder builder = new GraphBuilder(graph.size());
|
||||
for (int[] scc : sccs) {
|
||||
if (scc.length == 1) {
|
||||
continue;
|
||||
}
|
||||
hasNonTrivialSccs = true;
|
||||
|
||||
for (int i = 1; i < scc.length; ++i) {
|
||||
backMap[scc[i]] = scc[0];
|
||||
builder.addEdge(scc[0], scc[i]);
|
||||
}
|
||||
}
|
||||
if (!hasNonTrivialSccs) {
|
||||
return graph;
|
||||
}
|
||||
|
||||
for (int i = 0; i < graph.size(); ++i) {
|
||||
for (int j : graph.outgoingEdges(i)) {
|
||||
if (backMap[j] == i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.addEdge(i, backMap[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void initNullness(IntDeque queue) {
|
||||
NullnessInitVisitor visitor = new NullnessInitVisitor(queue);
|
||||
for (BasicBlock block : program.getBasicBlocks()) {
|
||||
|
@ -16,7 +16,9 @@
|
||||
package org.teavm.model.analysis.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import com.carrotsearch.hppc.ObjectByteHashMap;
|
||||
import com.carrotsearch.hppc.ObjectByteMap;
|
||||
import com.carrotsearch.hppc.cursors.ObjectCursor;
|
||||
@ -24,6 +26,7 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.Rule;
|
||||
@ -44,6 +47,7 @@ public class NullnessAnalysisTest {
|
||||
|
||||
private static final String NOT_NULL_DIRECTIVE = "// NOT_NULL ";
|
||||
private static final String NULLABLE_DIRECTIVE = "// NULLABLE ";
|
||||
private static final String NULL_DIRECTIVE = "// NULL ";
|
||||
|
||||
@Test
|
||||
public void simple() {
|
||||
@ -90,6 +94,11 @@ public class NullnessAnalysisTest {
|
||||
test();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void irreduciblePhiLoop() {
|
||||
test();
|
||||
}
|
||||
|
||||
private void test() {
|
||||
String baseName = "model/analysis/nullness/" + name.getMethodName();
|
||||
String originalResourceName = baseName + ".original.txt";
|
||||
@ -115,8 +124,20 @@ public class NullnessAnalysisTest {
|
||||
String varName = varNameCursor.value;
|
||||
Variable var = variablesByLabel.get(varName);
|
||||
assertNotNull("Variable " + varName + " is missing", var);
|
||||
boolean notNull = expectedNullness.get(varName) != 0;
|
||||
assertEquals("Variable " + varName + " non-null", notNull, information.isNotNull(var));
|
||||
byte nullness = expectedNullness.get(varName);
|
||||
switch (nullness) {
|
||||
case 0:
|
||||
assertTrue("Variable " + varName + " must be null", information.isNull(var));
|
||||
break;
|
||||
case 1:
|
||||
assertTrue("Variable " + varName + " must be non-null", information.isNotNull(var));
|
||||
break;
|
||||
case 2:
|
||||
assertFalse("Variable " + varName + " must not be null", information.isNull(var));
|
||||
assertFalse("Variable " + varName + " must not be non-null", information.isNotNull(var));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
information.dispose();
|
||||
@ -127,7 +148,7 @@ public class NullnessAnalysisTest {
|
||||
private ObjectByteMap<String> extractExpectedNullness(String name) {
|
||||
ClassLoader classLoader = NullnessAnalysisTest.class.getClassLoader();
|
||||
try (InputStream input = classLoader.getResourceAsStream(name);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
|
||||
ObjectByteMap<String> result = new ObjectByteHashMap<>();
|
||||
|
||||
while (true) {
|
||||
@ -136,7 +157,13 @@ public class NullnessAnalysisTest {
|
||||
break;
|
||||
}
|
||||
|
||||
int index = line.indexOf(NOT_NULL_DIRECTIVE);
|
||||
int index = line.indexOf(NULL_DIRECTIVE);
|
||||
if (index >= 0) {
|
||||
String variable = line.substring(index + NULL_DIRECTIVE.length()).trim();
|
||||
result.put(variable, (byte) 0);
|
||||
}
|
||||
|
||||
index = line.indexOf(NOT_NULL_DIRECTIVE);
|
||||
if (index >= 0) {
|
||||
String variable = line.substring(index + NOT_NULL_DIRECTIVE.length()).trim();
|
||||
result.put(variable, (byte) 1);
|
||||
@ -145,7 +172,7 @@ public class NullnessAnalysisTest {
|
||||
index = line.indexOf(NULLABLE_DIRECTIVE);
|
||||
if (index >= 0) {
|
||||
String variable = line.substring(index + NULLABLE_DIRECTIVE.length()).trim();
|
||||
result.put(variable, (byte) 0);
|
||||
result.put(variable, (byte) 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,5 +15,5 @@ $join
|
||||
return
|
||||
|
||||
// NULLABLE v
|
||||
// NULLABLE v_1
|
||||
// NULL v_1
|
||||
// NOT_NULL v_3
|
@ -0,0 +1,29 @@
|
||||
var @this as this
|
||||
$start
|
||||
@init := 'qwe'
|
||||
@n := null
|
||||
@xInit := 0
|
||||
goto $head
|
||||
$head
|
||||
@a := phi @init from $start, @b from $bodyEnd
|
||||
@x := phi @xInit from $start, @xUpdate from $bodyEnd
|
||||
@twenty := 20
|
||||
@xCmp := @x compareTo @twenty as int
|
||||
if @xCmp == 0 then goto $exit else goto $body
|
||||
$body
|
||||
@ten := 10
|
||||
@cmp := @x compareTo @ten as int
|
||||
if @cmp == 0 then goto $update else goto $bodyEnd
|
||||
$update
|
||||
goto $bodyEnd
|
||||
$bodyEnd
|
||||
@b := phi @a from $body, @n from $update
|
||||
@one := 1
|
||||
@xUpdate := @x + @one as int
|
||||
goto $head
|
||||
$exit
|
||||
return
|
||||
|
||||
|
||||
// NULLABLE a
|
||||
// NULLABLE b
|
@ -0,0 +1,25 @@
|
||||
var @this as this
|
||||
$start
|
||||
@init := 'qwe'
|
||||
@n := null
|
||||
@xInit := 0
|
||||
goto $head
|
||||
$head
|
||||
@a := phi @init from $start, @b from $bodyEnd
|
||||
@x := phi @xInit from $start, @xUpdate from $bodyEnd
|
||||
@twenty := 20
|
||||
@xCmp := @x compareTo @twenty as int
|
||||
if @xCmp == 0 then goto $exit else goto $body
|
||||
$body
|
||||
@ten := 10
|
||||
@cmp := @x compareTo @ten as int
|
||||
if @cmp == 0 then goto $update else goto $bodyEnd
|
||||
$update
|
||||
goto $bodyEnd
|
||||
$bodyEnd
|
||||
@b := phi @a from $body, @n from $update
|
||||
@one := 1
|
||||
@xUpdate := @x + @one as int
|
||||
goto $head
|
||||
$exit
|
||||
return
|
@ -13,5 +13,5 @@ $joint
|
||||
return @c
|
||||
|
||||
// NULLABLE c
|
||||
// NULLABLE a_1
|
||||
// NULL a_1
|
||||
// NOT_NULL a_2
|
@ -18,8 +18,8 @@ $join
|
||||
@v := @b_3
|
||||
return
|
||||
|
||||
// NULLABLE tmp
|
||||
// NULLABLE p
|
||||
// NULLABLE q
|
||||
// NULL tmp
|
||||
// NULL p
|
||||
// NULL q
|
||||
// NULLABLE u
|
||||
// NULLABLE v
|
Loading…
Reference in New Issue
Block a user