Skip to content

Commit

Permalink
SI-7488 REPL javap finds new style delayedEndpoint
Browse files Browse the repository at this point in the history
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     scala#29                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
         3: ldc           scala#31                 // String this is the initialization code of C
         5: invokevirtual scala#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     scala#13                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
         3: ldc           scala#15                 // String this is the initialization code of C
         5: invokevirtual scala#19                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
         8: getstatic     scala#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
```
  • Loading branch information
som-snytt committed Jul 16, 2013
1 parent 11dcf82 commit 1010a32
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 18 deletions.
82 changes: 64 additions & 18 deletions src/repl/scala/tools/nsc/interpreter/JavapClass.scala
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
39 changes: 39 additions & 0 deletions test/files/run/repl-javap-app.check
@@ -0,0 +1,39 @@
#partest java6
Type in expressions to have them evaluated.
Type :help for more information.

scala> :javap -app MyApp$
public final void delayedEndpoint$MyApp$1();
Code:
Stack=2, Locals=1, Args_size=1
0: getstatic #61; //Field scala/Console$.MODULE$:Lscala/Console$;
3: ldc #63; //String Hello, delayed world.
5: invokevirtual #67; //Method scala/Console$.println:(Ljava/lang/Object;)V
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LMyApp$;
}

scala>
#partest !java6
Type in expressions to have them evaluated.
Type :help for more information.

scala> :javap -app MyApp$
public final void delayedEndpoint$MyApp$1();
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
0: getstatic #61 // Field scala/Console$.MODULE$:Lscala/Console$;
3: ldc #63 // String Hello, delayed world.
5: invokevirtual #67 // Method scala/Console$.println:(Ljava/lang/Object;)V
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LMyApp$;
LineNumberTable:
line 5: 0
}

scala>
10 changes: 10 additions & 0 deletions test/files/run/repl-javap-app.scala
@@ -0,0 +1,10 @@

import scala.tools.partest.ReplTest

object MyApp extends App {
Console println "Hello, delayed world."
}

object Test extends ReplTest {
def code = ":javap -app MyApp$"
}

0 comments on commit 1010a32

Please sign in to comment.