Hello,

Follow-up below...

On 01/30/2012 09:10 AM, Joe Darcy wrote:
On 01/29/2012 10:52 PM, Rémi Forax wrote:
On 01/30/2012 04:58 AM, Joe Darcy wrote:
Hello,

As an indirect outgrowth of warnings cleanup day, various categories of warnings can be eliminated by in the use of Cloneable types by overriding the

    Object clone()

method inherited from java.lang.Object with a covariant override such as

    MyType clone()

Please review my changes for

    7140820 Add covariant overrides to Collections clone methods
    http://cr.openjdk.java.net/~darcy/7140820.0/

which add such covariant override clone methods to collections and a few other classes in java.util. Doing a full JDK build with these changes, I've also made alterations to other classes to remove now superfuous casts (casts which are a javac lint warning!) and some unneeded @SuppressWarnings annotations. I also cleaned up a few rawtypes warnings while in editing files in java.util.

(Note that the old specListeners method in EventRequestSpecList.java was much buggy; it cast an ArrayList from runtime.specListeners to a Vector.)

Thanks,

-Joe

WTF !

while it's maybe a binary compatible change, I haven't take a look to all modified classes to be sure
it's not a source compatible change.

Adding the covariant override is a binary compatible change because there would be a bridge method providing the original "Object clone()" signature.


People had created class that inherits from ArrayList and override clone, while it's not a good practice, there is a *lot* of code from pre-1.5 days that does exactly that,
this change will simply break all those codes.


*sigh*

Yes, I didn't fully consider the source compatibility implications given that these types can be subclasses; I'll look this over some more.

(This was meant to be part of a larger effort to review of potentially overridable clone methods in the JDK. I wrote an annotation processor to look over the code base to find potential classes to upgrade; I can refine the processor to look for classes that don't override clone and are also final or effectively final, having no accessible constructors.)

Thanks Remi,

-Joe

I've looked into the source compatibility aspects of this change. As a simplification, I considered two versions of the parent Cloneable class:

    Parent A: Object clone()
    Parent B: Parent clone()

and three versions of the Child with an explicit clone method:

    Child A: Object clone()
    Child B: Parent clone()
    Child C: Child clone()

The Child.clone implementations dutifully calls super.clone. Using javac from JDK 7u2 I compiled each version of Child with the proper version of Parent on the classpath as a class file. The results are:

Parent A    Child A    compiles
Parent B    Child A    doesn't compile // Cannot override the bridge method

Parent A    Child B    compiles [* see behavioral compatibility note below]
Parent B    Child B    compiles

Parent A    Child C    compiles
Parent B    Child C    compiles

All terms of binary compatibility, the continued ability to link, each version of Child when compiled against Parent A, linked against either Parent A or Parent B. In other words, adding the covariant overrides in Parent is a binary compatible change.

In terms of behavioral compatibility, each version of Child when compiled against Parent A, ran against either Parent A or Parent B *except* for Child B compiled against Parent A and run against Parent B, which gave a stack overflow error.

In the problematic case, we have the new expected clone methods in the class file of Parent B:

  public Parent clone();
    flags: ACC_PUBLIC
    ...

public java.lang.Object clone() throws java.lang.CloneNotSupportedException;
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #10                 // Method clone:()LParent;
         4: areturn
      LineNumberTable:
        line 1: 0
    Exceptions:
      throws java.lang.CloneNotSupportedException

and in the class file of Child B compiled against Parent A

  public Parent clone();
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
1: invokespecial #2 // Method Parent.clone:()Ljava/lang/Object;
    ...

  public java.lang.Object clone();
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #8                  // Method clone:()LParent;
         4: areturn
      LineNumberTable:
        line 1: 0

So, what happens executing the seemingly innocent code

     Object o = (new Child()).clone();

that gets compiled to byte code

7: invokevirtual #8 // Method clone:()LParent;

is that the "Parent clone()" method on Child gets called. This in turns calls the "Object clone()" method on Parent. The "Object clone" method in Parent is a bridge method and does an invoke virtual on "Parent clone", which starts the cycle all over. In the end the following stack trace is generated:

    at Parent.clone(Parent.java:1)
    at Child.clone(Child.java:12)
    at Parent.clone(Parent.java:1)
    at Child.clone(Child.java:12)
    ...

So, as long as the Child class either:

* Didn't override clone at all
* Overrode clone to return Child

then all is well. There are different problems if Child overrode clone to return Object or to return Parent.

The JDK generally doesn't promise 100% source compatibility between major releases so the Parent B + Child A scenario doesn't necessarily disqualify this change. (I think much more code would benefit from the covariant return on core collections than be harmed by it.)

Having Child override clone to return Parent would be weird and presumably rare.

So adding the covariant overrides of clone has a larger compatibility impact than I expected, but I'm still considering going cautiously forward with the work. If it does go forward, I'd grudgingly acknowledge it might have to be backed out later in the release if there was too large a compatibility impact in practice.

-Joe

Reply via email to