Skip to content

wrong assumption about invokespecial #143

Closed
@lrytz

Description

@lrytz

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:

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 of x in the parent trait of C whose simple name is T.

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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions