diff options
Diffstat (limited to 'test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala')
-rw-r--r-- | test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala | 174 |
1 files changed, 89 insertions, 85 deletions
diff --git a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala index 94e776aadb..c173bacd46 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala @@ -2,54 +2,39 @@ package scala.tools.nsc package backend.jvm package analysis +import org.junit.Assert._ +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.junit.Test -import scala.tools.asm.Opcodes._ -import org.junit.Assert._ -import CodeGenTools._ -import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} +import scala.collection.JavaConverters._ +import scala.tools.asm.tree.MethodNode +import scala.tools.nsc.backend.jvm.AsmUtils._ import scala.tools.nsc.backend.jvm.BTypes._ -import scala.tools.partest.ASMConverters -import ASMConverters._ -import scala.tools.testing.ClearAfterClass import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ -import AsmUtils._ - -import scala.collection.convert.decorateAsScala._ - -object NullnessAnalyzerTest extends ClearAfterClass.Clearable { - var noOptCompiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none") - - def clear(): Unit = { - noOptCompiler = null - } -} +import scala.tools.testing.BytecodeTesting +import scala.tools.testing.BytecodeTesting._ @RunWith(classOf[JUnit4]) -class NullnessAnalyzerTest extends ClearAfterClass { - ClearAfterClass.stateToClear = NullnessAnalyzerTest - val noOptCompiler = NullnessAnalyzerTest.noOptCompiler - - def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C"): NullnessAnalyzer = { - val nullnessAnalyzer = new NullnessAnalyzer - nullnessAnalyzer.analyze(classInternalName, methodNode) - nullnessAnalyzer - } +class NullnessAnalyzerTest extends BytecodeTesting { + override def compilerArgs = "-opt:l:none" + import compiler._ + import global.genBCode.bTypes.backendUtils._ + + def newNullnessAnalyzer(methodNode: MethodNode, classInternalName: InternalName = "C") = new AsmAnalyzer(methodNode, classInternalName, new NullnessAnalyzer(global.genBCode.bTypes, methodNode)) - def testNullness(analyzer: NullnessAnalyzer, method: MethodNode, query: String, index: Int, nullness: Nullness): Unit = { - for (i <- findInstr(method, query)) { - val r = analyzer.frameAt(i, method).getValue(index).nullness + def testNullness(analyzer: AsmAnalyzer[NullnessValue], method: MethodNode, query: String, index: Int, nullness: NullnessValue): Unit = { + for (i <- findInstrs(method, query)) { + val r = analyzer.frameAt(i).getValue(index) assertTrue(s"Expected: $nullness, found: $r. At instr ${textify(i)}", nullness == r) } } // debug / helper for writing tests - def showAllNullnessFrames(analyzer: NullnessAnalyzer, method: MethodNode): String = { + def showAllNullnessFrames(analyzer: AsmAnalyzer[NullnessValue], method: MethodNode): String = { val instrLength = method.instructions.iterator.asScala.map(textify(_).length).max val lines = for (i <- method.instructions.iterator.asScala) yield { - val f = analyzer.frameAt(i, method) + val f = analyzer.frameAt(i) val frameString = { if (f == null) "null" else (0 until (f.getLocals + f.getStackSize)).iterator @@ -65,39 +50,40 @@ class NullnessAnalyzerTest extends ClearAfterClass { @Test def showNullnessFramesTest(): Unit = { - val List(m) = compileMethods(noOptCompiler)("def f = this.toString") + val m = compileAsmMethod("def f = this.toString") // NOTE: the frame for an instruction represents the state *before* executing that instr. // So in the frame for `ALOAD 0`, the stack is still empty. val res = - """ L0: 0: NotNull - | LINENUMBER 1 L0: 0: NotNull - | ALOAD 0: 0: NotNull - |INVOKEVIRTUAL java/lang/Object.toString ()Ljava/lang/String;: 0: NotNull, 1: NotNull - | ARETURN: 0: NotNull, 1: Unknown1 - | L0: null""".stripMargin + """ L0: 0: NotNull + | LINENUMBER 1 L0: 0: NotNull + | ALOAD 0: 0: NotNull + |INVOKEVIRTUAL C.toString ()Ljava/lang/String;: 0: NotNull, 1: NotNull + | ARETURN: 0: NotNull, 1: Unknown1 + | L0: null""".stripMargin +// println(showAllNullnessFrames(newNullnessAnalyzer(m), m)) assertEquals(showAllNullnessFrames(newNullnessAnalyzer(m), m), res) } @Test def thisNonNull(): Unit = { - val List(m) = compileMethods(noOptCompiler)("def f = this.toString") + val m = compileAsmMethod("def f = this.toString") val a = newNullnessAnalyzer(m) - testNullness(a, m, "ALOAD 0", 0, NotNull) + testNullness(a, m, "ALOAD 0", 0, NotNullValue) } @Test def instanceMethodCall(): Unit = { - val List(m) = compileMethods(noOptCompiler)("def f(a: String) = a.trim") + val m = compileAsmMethod("def f(a: String) = a.trim") val a = newNullnessAnalyzer(m) - testNullness(a, m, "INVOKEVIRTUAL java/lang/String.trim", 1, Unknown) - testNullness(a, m, "ARETURN", 1, NotNull) + testNullness(a, m, "INVOKEVIRTUAL java/lang/String.trim", 1, UnknownValue1) + testNullness(a, m, "ARETURN", 1, NotNullValue) } @Test def constructorCall(): Unit = { - val List(m) = compileMethods(noOptCompiler)("def f = { val a = new Object; a.toString }") + val m = compileAsmMethod("def f = { val a = new Object; a.toString }") val a = newNullnessAnalyzer(m) // for reference, the output of showAllNullnessFrames(a, m) - note that the frame represents the state *before* executing the instr. @@ -110,40 +96,58 @@ class NullnessAnalyzerTest extends ClearAfterClass { // ARETURN: 0: NotNull, 1: NotNull, 2: Unknown for ((insn, index, nullness) <- List( - ("+NEW", 2, Unknown), // new value at slot 2 on the stack - ("+DUP", 3, Unknown), - ("+INVOKESPECIAL java/lang/Object", 2, NotNull), // after calling the initializer on 3, the value at 2 becomes NotNull - ("ASTORE 1", 1, Unknown), // before the ASTORE 1, nullness of the value in local 1 is Unknown - ("+ASTORE 1", 1, NotNull), // after storing the value at 2 in local 1, the local 1 is NotNull - ("+ALOAD 1", 2, NotNull), // loading the value 1 puts a NotNull value on the stack (at 2) - ("+INVOKEVIRTUAL java/lang/Object.toString", 2, Unknown) // nullness of value returned by `toString` is Unknown + ("+NEW", 2, UnknownValue1), // new value at slot 2 on the stack + ("+DUP", 3, UnknownValue1), + ("+INVOKESPECIAL java/lang/Object", 2, NotNullValue), // after calling the initializer on 3, the value at 2 becomes NotNull + ("ASTORE 1", 1, UnknownValue1), // before the ASTORE 1, nullness of the value in local 1 is Unknown + ("+ASTORE 1", 1, NotNullValue), // after storing the value at 2 in local 1, the local 1 is NotNull + ("+ALOAD 1", 2, NotNullValue), // loading the value 1 puts a NotNull value on the stack (at 2) + ("+INVOKEVIRTUAL java/lang/Object.toString", 2, UnknownValue1) // nullness of value returned by `toString` is Unknown )) testNullness(a, m, insn, index, nullness) } @Test def explicitNull(): Unit = { - val List(m) = compileMethods(noOptCompiler)("def f = { var a: Object = null; a }") + val m = compileAsmMethod("def f = { var a: Object = null; a }") val a = newNullnessAnalyzer(m) for ((insn, index, nullness) <- List( - ("+ACONST_NULL", 2, Null), - ("+ASTORE 1", 1, Null), - ("+ALOAD 1", 2, Null) + ("+ACONST_NULL", 2, NullValue), + ("+ASTORE 1", 1, NullValue), + ("+ALOAD 1", 2, NullValue) )) testNullness(a, m, insn, index, nullness) } @Test def stringLiteralsNotNull(): Unit = { - val List(m) = compileMethods(noOptCompiler)("""def f = { val a = "hi"; a.trim }""") + val m = compileAsmMethod("""def f = { val a = "hi"; a.trim }""") val a = newNullnessAnalyzer(m) - testNullness(a, m, "+ASTORE 1", 1, NotNull) + testNullness(a, m, "+ASTORE 1", 1, NotNullValue) } @Test def newArraynotNull() { - val List(m) = compileMethods(noOptCompiler)("def f = { val a = new Array[Int](2); a(0) }") + val m = compileAsmMethod("def f = { val a = new Array[Int](2); a(0) }") + val a = newNullnessAnalyzer(m) + testNullness(a, m, "+NEWARRAY T_INT", 2, NotNullValue) // new array on stack + testNullness(a, m, "+ASTORE 1", 1, NotNullValue) // local var (a) + } + + @Test + def mergeNullNotNull(): Unit = { + val code = + """def f(o: Object) = { + | var a: Object = o + | var c: Object = null + | if ("".trim eq "-") { + | c = o + | } + | a.toString + |} + """.stripMargin + val m = compileAsmMethod(code) val a = newNullnessAnalyzer(m) - testNullness(a, m, "+NEWARRAY T_INT", 2, NotNull) // new array on stack - testNullness(a, m, "+ASTORE 1", 1, NotNull) // local var (a) + val toSt = "+INVOKEVIRTUAL java/lang/Object.toString" + testNullness(a, m, toSt, 3, UnknownValue1) } @Test @@ -166,29 +170,29 @@ class NullnessAnalyzerTest extends ClearAfterClass { | // d is null here, assinged in both branches. |} """.stripMargin - val List(m) = compileMethods(noOptCompiler)(code) + val m = compileAsmMethod(code) val a = newNullnessAnalyzer(m) val trim = "INVOKEVIRTUAL java/lang/String.trim" val toSt = "INVOKEVIRTUAL java/lang/Object.toString" val end = s"+$toSt" for ((insn, index, nullness) <- List( - (trim, 0, NotNull), // this - (trim, 1, Unknown), // parameter o - (trim, 2, Unknown), // a - (trim, 3, Null), // b - (trim, 4, Null), // c - (trim, 5, Unknown), // d - - (toSt, 2, Unknown), // a, still the same - (toSt, 3, Unknown), // b, was re-assinged in both branches to Unknown - (toSt, 4, Unknown), // c, was re-assigned in one branch to Unknown - (toSt, 5, Null), // d, was assigned to null in both branches - - (end, 2, NotNull), // a, NotNull (alias of b) - (end, 3, NotNull), // b, receiver of toString - (end, 4, Unknown), // c, no change (not an alias of b) - (end, 5, Null) // d, no change + (trim, 0, NotNullValue), // this + (trim, 1, UnknownValue1), // parameter o + (trim, 2, UnknownValue1), // a + (trim, 3, NullValue), // b + (trim, 4, NullValue), // c + (trim, 5, UnknownValue1), // d + + (toSt, 2, UnknownValue1), // a, still the same + (toSt, 3, UnknownValue1), // b, was re-assinged in both branches to Unknown + (toSt, 4, UnknownValue1), // c, was re-assigned in one branch to Unknown + (toSt, 5, NullValue), // d, was assigned to null in both branches + + (end, 2, NotNullValue), // a, NotNull (alias of b) + (end, 3, NotNullValue), // b, receiver of toString + (end, 4, UnknownValue1), // c, no change (not an alias of b) + (end, 5, NullValue) // d, no change )) testNullness(a, m, insn, index, nullness) } @@ -202,7 +206,7 @@ class NullnessAnalyzerTest extends ClearAfterClass { | a.asInstanceOf[String].trim // the stack value (LOAD of local a) is still not-null after the CHECKCAST |} """.stripMargin - val List(m) = compileMethods(noOptCompiler)(code) + val m = compileAsmMethod(code) val a = newNullnessAnalyzer(m) val instof = "+INSTANCEOF" @@ -210,11 +214,11 @@ class NullnessAnalyzerTest extends ClearAfterClass { val trim = "INVOKEVIRTUAL java/lang/String.trim" for ((insn, index, nullness) <- List( - (instof, 1, Unknown), // a after INSTANCEOF - (instof, 2, Unknown), // x after INSTANCEOF - (tost, 1, NotNull), - (tost, 2, NotNull), - (trim, 3, NotNull) // receiver at `trim` + (instof, 1, UnknownValue1), // a after INSTANCEOF + (instof, 2, UnknownValue1), // x after INSTANCEOF + (tost, 1, NotNullValue), + (tost, 2, NotNullValue), + (trim, 3, NotNullValue) // receiver at `trim` )) testNullness(a, m, insn, index, nullness) } } |