Description
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 method m
defined in class A
, which is not the case: it may invoke an overriding member in a subclass of A.
Related / affected issues:
- inline new mixin methods #86: the optimizer considers
invokespecial
as statically resolved, this may not hold under separate compilation - new trait encoding: skip unnecessary mixin methods #98
- https://issues.scala-lang.org/browse/SI-4989
Java example:
public class A {
public int m() { return 1; }
}
public class B extends A {
// public int m() { return 2; }
}
public class C extends B {
public int m() { return super.m(); }
public static void main(String[] args) {
System.out.println((new C()).m());
}
}
Method C.m
has the invocation INVOKESPECIAL B.m ()I
, even though there's no method m
in B
(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 of C
, continuing with further superclasses, and finally interfaces (default methods). So it will invoke A.m
and print 1
If we un-comment the definition of B.m
and only re-compile B.java
, the INVOKESPECIAL
will now invoke B.m
and print 2
.
Here's an example where we get things wrong:
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
}
object Test {
def main(args: Array[String]): Unit = {
println((new C).m)
}
}
According to the spec this should print 1
(right?)
C.super[T].x
[...] is called a static super reference. In this case, the reference is to the type or method ofx
in the parent trait ofC
whose simple name isT
.
In M4 we get
public class C extends B implements T {
public m()I
ALOAD 0
INVOKESPECIAL T.m ()I
IRETURN
}
public abstract interface T {
// no method m
}
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:
public class C extends B implements T {
public m()I
ALOAD 0
INVOKESPECIAL A.m ()I
IRETURN
}
Running the example gives 2
. What happens is that method selection (see the invokespecial
spec) starts at the superclass of C
, which is B
, and looks for a member matching m()I
. Selection does NOT start at A
.
We make the same mistake in 2.11: the body of C.m
contains INVOKESPECIAL A.m ()I
, so running the example also gives 2
.