-
Notifications
You must be signed in to change notification settings - Fork 14
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
wrong assumption about invokespecial
#143
Comments
I added a lot of labels to this bug to make it look more important 🤓 |
From the linked spec:
Note the last sentence. Apparently we could achieve what we initially want simply by not setting |
Also, FTR, the corresponding Scala.js IR node ( |
Uh re
(from https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1) |
yeah, |
Eek. If only we could do in regular bytecode as https://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.Lookup.html unreflectSpecial is able to do reflectively. |
Static methods (paired with non static forwarders) are looking more and more like a way out of our invokespecial blues. |
Shockingly, even
This produces |
It sure is hard to disclaim one's inheritance. In addition to the separate compilation example, it is legal in Java source to write B.m in the absence of the method in B if it exists in A. If A is inaccessible, this can be the only way to call it. |
Ah, right! Anyway this issue is less problematic because we can statically resolve the method (in the scala compiler / optimizer), assuming the correct classes are on the classpath, which is a core assumption of the optimizer anyway. |
I thought you might be interested in the shenanigans I had to go through to do a bit of monkey-patching of I wanted to "override" the nested That happens because javac expects the super constructor to have the signature The workaround was monstrous but effective. |
I am prototyping a backend change to use |
@retronym Under that scheme, what will happen to super calls to Java-defined default methods? |
@sjrd Good question. Here's a test case to consider: trait T extends java.lang.Iterable[String] {
override def spliterator(): java.util.Spliterator[String] = {
println("T.spliterator")
println("super.spliterator = " + super.spliterator)
null
}
def foo = {
println("T.foo")
println("super[Iterable].spliterator = " + super[Iterable].spliterator)
()
}
def iterator(): java.util.Iterator[String] = java.util.Collections.emptyList().iterator()
}
class C extends T
object Test {
def main(args: Array[String]): Unit = {
val t = new C
t.foo
t.spliterator
}
} Clearly we can't rewrite super calls to Java defaults as
So we either need to:
Incidentally, that test case crashes in Mixin in 2.11.8 |
with scala/scala#5251 merged, we can now also fix this one. |
Bummer, I just realized that we cannot fix the example in this ticket, here it is again: class A { def m = 1 }
class B extends A { override def m = 2 }
trait T extends A
class C extends B with T {
override def m = super[T].m // should invoke A.m
} In bytecode, there are only two implementations of It doesn't matter whether we emit |
I've prototyped a @DarkDimius's idea about using reflection +indy to link exactly to the methods we want. This fixes the example above, and also lets us collapse the static-method-and-forwarder pair for traits back to a single method. But... it requires us to sneakily get a hold of |
@retronym, why do you need it? Afaik it would only be needed if you are intending to access the method outside of subclass higherarchy. This is why in my scheme I proposed using static fields to obtain the method handles. Note, that in case of super[X] in traints, as traits are interfaces that only inherit Object, the are outside of the hierarchy. But we create super-accessors in classes when they are mixed in, so we should be fine. |
|
Assigning to backlog because we don't seem to have a solution in mind that is workable for 2.12.0. The fundamental problem seems to be the fact that Scala allows traits to extend classes. I wonder if this something that we should deprecate in dotty? A trait could still have the class a a self-type. |
@DarkDimius Unfortunately we need public static CallSite bootstrap(MethodHandles.Lookup lookup, String invokedName, MethodType invokedType,
Class<?> fromSite) throws Throwable {
Method method = fromSite.getMethod(invokedName, invokedType.dropParameterTypes(0, 1).parameterArray());
System.out.println(lookup.lookupClass());
System.out.println(fromSite);
return new ConstantCallSite(lookup.in(fromSite).unreflectSpecial(method, fromSite));
}
|
Alex Buckley pointed out a plausible historical explanation for this latent bug: the semantics of So we sort of stepped into the wrong semantics with by declaring the |
scala/scala#5369 for the example shown in #143 (comment) |
See also scala/scala#5377 |
I went again through the
invokespecial
spec. One thing i didn't realize before is that the invoked method may be defined in a different class than the one used as receiver in the invocation instruction.The compiler assumes that
invokespecial A.m
will always invoke methodm
defined in classA
, which is not the case: it may invoke an overriding member in a subclass of A.Related / affected issues:
invokespecial
as statically resolved, this may not hold under separate compilationJava example:
Method
C.m
has the invocationINVOKESPECIAL B.m ()I
, even though there's no methodm
inB
(it's commented out). According to the spec, the method to be invoked is selected by searching for a matching member (name and signature), starting at the superclass ofC
, continuing with further superclasses, and finally interfaces (default methods). So it will invokeA.m
and print1
If we un-comment the definition of
B.m
and only re-compileB.java
, theINVOKESPECIAL
will now invokeB.m
and print2
.Here's an example where we get things wrong:
According to the spec this should print
1
(right?)In M4 we get
Which is incorrect, running the example gives
java.lang.NoSuchMethodError: T.m()I
.In current 2.12.x we get this - the change is probably due to my recent PR scala/scala#5096:
Running the example gives
2
. What happens is that method selection (see theinvokespecial
spec) starts at the superclass ofC
, which isB
, and looks for a member matchingm()I
. Selection does NOT start atA
.We make the same mistake in 2.11: the body of
C.m
containsINVOKESPECIAL A.m ()I
, so running the example also gives2
.The text was updated successfully, but these errors were encountered: