diff options
author | Som Snytt <som.snytt@gmail.com> | 2013-07-15 17:51:44 -0700 |
---|---|---|
committer | Som Snytt <som.snytt@gmail.com> | 2013-07-15 22:37:41 -0700 |
commit | 1010a324f20dfa1221f506d67700d748e6ed79d4 (patch) | |
tree | e04d13aa2a3a9de997f0c7045cd81bd046500ba9 /src | |
parent | 11dcf82910b388046e675d76d277b09b931d5363 (diff) | |
download | scala-1010a324f20dfa1221f506d67700d748e6ed79d4.tar.gz scala-1010a324f20dfa1221f506d67700d748e6ed79d4.tar.bz2 scala-1010a324f20dfa1221f506d67700d748e6ed79d4.zip |
SI-7488 REPL javap finds new style delayedEndpoint
The REPL :java -app command is a convenience to locate
the body of DelayedInit code. Now it will look for
new style delayedEndpoints on the class before it
falls back to showing the apply method of the
delayedInit$body closure.
```
apm@mara:~/tmp$ skala
Welcome to Scala version 2.11.0-20130711-153246-eb1c3137f5 (OpenJDK 64-Bit Server VM, Java 1.7.0_21).
Type in expressions to have them evaluated.
Type :help for more information.
scala> :javap -pv -app delayed.C
public final void delayedEndpoint$delayed$C$1();
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
0: getstatic #29 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: ldc #31 // String this is the initialization code of C
5: invokevirtual #35 // Method scala/Predef$.println:(Ljava/lang/Object;)V
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Ldelayed/C;
LineNumberTable:
line 11: 0
scala> :q
apm@mara:~/tmp$ rm delayed/*.class
apm@mara:~/tmp$ scalac delayed.scala
apm@mara:~/tmp$ skala
Welcome to Scala version 2.11.0-20130711-153246-eb1c3137f5 (OpenJDK 64-Bit Server VM, Java 1.7.0_21).
Type in expressions to have them evaluated.
Type :help for more information.
scala> :javap -pv -app delayed.C
public final java.lang.Object apply();
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
0: getstatic #13 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: ldc #15 // String this is the initialization code of C
5: invokevirtual #19 // Method scala/Predef$.println:(Ljava/lang/Object;)V
8: getstatic #25 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
11: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 this Ldelayed/C$delayedInit$body;
LineNumberTable:
line 11: 0
line 10: 8
```
Diffstat (limited to 'src')
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/JavapClass.scala | 82 |
1 files changed, 64 insertions, 18 deletions
diff --git a/src/repl/scala/tools/nsc/interpreter/JavapClass.scala b/src/repl/scala/tools/nsc/interpreter/JavapClass.scala index ef6f4c2920..49bdd69a8b 100644 --- a/src/repl/scala/tools/nsc/interpreter/JavapClass.scala +++ b/src/repl/scala/tools/nsc/interpreter/JavapClass.scala @@ -44,33 +44,78 @@ class JavapClass( val (flags, upgraded) = upgrade(options) import flags.{ app, fun, help, raw } val targets = if (fun && !help) FunFinder(loader, intp).funs(claases) else claases - if (help || claases.isEmpty) List(JpResult(JavapTool.helper(printWriter))) - else if (targets.isEmpty) List(JpResult("No anonfuns found.")) - else tool(raw, upgraded)(targets map (claas => claas -> bytesFor(claas, app))) + if (help || claases.isEmpty) + List(JpResult(JavapTool.helper(printWriter))) + else if (targets.isEmpty) + List(JpResult("No anonfuns found.")) + else + tool(raw, upgraded)(targets map (claas => targeted(claas, app))) } /** Cull our tool options. */ - private def upgrade(options: Seq[String]): (ToolArgs, Seq[String]) = ToolArgs fromArgs options match { - case (t,s) if s.nonEmpty => (t,s) - case (t,s) => (t, JavapTool.DefaultOptions) - } + private def upgrade(options: Seq[String]): (ToolArgs, Seq[String]) = + ToolArgs fromArgs options match { + case (t, s) if s.nonEmpty => (t, s) + case (t, s) => (t, JavapTool.DefaultOptions) + } + + /** Associate the requested path with a possibly failed or empty array of bytes. */ + private def targeted(path: String, app: Boolean): (String, Try[Array[Byte]]) = + bytesFor(path, app) match { + case Success((target, bytes)) => (target, Try(bytes)) + case f: Failure[_] => (path, Failure(f.exception)) + } /** Find bytes. Handle "-", "-app", "Foo#bar" (by ignoring member), "#bar" (by taking "bar"). */ private def bytesFor(path: String, app: Boolean) = Try { def last = intp.get.mostRecentVar // fail if no intp - def req = if (path == "-") last else { - val s = path.splitHashMember - if (s._1.nonEmpty) s._1 - else s._2 getOrElse "#" + def req = path match { + case "-" => last + case HashSplit(prefix, member) => + if (prefix != null) prefix + else if (member != null) member + else "#" + } + val targetedBytes = if (app) findAppBody(req) else (req, findBytes(req)) + if (targetedBytes._2.isEmpty) throw new FileNotFoundException(s"Could not find class bytes for '$path'") + targetedBytes + } + + private def findAppBody(path: String): (String, Array[Byte]) = { + // is this new style delayedEndpoint? then find it. + // the name test is naive. could add $mangled path. + // assumes only the first match is of interest (because only one endpoint is generated). + def findNewStyle(bytes: Array[Byte]) = { + import scala.tools.asm.ClassReader + import scala.tools.asm.tree.ClassNode + import PartialFunction.cond + import JavaConverters._ + val rdr = new ClassReader(bytes) + val nod = new ClassNode + rdr.accept(nod, 0) + //foo/Bar.delayedEndpoint$foo$Bar$1 + val endpoint = "delayedEndpoint".r.unanchored + def isEndPoint(s: String) = (s contains '$') && cond(s) { case endpoint() => true } + nod.methods.asScala collectFirst { case m if isEndPoint(m.name) => m.name } } - def asAppBody(s: String) = { - val (cls, fix) = s.splitSuffix - s"${cls}$$delayedInit$$body${fix}" + // try new style, and add foo#delayedEndpoint$bar$1 to filter on the endpoint + def asNewStyle(bytes: Array[Byte]) = Some(bytes) filter (_.nonEmpty) flatMap { bs => + findNewStyle(bs) map (n => (s"$path#$n", bs)) } - def todo = if (app) asAppBody(req) else req - val bytes = findBytes(todo) - if (bytes.isEmpty) throw new FileNotFoundException(s"Could not find class bytes for '${path}'") - else bytes + // use old style, and add foo# to filter on apply method + def asOldStyle = { + def asAppBody(s: String) = { + val (cls, fix) = s.splitSuffix + s"${cls}$$delayedInit$$body${fix}" + } + val oldStyle = asAppBody(path) + val oldBytes = findBytes(oldStyle) + if (oldBytes.nonEmpty) (s"$oldStyle#", oldBytes) + else (path, oldBytes) + } + + val pathBytes = findBytes(path) + asNewStyle(pathBytes) getOrElse asOldStyle } def findBytes(path: String): Array[Byte] = tryFile(path) getOrElse tryClass(path) @@ -496,6 +541,7 @@ object JavapClass { intp: Option[IMain] = None ) = new JavapClass(loader, printWriter, intp) + val HashSplit = "(.*?)(?:#([^#]*))?".r // We enjoy flexibility in specifying either a fully-qualified class name com.acme.Widget // or a resource path com/acme/Widget.class; but not widget.out implicit class MaybeClassLike(val s: String) extends AnyVal { |