Skip to content
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

Add nullable JS API parameter support #1756

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rhino/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ plugins {
id 'rhino.library-conventions'
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-metadata-jvm:2.1.0"
}

publishing {
publications {
Expand Down
2 changes: 2 additions & 0 deletions rhino/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
requires java.compiler;
requires jdk.dynalink;
requires transitive java.desktop;
requires kotlin.metadata.jvm;
requires kotlin.stdlib;
}
3 changes: 2 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/AccessorSlot.java
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,9 @@ public boolean setValue(Object value, Scriptable owner, Scriptable start) {
// XXX: cache tag since it is already calculated in
// defineProperty ?
Class<?> valueType = pTypes[pTypes.length - 1];
boolean isNullable = member.argNullability[pTypes.length - 1];
int tag = FunctionObject.getTypeTag(valueType);
Object actualArg = FunctionObject.convertArg(cx, start, value, tag);
Object actualArg = FunctionObject.convertArg(cx, start, value, tag, isNullable);

if (member.delegateTo == null) {
member.invoke(start, new Object[] {actualArg});
Expand Down
29 changes: 20 additions & 9 deletions rhino/src/main/java/org/mozilla/javascript/FunctionObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -163,20 +163,29 @@ public static int getTypeTag(Class<?> type) {
return JAVA_UNSUPPORTED_TYPE;
}

public static Object convertArg(Context cx, Scriptable scope, Object arg, int typeTag) {
public static Object convertArg(
Context cx, Scriptable scope, Object arg, int typeTag, boolean isNullable) {
switch (typeTag) {
case JAVA_STRING_TYPE:
if (arg instanceof String) return arg;
return ScriptRuntime.toString(arg);
return (arg == null && isNullable) ? null : ScriptRuntime.toString(arg);
case JAVA_INT_TYPE:
if (arg instanceof Integer) return arg;
return Integer.valueOf(ScriptRuntime.toInt32(arg));
return (arg == null && isNullable)
? null
: Integer.valueOf(ScriptRuntime.toInt32(arg));
case JAVA_BOOLEAN_TYPE:
if (arg instanceof Boolean) return arg;
return ScriptRuntime.toBoolean(arg) ? Boolean.TRUE : Boolean.FALSE;
if (arg == null && isNullable) {
return null;
} else {
return ScriptRuntime.toBoolean(arg) ? Boolean.TRUE : Boolean.FALSE;
}
case JAVA_DOUBLE_TYPE:
if (arg instanceof Double) return arg;
return Double.valueOf(ScriptRuntime.toNumber(arg));
return (arg == null && isNullable)
? null
: Double.valueOf(ScriptRuntime.toNumber(arg));
case JAVA_SCRIPTABLE_TYPE:
return ScriptRuntime.toObjectOrNull(cx, arg, scope);
case JAVA_OBJECT_TYPE:
Expand Down Expand Up @@ -321,15 +330,15 @@ void initAsConstructor(Scriptable scope, Scriptable prototype, int attributes) {

/**
* @deprecated Use {@link #getTypeTag(Class)} and {@link #convertArg(Context, Scriptable,
* Object, int)} for type conversion.
* Object, int, boolean)} for type conversion.
*/
@Deprecated
public static Object convertArg(Context cx, Scriptable scope, Object arg, Class<?> desired) {
int tag = getTypeTag(desired);
if (tag == JAVA_UNSUPPORTED_TYPE) {
throw Context.reportRuntimeErrorById("msg.cant.convert", desired.getName());
}
return convertArg(cx, scope, arg, tag);
return convertArg(cx, scope, arg, tag, false);
}

/**
Expand Down Expand Up @@ -401,7 +410,8 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar
invokeArgs = args;
for (int i = 0; i != parmsLength; ++i) {
Object arg = args[i];
Object converted = convertArg(cx, scope, arg, typeTags[i]);
Object converted =
convertArg(cx, scope, arg, typeTags[i], member.argNullability[i]);
if (arg != converted) {
if (invokeArgs == args) {
invokeArgs = args.clone();
Expand All @@ -415,7 +425,8 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar
invokeArgs = new Object[parmsLength];
for (int i = 0; i != parmsLength; ++i) {
Object arg = (i < argsLength) ? args[i] : Undefined.instance;
invokeArgs[i] = convertArg(cx, scope, arg, typeTags[i]);
invokeArgs[i] =
convertArg(cx, scope, arg, typeTags[i], member.argNullability[i]);
}
}

Expand Down
57 changes: 56 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/MemberBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package org.mozilla.javascript;

import static kotlin.metadata.Attributes.isNullable;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
Expand All @@ -15,6 +17,13 @@
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;
import kotlin.Metadata;
import kotlin.metadata.KmClass;
import kotlin.metadata.KmFunction;
import kotlin.metadata.KmValueParameter;
import kotlin.metadata.jvm.KotlinClassMetadata;

/**
* Wrapper class for Method and Constructor instances to cache getParameterTypes() results, recover
Expand All @@ -27,6 +36,7 @@ final class MemberBox implements Serializable {

private transient Member memberObject;
transient Class<?>[] argTypes;
transient boolean[] argNullability;
transient boolean vararg;

transient Function asGetterFunction;
Expand All @@ -44,12 +54,56 @@ final class MemberBox implements Serializable {
private void init(Method method) {
this.memberObject = method;
this.argTypes = method.getParameterTypes();
this.argNullability = getParameterNullability(method);
this.vararg = method.isVarArgs();
}

private boolean[] getParameterNullability(Method method) {
boolean[] result = new boolean[method.getParameters().length];
Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class);
if (metadata != null) {
result = getParameterNullabilityFromKotlinMetadata(metadata, method.getName(), result);
}
return result;
}

private boolean[] getParameterNullability(Constructor<?> constructor) {
boolean[] result = new boolean[constructor.getParameters().length];
Metadata metadata = constructor.getDeclaringClass().getAnnotation(Metadata.class);
if (metadata != null) {
result =
getParameterNullabilityFromKotlinMetadata(
metadata, constructor.getName(), result);
}
return result;
}

private boolean[] getParameterNullabilityFromKotlinMetadata(
Metadata metadata, String methodName, boolean[] fallback) {
KotlinClassMetadata.Class kMetadata =
(KotlinClassMetadata.Class) KotlinClassMetadata.readLenient(metadata);
KmClass clazz = kMetadata.getKmClass();
Optional<KmFunction> function =
clazz.getFunctions().stream()
.filter(f -> f.getName().equals(methodName))
.findFirst();
if (function.isPresent()) {
List<KmValueParameter> params = function.get().getValueParameters();
boolean[] result = new boolean[params.size()];
int index = 0;
for (KmValueParameter parameter : params) {
result[index++] = isNullable(parameter.getType());
}
return result;
} else {
return fallback;
}
}

private void init(Constructor<?> constructor) {
this.memberObject = constructor;
this.argTypes = constructor.getParameterTypes();
this.argNullability = getParameterNullability(constructor);
this.vararg = constructor.isVarArgs();
}

Expand Down Expand Up @@ -184,7 +238,8 @@ public Object call(
thisObj,
originalArgs[0],
FunctionObject.getTypeTag(
nativeSetter.argTypes[0]))
nativeSetter.argTypes[0]),
nativeSetter.argNullability[0])
: Undefined.instance;
if (nativeSetter.delegateTo == null) {
setterThis = thisObj;
Expand Down
Loading