summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala
blob: 402dc66a7f39a21781999c7b3b029b7b94a5e12c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* NSC -- new Scala compiler
 * Copyright 2005-2014 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala.tools.nsc.backend.jvm

import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, FieldNode, InsnList, MethodNode}
import java.io.{PrintWriter, StringWriter}
import java.util

import scala.tools.asm.util.{CheckClassAdapter, Textifier, TraceClassVisitor, TraceMethodVisitor}
import scala.tools.asm.{Attribute, ClassReader, ClassWriter}
import scala.collection.JavaConverters._
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
import scala.tools.nsc.backend.jvm.analysis.InitialProducer
import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype

object AsmUtils {

  /**
   * Print the bytecode of methods generated by GenBCode to the standard output. Only methods
   * whose name contains `traceMethodPattern` are traced.
   */
  final val traceMethodEnabled = false
  final val traceMethodPattern = ""

  /**
   * Print the bytecode of classes generated by GenBCode to the standard output.
   */
  final val traceClassEnabled = false
  final val traceClassPattern = ""

  /**
   * Print the bytecode of classes as they are serialized by the ASM library. The serialization
   * performed by `asm.ClassWriter` can change the code generated by GenBCode. For example, it
   * introduces stack map frames, it computes the maximal stack sizes, and it replaces dead
   * code by NOPs (see also https://github.com/scala/scala/pull/3726#issuecomment-42861780).
   */
  final val traceSerializedClassEnabled = false
  final val traceSerializedClassPattern = ""

  def traceMethod(mnode: MethodNode): Unit = {
    println(s"Bytecode for method ${mnode.name}")
    println(textify(mnode))
  }

  def traceClass(cnode: ClassNode): Unit = {
    println(s"Bytecode for class ${cnode.name}")
    println(textify(cnode))
  }

  def traceClass(bytes: Array[Byte]): Unit = traceClass(readClass(bytes))

  def readClass(bytes: Array[Byte]): ClassNode = {
    val node = new ClassNode()
    new ClassReader(bytes).accept(node, Array[Attribute](InlineInfoAttributePrototype), 0)
    node
  }

  def readClass(filename: String): ClassNode = readClass(classBytes(filename))

  def classBytes(file: String): Array[Byte] = {
    val f = new java.io.RandomAccessFile(file, "r")
    val bytes = new Array[Byte](f.length.toInt)
    f.read(bytes)
    bytes
  }

  def classFromBytes(bytes: Array[Byte]): ClassNode = {
    val node = new ClassNode()
    new ClassReader(bytes).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)

    node
  }

//  def main(args: Array[String]): Unit = println(textify(sortedClassRead(classBytes(args.head))))

  def sortClassMembers(node: ClassNode): node.type = {
    node.fields.sort(_.name compareTo _.name)
    node.methods.sort(_.name compareTo _.name)
    node
  }

  // drop ScalaSig annotation and class attributes
  def zapScalaClassAttrs(node: ClassNode): node.type = {
    if (node.visibleAnnotations != null)
      node.visibleAnnotations = node.visibleAnnotations.asScala.filterNot(a => a == null || a.desc.contains("Lscala/reflect/ScalaSignature")).asJava

    node.attrs = null
    node
  }

  def main(args: Array[String]): Unit = args.par.foreach { classFileName =>
    val node = zapScalaClassAttrs(sortClassMembers(classFromBytes(classBytes(classFileName))))

    val pw = new PrintWriter(classFileName + ".asm")
    val trace = new TraceClassVisitor(pw)
    node.accept(trace)
    pw.close()
  }

  /**
   * Returns a human-readable representation of the cnode ClassNode.
   */
  def textify(cnode: ClassNode): String = {
    val trace = new TraceClassVisitor(new PrintWriter(new StringWriter))
    cnode.accept(trace)
    val sw = new StringWriter
    val pw = new PrintWriter(sw)
    trace.p.print(pw)
    sw.toString
  }

  /**
   * Returns a human-readable representation of the code in the mnode MethodNode.
   */
  def textify(mnode: MethodNode): String = {
    val trace = new TraceClassVisitor(new PrintWriter(new StringWriter))
    mnode.accept(trace)
    val sw = new StringWriter
    val pw = new PrintWriter(sw)
    trace.p.print(pw)
    sw.toString
  }

  /**
   * Returns a human-readable representation of the given instruction.
   */
  def textify(insn: AbstractInsnNode): String = insn match {
    case _: InitialProducer =>
      insn.toString
    case _ =>
      val trace = new TraceMethodVisitor(new Textifier)
      insn.accept(trace)
      val sw = new StringWriter
      val pw = new PrintWriter(sw)
      trace.p.print(pw)
      sw.toString.trim
  }

  /**
   * Returns a human-readable representation of the given instruction sequence.
   */
  def textify(insns: Iterator[AbstractInsnNode]): String = {
    val trace = new TraceMethodVisitor(new Textifier)
    insns.foreach(_.accept(trace))
    val sw: StringWriter = new StringWriter
    val pw: PrintWriter = new PrintWriter(sw)
    trace.p.print(pw)
    sw.toString.trim
  }

  /**
   * Returns a human-readable representation of the given instruction sequence.
   */
  def textify(insns: InsnList): String = textify(insns.iterator().asScala)

  /**
   * Run ASM's CheckClassAdapter over a class. Returns None if no problem is found, otherwise
   * Some(msg) with the verifier's error message.
   */
  def checkClass(classNode: ClassNode, dumpNonErroneous: Boolean = false): Option[String] = {
    val cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
    classNode.accept(cw)
    val sw = new StringWriter()
    val pw = new PrintWriter(sw)
    CheckClassAdapter.verify(new ClassReader(cw.toByteArray), dumpNonErroneous, pw)
    val res = sw.toString
    if (res.isEmpty) None else Some(res)
  }
}