-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Problem with abstract class and assisted inject after migration to Java 8 #904
Comments
Java8 does change a few things, but the error & diff you pasted don't seem to have anything to do with java8. Can you point to |
[1] https://gerrit.googlesource.com/gerrit/+/master/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java |
This indeed looks like a problem with the way assistedinject scans for factory methods & the way java8 introduces new synthetic/bridge methods. I have an idea of what's wrong and will write up a testcase + fix. Thanks for reporting it! |
Hi, I have been banging my head against this problem this week. Hopefully the following will aid you in your testing. Any updates on a fix will be greatly appreciated, as my shop can't wait to upgrade to Java 8. Kind regards. package java8;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.google.inject.assistedinject.FactoryModuleBuilder;
public class InyeccionAsistida {
public interface AbstractClass {}
public interface AbstractConstructionParam {}
public interface AbstractFactory <T extends AbstractConstructionParam> {
AbstractClass create( T param );
}
public static class ConcreteClass implements AbstractClass {
private ConcreteConstructionParam mValue;
@AssistedInject
ConcreteClass( @Assisted ConcreteConstructionParam value ) {
mValue = value;
}
@Override
public String toString() {
return mValue.toString();
}
}
public static class ConcreteConstructionParam implements AbstractConstructionParam {}
public static interface ConcreteFactory extends AbstractFactory<ConcreteConstructionParam> {
ConcreteClass create( ConcreteConstructionParam param );
}
public static void main( String[] args ) {
Module module = new AbstractModule() {
@Override
protected void configure() {
Module factoryModule = new FactoryModuleBuilder()
.implement( AbstractClass.class, ConcreteClass.class )
.build( ConcreteFactory.class );
install( factoryModule );
}
};
Injector injector = Guice.createInjector( module );
ConcreteFactory concreteFactory = injector.getInstance( ConcreteFactory.class );
System.out.println( concreteFactory.create( new ConcreteConstructionParam() ) );
AbstractFactory<AbstractConstructionParam> abstractFactory = cast( concreteFactory );
System.out.println( abstractFactory.create( new ConcreteConstructionParam() ) );
}
@SuppressWarnings("unchecked")
private static <T extends AbstractConstructionParam> AbstractFactory<T> cast( ConcreteFactory concreteFactory ) {
return (AbstractFactory<T>) concreteFactory;
}
} |
scanning did not ignore synthetic methods created by java8, leading to errors when the factory interface extended from a superinterface that had generics. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=86628771
Fix will be merged soon, in the latest pull request. Sorry for the troubles. |
Thanks for the pointers to your commit. Unfortunately the fix is not quite there yet. I've attempted to modify your unit test to demonstrate the problem: diff --git a/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java b/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
index f58352b..26e6f21 100644
--- a/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
+++ b/extensions/assistedinject/test/com/google/inject/assistedinject/FactoryProvider2Test.java
@@ -29,6 +29,7 @@ import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
+import com.google.inject.assistedinject.FactoryProvider2Test.ConcreteAssistedWithOverride.Factory;
import com.google.inject.assistedinject.FactoryProvider2Test.Equals.ComparisonMethod;
import com.google.inject.assistedinject.FactoryProvider2Test.Equals.Impl;
import com.google.inject.matcher.Matchers;
@@ -1133,8 +1134,8 @@ public class FactoryProvider2Test extends TestCase {
}
static abstract class AbstractAssisted {
- interface Factory<T extends AbstractAssisted> {
- T create(String string);
+ interface Factory<T extends AbstractAssisted,P extends CharSequence> {
+ T create(P string);
}
}
@@ -1145,26 +1146,33 @@ public class FactoryProvider2Test extends TestCase {
static class ConcreteAssistedWithOverride extends AbstractAssisted {
@Inject ConcreteAssistedWithOverride(@SuppressWarnings("unused") @Assisted String string) {}
- interface Factory extends AbstractAssisted.Factory<ConcreteAssistedWithOverride> {
+ interface Factory extends AbstractAssisted.Factory<ConcreteAssistedWithOverride,String> {
@Override ConcreteAssistedWithOverride create(String string);
}
}
static class ConcreteAssistedWithoutOverride extends AbstractAssisted {
@Inject ConcreteAssistedWithoutOverride(@SuppressWarnings("unused") @Assisted String string) {}
- interface Factory extends AbstractAssisted.Factory<ConcreteAssistedWithoutOverride> {}
+ interface Factory extends AbstractAssisted.Factory<ConcreteAssistedWithoutOverride,String> {}
}
// See https://github.com/google/guice/issues/904
public void testIgnoresSyntheticFactoryMethods() {
// Validate the injector can be successfully created.
- Guice.createInjector(new AbstractModule() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
install(new FactoryModuleBuilder().build(ConcreteAssistedWithOverride.Factory.class));
install(new FactoryModuleBuilder().build(ConcreteAssistedWithoutOverride.Factory.class));
install(new FactoryModuleBuilder().build(
- new TypeLiteral<AbstractAssisted.Factory<ConcreteAssisted>>() {}));
+ new TypeLiteral<AbstractAssisted.Factory<ConcreteAssisted,String>>() {}));
}
});
+
+ Factory concreteFactory = injector.getInstance( ConcreteAssistedWithOverride.Factory.class );
+ concreteFactory.create( "" );
+
+ AbstractAssisted.Factory abstractFactory = concreteFactory;
+
+ abstractFactory.create( "NPE" );
}
} If you run the modified unit test, you get this result:
As you probably already know, this is not a problem with the Eclipse compiler, since it doesn't insert the additional method to the interface. BTW, if you run the code I posted before, you get the same error:
I hope you find this useful, and thanks for looking into this. |
Thanks for the expanded test, I'll give this another shot. |
I hacked a crude fix for In other words, we are eagerly awaiting the proper fix. Thanks again. |
I have a hack in the works. The JDK unfortunately gives no good way to do this. The problem is we need to identify and call default methods with the default (not proxied) implementation. The MethodHandles class is supposed to let you, but it doesn't let you override visibility restrictions. Supposedly it's possible to use reflection to hack up MethodHandles to let you do it, but I couldn't get it working reliably, and it's also not safe to not use the public API. So regardless, we need a backup way of doing it... I've got a backup, but it isn't foolproof, so I'm going to take another stab at MethodHandles. |
default methods for subclasses when they override generic methods with the more specific type. We use a two-tiered approach to fixing: (1) try to use MethodHandles + unreflectSpecial, which lets us call default method implementations directly, and if that doesn't work then (2) try to map default methods to compatible method signatures that could be the overrides of the method. (1) may not always work because we're using a private API [new Lookup(clazz, int)], but we need to do that in order to non-public classes. (2) may not always work because it's possible to have more than one compatible method signature. In the unlikely case that both (1) & (2) fail, we give an error message. Also: we must validate the default method's return type for visibility vs the factory type's visibility too. This ends up with two possible differences caused by java8: a) If the Lookup cxtor can't be used (different JDK, version skew, etc..) and there's more than one compatible method signature: we fail. b) If the default method's return type isn't public but the factory is public: we fail. For reference, javac8 generates a default method in the following scenario: interface Parent<I extends CharSequence, O extends Number> { O create(I input); } interface Child<String, Integer> { Integer create(String input); } Child has a generated default method of: Number create(CharSequence input); ... so, for example, failure could be newly triggered if 'Number' was package-private but Child was public, or if the reflection APIs didn't work and Child also had a 'Integer create(StringBuilder input);' method. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=87097207
FYI, latest push to #905 includes a fix for the NPE too. Could you give it a shot and let me know if it works for you? |
I tested my code and didn't run into any more issues. Thanks for the outstanding work! |
Thanks for the fix. Any ETA for new release that contains it? |
Fixes a bug [1] which prevented Gerrit startup under Java 8. [1] google/guice#904 Change-Id: Ie8be80351c06cef77e892725cc7a03663c4c4e02
Fixes a bug [1] which prevented Gerrit startup under Java 8. [1] google/guice#904 Change-Id: Ie8be80351c06cef77e892725cc7a03663c4c4e02
…ontinue using reflection to access the private constructor for MethodHandles.Lookups. See #904 for more information on why we need that particular bit of reflection. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=238113953
Fixes google#1321. The disabled test is referencing google#904 that was reported by me for Gerrit project. However, with this change, Gerrit works as expected.
scanning did not ignore synthetic methods created by java8, leading to errors when the factory interface extended from a superinterface that had generics. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=86628771
default methods for subclasses when they override generic methods with the more specific type. We use a two-tiered approach to fixing: (1) try to use MethodHandles + unreflectSpecial, which lets us call default method implementations directly, and if that doesn't work then (2) try to map default methods to compatible method signatures that could be the overrides of the method. (1) may not always work because we're using a private API [new Lookup(clazz, int)], but we need to do that in order to non-public classes. (2) may not always work because it's possible to have more than one compatible method signature. In the unlikely case that both (1) & (2) fail, we give an error message. Also: we must validate the default method's return type for visibility vs the factory type's visibility too. This ends up with two possible differences caused by java8: a) If the Lookup cxtor can't be used (different JDK, version skew, etc..) and there's more than one compatible method signature: we fail. b) If the default method's return type isn't public but the factory is public: we fail. For reference, javac8 generates a default method in the following scenario: interface Parent<I extends CharSequence, O extends Number> { O create(I input); } interface Child<String, Integer> { Integer create(String input); } Child has a generated default method of: Number create(CharSequence input); ... so, for example, failure could be newly triggered if 'Number' was package-private but Child was public, or if the reflection APIs didn't work and Child also had a 'Integer create(StringBuilder input);' method. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=87097207
…ontinue using reflection to access the private constructor for MethodHandles.Lookups. See google/guice#904 for more information on why we need that particular bit of reflection. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=238113953
scanning did not ignore synthetic methods created by java8, leading to errors when the factory interface extended from a superinterface that had generics. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=86628771
default methods for subclasses when they override generic methods with the more specific type. We use a two-tiered approach to fixing: (1) try to use MethodHandles + unreflectSpecial, which lets us call default method implementations directly, and if that doesn't work then (2) try to map default methods to compatible method signatures that could be the overrides of the method. (1) may not always work because we're using a private API [new Lookup(clazz, int)], but we need to do that in order to non-public classes. (2) may not always work because it's possible to have more than one compatible method signature. In the unlikely case that both (1) & (2) fail, we give an error message. Also: we must validate the default method's return type for visibility vs the factory type's visibility too. This ends up with two possible differences caused by java8: a) If the Lookup cxtor can't be used (different JDK, version skew, etc..) and there's more than one compatible method signature: we fail. b) If the default method's return type isn't public but the factory is public: we fail. For reference, javac8 generates a default method in the following scenario: interface Parent<I extends CharSequence, O extends Number> { O create(I input); } interface Child<String, Integer> { Integer create(String input); } Child has a generated default method of: Number create(CharSequence input); ... so, for example, failure could be newly triggered if 'Number' was package-private but Child was public, or if the reflection APIs didn't work and Child also had a 'Integer create(StringBuilder input);' method. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=87097207
…ontinue using reflection to access the private constructor for MethodHandles.Lookups. See google/guice#904 for more information on why we need that particular bit of reflection. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=238113953
With migration change [1] to Java 8, Gerrit Code Review doesn't start any more: [2]. This diff fixed this: [3]. All is fine with the code on Java 7. Is this expected?
[1] https://gerrit-review.googlesource.com/57852
[2] http://paste.openstack.org/show/176620
[3] https://gerrit-review.googlesource.com/64820
The text was updated successfully, but these errors were encountered: