Skip to content

Runtime reflection of classes in the empty package broken under Java 9 #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
retronym opened this issue Feb 16, 2017 · 9 comments
Closed
Assignees
Milestone

Comments

@retronym
Copy link
Member

⚡ cat /tmp/Test.java; javac -d /tmp/out /tmp/Test.java && (java_use 9; scala -nobootcp -J-Dscala.ext.dirs=/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/scala-ext)
public class Test {
}

Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 9-ea).
Type in expressions for evaluation. Or try :help.

scala> val cl = new java.net.URLClassLoader(Array(new java.io.File("/tmp/out").toURI.toURL), getClass.getClassLoader); val cls = cl.loadClass("Test"); val instance = cls.getConstructor().newInstance(); val mirror = reflect.runtime.universe.runtimeMirror(cl); val sym = mirror.reflect(instance)(reflect.ClassTag(cls)).symbol
java.lang.AssertionError: assertion failed: no symbol could be loaded from class Test in package with name Test and classloader java.net.URLClassLoader@199806aa
  at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala1(JavaMirrors.scala:1022)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$classToScala$1(JavaMirrors.scala:980)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$toScala$1(JavaMirrors.scala:97)
  at scala.reflect.runtime.TwoWayCaches$TwoWayCache.$anonfun$toScala$1(TwoWayCaches.scala:38)
  at scala.reflect.runtime.TwoWayCaches$TwoWayCache.toScala(TwoWayCaches.scala:34)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.toScala(JavaMirrors.scala:95)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala(JavaMirrors.scala:980)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.classSymbol(JavaMirrors.scala:196)
  at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaInstanceMirror.symbol(JavaMirrors.scala:234)
  at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaInstanceMirror.symbol(JavaMirrors.scala:233)
  ... 29 elided

By contrast, if we place Test in some package, p1:

⚡ cat /tmp/Test.java; javac -d /tmp/out /tmp/Test.java && (java_use 9; scala -nobootcp -J-Dscala.ext.dirs=/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/scala-ext)
package p1;

public class Test {
}

Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 9-ea).
Type in expressions for evaluation. Or try :help.


scala> val cl = new java.net.URLClassLoader(Array(new java.io.File("/tmp/out").toURI.toURL), getClass.getClassLoader); val cls = cl.loadClass("p1.Test"); val instance = cls.getConstructor().newInstance(); val mirror = reflect.runtime.universe.runtimeMirror(cl); val sym = mirror.reflect(instance)(reflect.ClassTag(cls)).symbol
cl: java.net.URLClassLoader = java.net.URLClassLoader@1a8e44fe
cls: Class[_] = class p1.Test
instance: Any = p1.Test@5d8e4fa8
mirror: reflect.runtime.universe.Mirror = JavaMirror with java.net.URLClassLoader@1a8e44fe of type class java.net.URLClassLoader with classpath [file:/tmp/out/] and parent being scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@7b9088f2 of type class scala.tools.nsc.interpreter.IMain$TranslatingClassLoader with classpath [(memory)] and parent being scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@3acc3ee of type class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader with classpath [jrt:/modules/java.activation,jrt:/modules/java.annotations.common,jrt:/modules/java.base,jrt:/modules/java.compact1,jrt:/modules/java.compact2,jrt:/modules...

The same problem exists in a more vanilla classloader setup:

cat /tmp/Test.java; javac -d /tmp/out /tmp/Test.java && (java_use 9; scala -classpath /tmp/out -nobootcp -J-Dscala.ext.dirs=/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/scala-ext)
public class Test {
}

Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 9-ea).
Type in expressions for evaluation. Or try :help.

scala> val test = new Test()
test: Test = Test@6aad919c

scala> val mirror = reflect.runtime.universe.runtimeMirror(test.getClass.getClassLoader)
mirror: reflect.runtime.universe.Mirror = JavaMirror with scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@3b57f915 of type class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader with classpath [jrt:/modules/java.activation,jrt:/modules/java.annotations.common,jrt:/modules/java.base,jrt:/modules/java.compact1,jrt:/modules/java.compact2,jrt:/modules/java.compact3,jrt:/modules/java.compiler,jrt:/modules/java.corba,jrt:/modules/java.datatransfer,jrt:/modules/java.desktop,jrt:/modules/java.httpclient,jrt:/modules/java.instrument,jrt:/modules/java.jnlp,jrt:/modules/java.logging,jrt:/modules/java.management,jrt:/modules/java.naming,jrt:/modules/java.prefs,jrt:/modules/java.rmi,jrt:/modules/java.scripting,jrt:/modules/java.se,jrt:/modules/java.se.ee,jrt:/modules/java.secu...
scala> val sym = mirror.reflect(test).symbol
java.lang.AssertionError: assertion failed: no symbol could be loaded from class Test in package with name Test and classloader scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@3b57f915
  at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala1(JavaMirrors.scala:1022)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$classToScala$1(JavaMirrors.scala:980)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$toScala$1(JavaMirrors.scala:97)
  at scala.reflect.runtime.TwoWayCaches$TwoWayCache.$anonfun$toScala$1(TwoWayCaches.scala:38)
  at scala.reflect.runtime.TwoWayCaches$TwoWayCache.toScala(TwoWayCaches.scala:34)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.toScala(JavaMirrors.scala:95)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala(JavaMirrors.scala:980)
  at scala.reflect.runtime.JavaMirrors$JavaMirror.classSymbol(JavaMirrors.scala:196)
  at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaInstanceMirror.symbol(JavaMirrors.scala:234)
  at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaInstanceMirror.symbol(JavaMirrors.scala:233)
  ... 29 elided

This issue prevents startup of SBT under Java 9 (even after using the patched SBT in sbt/sbt#2951 with the instructions) if the build definitition contains an AutoPlugin with an autoimport module, like this one in scala/scala.

retronym added a commit to retronym/scala that referenced this issue Feb 16, 2017
The underlying bug is tracked as scala/scala-dev#304
and blocks our SBT starting on JDK 9.

This commit avoids using the empty package in our build definition.
@retronym
Copy link
Member Author

The workaround is to move .scala files in the build definition out of the empty package.

retronym added a commit to retronym/scala that referenced this issue Feb 16, 2017
The underlying bug is tracked as scala/scala-dev#304
and blocks our SBT starting on JDK 9.

This commit avoids using the empty package in our build definition.

After this change, I needed to manually clean the class files from the
build definition as follows, which might indicate a bug in SBT.

   $ sbt ...
   /Users/jz/code/scala-java9-ci/build.sbt:0: warning: imported `BuildSettings' is permanently hidden by definition of object BuildSettings
   import ..., _root_.scala.build.BuildSettings, ...
   ^C

   % rm -rf project/target/scala-2.10/sbt-0.13/classes/

   % sbt # okay second time
@dwijnand
Copy link
Member

Generally isn't using the root/empty package there-be-dragons territory? Akin to using dollars in type names?

@dwijnand
Copy link
Member

sbt doesn't help by setting a bad example

@retronym
Copy link
Member Author

retronym commented Jun 1, 2017

REPL free reproduction:

⚡ tail sandbox/test.java sandbox/Test.scala
==> sandbox/test.java <==
public class Test {
    public static void main(String[] args) {
        new p1.ReflectTest().test(new Test());
    }
}

==> sandbox/Test.scala <==
package p1

class ReflectTest {
  def test(a: AnyRef): Unit = {
    val mirror = reflect.runtime.universe.runtimeMirror(a.getClass.getClassLoader)
    println(mirror.reflect(a).symbol)
  }
}

% scalac -d /tmp sandbox/test.scala; javac -cp /tmp -d /tmp sandbox/Test.java 

% (java_use 1.8; java -classpath $HOME'/scala/2.12.1/lib/*:/tmp' Test)
class Test

% (java_use 9; java -classpath $HOME'/scala/2.12.1/lib/*:/tmp' Test)
Exception in thread "main" java.lang.AssertionError: assertion failed: no symbol could be loaded from class Test in package with name Test and classloader jdk.internal.loader.ClassLoaders$AppClassLoader@484b61fc
	at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala1(JavaMirrors.scala:1022)
	at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$classToScala$1(JavaMirrors.scala:980)
	at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$toScala$1(JavaMirrors.scala:97)
	at scala.reflect.runtime.TwoWayCaches$TwoWayCache.$anonfun$toScala$1(TwoWayCaches.scala:38)
	at scala.reflect.runtime.TwoWayCaches$TwoWayCache.toScala(TwoWayCaches.scala:34)
	at scala.reflect.runtime.JavaMirrors$JavaMirror.toScala(JavaMirrors.scala:95)
	at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala(JavaMirrors.scala:980)
	at scala.reflect.runtime.JavaMirrors$JavaMirror.classSymbol(JavaMirrors.scala:196)
	at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaInstanceMirror.symbol(JavaMirrors.scala:234)
	at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaInstanceMirror.symbol(JavaMirrors.scala:233)
	at p1.ReflectTest.test(test.scala:6)
	at Test.main(Test.java:3)

@retronym
Copy link
Member Author

Okay, finally took another look at this. Here's the behaviour difference in Java reflection that we're stumbling into:

⚡ for V in 1.8 9; do (java_use $V; scalac $(f "class C; object C extends App { println(classOf[C].getPackage) }") && scala C ); done
null
package

The null return value in 1.8 might have been a bug. The relevant JDK code has been overhauled with the implementation JPMS. Should be easy enough to adapt on our side, I'm going to try that now.

@SethTisue
Copy link
Member

SethTisue commented Oct 5, 2017

@eed3si9n @dwijnand is there any possibility of an sbt 0.13.17 release that has some kind of workaround for this?

this is already turning into a big problem for the Scala community build on JDK 9 — nearly every project I've tried to add has hit it, because practically any build uses some sbt plugin that has code in the empty package

@eed3si9n
Copy link
Member

eed3si9n commented Oct 5, 2017

I'd be happy to ship 0.13.x if we have some workaround for better JDK 9 compatibility.

@dwijnand
Copy link
Member

dwijnand commented Oct 6, 2017

Will scala/scala#6097 be backported to 2.10 and a release of 2.10 cut with it? Otherwise we can only fix this in sbt 1.

@retronym
Copy link
Member Author

retronym commented Oct 6, 2017

We're considering that in scala/scala#6113

sjrd added a commit to sjrd/scala-js that referenced this issue Oct 10, 2017
We put all our build-level `.scala` files in a `package build`,
rather than in the default package, to work around
scala/scala-dev#304.

This does not yet allow to build the test suite.
sjrd added a commit to sjrd/scala-js that referenced this issue Oct 11, 2017
We put all our build-level `.scala` files in a `package build`,
rather than in the default package, to work around
scala/scala-dev#304.

This does not yet allow to build the test suite.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants