Why does javac use "invokespecial" for diamond inheritance?

Rafael Winterhalter rafael.wth at gmail.com
Mon Oct 3 15:52:14 UTC 2016


Thank you both for the clarification. Given your example, it makes sense to
me. But I am still a bit surpised of the behavior since, as you say, its a
matter of picking your poison. Personally, I must say that I would prefer
the infite loop as an outcome as this at least crashes the application.
This accidental super method invocation might instead get my application
into a bad state or even offer an exploit when allowing some security
circumvention or corrupting a data base.

Best regards, Rafael

2016-10-03 17:18 GMT+02:00 Maurizio Cimadamore <
maurizio.cimadamore at oracle.com>:

> 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> <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/cb1f9e60/attachment.html>


More information about the compiler-dev mailing list