aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java')
-rw-r--r--src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java262
1 files changed, 235 insertions, 27 deletions
diff --git a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java
index 6b3acf63..7986b8b4 100644
--- a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java
+++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java
@@ -56,7 +56,9 @@ import java.util.Arrays;
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;
@@ -64,7 +66,9 @@ import jdk.internal.org.objectweb.asm.Label;
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.api.scripting.ScriptUtils;
import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome;
@@ -131,9 +135,12 @@ import sun.reflect.CallerSensitive;
* implemented securely.
*/
final class JavaAdapterBytecodeGenerator {
- static final Type OBJECT_TYPE = Type.getType(Object.class);
+ 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>";
@@ -145,6 +152,7 @@ final class JavaAdapterBytecodeGenerator {
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);
+ 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);
@@ -166,7 +174,12 @@ final class JavaAdapterBytecodeGenerator {
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(Type.getType(Class.class));
+ 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);
// 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.
@@ -177,6 +190,7 @@ final class JavaAdapterBytecodeGenerator {
private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 255;
private static final String CLASS_INIT = "<clinit>";
+ static final String CONVERTER_INIT = "<converter-init>";
// Method name prefix for invoking super-methods
static final String SUPER_PREFIX = "super$";
@@ -206,6 +220,22 @@ final class JavaAdapterBytecodeGenerator {
private boolean autoConvertibleFromFunction = false;
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;
/**
@@ -243,6 +273,7 @@ final class JavaAdapterBytecodeGenerator {
gatherMethods(interfaces);
samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null;
generateHandleFields();
+ generateConverterFields();
if(classOverride) {
generateClassInit();
}
@@ -315,6 +346,24 @@ final class JavaAdapterBytecodeGenerator {
}
}
+ 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();
+ }
+ }
+ }
+ }
+
private void generateClassInit() {
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_STATIC, CLASS_INIT,
Type.getMethodDescriptor(Type.VOID_TYPE), null, null));
@@ -334,8 +383,7 @@ final class JavaAdapterBytecodeGenerator {
for (final MethodInfo mi : methodInfos) {
if(mi.getName().equals(samName)) {
mv.dup();
- mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_FUNCTION_DESCRIPTOR, false);
+ loadMethodTypeAndGetHandle(mv, mi, GET_HANDLE_FUNCTION_DESCRIPTOR);
} else {
mv.visitInsn(ACONST_NULL);
}
@@ -351,8 +399,7 @@ final class JavaAdapterBytecodeGenerator {
for (final MethodInfo mi : methodInfos) {
mv.dup();
mv.aconst(mi.getName());
- mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_OBJECT_DESCRIPTOR, false);
+ loadMethodTypeAndGetHandle(mv, mi, GET_HANDLE_OBJECT_DESCRIPTOR);
mv.putstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
}
@@ -363,9 +410,41 @@ final class JavaAdapterBytecodeGenerator {
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();
@@ -422,7 +501,7 @@ final class JavaAdapterBytecodeGenerator {
// Invoke super constructor with the same arguments.
mv.visitVarInsn(ALOAD, 0);
int offset = 1; // First arg is at position 1, after this.
- for (Type argType: argTypes) {
+ for (final Type argType: argTypes) {
mv.load(offset, argType);
offset += argType.getSize();
}
@@ -458,8 +537,8 @@ final class JavaAdapterBytecodeGenerator {
final int argLen = originalArgTypes.length;
final Type[] newArgTypes = new Type[argLen + 1];
- // Insert ScriptFunction|Object as the last argument to the constructor
- final Type extraArgumentType = fromFunction ? SCRIPT_FUNCTION_TYPE : OBJECT_TYPE;
+ // Insert ScriptFunction|ScriptObject as the last argument to the constructor
+ final Type extraArgumentType = fromFunction ? SCRIPT_FUNCTION_TYPE : SCRIPT_OBJECT_TYPE;
newArgTypes[argLen] = extraArgumentType;
System.arraycopy(originalArgTypes, 0, newArgTypes, 0, argLen);
@@ -497,8 +576,7 @@ final class JavaAdapterBytecodeGenerator {
if(!fromFunction) {
mv.aconst(mi.getName());
}
- mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
- mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor, false);
+ loadMethodTypeAndGetHandle(mv, mi, getHandleDescriptor);
}
mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
}
@@ -508,6 +586,36 @@ final class JavaAdapterBytecodeGenerator {
invokeGetGlobalWithNullCheck(mv);
mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
+ // Initialize converters
+ generateConverterInit(mv, fromFunction);
+ 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());
+ }
+ }
+
+ // 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) {
+ 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);
endInitMethod(mv);
}
@@ -622,7 +730,8 @@ final class JavaAdapterBytecodeGenerator {
final Label handleDefined = new Label();
- final Type asmReturnType = Type.getType(type.returnType());
+ final Class<?> returnType = type.returnType();
+ final Type asmReturnType = Type.getType(returnType);
// See if we have overriding method handle defined
if(classOverride) {
@@ -632,7 +741,8 @@ final class JavaAdapterBytecodeGenerator {
mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
}
// stack: [handle]
- jumpIfNonNullKeepOperand(mv, handleDefined);
+ mv.visitInsn(DUP);
+ mv.visitJumpInsn(IFNONNULL, handleDefined);
// No handle is available, fall back to default behavior
if(Modifier.isAbstract(method.getModifiers())) {
@@ -642,6 +752,7 @@ final class JavaAdapterBytecodeGenerator {
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);
}
@@ -703,17 +814,20 @@ final class JavaAdapterBytecodeGenerator {
mv.visitVarInsn(ISTORE, globalsDifferVar);
// stack: [handle]
- // Load all parameters back on stack for dynamic invocation.
+ // 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.
int varOffset = 1;
for (final Type t : asmArgTypes) {
mv.load(varOffset, t);
+ boxStackTop(mv, t);
varOffset += t.getSize();
}
// Invoke the target method handle
final Label tryBlockStart = new Label();
mv.visitLabel(tryBlockStart);
- mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString(), false);
+ emitInvokeExact(mv, type.generic());
+ convertReturnValue(mv, returnType, asmReturnType);
final Label tryBlockEnd = new Label();
mv.visitLabel(tryBlockEnd);
emitFinally(mv, currentGlobalVar, globalsDifferVar);
@@ -743,7 +857,7 @@ final class JavaAdapterBytecodeGenerator {
mv.visitLabel(methodEnd);
mv.visitLocalVariable("currentGlobal", GLOBAL_TYPE_DESCRIPTOR, null, setupGlobal, methodEnd, currentGlobalVar);
- mv.visitLocalVariable("globalsDiffer", Type.INT_TYPE.getDescriptor(), null, setupGlobal, methodEnd, globalsDifferVar);
+ mv.visitLocalVariable("globalsDiffer", Type.BOOLEAN_TYPE.getDescriptor(), null, setupGlobal, methodEnd, globalsDifferVar);
if(throwableDeclared) {
mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, THROWABLE_TYPE_NAME);
@@ -759,16 +873,110 @@ final class JavaAdapterBytecodeGenerator {
endMethod(mv);
}
- /**
- * Emits code for jumping to a label if the top stack operand is not null. The operand is kept on the stack if it
- * is not null (so is available to code at the jump address) and is popped if it is null.
- * @param mv the instruction adapter being used to emit code
- * @param label the label to jump to
- */
- private static void jumpIfNonNullKeepOperand(final InstructionAdapter mv, final Label label) {
- mv.visitInsn(DUP);
- mv.visitJumpInsn(IFNONNULL, label);
- mv.visitInsn(POP);
+ 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 void emitInvokeExact(final InstructionAdapter mv, final MethodType type) {
+ mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString(), false);
+ }
+
+ private static void boxStackTop(final InstructionAdapter mv, final Type t) {
+ 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');
+ break;
+ case Type.FLOAT:
+ // floats get boxed as doubles
+ mv.visitInsn(Opcodes.F2D);
+ invokeValueOf(mv, "Double", 'D');
+ 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
+ break;
+ case Type.OBJECT:
+ if(t.equals(OBJECT_TYPE)) {
+ mv.invokestatic(SCRIPTUTILS_TYPE_NAME, "unwrap", UNWRAP_METHOD_DESCRIPTOR, false);
+ }
+ 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);
}
/**
@@ -805,7 +1013,7 @@ final class JavaAdapterBytecodeGenerator {
}
}
- private void generateSuperMethod(MethodInfo mi) {
+ private void generateSuperMethod(final MethodInfo mi) {
final Method method = mi.method;
final String methodDesc = mi.type.toMethodDescriptorString();