8133299: Nashorn Java adapters should not early bind to functions
Reviewed-by: hannesw, lagergren, sundar
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/AdaptationResult.java Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/AdaptationResult.java Sat Jan 23 11:50:24 2016 +0100
@@ -55,11 +55,17 @@
static final AdaptationResult SUCCESSFUL_RESULT = new AdaptationResult(Outcome.SUCCESS, "");
private final Outcome outcome;
+ private final RuntimeException cause;
private final String[] messageArgs;
+ AdaptationResult(final Outcome outcome, final RuntimeException cause, final String... messageArgs) {
+ this.outcome = outcome;
+ this.cause = cause;
+ this.messageArgs = messageArgs;
+ }
+
AdaptationResult(final Outcome outcome, final String... messageArgs) {
- this.outcome = outcome;
- this.messageArgs = messageArgs;
+ this(outcome, null, messageArgs);
}
Outcome getOutcome() {
@@ -67,6 +73,6 @@
}
ECMAException typeError() {
- return ECMAErrors.typeError("extend." + outcome, messageArgs);
+ return ECMAErrors.typeError(cause, "extend." + outcome, messageArgs);
}
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/Bootstrap.java Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/Bootstrap.java Sat Jan 23 11:50:24 2016 +0100
@@ -193,7 +193,7 @@
* Create a call site and link it for Nashorn. This version of the method conforms to the invokedynamic bootstrap
* method expected signature and is referenced from Nashorn generated bytecode as the bootstrap method for all
* invokedynamic instructions.
- * @param lookup MethodHandle lookup. Ignored as Nashorn only uses public lookup.
+ * @param lookup MethodHandle lookup.
* @param opDesc Dynalink dynamic operation descriptor.
* @param type Method type.
* @param flags flags for call type, trace/profile etc.
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java Sat Jan 23 11:50:24 2016 +0100
@@ -31,19 +31,21 @@
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS;
-import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL;
import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE;
-import static jdk.internal.org.objectweb.asm.Opcodes.DUP;
-import static jdk.internal.org.objectweb.asm.Opcodes.IFNONNULL;
-import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
-import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE;
-import static jdk.internal.org.objectweb.asm.Opcodes.POP;
+import static jdk.internal.org.objectweb.asm.Opcodes.D2F;
+import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC;
+import static jdk.internal.org.objectweb.asm.Opcodes.I2B;
+import static jdk.internal.org.objectweb.asm.Opcodes.I2S;
import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
+import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup;
+import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome.ERROR_NO_ACCESSIBLE_CONSTRUCTOR;
+import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
@@ -56,9 +58,7 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Handle;
@@ -67,8 +67,7 @@
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
import jdk.nashorn.api.scripting.ScriptUtils;
-import jdk.nashorn.internal.runtime.Context;
-import jdk.nashorn.internal.runtime.JSType;
+import jdk.nashorn.internal.codegen.CompilerConstants.Call;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome;
@@ -86,8 +85,7 @@
* dispatched by name. A single JavaScript function will act as the implementation for all overloaded methods of the
* same name. When methods on an adapter instance are invoked, the functions are invoked having the ScriptObject passed
* in the instance constructor as their "this". Subsequent changes to the ScriptObject (reassignment or removal of its
- * functions) are not reflected in the adapter instance; the method implementations are bound to functions at
- * constructor invocation time.
+ * functions) will be reflected in the adapter instance as it is live dispatching to its members on every method invocation.
* {@code java.lang.Object} methods {@code equals}, {@code hashCode}, and {@code toString} can also be overridden. The
* only restriction is that since every JavaScript object already has a {@code toString} function through the
* {@code Object.prototype}, the {@code toString} in the adapter is only overridden if the passed ScriptObject has a
@@ -104,7 +102,7 @@
* strict or not.
* </li>
* <li>
- * If the adapter being generated can have class-level overrides, constructors taking same arguments as the superclass
+ * If the adapter being generated has class-level overrides, constructors taking same arguments as the superclass
* constructors are created. These constructors simply delegate to the superclass constructor. They are simply used to
* create instances of the adapter class, with no instance-level overrides, as they don't have them. If the original
* class' constructor was variable arity, the adapter constructor will also be variable arity. Protected constructors
@@ -115,7 +113,7 @@
* For adapter methods that return values, all the JavaScript-to-Java conversions supported by Nashorn will be in effect
* to coerce the JavaScript function return value to the expected Java return type.
* </p><p>
- * Since we are adding a trailing argument to the generated constructors in the adapter class, they will never be
+ * Since we are adding a trailing argument to the generated constructors in the adapter class with instance-level overrides, they will never be
* declared as variable arity, even if the original constructor in the superclass was declared as variable arity. The
* reason we are passing the additional argument at the end of the argument list instead at the front is that the
* source-level script expression <code>new X(a, b) { ... }</code> (which is a proprietary syntax extension Nashorn uses
@@ -137,51 +135,67 @@
* implemented securely.
*/
final class JavaAdapterBytecodeGenerator {
- private static final Type SCRIPTUTILS_TYPE = Type.getType(ScriptUtils.class);
- private static final Type OBJECT_TYPE = Type.getType(Object.class);
- private static final Type CLASS_TYPE = Type.getType(Class.class);
-
- static final String OBJECT_TYPE_NAME = OBJECT_TYPE.getInternalName();
- static final String SCRIPTUTILS_TYPE_NAME = SCRIPTUTILS_TYPE.getInternalName();
-
- static final String INIT = "<init>";
+ // Field names in adapters
+ private static final String GLOBAL_FIELD_NAME = "global";
+ private static final String DELEGATE_FIELD_NAME = "delegate";
+ private static final String IS_FUNCTION_FIELD_NAME = "isFunction";
+ private static final String CALL_THIS_FIELD_NAME = "callThis";
- static final String GLOBAL_FIELD_NAME = "global";
+ // Initializer names
+ private static final String INIT = "<init>";
+ private static final String CLASS_INIT = "<clinit>";
- // "global" is declared as Object instead of Global - avoid static references to internal Nashorn classes when possible.
- static final String GLOBAL_TYPE_DESCRIPTOR = OBJECT_TYPE.getDescriptor();
-
- static final String SET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE);
- static final String VOID_NOARG_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE);
-
+ // Types often used in generated bytecode
+ private static final Type OBJECT_TYPE = Type.getType(Object.class);
private static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class);
private static final Type SCRIPT_FUNCTION_TYPE = Type.getType(ScriptFunction.class);
- private static final Type STRING_TYPE = Type.getType(String.class);
- private static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class);
- private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class);
- private static final String GET_HANDLE_OBJECT_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE,
- OBJECT_TYPE, STRING_TYPE, METHOD_TYPE_TYPE);
- private static final String GET_HANDLE_FUNCTION_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE,
- SCRIPT_FUNCTION_TYPE, METHOD_TYPE_TYPE);
- private static final String GET_CLASS_INITIALIZER_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE);
- private static final Type RUNTIME_EXCEPTION_TYPE = Type.getType(RuntimeException.class);
- private static final Type THROWABLE_TYPE = Type.getType(Throwable.class);
- private static final Type UNSUPPORTED_OPERATION_TYPE = Type.getType(UnsupportedOperationException.class);
+
+ // JavaAdapterServices methods used in generated bytecode
+ private static final Call CHECK_FUNCTION = lookupServiceMethod("checkFunction", ScriptFunction.class, Object.class, String.class);
+ private static final Call EXPORT_RETURN_VALUE = lookupServiceMethod("exportReturnValue", Object.class, Object.class);
+ private static final Call GET_CALL_THIS = lookupServiceMethod("getCallThis", Object.class, ScriptFunction.class, Object.class);
+ private static final Call GET_CLASS_OVERRIDES = lookupServiceMethod("getClassOverrides", ScriptObject.class);
+ private static final Call GET_NON_NULL_GLOBAL = lookupServiceMethod("getNonNullGlobal", ScriptObject.class);
+ private static final Call HAS_OWN_TO_STRING = lookupServiceMethod("hasOwnToString", boolean.class, ScriptObject.class);
+ private static final Call INVOKE_NO_PERMISSIONS = lookupServiceMethod("invokeNoPermissions", void.class, MethodHandle.class, Object.class);
+ private static final Call NOT_AN_OBJECT = lookupServiceMethod("notAnObject", void.class, Object.class);
+ private static final Call SET_GLOBAL = lookupServiceMethod("setGlobal", Runnable.class, ScriptObject.class);
+ private static final Call TO_CHAR_PRIMITIVE = lookupServiceMethod("toCharPrimitive", char.class, Object.class);
+ private static final Call UNSUPPORTED = lookupServiceMethod("unsupported", UnsupportedOperationException.class);
+ private static final Call WRAP_THROWABLE = lookupServiceMethod("wrapThrowable", RuntimeException.class, Throwable.class);
+
+ // Other methods invoked by the generated bytecode
+ private static final Call UNWRAP = staticCallNoLookup(ScriptUtils.class, "unwrap", Object.class, Object.class);
+ private static final Call CHAR_VALUE_OF = staticCallNoLookup(Character.class, "valueOf", Character.class, char.class);
+ private static final Call DOUBLE_VALUE_OF = staticCallNoLookup(Double.class, "valueOf", Double.class, double.class);
+ private static final Call LONG_VALUE_OF = staticCallNoLookup(Long.class, "valueOf", Long.class, long.class);
+ private static final Call RUN = interfaceCallNoLookup(Runnable.class, "run", void.class);
- private static final String SERVICES_CLASS_TYPE_NAME = Type.getInternalName(JavaAdapterServices.class);
- private static final String RUNTIME_EXCEPTION_TYPE_NAME = RUNTIME_EXCEPTION_TYPE.getInternalName();
- private static final String ERROR_TYPE_NAME = Type.getInternalName(Error.class);
- private static final String THROWABLE_TYPE_NAME = THROWABLE_TYPE.getInternalName();
- private static final String UNSUPPORTED_OPERATION_TYPE_NAME = UNSUPPORTED_OPERATION_TYPE.getInternalName();
+ // ASM handle to the bootstrap method
+ private static final Handle BOOTSTRAP_HANDLE = new Handle(H_INVOKESTATIC,
+ Type.getInternalName(JavaAdapterServices.class), "bootstrap",
+ MethodType.methodType(CallSite.class, Lookup.class, String.class,
+ MethodType.class, int.class).toMethodDescriptorString());
+
+ // ASM handle to the bootstrap method for array populator
+ private static final Handle CREATE_ARRAY_BOOTSTRAP_HANDLE = new Handle(H_INVOKESTATIC,
+ Type.getInternalName(JavaAdapterServices.class), "createArrayBootstrap",
+ MethodType.methodType(CallSite.class, Lookup.class, String.class,
+ MethodType.class).toMethodDescriptorString());
- private static final String METHOD_HANDLE_TYPE_DESCRIPTOR = METHOD_HANDLE_TYPE.getDescriptor();
- private static final String GET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE);
- private static final String GET_CLASS_METHOD_DESCRIPTOR = Type.getMethodDescriptor(CLASS_TYPE);
- private static final String EXPORT_RETURN_VALUE_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE);
- private static final String UNWRAP_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE);
- private static final String GET_CONVERTER_METHOD_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE, CLASS_TYPE);
- private static final String TO_CHAR_PRIMITIVE_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.CHAR_TYPE, OBJECT_TYPE);
- private static final String TO_STRING_METHOD_DESCRIPTOR = Type.getMethodDescriptor(STRING_TYPE, OBJECT_TYPE);
+ // Field type names used in the generated bytecode
+ private static final String SCRIPT_OBJECT_TYPE_DESCRIPTOR = SCRIPT_OBJECT_TYPE.getDescriptor();
+ private static final String OBJECT_TYPE_DESCRIPTOR = OBJECT_TYPE.getDescriptor();
+ private static final String BOOLEAN_TYPE_DESCRIPTOR = Type.BOOLEAN_TYPE.getDescriptor();
+
+ // Throwable names used in the generated bytecode
+ private static final String RUNTIME_EXCEPTION_TYPE_NAME = Type.getInternalName(RuntimeException.class);
+ private static final String ERROR_TYPE_NAME = Type.getInternalName(Error.class);
+ private static final String THROWABLE_TYPE_NAME = Type.getInternalName(Throwable.class);
+
+ // Some more frequently used method descriptors
+ private static final String GET_METHOD_PROPERTY_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE, SCRIPT_OBJECT_TYPE);
+ private static final String VOID_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE);
// Package used when the adapter can't be defined in the adaptee's package (either because it's sealed, or because
// it's a java.* package.
@@ -191,11 +205,13 @@
private static final String JAVA_PACKAGE_PREFIX = "java/";
private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 255;
- private static final String CLASS_INIT = "<clinit>";
-
// Method name prefix for invoking super-methods
static final String SUPER_PREFIX = "super$";
+ // Method name and type for the no-privilege finalizer delegate
+ private static final String FINALIZER_DELEGATE_NAME = "$$nashornFinalizerDelegate";
+ private static final String FINALIZER_DELEGATE_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE);
+
/**
* Collection of methods we never override: Object.clone(), Object.finalize().
*/
@@ -215,30 +231,13 @@
private final String superClassName;
// Binary name of the generated class.
private final String generatedClassName;
- private final Set<String> usedFieldNames = new HashSet<>();
private final Set<String> abstractMethodNames = new HashSet<>();
private final String samName;
private final Set<MethodInfo> finalMethods = new HashSet<>(EXCLUDED);
private final Set<MethodInfo> methodInfos = new HashSet<>();
- private boolean autoConvertibleFromFunction = false;
+ private final boolean autoConvertibleFromFunction;
private boolean hasExplicitFinalizer = false;
- /**
- * Names of static fields holding type converter method handles for return value conversion. We are emitting code
- * for invoking these explicitly after the delegate handle is invoked, instead of doing an asType or
- * filterReturnValue on the delegate handle, as that would create a new converter handle wrapping the function's
- * handle for every instance of the adapter, causing the handle.invokeExact() call sites to become megamorphic.
- */
- private final Map<Class<?>, String> converterFields = new LinkedHashMap<>();
-
- /**
- * Subset of possible return types for all methods; namely, all possible return types of the SAM methods (we
- * identify SAM types by having all of their abstract methods share a single name, so there can be multiple
- * overloads with multiple return types. We use this set when emitting the constructor taking a ScriptFunction (the
- * SAM initializer) to avoid populating converter fields that will never be used by SAM methods.
- */
- private final Set<Class<?>> samReturnTypes = new HashSet<>();
-
private final ClassWriter cw;
/**
@@ -271,17 +270,22 @@
generatedClassName = getGeneratedClassName(superClass, interfaces);
cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER, generatedClassName, null, superClassName, getInternalTypeNames(interfaces));
- generateGlobalFields();
+ generateField(GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+ generateField(DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
gatherMethods(superClass);
gatherMethods(interfaces);
- samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null;
- generateHandleFields();
- generateConverterFields();
+ if (abstractMethodNames.size() == 1) {
+ samName = abstractMethodNames.iterator().next();
+ generateField(CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
+ generateField(IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+ } else {
+ samName = null;
+ }
if(classOverride) {
generateClassInit();
}
- generateConstructors();
+ autoConvertibleFromFunction = generateConstructors();
generateMethods();
generateSuperMethods();
if (hasExplicitFinalizer) {
@@ -291,9 +295,8 @@
cw.visitEnd();
}
- private void generateGlobalFields() {
- cw.visitField(ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0), GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR, null, null).visitEnd();
- usedFieldNames.add(GLOBAL_FIELD_NAME);
+ private void generateField(final String name, final String fieldDesc) {
+ cw.visitField(ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0), name, fieldDesc, null, null).visitEnd();
}
JavaAdapterClassLoader createAdapterClassLoader() {
@@ -343,154 +346,84 @@
return interfaceNames;
}
- private void generateHandleFields() {
- final int flags = ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0);
- for (final MethodInfo mi: methodInfos) {
- cw.visitField(flags, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd();
+ private void generateClassInit() {
+ final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_STATIC, CLASS_INIT,
+ VOID_METHOD_DESCRIPTOR, null, null));
+
+ // Assign "global = Context.getGlobal()"
+ GET_NON_NULL_GLOBAL.invoke(mv);
+ mv.putstatic(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+ GET_CLASS_OVERRIDES.invoke(mv);
+ if(samName != null) {
+ // If the class is a SAM, allow having ScriptFunction passed as class overrides
+ mv.dup();
+ mv.instanceOf(SCRIPT_FUNCTION_TYPE);
+ mv.dup();
+ mv.putstatic(generatedClassName, IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+ final Label notFunction = new Label();
+ mv.ifeq(notFunction);
+ mv.dup();
+ mv.checkcast(SCRIPT_FUNCTION_TYPE);
+ emitInitCallThis(mv);
+ mv.visitLabel(notFunction);
}
+ mv.putstatic(generatedClassName, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+ endInitMethod(mv);
}
- private void generateConverterFields() {
- final int flags = ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0);
- for (final MethodInfo mi: methodInfos) {
- final Class<?> returnType = mi.type.returnType();
- // Handle primitive types, Object, and String specially
- if(!returnType.isPrimitive() && returnType != Object.class && returnType != String.class) {
- if(!converterFields.containsKey(returnType)) {
- final String name = nextName("convert");
- converterFields.put(returnType, name);
- if(mi.getName().equals(samName)) {
- samReturnTypes.add(returnType);
- }
- cw.visitField(flags, name, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd();
- }
- }
+ /**
+ * Emit bytecode for initializing the "callThis" field.
+ */
+ private void emitInitCallThis(final InstructionAdapter mv) {
+ loadField(mv, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+ GET_CALL_THIS.invoke(mv);
+ if(classOverride) {
+ mv.putstatic(generatedClassName, CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
+ } else {
+ // It is presumed ALOAD 0 was already executed
+ mv.putfield(generatedClassName, CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
}
}
- private void generateClassInit() {
- final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_STATIC, CLASS_INIT,
- Type.getMethodDescriptor(Type.VOID_TYPE), null, null));
-
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getClassOverrides", GET_CLASS_INITIALIZER_DESCRIPTOR, false);
- final Label initGlobal;
- if(samName != null) {
- // If the class is a SAM, allow having a ScriptFunction passed as class overrides
- final Label notAFunction = new Label();
- mv.dup();
- mv.instanceOf(SCRIPT_FUNCTION_TYPE);
- mv.ifeq(notAFunction);
- mv.checkcast(SCRIPT_FUNCTION_TYPE);
-
- // Assign MethodHandle fields through invoking getHandle() for a ScriptFunction, only assigning the SAM
- // method(s).
- for (final MethodInfo mi : methodInfos) {
- if(mi.getName().equals(samName)) {
- mv.dup();
- loadMethodTypeAndGetHandle(mv, mi, GET_HANDLE_FUNCTION_DESCRIPTOR);
- } else {
- mv.visitInsn(ACONST_NULL);
- }
- mv.putstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
- }
- initGlobal = new Label();
- mv.goTo(initGlobal);
- mv.visitLabel(notAFunction);
- } else {
- initGlobal = null;
- }
- // Assign MethodHandle fields through invoking getHandle() for a ScriptObject
- for (final MethodInfo mi : methodInfos) {
- mv.dup();
- mv.aconst(mi.getName());
- loadMethodTypeAndGetHandle(mv, mi, GET_HANDLE_OBJECT_DESCRIPTOR);
- mv.putstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
- }
-
- if(initGlobal != null) {
- mv.visitLabel(initGlobal);
- }
- // Assign "global = Context.getGlobal()"
- invokeGetGlobalWithNullCheck(mv);
- mv.putstatic(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
-
- generateConverterInit(mv, false);
- endInitMethod(mv);
- }
-
- private void generateConverterInit(final InstructionAdapter mv, final boolean samOnly) {
- assert !samOnly || !classOverride;
- for(final Map.Entry<Class<?>, String> converterField: converterFields.entrySet()) {
- final Class<?> returnType = converterField.getKey();
- if(!classOverride) {
- mv.visitVarInsn(ALOAD, 0);
- }
-
- if(samOnly && !samReturnTypes.contains(returnType)) {
- mv.visitInsn(ACONST_NULL);
- } else {
- mv.aconst(Type.getType(converterField.getKey()));
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getObjectConverter", GET_CONVERTER_METHOD_DESCRIPTOR, false);
- }
-
- if(classOverride) {
- mv.putstatic(generatedClassName, converterField.getValue(), METHOD_HANDLE_TYPE_DESCRIPTOR);
- } else {
- mv.putfield(generatedClassName, converterField.getValue(), METHOD_HANDLE_TYPE_DESCRIPTOR);
- }
- }
- }
-
- private static void loadMethodTypeAndGetHandle(final InstructionAdapter mv, final MethodInfo mi, final String getHandleDescriptor) {
- // NOTE: we're using generic() here because we'll be linking to the "generic" invoker version of
- // the functions anyway, so we cut down on megamorphism in the invokeExact() calls in adapter
- // bodies. Once we start linking to type-specializing invokers, this should be changed.
- mv.aconst(Type.getMethodType(mi.type.generic().toMethodDescriptorString()));
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor, false);
- }
-
- private static void invokeGetGlobalWithNullCheck(final InstructionAdapter mv) {
- invokeGetGlobal(mv);
- mv.dup();
- mv.invokevirtual(OBJECT_TYPE_NAME, "getClass", GET_CLASS_METHOD_DESCRIPTOR, false); // check against null Context
- mv.pop();
- }
-
- private void generateConstructors() throws AdaptationException {
+ private boolean generateConstructors() throws AdaptationException {
boolean gotCtor = false;
+ boolean canBeAutoConverted = false;
for (final Constructor<?> ctor: superClass.getDeclaredConstructors()) {
final int modifier = ctor.getModifiers();
if((modifier & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0 && !isCallerSensitive(ctor)) {
- generateConstructors(ctor);
+ canBeAutoConverted = generateConstructors(ctor) | canBeAutoConverted;
gotCtor = true;
}
}
if(!gotCtor) {
throw new AdaptationException(ERROR_NO_ACCESSIBLE_CONSTRUCTOR, superClass.getCanonicalName());
}
+ return canBeAutoConverted;
}
- private void generateConstructors(final Constructor<?> ctor) {
+ private boolean generateConstructors(final Constructor<?> ctor) {
if(classOverride) {
// Generate a constructor that just delegates to ctor. This is used with class-level overrides, when we want
// to create instances without further per-instance overrides.
generateDelegatingConstructor(ctor);
- } else {
- // Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the
- // beginning of its parameter list.
- generateOverridingConstructor(ctor, false);
+ return false;
+ }
+
+ // Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the
+ // beginning of its parameter list.
+ generateOverridingConstructor(ctor, false);
- if (samName != null) {
- if (!autoConvertibleFromFunction && ctor.getParameterTypes().length == 0) {
- // If the original type only has a single abstract method name, as well as a default ctor, then it can
- // be automatically converted from JS function.
- autoConvertibleFromFunction = true;
- }
- // If all our abstract methods have a single name, generate an additional constructor, one that takes a
- // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
- generateOverridingConstructor(ctor, true);
- }
+ if (samName == null) {
+ return false;
}
+ // If all our abstract methods have a single name, generate an additional constructor, one that takes a
+ // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
+ generateOverridingConstructor(ctor, true);
+ // If the original type only has a single abstract method name, as well as a default ctor, then it can
+ // be automatically converted from JS function.
+ return ctor.getParameterTypes().length == 0;
}
private void generateDelegatingConstructor(final Constructor<?> ctor) {
@@ -503,14 +436,7 @@
Type.getMethodDescriptor(originalCtorType.getReturnType(), argTypes), null, null));
mv.visitCode();
- // Invoke super constructor with the same arguments.
- mv.visitVarInsn(ALOAD, 0);
- int offset = 1; // First arg is at position 1, after this.
- for (final Type argType: argTypes) {
- mv.load(offset, argType);
- offset += argType.getSize();
- }
- mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor(), false);
+ emitSuperConstructorCall(mv, originalCtorType.getDescriptor());
endInitMethod(mv);
}
@@ -548,80 +474,54 @@
System.arraycopy(originalArgTypes, 0, newArgTypes, 0, argLen);
// All constructors must be public, even if in the superclass they were protected.
- // Existing super constructor <init>(this, args...) triggers generating <init>(this, args..., scriptObj).
+ // Existing super constructor <init>(this, args...) triggers generating <init>(this, args..., delegate).
// Any variable arity constructors become fixed-arity with explicit array arguments.
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, INIT,
Type.getMethodDescriptor(originalCtorType.getReturnType(), newArgTypes), null, null));
mv.visitCode();
- // First, invoke super constructor with original arguments. If the form of the constructor we're generating is
- // <init>(this, args..., scriptFn), then we're invoking super.<init>(this, args...).
- mv.visitVarInsn(ALOAD, 0);
- final Class<?>[] argTypes = ctor.getParameterTypes();
- int offset = 1; // First arg is at position 1, after this.
- for (int i = 0; i < argLen; ++i) {
- final Type argType = Type.getType(argTypes[i]);
- mv.load(offset, argType);
- offset += argType.getSize();
- }
- mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor(), false);
-
- // Get a descriptor to the appropriate "JavaAdapterFactory.getHandle" method.
- final String getHandleDescriptor = fromFunction ? GET_HANDLE_FUNCTION_DESCRIPTOR : GET_HANDLE_OBJECT_DESCRIPTOR;
-
- // Assign MethodHandle fields through invoking getHandle()
- for (final MethodInfo mi : methodInfos) {
- mv.visitVarInsn(ALOAD, 0);
- if (fromFunction && !mi.getName().equals(samName)) {
- // Constructors initializing from a ScriptFunction only initialize methods with the SAM name.
- // NOTE: if there's a concrete overloaded method sharing the SAM name, it'll be overridden too. This
- // is a deliberate design choice. All other method handles are initialized to null.
- mv.visitInsn(ACONST_NULL);
- } else {
- mv.visitVarInsn(ALOAD, offset);
- if(!fromFunction) {
- mv.aconst(mi.getName());
- }
- loadMethodTypeAndGetHandle(mv, mi, getHandleDescriptor);
- }
- mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
- }
+ // First, invoke super constructor with original arguments.
+ final int extraArgOffset = emitSuperConstructorCall(mv, originalCtorType.getDescriptor());
// Assign "this.global = Context.getGlobal()"
mv.visitVarInsn(ALOAD, 0);
- invokeGetGlobalWithNullCheck(mv);
- mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
+ GET_NON_NULL_GLOBAL.invoke(mv);
+ mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+ // Assign "this.delegate = delegate"
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, extraArgOffset);
+ mv.putfield(generatedClassName, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
- // Initialize converters
- generateConverterInit(mv, fromFunction);
+ if (fromFunction) {
+ // Assign "isFunction = true"
+ mv.visitVarInsn(ALOAD, 0);
+ mv.iconst(1);
+ mv.putfield(generatedClassName, IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, extraArgOffset);
+ emitInitCallThis(mv);
+ }
+
endInitMethod(mv);
if (! fromFunction) {
newArgTypes[argLen] = OBJECT_TYPE;
final InstructionAdapter mv2 = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, INIT,
Type.getMethodDescriptor(originalCtorType.getReturnType(), newArgTypes), null, null));
- generateOverridingConstructorWithObjectParam(mv2, ctor, originalCtorType.getDescriptor());
+ generateOverridingConstructorWithObjectParam(mv2, originalCtorType.getDescriptor());
}
}
// Object additional param accepting constructor - generated to handle null and undefined value
// for script adapters. This is effectively to throw TypeError on such script adapters. See
// JavaAdapterServices.getHandle as well.
- private void generateOverridingConstructorWithObjectParam(final InstructionAdapter mv, final Constructor<?> ctor, final String ctorDescriptor) {
+ private void generateOverridingConstructorWithObjectParam(final InstructionAdapter mv, final String ctorDescriptor) {
mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- final Class<?>[] argTypes = ctor.getParameterTypes();
- int offset = 1; // First arg is at position 1, after this.
- for (int i = 0; i < argTypes.length; ++i) {
- final Type argType = Type.getType(argTypes[i]);
- mv.load(offset, argType);
- offset += argType.getSize();
- }
- mv.invokespecial(superClassName, INIT, ctorDescriptor, false);
- mv.visitVarInsn(ALOAD, offset);
- mv.visitInsn(ACONST_NULL);
- mv.visitInsn(ACONST_NULL);
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_OBJECT_DESCRIPTOR, false);
+ final int extraArgOffset = emitSuperConstructorCall(mv, ctorDescriptor);
+ mv.visitVarInsn(ALOAD, extraArgOffset);
+ NOT_AN_OBJECT.invoke(mv);
endInitMethod(mv);
}
@@ -635,14 +535,6 @@
mv.visitEnd();
}
- private static void invokeGetGlobal(final InstructionAdapter mv) {
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getGlobal", GET_GLOBAL_METHOD_DESCRIPTOR, false);
- }
-
- private static void invokeSetGlobal(final InstructionAdapter mv) {
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR, false);
- }
-
/**
* Encapsulation of the information used to generate methods in the adapter classes. Basically, a wrapper around the
* reflective Method object, a cached MethodType, and the name of the field in the adapter class that will hold the
@@ -652,7 +544,6 @@
private static class MethodInfo {
private final Method method;
private final MethodType type;
- private String methodHandleFieldName;
private MethodInfo(final Class<?> clazz, final String name, final Class<?>... argTypes) throws NoSuchMethodException {
this(clazz.getDeclaredMethod(name, argTypes));
@@ -681,21 +572,6 @@
public int hashCode() {
return getName().hashCode() ^ type.hashCode();
}
-
- void setIsCanonical(final JavaAdapterBytecodeGenerator self) {
- methodHandleFieldName = self.nextName(getName());
- }
- }
-
- private String nextName(final String name) {
- int i = 0;
- String nextName = name;
- while (!usedFieldNames.add(nextName)) {
- final String ordinal = String.valueOf(i++);
- final int maxNameLen = 255 - ordinal.length();
- nextName = (name.length() <= maxNameLen ? name : name.substring(0, maxNameLen)).concat(ordinal);
- }
- return nextName;
}
private void generateMethods() {
@@ -705,18 +581,25 @@
}
/**
- * Generates a method in the adapter class that adapts a method from the original class. The generated methods will
- * inspect the method handle field assigned to them. If it is null (the JS object doesn't provide an implementation
- * for the method) then it will either invoke its version in the supertype, or if it is abstract, throw an
- * {@link UnsupportedOperationException}. Otherwise, if the method handle field's value is not null, the handle is
- * invoked using invokeExact (signature polymorphic invocation as per JLS 15.12.3). Before the invocation, the
- * current Nashorn {@link Context} is checked, and if it is different than the global used to create the adapter
- * instance, the creating global is set to be the current global. In this case, the previously current global is
- * restored after the invocation. If invokeExact results in a Throwable that is not one of the method's declared
- * exceptions, and is not an unchecked throwable, then it is wrapped into a {@link RuntimeException} and the runtime
- * exception is thrown. The method handle retrieved from the field is guaranteed to exactly match the signature of
- * the method; this is guaranteed by the way constructors of the adapter class obtain them using
- * {@link #getHandle(Object, String, MethodType, boolean)}.
+ * Generates a method in the adapter class that adapts a method from the
+ * original class. The generated method will either invoke the delegate
+ * using a CALL dynamic operation call site (if it is a SAM method and the
+ * delegate is a ScriptFunction), or invoke GET_METHOD_PROPERTY dynamic
+ * operation with the method name as the argument and then invoke the
+ * returned ScriptFunction using the CALL dynamic operation. If
+ * GET_METHOD_PROPERTY returns null or undefined (that is, the JS object
+ * doesn't provide an implementation for the method) then the method will
+ * either do a super invocation to base class, or if the method is abstract,
+ * throw an {@link UnsupportedOperationException}. Finally, if
+ * GET_METHOD_PROPERTY returns something other than a ScriptFunction, null,
+ * or undefined, a TypeError is thrown. The current Global is checked before
+ * the dynamic operations, and if it is different than the Global used to
+ * create the adapter, the creating Global is set to be the current Global.
+ * In this case, the previously current Global is restored after the
+ * invocation. If CALL results in a Throwable that is not one of the
+ * method's declared exceptions, and is not an unchecked throwable, then it
+ * is wrapped into a {@link RuntimeException} and the runtime exception is
+ * thrown.
* @param mi the method info describing the method to be generated.
*/
private void generateMethod(final MethodInfo mi) {
@@ -734,109 +617,158 @@
methodDesc, null, exceptionNames));
mv.visitCode();
- final Label handleDefined = new Label();
-
final Class<?> returnType = type.returnType();
final Type asmReturnType = Type.getType(returnType);
- // See if we have overriding method handle defined
- if(classOverride) {
- mv.getstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
- } else {
- mv.visitVarInsn(ALOAD, 0);
- mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
- }
- // stack: [handle]
- mv.visitInsn(DUP);
- mv.visitJumpInsn(IFNONNULL, handleDefined);
-
- // No handle is available, fall back to default behavior
- if(Modifier.isAbstract(method.getModifiers())) {
- // If the super method is abstract, throw an exception
- mv.anew(UNSUPPORTED_OPERATION_TYPE);
- mv.dup();
- mv.invokespecial(UNSUPPORTED_OPERATION_TYPE_NAME, INIT, VOID_NOARG_METHOD_DESCRIPTOR, false);
- mv.athrow();
- } else {
- mv.visitInsn(POP);
- // If the super method is not abstract, delegate to it.
- emitSuperCall(mv, method.getDeclaringClass(), name, methodDesc);
- }
-
- mv.visitLabel(handleDefined);
- // Load the creatingGlobal object
- if(classOverride) {
- // If class handle is defined, load the static defining global
- mv.getstatic(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
- } else {
- mv.visitVarInsn(ALOAD, 0);
- mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
- }
- // stack: [creatingGlobal, handle]
- final Label setupGlobal = new Label();
- mv.visitLabel(setupGlobal);
-
// Determine the first index for a local variable
int nextLocalVar = 1; // "this" is at 0
for(final Type t: asmArgTypes) {
nextLocalVar += t.getSize();
}
- // Set our local variable indices
- final int currentGlobalVar = nextLocalVar++;
- final int globalsDifferVar = nextLocalVar++;
+ // Set our local variable index
+ final int globalRestoringRunnableVar = nextLocalVar++;
+
+ // Load the creatingGlobal object
+ loadField(mv, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+ // stack: [creatingGlobal]
+ SET_GLOBAL.invoke(mv);
+ // stack: [runnable]
+ mv.visitVarInsn(ASTORE, globalRestoringRunnableVar);
+ // stack: []
+
+ final Label tryBlockStart = new Label();
+ mv.visitLabel(tryBlockStart);
- mv.dup();
- // stack: [creatingGlobal, creatingGlobal, handle]
+ final Label callCallee = new Label();
+ final Label defaultBehavior = new Label();
+ // If this is a SAM type...
+ if (samName != null) {
+ // ...every method will be checking whether we're initialized with a
+ // function.
+ loadField(mv, IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+ // stack: [isFunction]
+ if (name.equals(samName)) {
+ final Label notFunction = new Label();
+ mv.ifeq(notFunction);
+ // stack: []
+ // If it's a SAM method, it'll load delegate as the "callee" and
+ // "callThis" as "this" for the call if delegate is a function.
+ loadField(mv, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+ // NOTE: if we added "mv.checkcast(SCRIPT_FUNCTION_TYPE);" here
+ // we could emit the invokedynamic CALL instruction with signature
+ // (ScriptFunction, Object, ...) instead of (Object, Object, ...).
+ // We could combine this with an optimization in
+ // ScriptFunction.findCallMethod where it could link a call with a
+ // thinner guard when the call site statically guarantees that the
+ // callee argument is a ScriptFunction. Additionally, we could use
+ // a "ScriptFunction function" field in generated classes instead
+ // of a "boolean isFunction" field to avoid the checkcast.
+ loadField(mv, CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
+ // stack: [callThis, delegate]
+ mv.goTo(callCallee);
+ mv.visitLabel(notFunction);
+ } else {
+ // If it's not a SAM method, and the delegate is a function,
+ // it'll fall back to default behavior
+ mv.ifne(defaultBehavior);
+ // stack: []
+ }
+ }
- // Emit code for switching to the creating global
- // Global currentGlobal = Context.getGlobal();
- invokeGetGlobal(mv);
- mv.dup();
+ // At this point, this is either not a SAM method or the delegate is
+ // not a ScriptFunction. We need to emit a GET_METHOD_PROPERTY Nashorn
+ // invokedynamic.
- mv.visitVarInsn(ASTORE, currentGlobalVar);
- // stack: [currentGlobal, creatingGlobal, creatingGlobal, handle]
- // if(definingGlobal == currentGlobal) {
- final Label globalsDiffer = new Label();
- mv.ifacmpne(globalsDiffer);
- // stack: [creatingGlobal, handle]
- // globalsDiffer = false
- mv.pop();
- // stack: [handle]
- mv.iconst(0); // false
- // stack: [false, handle]
- final Label invokeHandle = new Label();
- mv.goTo(invokeHandle);
- mv.visitLabel(globalsDiffer);
- // } else {
- // Context.setGlobal(definingGlobal);
- // stack: [creatingGlobal, handle]
- invokeSetGlobal(mv);
- // stack: [handle]
- // globalsDiffer = true
- mv.iconst(1);
- // stack: [true, handle]
+ if(name.equals("toString")) {
+ // Since every JS Object has a toString, we only override
+ // "String toString()" it if it's explicitly specified on the object.
+ loadField(mv, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+ // stack: [delegate]
+ HAS_OWN_TO_STRING.invoke(mv);
+ // stack: [hasOwnToString]
+ mv.ifeq(defaultBehavior);
+ }
+
+ loadField(mv, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+ mv.dup();
+ // stack: [delegate, delegate]
+ final String encodedName = NameCodec.encode(name);
+ mv.visitInvokeDynamicInsn(encodedName,
+ GET_METHOD_PROPERTY_METHOD_DESCRIPTOR, BOOTSTRAP_HANDLE,
+ NashornCallSiteDescriptor.GET_METHOD_PROPERTY);
+ // stack: [callee, delegate]
+ mv.visitLdcInsn(name);
+ // stack: [name, callee, delegate]
+ CHECK_FUNCTION.invoke(mv);
+ // stack: [fnCalleeOrNull, delegate]
+ final Label hasFunction = new Label();
+ mv.dup();
+ // stack: [fnCalleeOrNull, fnCalleeOrNull, delegate]
+ mv.ifnonnull(hasFunction);
+ // stack: [null, delegate]
+ // If it's null or undefined, clear stack and fall back to default
+ // behavior.
+ mv.pop2();
+ // stack: []
- mv.visitLabel(invokeHandle);
- mv.visitVarInsn(ISTORE, globalsDifferVar);
- // stack: [handle]
+ // We can also arrive here from check for "delegate instanceof ScriptFunction"
+ // in a non-SAM method as well as from a check for "hasOwnToString(delegate)"
+ // for a toString delegate.
+ mv.visitLabel(defaultBehavior);
+ final Runnable emitFinally = ()->emitFinally(mv, globalRestoringRunnableVar);
+ final Label normalFinally = new Label();
+ if(Modifier.isAbstract(method.getModifiers())) {
+ // If the super method is abstract, throw UnsupportedOperationException
+ UNSUPPORTED.invoke(mv);
+ // NOTE: no need to invoke emitFinally.run() as we're inside the
+ // tryBlockStart/tryBlockEnd range, so throwing this exception will
+ // transfer control to the rethrow handler and the finally block in it
+ // will execute.
+ mv.athrow();
+ } else {
+ // If the super method is not abstract, delegate to it.
+ emitSuperCall(mv, method.getDeclaringClass(), name, methodDesc);
+ mv.goTo(normalFinally);
+ }
- // Load all parameters back on stack for dynamic invocation. NOTE: since we're using a generic
- // Object(Object, Object, ...) type signature for the method, we must box all arguments here.
+ mv.visitLabel(hasFunction);
+ // stack: [callee, delegate]
+ mv.swap();
+ // stack [delegate, callee]
+ mv.visitLabel(callCallee);
+
+
+ // Load all parameters back on stack for dynamic invocation.
+
int varOffset = 1;
+ // If the param list length is more than 253 slots, we can't invoke it
+ // directly as with (callee, this) it'll exceed 255.
+ final boolean isVarArgCall = getParamListLengthInSlots(asmArgTypes) > 253;
for (final Type t : asmArgTypes) {
mv.load(varOffset, t);
- boxStackTop(mv, t);
+ convertParam(mv, t, isVarArgCall);
varOffset += t.getSize();
}
+ // stack: [args..., callee, delegate]
+
+ // If the resulting parameter list length is too long...
+ if (isVarArgCall) {
+ // ... we pack the parameters (except callee and this) into an array
+ // and use Nashorn vararg invocation.
+ mv.visitInvokeDynamicInsn(NameCodec.EMPTY_NAME,
+ getArrayCreatorMethodType(type).toMethodDescriptorString(),
+ CREATE_ARRAY_BOOTSTRAP_HANDLE);
+ }
// Invoke the target method handle
- final Label tryBlockStart = new Label();
- mv.visitLabel(tryBlockStart);
- emitInvokeExact(mv, type.generic());
- convertReturnValue(mv, returnType, asmReturnType);
- final Label tryBlockEnd = new Label();
- mv.visitLabel(tryBlockEnd);
- emitFinally(mv, currentGlobalVar, globalsDifferVar);
+ mv.visitInvokeDynamicInsn(encodedName,
+ getCallMethodType(isVarArgCall, type).toMethodDescriptorString(),
+ BOOTSTRAP_HANDLE, NashornCallSiteDescriptor.CALL);
+ // stack: [returnValue]
+ convertReturnValue(mv, returnType);
+ mv.visitLabel(normalFinally);
+ emitFinally.run();
mv.areturn(asmReturnType);
// If Throwable is not declared, we need an adapter from Throwable to RuntimeException
@@ -846,10 +778,7 @@
// Add "throw new RuntimeException(Throwable)" handler for Throwable
throwableHandler = new Label();
mv.visitLabel(throwableHandler);
- mv.anew(RUNTIME_EXCEPTION_TYPE);
- mv.dupX1();
- mv.swap();
- mv.invokespecial(RUNTIME_EXCEPTION_TYPE_NAME, INIT, Type.getMethodDescriptor(Type.VOID_TYPE, THROWABLE_TYPE), false);
+ WRAP_THROWABLE.invoke(mv);
// Fall through to rethrow handler
} else {
throwableHandler = null;
@@ -857,149 +786,166 @@
final Label rethrowHandler = new Label();
mv.visitLabel(rethrowHandler);
// Rethrow handler for RuntimeException, Error, and all declared exception types
- emitFinally(mv, currentGlobalVar, globalsDifferVar);
+ emitFinally.run();
mv.athrow();
- final Label methodEnd = new Label();
- mv.visitLabel(methodEnd);
-
- mv.visitLocalVariable("currentGlobal", GLOBAL_TYPE_DESCRIPTOR, null, setupGlobal, methodEnd, currentGlobalVar);
- mv.visitLocalVariable("globalsDiffer", Type.BOOLEAN_TYPE.getDescriptor(), null, setupGlobal, methodEnd, globalsDifferVar);
if(throwableDeclared) {
- mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, THROWABLE_TYPE_NAME);
+ mv.visitTryCatchBlock(tryBlockStart, normalFinally, rethrowHandler, THROWABLE_TYPE_NAME);
assert throwableHandler == null;
} else {
- mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, RUNTIME_EXCEPTION_TYPE_NAME);
- mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, ERROR_TYPE_NAME);
+ mv.visitTryCatchBlock(tryBlockStart, normalFinally, rethrowHandler, RUNTIME_EXCEPTION_TYPE_NAME);
+ mv.visitTryCatchBlock(tryBlockStart, normalFinally, rethrowHandler, ERROR_TYPE_NAME);
for(final String excName: exceptionNames) {
- mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, excName);
+ mv.visitTryCatchBlock(tryBlockStart, normalFinally, rethrowHandler, excName);
}
- mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, throwableHandler, THROWABLE_TYPE_NAME);
+ mv.visitTryCatchBlock(tryBlockStart, normalFinally, throwableHandler, THROWABLE_TYPE_NAME);
}
endMethod(mv);
}
- private void convertReturnValue(final InstructionAdapter mv, final Class<?> returnType, final Type asmReturnType) {
- switch(asmReturnType.getSort()) {
- case Type.VOID:
- mv.pop();
- break;
- case Type.BOOLEAN:
- JSType.TO_BOOLEAN.invoke(mv);
- break;
- case Type.BYTE:
- JSType.TO_INT32.invoke(mv);
- mv.visitInsn(Opcodes.I2B);
- break;
- case Type.SHORT:
- JSType.TO_INT32.invoke(mv);
- mv.visitInsn(Opcodes.I2S);
- break;
- case Type.CHAR:
- // JSType doesn't have a TO_CHAR, so we have services supply us one.
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "toCharPrimitive", TO_CHAR_PRIMITIVE_METHOD_DESCRIPTOR, false);
- break;
- case Type.INT:
- JSType.TO_INT32.invoke(mv);
- break;
- case Type.LONG:
- JSType.TO_LONG.invoke(mv);
- break;
- case Type.FLOAT:
- JSType.TO_NUMBER.invoke(mv);
- mv.visitInsn(Opcodes.D2F);
- break;
- case Type.DOUBLE:
- JSType.TO_NUMBER.invoke(mv);
- break;
- default:
- if(asmReturnType.equals(OBJECT_TYPE)) {
- // Must hide ConsString (and potentially other internal Nashorn types) from callers
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "exportReturnValue", EXPORT_RETURN_VALUE_METHOD_DESCRIPTOR, false);
- } else if(asmReturnType.equals(STRING_TYPE)){
- // Well-known conversion to String. Not using the JSType one as we want to preserve null as null instead
- // of the string "n,u,l,l".
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "toString", TO_STRING_METHOD_DESCRIPTOR, false);
- } else {
- // Invoke converter method handle for everything else. Note that we could have just added an asType or
- // filterReturnValue to the invoked handle instead, but then every instance would have the function
- // method handle wrapped in a separate converter method handle, making handle.invokeExact() megamorphic.
- if(classOverride) {
- mv.getstatic(generatedClassName, converterFields.get(returnType), METHOD_HANDLE_TYPE_DESCRIPTOR);
- } else {
- mv.visitVarInsn(ALOAD, 0);
- mv.getfield(generatedClassName, converterFields.get(returnType), METHOD_HANDLE_TYPE_DESCRIPTOR);
- }
- mv.swap();
- emitInvokeExact(mv, MethodType.methodType(returnType, Object.class));
+ private static MethodType getCallMethodType(final boolean isVarArgCall, final MethodType type) {
+ final Class<?>[] callParamTypes;
+ if (isVarArgCall) {
+ // Variable arity calls are always (Object callee, Object this, Object[] params)
+ callParamTypes = new Class<?>[] { Object.class, Object.class, Object[].class };
+ } else {
+ // Adjust invocation type signature for conversions we instituted in
+ // convertParam; also, byte and short get passed as ints.
+ final Class<?>[] origParamTypes = type.parameterArray();
+ callParamTypes = new Class<?>[origParamTypes.length + 2];
+ callParamTypes[0] = Object.class; // callee; could be ScriptFunction.class ostensibly
+ callParamTypes[1] = Object.class; // this
+ for(int i = 0; i < origParamTypes.length; ++i) {
+ callParamTypes[i + 2] = getNashornParamType(origParamTypes[i], false);
}
}
+ return MethodType.methodType(getNashornReturnType(type.returnType()), callParamTypes);
+ }
+
+ private static MethodType getArrayCreatorMethodType(final MethodType type) {
+ final Class<?>[] callParamTypes = type.parameterArray();
+ for(int i = 0; i < callParamTypes.length; ++i) {
+ callParamTypes[i] = getNashornParamType(callParamTypes[i], true);
+ }
+ return MethodType.methodType(Object[].class, callParamTypes);
+ }
+
+ private static Class<?> getNashornParamType(final Class<?> clazz, final boolean varArg) {
+ if (clazz == byte.class || clazz == short.class) {
+ return int.class;
+ } else if (clazz == float.class) {
+ // If using variable arity, we'll pass a Double instead of double
+ // so that floats don't extend the length of the parameter list.
+ // We return Object.class instead of Double.class though as the
+ // array collector will anyway operate on Object.
+ return varArg ? Object.class : double.class;
+ } else if (!clazz.isPrimitive() || clazz == long.class || clazz == char.class) {
+ return Object.class;
+ }
+ return clazz;
+ }
+
+ private static Class<?> getNashornReturnType(final Class<?> clazz) {
+ if (clazz == byte.class || clazz == short.class) {
+ return int.class;
+ } else if (clazz == float.class) {
+ return double.class;
+ } else if (clazz == void.class || clazz == char.class) {
+ return Object.class;
+ }
+ return clazz;
+ }
+
+
+ private void loadField(final InstructionAdapter mv, final String name, final String desc) {
+ if(classOverride) {
+ mv.getstatic(generatedClassName, name, desc);
+ } else {
+ mv.visitVarInsn(ALOAD, 0);
+ mv.getfield(generatedClassName, name, desc);
+ }
}
- private static void emitInvokeExact(final InstructionAdapter mv, final MethodType type) {
- mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString(), false);
+ private static void convertReturnValue(final InstructionAdapter mv, final Class<?> origReturnType) {
+ if (origReturnType == void.class) {
+ mv.pop();
+ } else if (origReturnType == Object.class) {
+ // Must hide ConsString (and potentially other internal Nashorn types) from callers
+ EXPORT_RETURN_VALUE.invoke(mv);
+ } else if (origReturnType == byte.class) {
+ mv.visitInsn(I2B);
+ } else if (origReturnType == short.class) {
+ mv.visitInsn(I2S);
+ } else if (origReturnType == float.class) {
+ mv.visitInsn(D2F);
+ } else if (origReturnType == char.class) {
+ TO_CHAR_PRIMITIVE.invoke(mv);
+ }
}
- private static void boxStackTop(final InstructionAdapter mv, final Type t) {
+ /**
+ * Emits instruction for converting a parameter on the top of the stack to
+ * a type that is understood by Nashorn.
+ * @param mv the current method visitor
+ * @param t the type on the top of the stack
+ * @param varArg if the invocation will be variable arity
+ */
+ private static void convertParam(final InstructionAdapter mv, final Type t, final boolean varArg) {
+ // We perform conversions of some primitives to accommodate types that
+ // Nashorn can handle.
switch(t.getSort()) {
- case Type.BOOLEAN:
- invokeValueOf(mv, "Boolean", 'Z');
- break;
- case Type.BYTE:
- case Type.SHORT:
- case Type.INT:
- // bytes and shorts get boxed as integers
- invokeValueOf(mv, "Integer", 'I');
- break;
case Type.CHAR:
- invokeValueOf(mv, "Character", 'C');
+ // Chars are boxed, as we don't know if the JS code wants to treat
+ // them as an effective "unsigned short" or as a single-char string.
+ CHAR_VALUE_OF.invoke(mv);
break;
case Type.FLOAT:
- // floats get boxed as doubles
+ // Floats are widened to double.
mv.visitInsn(Opcodes.F2D);
- invokeValueOf(mv, "Double", 'D');
+ if (varArg) {
+ // We'll be boxing everything anyway for the vararg invocation,
+ // so we might as well do it proactively here and thus not cause
+ // a widening in the number of slots, as that could even make
+ // the array creation invocation go over 255 param slots.
+ DOUBLE_VALUE_OF.invoke(mv);
+ }
break;
case Type.LONG:
- invokeValueOf(mv, "Long", 'J');
- break;
- case Type.DOUBLE:
- invokeValueOf(mv, "Double", 'D');
- break;
- case Type.ARRAY:
- case Type.METHOD:
- // Already boxed
+ // Longs are boxed as Nashorn can't represent them precisely as a
+ // primitive number.
+ LONG_VALUE_OF.invoke(mv);
break;
case Type.OBJECT:
if(t.equals(OBJECT_TYPE)) {
- mv.invokestatic(SCRIPTUTILS_TYPE_NAME, "unwrap", UNWRAP_METHOD_DESCRIPTOR, false);
+ // Object can carry a ScriptObjectMirror and needs to be unwrapped
+ // before passing into a Nashorn function.
+ UNWRAP.invoke(mv);
}
break;
- default:
- // Not expecting anything else (e.g. VOID)
- assert false;
- break;
}
}
- private static void invokeValueOf(final InstructionAdapter mv, final String boxedType, final char unboxedType) {
- mv.invokestatic("java/lang/" + boxedType, "valueOf", "(" + unboxedType + ")Ljava/lang/" + boxedType + ";", false);
+ private static int getParamListLengthInSlots(final Type[] paramTypes) {
+ int len = paramTypes.length;
+ for(final Type t: paramTypes) {
+ final int sort = t.getSort();
+ if (sort == Type.FLOAT || sort == Type.DOUBLE) {
+ // Floats are widened to double, so they'll take up two slots.
+ // Longs on the other hand are always boxed, so their width
+ // becomes 1 and thus they don't contribute an extra slot here.
+ ++len;
+ }
+ }
+ return len;
}
-
/**
* Emit code to restore the previous Nashorn Context when needed.
* @param mv the instruction adapter
- * @param currentGlobalVar index of the local variable holding the reference to the current global at method
- * entry.
- * @param globalsDifferVar index of the boolean local variable that is true if the global needs to be restored.
+ * @param globalRestoringRunnableVar index of the local variable holding the reference to the global restoring Runnable
*/
- private static void emitFinally(final InstructionAdapter mv, final int currentGlobalVar, final int globalsDifferVar) {
- // Emit code to restore the previous Nashorn global if needed
- mv.visitVarInsn(ILOAD, globalsDifferVar);
- final Label skip = new Label();
- mv.ifeq(skip);
- mv.visitVarInsn(ALOAD, currentGlobalVar);
- invokeSetGlobal(mv);
- mv.visitLabel(skip);
+ private static void emitFinally(final InstructionAdapter mv, final int globalRestoringRunnableVar) {
+ mv.visitVarInsn(ALOAD, globalRestoringRunnableVar);
+ RUN.invoke(mv);
}
private static boolean isThrowableDeclared(final Class<?>[] exceptions) {
@@ -1030,7 +976,7 @@
mv.visitCode();
emitSuperCall(mv, method.getDeclaringClass(), name, methodDesc);
-
+ mv.areturn(Type.getType(mi.type.returnType()));
endMethod(mv);
}
@@ -1052,7 +998,15 @@
throw new AssertionError("can't find the class/interface that extends " + cl);
}
- private void emitSuperCall(final InstructionAdapter mv, final Class<?> owner, final String name, final String methodDesc) {
+ private int emitSuperConstructorCall(final InstructionAdapter mv, final String methodDesc) {
+ return emitSuperCall(mv, null, INIT, methodDesc, true);
+ }
+
+ private int emitSuperCall(final InstructionAdapter mv, final Class<?> owner, final String name, final String methodDesc) {
+ return emitSuperCall(mv, owner, name, methodDesc, false);
+ }
+
+ private int emitSuperCall(final InstructionAdapter mv, final Class<?> owner, final String name, final String methodDesc, final boolean constructor) {
mv.visitVarInsn(ALOAD, 0);
int nextParam = 1;
final Type methodType = Type.getMethodType(methodDesc);
@@ -1062,48 +1016,46 @@
}
// default method - non-abstract, interface method
- if (Modifier.isInterface(owner.getModifiers())) {
+ if (!constructor && Modifier.isInterface(owner.getModifiers())) {
// we should call default method on the immediate "super" type - not on (possibly)
// the indirectly inherited interface class!
mv.invokespecial(Type.getInternalName(findInvokespecialOwnerFor(owner)), name, methodDesc, false);
} else {
mv.invokespecial(superClassName, name, methodDesc, false);
}
- mv.areturn(methodType.getReturnType());
+ return nextParam;
}
private void generateFinalizerMethods() {
- final String finalizerDelegateName = nextName("access$");
- generateFinalizerDelegate(finalizerDelegateName);
- generateFinalizerOverride(finalizerDelegateName);
+ generateFinalizerDelegate();
+ generateFinalizerOverride();
}
- private void generateFinalizerDelegate(final String finalizerDelegateName) {
+ private void generateFinalizerDelegate() {
// Generate a delegate that will be invoked from the no-permission trampoline. Note it can be private, as we'll
// refer to it with a MethodHandle constant pool entry in the overridden finalize() method (see
// generateFinalizerOverride()).
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PRIVATE | ACC_STATIC,
- finalizerDelegateName, Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE), null, null));
+ FINALIZER_DELEGATE_NAME, FINALIZER_DELEGATE_METHOD_DESCRIPTOR, null, null));
// Simply invoke super.finalize()
mv.visitVarInsn(ALOAD, 0);
mv.checkcast(Type.getType(generatedClassName));
- mv.invokespecial(superClassName, "finalize", Type.getMethodDescriptor(Type.VOID_TYPE), false);
+ mv.invokespecial(superClassName, "finalize", VOID_METHOD_DESCRIPTOR, false);
mv.visitInsn(RETURN);
endMethod(mv);
}
- private void generateFinalizerOverride(final String finalizerDelegateName) {
+ private void generateFinalizerOverride() {
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, "finalize",
- VOID_NOARG_METHOD_DESCRIPTOR, null, null));
+ VOID_METHOD_DESCRIPTOR, null, null));
// Overridden finalizer will take a MethodHandle to the finalizer delegating method, ...
- mv.aconst(new Handle(Opcodes.H_INVOKESTATIC, generatedClassName, finalizerDelegateName,
- Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE)));
+ mv.aconst(new Handle(Opcodes.H_INVOKESTATIC, generatedClassName, FINALIZER_DELEGATE_NAME,
+ FINALIZER_DELEGATE_METHOD_DESCRIPTOR));
mv.visitVarInsn(ALOAD, 0);
// ...and invoke it through JavaAdapterServices.invokeNoPermissions
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "invokeNoPermissions",
- Type.getMethodDescriptor(METHOD_HANDLE_TYPE, OBJECT_TYPE), false);
+ INVOKE_NO_PERMISSIONS.invoke(mv);
mv.visitInsn(RETURN);
endMethod(mv);
}
@@ -1157,11 +1109,8 @@
final MethodInfo mi = new MethodInfo(typeMethod);
if (Modifier.isFinal(m) || isCallerSensitive(typeMethod)) {
finalMethods.add(mi);
- } else if (!finalMethods.contains(mi) && methodInfos.add(mi)) {
- if (Modifier.isAbstract(m)) {
- abstractMethodNames.add(mi.getName());
- }
- mi.setIsCanonical(this);
+ } else if (!finalMethods.contains(mi) && methodInfos.add(mi) && Modifier.isAbstract(m)) {
+ abstractMethodNames.add(mi.getName());
}
}
}
@@ -1222,7 +1171,7 @@
return type2;
}
if (c1.isInterface() || c2.isInterface()) {
- return OBJECT_TYPE_NAME;
+ return OBJECT_TYPE.getInternalName();
}
return assignableSuperClass(c1, c2).getName().replace('.', '/');
} catch(final ClassNotFoundException e) {
@@ -1238,4 +1187,8 @@
private static boolean isCallerSensitive(final AccessibleObject e) {
return e.isAnnotationPresent(CallerSensitive.class);
}
+
+ private static final Call lookupServiceMethod(final String name, final Class<?> rtype, final Class<?>... ptypes) {
+ return staticCallNoLookup(JavaAdapterServices.class, name, rtype, ptypes);
+ }
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java Sat Jan 23 11:50:24 2016 +0100
@@ -54,6 +54,7 @@
import jdk.nashorn.internal.runtime.ECMAException;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
+import jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome;
/**
* A factory class that generates adapter classes. Adapter classes allow
@@ -211,7 +212,7 @@
* be generated from a ScriptFunction.
*/
static boolean isAutoConvertibleFromFunction(final Class<?> clazz) {
- return getAdapterInfo(new Class<?>[] { clazz }).autoConvertibleFromFunction;
+ return getAdapterInfo(new Class<?>[] { clazz }).isAutoConvertibleFromFunction();
}
private static AdapterInfo getAdapterInfo(final Class<?>[] types) {
@@ -273,7 +274,7 @@
} catch (final AdaptationException e) {
return new AdapterInfo(e.getAdaptationResult());
} catch (final RuntimeException e) {
- return new AdapterInfo(new AdaptationResult(AdaptationResult.Outcome.ERROR_OTHER, Arrays.toString(types), e.toString()));
+ return new AdapterInfo(new AdaptationResult(Outcome.ERROR_OTHER, e, Arrays.toString(types), e.toString()));
}
}
}, CREATE_ADAPTER_INFO_ACC_CTXT);
@@ -319,6 +320,13 @@
getClassAdapterClass(classOverrides, protectionDomain);
}
+ boolean isAutoConvertibleFromFunction() {
+ if(adaptationResult.getOutcome() == AdaptationResult.Outcome.ERROR_OTHER) {
+ throw adaptationResult.typeError();
+ }
+ return autoConvertibleFromFunction;
+ }
+
private StaticClass getInstanceAdapterClass(final ProtectionDomain protectionDomain) {
CodeSource codeSource = protectionDomain.getCodeSource();
if(codeSource == null) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java Sat Jan 23 11:50:24 2016 +0100
@@ -33,8 +33,11 @@
import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.security.AccessController;
import java.security.CodeSigner;
@@ -43,15 +46,18 @@
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
+import java.util.Objects;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
+import jdk.nashorn.internal.objects.Global;
import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.ECMAException;
+import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
-import jdk.nashorn.internal.runtime.Undefined;
/**
* Provides static utility services to generated Java adapter classes.
@@ -64,50 +70,47 @@
}
/**
- * Given a JS script function, binds it to null JS "this", and adapts its parameter types, return types, and arity
- * to the specified type and arity. This method is public mainly for implementation reasons, so the adapter classes
- * can invoke it from their constructors that take a ScriptFunction in its first argument to obtain the method
- * handles for their abstract method implementations.
- * @param fn the script function
- * @param type the method type it has to conform to
- * @return the appropriately adapted method handle for invoking the script function.
+ * Given a script function used as a delegate for a SAM adapter, figure out
+ * the right object to use as its "this" when called.
+ * @param delegate the delegate function
+ * @param global the current global of the adapter
+ * @return either the passed global, or UNDEFINED if the function is strict.
*/
- public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type) {
- // JS "this" will be global object or undefined depending on if 'fn' is strict or not
- return bindAndAdaptHandle(fn, fn.isStrict()? ScriptRuntime.UNDEFINED : Context.getGlobal(), type);
+ public static Object getCallThis(final ScriptFunction delegate, final Object global) {
+ return delegate.isStrict() ? ScriptRuntime.UNDEFINED : global;
+ }
+
+ /**
+ * Throws a "not.an.object" type error. Used when the delegate passed to the
+ * adapter constructor is not a script object.
+ * @param obj the object that is not a script object.
+ */
+ public static void notAnObject(final Object obj) {
+ throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
}
/**
- * Given a JS script object, retrieves a function from it by name, binds it to the script object as its "this", and
- * adapts its parameter types, return types, and arity to the specified type and arity. This method is public mainly
- * for implementation reasons, so the adapter classes can invoke it from their constructors that take a Object
- * in its first argument to obtain the method handles for their method implementations.
- * @param obj the script obj
- * @param name the name of the property that contains the function
- * @param type the method type it has to conform to
- * @return the appropriately adapted method handle for invoking the script function, or null if the value of the
- * property is either null or undefined, or "toString" was requested as the name, but the object doesn't directly
- * define it but just inherits it through prototype.
+ * Checks if the passed object, which is supposed to be a callee retrieved
+ * through applying the GET_METHOD_PROPERTY operation on the delegate, is
+ * a ScriptFunction, or null or undefined. These are the only allowed values
+ * for adapter method implementations, so in case it is neither, it throws
+ * a type error. Note that this restriction is somewhat artificial; as the
+ * CALL dynamic operation could invoke any Nashorn callable. We are
+ * restricting adapters to actual ScriptFunction objects for now though.
+ * @param callee the callee to check
+ * @param name the name of the function
+ * @return the callee cast to a ScriptFunction, or null if it was null or undefined.
+ * @throws ECMAException representing a JS TypeError with "not.a.function"
+ * message if the passed callee is neither a script function, nor null, nor
+ * undefined.
*/
- public static MethodHandle getHandle(final Object obj, final String name, final MethodType type) {
- if (! (obj instanceof ScriptObject)) {
- throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
- }
-
- final ScriptObject sobj = (ScriptObject)obj;
- // Since every JS Object has a toString, we only override "String toString()" it if it's explicitly specified
- if ("toString".equals(name) && !sobj.hasOwnProperty("toString")) {
+ public static ScriptFunction checkFunction(final Object callee, final String name) {
+ if (callee instanceof ScriptFunction) {
+ return (ScriptFunction)callee;
+ } else if (JSType.nullOrUndefined(callee)) {
return null;
}
-
- final Object fnObj = sobj.get(name);
- if (fnObj instanceof ScriptFunction) {
- return bindAndAdaptHandle((ScriptFunction)fnObj, sobj, type);
- } else if(fnObj == null || fnObj instanceof Undefined) {
- return null;
- } else {
- throw typeError("not.a.function", name);
- }
+ throw typeError("not.a.function.value", name, ScriptRuntime.safeToString(callee));
}
/**
@@ -116,8 +119,8 @@
* static initializers.
* @return the thread-local JS object used to define methods for the class being initialized.
*/
- public static Object getClassOverrides() {
- final Object overrides = classOverrides.get();
+ public static ScriptObject getClassOverrides() {
+ final ScriptObject overrides = classOverrides.get();
assert overrides != null;
return overrides;
}
@@ -135,29 +138,59 @@
}
/**
- * Set the current global scope
- * @param global the global scope
+ * Set the current global scope to that of the adapter global
+ * @param adapterGlobal the adapter's global scope
+ * @return a Runnable that when invoked restores the previous global
*/
- public static void setGlobal(final Object global) {
- Context.setGlobal((ScriptObject)global);
+ public static Runnable setGlobal(final ScriptObject adapterGlobal) {
+ final Global currentGlobal = Context.getGlobal();
+ if (adapterGlobal != currentGlobal) {
+ Context.setGlobal(adapterGlobal);
+ return ()->Context.setGlobal(currentGlobal);
+ }
+ return ()->{};
+ }
+
+ /**
+ * Get the current non-null global scope
+ * @return the current global scope
+ * @throws NullPointerException if the current global scope is null.
+ */
+ public static ScriptObject getNonNullGlobal() {
+ return Objects.requireNonNull(Context.getGlobal(), "Current global is null");
}
/**
- * Get the current global scope
- * @return the current global scope
+ * Returns true if the object has its own toString function. Used
+ * when implementing toString for adapters. Since every JS Object has a
+ * toString function, we only override "String toString()" in adapters if
+ * it is explicitly specified and not inherited from a prototype.
+ * @param sobj the object
+ * @return true if the object has its own toString function.
*/
- public static Object getGlobal() {
- return Context.getGlobal();
+ public static boolean hasOwnToString(final ScriptObject sobj) {
+ // NOTE: we could just use ScriptObject.hasOwnProperty("toString"), but
+ // its logic is more complex and this is what it boils down to with a
+ // fixed "toString" argument.
+ return sobj.getMap().findProperty("toString") != null;
+ }
+
+ /**
+ * Delegate to {@link Bootstrap#bootstrap(Lookup, String, MethodType, int)}.
+ * @param lookup MethodHandle lookup.
+ * @param opDesc Dynalink dynamic operation descriptor.
+ * @param type Method type.
+ * @param flags flags for call type, trace/profile etc.
+ * @return CallSite with MethodHandle to appropriate method or null if not found.
+ */
+ public static CallSite bootstrap(final Lookup lookup, final String opDesc, final MethodType type, final int flags) {
+ return Bootstrap.bootstrap(lookup, opDesc, type, flags);
}
static void setClassOverrides(final ScriptObject overrides) {
classOverrides.set(overrides);
}
- private static MethodHandle bindAndAdaptHandle(final ScriptFunction fn, final Object self, final MethodType type) {
- return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(fn.getBoundInvokeHandle(self), type, false), type);
- }
-
private static MethodHandle createNoPermissionsInvoker() {
final String className = "NoPermissionsInvoker";
@@ -203,16 +236,6 @@
}
/**
- * Returns a method handle used to convert a return value from a delegate method (always Object) to the expected
- * Java return type.
- * @param returnType the return type
- * @return the converter for the expected return type
- */
- public static MethodHandle getObjectConverter(final Class<?> returnType) {
- return Bootstrap.getLinkerServices().getTypeConverter(Object.class, returnType);
- }
-
- /**
* Invoked when returning Object from an adapted method to filter out internal Nashorn objects that must not be seen
* by the callers. Currently only transforms {@code ConsString} into {@code String} and transforms {@code ScriptObject} into {@code ScriptObjectMirror}.
* @param obj the return value
@@ -233,13 +256,39 @@
}
/**
- * Invoked to convert a return value of a delegate function to String. It is similar to
- * {@code JSType.toString(Object)}, except it doesn't handle StaticClass specially, and it returns null for null
- * input instead of the string "null".
- * @param obj the return value.
- * @return the String value of the return value
+ * Returns a new {@link RuntimeException} wrapping the passed throwable.
+ * Makes generated bytecode smaller by doing an INVOKESTATIC to this method
+ * rather than the NEW/DUP_X1/SWAP/INVOKESPECIAL <init> sequence.
+ * @param t the original throwable to wrap
+ * @return a newly created runtime exception wrapping the passed throwable.
+ */
+ public static RuntimeException wrapThrowable(final Throwable t) {
+ return new RuntimeException(t);
+ }
+
+ /**
+ * Creates and returns a new {@link UnsupportedOperationException}. Makes
+ * generated bytecode smaller by doing INVOKESTATIC to this method rather
+ * than the NEW/DUP/INVOKESPECIAL <init> sequence.
+ * @return a newly created {@link UnsupportedOperationException}.
*/
- public static String toString(final Object obj) {
- return JavaArgumentConverters.toString(obj);
+ public static UnsupportedOperationException unsupported() {
+ return new UnsupportedOperationException();
+ }
+
+ /**
+ * A bootstrap method used to collect invocation arguments into an Object array.
+ * for variable arity invocation.
+ * @param lookup the adapter's lookup (not used).
+ * @param name the call site name (not used).
+ * @param type the method type
+ * @return a method that takes the input parameters and packs them into a
+ * newly allocated Object array.
+ */
+ public static CallSite createArrayBootstrap(final MethodHandles.Lookup lookup, final String name, final MethodType type) {
+ return new ConstantCallSite(
+ MethodHandles.identity(Object[].class)
+ .asCollector(Object[].class, type.parameterCount())
+ .asType(type));
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/src/jdk/nashorn/internal/runtime/linker/test/JavaAdapterTest.java Sat Jan 23 11:50:24 2016 +0100
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.nashorn.internal.runtime.linker.test;
+
+import java.util.Collection;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.function.Supplier;
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+import jdk.nashorn.api.scripting.JSObject;
+import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
+import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import jdk.nashorn.internal.runtime.Context;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class JavaAdapterTest {
+ public interface TestConversions {
+ public byte getByte(byte b);
+ public short getShort(short b);
+ public char getChar(char c);
+ public int getInt(int i);
+ public float getFloat(float f);
+ public long getLong(long l);
+ public double getDouble(double d);
+ }
+
+ @Test
+ public static void testBlah() throws ScriptException {
+ final ScriptEngine e = createEngine();
+ e.eval("new java.util.Comparator({})");
+ }
+
+ @Test
+ public static void testConversions() throws ScriptException {
+ final ScriptEngine e = createEngine();
+ e.put("TestConversionsClass", TestConversions.class);
+ final TestConversions tc = (TestConversions)e.eval(
+ "function id(x) { return x };" +
+ "new TestConversionsClass.static({" +
+ " getByte: id, getShort: id, getChar: id, getInt: id," +
+ " getFloat: id, getLong: id, getDouble: id });");
+
+ Assert.assertEquals(Byte.MIN_VALUE, tc.getByte(Byte.MIN_VALUE));
+ Assert.assertEquals(Byte.MAX_VALUE, tc.getByte(Byte.MAX_VALUE));
+
+ Assert.assertEquals(Short.MIN_VALUE, tc.getShort(Short.MIN_VALUE));
+ Assert.assertEquals(Short.MAX_VALUE, tc.getShort(Short.MAX_VALUE));
+
+ Assert.assertEquals(Character.MIN_VALUE, tc.getChar(Character.MIN_VALUE));
+ Assert.assertEquals(Character.MAX_VALUE, tc.getChar(Character.MAX_VALUE));
+
+ Assert.assertEquals(Integer.MIN_VALUE, tc.getInt(Integer.MIN_VALUE));
+ Assert.assertEquals(Integer.MAX_VALUE, tc.getInt(Integer.MAX_VALUE));
+
+ Assert.assertEquals(Long.MIN_VALUE, tc.getLong(Long.MIN_VALUE));
+ Assert.assertEquals(Long.MAX_VALUE, tc.getLong(Long.MAX_VALUE));
+
+ Assert.assertEquals(Float.MIN_VALUE, tc.getFloat(Float.MIN_VALUE));
+ Assert.assertEquals(Float.MAX_VALUE, tc.getFloat(Float.MAX_VALUE));
+ Assert.assertEquals(Float.MIN_NORMAL, tc.getFloat(Float.MIN_NORMAL));
+ Assert.assertEquals(Float.POSITIVE_INFINITY, tc.getFloat(Float.POSITIVE_INFINITY));
+ Assert.assertEquals(Float.NEGATIVE_INFINITY, tc.getFloat(Float.NEGATIVE_INFINITY));
+ Assert.assertTrue(Float.isNaN(tc.getFloat(Float.NaN)));
+
+ Assert.assertEquals(Double.MIN_VALUE, tc.getDouble(Double.MIN_VALUE));
+ Assert.assertEquals(Double.MAX_VALUE, tc.getDouble(Double.MAX_VALUE));
+ Assert.assertEquals(Double.MIN_NORMAL, tc.getDouble(Double.MIN_NORMAL));
+ Assert.assertEquals(Double.POSITIVE_INFINITY, tc.getDouble(Double.POSITIVE_INFINITY));
+ Assert.assertEquals(Double.NEGATIVE_INFINITY, tc.getDouble(Double.NEGATIVE_INFINITY));
+ Assert.assertTrue(Double.isNaN(tc.getDouble(Double.NaN)));
+ }
+
+ private static ScriptEngine createEngine() {
+ // Use no optimistic typing so we run faster; short-running tests.
+ return new NashornScriptEngineFactory().getScriptEngine("-ot=false");
+ }
+
+ @Test
+ public static void testUnimplemented() throws ScriptException {
+ final ScriptEngine e = createEngine();
+ final Runnable r = (Runnable) e.eval("new java.lang.Runnable({})");
+ Assert.assertNull(Context.getGlobal());
+ try {
+ r.run();
+ Assert.fail();
+ } catch(final UnsupportedOperationException x) {
+ // This is expected
+ }
+ // Check global has been restored
+ Assert.assertNull(Context.getGlobal());
+ }
+
+ public interface ThrowingRunnable {
+ public void run() throws Throwable;
+ }
+
+ @Test
+ public static void testUnimplementedWithThrowable() throws Throwable {
+ final ScriptEngine e = createEngine();
+ e.put("ThrowingRunnableClass", ThrowingRunnable.class);
+ final ThrowingRunnable r = (ThrowingRunnable) e.eval("new ThrowingRunnableClass.static({})");
+ Assert.assertNull(Context.getGlobal());
+ try {
+ r.run();
+ Assert.fail();
+ } catch(final UnsupportedOperationException x) {
+ // This is expected
+ }
+ // Check global has been restored
+ Assert.assertNull(Context.getGlobal());
+ }
+
+ public interface IntSupplierWithDefault {
+ public default int get() { return 42; }
+ }
+
+ @Test
+ public static void testUnimplementedWithDefault() throws ScriptException {
+ final ScriptEngine e = createEngine();
+ e.put("IntSupplierWithDefault", IntSupplierWithDefault.class);
+ final IntSupplierWithDefault s1 = (IntSupplierWithDefault) e.eval("new IntSupplierWithDefault.static({})");
+ Assert.assertEquals(42, s1.get());
+ final IntSupplierWithDefault s2 = (IntSupplierWithDefault) e.eval("new IntSupplierWithDefault.static({ get: function() { return 43 }})");
+ Assert.assertEquals(43, s2.get());
+ }
+
+ public interface SupplierSupplier {
+ public Supplier<Object> getSupplier();
+ }
+
+ @Test
+ public static void testReturnAdapter() throws ScriptException {
+ final ScriptEngine e = createEngine();
+ e.put("SupplierSupplier", SupplierSupplier.class);
+ final SupplierSupplier s = (SupplierSupplier) e.eval("new SupplierSupplier.static(function(){ return function() { return 'foo' } })");
+ Assert.assertEquals("foo", s.getSupplier().get());
+ }
+
+ public interface MaxParams {
+ public Object method(boolean p1, byte p2, short p3, char p4, int p5, float p6, long p7, double p8,
+ Object p9, Object p10, Object p11, Object p12, Object p13, Object p14, Object p15, Object p16,
+ Object p17, Object p18, Object p19, Object p20, Object p21, Object p22, Object p23, Object p24,
+ Object p25, Object p26, Object p27, Object p28, Object p29, Object p30, Object p31, Object p32,
+ Object p33, Object p34, Object p35, Object p36, Object p37, Object p38, Object p39, Object p40,
+ Object p41, Object p42, Object p43, Object p44, Object p45, Object p46, Object p47, Object p48,
+ Object p49, Object p50, Object p51, Object p52, Object p53, Object p54, Object p55, Object p56,
+ Object p57, Object p58, Object p59, Object p60, Object p61, Object p62, Object p63, Object p64,
+ Object p65, Object p66, Object p67, Object p68, Object p69, Object p70, Object p71, Object p72,
+ Object p73, Object p74, Object p75, Object p76, Object p77, Object p78, Object p79, Object p80,
+ Object p81, Object p82, Object p83, Object p84, Object p85, Object p86, Object p87, Object p88,
+ Object p89, Object p90, Object p91, Object p92, Object p93, Object p94, Object p95, Object p96,
+ Object p97, Object p98, Object p99, Object p100, Object p101, Object p102, Object p103, Object p104,
+ Object p105, Object p106, Object p107, Object p108, Object p109, Object p110, Object p111, Object p112,
+ Object p113, Object p114, Object p115, Object p116, Object p117, Object p118, Object p119, Object p120,
+ Object p121, Object p122, Object p123, Object p124, Object p125, Object p126, Object p127, Object p128,
+ Object p129, Object p130, Object p131, Object p132, Object p133, Object p134, Object p135, Object p136,
+ Object p137, Object p138, Object p139, Object p140, Object p141, Object p142, Object p143, Object p144,
+ Object p145, Object p146, Object p147, Object p148, Object p149, Object p150, Object p151, Object p152,
+ Object p153, Object p154, Object p155, Object p156, Object p157, Object p158, Object p159, Object p160,
+ Object p161, Object p162, Object p163, Object p164, Object p165, Object p166, Object p167, Object p168,
+ Object p169, Object p170, Object p171, Object p172, Object p173, Object p174, Object p175, Object p176,
+ Object p177, Object p178, Object p179, Object p180, Object p181, Object p182, Object p183, Object p184,
+ Object p185, Object p186, Object p187, Object p188, Object p189, Object p190, Object p191, Object p192,
+ Object p193, Object p194, Object p195, Object p196, Object p197, Object p198, Object p199, Object p200,
+ Object p201, Object p202, Object p203, Object p204, Object p205, Object p206, Object p207, Object p208,
+ Object p209, Object p210, Object p211, Object p212, Object p213, Object p214, Object p215, Object p216,
+ Object p217, Object p218, Object p219, Object p220, Object p221, Object p222, Object p223, Object p224,
+ Object p225, Object p226, Object p227, Object p228, Object p229, Object p230, Object p231, Object p232,
+ Object p233, Object p234, Object p235, Object p236, Object p237, Object p238, Object p239, Object p240,
+ Object p241, Object p242, Object p243, Object p244, Object p245, Object p246, Object p247, Object p248,
+ Object p249, Object p250, Object p251, Object p252);
+ }
+
+ @Test
+ public static void testMaxLengthAdapter() throws ScriptException {
+ final ScriptEngine e = createEngine();
+ e.put("MaxParams", MaxParams.class);
+ final MaxParams s = (MaxParams) e.eval("new MaxParams.static(function(){ return arguments })");
+ final ScriptObjectMirror m = (ScriptObjectMirror)s.method(true, Byte.MIN_VALUE, Short.MIN_VALUE, 'a', Integer.MAX_VALUE, Float.MAX_VALUE, Long.MAX_VALUE, Double.MAX_VALUE,
+ "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26",
+ "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44",
+ "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62",
+ "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80",
+ "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98",
+ "99", "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114",
+ "115", "116", "117", "118", "119", "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", "130",
+ "131", "132", "133", "134", "135", "136", "137", "138", "139", "140", "141", "142", "143", "144", "145", "146",
+ "147", "148", "149", "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", "160", "161", "162",
+ "163", "164", "165", "166", "167", "168", "169", "170", "171", "172", "173", "174", "175", "176", "177", "178",
+ "179", "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", "190", "191", "192", "193", "194",
+ "195", "196", "197", "198", "199", "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", "210",
+ "211", "212", "213", "214", "215", "216", "217", "218", "219", "220", "221", "222", "223", "224", "225", "226",
+ "227", "228", "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242",
+ "243", "244", "245", "246", "247", "248", "249", "250", "251");
+ Assert.assertEquals(true, m.getSlot(0));
+ Assert.assertEquals(Integer.valueOf(Byte.MIN_VALUE), m.getSlot(1)); // Byte becomes Integer
+ Assert.assertEquals(Integer.valueOf(Short.MIN_VALUE), m.getSlot(2)); // Short becomes Integer
+ Assert.assertEquals(Character.valueOf('a'), m.getSlot(3));
+ Assert.assertEquals(Integer.valueOf(Integer.MAX_VALUE), m.getSlot(4));
+ Assert.assertEquals(Double.valueOf(Float.MAX_VALUE), m.getSlot(5)); // Float becomes Double
+ Assert.assertEquals(Long.valueOf(Long.MAX_VALUE), m.getSlot(6)); // Long was untouched
+ Assert.assertEquals(Double.valueOf(Double.MAX_VALUE), m.getSlot(7));
+ for (int i = 8; i < 252; ++i) {
+ Assert.assertEquals(String.valueOf(i), m.getSlot(i));
+ }
+ }
+
+ public interface TestScriptObjectMirror {
+ public JSObject getJSObject();
+ public ScriptObjectMirror getScriptObjectMirror();
+ public Map<Object, Object> getMap();
+ public Bindings getBindings();
+ }
+
+ @Test
+ public static void testReturnsScriptObjectMirror() throws ScriptException {
+ final ScriptEngine e = createEngine();
+ e.put("TestScriptObjectMirrorClass", TestScriptObjectMirror.class);
+ final TestScriptObjectMirror tsom = (TestScriptObjectMirror)e.eval(
+ "new TestScriptObjectMirrorClass.static({\n" +
+ " getJSObject: function() { return { 'kind': 'JSObject' } },\n" +
+ " getScriptObjectMirror: function() { return { 'kind': 'ScriptObjectMirror' } },\n" +
+ " getMap: function() { return { 'kind': 'Map' } },\n" +
+ " getBindings: function() { return { 'kind': 'Bindings' } } })\n");
+ Assert.assertEquals(tsom.getJSObject().getMember("kind"), "JSObject");
+ Assert.assertEquals(tsom.getScriptObjectMirror().getMember("kind"), "ScriptObjectMirror");
+ Assert.assertEquals(tsom.getMap().get("kind"), "Map");
+ Assert.assertEquals(tsom.getBindings().get("kind"), "Bindings");
+ }
+
+ public interface TestListAdapter {
+ public List<Object> getList();
+ public Collection<Object> getCollection();
+ public Queue<Object> getQueue();
+ public Deque<Object> getDequeue();
+ }
+
+ @Test
+ public static void testReturnsListAdapter() throws ScriptException {
+ final ScriptEngine e = createEngine();
+ e.put("TestListAdapterClass", TestListAdapter.class);
+ final TestListAdapter tla = (TestListAdapter)e.eval(
+ "new TestListAdapterClass.static({\n" +
+ " getList: function() { return [ 'List' ] },\n" +
+ " getCollection: function() { return [ 'Collection' ] },\n" +
+ " getQueue: function() { return [ 'Queue' ] },\n" +
+ " getDequeue: function() { return [ 'Dequeue' ] } })\n");
+ Assert.assertEquals(tla.getList().get(0), "List");
+ Assert.assertEquals(tla.getCollection().iterator().next(), "Collection");
+ Assert.assertEquals(tla.getQueue().peek(), "Queue");
+ Assert.assertEquals(tla.getDequeue().peek(), "Dequeue");
+ }
+}