Are outer fields ever accessed from a different compilation unit?

77 views
Skip to first unread message

Sébastien Doeraene

unread,
May 19, 2016, 8:34:21 AM5/19/16
to scala-internals, Nicolas Stucki, Tobias Schlatter
Hello scalac gurus,

tl;dr: Does it ever happen that scalac generates an access (read or write) to an $outer field in a different compilation unit than where it is defined?

Context:

We're in a bit of a situation in Scala.js about our bug #2382. It seems $outer fields are always public and non-mangled when they arrive from scalac, and we've been blindly compiling them as such. But now we have clashes in complex hierarchies.

We do have a fix in PR #2384, but we're not sure whether it is binary compatible. Basically now we're mangling all outer fields with the fully qualified name of their enclosing class. This is binary compatible if and only if outer *fields* (not outer accessor methods) are never accessed from a compilation unit different than the one defining that field.

If that's true, our fix is fine and we can merge it now. If that isn't the case, either we have to find another binary compatible fix, or delay the fix to the next major version of Scala.js (i.e., 1.0.0).

Can anyone provide an authoritative answer on the topic?

Cheers,
Sébastien

PS: for those wondering what "binary compatible" could possibly mean for Scala.js, it's about the compatibility of .sjsir files coming from separate compiler runs. Basically our .sjsir files are the analogue of .class files for Scala/JVM.

Adriaan Moors

unread,
May 23, 2016, 7:26:52 PM5/23/16
to scala-i...@googlegroups.com
I can't think of any inter-compilation unit accesses to the field. The pattern matcher goes through the accessor for outer checks, so I think you're fine.

--
You received this message because you are subscribed to the Google Groups "scala-internals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-interna...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Sébastien Doeraene

unread,
May 25, 2016, 6:03:33 AM5/25/16
to scala-internals
Hello,

Thanks!
And apparently we were able to compile (modified sources of) scala-reflect.jar and scala-compiler.jar with this fix enabled, and link them against scala-library.jar and scalajs-library.jar compiled without the fix. That's also good evidence that it's indeed compatible.

Cheers,
Sébastien

Matthew Pocock

unread,
May 25, 2016, 6:17:46 AM5/25/16
to scala-i...@googlegroups.com
Hi Sébastien,

I'm struggling to think of a way that $outer could leak at the scala language level. It isn't syntactically accessible outside of the inner class. How about this?

cu1:

class A {
  class B { ... reference to $outer ... }
}

cu2:

class AA extends A {
  class BB extends B { ... reference to $outer ... }
}

So in that case, does the $outer in BB re-use the $outer in B and simply cast to AA, or is it a new $outer field? This is the only way that I can think of $outer  leaking across compilation units.

M
Dr Matthew Pocock
Turing ate my hamster LTD

Integrative Bioinformatics Group, School of Computing Science, Newcastle University

skype: matthew.pocock
tel: (0191) 2566550

Sébastien Doeraene

unread,
May 25, 2016, 6:27:50 AM5/25/16
to scala-internals
Well ... That simple test case seems to show that BB actually reuses B's $outer *field*. I though it reused B's $outer *accessor*, but no, it's the field.
So that seems to break binary compatibility, indeed.

Ah! As much as I hate the answer, thank you Matthew for bringing up this example! We could have released a seriously broken release without you!

Sébastien

Matthew Pocock

unread,
May 25, 2016, 12:08:13 PM5/25/16
to scala-i...@googlegroups.com
I think this falls into the category "saddened but not surprised" :) The obvious fix would be for $outer to be accessed through a synthetic, protected accessor. But that's a change that would have to be made in the scala compiler side I'm guessing, unless you monkey-patch that in the first layer of sjs classfile mangling? It's a code smell in any case.

M

Sébastien Doeraene

unread,
May 25, 2016, 12:58:59 PM5/25/16
to scala-internals
Well, it's too late for that anyway. We've got to deal with the hundreds of published artifacts that were compiled with the previous version.

Now I'm trying to come up with a link-time patch, to serve until we break binary compat. I've got something in
https://github.com/scala-js/scala-js/compare/master...sjrd:fix-outer-at-link-time
which should work for all Scala classes. It doesn't work for Scala.js-defined JS classes, though, because they all have the same IR type `any`.

Sébastien

Adriaan Moors

unread,
May 25, 2016, 3:19:48 PM5/25/16
to scala-i...@googlegroups.com
Wow, that sucks (I suppose it was originally done for performance, but I wonder whether modern JVMs care about this indirection). 

I made the same assumption... Matthew's counter-example did trigger an unhappy memory of another way we can go directly to the outer field:


$ scalac -Xprint:expl /Users/adriaan/Library/Preferences/IdeaIC2016.1/scratches/scratch_8 -uniqid
package <empty>#4 {
  class A#6619 extends Object#168 {
    def <init>#6774(): A#6619 = {
      A#6619.super.<init>#2315();
      ()
    };
    final class B#6775 extends Object#168 {
      def <init>#14049($outer#15956: A#6619.this.type): A#6619.this.B#6775 = {
        B#6775.super.<init>#2315();
        ()
      };
      scala#24.Predef#1340.println#6404(B#6775.this.$outer#15955); // normally uses the outer accessor, unless the class is final
      <synthetic> <paramaccessor> <artifact> private[this] val $outer#15955: A#6619.this.type = _;
      <synthetic> <stable> <artifact> def $outer#15954(): A#6619.this.type = B#6775.this.$outer#15955
    }
  }
}
Reply all
Reply to author
Forward
0 new messages