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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
|
/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Paul Phillips
*/
package scala.tools
package util
import java.lang.{ ClassLoader => JavaClassLoader, Iterable => JIterable }
import scala.tools.nsc.util.ScalaClassLoader
import java.io.{ ByteArrayInputStream, CharArrayWriter, FileNotFoundException, InputStream,
PrintWriter, Writer }
import java.util.{ Locale }
import javax.tools.{ Diagnostic, DiagnosticCollector, DiagnosticListener,
ForwardingJavaFileManager, JavaFileManager, JavaFileObject,
SimpleJavaFileObject, StandardLocation }
import scala.tools.nsc.io.File
import scala.util.{ Properties, Try, Success, Failure }
import scala.collection.JavaConverters
import scala.language.reflectiveCalls
import Javap._
trait Javap {
def loader: ScalaClassLoader
def printWriter: PrintWriter
def apply(args: Seq[String]): List[JpResult]
def tryFile(path: String): Option[Array[Byte]]
def tryClass(path: String): Array[Byte]
}
object NoJavap extends Javap {
def loader: ScalaClassLoader = getClass.getClassLoader
def printWriter: PrintWriter = new PrintWriter(System.err, true)
def apply(args: Seq[String]): List[JpResult] = Nil
def tryFile(path: String): Option[Array[Byte]] = None
def tryClass(path: String): Array[Byte] = Array()
}
class JavapClass(
val loader: ScalaClassLoader = ScalaClassLoader.appLoader,
val printWriter: PrintWriter = new PrintWriter(System.out, true)
) extends Javap {
lazy val tool = JavapTool(loader, printWriter)
/** Run the tool. Option args start with "-".
* The default options are "-protected -verbose".
* Byte data for filename args is retrieved with findBytes.
* If the filename does not end with ".class", javap will
* insert a banner of the form:
* `Binary file dummy contains simple.Complex`.
*/
def apply(args: Seq[String]): List[JpResult] = {
val (optional, claases) = args partition (_ startsWith "-")
val options = if (optional.nonEmpty) optional else JavapTool.DefaultOptions
tool(options)(claases map (claas => claas -> bytesFor(claas)))
}
private def bytesFor(path: String) = Try {
val bytes = findBytes(path)
if (bytes.isEmpty) throw new FileNotFoundException(s"Could not find class bytes for '${path}'")
else bytes
}
def findBytes(path: String): Array[Byte] = tryFile(path) getOrElse tryClass(path)
/** Assume the string is a path and try to find the classfile
* it represents.
*/
def tryFile(path: String): Option[Array[Byte]] = {
val file = File(
if (path.endsWith(".class")) path
else path.replace('.', '/') + ".class"
)
if (!file.exists) None
else try Some(file.toByteArray) catch { case x: Exception => None }
}
/** Assume the string is a fully qualified class name and try to
* find the class object it represents.
*/
def tryClass(path: String): Array[Byte] = loader classBytes {
if (path endsWith ".class") (path dropRight 6).replace('/', '.')
else path
}
}
abstract class JavapTool {
type ByteAry = Array[Byte]
type Input = Pair[String, Try[ByteAry]]
def apply(options: Seq[String])(inputs: Seq[Input]): List[JpResult]
// Since the tool is loaded by reflection, check for catastrophic failure.
protected def failed: Boolean
implicit protected class Failer[A](a: =>A) {
def orFailed[B >: A](b: =>B) = if (failed) b else a
}
protected def noToolError = new JpError(s"No javap tool available: ${getClass.getName} failed to initialize.")
}
class JavapTool6(loader: ScalaClassLoader, printWriter: PrintWriter) extends JavapTool {
import JavapTool._
val EnvClass = loader.tryToInitializeClass[FakeEnvironment](Env).orNull
val PrinterClass = loader.tryToInitializeClass[FakePrinter](Printer).orNull
override protected def failed = (EnvClass eq null) || (PrinterClass eq null)
val PrinterCtr = PrinterClass.getConstructor(classOf[InputStream], classOf[PrintWriter], EnvClass) orFailed null
def newPrinter(in: InputStream, env: FakeEnvironment): FakePrinter =
PrinterCtr.newInstance(in, printWriter, env) orFailed null
def showable(fp: FakePrinter) = new Showable {
def show() = fp.asInstanceOf[{ def print(): Unit }].print()
}
lazy val parser = new JpOptions
def newEnv(opts: Seq[String]): FakeEnvironment = {
def result = {
val env: FakeEnvironment = EnvClass.newInstance()
parser(opts) foreach { case (name, value) =>
val field = EnvClass getDeclaredField name
field setAccessible true
field.set(env, value.asInstanceOf[AnyRef])
}
env
}
result orFailed null
}
override def apply(options: Seq[String])(inputs: Seq[Input]): List[JpResult] =
(inputs map {
case (_, Success(ba)) => new JpSuccess(showable(newPrinter(new ByteArrayInputStream(ba), newEnv(options))))
case (_, Failure(e)) => new JpError(e.toString)
}).toList orFailed List(noToolError)
}
class JavapTool7(loader: ScalaClassLoader, printWriter: PrintWriter) extends JavapTool {
import JavapTool._
type Task = {
def call(): Boolean // true = ok
//def run(args: Array[String]): Int // all args
//def handleOptions(args: Array[String]): Unit // options, then run() or call()
}
// result of Task.run
//object TaskResult extends Enumeration {
// val Ok, Error, CmdErr, SysErr, Abnormal = Value
//}
val TaskClaas = loader.tryToInitializeClass[Task](JavapTool.Tool).orNull
override protected def failed = TaskClaas eq null
val TaskCtor = TaskClaas.getConstructor(
classOf[Writer],
classOf[JavaFileManager],
classOf[DiagnosticListener[_]],
classOf[JIterable[String]],
classOf[JIterable[String]]
) orFailed null
val reporter = new DiagnosticCollector[JavaFileObject]
// DisassemblerTool.getStandardFileManager(reporter,locale,charset)
val defaultFileManager: JavaFileManager =
(loader.tryToLoadClass[JavaFileManager]("com.sun.tools.javap.JavapFileManager").get getMethod (
"create",
classOf[DiagnosticListener[_]],
classOf[PrintWriter]
) invoke (null, reporter, new PrintWriter(System.err, true))).asInstanceOf[JavaFileManager] orFailed null
// manages named arrays of bytes, which might have failed to load
class JavapFileManager(val managed: Seq[Input])(delegate: JavaFileManager = defaultFileManager)
extends ForwardingJavaFileManager[JavaFileManager](delegate) {
import JavaFileObject.Kind
import Kind._
import StandardLocation._
import JavaFileManager.Location
import java.net.URI
def uri(name: String): URI = new URI(name) // new URI("jfo:" + name)
def inputNamed(name: String): Try[ByteAry] = (managed find (_._1 == name)).get._2
def managedFile(name: String, kind: Kind) = kind match {
case CLASS => fileObjectForInput(name, inputNamed(name), kind)
case _ => null
}
// todo: just wrap it as scala abstractfile and adapt it uniformly
def fileObjectForInput(name: String, bytes: Try[ByteAry], kind: Kind): JavaFileObject =
new SimpleJavaFileObject(uri(name), kind) {
override def openInputStream(): InputStream = new ByteArrayInputStream(bytes.get)
// if non-null, ClassWriter wrongly requires scheme non-null
override def toUri: URI = null
override def getName: String = name
// suppress
override def getLastModified: Long = -1L
}
override def getJavaFileForInput(location: Location, className: String, kind: Kind): JavaFileObject =
location match {
case CLASS_PATH => managedFile(className, kind)
case _ => null
}
override def hasLocation(location: Location): Boolean =
location match {
case CLASS_PATH => true
case _ => false
}
}
val writer = new CharArrayWriter
def fileManager(inputs: Seq[Input]) = new JavapFileManager(inputs)()
def showable(): Showable = {
val written = {
writer.flush()
val w = writer.toString
writer.reset()
w
}
val msgs = {
import Properties.lineSeparator
val m = reporter.messages
if (m.nonEmpty) m mkString ("", lineSeparator, lineSeparator)
else ""
}
new Showable {
def show() = {
val mw = msgs + written
printWriter.write(mw, 0, mw.length) // ReplStrippingWriter clips on write(String) if truncating
printWriter.flush()
}
}
}
// eventually, use the tool interface
def task(options: Seq[String], claases: Seq[String], inputs: Seq[Input]): Task = {
//ServiceLoader.load(classOf[javax.tools.DisassemblerTool]).
//getTask(writer, fileManager, reporter, options.asJava, claases.asJava)
import JavaConverters.asJavaIterableConverter
TaskCtor.newInstance(writer, fileManager(inputs), reporter, options.asJava, claases.asJava)
.orFailed (throw new IllegalStateException)
}
// a result per input
override def apply(options: Seq[String])(inputs: Seq[Input]): List[JpResult] = (inputs map {
case (claas, Success(ba)) =>
if (task(options, Seq(claas), inputs).call()) new JpSuccess(showable())
else new JpError(reporter.messages mkString ",")
case (_, Failure(e)) => new JpError(e.toString)
}).toList orFailed List(noToolError)
}
object JavapTool {
// >= 1.7
val Tool = "com.sun.tools.javap.JavapTask"
// < 1.7
val Env = "sun.tools.javap.JavapEnvironment"
val Printer = "sun.tools.javap.JavapPrinter"
// "documentation"
type FakeEnvironment = AnyRef
type FakePrinter = AnyRef
// support JavapEnvironment
class JpOptions {
private object Access {
final val PRIVATE = 0
final val PROTECTED = 1
final val PACKAGE = 2
final val PUBLIC = 3
}
private val envActionMap: Map[String, (String, Any)] = {
val map = Map(
"-l" -> (("showLineAndLocal", true)),
"-c" -> (("showDisassembled", true)),
"-s" -> (("showInternalSigs", true)),
"-verbose" -> (("showVerbose", true)),
"-private" -> (("showAccess", Access.PRIVATE)),
"-package" -> (("showAccess", Access.PACKAGE)),
"-protected" -> (("showAccess", Access.PROTECTED)),
"-public" -> (("showAccess", Access.PUBLIC)),
"-all" -> (("showallAttr", true))
)
map ++ List(
"-v" -> map("-verbose"),
"-p" -> map("-private")
)
}
def apply(opts: Seq[String]): Seq[(String, Any)] = {
opts flatMap { opt =>
envActionMap get opt match {
case Some(pair) => List(pair)
case _ =>
val charOpts = opt.tail.toSeq map ("-" + _)
if (charOpts forall (envActionMap contains _))
charOpts map envActionMap
else Nil
}
}
}
}
val DefaultOptions = List("-protected", "-verbose")
def isAvailable(cl: ScalaClassLoader = ScalaClassLoader.appLoader) = Seq(Env, Tool) exists (cn => hasClass(cl, cn))
private def hasClass(cl: ScalaClassLoader, cn: String) = cl.tryToInitializeClass[AnyRef](cn).isDefined
private def isTaskable(cl: ScalaClassLoader) = hasClass(cl, Tool)
def apply(cl: ScalaClassLoader, pw: PrintWriter) =
if (isTaskable(cl)) new JavapTool7(cl, pw) else new JavapTool6(cl, pw)
/** A richer [[javax.tools.DiagnosticCollector]]. */
implicit class JavaReporter(val c: DiagnosticCollector[JavaFileObject]) extends AnyVal {
import scala.collection.JavaConverters.iterableAsScalaIterableConverter
/** All diagnostics in the collector. */
def diagnostics: Iterable[Diagnostic[_ <: JavaFileObject]] = c.getDiagnostics.asScala
/** All diagnostic messages.
* @param locale Locale for diagnostic messages, null by default.
*/
def messages(implicit locale: Locale = null) = (diagnostics map (_ getMessage locale)).toList
/*
import Diagnostic.Kind.ERROR
private def isErr(d: Diagnostic[_]) = d.getKind == ERROR
/** Count the errors. */
def errorCount: Int = diagnostics count isErr
/** Error diagnostics in the collector. */
def errors = (diagnostics filter isErr).toList
*/
}
}
object Javap {
def isAvailable(cl: ScalaClassLoader = ScalaClassLoader.appLoader) = JavapTool.isAvailable(cl)
def apply(path: String): Unit = apply(Seq(path))
def apply(args: Seq[String]): Unit = new JavapClass() apply args foreach (_.show())
trait Showable {
def show(): Unit
}
sealed trait JpResult {
type ResultType
def isError: Boolean
def value: ResultType
def show(): Unit
// todo
// def header(): String
// def fields(): List[String]
// def methods(): List[String]
// def signatures(): List[String]
}
class JpError(msg: String) extends JpResult {
type ResultType = String
def isError = true
def value = msg
def show() = println(msg) // makes sense for :javap, less for -Ygen-javap
}
class JpSuccess(val value: Showable) extends JpResult {
type ResultType = AnyRef
def isError = false
def show() = value.show() // output to tool's PrintWriter
}
}
|