Why does javac use "invokespecial" for diamond inheritance?

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Oct 3 15:18:13 UTC 2016


Ah - finally found an example where things would regress (inspired from 
this old thread [1]). Consider this example:

interface Foo {
    Object m();
}

class Sup {
    public Object m() { return null; }
}


class Parent extends Sup implements Foo  { }

class Child extends Parent {
    public Parent m() { return (Parent)super.m(); }


    public static void main(String[] args) {
       new Child().m();
    }
}


Let's compile all classes, and then tweak Sup as follows:

class Sup {
    public *Parent* m() { return null; }
}

And then just recompile Sup.

This refactoring works - and running Child after the change to Sup still 
works.

But if the bridge generation logic is tweaked so that invokevirtual is 
used instead of invokespecial, you end up with a loop (because Child.m() 
and the bridge method for Parent.m() keep calling each other). In the 
current world, the bridge in Parent uses invokespecial to target Sup.m 
which doesn't cause infinite looping (but, as observed in this thread, 
is more prone to problems when subclasses happen to override m()).

Concluding, this seems mostly a matter of picking your poison. There's 
no (static) scheme that could guarantee perfect binary compatibility and 
absence of spurious loops in the face of code motion. One could argue 
that the problem outlined in this email already exists, so the proposed 
bridge generation change would not introduce a new problem, but make an 
existing one bigger. That said, I don' think there's enough basis here 
to go and change what javac does today.

[1] - 
http://mail.openjdk.java.net/pipermail/core-libs-dev/2012-February/009119.html

Maurizio





On 03/10/16 15:30, Maurizio Cimadamore wrote:
>
> Hi,
> I had the same reaction as Remi when first reading this thread. In 
> principle, yes, replacing invokespecial with invokevirtual can cause 
> bridge calls to go up and down the hierarchy and has more potential 
> for generating infinite loops. That said, I have not been able to come 
> up with a reasonable test case that would introduce an infinite loop 
> should javac implement the proposed change. While 6996415 is 
> definitively related, I believe that the loop in that example is 
> started by a bad descriptor ending up in the explicit supercall (see 
> bug evaluation) rather than by a problem between invokevirtual vs. 
> invokespecial.
>
> I also note that ECJ already generates an invokevirtual in the 
> reported example. Again, I could not find any clue of a bridge-related 
> loop that shows up in ECJ but not in javac (but I'd be happy to know 
> if something like that exists),.
>
> So, maybe it's a safe move after all?
>
> Maurizio
>
>
>
> On 03/10/16 14:42, Remi Forax wrote:
>> Hi Rafael,
>> it's not a binary compatible change.
>>
>> if the compiler emits an invokevirtual instead of an invokespecial, 
>> you can create an infinite loop between the bridges
>> https://bugs.openjdk.java.net/browse/JDK-6996415
>>
>> Rémi
>>
>> ------------------------------------------------------------------------
>>
>>     *De: *"Rafael Winterhalter" <rafael.wth at gmail.com>
>>     *À: *compiler-dev at openjdk.java.net
>>     *Envoyé: *Lundi 3 Octobre 2016 12:13:29
>>     *Objet: *Why does javac use "invokespecial" for diamond inheritance?
>>
>>     I am wondering why javac is using an invokespecial instruction
>>     for a bridge method in the following case. Considering the
>>     following refactoring starting from:
>>
>>     class A {
>>       public void m(String s) {
>>         System.out.println("foo");
>>       }
>>     }
>>
>>     class B extends A { }
>>
>>     class C extends B {
>>       public void m(String s) {
>>         System.out.println("bar");
>>       }
>>     }
>>
>>     I would change the implementing of B to implement an additional
>>     interface:
>>
>>     interface I<T> {
>>       void m(T t);
>>     }
>>
>>     class B extends A implements I<String> { }
>>
>>     C would live in its own module that is not recompiled upon
>>     changing B. I do however not consider this a problem as the
>>     change of B is binary compatible. However, B ends up with the
>>     following bridge method:
>>
>>     class B extends A implements I<String> {
>>       public bridge void m(Object o) {
>>         Foo.special.m((String) o);
>>       }
>>     }
>>
>>     If a program does now invoke I::m on an instance of C, "foo" is
>>     printed instead of "bar". This violates the intended virtual
>>     dispatch for C. In my opinion, this should not happen. Bridge
>>     methods, unless "visibility bridges" should always use
>>     invokevirtual as an override in C, should make it impossible to
>>     invoke A::m directly from outside the class.
>>
>>     Did I miss something here or is this a bug?
>>
>>     Thank you for the clarification,
>>     Best regards, Rafael
>>
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20161003/93bd6f90/attachment-0001.html>


More information about the compiler-dev mailing list