bug
bug copied to clipboard
REPL :javap disallowed by module system on JDK 16
Welcome to Scala 2.13.5 (OpenJDK 64-Bit Server VM, Java 16).
Type in expressions for evaluation. Or try :help.
scala> :javap scala.Some
java.lang.IllegalAccessException: class scala.tools.nsc.interpreter.shell.JavapTask cannot access class com.sun.tools.javap.JavapFileManager (in module jdk.jdeps) because module jdk.jdeps does not export com.sun.tools.javap to unnamed module @324e4822
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:385)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:687)
at java.base/java.lang.reflect.Method.invoke(Method.java:559)
at scala.tools.nsc.interpreter.shell.JavapTask.<init>(JavapClass.scala:498)
From what I understand, the complication is that we need to make javap read a class from the repl's in-memory representation / class loader.
It seems the only accessible API is ToolProvider
which only has a run
method. I assume we cannot make it search classes in a custom class loader through this.
Personally, I'd be fine throwing out :javap
in favor of :asmp
which would be easy to implement
scala> :power
Power mode enabled. :phase is at typer.
import scala.tools.nsc._, intp.global._, definitions._
Try :help or completions for vals._ and power._
scala> class HalloVelo
class HalloVelo
scala> scala.tools.nsc.backend.jvm.AsmUtils.traceClass(intp.classLoader.classBytes("HalloVelo"))
Bytecode for class $line31/$read$$iw$HalloVelo
// class version 52.0 (52)
// access flags 0x21
public class $line31/$read$$iw$HalloVelo {
// compiled from: <console>
ATTRIBUTE Scala : unknown
ATTRIBUTE ScalaInlineInfo : unknown
// access flags 0x1
public INNERCLASS $line31/$read$$iw $line31/$read $iw
// access flags 0x1
public INNERCLASS $line31/$read$$iw$HalloVelo $line31/$read$$iw HalloVelo
// access flags 0x1011
public final synthetic L$line31/$read$$iw; $outer
// access flags 0x1001
public synthetic $line31$$read$$iw$HalloVelo$$$outer()L$line31/$read$$iw;
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD $line31/$read$$iw$HalloVelo.$outer : L$line31/$read$$iw;
ARETURN
L1
LOCALVARIABLE this L$line31/$read$$iw$HalloVelo; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public <init>(L$line31/$read$$iw;)V
// parameter final synthetic $outer
L0
LINENUMBER 1 L0
ALOAD 1
IFNONNULL L1
ACONST_NULL
ATHROW
L1
FRAME SAME
ALOAD 0
ALOAD 1
PUTFIELD $line31/$read$$iw$HalloVelo.$outer : L$line31/$read$$iw;
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L2
LOCALVARIABLE this L$line31/$read$$iw$HalloVelo; L0 L2 0
LOCALVARIABLE $outer L$line31/$read$$iw; L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
}
Agreed! Wanted this a long time.
I guess Scala 2 doesn't make use of the Java module system. For inside thoughts, see these articles :
- Java 9+ modularity: The difficulties and pitfalls of migrating from Java 8 to Java 9+ (part 5 of a series on Java modularity) by Mohamed Taman (IBM developer).
- Five Command Line Options To Hack The Java Module System by Nicolai Parlog.
:asmp
sounds like it would be also be easier to port over to Scala 3 than :javap
.
Personally, I'd be fine throwing out
:javap
in favor of:asmp
which would be easy to implement
It seems that asm does not rely on JDK classes, So our solution to this problem is to give up javap
and add asmp
command?
or both. scala 2 repl has a classloader issue that requires -nobootcp, so there may be the same issue with scala 3. I like the idea of having many tools in the tool belt.
I will refer to dotty to implement asmp for scala2. Any interested volunteers can pick it up before I submit pr.
For reference until we have a lasting answer for the Scala 2 REPL, @retronym pointed out the escape hatch for the module access on a previous tromp down this lane, still an applicable workaround in JDK 17: https://github.com/scala/scala/pull/8400#issuecomment-530211100
$ scala -nobootcp -J--add-exports=jdk.jdeps/com.sun.tools.javap=ALL-UNNAMED
which could be set in JAVA_OPTS
more persistently.
here's the Scala-CLI form of the workaround:
scala-cli repl -S 2 -J --add-exports=jdk.jdeps/com.sun.tools.javap=ALL-UNNAMED
(-nobootcp
doesn't seem to be necessary? :shrug:)
The "workaround" for -nobootcp
mentioned in the error message:
scala -Dscala.repl.info -Xsource:3 -J--add-exports -Jjdk.jdeps/com.sun.tools.javap=ALL-UNNAMED -Yrepl-outdir myreplout -toolcp myreplout
in other words
➜ snips scala -Dscala.repl.info -Xsource:3 -J--add-exports -Jjdk.jdeps/com.sun.tools.javap=ALL-UNNAMED
[info] started at Sat Sep 16 10:39:25 PDT 2023
Welcome to Scala 2.13.12 (OpenJDK 64-Bit Server VM, Java 20.0.2).
Type in expressions for evaluation. Or try :help.
scala 2.13.12> class C
class C
scala 2.13.12> :javap C
On JDK 9 or higher, use -nobootcp to enable :javap, or set -Yrepl-outdir to a file system path on the tool class path with -toolcp.
scala 2.13.12>
:quit
➜ snips scala -Dscala.repl.info -Xsource:3 -J--add-exports -Jjdk.jdeps/com.sun.tools.javap=ALL-UNNAMED -Yrepl-outdir myreplout -toolcp myreplout
[info] started at Sat Sep 16 10:39:50 PDT 2023
Welcome to Scala 2.13.12 (OpenJDK 64-Bit Server VM, Java 20.0.2).
Type in expressions for evaluation. Or try :help.
scala 2.13.12> class C
class C
scala 2.13.12> :javap -public C
Compiled from "<console>"
public class C {
public final $iw $outer;
public $iw C$$$outer();
public C($iw);
}
scala 2.13.12>
where myreplout
is a real directory mentioned by both options.
I don't know if there is a use case where it's not feasible to use -nobootcp
, such that this mode is necessary.
I see that :paste -raw
class output goes directly under -Yrepl-outdir
. But I also see :paste -java
output is only virtual.