@@ -8,11 +8,11 @@ import asm.tree.{ClassNode, MethodNode, InsnList}
8
8
import java .io .InputStream
9
9
10
10
/**
11
- * Providies utilities for inspecting bytecode using ASM library.
11
+ * Provides utilities for inspecting bytecode using ASM library.
12
12
*
13
13
* HOW TO USE
14
14
* 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
16
16
* want to inspect the bytecode for. The '_1' suffix signals to partest that it
17
17
* should compile this file first.
18
18
* 3. Create $TESTDIR/Test.scala:
@@ -35,19 +35,31 @@ abstract class BytecodeTest {
35
35
36
36
def main (args : Array [String ]): Unit = show
37
37
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
38
50
protected def getMethod (classNode : ClassNode , name : String ): MethodNode =
39
51
classNode.methods.asScala.find(_.name == name) getOrElse
40
52
sys.error(s " Didn't find method ' $name' in class ' ${classNode.name}' " )
41
53
42
- protected def loadClassNode (name : String ): ClassNode = {
54
+ protected def loadClassNode (name : String , skipDebugInfo : Boolean = true ): ClassNode = {
43
55
val classBytes : InputStream = (for {
44
56
classRep <- classpath.findClass(name)
45
57
binary <- classRep.binary
46
58
} yield binary.input) getOrElse sys.error(s " failed to load class ' $name'; classpath = $classpath" )
47
59
48
60
val cr = new ClassReader (classBytes)
49
61
val cn = new ClassNode ()
50
- cr.accept(cn, 0 )
62
+ cr.accept(cn, if (skipDebugInfo) ClassReader . SKIP_DEBUG else 0 )
51
63
cn
52
64
}
53
65
@@ -58,4 +70,61 @@ abstract class BytecodeTest {
58
70
val containers = DefaultJavaContext .classesInExpandedPath(Defaults .javaUserClassPath)
59
71
new JavaClassPath (containers, DefaultJavaContext )
60
72
}
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
+ }
61
130
}
0 commit comments