-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #4430: Disallow access to Java bridge methods before erasure #4639
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
Conversation
tests/run/i4430.scala
Outdated
@@ -0,0 +1,5 @@ | |||
object Test { | |||
def main(args: Array[String]): Unit = { | |||
(new java.lang.StringBuilder()).append(Array[Char](), 0, 0) |
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 should be a DottyBytecodeTests that checks the call signature.
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.
Can somebody else do the bytecode test? It's one of the few things I don't want to get into.
@retronym do you know how scalac deals with Java bridges? |
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.
Makes perfect sense to me (apart from @smarter’s comment of course).
Turns out, |
Scala bridge methods only come to existence during erasure, so are not visible before. But Java bridge methods come with their class files. It first I thought we should get parity, so have to ignore Java bridge methods before erasure. But that did not work, because unlike for Scala, some Java bridges are generated to allow access to inherited methods. The situation can be described using StringBuilder. It inherits a class AbstractStringBuilder which is not accessible from Scala. Here's a minimized version highlighting the problem: private class AbstractStringBuilder { def length: Int = ... def append(xs: Array[Char]): AbstractStringBuilder = ... } class StringBuilder extends AbstractStringBuilder { override def append(xs: Array[Char]): StringBuilder = ... <bridge> def length: Int = super.length <bridge> def append(xs: Array[Char]): AbstractStringBuilder = this.append(xs) } User code val sb = new StringBuilder sb.length // needs to access bridge in StringBuilder sb.append(arr) // should not access bridge in StringBuilder In the first case, `sb.length` needs to access the bridge method, as the inherited method is inaccssible. So we cannot simply hide bridge methods before erasure, as we do for Scala bridges. But in the second case, we should not access the bridge append, because it refers an inaccessible class in its result type. The fix is simple, but it took me a long time finding it: When merging denotations with the same signature (which is the case for the two appends, since results are ignored), always prefer a non-bridge over a bridge as the symbol of a JointRefDenotation.
Still wrong. This is a lot trickier than I thought. Turns out some Java bridges need to be accessible from the start. |
Was turned on for debugging, accidentally made it into commit.
I think something else is going on here. If I run the added test case with -Xprint-types -Xprint:all, the type of append is initially correct (the result type is StringBuilder), but after Erasure this changes (the result type is now AbstractStringBuilder), so it looks like Erasure is redoing overloading resolution somehow and getting it wrong. |
@allanrenucci Can I ask you to add the bytecode test? Thanks! |
@smarter I believe it is |
By default,
|
That's what I tried as well, but then |
The backend uses the qualifier type as the
There are other corner cases handled in erasure: @lrytz was the last to work on these, adding some documentation in the code in https://github.com/scala/scala/pull/6023/files |
Ah, that would explain it. We have to check in the backend to see whether it does the right thing and to fix it if not. |
I believe the change in this PR is harmless: when in doubt it is better to select a non-bridge over a bridge. Ideally, the backend would compensate. But is somebody ready to do this right now? Or should we wait for the merged backend. In the latter case I propose to merge this PR, so that we have one important issue less to deal with. |
Scala bridge methods only come to existence during erasure, so are not visible
before. But Java bridge methods come with their class files. It first I thought
we should get parity, so have to ignore Java bridge methods before erasure. But
that did not work, because unlike for Scala, some Java bridges are generated
to allow access to inherited methods. The situation can be described using
StringBuilder. It inherits a class AbstractStringBuilder which is not accessible
from Scala. Here's a minimized version highlighting the problem:
User code
In the first case,
sb.length
needs to access the bridge method, asthe inherited method is inaccssible. So we cannot simply hide bridge
methods before erasure, as we do for Scala bridges. But in the second
case, we should not access the bridge append, because it refers to an
inaccessible class in its result type.
The fix is simple, but it took me a long time to find it: When merging denotations
with the same signature (which is the case for the two appends, since results are
ignored), always prefer a non-bridge over a bridge as the symbol of a JointRefDenotation.