This page is no longer maintained — Please continue to the home page at www.scala-lang.org

traits, binary compatibility, and the protection racket

2 replies
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Consider the perversion taking place in the following tale.
    trait A {      private[this] val bippy = 5      def dingus = bippy * bippy    }    class B extends A
trait A has a single public method and a private[this] immutable fieldwhich does not escape and which the language prohibits anything outsideof A from accessing. Nevertheless, we generate not only a public getter, but a public setter, thus making the name of the private[this] field acentral piece of A's binary compatibility story. May as well be a publicvar for all the insulation A has from its implementation.
Now class B comes along and extends A. It receives the private field andthe public getter and setter. To populate the field, B's constructorcalls into A$class, which turns around and calls the public setter on B, the need for which is now apparent. To implement the one interfacemethod which A intentionally exposed, we again send B packing to A$classto perform the actual operation. Having left the warm confines of B where we could have accessed the field directly, we must resort to usingthe public getter which also is trying to justify its existence.
I understand why this machinery is in place and that (at least given the current encoding) it's necessary to support access which is scala-legalbut jvm-illegal. But I can't see why it can't be done more selectively,with the especially interesting case being the private field which never hurt a fly. Separate compilation can hardly be banking on the presenceof private fields which can't be separately accessed.
Optimally:   interface A {     def dingus(): Int  }  class B extends A {    private int bippy = 5    def dingus = bippy * bippy  }
In actual fact:
  interface A {    def dingus(): Int    def A$$bippy(): Int    def A$_setter_$A$$bippy_$eq(x: Int): Unit  }  class B extends A {    private int A$$bippy = 5     def A$$bippy(): Int = A$$bippy    def A$_setter_$A$$bippy_$eq(x: Int): Unit = A$$bippy = x    def dingus(): Int = A$class.dingus(this)  }  class A$class {    def dingus(x: A) = x.A$$bippy() * x.A$$bippy()   }
Bytecode:
// interface Apublic abstract int A$$bippy();public abstract void A$_setter_$A$$bippy_$eq(int);public abstract int dingus();
// class Bprivate final int A$$bippy;
public int A$$bippy();   0: aload_0   1: getfield #11; //Field A$$bippy:I    4: ireturn
public void A$_setter_$A$$bippy_$eq(int);   0: aload_0    1: iload_1   2: putfield #11; //Field A$$bippy:I    5: return
public int dingus();   0: aload_0   1: invokestatic #19; //Method A$class.dingus:(LA;)I    4: ireturn
// Implementation class A$class:public static int dingus(A);   0: aload_0    1: invokeinterface #12,  1; //InterfaceMethod A.A$$bippy:()I   6: aload_0    7: invokeinterface #12,  1; //InterfaceMethod A.A$$bippy:()I   12: imul    13: ireturn
public static void $init$(A);   0: aload_0    1: iconst_5   2: invokeinterface #26,  2; //InterfaceMethod A.A$_setter_$A$$bippy_$eq:(I)V    7: return
geoff
Joined: 2008-08-20,
User offline. Last seen 1 year 25 weeks ago.
Re: traits, binary compatibility, and the protection racket

On Wed, Dec 21, 2011 at 10:34:38PM -0800, Paul Phillips said
> I understand why this machinery is in place and that (at least given the
> current encoding) it's necessary to support access which is scala-legal
> but jvm-illegal. But I can't see why it can't be done more selectively,
> with the especially interesting case being the private field which never
> hurt a fly. Separate compilation can hardly be banking on the presence
> of private fields which can't be separately accessed.

I think it can make a difference. Say A was in some library xyz that has
two different versions

Version 1:

trait A {
private[this] val bippy = 5
def dingus = bippy * bippy
}

Version 2:

trait A {
private[this] val bippy = 10
def dingus = bippy * bippy
}

and B was in some app using xyz

and we also had

object C {
def main(args: Array[String]) { println(new B.dingus) }
}

As it stands now, with no recompilation of the app inbetween

% scala -cp xyz-1.jar:app.jar C
25

and

% scala -cp xyz-2.jar:app.jar C
100

> Optimally:
>
> interface A {
> def dingus(): Int
> }
> class B extends A {
> private int bippy = 5
> def dingus = bippy * bippy
> }

Then we would have, with no recompilation of the app inbetween

% scala -cp xyz-1.jar:app.jar C
25

and

% scala -cp xyz-2.jar:app.jar C
25

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: traits, binary compatibility, and the protection racket


On Thu, Dec 22, 2011 at 10:35 AM, Geoff Reedy <geoff@programmer-monk.net> wrote:
I think it can make a difference.

Oh, I didn't say it couldn't make a difference, I said that separate compilation shouldn't be banking on it.  I think there are all kinds of hijinx you can bring out with selective recompilation and private members.
I think anything where different semantics arise for A based on implementation details of the trait encoding should be a guarantee-free zone.

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland