diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2016-08-08 11:19:34 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2016-08-18 10:52:02 +1000 |
commit | bb882e7249d4b9aa0b0c92ec9058d58464f6ed7a (patch) | |
tree | e632ee5a1436e93471802229c59fd341e698ca57 /src/compiler/scala/tools/nsc/backend/jvm | |
parent | 804133f60dd3c78909dc9e557e91b5c9923240ff (diff) | |
download | scala-bb882e7249d4b9aa0b0c92ec9058d58464f6ed7a.tar.gz scala-bb882e7249d4b9aa0b0c92ec9058d58464f6ed7a.tar.bz2 scala-bb882e7249d4b9aa0b0c92ec9058d58464f6ed7a.zip |
SD-194 Tweak module initialization to comply with JVM spec
Top level modules in Scala currently desugar as:
```
class C; object O extends C { toString }
```
```
public final class O$ extends C {
public static final O$ MODULE$;
public static {};
Code:
0: new #2 // class O$
3: invokespecial #12 // Method "<init>":()V
6: return
private O$();
Code:
0: aload_0
1: invokespecial #13 // Method C."<init>":()V
4: aload_0
5: putstatic #15 // Field MODULE$:LO$;
8: aload_0
9: invokevirtual #21 // Method java/lang/Object.toString:()Ljava/lang/String;
12: pop
13: return
}
```
The static initalizer `<clinit>` calls the constructor `<init>`, which
invokes superclass constructor, assigns `MODULE$= this`, and then runs
the remainder of the object's constructor (`toString` in the example
above.)
It turns out that this relies on a bug in the JVM's verifier: assignment to a
static final must occur lexically within the <clinit>, not from within `<init>`
(even if the latter is happens to be called by the former).
I'd like to move the assignment to <clinit> but that would
change behaviour of "benign" cyclic references between modules.
Example:
```
package p1; class CC { def foo = O.bar}; object O {new CC().foo; def bar = println(1)};
// Exiting paste mode, now interpreting.
scala> p1.O
1
```
This relies on the way that we assign MODULE$ field after the super class constructors
are finished, but before the rest of the module constructor is called.
Instead, this commit removes the ACC_FINAL bit from the field. It actually wasn't
behaving as final at all, precisely the issue that the stricter verifier
now alerts us to.
```
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)
package p1; object O
// Exiting paste mode, now interpreting.
scala> val O1 = p1.O
O1: p1.O.type = p1.O$@ee7d9f1
scala> scala.reflect.ensureAccessible(p1.O.getClass.getDeclaredConstructor()).newInstance()
res0: p1.O.type = p1.O$@64cee07
scala> O1 eq p1.O
res1: Boolean = false
```
We will still achieve safe publication of the assignment to other threads
by virtue of the fact that `<clinit>` is executed within the scope of
an initlization lock, as specified by:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5
Fixes scala/scala-dev#SD-194
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 9 |
1 files changed, 8 insertions, 1 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index d4d532f4df..0dd6058f3e 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -181,8 +181,15 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { * can-multi-thread */ private def addModuleInstanceField() { + // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + // SD-194 This can't be FINAL on JVM 1.9+ because we assign it from within the + // instance constructor, not from <clinit> directly. Assignment from <clinit>, + // after the constructor has completely finished, seems like the principled + // thing to do, but it would change behaviour when "benign" cyclic references + // between modules exist. + val mods = GenBCode.PublicStatic val fv = - cnode.visitField(GenBCode.PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED + cnode.visitField(mods, strMODULE_INSTANCE_FIELD, thisBType.descriptor, null, // no java-generic-signature |