diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-01-31 10:20:32 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-01-31 11:00:42 -0800 |
commit | 415becdab8aae63470deac5a6e122fa7a697cbe0 (patch) | |
tree | cd30e8f9352df8c467f580e741f372867213a146 /src/partest | |
parent | a07555f015939412c6e4421860abce6e5f90f710 (diff) | |
download | scala-415becdab8aae63470deac5a6e122fa7a697cbe0.tar.gz scala-415becdab8aae63470deac5a6e122fa7a697cbe0.tar.bz2 scala-415becdab8aae63470deac5a6e122fa7a697cbe0.zip |
support testing bytecode similarity in ByteCodeTest
one similarity measure comes free of charge: it ignores which variable
is stored/loaded, everything else must be identical
like this: `similarBytecode(methNodeA, methNodeB, equalsModuloVar)`
also implemented prettier diffing
Diffstat (limited to 'src/partest')
-rw-r--r-- | src/partest/scala/tools/partest/ASMConverters.scala | 71 | ||||
-rw-r--r-- | src/partest/scala/tools/partest/BytecodeTest.scala | 94 |
2 files changed, 104 insertions, 61 deletions
diff --git a/src/partest/scala/tools/partest/ASMConverters.scala b/src/partest/scala/tools/partest/ASMConverters.scala new file mode 100644 index 0000000000..d618e086f4 --- /dev/null +++ b/src/partest/scala/tools/partest/ASMConverters.scala @@ -0,0 +1,71 @@ +package scala.tools.partest + +import scala.collection.JavaConverters._ +import scala.tools.asm +import asm.tree.{ClassNode, MethodNode, InsnList} + +/** Makes using ASM from ByteCodeTests more convenient. + * + * Wraps ASM instructions in case classes so that equals and toString work + * for the purpose of bytecode diffing and pretty printing. + */ +trait ASMConverters { + // wrap ASM's instructions so we get case class-style `equals` and `toString` + object instructions { + def fromMethod(meth: MethodNode): List[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[Instruction]] + } + + sealed abstract class Instruction { def opcode: String } + case class Field (opcode: String, desc: String, name: String, owner: String) extends Instruction + case class Incr (opcode: String, incr: Int, `var`: Int) extends Instruction + case class Op (opcode: String) extends Instruction + case class IntOp (opcode: String, operand: Int) extends Instruction + case class Jump (opcode: String, label: Label) extends Instruction + case class Ldc (opcode: String, cst: Any) extends Instruction + case class LookupSwitch (opcode: String, dflt: Label, keys: List[Integer], labels: List[Label]) extends Instruction + case class TableSwitch (opcode: String, dflt: Label, max: Int, min: Int, labels: List[Label]) extends Instruction + case class Method (opcode: String, desc: String, name: String, owner: String) extends Instruction + case class NewArray (opcode: String, desc: String, dims: Int) extends Instruction + case class TypeOp (opcode: String, desc: String) extends Instruction + case class VarOp (opcode: String, `var`: Int) extends Instruction + case class Label (offset: Int) extends Instruction { def opcode: String = "" } + case class FrameEntry (local: List[Any], stack: List[Any]) extends Instruction { def opcode: String = "" } + case class LineNumber (line: Int, start: Label) extends Instruction { def opcode: String = "" } + } + + 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 op(i: asm.tree.AbstractInsnNode) = if (asm.util.Printer.OPCODES.isDefinedAt(i.getOpcode)) asm.util.Printer.OPCODES(i.getOpcode) else "?" + 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 (op(i), i.desc: String, i.name: String, i.owner: String) + case i: asm.tree.IincInsnNode => Incr (op(i), i.incr: Int, i.`var`: Int) + case i: asm.tree.InsnNode => Op (op(i)) + case i: asm.tree.IntInsnNode => IntOp (op(i), i.operand: Int) + case i: asm.tree.JumpInsnNode => Jump (op(i), this(i.label)) + case i: asm.tree.LdcInsnNode => Ldc (op(i), i.cst: Any) + case i: asm.tree.LookupSwitchInsnNode => LookupSwitch (op(i), this(i.dflt), lst(i.keys), mapOver(lst(i.labels)).asInstanceOf[List[Label]]) + case i: asm.tree.TableSwitchInsnNode => TableSwitch (op(i), this(i.dflt), i.max: Int, i.min: Int, mapOver(lst(i.labels)).asInstanceOf[List[Label]]) + case i: asm.tree.MethodInsnNode => Method (op(i), i.desc: String, i.name: String, i.owner: String) + case i: asm.tree.MultiANewArrayInsnNode => NewArray (op(i), i.desc: String, i.dims: Int) + case i: asm.tree.TypeInsnNode => TypeOp (op(i), i.desc: String) + case i: asm.tree.VarInsnNode => VarOp (op(i), i.`var`: Int) + case i: asm.tree.LabelNode => Label (labelIndex(x)) + case i: asm.tree.FrameNode => FrameEntry (mapOver(lst(i.local)), mapOver(lst(i.stack))) + case i: asm.tree.LineNumberNode => LineNumber (i.line: Int, this(i.start): Label) + } + } +}
\ No newline at end of file diff --git a/src/partest/scala/tools/partest/BytecodeTest.scala b/src/partest/scala/tools/partest/BytecodeTest.scala index 1f43ee7d2e..41329a8264 100644 --- a/src/partest/scala/tools/partest/BytecodeTest.scala +++ b/src/partest/scala/tools/partest/BytecodeTest.scala @@ -28,7 +28,7 @@ import java.io.InputStream * See test/files/jvm/bytecode-test-example for an example of bytecode test. * */ -abstract class BytecodeTest { +abstract class BytecodeTest extends ASMConverters { /** produce the output to be compared against a checkfile */ protected def show(): Unit @@ -40,9 +40,38 @@ abstract class BytecodeTest { 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) + else diffInstructions(isa, isb) + } + + import instructions._ + // bytecode is equal modulo local variable numbering + def equalsModuloVar(a: Instruction, b: Instruction) = (a, b) match { + case _ if a == b => true + case (VarOp(op1, _), VarOp(op2, _)) if op1 == op2 => true + case _ => false + } + + def similarBytecode(methA: MethodNode, methB: MethodNode, similar: (Instruction, Instruction) => Boolean) = { + val isa = fromMethod(methA) + val isb = fromMethod(methB) + if (isa == isb) println("bytecode identical") + else if ((isa, isb).zipped.forall { case (a, b) => similar(a, b) }) println("bytecode similar") + else diffInstructions(isa, isb) + } + + def diffInstructions(isa: List[Instruction], isb: List[Instruction]) = { + val len = Math.max(isa.length, isb.length) + if (len > 0 ) { + val width = isa.map(_.toString.length).max + val lineWidth = len.toString.length + (1 to len) foreach { line => + val isaPadded = isa.map(_.toString) orElse Stream.continually("") + val isbPadded = isb.map(_.toString) orElse Stream.continually("") + val a = isaPadded(line-1) + val b = isbPadded(line-1) + + println(s"""$line${" " * (lineWidth-line.toString.length)} ${if (a==b) "==" else "<>"} $a${" " * (width-a.length)} | $b""") + } } } @@ -70,61 +99,4 @@ 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) - } - } } |