Discussion:
Question on binary compatibility regarding default methods
Krystal Mok
2014-03-18 23:36:37 UTC
Permalink
Hi all,

I'm curious about a corner case of binary compatibility with regard to
default methods and separate compilation (the source of many evils...). The
example is run with JDK 8 build 132.

Description:

The starting point is that I've got an interface and an implementation
class:

public interface IFoo<E> {
// empty
}

public class Foo implements IFoo<Foo> {
public void bar(Foo o) {
System.out.println("Foo.bar(Foo)");
}
}

Both the interface and the implementation are compiled into Class files.
Refer to this as ver1.
Foo won't have any bridge methods on it at this point.

Later on, the interface adds a default method, and is compiled separately,
without recompiling the implementation class:

public interface IFoo<E> {
default void bar(E e) {
System.out.println("IFoo.bar(E)");
}
}

Refer to this as ver2.

Then, another class uses the ver2 interface with the ver1 implementation
class:

public class Main {
public static void main(String[] args) {
Foo f = new Foo();
f.bar(f); // (1) invokevirtual Foo.bar(Foo)void

IFoo<Foo> i = f;
i.bar(f); // (2) invokeinterface IFoo.bar(Object)void
}
}

The output at (1) and (2) are:
Foo.bar(Foo)
IFoo.bar(E)

My question is: is this the expected behavior according to the current
spec, by design? I suppose it is.

The implementation in HotSpot seems to be only using the erased (name,
signature) of bar(Object)void when searching candidates, so naturally it
won't find the Foo.bar(Foo), and it can't find a bridge method for that
because of separate compilation.

Thanks,
Kris
Jin Mingjian
2014-03-19 10:10:11 UTC
Permalink
Hi, Kris,

It seemed a synthetic method was added when the "famous" Java generics
joining. You may need to consult experts in lambda-dev at openjdk.java.net:)

Jin
Post by Krystal Mok
Hi all,
I'm curious about a corner case of binary compatibility with regard to
default methods and separate compilation (the source of many evils...). The
example is run with JDK 8 build 132.
The starting point is that I've got an interface and an implementation
public interface IFoo<E> {
// empty
}
public class Foo implements IFoo<Foo> {
public void bar(Foo o) {
System.out.println("Foo.bar(Foo)");
}
}
Both the interface and the implementation are compiled into Class files.
Refer to this as ver1.
Foo won't have any bridge methods on it at this point.
Later on, the interface adds a default method, and is compiled separately,
public interface IFoo<E> {
default void bar(E e) {
System.out.println("IFoo.bar(E)");
}
}
Refer to this as ver2.
Then, another class uses the ver2 interface with the ver1 implementation
public class Main {
public static void main(String[] args) {
Foo f = new Foo();
f.bar(f); // (1) invokevirtual Foo.bar(Foo)void
IFoo<Foo> i = f;
i.bar(f); // (2) invokeinterface IFoo.bar(Object)void
}
}
Foo.bar(Foo)
IFoo.bar(E)
My question is: is this the expected behavior according to the current
spec, by design? I suppose it is.
The implementation in HotSpot seems to be only using the erased (name,
signature) of bar(Object)void when searching candidates, so naturally it
won't find the Foo.bar(Foo), and it can't find a bridge method for that
because of separate compilation.
Thanks,
Kris
Remi Forax
2014-03-19 10:15:08 UTC
Permalink
Post by Krystal Mok
Hi all,
I'm curious about a corner case of binary compatibility with regard to
default methods and separate compilation (the source of many evils...). The
example is run with JDK 8 build 132.
The starting point is that I've got an interface and an implementation
public interface IFoo<E> {
// empty
}
public class Foo implements IFoo<Foo> {
public void bar(Foo o) {
System.out.println("Foo.bar(Foo)");
}
}
Both the interface and the implementation are compiled into Class files.
Refer to this as ver1.
Foo won't have any bridge methods on it at this point.
Later on, the interface adds a default method, and is compiled separately,
public interface IFoo<E> {
default void bar(E e) {
System.out.println("IFoo.bar(E)");
}
}
Refer to this as ver2.
Then, another class uses the ver2 interface with the ver1 implementation
public class Main {
public static void main(String[] args) {
Foo f = new Foo();
f.bar(f); // (1) invokevirtual Foo.bar(Foo)void
IFoo<Foo> i = f;
i.bar(f); // (2) invokeinterface IFoo.bar(Object)void
}
}
Foo.bar(Foo)
IFoo.bar(E)
My question is: is this the expected behavior according to the current
spec, by design? I suppose it is.
yes
Post by Krystal Mok
The implementation in HotSpot seems to be only using the erased (name,
signature) of bar(Object)void when searching candidates, so naturally it
won't find the Foo.bar(Foo), and it can't find a bridge method for that
because of separate compilation.
yes,
as you note to solve this issue you have to teach the VM the precise
semantics of Java generics.
The lambda EG has initially though it was a good idea to solve this
corner case so Keith McGuigan have done the daunting jobs to implement
in the VM the generation of bridges with all the corner cases related to
generics leading to awful issues like the fact that to generate bridges
you have to load more classes than necessary.
So we have backpedaled on that point considering that linking the VM
semantics to the Java type system was wrong and even evil.

It's obvious now and the lambda EG should have been more more
clairvoyant on that point (sorry Keith).
Post by Krystal Mok
Thanks,
Kris
cheers,
R?mi
Krystal Mok
2014-03-19 15:33:22 UTC
Permalink
Hi R?mi,

Great! Thanks a lot for your answer. That's what I'm looking for.

I do remember that at one point there was a piece of code in the VM that
injected bridge methods, but I didn't follow the development of this area
later, so I thought the VM was still doing that, but it isn't. So I was a
bit confused and needed clarification. Now I've got it :-)

Thanks,
Kris
Post by Remi Forax
Post by Krystal Mok
Hi all,
I'm curious about a corner case of binary compatibility with regard to
default methods and separate compilation (the source of many evils...). The
example is run with JDK 8 build 132.
The starting point is that I've got an interface and an implementation
public interface IFoo<E> {
// empty
}
public class Foo implements IFoo<Foo> {
public void bar(Foo o) {
System.out.println("Foo.bar(Foo)");
}
}
Both the interface and the implementation are compiled into Class files.
Refer to this as ver1.
Foo won't have any bridge methods on it at this point.
Later on, the interface adds a default method, and is compiled separately,
public interface IFoo<E> {
default void bar(E e) {
System.out.println("IFoo.bar(E)");
}
}
Refer to this as ver2.
Then, another class uses the ver2 interface with the ver1 implementation
public class Main {
public static void main(String[] args) {
Foo f = new Foo();
f.bar(f); // (1) invokevirtual Foo.bar(Foo)void
IFoo<Foo> i = f;
i.bar(f); // (2) invokeinterface IFoo.bar(Object)void
}
}
Foo.bar(Foo)
IFoo.bar(E)
My question is: is this the expected behavior according to the current
spec, by design? I suppose it is.
yes
Post by Krystal Mok
The implementation in HotSpot seems to be only using the erased (name,
signature) of bar(Object)void when searching candidates, so naturally it
won't find the Foo.bar(Foo), and it can't find a bridge method for that
because of separate compilation.
yes,
as you note to solve this issue you have to teach the VM the precise
semantics of Java generics.
The lambda EG has initially though it was a good idea to solve this corner
case so Keith McGuigan have done the daunting jobs to implement in the VM
the generation of bridges with all the corner cases related to generics
leading to awful issues like the fact that to generate bridges you have to
load more classes than necessary.
So we have backpedaled on that point considering that linking the VM
semantics to the Java type system was wrong and even evil.
It's obvious now and the lambda EG should have been more more clairvoyant
on that point (sorry Keith).
Post by Krystal Mok
Thanks,
Kris
cheers,
R?mi
Loading...