Fix JS varargs when null array is passed.
There is no way to express this situation in JS, the arguments object
is always non null. Hence, we insert a precondition check when calling
a JS varargs method with null as the varargs parameter when necessary.
Change-Id: If63cb06a36597f4fa5c6291de36ce82bd5ae70c1
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java b/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java
index 47042ba..46bd8f4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java
@@ -24,6 +24,7 @@
public static final String ASYNC_FRAGMENT_LOADER_ON_LOAD = "AsyncFragmentLoader.onLoad";
public static final String ASYNC_FRAGMENT_LOADER_RUN_ASYNC = "AsyncFragmentLoader.runAsync";
+ public static final String ARRAY_ENSURE_NOT_NULL = "Array.ensureNotNull";
public static final String ARRAY_GET_CLASS_LITERAL_FOR_ARRAY = "Array.getClassLiteralForArray";
public static final String ARRAY_INITIALIZE_UNIDIMENSIONAL_ARRAY = "Array.initUnidimensionalArray";
public static final String ARRAY_INITIALIZE_MULTIDIMENSIONAL_ARRAY = "Array.initMultidimensionalArray";
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java
index 610af99..0938544 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java
@@ -43,6 +43,7 @@
import com.google.gwt.dev.jjs.ast.JUnaryOperator;
import com.google.gwt.dev.jjs.ast.JVariableRef;
import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.RuntimeConstants;
import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import java.util.Collections;
@@ -289,6 +290,9 @@
varargsParameter = Iterables.getLast(x.getParams());
varargsIndex = x.getParams().size() - 1;
+ // JsVarargs parameter can be assumend not null in the implementing method
+ varargsParameter.setType(varargsParameter.getType().strengthenToNonNull());
+
argumentsCopyVariable = null;
switch (needsVarargsProcessing(x)) {
case GENERAL_ACCESS:
@@ -360,6 +364,7 @@
SourceInfo sourceInfo = varargsParameter.getSourceInfo();
JBlock preamble = new JBlock(sourceInfo);
+ JArrayType varargsArrayType = (JArrayType) varargsParameter.getType().getUnderlyingType();
// (1) varargs_ = new VarArgsType[varargs.length - varArgsParameterIndex]
JExpression lengthMinusVarargsIndex = varargsIndex == 0
@@ -368,10 +373,9 @@
new JArrayLength(sourceInfo, varargsParameter.createRef(sourceInfo)),
new JIntLiteral(sourceInfo, varargsIndex));
JNewArray arrayVariable = JNewArray.createArrayWithDimensionExpressions(sourceInfo,
- (JArrayType) varargsParameter.getType(),
- Collections.singletonList(lengthMinusVarargsIndex));
+ varargsArrayType, Collections.singletonList(lengthMinusVarargsIndex));
arrayVariable.getLeafTypeClassLiteral().setField(
- program.getClassLiteralField(((JArrayType) varargsParameter.getType()).getLeafType()));
+ program.getClassLiteralField(varargsArrayType.getLeafType()));
preamble.addStmt(new JDeclarationStatement(
sourceInfo, argumentsCopyVariable.createRef(sourceInfo), arrayVariable));
@@ -385,7 +389,7 @@
JBlock block = new JBlock(sourceInfo);
block.addStmt(new JBinaryOperation(
sourceInfo,
- ((JArrayType) varargsParameter.getType()).getElementType(),
+ varargsArrayType.getElementType(),
JBinaryOperator.ASG,
new JArrayRef(sourceInfo, replacer.replace(varargsParameter.createRef(sourceInfo)),
index.createRef(sourceInfo)),
@@ -437,10 +441,20 @@
JNewArray varargArray = (JNewArray) varargArgument;
if (varargArray.getInitializers() != null) {
x.setArg(varargIndex, ArrayNormalizer.getInitializerArray(varargArray));
+ madeChanges();
return;
}
}
- // Passed as an array to varargs method.
+
+ SourceInfo varargsArgumentsourceInfo = varargArgument.getSourceInfo();
+ if (varargArgument.getType().canBeNull()) {
+ // varargsArgument => Array.ensureNotNull(varargsArgument)
+ x.setArg(varargIndex, new JMethodCall(varargsArgumentsourceInfo, null,
+ program.getIndexedMethod(RuntimeConstants.ARRAY_ENSURE_NOT_NULL), varargArgument));
+ }
+
+ // Passed as an array to varargs method will result in an apply call, in which case hoist the
+ // qualifier to make sure it is only evaluated once.
JExpression instance = x.getInstance();
if (x.getTarget().needsDynamicDispatch() && !x.isStaticDispatchOnly()
&& instance != null && !(instance instanceof JVariableRef)) {
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
index 1af28b4..c16a522 100644
--- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
+++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
@@ -16,6 +16,7 @@
package com.google.gwt.lang;
import static javaemul.internal.InternalPreconditions.checkArrayType;
+import static javaemul.internal.InternalPreconditions.checkNotNull;
import com.google.gwt.core.client.JavaScriptObject;
@@ -322,6 +323,10 @@
&& elementTypeCategory <= TYPE_PRIMITIVE_BOOLEAN;
};
+ public static Object ensureNotNull(Object array) {
+ return checkNotNull(array);
+ }
+
private Array() {
}
}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
index c11946f..5bb4166 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -73,6 +73,7 @@
" int elementTypeId, int elementTypeCategory, Object array) { }",
" static <T> Class<T> getClassLiteralForArray() { return null; }",
" public static boolean isJavaArray(Object o) { return false; }",
+ " static Object ensureNotNull(Object o) { return null; }",
"}"
);
}
diff --git a/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java b/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java
index 82ce8b0..9ab1c19 100644
--- a/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java
+++ b/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java
@@ -20,6 +20,7 @@
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.junit.client.GWTTestCase;
+import javaemul.internal.annotations.DoNotInline;
import jsinterop.annotations.JsFunction;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsPackage;
@@ -54,28 +55,50 @@
}-*/;
@JsMethod
- public static native int varargsMethod1(Object... varargs) /*-{
+ @DoNotInline
+ public static native int varargsLengthThruArguments(Object... varargs) /*-{
return arguments.length;
}-*/;
@JsMethod
- public static int varargsMethod2(Object... varargs) {
+ @DoNotInline
+ public static int varargsLength(Object... varargs) {
+ return varargs.length;
+ }
+
+ @JsMethod
+ @DoNotInline
+ public static int stringVarargsLength(String... varargs) {
+ return varargs.length;
+ }
+
+ @JsMethod
+ @DoNotInline
+ public static int stringVarargsLengthV2(int i, String... varargs) {
return varargs.length;
}
@JsMethod(namespace = JsPackage.GLOBAL)
- public static Object varargsMethod3(int slot, Object... varargs) {
+ @DoNotInline
+ public static Object getVarargsSlot(int slot, Object... varargs) {
return varargs[slot];
}
@JsMethod(namespace = JsPackage.GLOBAL)
- public static Object[] varargsMethod4(int slot, Object... varargs) {
+ @DoNotInline
+ public static Object[] clrearVarargsSlot(int slot, Object... varargs) {
varargs[slot] = null;
return varargs;
}
- private static native Object callVarargsMethod3FromJSNI() /*-{
- return $global.varargsMethod3(2, "1", "2", "3", "4");
+ @JsMethod(namespace = JsPackage.GLOBAL)
+ @DoNotInline
+ public static Class<?> getVarargsArrayClass(String... varargs) {
+ return varargs.getClass();
+ }
+
+ private static native Object callGetVarargsSlotUsingJsName() /*-{
+ return $global.getVarargsSlot(2, "1", "2", "3", "4");
}-*/;
@JsType(isNative = true, namespace = GLOBAL, name = "Object")
@@ -108,7 +131,7 @@
static class SubSubclassNativeWithVarargsConstructor
extends SubclassNativeWithVarargsConstructor {
SubSubclassNativeWithVarargsConstructor() {
- super(0, null);
+ super(0, new Object[0]);
}
Object varargsMethod(int i, Object... args) {
@@ -121,15 +144,60 @@
}
public void testVarargsCall_regularMethods() {
- assertEquals(3, varargsMethod1("A", "B", "C"));
- assertEquals(4, varargsMethod2("A", "B", "C", "D"));
- assertEquals(2, varargsMethod1(new NativeJsType[]{null, null}));
- assertEquals(5, varargsMethod2(new NativeJsType[]{null, null, null, null, null}));
- assertEquals("C", varargsMethod3(2, "A", "B", "C", "D"));
- assertEquals("3", callVarargsMethod3FromJSNI());
- assertNull(varargsMethod4(1, "A", "B", "C")[1]);
- assertEquals("A", varargsMethod4(1, "A", "B", "C")[0]);
- assertEquals(3, varargsMethod4(1, "A", "B", "C").length);
+ assertEquals(3, varargsLengthThruArguments("A", "B", "C"));
+ assertEquals(4, varargsLength("A", "B", "C", "D"));
+ assertEquals(2, varargsLengthThruArguments(new NativeJsType[]{null, null}));
+ assertEquals(5, varargsLength(new NativeJsType[]{null, null, null, null, null}));
+ assertEquals("C", getVarargsSlot(2, "A", "B", "C", "D"));
+ assertEquals("3", callGetVarargsSlotUsingJsName());
+ assertNull(clrearVarargsSlot(1, "A", "B", "C")[1]);
+ assertEquals("A", clrearVarargsSlot(1, "A", "B", "C")[0]);
+ assertEquals(3, clrearVarargsSlot(1, "A", "B", "C").length);
+ assertSame(String[].class, getVarargsArrayClass("A", "B", "C"));
+ }
+ public void testVarargsCall_edgeCases() {
+ assertSame(String[].class, getVarargsArrayClass());
+ assertSame(String[].class, getVarargsArrayClass(new String[0]));
+ assertSame(String[].class, getVarargsArrayClass((String) null));
+ try {
+ assertSame(String[].class, getVarargsArrayClass(null));
+ fail("Should have thrown exception");
+ } catch (NullPointerException expected) {
+ }
+ try {
+ assertSame(String[].class, getVarargsArrayClass((String[]) null));
+ fail("Should have thrown exception");
+ } catch (NullPointerException expected) {
+ }
+
+ assertEquals(0, stringVarargsLength());
+ assertEquals(0, stringVarargsLength(new String[0]));
+ assertEquals(1, stringVarargsLength((String) null));
+ try {
+ assertEquals(0, stringVarargsLength(null));
+ fail("Should have thrown exception");
+ } catch (NullPointerException expected) {
+ }
+ try {
+ assertEquals(0, stringVarargsLength((String[]) null));
+ fail("Should have thrown exception");
+ } catch (NullPointerException expected) {
+ }
+
+ // Test with an additional parameter as it results in a slightly different call site.
+ assertEquals(0, stringVarargsLengthV2(0));
+ assertEquals(0, stringVarargsLengthV2(0, new String[0]));
+ assertEquals(1, stringVarargsLengthV2(0, (String) null));
+ try {
+ assertEquals(0, stringVarargsLengthV2(0, null));
+ fail("Should have thrown exception");
+ } catch (NullPointerException expected) {
+ }
+ try {
+ assertEquals(0, stringVarargsLengthV2(0, (String[]) null));
+ fail("Should have thrown exception");
+ } catch (NullPointerException expected) {
+ }
}
public void testVarargsCall_constructors() {
@@ -220,7 +288,8 @@
return obj;
}
public void testVarargsCall_sideEffectingInstance() {
- SubclassNativeWithVarargsConstructor object = new SubclassNativeWithVarargsConstructor(0, null);
+ SubclassNativeWithVarargsConstructor object =
+ new SubclassNativeWithVarargsConstructor(0, new Object[0]);
sideEffectCount = 0;
Object[] params = new Object[] { object, null };
assertSame(object, doSideEffect(object).varargsMethod(0, params));