Fix AIOOBE when compiling method references involving varargs.
The construction of lambdas for method references is very fragile
and should be cleaned up.
Bug: #9550
Bug-Link: https://github.com/gwtproject/gwt/issues/9550
Change-Id: I4e3156468cefe645421a41fe49271f51f3a23feb
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
index 688db65..eafcf3e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -1854,8 +1854,10 @@
// Comparator<T>.
// The first argument serves as the qualifier, so for example, the method dispatch looks
// like this: int compare(T a, T b) { a.compareTo(b); }
- if (!hasQualifier && !referredMethod.isStatic() && instance == null &&
- samMethod.getParams().size() == referredMethod.getParams().size() + 1) {
+ if (!hasQualifier
+ && !referredMethod.isStatic()
+ && !referredMethod.isConstructor()
+ && instance == null) {
// the instance qualifier is the first parameter in this case.
// Needs to be cast the actual type due to generics.
instance = new JCastOperation(info, typeMap.get(referredMethodBinding.declaringClass),
@@ -1890,8 +1892,14 @@
// interface Foo { m(int x, int y); } bound to reference foo(int... args)
// if varargs and incoming param is not already a var-arg, we'll need to convert
// trailing args of the target interface into an array
+ boolean isVarargArgumentSuppliedDirectlyAsAnArray =
+ referredMethodBinding.isVarargs()
+ && samBinding.parameters.length == referredMethodBinding.parameters.length
+ && samBinding.parameters[varArg]
+ .isCompatibleWith(referredMethodBinding.parameters[varArg]);
+
if (referredMethodBinding.isVarargs()
- && !samBinding.parameters[varArg].isArrayType()) {
+ && !isVarargArgumentSuppliedDirectlyAsAnArray) {
varArgInitializers = Lists.newArrayList();
}
@@ -1900,14 +1908,23 @@
JExpression paramExpr = param.makeRef(info);
// params may need to be boxed or unboxed
TypeBinding destParam = null;
- // The method declared in the functional interface might have more parameters than the
- // method referred by the method reference. In the case of an instance method without
+
+ int declarationParameterOffset =
+ declarationSamBinding.parameters.length
+ - referredMethodBinding.parameters.length;
+ // The method declared in the functional interface might have more or less parameters than
+ // the method referred by the method reference. In the case of an instance method without
// an explicit qualifier (A::m vs instance::m) the method in the functional interface will
// have an additional parameter for the instance preceding all the method parameters.
+ // So truncate the value of the index to refer to the right parameter.
+ int declarationParameterIndex = Math.max(0,
+ Math.min(
+ paramNumber
+ + declarationParameterOffset,
+ declarationSamBinding.parameters.length - 1)
+ );
TypeBinding samParameterBinding =
- declarationSamBinding.parameters[paramNumber
- + (declarationSamBinding.parameters.length
- - referredMethodBinding.parameters.length)];
+ declarationSamBinding.parameters[declarationParameterIndex];
// if it is not the trailing param or varargs, or interface method is already varargs
if (varArgInitializers == null
|| !referredMethodBinding.isVarargs()
@@ -4408,11 +4425,11 @@
}
private boolean hasQualifier(ReferenceExpression x) {
- return (Boolean) accessPrivateField(JdtPrivateHacks.haveReceiverField, x);
+ return !x.isTypeAccess();
}
private TypeBinding getCollectionElementTypeBinding(ForeachStatement x) {
- return (TypeBinding) accessPrivateField(JdtPrivateHacks.collectionElementTypeField, x);
+ return (TypeBinding) accessPrivateField(JdtPrivateHacks.collectionElementTypeField, x);
}
private Object accessPrivateField(Field field, ASTNode astNode) {
@@ -4428,10 +4445,6 @@
* Reflective access to {@link ForeachStatement#collectionElementType}.
*/
private static final Field collectionElementTypeField;
- /**
- * Reflective access to {@link ReferenceExpression#haveReceiver}.
- */
- private static final Field haveReceiverField;
static {
try {
@@ -4443,14 +4456,6 @@
"Unexpectedly unable to access ForeachStatement.collectionElementType via reflection",
e);
}
-
- try {
- haveReceiverField = ReferenceExpression.class.getDeclaredField("haveReceiver");
- haveReceiverField.setAccessible(true);
- } catch (Exception e) {
- throw new RuntimeException(
- "Unexpectedly unable to access ReferenceExpression.haveReceiver via reflection", e);
- }
}
}
}
diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
index 782a42c..e9c7aec 100644
--- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -1453,6 +1453,26 @@
V apply(T t, U u);
}
+ @FunctionalInterface
+ interface MyFunction3<T, U, V, W> {
+ W apply(T t, U u, V v);
+ }
+
+ @FunctionalInterface
+ interface IntFunction1<U> {
+ U apply(int t);
+ }
+
+ @FunctionalInterface
+ interface IntFunction2<V> {
+ V apply(int t, int u);
+ }
+
+ @FunctionalInterface
+ interface IntFunction3<W> {
+ W apply(int t, int u, int v);
+ }
+
public void testMethodReference_implementedInSuperclass() {
MyFunction1<StringBuilder, String> toString = StringBuilder::toString;
assertEquals("Hello", toString.apply(new StringBuilder("Hello")));
@@ -1465,6 +1485,145 @@
new Some<String>("Hell", concat), "Hell", "o", concat);
}
+ static String concat(String... strs) {
+ String result = "";
+ for (String s : strs) {
+ result += s;
+ }
+ return result;
+ }
+
+ static String anotherConcat(String s1, String s2, String... strs) {
+ String result = s1 + s2;
+ for (String s : strs) {
+ result += s;
+ }
+ return result;
+ }
+
+ public String instanceConcat(String... strs) {
+ String result = "";
+ for (String s : strs) {
+ result += s;
+ }
+ return result;
+ }
+
+ public String anotherInstanceConcat(String s1, String... strs) {
+ String result = s1;
+ for (String s : strs) {
+ result += s;
+ }
+ return result;
+ }
+
+ private static class ClassWithVarArgsConstructor {
+ private class Inner {
+ private int sum;
+ Inner(int i, Integer... nums) {
+ this.sum = ClassWithVarArgsConstructor.this.sum + i;
+ for (Integer n: nums) {
+ sum += n;
+ }
+ }
+ }
+
+ private int sum;
+ ClassWithVarArgsConstructor(int i, Integer... nums) {
+ sum = i;
+ for (Integer n: nums) {
+ sum += n;
+ }
+ }
+
+ private MyFunction1<Integer, Inner> createInner1Param() {
+ return (MyFunction1<Integer, Inner>) Inner::new;
+ }
+
+ private MyFunction2<Integer, Integer, Inner> createInner2Param() {
+ return (MyFunction2<Integer, Integer, Inner>) Inner::new;
+ }
+
+ private MyFunction3<Integer, Integer, Integer, Inner> createInner3Param() {
+ return (MyFunction3<Integer, Integer, Integer, Inner>) Inner::new;
+ }
+
+ private MyFunction2<Integer, Integer[], Inner> createInner2ParamArray() {
+ return (MyFunction2<Integer, Integer[], Inner>) Inner::new;
+ }
+
+ private IntFunction1<Inner> createInner1IntParam() {
+ return (IntFunction1<Inner>) Inner::new;
+ }
+
+ private IntFunction2<Inner> createInner2IntParam() {
+ return (IntFunction2<Inner>) Inner::new;
+ }
+
+ private IntFunction3<Inner> createInner3IntParam() {
+ return (IntFunction3<Inner>) Inner::new;
+ }
+ }
+
+ public void testMethodReference_varargs() {
+ // More functional arguments than varargs
+ MyFunction2<String, String, String> concat = Java8Test::concat;
+ assertEquals("ab", concat.apply("a", "b"));
+
+ // Less functional arguments than varargs
+ MyFunction2<String, String, String> anotherConcat = Java8Test::anotherConcat;
+ assertEquals("ab", anotherConcat.apply("a", "b"));
+
+ MyFunction2<Java8Test, String, String> instanceConcat = Java8Test::instanceConcat;
+ assertEquals("a", instanceConcat.apply(this, "a"));
+
+ MyFunction2<Java8Test, String, String> anotherInstanceConcat = Java8Test::anotherInstanceConcat;
+ assertEquals("a", anotherInstanceConcat.apply(this, "a"));
+
+ // constructor varargs
+ MyFunction1<Integer, ClassWithVarArgsConstructor> constructor1Param =
+ ClassWithVarArgsConstructor::new;
+ assertEquals(1, constructor1Param.apply(1).sum);
+
+ MyFunction2<Integer, Integer, ClassWithVarArgsConstructor> constructor2Param =
+ ClassWithVarArgsConstructor::new;
+ assertEquals(3, constructor2Param.apply(1, 2).sum);
+
+ MyFunction3<Integer, Integer, Integer, ClassWithVarArgsConstructor> constructor3Param =
+ ClassWithVarArgsConstructor::new;
+ assertEquals(6, constructor3Param.apply(1, 2, 3).sum);
+
+ MyFunction2<Integer, Integer[], ClassWithVarArgsConstructor> constructor2ParamArray =
+ ClassWithVarArgsConstructor::new;
+ assertEquals(6, constructor2ParamArray.apply(1, new Integer[] {2, 3}).sum);
+
+ // constructor varargs + autoboxing
+ IntFunction1<ClassWithVarArgsConstructor> constructor1IntParam =
+ ClassWithVarArgsConstructor::new;
+ assertEquals(1, constructor1IntParam.apply(1).sum);
+
+ IntFunction2<ClassWithVarArgsConstructor> constructor2IntParam =
+ ClassWithVarArgsConstructor::new;
+ assertEquals(3, constructor2IntParam.apply(1, 2).sum);
+
+ IntFunction3<ClassWithVarArgsConstructor> constructor3IntParam =
+ ClassWithVarArgsConstructor::new;
+ assertEquals(6, constructor3IntParam.apply(1, 2, 3).sum);
+
+ ClassWithVarArgsConstructor outer = new ClassWithVarArgsConstructor(1);
+
+ // inner class constructor varargs
+ assertEquals(2, outer.createInner1Param().apply(1).sum);
+ assertEquals(4, outer.createInner2Param().apply(1, 2).sum);
+ assertEquals(7, outer.createInner3Param().apply(1, 2, 3).sum);
+ assertEquals(7, outer.createInner2ParamArray().apply(1, new Integer[] {2, 3}).sum);
+
+ // inner class constructor varargs + autoboxing
+ assertEquals(2, outer.createInner1IntParam().apply(1).sum);
+ assertEquals(4, outer.createInner2IntParam().apply(1, 2).sum);
+ assertEquals(7, outer.createInner3IntParam().apply(1, 2, 3).sum);
+ }
+
private static <T> void testMethodReference_genericTypeParameters(
Some<T> some, T t1, T t2, MyFunction2<T, T, T> combine) {
T t1t2 = combine.apply(t1, t2);
diff --git a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
index cafda4c..1228184 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -288,6 +288,10 @@
assertFalse(isGwtSourceLevel8());
}
+ public void testMethodReference_varargs() {
+ assertFalse(isGwtSourceLevel8());
+ }
+
public void testNativeJsOverlay_lambda() {
assertFalse(isGwtSourceLevel8());
}
@@ -327,4 +331,4 @@
private boolean isGwtSourceLevel8() {
return JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA8) >= 0;
}
-}
\ No newline at end of file
+}