Skip to content
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

Fix #6152: Dotty tries to override Java bridge methods #6266

Closed
wants to merge 1 commit into from

Conversation

anatoliykmetyuk
Copy link
Contributor

@anatoliykmetyuk anatoliykmetyuk commented Apr 9, 2019

Dotty overrides Java bridge methods when the user intends to override
the methods for which the bridges were generated. This commit forbids
Dotty to override bridge methods altogether.

This fix rests on the assumption that it is never legal to override synthetically generated bridge methods from the user's code.

Also, it seems that the current test framework does not allow to create tests for this issue. I haven't found a way to reproduce the issue as specified here with the existing test framework.

Copy link
Member

@dottybot dottybot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, and thank you for opening this PR! 🎉

All contributors have signed the CLA, thank you! ❤️

Have an awesome day! ☀️

@smarter
Copy link
Member

smarter commented Apr 9, 2019

This works but I wonder if instead we could change ClassfileParser to not create symbols for bridge methods. This way joint and separate compilation would see the same symbols and this reduces the chance that they'll behave differently.

@odersky
Copy link
Contributor

odersky commented Apr 9, 2019

We should find out what Scala-2 is doing. It might be that we ignore bridge methods before erasure but make them visible afterwards.

@smarter
Copy link
Member

smarter commented Apr 9, 2019

@odersky So Scala 2 has a very peculiar handling of bridges, as I noted in #6152 (comment):

it's interesting to note that member in Scala 2 excludes members with the bridge flag by default, where as member in Dotty doesn't do that. Exclusion also works differently in both compiler, in Scala 2 if a symbol with an excluded flag overrides a symbol without the flag, the overridden symbol is returned, whereas no symbol is returned in Dotty.

All in all, it seems simpler to try to align what we do for separate and joint compilation by just not creating bridge symbols in the first place.

@anatoliykmetyuk
Copy link
Contributor Author

There may be two separate considerations here: the consistency of the Dotty separate compilation with Scala 2 and the consistency of the Dotty separate compilation with the Dotty joint compilation. IMO the internal consistency of the project should be of a higher priority than the external one. The former one yields bugs if not observed, the latter one yields compatibility issues.

We could FTTB make the separate compilation consistent with the joint compilation to get rid of the bug while creating a task (an issue, a milestone etc) to get consistent with Scala 2.

@@ -96,7 +96,8 @@ class CompilationTests extends ParallelTesting {
"tests/neg-custom-args/fatal-warnings/xfatalWarnings.scala",
defaultOptions.and("-nowarn", "-Xfatal-warnings")
) +
compileFile("tests/pos-special/typeclass-scaling.scala", defaultOptions.and("-Xmax-inlines", "40"))
compileFile("tests/pos-special/typeclass-scaling.scala", defaultOptions.and("-Xmax-inlines", "40")) +
compileDir("tests/pos-special/i6152", defaultOptions, separateCompilation = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use pos-special ? If you put i6152 in tests/pos it should work (except that decompilation tests are broken when java files are involved, we work around this by putting the tests in tests/pos-java-interop)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it worked. It seems compileFilesInDir does not treat the sources in its base directory as the part of the same compilation, which confused me. Makes sense.

@anatoliykmetyuk
Copy link
Contributor Author

It looks like we can't trivially make Dotty bridge-blind because Dotty makes use of the bridge information.

Consider the following line:

val f: (=> Int) => Unit = a => ()

Normally it should compile to the following bytecode:

  public void main(java.lang.String[]);
    Code:
       0: aload_0
       1: invokedynamic #47,  0             // InvokeDynamic #0:applyVoid:(LTest$;)Ldotty/runtime/function/JProcedure1;
       6: astore_2
       7: return

With the bridge-blindness (the modified fix proposed in this PR), it compiles to:

  public void main(java.lang.String[]);
    Code:
       0: aload_0
       1: invokedynamic #47,  0             // InvokeDynamic #0:apply:(LTest$;)Ldotty/runtime/function/JProcedure1;
       6: astore_2
       7: return

In the former case, JProcedure1.applyVoid is called, in the latter case, JProcedure.apply is called. The latter case yields a runtime exception.

During the bytecode generation, JProcedure1 is assumed to be a functional interface (containing a single abstract method). The method is retrieved at this line. samMethod works by filtering all the abstract methods of the symbol it is called on and returning the first one (which is unsafe: sam in the name stands for a single abstract method, and no checks are performed to verify whether the method is indeed single).

Now, JProcedure1 is a successor of Scala's Function1, which has an abstract method apply (erased to Object => Object). JProcedure1 overrides that method with a more specific version (Object => BoxedUnit). However, since Dotty doesn't see the bridges, it doesn't see that the method is overriden. Dotty sees two abstract methods, applyVoid and apply, which leads to the error.

Considerations

So far the issue above is specific to the (=> Int) => Unit example, since it is treated in a special way by the compiler. samMethod, for example, doesn't seem to be used in any other setting rather than for the procedures as above. Hence if we go with the bridge-blindless, we may be able to localise the issue by just modifying the bytecode generation logic for the procedures.

However, the example reveals that the bridges may potentially provide the information on whether a method is abstract or concrete. If we make Dotty bridge-blind to make the separate compilation similar to the joint compilation, we'll have to explicitly perform the mentioned checks whenever we need them. This may impact performance and give us more headache when we forget to do so.

So my opinion is that it'd be best to make all of the available information exposed to Dotty.

@smarter, @odersky, what do you think should be done?

@odersky
Copy link
Contributor

odersky commented Apr 13, 2019

I think we should do exactly what scalac does. It's a tricky subject and scalac had 10 years to refine it.

@smarter
Copy link
Member

smarter commented Apr 13, 2019

If we make Dotty bridge-blind to make the separate compilation similar to the joint compilation, we'll have to explicitly perform the mentioned checks whenever we need them. This may impact performance and give us more headache when we forget to do so.
So my opinion is that it'd be best to make all of the available information exposed to Dotty.

I'm not convinced, based on what you're saying it seems that we've always had a bug under joint compilation and that making separate compilation behave like joint compilation is just now exposing this bug. Consider that when dotty-library is compiled, JProcedure1 is jointly compiled with other .scala files, what happens if one of these scala files contains a line like val f: (=> Int) => Unit = a => () ?

@smarter
Copy link
Member

smarter commented Apr 13, 2019

Also consider that if JProcedure1 was written in Scala and not in Java, we would not see the bridges defined in it when using separate compilation, since pickling is before bridge generation.

@smarter
Copy link
Member

smarter commented Apr 13, 2019

I think there's just no way we can accurately compute which members are abstract after erasure, because inevitably there's bridges we cannot see (under separate compilation, we cannot see bridges from other Scala files (because pickling is before bridge generation), and under joint compilation, we cannot see bridges from other Java files (because we drop Java compilation units after Typer)). So samMethod should be wrapped in a ctx.atPhase(ctx.erasurePhase) { ... } like some other methods in DottyBackendInterface already are. Scala 2 does something which achieves a similar result (they record information about the SAM type in a tree attachment in an early phase for use in the backend).

@smarter
Copy link
Member

smarter commented Apr 13, 2019

And longer term we should consider changing the definition of the Closure tree node to record which method is the single abstract one of the type instead of computing this information in the backend.

@anatoliykmetyuk
Copy link
Contributor Author

I'm not convinced, based on what you're saying it seems that we've always had a bug under joint compilation and that making separate compilation behave like joint compilation is just now exposing this bug

Joint compilation works fine since we can determine whether a method is abstract by analysing Java files. We do not need the bridges that come from the class files there.

we would not see the bridges defined in it when using separate compilation, since pickling is before bridge generation

Can you please elaborate? I thought the order of the phases is irrelevant when using separate compilation since the second compilation run happens after the first one has completed. Hence during the second run, we'll have the class files with the bridges in them.

I think we should do exactly what scalac does. It's a tricky subject and scalac had 10 years to refine it.

Ok, it seems we need to solve the #6152 in the context/after #6136. So I'll close this PR FTTB.

@smarter
Copy link
Member

smarter commented Apr 15, 2019

Joint compilation works fine since we can determine whether a method is abstract by analysing Java files.

To determine whether a method in a base class is abstract or not, you need to compute overriding relationship, and what I'm saying is that this cannot be done correctly after erasure.

Hence during the second run, we'll have the class files with the bridges in them.

For Dotty classes, we do not unpickle symbols based on the information in the classfile, we only look at the information stored in tasty files, and for Scala 2 classes we only look at the information in the ScalaSig attribute. So in both of these cases we're not going to see any bridge.

Ok, it seems we need to solve the #6152 in the context/after #6136

I don't think any of the improvement in the Scala 2 ClassfileParser are going to change anything to the situation here. The real solution is in #6266 (comment).

@anatoliykmetyuk
Copy link
Contributor Author

To determine whether a method in a base class is abstract or not, you need to compute overriding relationship, and what I'm saying is that this cannot be done correctly after erasure.

I believe when we are doing the joint compilation, we are working before the erasure?

For Dotty classes, we do not unpickle symbols based on the information in the classfile, we only look at the information stored in tasty files, and for Scala 2 classes we only look at the information in the ScalaSig attribute. So in both of these cases we're not going to see any bridge.

So we are still getting the new information different from the one available for the Java-derived class files.

  • Dotty: tasty -> deferred flags on symbols
  • Scala 2: ScalaSig -> deferred flags on symbols
  • Java: bridges -> deferred flags on symbols

I don't think any of the improvement in the Scala 2 ClassfileParser are going to change anything to the situation here. The real solution is in #6266 (comment).

Could be, we never know until we try with such things :) I guess we still need to bring ourselves up to date with Scala 2 since we do not want a divergence here. So if this one can wait, I think it'd be best to avoid tactical fixes here. Even if Scala 2 improvements don't fix this one, we still need to synergise whatever we come up with whatever Scala 2 did.

@smarter
Copy link
Member

smarter commented Apr 15, 2019

I believe when we are doing the joint compilation, we are working before the erasure?

samMethodis called after erasure, that's where things go wrong.

@anatoliykmetyuk
Copy link
Contributor Author

samMethodis called after erasure, that's where things go wrong.

Yes, but it is working on the symbols generated before the erasure.

@smarter
Copy link
Member

smarter commented Apr 15, 2019

And some of these symbols are going to come from Scala 2 or Dotty classfiles, so we won't generate bridges for them, so the overriding relationship after erasure may incorrectly show some methods as being abstract.

@anatoliykmetyuk
Copy link
Contributor Author

The point is not the bridges and not the overriding relationship. The point is the symbols being identified as deferred. This is because of samMethod, the single abstract method. We don't need to know if a method overrides another, we only need to know if it is abstract.

In different scenarios, we have different info in our disposal: bridges, tasty, Java sources, etc. The idea is to use this info to prove that a method is deferred. Some of this info is enough to mark a method as deferred, some may be not.

I guess inconsistency or impossibility to derive "deferred" from given info is a tricky subject.

@smarter
Copy link
Member

smarter commented Apr 15, 2019

abstractTermMembers works by looking at all members of the class and checking which one have a Deferred flag, the subtle part here is "all members of the class" which is not going to give you a member in a parent class if it's overridden in a subclass, so the way overriding is computed is crucial to abstractTermMembers.

@anatoliykmetyuk
Copy link
Contributor Author

True, I believe it is quite likely that to mark a symbol as deferred, overriding needs to be taken into account. I also believe that it is likely that the info supplied must be sufficient to correctly implement inheritance both at compile time and at runtime: inheritance is fundamental to Java, and Java's been around for a long time, so it makes sense for them to get it right. I also believe that if for some reason it is not possible to do so in Scala, we should consider that Scala itself may be buggy.

However, I am not familiar with the specifics, so I won't speculate further.

Whether deferrence indeed involves an explicit computation of overriding, and whether it is indeed fundamentally impossible to derive it from the info we have is an interesting topic. So if you strongly believe it is the case, maybe you can create an issue so that it is on file and we can work with this later?

@anatoliykmetyuk anatoliykmetyuk force-pushed the i6152 branch 2 times, most recently from 9dbade3 to 62d5f3a Compare May 17, 2019 10:59
@anatoliykmetyuk
Copy link
Contributor Author

I had a look at what Scala 2 does. Seems like they do allow bridges to be parsed in the classfile parser (dropping only private methods). They make sure such methods do not participate in the overriding relationships.

@@ -375,6 +375,6 @@ object ClassfileConstants {
override def baseFlags(jflags: Int) = if ((jflags & JAVA_ACC_FINAL) == 0) Mutable else EmptyFlags
}
val methodTranslation: FlagTranslation = new FlagTranslation {
override def baseFlags(jflags: Int) = if ((jflags & JAVA_ACC_BRIDGE) != 0) Bridge else EmptyFlags
override def baseFlags(jflags: Int) = if ((jflags & JAVA_ACC_BRIDGE) != 0) BridgeArtifact else EmptyFlags
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be needed since all bridges are emitted with the ACC_SYNTHETIC flag which corresponds to Artifact in Scala. The problem seems to be that we don't actually set the Artifact flag for them: https://github.com/lampepfl/dotty/blob/9dbade3e3c08a81a2b2744d3afa5a8434bd0cdba/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala#L344 compare with https://github.com/scala/scala/blob/a26aeb7472ae2d2bab331bfcf9f785c3c7d1c18a/src/reflect/scala/reflect/internal/ClassfileConstants.scala#L350

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular reason I think, since no one ever generates non-synthetic bridges it doesn't make a difference (but non-bridge synthetic do exist so fixing that is important)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, seems to be working without that line

@@ -1141,7 +1141,7 @@ object SymDenotations {
* either as overrider or overridee.
*/
final def memberCanMatchInheritedSymbols(implicit ctx: Context): Boolean =
!isConstructor && !is(Private)
!isConstructor && !is(Private) && !is(Artifact)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
!isConstructor && !is(Private) && !is(Artifact)
!isConstructor && !is(Private | Artifact)

@@ -40,7 +40,7 @@ object OverridingPairs {
* relative to <base>.this do
*/
protected def matches(sym1: Symbol, sym2: Symbol): Boolean =
sym1.isType || self.memberInfo(sym1).matches(self.memberInfo(sym2))
sym1.isType || (!exclude(sym2) && self.memberInfo(sym1).matches(self.memberInfo(sym2)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change needed ? Doesn't the existing logic already take care of calling exclude when necessary ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, seems to be working without this change

@anatoliykmetyuk anatoliykmetyuk force-pushed the i6152 branch 4 times, most recently from d19d555 to 9fdeb94 Compare May 20, 2019 12:41
@@ -20,14 +20,15 @@ object OverridingPairs {
/** The cursor class
* @param base the base class that contains the overriding pairs
*/
class Cursor(base: Symbol)(implicit ctx: Context) {
class Cursor(base: Symbol, excludeArtifacts: Boolean = true)(implicit ctx: Context) {
Copy link
Member

@smarter smarter May 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit roundabout to me. Instead of creating symbols for artifacts just to ignore them afterwards, I think we should just not create symbols for artifacts in ClassfileParser. Given that we don't see these symbols under joint compilation it should reduce the differences in execution between joint and separate compilation and thus reduce the number of unique bugs to one or the other.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to make this into a separate issue since it seems to be a tricky topic.

Dotty overrides Java bridge methods when the user intends to override
the method for which the bridge was generated. This commit forbids
Dotty to override bridge methods altogether.
@anatoliykmetyuk
Copy link
Contributor Author

anatoliykmetyuk commented May 20, 2019

What happened is:

testCompilation i4430
...
Test 'tests/run/i4430.scala' failed with output:
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at dotty.tools.vulpix.ChildJVMMain.runMain(ChildJVMMain.java:40)
	at dotty.tools.vulpix.ChildJVMMain.main(ChildJVMMain.java:47)
Caused by: java.lang.IllegalAccessError: tried to access class java.lang.AbstractStringBuilder from class Test$
	at Test$.main(i4430.scala:5)
	at Test.main(i4430.scala)
	... 6 more

That test:

object Test {
  def main(args: Array[String]): Unit = {
    val sb = new java.lang.StringBuilder()
    sb.append(Array[Char](), 0, 0)
    sb.length
  }
}

The master version translates the sb.length call to:

invokevirtual #74                 // Method java/lang/StringBuilder.length:()I

This PR version translates it to:

invokevirtual #76                 // Method java/lang/AbstractStringBuilder.length:()I

At a glance, looks like the master version uses a bridge and this PR version goes for the second-best alternative, the bridge being absent...

@anatoliykmetyuk
Copy link
Contributor Author

I traced ClassfileParser with:

  def run()(implicit ctx: Context): Option[Embedded] = try {
    ctx.debuglog("[class] >> " + classRoot.fullName)
    parseHeader()
    this.pool = new ConstantPool
    val res = parseClass()
    if (Set("java.lang.AbstractStringBuilder", "java.lang.StringBuilder").contains(classRoot.fullName.toString)) {
      val length = classRoot.info.decls.filter(_.name.toString.contains("length")).head
      println(s"""
      |Parsed ${classRoot.fullName}
      |Length: ${length}
      |Type: ${length.info.show}
      |Bridge: ${length.is(Flags.Bridge)}
      """.stripMargin)
    }

[...]

Output on the above test compilation:

Parsed java.lang.AbstractStringBuilder
Length: method length
Type: (): Int
Bridge: false


Parsed java.lang.StringBuilder
Length: method length
Type: (): Int
Bridge: true

So it seems the StringBuilder's length method is, in fact, a bridge, which is quite strange... Any idea why this might be so @smarter?

@smarter
Copy link
Member

smarter commented May 20, 2019

Interesting, javac doesn't do this, so we're not imitating it well enough here:

class i6266 {
  public void foo(StringBuilder sb) {
    System.out.println(sb.length());
  }
}
  public void foo(java.lang.StringBuilder);
    descriptor: (Ljava/lang/StringBuilder;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_1
         4: invokevirtual #3                  // Method java/lang/StringBuilder.length:()I
         7: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V

@smarter
Copy link
Member

smarter commented May 20, 2019

So it seems the StringBuilder's length method is, in fact, a bridge, which is quite strange...

javac sometimes generates bridges to make a method visible (and thus prevent IllegalAccessError), see http://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html for some examples.

@anatoliykmetyuk
Copy link
Contributor Author

anatoliykmetyuk commented May 20, 2019

Interesting, javac doesn't do this, so we're not imitating it well enough here:

master also always picks the StringBuilder's method, so I think we are in line with javac here.

javac sometimes generates bridges to make a method visible (and thus prevent IllegalAccessError), see http://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html for some examples.

So it seems here the bridge is generated because AbstractStringBuilder is package-private. I don't think we can drop the bridges given they are sometimes generated by Java to increase visibility and not for the variance reasons.

@anatoliykmetyuk
Copy link
Contributor Author

I propose that in the OverridingPairs we only ignore bridges and not artefacts. This will solve the problem of needing an additional flag in OverridingPairs.Cursor, since we want to ignore bridges at all times there, and artefacts – only sometimes.

@smarter
Copy link
Member

smarter commented May 20, 2019

master also always picks the StringBuilder method, so I think we are in line with javac here.

Not quite. The theory says that ignoring synthetic methods coming from classfiles should make separate compilation more like joint compilation, and indeed we can reproduce the bug on master with joint compilation :).

package pkg;
class P {
  public void foo() {
  }
}

public class A extends P {
}
object B {
  def main(args: Array[String]): Unit = {
    val a = new pkg.A
    a.foo()
  }
}
% dotc A.java B.scala
% javac A.java
% dotr B
Exception in thread "main" java.lang.IllegalAccessError: tried to access class pkg.P from class B$
        at B$.main(B.scala:4)
        at B.main(B.scala)

And indeed we use the wrong signature in the method call:

         9: invokevirtual #35                 // Method pkg/P.foo:()V

Compare with scalac:

         9: invokevirtual #21                 // Method pkg/A.foo:()V

@anatoliykmetyuk
Copy link
Contributor Author

Ok, you're right, this does look like an issue. Probably we can try this solution again after solving that issue.

@anatoliykmetyuk
Copy link
Contributor Author

On the second thought, even with the issue fixed, I think we'll still have to retain the bridges when parsing class files. The method calls in question target exactly the bridges, and hence the compiler must be aware of them. Either predict their existence in the joint compilation scenario, or obtain them from the class file on separate compilation.

The point is, we do use the bridges on compilation, hence we can't drop them. The IllegalAccessError on joint compilation arises precisely from the fact that the compiler is not aware of the bridges on joint compilation.

@smarter
Copy link
Member

smarter commented May 20, 2019

Either predict their existence in the joint compilation scenario, or obtain them from the class file on separate compilation.

If we can predict them, then we don't need to obtain them in any other way. (and the prediction part is probably not very complicated in practice, we just need to figure out what scalac is doing exactly).

@anatoliykmetyuk
Copy link
Contributor Author

anatoliykmetyuk commented May 21, 2019

If we can predict them, then we don't need to obtain them in any other way

I would say the reverse is more efficient. In case of joint compilation we have no other choice but predicting them. In case of classfiles, they are already there, so why put extra effort in predicting them?

@smarter
Copy link
Member

smarter commented May 21, 2019

Because I suspect that the "prediction" will actually be really fast. Because in the backend you don't know which symbols came from Java sources and which came from Java classfiles so you can't treat them differently, and most important of all because using different ways to achieve the same result increases the chance of bugs

smarter added a commit to dotty-staging/dotty that referenced this pull request Mar 10, 2020
This is a port of
scala/scala@765eb29:

  When emitting a virtual call, the receiver in the bytecode cannot just
  be the method's owner (the class in which it is declared), because that
  class may not be accessible at the callsite. Instead we use the type
  of the receiver. This was basically done to fix
  - aladdin bug 455 (9954eaf)
  - SI-1430 (0bea2ab) - basically the same bug, slightly different
  - SI-4283 (8707c9e) - the same for field reads

In Dotty, we rarely encountered this issue because under separate
compilation, we see the bridge symbols generated by javac and use their
owner as the receiver, but under joint compilation of .java and .scala
sources this doesn't happen, in particular this commit fixes scala#6546. It
also means that we should now be able to stop creating symbols in the
ClassfileParser for Java bridges, this would make joint and separate
compilation more similar and thus should reduce the number of bugs which
appear in one but not the other as discussed in
scala#6266.

After this commit, some more changes are necessary to get the updated
backend to work correctly with dotty, these are implemented in the
later commits of this PR.

Co-Authored-By: Lukas Rytz <[email protected]>
smarter added a commit to dotty-staging/dotty that referenced this pull request Mar 10, 2020
This is a port of
scala/scala@765eb29:

  When emitting a virtual call, the receiver in the bytecode cannot just
  be the method's owner (the class in which it is declared), because that
  class may not be accessible at the callsite. Instead we use the type
  of the receiver. This was basically done to fix
  - aladdin bug 455 (9954eaf)
  - SI-1430 (0bea2ab) - basically the same bug, slightly different
  - SI-4283 (8707c9e) - the same for field reads

In Dotty, we rarely encountered this issue because under separate
compilation, we see the bridge symbols generated by javac and use their
owner as the receiver, but under joint compilation of .java and .scala
sources this doesn't happen, in particular this commit fixes scala#6546. It
also means that we should now be able to stop creating symbols in the
ClassfileParser for Java bridges, this would make joint and separate
compilation more similar and thus should reduce the number of bugs which
appear in one but not the other as discussed in
scala#6266.

After this commit, some more changes are necessary to get the updated
backend to work correctly with dotty, these are implemented in the
later commits of this PR.

Co-Authored-By: Lukas Rytz <[email protected]>
smarter added a commit to dotty-staging/dotty that referenced this pull request Mar 12, 2020
This is a port of
scala/scala@765eb29:

  When emitting a virtual call, the receiver in the bytecode cannot just
  be the method's owner (the class in which it is declared), because that
  class may not be accessible at the callsite. Instead we use the type
  of the receiver. This was basically done to fix
  - aladdin bug 455 (9954eaf)
  - SI-1430 (0bea2ab) - basically the same bug, slightly different
  - SI-4283 (8707c9e) - the same for field reads

In Dotty, we rarely encountered this issue because under separate
compilation, we see the bridge symbols generated by javac and use their
owner as the receiver, but under joint compilation of .java and .scala
sources this doesn't happen, in particular this commit fixes scala#6546. It
also means that we should now be able to stop creating symbols in the
ClassfileParser for Java bridges, this would make joint and separate
compilation more similar and thus should reduce the number of bugs which
appear in one but not the other as discussed in
scala#6266.

After this commit, some more changes are necessary to get the updated
backend to work correctly with dotty, these are implemented in the
later commits of this PR.

Co-Authored-By: Lukas Rytz <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants