-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Mixin fields with trait setters shouldn't be JVM final #7028
Conversation
74122e1
to
d8c0dad
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few questions, mostly because I still haven't fully internalized the memory model :-)
mkAccessor(setter)(Assign(Select(This(clazz), fieldSel), castHack(Ident(setter.firstParam), fieldSel.info))) | ||
} | ||
case fieldSel => | ||
if (!fieldSel.hasFlag(MUTABLE)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use a comment: If the field is mutable, it won't be final, so we can write to it in a setter. If it's not, we still need to initialize it, and make sure it's safely published. Since initialization is performed (lexically) outside of the constructor (in the trait setter), we have to make the field mutable starting with classfile format 53 (it was never allowed, but the verifier enforces this now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated with this comment
@@ -560,6 +567,7 @@ sealed abstract class List[+A] | |||
|
|||
final case class :: [+A](override val head: A, private[scala] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally | |||
extends List[A] { | |||
ScalaRunTime.releaseFence() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a comment to the class that every time next
is mutated and the resulting ::
instance is published, you should first call releaseFence
. Maybe, maybe, we can actually add a method publishNext
or something to make this more clear.
@@ -215,6 +216,7 @@ final class Vector[+A] private[immutable] (private[collection] val startIndex: I | |||
s.dirty = dirty | |||
s.gotoPosWritable(focus, idx, focus ^ idx) // if dirty commit changes; go to new pos and prepare for writing | |||
s.display0(idx & 31) = elem.asInstanceOf[AnyRef] | |||
ScalaRunTime.releaseFence() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this to publish the mutation of dirty
? Should we also do this in the constructor?
@@ -61,6 +62,7 @@ class ListBuffer[A] | |||
// Avoids copying where possible. | |||
override def toList: List[A] = { | |||
aliased = nonEmpty | |||
ScalaRunTime.releaseFence() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why no fence for the other methods that mutate? I see they're private, but they are called from public methods, I assume?
@retronym should we merge this for M5, or hold it for RC1? |
d8c0dad
to
cebfe1a
Compare
Before: ``` scala> trait T { val foo = 24 }; class C extends T defined trait T defined class C scala> :javap -private -c C Compiled from "<console>" public class $line3.$read$$iw$$iw$C implements $line3.$read$$iw$$iw$T { private final int foo; public int foo(); Code: 0: aload_0 1: getfield #21 // Field foo:I 4: ireturn public void $line3$$read$$iw$$iw$T$_setter_$foo_$eq(int); Code: 0: aload_0 1: iload_1 2: putfield #21 // Field foo:I 5: return public $line3.$read$$iw$$iw$C(); Code: 0: aload_0 1: invokespecial #30 // Method java/lang/Object."<init>":()V 4: aload_0 5: invokestatic #34 // InterfaceMethod $line3/$read$$iw$$iw$T.$init$:(L$line3/$read$$iw$$iw$T;)V 8: return } ``` The assignment to the final field `foo` has always contravened the JVM spec, and this rule is enforced for any classfiles of format 53 and higher. After this patch: ``` scala> trait T { val foo = 24 }; class C extends T defined trait T defined class C scala> :javap -private -c C Compiled from "<console>" public class $line3.$read$$iw$$iw$C implements $line3.$read$$iw$$iw$T { private int foo; public int foo(); Code: 0: aload_0 1: getfield #21 // Field foo:I 4: ireturn public void $line3$$read$$iw$$iw$T$_setter_$foo_$eq(int); Code: 0: aload_0 1: iload_1 2: putfield #21 // Field foo:I 5: return public $line3.$read$$iw$$iw$C(); Code: 0: aload_0 1: invokespecial #30 // Method java/lang/Object."<init>":()V 4: aload_0 5: invokestatic #34 // InterfaceMethod $line3/$read$$iw$$iw$T.$init$:(L$line3/$read$$iw$$iw$T;)V 8: getstatic #40 // Field scala/runtime/ScalaRunTime$.MODULE$:Lscala/runtime/ScalaRunTime$; 11: invokevirtual #43 // Method scala/runtime/ScalaRunTime$.releaseFence:()V 14: return } ```
cebfe1a
to
f9ecece
Compare
Rebased, this still shares the preliminary commits with #6425. I think we should get this into M5. |
This is a forward port of relevant parts of the upstream PR scala/scala#7028
This is a forward port of relevant parts of the upstream PR scala/scala#7028
This is a forward port of relevant parts of the upstream PR scala/scala#7028
Following on from #6425, also a release fence to the constructor of classes that mixin traits with
val
fields, and changed the backing JVM field to be non-final. This lets us comply with the JVM spec ("assignment to a final field must lexically scoped in the class constructor"), which is enforced under Classfile format 53Fixes scala/scala-dev#408
Before:
The assignment to the final field
foo
has always contravened the JVMspec, and this rule is enforced for any classfiles of format 53 and higher.
After this patch: