diff --git a/core/src/main/java/org/teavm/model/Variable.java b/core/src/main/java/org/teavm/model/Variable.java index e9de49fc3..d3262daf6 100644 --- a/core/src/main/java/org/teavm/model/Variable.java +++ b/core/src/main/java/org/teavm/model/Variable.java @@ -70,4 +70,8 @@ public class Variable implements VariableReader { public void setLabel(String label) { this.label = label; } + + public String getDisplayLabel() { + return label != null ? label : String.valueOf(index); + } } diff --git a/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java b/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java index 8742c381f..90f53c718 100644 --- a/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java +++ b/core/src/main/java/org/teavm/model/analysis/NullnessInformationBuilder.java @@ -84,9 +84,9 @@ class NullnessInformationBuilder { } private void extendProgram() { + notNullVariables.set(0); insertAdditionalVariables(); - notNullVariables.set(0); Variable[] parameters = new Variable[methodDescriptor.parameterCount() + 1]; for (int i = 0; i < parameters.length; ++i) { parameters[i] = program.variableAt(i); @@ -123,7 +123,7 @@ class NullnessInformationBuilder { for (BasicBlock block : program.getBasicBlocks()) { for (Phi phi : block.getPhis()) { for (Incoming incoming : phi.getIncomings()) { - builder.addEdge(incoming.getSource().getIndex(), phi.getReceiver().getIndex()); + builder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex()); } } for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) { @@ -183,6 +183,7 @@ class NullnessInformationBuilder { continue; } visited[node] = true; + notNullVariables.set(node); for (int successor : assignmentGraph.outgoingEdges(node)) { if (sccIndexes[successor] == 0 || sccIndexes[successor] != sccIndexes[node]) { if (--notNullPredecessorsLeft[successor] == 0) { @@ -203,6 +204,10 @@ class NullnessInformationBuilder { public State visit(BasicBlock block) { currentState = new State(); + if (block.getExceptionVariable() != null) { + notNullVariables.set(block.getIndex()); + } + currentBlock = block; if (nullSuccessors.containsKey(block.getIndex())) { int varIndex = nullSuccessors.remove(block.getIndex()); diff --git a/core/src/main/java/org/teavm/model/util/PhiUpdater.java b/core/src/main/java/org/teavm/model/util/PhiUpdater.java index 180aae46a..b58804719 100644 --- a/core/src/main/java/org/teavm/model/util/PhiUpdater.java +++ b/core/src/main/java/org/teavm/model/util/PhiUpdater.java @@ -488,7 +488,7 @@ public class PhiUpdater { private Variable use(Variable var) { Variable mappedVar = variableMap[var.getIndex()]; if (mappedVar == null) { - throw new AssertionError("Variable used before definition: " + var.getIndex()); + throw new AssertionError("Variable used before definition: " + var.getDisplayLabel()); } usedPhis.set(mappedVar.getIndex()); return mappedVar; diff --git a/core/src/test/java/org/teavm/model/analysis/test/NullnessAnalysisTest.java b/core/src/test/java/org/teavm/model/analysis/test/NullnessAnalysisTest.java index 19232f6cf..9aee7278b 100644 --- a/core/src/test/java/org/teavm/model/analysis/test/NullnessAnalysisTest.java +++ b/core/src/test/java/org/teavm/model/analysis/test/NullnessAnalysisTest.java @@ -60,6 +60,21 @@ public class NullnessAnalysisTest { test(); } + @Test + public void phiPropagation() { + test(); + } + + @Test + public void tryCatchJoint() { + test(); + } + + @Test + public void loop() { + test(); + } + private void test() { String baseName = "model/analysis/nullness/" + name.getMethodName(); String originalResourceName = baseName + ".original.txt"; diff --git a/core/src/test/resources/model/analysis/nullness/loop.extended.txt b/core/src/test/resources/model/analysis/nullness/loop.extended.txt new file mode 100644 index 000000000..36ec8446a --- /dev/null +++ b/core/src/test/resources/model/analysis/nullness/loop.extended.txt @@ -0,0 +1,21 @@ +var @this as this + +$start + @first := field `Foo.first` @this as `LBar;` + @checkedFirst := nullCheck @first + goto $head +$head + @current := phi @checkedFirst from $start, @checkedNext from $body + @isLast := field `Bar.last` @current as Z + @current_2 := nullCheck @current + if @isLast == 0 then goto $exit else goto $body +$body + @next := field `Bar.next` @current_2 as `LBar;` + @checkedNext := nullCheck @next + goto $head +$exit + @value := field `Bar.value` @current_2 as `Ljava/lang/Object;` + return @value + +// NOT_NULL current +// NOT_NULL current_2 diff --git a/core/src/test/resources/model/analysis/nullness/loop.original.txt b/core/src/test/resources/model/analysis/nullness/loop.original.txt new file mode 100644 index 000000000..09cb0f4ea --- /dev/null +++ b/core/src/test/resources/model/analysis/nullness/loop.original.txt @@ -0,0 +1,17 @@ +var @this as this + +$start + @first := field `Foo.first` @this as `LBar;` + @checkedFirst := nullCheck @first + goto $head +$head + @current := phi @checkedFirst from $start, @checkedNext from $body + @isLast := field `Bar.last` @current as Z + if @isLast == 0 then goto $exit else goto $body +$body + @next := field `Bar.next` @current as `LBar;` + @checkedNext := nullCheck @next + goto $head +$exit + @value := field `Bar.value` @current as `Ljava/lang/Object;` + return @value \ No newline at end of file diff --git a/core/src/test/resources/model/analysis/nullness/phiJoin.extended.txt b/core/src/test/resources/model/analysis/nullness/phiJoin.extended.txt index 6417695ba..dff4d52b8 100644 --- a/core/src/test/resources/model/analysis/nullness/phiJoin.extended.txt +++ b/core/src/test/resources/model/analysis/nullness/phiJoin.extended.txt @@ -1,4 +1,7 @@ +var @this as this + $start + @cond := invokeStatic `Cond.get()Ljava/lang/Object;` if @cond === null then goto $ifNull else goto $ifNotNull $ifNull @cond_1 := null @@ -14,8 +17,8 @@ $join @d := phi @a from $ifNull, @b_1 from $ifNotNull return @c -// NULLABLE: b -// NOT_NULL: b_1 -// NOT_NULL: a -// NULLABLE: c -// NOT_NULL: d \ No newline at end of file +// NULLABLE b +// NOT_NULL b_1 +// NOT_NULL a +// NULLABLE c +// NOT_NULL d \ No newline at end of file diff --git a/core/src/test/resources/model/analysis/nullness/phiJoin.original.txt b/core/src/test/resources/model/analysis/nullness/phiJoin.original.txt index bd0642292..62ce89a93 100644 --- a/core/src/test/resources/model/analysis/nullness/phiJoin.original.txt +++ b/core/src/test/resources/model/analysis/nullness/phiJoin.original.txt @@ -1,4 +1,7 @@ +var @this as this + $start + @cond := invokeStatic `Cond.get()Ljava/lang/Object;` if @cond === null then goto $ifNull else goto $ifNotNull $ifNull @a := 'qwe' diff --git a/core/src/test/resources/model/analysis/nullness/phiPropagation.extended.txt b/core/src/test/resources/model/analysis/nullness/phiPropagation.extended.txt new file mode 100644 index 000000000..6d97f4ba7 --- /dev/null +++ b/core/src/test/resources/model/analysis/nullness/phiPropagation.extended.txt @@ -0,0 +1,27 @@ +var @this as this + +$start + @a := invokeStatic `Value.extract()Ljava/lang/String;` + @cond := invokeStatic `Cond.invoke()I` + if @cond == 0 then goto $if0 else goto $else +$if0 + @len := invokeStatic `Value.extractLen()I` + if @len == 0 then goto $ifEmpty else goto $ifNotEmpty +$ifEmpty + invokeStatic `Foo.f(Ljava/lang/String;)V` @a + goto $joinIf0 +$ifNotEmpty + invokeVirtual `java.lang.String.trim()Ljava/lang/String;` @a + @a_1 := nullCheck @a + goto $joinIf0 +$joinIf0 + @a_2 := phi @a from $ifEmpty, @a_1 from $ifNotEmpty + invokeStatic `Foo.g(Ljava/lang/String;)V` @a_2 + goto $join +$else + invokeStatic `Foo.bar(Ljava/lang/String;)V` @a + goto $join +$join + @a_3 := phi @a_2 from $joinIf0, @a from $else + invokeStatic `Foo.baz(Ljava/lang/String;)V` @a_3 + return \ No newline at end of file diff --git a/core/src/test/resources/model/analysis/nullness/phiPropagation.original.txt b/core/src/test/resources/model/analysis/nullness/phiPropagation.original.txt new file mode 100644 index 000000000..9b0408273 --- /dev/null +++ b/core/src/test/resources/model/analysis/nullness/phiPropagation.original.txt @@ -0,0 +1,24 @@ +var @this as this + +$start + @a := invokeStatic `Value.extract()Ljava/lang/String;` + @cond := invokeStatic `Cond.invoke()I` + if @cond == 0 then goto $if0 else goto $else +$if0 + @len := invokeStatic `Value.extractLen()I` + if @len == 0 then goto $ifEmpty else goto $ifNotEmpty +$ifEmpty + invokeStatic `Foo.f(Ljava/lang/String;)V` @a + goto $joinIf0 +$ifNotEmpty + invokeVirtual `java.lang.String.trim()Ljava/lang/String;` @a + goto $joinIf0 +$joinIf0 + invokeStatic `Foo.g(Ljava/lang/String;)V` @a + goto $join +$else + invokeStatic `Foo.bar(Ljava/lang/String;)V` @a + goto $join +$join + invokeStatic `Foo.baz(Ljava/lang/String;)V` @a + return \ No newline at end of file diff --git a/core/src/test/resources/model/analysis/nullness/tryCatchJoint.extended.txt b/core/src/test/resources/model/analysis/nullness/tryCatchJoint.extended.txt new file mode 100644 index 000000000..a8bad3c1e --- /dev/null +++ b/core/src/test/resources/model/analysis/nullness/tryCatchJoint.extended.txt @@ -0,0 +1,29 @@ +var @this as this + +$start + @bar := invokeVirtual `Foo.bar()LBar;` @this + @cond := invokeVirtual `Foo.cond()I` @this + if @cond == 0 then goto $if0 else goto $else +$if0 + invokeVirtual `Bar.baz()LBar;` @bar + @bar_1 := nullCheck @bar + goto $join +catch java.lang.RuntimeException goto $if0Handler + @bar_2 := ephi @bar, @bar_1 +$if0Handler + goto $join +$else + invokeVirtual `Bar.baz()LBar;` @bar + @bar_3 := nullCheck @bar + goto $else1 +$else1 + invokeVirtual `Bar.baz2()LBar;` @bar_3 + goto $join +catch java.lang.RuntimeException goto $elseHandler +$elseHandler + goto $join +$join + @bar_4 := phi @bar_1 from $if0, @bar_2 from $if0Handler, @bar_3 from $else1, @bar_3 from $elseHandler + return @bar_4 + +// NULLABLE bar_2 \ No newline at end of file diff --git a/core/src/test/resources/model/analysis/nullness/tryCatchJoint.original.txt b/core/src/test/resources/model/analysis/nullness/tryCatchJoint.original.txt new file mode 100644 index 000000000..20a724067 --- /dev/null +++ b/core/src/test/resources/model/analysis/nullness/tryCatchJoint.original.txt @@ -0,0 +1,23 @@ +var @this as this + +$start + @bar := invokeVirtual `Foo.bar()LBar;` @this + @cond := invokeVirtual `Foo.cond()I` @this + if @cond == 0 then goto $if0 else goto $else +$if0 + invokeVirtual `Bar.baz()LBar;` @bar + goto $join +catch java.lang.RuntimeException goto $if0Handler +$if0Handler + goto $join +$else + invokeVirtual `Bar.baz()LBar;` @bar + goto $else1 +$else1 + invokeVirtual `Bar.baz2()LBar;` @bar + goto $join +catch java.lang.RuntimeException goto $elseHandler +$elseHandler + goto $join +$join + return @bar \ No newline at end of file