-
Notifications
You must be signed in to change notification settings - Fork 891
Fix bridge type erasure #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
eca6ad0
to
b6a7a11
Compare
Thanks for the excellent analysis and patch. I'll take a closer look next On Fri, Aug 26, 2016, 1:53 AM Greg Bowyer notifications@github.com wrote:
|
I don't think the assumption that the bridge's argument is class Test {
interface ErasedType {}
interface RetType extends ErasedType {}
interface Bound {}
interface Refined extends Bound, ErasedType {}
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 extends Bound> {
T aMethod(T obj);
ErasedType widenReturn(T obj);
}
public static class OtherImpl extends Superclass<Refined>
implements ParameterisedInterfaceNotTypeBounded<Refined> {
public String irrelvantMethod() { return "Not important"; }
}
}
I put together a naive fix for the same limitation in #90 before seeing this. What do we lose by just adding checkcasts? Are there cases where that changes overriding behaviour? |
Don't think we lose much by adding the casts -- I wasn't terribly familiar On Mon, Sep 19, 2016 at 9:12 PM Liam Miller-Cushon notifications@github.com
|
I never said the fix was all that great here, I slammed it together to get started on the conversation. I guess if it always does a checkcast the code itself could actually get simpler (and we would not have to read the classfiles like my version does too). If it helps, for further information; this appears if the classfile for the target is found on the classloader that cglib resides in (typically the system / root classloader). If that class does not get found, cglib basically generates a version with checkcasts for simplicity. |
@cushon I much prefer your fix |
I wasn't sure whether we want to preserve invokesuper in this case - this patch does, mine doesn't. But if everyone's OK with starting with the simple fix (which definitely avoids the VerifyError), that sounds good to me. |
@GregBowyer do you know if there are cases where preserving I wasn't sure if we could ever end up accidentally overriding something. Having the bridge redirect through other proxy methods seems like it's usually the right behaviour. |
This is just an academic guess, but I assume that the bridge methods might use |
cglib already leaves invokespecial bridges alone if they target a method with the same signature [1]. If a bridge was using invokespecial to avoid dynamic dispatch on a method with a different signature, that would mean there were two methods with the same erasure that didn't override each other, which would be an error in source. In this case, invokespecial appears to be a small javac optimization for when the method that needs the bridge isn't implemented in the current class [2]. ecj gets away with using invokevirtual: interface I<T> { void f(T t); }
static class A { public void f(String s) {} }
static class B extends A implements I<String> {
// bridge void f(Object) {
// javac: invokespecial A.f(String)V
// ecj: invokevirtual B.f(String)V
// }
}
static class C extends B {
public void f(String s) {}
// bridge void f(Object) {
// javac: invokevirtual C.f(String)V
// ecj: invokevirtual C.f(String)V
// }
} [1] https://github.com/cglib/cglib/blob/0258a52487421a4b33b9e9426f7b13ed6693c387/cglib/src/main/java/net/sf/cglib/proxy/BridgeMethodResolver.java#L95-100 |
I added the bridge detection in df8bb42 -- the reason is described in the commit log: I added the change primarily to fix an interceptor bug in Guice: google/guice@d4ec8b0. I figure the pull requests are an improvement no matter what because previously the code just didn't work... but it'd be even better if we can validate that the PR doesn't re-introduce the issues the bridge detection was intended to fix. |
Just for the record, javac adds so-called visibility bridges to public types that extend non-public types with a public method. These bridges are not real bridge methods but only work to enable reflection. http://bugs.java.com/view_bug.do?bug_id=6342411 javac only compiles such bridges to use invokespecial (as they call their super method). Ideally, cglib should not even override bridge methods but only such visibility bridges. |
cglib needs to override all invokespecial bridges, and javac doesn't use invokespecial exclusively for accessibility bridges.
I verified that the guice tests still pass with the changes in #90. |
You are right. I think however this invokespecial is breaking the spec. If What I mean by that is that I do not think we need to preserve the invokespecial for bridges but I still disagree that overriding all bridges is a good default behavior. Considering the following enhancement: class Foo<T> {
void m(T t) { }
}
class Bar extends Foo<String> {
@Override
public void m(String s) { }
} I would consider the following outcome unexpected: Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Bar.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Intercepted");
return methodProxy.invokeSuper(o, objects);
}
});
System.out.println("Intercepted once");
Bar bar = (Bar) enhancer.create();
bar.m("");
System.out.println("Intercepted twice");
Foo<String> foo = bar;
foo.m(""); Of course, it is always possible to exclude any methods with the bridge modifier which does however also exclude visibility bridges what I would neither expect cglib to do. This said, the type casting is required and this PR is definetly an improvement. I do however suggest merging @cushon's fix. |
Just FYI, it is kind of a bug that was introduced to avoid another bug; have a look at http://mail.openjdk.java.net/pipermail/compiler-dev/2016-October/010369.html if you are interested. In conclusion, it is not necessary to retain invokespecial as cglib mock classes are final anyways. Is everybody good with merging #90 and closing this PR (thanks @GregBowyer for your patch, too, if nobody had raced you to a fix, your suggestion would surely have made it after a refactor to cover the actual bridge erasure). |
+1 merging #90. |
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.
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 classSuperclass
.This bridge has one of the following forms in the wild (it seems to be arbitrary as to where javac places the
checkcast
).Now, with the way cglib currently handles bridges that contain
invokespecial
we wind up basically invalidating this statement inEnhancer.java
If we generate a proxy for this, and fail to correctly implement the
Object aMethod(Object)
bridge we get aVerifyError
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 goObject
→<T>
, and in the presence of such bridges emit a pureinvokespecial
call rather than having the proxy patch up the bridge withinvokevirtual
.This change is