Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

bytecode diffing support in ByteCodeTest

use sameByteCode(methodNode1, methodNode2) to check methods
compile to identical bytecode (line number info is not taken into account)
  • Loading branch information...
commit a07555f015939412c6e4421860abce6e5f90f710 1 parent 42c4cc7
Adriaan Moors adriaanm authored
Showing with 73 additions and 4 deletions.
  1. +73 −4 src/partest/scala/tools/partest/BytecodeTest.scala
77 src/partest/scala/tools/partest/BytecodeTest.scala
View
@@ -8,11 +8,11 @@ import asm.tree.{ClassNode, MethodNode, InsnList}
import java.io.InputStream
/**
- * Providies utilities for inspecting bytecode using ASM library.
+ * Provides 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
+ * 2. Create $TESTDIR/BytecodeSrc_1.scala that contains Scala source file that 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:
@@ -35,11 +35,23 @@ abstract class BytecodeTest {
def main(args: Array[String]): Unit = show
+// asserts
+ def sameBytecode(methA: MethodNode, methB: MethodNode) = {
+ val isa = instructions.fromMethod(methA)
+ val isb = instructions.fromMethod(methB)
+ if (isa == isb) println("bytecode identical")
+ else (isa, isb).zipped.foreach { case (a, b) =>
+ if (a == b) println("OK : "+ a)
+ else println("DIFF: "+ a +" <=> "+ b)
+ }
+ }
+
+// loading
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 = {
+ protected def loadClassNode(name: String, skipDebugInfo: Boolean = true): ClassNode = {
val classBytes: InputStream = (for {
classRep <- classpath.findClass(name)
binary <- classRep.binary
@@ -47,7 +59,7 @@ abstract class BytecodeTest {
val cr = new ClassReader(classBytes)
val cn = new ClassNode()
- cr.accept(cn, 0)
+ cr.accept(cn, if (skipDebugInfo) ClassReader.SKIP_DEBUG else 0)
cn
}
@@ -58,4 +70,61 @@ abstract class BytecodeTest {
val containers = DefaultJavaContext.classesInExpandedPath(Defaults.javaUserClassPath)
new JavaClassPath(containers, DefaultJavaContext)
}
+
+ // wrap ASM's instructions so we get case class-style `equals` and `toString`
+ object instructions {
+ def fromMethod(meth: MethodNode): List[instructions.Instruction] = {
+ val insns = meth.instructions
+ val asmToScala = new AsmToScala{ def labelIndex(l: asm.tree.AbstractInsnNode) = insns.indexOf(l) }
+
+ asmToScala.mapOver(insns.iterator.asScala.toList).asInstanceOf[List[instructions.Instruction]]
+ }
+
+ sealed abstract class Instruction { def opcode: Int }
+ case class Field (opcode: Int, desc: String, name: String, owner: String) extends Instruction
+ case class Incr (opcode: Int, incr: Int, `var`: Int) extends Instruction
+ case class Op (opcode: Int) extends Instruction
+ case class IntOp (opcode: Int, operand: Int) extends Instruction
+ case class Jump (opcode: Int, label: Label) extends Instruction
+ case class Ldc (opcode: Int, cst: Any) extends Instruction
+ case class LookupSwitch (opcode: Int, dflt: Label, keys: List[Integer], labels: List[Label]) extends Instruction
+ case class TableSwitch (opcode: Int, dflt: Label, max: Int, min: Int, labels: List[Label]) extends Instruction
+ case class Method (opcode: Int, desc: String, name: String, owner: String) extends Instruction
+ case class NewArray (opcode: Int, desc: String, dims: Int) extends Instruction
+ case class TypeOp (opcode: Int, desc: String) extends Instruction
+ case class VarOp (opcode: Int, `var`: Int) extends Instruction
+ case class Label (opcode: Int, offset: Int) extends Instruction
+ case class FrameEntry (opcode: Int, local: List[Any], stack: List[Any]) extends Instruction
+ case class LineNumber (opcode: Int, line: Int, start: Label) extends Instruction
+ }
+
+ abstract class AsmToScala {
+ import instructions._
+ def labelIndex(l: asm.tree.AbstractInsnNode): Int
+
+ def mapOver(is: List[Any]): List[Any] = is map {
+ case i: asm.tree.AbstractInsnNode => apply(i)
+ case x => x
+ }
+
+ def lst[T](xs: java.util.List[T]): List[T] = if (xs == null) Nil else xs.asScala.toList
+ def apply(l: asm.tree.LabelNode): Label = this(l: asm.tree.AbstractInsnNode).asInstanceOf[Label]
+ def apply(x: asm.tree.AbstractInsnNode): Instruction = x match {
+ case i: asm.tree.FieldInsnNode => Field (i.getOpcode: Int, i.desc: String, i.name: String, i.owner: String)
+ case i: asm.tree.IincInsnNode => Incr (i.getOpcode: Int, i.incr: Int, i.`var`: Int)
+ case i: asm.tree.InsnNode => Op (i.getOpcode: Int)
+ case i: asm.tree.IntInsnNode => IntOp (i.getOpcode: Int, i.operand: Int)
+ case i: asm.tree.JumpInsnNode => Jump (i.getOpcode: Int, this(i.label))
+ case i: asm.tree.LdcInsnNode => Ldc (i.getOpcode: Int, i.cst: Any)
+ case i: asm.tree.LookupSwitchInsnNode => LookupSwitch (i.getOpcode: Int, this(i.dflt), lst(i.keys), mapOver(lst(i.labels)).asInstanceOf[List[Label]])
+ case i: asm.tree.TableSwitchInsnNode => TableSwitch (i.getOpcode: Int, this(i.dflt), i.max: Int, i.min: Int, mapOver(lst(i.labels)).asInstanceOf[List[Label]])
+ case i: asm.tree.MethodInsnNode => Method (i.getOpcode: Int, i.desc: String, i.name: String, i.owner: String)
+ case i: asm.tree.MultiANewArrayInsnNode => NewArray (i.getOpcode: Int, i.desc: String, i.dims: Int)
+ case i: asm.tree.TypeInsnNode => TypeOp (i.getOpcode: Int, i.desc: String)
+ case i: asm.tree.VarInsnNode => VarOp (i.getOpcode: Int, i.`var`: Int)
+ case i: asm.tree.LabelNode => Label (i.getOpcode: Int, labelIndex(x))
+ case i: asm.tree.FrameNode => FrameEntry (i.getOpcode: Int, mapOver(lst(i.local)), mapOver(lst(i.stack)))
+ case i: asm.tree.LineNumberNode => LineNumber (i.getOpcode: Int, i.line: Int, this(i.start): Label)
+ }
+ }
}
Please sign in to comment.
Something went wrong with that request. Please try again.