Skip to content

Commit a07555f

Browse files
committed
bytecode diffing support in ByteCodeTest
use sameByteCode(methodNode1, methodNode2) to check methods compile to identical bytecode (line number info is not taken into account)
1 parent 42c4cc7 commit a07555f

File tree

1 file changed

+73
-4
lines changed

1 file changed

+73
-4
lines changed

src/partest/scala/tools/partest/BytecodeTest.scala

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import asm.tree.{ClassNode, MethodNode, InsnList}
88
import java.io.InputStream
99

1010
/**
11-
* Providies utilities for inspecting bytecode using ASM library.
11+
* Provides utilities for inspecting bytecode using ASM library.
1212
*
1313
* HOW TO USE
1414
* 1. Create subdirectory in test/files/jvm for your test. Let's name it $TESTDIR.
15-
* 2. Create $TESTDIR/BytecodeSrc_1.scala that contains Scala source file which you
15+
* 2. Create $TESTDIR/BytecodeSrc_1.scala that contains Scala source file that you
1616
* want to inspect the bytecode for. The '_1' suffix signals to partest that it
1717
* should compile this file first.
1818
* 3. Create $TESTDIR/Test.scala:
@@ -35,19 +35,31 @@ abstract class BytecodeTest {
3535

3636
def main(args: Array[String]): Unit = show
3737

38+
// asserts
39+
def sameBytecode(methA: MethodNode, methB: MethodNode) = {
40+
val isa = instructions.fromMethod(methA)
41+
val isb = instructions.fromMethod(methB)
42+
if (isa == isb) println("bytecode identical")
43+
else (isa, isb).zipped.foreach { case (a, b) =>
44+
if (a == b) println("OK : "+ a)
45+
else println("DIFF: "+ a +" <=> "+ b)
46+
}
47+
}
48+
49+
// loading
3850
protected def getMethod(classNode: ClassNode, name: String): MethodNode =
3951
classNode.methods.asScala.find(_.name == name) getOrElse
4052
sys.error(s"Didn't find method '$name' in class '${classNode.name}'")
4153

42-
protected def loadClassNode(name: String): ClassNode = {
54+
protected def loadClassNode(name: String, skipDebugInfo: Boolean = true): ClassNode = {
4355
val classBytes: InputStream = (for {
4456
classRep <- classpath.findClass(name)
4557
binary <- classRep.binary
4658
} yield binary.input) getOrElse sys.error(s"failed to load class '$name'; classpath = $classpath")
4759

4860
val cr = new ClassReader(classBytes)
4961
val cn = new ClassNode()
50-
cr.accept(cn, 0)
62+
cr.accept(cn, if (skipDebugInfo) ClassReader.SKIP_DEBUG else 0)
5163
cn
5264
}
5365

@@ -58,4 +70,61 @@ abstract class BytecodeTest {
5870
val containers = DefaultJavaContext.classesInExpandedPath(Defaults.javaUserClassPath)
5971
new JavaClassPath(containers, DefaultJavaContext)
6072
}
73+
74+
// wrap ASM's instructions so we get case class-style `equals` and `toString`
75+
object instructions {
76+
def fromMethod(meth: MethodNode): List[instructions.Instruction] = {
77+
val insns = meth.instructions
78+
val asmToScala = new AsmToScala{ def labelIndex(l: asm.tree.AbstractInsnNode) = insns.indexOf(l) }
79+
80+
asmToScala.mapOver(insns.iterator.asScala.toList).asInstanceOf[List[instructions.Instruction]]
81+
}
82+
83+
sealed abstract class Instruction { def opcode: Int }
84+
case class Field (opcode: Int, desc: String, name: String, owner: String) extends Instruction
85+
case class Incr (opcode: Int, incr: Int, `var`: Int) extends Instruction
86+
case class Op (opcode: Int) extends Instruction
87+
case class IntOp (opcode: Int, operand: Int) extends Instruction
88+
case class Jump (opcode: Int, label: Label) extends Instruction
89+
case class Ldc (opcode: Int, cst: Any) extends Instruction
90+
case class LookupSwitch (opcode: Int, dflt: Label, keys: List[Integer], labels: List[Label]) extends Instruction
91+
case class TableSwitch (opcode: Int, dflt: Label, max: Int, min: Int, labels: List[Label]) extends Instruction
92+
case class Method (opcode: Int, desc: String, name: String, owner: String) extends Instruction
93+
case class NewArray (opcode: Int, desc: String, dims: Int) extends Instruction
94+
case class TypeOp (opcode: Int, desc: String) extends Instruction
95+
case class VarOp (opcode: Int, `var`: Int) extends Instruction
96+
case class Label (opcode: Int, offset: Int) extends Instruction
97+
case class FrameEntry (opcode: Int, local: List[Any], stack: List[Any]) extends Instruction
98+
case class LineNumber (opcode: Int, line: Int, start: Label) extends Instruction
99+
}
100+
101+
abstract class AsmToScala {
102+
import instructions._
103+
def labelIndex(l: asm.tree.AbstractInsnNode): Int
104+
105+
def mapOver(is: List[Any]): List[Any] = is map {
106+
case i: asm.tree.AbstractInsnNode => apply(i)
107+
case x => x
108+
}
109+
110+
def lst[T](xs: java.util.List[T]): List[T] = if (xs == null) Nil else xs.asScala.toList
111+
def apply(l: asm.tree.LabelNode): Label = this(l: asm.tree.AbstractInsnNode).asInstanceOf[Label]
112+
def apply(x: asm.tree.AbstractInsnNode): Instruction = x match {
113+
case i: asm.tree.FieldInsnNode => Field (i.getOpcode: Int, i.desc: String, i.name: String, i.owner: String)
114+
case i: asm.tree.IincInsnNode => Incr (i.getOpcode: Int, i.incr: Int, i.`var`: Int)
115+
case i: asm.tree.InsnNode => Op (i.getOpcode: Int)
116+
case i: asm.tree.IntInsnNode => IntOp (i.getOpcode: Int, i.operand: Int)
117+
case i: asm.tree.JumpInsnNode => Jump (i.getOpcode: Int, this(i.label))
118+
case i: asm.tree.LdcInsnNode => Ldc (i.getOpcode: Int, i.cst: Any)
119+
case i: asm.tree.LookupSwitchInsnNode => LookupSwitch (i.getOpcode: Int, this(i.dflt), lst(i.keys), mapOver(lst(i.labels)).asInstanceOf[List[Label]])
120+
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]])
121+
case i: asm.tree.MethodInsnNode => Method (i.getOpcode: Int, i.desc: String, i.name: String, i.owner: String)
122+
case i: asm.tree.MultiANewArrayInsnNode => NewArray (i.getOpcode: Int, i.desc: String, i.dims: Int)
123+
case i: asm.tree.TypeInsnNode => TypeOp (i.getOpcode: Int, i.desc: String)
124+
case i: asm.tree.VarInsnNode => VarOp (i.getOpcode: Int, i.`var`: Int)
125+
case i: asm.tree.LabelNode => Label (i.getOpcode: Int, labelIndex(x))
126+
case i: asm.tree.FrameNode => FrameEntry (i.getOpcode: Int, mapOver(lst(i.local)), mapOver(lst(i.stack)))
127+
case i: asm.tree.LineNumberNode => LineNumber (i.getOpcode: Int, i.line: Int, this(i.start): Label)
128+
}
129+
}
61130
}

0 commit comments

Comments
 (0)