From 44b5c261a8e585c5747380895aa06c84f9d63f6c Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Mon, 25 Aug 2014 23:13:05 +0200 Subject: JUnit tests for dead code elimination. JUnit tests may use tools from partest-extras (ASMConverters) --- test/files/jvm/t6941/test.scala | 4 +- test/files/jvm/t7253/test.scala | 6 +- .../scala/tools/nsc/backend/jvm/BTypesTest.scala | 1 - .../scala/tools/nsc/backend/jvm/CodeGenTools.scala | 27 ++++ .../nsc/backend/jvm/opt/UnreachableCodeTest.scala | 162 +++++++++++++++++++++ .../scala/tools/testing/AssertThrowsTest.scala | 11 +- 6 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala create mode 100644 test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala (limited to 'test') diff --git a/test/files/jvm/t6941/test.scala b/test/files/jvm/t6941/test.scala index 248617f71f..fceb54487f 100644 --- a/test/files/jvm/t6941/test.scala +++ b/test/files/jvm/t6941/test.scala @@ -1,4 +1,4 @@ -import scala.tools.partest.BytecodeTest +import scala.tools.partest.{BytecodeTest, ASMConverters} import scala.tools.nsc.util.JavaClassPath import java.io.InputStream @@ -10,6 +10,6 @@ import scala.collection.JavaConverters._ object Test extends BytecodeTest { def show: Unit = { val classNode = loadClassNode("SameBytecode") - similarBytecode(getMethod(classNode, "a"), getMethod(classNode, "b"), equalsModuloVar) + similarBytecode(getMethod(classNode, "a"), getMethod(classNode, "b"), ASMConverters.equivalentBytecode(_, _)) } } diff --git a/test/files/jvm/t7253/test.scala b/test/files/jvm/t7253/test.scala index 7fe08e8813..a3f1e86e65 100644 --- a/test/files/jvm/t7253/test.scala +++ b/test/files/jvm/t7253/test.scala @@ -1,4 +1,4 @@ -import scala.tools.partest.BytecodeTest +import scala.tools.partest.{BytecodeTest, ASMConverters} import scala.tools.nsc.util.JavaClassPath import java.io.InputStream @@ -8,10 +8,10 @@ import asm.tree.{ClassNode, InsnList} import scala.collection.JavaConverters._ object Test extends BytecodeTest { - import instructions._ + import ASMConverters._ def show: Unit = { - val instrBaseSeqs = Seq("ScalaClient_1", "JavaClient_1") map (name => instructions.fromMethod(getMethod(loadClassNode(name), "foo"))) + val instrBaseSeqs = Seq("ScalaClient_1", "JavaClient_1") map (name => instructionsFromMethod(getMethod(loadClassNode(name), "foo"))) val instrSeqs = instrBaseSeqs map (_ filter isInvoke) cmpInstructions(instrSeqs(0), instrSeqs(1)) } diff --git a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala index cb7e7050b0..221aad6536 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/BTypesTest.scala @@ -1,7 +1,6 @@ package scala.tools.nsc package backend.jvm -import scala.tools.testing.AssertUtil._ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.Test diff --git a/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala new file mode 100644 index 0000000000..256dff85c3 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/CodeGenTools.scala @@ -0,0 +1,27 @@ +package scala.tools.nsc.backend.jvm + +import scala.tools.asm.Opcodes +import scala.tools.asm.tree.{AbstractInsnNode, LabelNode, ClassNode, MethodNode} +import scala.tools.partest.ASMConverters +import scala.collection.JavaConverters._ + +object CodeGenTools { + import ASMConverters._ + + def genMethod( flags: Int = Opcodes.ACC_PUBLIC, + name: String = "m", + descriptor: String = "()V", + genericSignature: String = null, + throwsExceptions: Array[String] = null)(body: Instruction*): MethodNode = { + val node = new MethodNode(flags, name, descriptor, genericSignature, throwsExceptions) + applyToMethod(node, body.toList) + node + } + + def wrapInClass(method: MethodNode): ClassNode = { + val cls = new ClassNode() + cls.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "C", null, "java/lang/Object", null) + cls.methods.add(method) + cls + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala new file mode 100644 index 0000000000..4e62cc64e4 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala @@ -0,0 +1,162 @@ +package scala.tools.nsc +package backend.jvm +package opt + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import scala.tools.asm.Opcodes._ +import org.junit.Assert._ + +import scala.tools.testing.AssertUtil._ + +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ + +@RunWith(classOf[JUnit4]) +class UnreachableCodeTest { + import UnreachableCodeTest._ + + @Test + def basicElimination(): Unit = { + assertEliminateDead( + Op(ACONST_NULL), + Op(ATHROW), + Op(RETURN).dead + ) + + assertEliminateDead( + Op(RETURN) + ) + + assertEliminateDead( + Op(RETURN), + Op(ACONST_NULL).dead, + Op(ATHROW).dead + ) + } + + @Test + def eliminateNop(): Unit = { + assertEliminateDead( + // not dead, since visited by data flow analysis. need a different opt to eliminate it. + Op(NOP), + Op(RETURN), + Op(NOP).dead + ) + } + + @Test + def eliminateBranchOver(): Unit = { + assertEliminateDead( + Jump(GOTO, Label(1)), + Op(ACONST_NULL).dead, + Op(ATHROW).dead, + Label(1), + Op(RETURN) + ) + + assertEliminateDead( + Jump(GOTO, Label(1)), + Label(1), + Op(RETURN) + ) + } + + @Test + def deadLabelsRemain(): Unit = { + assertEliminateDead( + Op(RETURN), + Jump(GOTO, Label(1)).dead, + // not dead - labels may be referenced from other places in a classfile (eg exceptions table). + // will need a different opt to get rid of them + Label(1) + ) + } + + @Test + def pushPopNotEliminated(): Unit = { + assertEliminateDead( + // not dead, visited by data flow analysis. + Op(ACONST_NULL), + Op(POP), + Op(RETURN) + ) + } + + @Test + def nullnessNotConsidered(): Unit = { + assertEliminateDead( + Op(ACONST_NULL), + Jump(IFNULL, Label(1)), + Op(RETURN), // not dead + Label(1), + Op(RETURN) + ) + } + + @Test + def metaTest(): Unit = { + assertEliminateDead() // no instructions + + assertThrows[AssertionError]( + assertEliminateDead(Op(RETURN).dead), + _.contains("Expected: List()\nActual : List(Op(RETURN))") + ) + + assertThrows[AssertionError]( + assertEliminateDead(Op(RETURN), Op(RETURN)), + _.contains("Expected: List(Op(RETURN), Op(RETURN))\nActual : List(Op(RETURN))") + ) + } + + @Test + def bytecodeEquivalence: Unit = { + assertTrue(List(VarOp(ILOAD, 1)) === + List(VarOp(ILOAD, 2))) + assertTrue(List(VarOp(ILOAD, 1), VarOp(ISTORE, 1)) === + List(VarOp(ILOAD, 2), VarOp(ISTORE, 2))) + + // the first Op will associate 1->2, then the 2->2 will fail + assertFalse(List(VarOp(ILOAD, 1), VarOp(ISTORE, 2)) === + List(VarOp(ILOAD, 2), VarOp(ISTORE, 2))) + + // will associate 1->2 and 2->1, which is OK + assertTrue(List(VarOp(ILOAD, 1), VarOp(ISTORE, 2)) === + List(VarOp(ILOAD, 2), VarOp(ISTORE, 1))) + + assertTrue(List(Label(1), Label(2), Label(1)) === + List(Label(2), Label(4), Label(2))) + assertTrue(List(LineNumber(1, Label(1)), Label(1)) === + List(LineNumber(1, Label(3)), Label(3))) + assertFalse(List(LineNumber(1, Label(1)), Label(1)) === + List(LineNumber(1, Label(3)), Label(1))) + + assertTrue(List(TableSwitch(TABLESWITCH, 1, 3, Label(4), List(Label(5), Label(6))), Label(4), Label(5), Label(6)) === + List(TableSwitch(TABLESWITCH, 1, 3, Label(9), List(Label(3), Label(4))), Label(9), Label(3), Label(4))) + + assertTrue(List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(3)), List("java/lang/Object", Label(4))), Label(3), Label(4)) === + List(FrameEntry(F_FULL, List(INTEGER, DOUBLE, Label(1)), List("java/lang/Object", Label(3))), Label(1), Label(3))) + } +} + +object UnreachableCodeTest { + import scala.language.implicitConversions + implicit def aliveInstruction(ins: Instruction): (Instruction, Boolean) = (ins, true) + + implicit class MortalInstruction(val ins: Instruction) extends AnyVal { + def dead: (Instruction, Boolean) = (ins, false) + } + + def assertEliminateDead(code: (Instruction, Boolean)*): Unit = { + val cls = wrapInClass(genMethod()(code.map(_._1): _*)) + LocalOpt.removeUnreachableCode(cls) + val nonEliminated = instructionsFromMethod(cls.methods.get(0)) + + val expectedLive = code.filter(_._2).map(_._1).toList + + assertTrue(s"\nExpected: $expectedLive\nActual : $nonEliminated", nonEliminated === expectedLive) + } + +} diff --git a/test/junit/scala/tools/testing/AssertThrowsTest.scala b/test/junit/scala/tools/testing/AssertThrowsTest.scala index a70519e63c..d91e450bac 100644 --- a/test/junit/scala/tools/testing/AssertThrowsTest.scala +++ b/test/junit/scala/tools/testing/AssertThrowsTest.scala @@ -31,4 +31,13 @@ class AssertThrowsTest { } }) -} \ No newline at end of file + @Test + def errorIfNoThrow: Unit = { + try { + assertThrows[Foo] { () } + } catch { + case e: AssertionError => return + } + assert(false, "assertThrows should error if the tested expression does not throw anything") + } +} -- cgit v1.2.3