Ok so this is a fun one
Lets say you have a superclass, some subclass and an interface that are generically typed. The interface implicitly defines methods that are implemented in the superclass, but for some reason the interface is associated to the subclass.
Now, just for extra fun, lets have the superclass implement some generic type but have it bounded. Lets have the interface also define some type, that is valid for the bound in the superclass, but have it lack the type bounding.
This leads to something that looks a little like this.
static abstract class Superclass<T extends ErasedType> {
public T aMethod(T t) { return null; }
public RetType widenReturn(T t) { return null; }
}
public interface ParameterisedInterfaceNotTypeBounded<T> {
T aMethod(T obj);
ErasedType widenReturn(T obj);
}
public static class OtherImpl extends Superclass<Refined>
implements ParameterisedInterfaceNotTypeBounded<Refined> {
public String irrelvantMethod() { return "Not important"; }
}
Notice that the only thing not correctly tying the type hierarchies together is the lack of bound in the interface generics declaration.
This has all the right ‟shape” for javac, and as such it complies this without error.
In the bytecode that is generated, we wind up with a bridge targeted at tightening the type bound from OtherImpl
→ Superclass
such that, OtherImpl
can satisfy type erasure from the interface, but still invoke the real implementation from the parent class Superclass
.
This bridge has one of the following forms in the wild (it seems to be arbitrary as to where javac places the checkcast
).
public java.lang.Object aMethod(java.lang.Object);
descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #3 // class net/sf/cglib/proxy/TestEnhancer$Refined
5: invokespecial #7 // Method net/sf/cglib/proxy/TestEnhancer$Superclass.aMethod:(Lnet/sf/cglib/proxy/TestEnhancer$ErasedType;)Lnet/sf/cglib/proxy/TestEnhancer$ErasedType;
8: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lnet/sf/cglib/proxy/TestEnhancer$OtherImpl;
MethodParameters:
Name Flags
t synthetic
Now, with the way cglib currently handles bridges that contain invokespecial
we wind up basically invalidating this statement in Enhancer.java
TODO: this assumes that the target has wider or the same type
parameters than the current.
In reality this should always be true because otherwise we wouldn't
have had a bridge doing an invokespecial.
If it isn't true, we would need to checkcast each argument
against the target's argument types
If we generate a proxy for this, and fail to correctly implement the Object aMethod(Object)
bridge we get a VerifyError
since we dont honor the superclass correctly.
This pull request contains two things towards this. The first is a unit test testBridgedTypeErasure
that tests this. The second is a (probably terrible) fast check to look for bridges that go Object
→ <T>
, and in the presence of such bridges emit a pure invokespecial
call rather than having the proxy patch up the bridge with invokevirtual
.
This change is