Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add Bytecode test (ASM-based) to partest.

This commit introduces a new kind of test `Bytecode` that allows
one to inspect bytecode generated for given piece of Scala code.

The bytecode inspection is achieved by inspection of ASM trees.
See the included example for details.

NOTE: This commit does not introduce a new category of pratest tests.
Bytecode tests should be run in `jvm` category of partest tests.

Specific list of changes:
  * Add BytecodeTest that contains common utilities to partest
  * Add asm to classpath when compiling partest. That's not a
    new dependency as it's being already done for javac task
    we were running while compiling partest.
  * Add an example test that shows how to count null checks in
    given method.
  • Loading branch information...
commit 8610d7ec063407f62b11df848dd588e4594b3b40 1 parent eff78b8
@gkossakowski gkossakowski authored
View
1  build.xml
@@ -1377,6 +1377,7 @@ QUICK BUILD (QUICK)
<pathelement location="${ant.jar}"/>
<path refid="forkjoin.classpath"/>
<path refid="fjbg.classpath"/>
+ <path refid="asm.classpath"/>
<pathelement location="${scalacheck.jar}"/>
</compilationpath>
</scalacfork>
View
61 src/partest/scala/tools/partest/BytecodeTest.scala
@@ -0,0 +1,61 @@
+package scala.tools.partest
+
+import scala.tools.nsc.util.JavaClassPath
+import scala.collection.JavaConverters._
+import scala.tools.asm
+import asm.ClassReader
+import asm.tree.{ClassNode, MethodNode, InsnList}
+import java.io.InputStream
+
+/**
+ * Providies utilities for inspecting bytecode using ASM library.
+ *
+ * HOW TO USE
+ * 1. Create subdirectory in test/files/jvm for your test. Let's name it $TESTDIR.
+ * 2. Create $TESTDIR/BytecodeSrc_1.scala that contains Scala source file which you
+ * want to inspect the bytecode for. The '_1' suffix signals to partest that it
+ * should compile this file first.
+ * 3. Create $TESTDIR/Test.scala:
+ * import scala.tools.partest.BytecodeTest
+ * object Test extends BytecodeTest {
+ * def show {
+ * // your code that inspect ASM trees and prints values
+ * }
+ * }
+ * 4. Create corresponding check file.
+ *
+ * EXAMPLE
+ * See test/files/jvm/bytecode-test-example for an example of bytecode test.
+ *
+ */
+abstract class BytecodeTest {
+
+ /** produce the output to be compared against a checkfile */
+ protected def show(): Unit
+
+ def main(args: Array[String]): Unit = show
+
+ protected def getMethod(classNode: ClassNode, name: String): MethodNode =
+ classNode.methods.asScala.find(_.name == name) getOrElse
+ sys.error(s"Didn't find method '$name' in class '${classNode.name}'")
+
+ protected def loadClassNode(name: String): ClassNode = {
+ val classBytes: InputStream = (for {
+ classRep <- classpath.findClass(name)
+ binary <- classRep.binary
+ } yield binary.input) getOrElse sys.error(s"failed to load class '$name'; classpath = $classpath")
+
+ val cr = new ClassReader(classBytes)
+ val cn = new ClassNode()
+ cr.accept(cn, 0)
+ cn
+ }
+
+ protected lazy val classpath: JavaClassPath = {
+ import scala.tools.nsc.util.ClassPath.DefaultJavaContext
+ import scala.tools.util.PathResolver.Defaults
+ // logic inspired by scala.tools.util.PathResolver implementation
+ val containers = DefaultJavaContext.classesInExpandedPath(Defaults.javaUserClassPath)
+ new JavaClassPath(containers, DefaultJavaContext)
+ }
+}
View
1  test/files/jvm/bytecode-test-example.check
@@ -0,0 +1 @@
+2
View
9 test/files/jvm/bytecode-test-example/Foo_1.scala
@@ -0,0 +1,9 @@
+class Foo_1 {
+ def foo(x: AnyRef): Int = {
+ val bool = x == null
+ if (x != null)
+ 1
+ else
+ 0
+ }
+}
View
32 test/files/jvm/bytecode-test-example/Test.scala
@@ -0,0 +1,32 @@
+import scala.tools.partest.BytecodeTest
+
+import scala.tools.nsc.util.JavaClassPath
+import java.io.InputStream
+import scala.tools.asm
+import asm.ClassReader
+import asm.tree.{ClassNode, InsnList}
+import scala.collection.JavaConverters._
+
+object Test extends BytecodeTest {
+ def show: Unit = {
+ val classNode = loadClassNode("Foo_1")
+ val methodNode = getMethod(classNode, "foo")
+ println(countNullChecks(methodNode.instructions))
+ }
+
+ def countNullChecks(insnList: InsnList): Int = {
+ /** Is given instruction a null check?
+ * NOTE
+ * This will detect direct null compparsion as in
+ * if (x == null) ...
+ * and not indirect as in
+ * val foo = null
+ * if (x == foo) ...
+ */
+ def isNullCheck(node: asm.tree.AbstractInsnNode): Boolean = {
+ val opcode = node.getOpcode
+ (opcode == asm.Opcodes.IFNULL) || (opcode == asm.Opcodes.IFNONNULL)
+ }
+ insnList.iterator.asScala.count(isNullCheck)
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.