blob: 4bc7b42261910765396c0f0fa30b7a699f5c6f02 [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, 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;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
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.RETURN;
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.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
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;
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;
import sun.reflect.CallerSensitive;
/**
* Generates bytecode for a Java adapter class. Used by the {@link JavaAdapterFactory}.
* </p><p>
* For every protected or public constructor in the extended class, the adapter class will have either one or two
* public constructors (visibility of protected constructors in the extended class is promoted to public).
* <li>
* <li>For adapter classes with instance-level overrides, a constructor taking a trailing ScriptObject argument preceded
* by original constructor arguments is always created on the adapter class. When such a constructor is invoked, the
* passed ScriptObject's member functions are used to implement and/or override methods on the original class,
* 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.
* {@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
* {@code toString} function as its own property, and not inherited from a prototype. All other adapter methods can be
* implemented or overridden through a prototype-inherited function of the ScriptObject passed to the constructor too.
* </li>
* <li>
* If the original types collectively have only one abstract method, or have several of them, but all share the
* same name, an additional constructor for instance-level override adapter is provided for every original constructor;
* this one takes a ScriptFunction as its last argument preceded by original constructor arguments. This constructor
* will use the passed function as the implementation for all abstract methods. For consistency, any concrete methods
* sharing the single abstract method name will also be overridden by the function. When methods on the adapter instance
* are invoked, the ScriptFunction is invoked with UNDEFINED or Global as its "this" depending whether the function is
* strict or not.
* </li>
* <li>
* If the adapter being generated can have 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
* are exposed as public.
* </li>
* </ul>
* </p><p>
* 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
* 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
* to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>.
* </p><p>
* It is possible to create two different adapter classes: those that can have class-level overrides, and those that can
* have instance-level overrides. When {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject)} is invoked
* with non-null {@code classOverrides} parameter, an adapter class is created that can have class-level overrides, and
* the passed script object will be used as the implementations for its methods, just as in the above case of the
* constructor taking a script object. Note that in the case of class-level overrides, a new adapter class is created on
* every invocation, and the implementation object is bound to the class, not to any instance. All created instances
* will share these functions. If it is required to have both class-level overrides and instance-level overrides, the
* class-level override adapter class should be subclassed with an instance-override adapter. Since adapters delegate to
* super class when an overriding method handle is not specified, this will behave as expected. It is not possible to
* have both class-level and instance-level overrides in the same class for security reasons: adapter classes are
* defined with a protection domain of their creator code, and an adapter class that has both class and instance level
* overrides would need to have two potentially different protection domains: one for class-based behavior and one for
* instance-based behavior; since Java classes can only belong to a single protection domain, this could not be
* 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>";
static final String GLOBAL_FIELD_NAME = "global";
// "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);
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);
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();
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);
// 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.
private static final String ADAPTER_PACKAGE_PREFIX = "jdk/nashorn/javaadapters/";
// Class name suffix used to append to the adaptee class name, when it can be defined in the adaptee's package.
private static final String ADAPTER_CLASS_NAME_SUFFIX = "$$NashornJavaAdapter";
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$";
/**
* Collection of methods we never override: Object.clone(), Object.finalize().
*/
private static final Collection<MethodInfo> EXCLUDED = getExcludedMethods();
// This is the superclass for our generated adapter.
private final Class<?> superClass;
// Interfaces implemented by our generated adapter.
private final List<Class<?>> interfaces;
// Class loader used as the parent for the class loader we'll create to load the generated class. It will be a class
// loader that has the visibility of all original types (class to extend and interfaces to implement) and of the
// Nashorn classes.
private final ClassLoader commonLoader;
// Is this a generator for the version of the class that can have overrides on the class level?
private final boolean classOverride;
// Binary name of the superClass
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 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;
/**
* Creates a generator for the bytecode for the adapter for the specified superclass and interfaces.
* @param superClass the superclass the adapter will extend.
* @param interfaces the interfaces the adapter will implement.
* @param commonLoader the class loader that can see all of superClass, interfaces, and Nashorn classes.
* @param classOverride true to generate the bytecode for the adapter that has class-level overrides, false to
* generate the bytecode for the adapter that has instance-level overrides.
* @throws AdaptationException if the adapter can not be generated for some reason.
*/
JavaAdapterBytecodeGenerator(final Class<?> superClass, final List<Class<?>> interfaces,
final ClassLoader commonLoader, final boolean classOverride) throws AdaptationException {
assert superClass != null && !superClass.isInterface();
assert interfaces != null;
this.superClass = superClass;
this.interfaces = interfaces;
this.classOverride = classOverride;
this.commonLoader = commonLoader;
cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) {
@Override
protected String getCommonSuperClass(final String type1, final String type2) {
// We need to override ClassWriter.getCommonSuperClass to use this factory's commonLoader as a class
// loader to find the common superclass of two types when needed.
return JavaAdapterBytecodeGenerator.this.getCommonSuperClass(type1, type2);
}
};
superClassName = Type.getInternalName(superClass);
generatedClassName = getGeneratedClassName(superClass, interfaces);
cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER, generatedClassName, null, superClassName, getInternalTypeNames(interfaces));
generateGlobalFields();
gatherMethods(superClass);
gatherMethods(interfaces);
samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null;
generateHandleFields();
generateConverterFields();
if(classOverride) {
generateClassInit();
}
generateConstructors();
generateMethods();
generateSuperMethods();
if (hasExplicitFinalizer) {
generateFinalizerMethods();
}
// }
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);
}
JavaAdapterClassLoader createAdapterClassLoader() {
return new JavaAdapterClassLoader(generatedClassName, cw.toByteArray());
}
boolean isAutoConvertibleFromFunction() {
return autoConvertibleFromFunction;
}
private static String getGeneratedClassName(final Class<?> superType, final List<Class<?>> interfaces) {
// The class we use to primarily name our adapter is either the superclass, or if it is Object (meaning we're
// just implementing interfaces or extending Object), then the first implemented interface or Object.
final Class<?> namingType = superType == Object.class ? (interfaces.isEmpty()? Object.class : interfaces.get(0)) : superType;
final Package pkg = namingType.getPackage();
final String namingTypeName = Type.getInternalName(namingType);
final StringBuilder buf = new StringBuilder();
if (namingTypeName.startsWith(JAVA_PACKAGE_PREFIX) || pkg == null || pkg.isSealed()) {
// Can't define new classes in java.* packages
buf.append(ADAPTER_PACKAGE_PREFIX).append(namingTypeName);
} else {
buf.append(namingTypeName).append(ADAPTER_CLASS_NAME_SUFFIX);
}
final Iterator<Class<?>> it = interfaces.iterator();
if(superType == Object.class && it.hasNext()) {
it.next(); // Skip first interface, it was used to primarily name the adapter
}
// Append interface names to the adapter name
while(it.hasNext()) {
buf.append("$$").append(it.next().getSimpleName());
}
return buf.toString().substring(0, Math.min(MAX_GENERATED_TYPE_NAME_LENGTH, buf.length()));
}
/**
* Given a list of class objects, return an array with their binary names. Used to generate the array of interface
* names to implement.
* @param classes the classes
* @return an array of names
*/
private static String[] getInternalTypeNames(final List<Class<?>> classes) {
final int interfaceCount = classes.size();
final String[] interfaceNames = new String[interfaceCount];
for(int i = 0; i < interfaceCount; ++i) {
interfaceNames[i] = Type.getInternalName(classes.get(i));
}
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 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));
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 {
boolean gotCtor = false;
for (final Constructor<?> ctor: superClass.getDeclaredConstructors()) {
final int modifier = ctor.getModifiers();
if((modifier & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0 && !isCallerSensitive(ctor)) {
generateConstructors(ctor);
gotCtor = true;
}
}
if(!gotCtor) {
throw new AdaptationException(ERROR_NO_ACCESSIBLE_CONSTRUCTOR, superClass.getCanonicalName());
}
}
private void 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);
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);
}
}
}
private void generateDelegatingConstructor(final Constructor<?> ctor) {
final Type originalCtorType = Type.getType(ctor);
final Type[] argTypes = originalCtorType.getArgumentTypes();
// All constructors must be public, even if in the superclass they were protected.
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC |
(ctor.isVarArgs() ? ACC_VARARGS : 0), INIT,
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);
endInitMethod(mv);
}
/**
* Generates a constructor for the instance adapter class. This constructor will take the same arguments as the supertype
* constructor passed as the argument here, and delegate to it. However, it will take an additional argument of
* either ScriptObject or ScriptFunction type (based on the value of the "fromFunction" parameter), and initialize
* all the method handle fields of the adapter instance with functions from the script object (or the script
* function itself, if that's what's passed). There is one method handle field in the adapter class for every method
* that can be implemented or overridden; the name of every field is same as the name of the method, with a number
* suffix that makes it unique in case of overloaded methods. The generated constructor will invoke
* {@link #getHandle(ScriptFunction, MethodType, boolean)} or {@link #getHandle(Object, String, MethodType,
* boolean)} to obtain the method handles; these methods make sure to add the necessary conversions and arity
* adjustments so that the resulting method handles can be invoked from generated methods using {@code invokeExact}.
* The constructor that takes a script function will only initialize the methods with the same name as the single
* abstract method. The constructor will also store the Nashorn global that was current at the constructor
* invocation time in a field named "global". The generated constructor will be public, regardless of whether the
* supertype constructor was public or protected. The generated constructor will not be variable arity, even if the
* supertype constructor was.
* @param ctor the supertype constructor that is serving as the base for the generated constructor.
* @param fromFunction true if we're generating a constructor that initializes SAM types from a single
* ScriptFunction passed to it, false if we're generating a constructor that initializes an arbitrary type from a
* ScriptObject passed to it.
*/
private void generateOverridingConstructor(final Constructor<?> ctor, final boolean fromFunction) {
final Type originalCtorType = Type.getType(ctor);
final Type[] originalArgTypes = originalCtorType.getArgumentTypes();
final int argLen = originalArgTypes.length;
final Type[] newArgTypes = new Type[argLen + 1];
// 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);
// 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).
// 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);
}
// Assign "this.global = Context.getGlobal()"
mv.visitVarInsn(ALOAD, 0);
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);
}
private static void endInitMethod(final InstructionAdapter mv) {
mv.visitInsn(RETURN);
endMethod(mv);
}
private static void endMethod(final InstructionAdapter mv) {
mv.visitMaxs(0, 0);
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
* method handle serving as the implementation of this method in adapter instances.
*
*/
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));
}
private MethodInfo(final Method method) {
this.method = method;
this.type = MH.type(method.getReturnType(), method.getParameterTypes());
}
@Override
public boolean equals(final Object obj) {
return obj instanceof MethodInfo && equals((MethodInfo)obj);
}
private boolean equals(final MethodInfo other) {
// Only method name and type are used for comparison; method handle field name is not.
return getName().equals(other.getName()) && type.equals(other.type);
}
String getName() {
return method.getName();
}
@Override
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() {
for(final MethodInfo mi: methodInfos) {
generateMethod(mi);
}
}
/**
* 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)}.
* @param mi the method info describing the method to be generated.
*/
private void generateMethod(final MethodInfo mi) {
final Method method = mi.method;
final Class<?>[] exceptions = method.getExceptionTypes();
final String[] exceptionNames = getExceptionNames(exceptions);
final MethodType type = mi.type;
final String methodDesc = type.toMethodDescriptorString();
final String name = mi.getName();
final Type asmType = Type.getMethodType(methodDesc);
final Type[] asmArgTypes = asmType.getArgumentTypes();
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(getAccessModifiers(method), name,
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++;
mv.dup();
// stack: [creatingGlobal, creatingGlobal, handle]
// Emit code for switching to the creating global
// Global currentGlobal = Context.getGlobal();
invokeGetGlobal(mv);
mv.dup();
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]
mv.visitLabel(invokeHandle);
mv.visitVarInsn(ISTORE, globalsDifferVar);
// stack: [handle]
// 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);
emitInvokeExact(mv, type.generic());
convertReturnValue(mv, returnType, asmReturnType);
final Label tryBlockEnd = new Label();
mv.visitLabel(tryBlockEnd);
emitFinally(mv, currentGlobalVar, globalsDifferVar);
mv.areturn(asmReturnType);
// If Throwable is not declared, we need an adapter from Throwable to RuntimeException
final boolean throwableDeclared = isThrowableDeclared(exceptions);
final Label throwableHandler;
if (!throwableDeclared) {
// 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);
// Fall through to rethrow handler
} else {
throwableHandler = null;
}
final Label rethrowHandler = new Label();
mv.visitLabel(rethrowHandler);
// Rethrow handler for RuntimeException, Error, and all declared exception types
emitFinally(mv, currentGlobalVar, globalsDifferVar);
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);
assert throwableHandler == null;
} else {
mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, RUNTIME_EXCEPTION_TYPE_NAME);
mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, ERROR_TYPE_NAME);
for(final String excName: exceptionNames) {
mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, excName);
}
mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, 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 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);
}
/**
* 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.
*/
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 boolean isThrowableDeclared(final Class<?>[] exceptions) {
for (final Class<?> exception : exceptions) {
if (exception == Throwable.class) {
return true;
}
}
return false;
}
private void generateSuperMethods() {
for(final MethodInfo mi: methodInfos) {
if(!Modifier.isAbstract(mi.method.getModifiers())) {
generateSuperMethod(mi);
}
}
}
private void generateSuperMethod(final MethodInfo mi) {
final Method method = mi.method;
final String methodDesc = mi.type.toMethodDescriptorString();
final String name = mi.getName();
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(getAccessModifiers(method),
SUPER_PREFIX + name, methodDesc, null, getExceptionNames(method.getExceptionTypes())));
mv.visitCode();
emitSuperCall(mv, method.getDeclaringClass(), name, methodDesc);
endMethod(mv);
}
// find the appropriate super type to use for invokespecial on the given interface
private Class<?> findInvokespecialOwnerFor(final Class<?> cl) {
assert Modifier.isInterface(cl.getModifiers()) : cl + " is not an interface";
if (cl.isAssignableFrom(superClass)) {
return superClass;
}
for (final Class<?> iface : interfaces) {
if (cl.isAssignableFrom(iface)) {
return iface;
}
}
// we better that interface that extends the given interface!
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) {
mv.visitVarInsn(ALOAD, 0);
int nextParam = 1;
final Type methodType = Type.getMethodType(methodDesc);
for(final Type t: methodType.getArgumentTypes()) {
mv.load(nextParam, t);
nextParam += t.getSize();
}
// default method - non-abstract, interface method
if (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());
}
private void generateFinalizerMethods() {
final String finalizerDelegateName = nextName("access$");
generateFinalizerDelegate(finalizerDelegateName);
generateFinalizerOverride(finalizerDelegateName);
}
private void generateFinalizerDelegate(final String finalizerDelegateName) {
// 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));
// Simply invoke super.finalize()
mv.visitVarInsn(ALOAD, 0);
mv.checkcast(Type.getType(generatedClassName));
mv.invokespecial(superClassName, "finalize", Type.getMethodDescriptor(Type.VOID_TYPE), false);
mv.visitInsn(RETURN);
endMethod(mv);
}
private void generateFinalizerOverride(final String finalizerDelegateName) {
final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, "finalize",
VOID_NOARG_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.visitVarInsn(ALOAD, 0);
// ...and invoke it through JavaAdapterServices.invokeNoPermissions
mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "invokeNoPermissions",
Type.getMethodDescriptor(METHOD_HANDLE_TYPE, OBJECT_TYPE), false);
mv.visitInsn(RETURN);
endMethod(mv);
}
private static String[] getExceptionNames(final Class<?>[] exceptions) {
final String[] exceptionNames = new String[exceptions.length];
for (int i = 0; i < exceptions.length; ++i) {
exceptionNames[i] = Type.getInternalName(exceptions[i]);
}
return exceptionNames;
}
private static int getAccessModifiers(final Method method) {
return ACC_PUBLIC | (method.isVarArgs() ? ACC_VARARGS : 0);
}
/**
* Gathers methods that can be implemented or overridden from the specified type into this factory's
* {@link #methodInfos} set. It will add all non-final, non-static methods that are either public or protected from
* the type if the type itself is public. If the type is a class, the method will recursively invoke itself for its
* superclass and the interfaces it implements, and add further methods that were not directly declared on the
* class.
* @param type the type defining the methods.
*/
private void gatherMethods(final Class<?> type) throws AdaptationException {
if (Modifier.isPublic(type.getModifiers())) {
final Method[] typeMethods = type.isInterface() ? type.getMethods() : type.getDeclaredMethods();
for (final Method typeMethod: typeMethods) {
final String name = typeMethod.getName();
if(name.startsWith(SUPER_PREFIX)) {
continue;
}
final int m = typeMethod.getModifiers();
if (Modifier.isStatic(m)) {
continue;
}
if (Modifier.isPublic(m) || Modifier.isProtected(m)) {
// Is it a "finalize()"?
if(name.equals("finalize") && typeMethod.getParameterCount() == 0) {
if(type != Object.class) {
hasExplicitFinalizer = true;
if(Modifier.isFinal(m)) {
// Must be able to override an explicit finalizer
throw new AdaptationException(Outcome.ERROR_FINAL_FINALIZER, type.getCanonicalName());
}
}
continue;
}
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);
}
}
}
}
// If the type is a class, visit its superclasses and declared interfaces. If it's an interface, we're done.
// Needing to invoke the method recursively for a non-interface Class object is the consequence of needing to
// see all declared protected methods, and Class.getDeclaredMethods() doesn't provide those declared in a
// superclass. For interfaces, we used Class.getMethods(), as we're only interested in public ones there, and
// getMethods() does provide those declared in a superinterface.
if (!type.isInterface()) {
final Class<?> superType = type.getSuperclass();
if (superType != null) {
gatherMethods(superType);
}
for (final Class<?> itf: type.getInterfaces()) {
gatherMethods(itf);
}
}
}
private void gatherMethods(final List<Class<?>> classes) throws AdaptationException {
for(final Class<?> c: classes) {
gatherMethods(c);
}
}
private static final AccessControlContext GET_DECLARED_MEMBERS_ACC_CTXT = ClassAndLoader.createPermAccCtxt("accessDeclaredMembers");
/**
* Creates a collection of methods that are not final, but we still never allow them to be overridden in adapters,
* as explicitly declaring them automatically is a bad idea. Currently, this means {@code Object.finalize()} and
* {@code Object.clone()}.
* @return a collection of method infos representing those methods that we never override in adapter classes.
*/
private static Collection<MethodInfo> getExcludedMethods() {
return AccessController.doPrivileged(new PrivilegedAction<Collection<MethodInfo>>() {
@Override
public Collection<MethodInfo> run() {
try {
return Arrays.asList(
new MethodInfo(Object.class, "finalize"),
new MethodInfo(Object.class, "clone"));
} catch (final NoSuchMethodException e) {
throw new AssertionError(e);
}
}
}, GET_DECLARED_MEMBERS_ACC_CTXT);
}
private String getCommonSuperClass(final String type1, final String type2) {
try {
final Class<?> c1 = Class.forName(type1.replace('/', '.'), false, commonLoader);
final Class<?> c2 = Class.forName(type2.replace('/', '.'), false, commonLoader);
if (c1.isAssignableFrom(c2)) {
return type1;
}
if (c2.isAssignableFrom(c1)) {
return type2;
}
if (c1.isInterface() || c2.isInterface()) {
return OBJECT_TYPE_NAME;
}
return assignableSuperClass(c1, c2).getName().replace('.', '/');
} catch(final ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private static Class<?> assignableSuperClass(final Class<?> c1, final Class<?> c2) {
final Class<?> superClass = c1.getSuperclass();
return superClass.isAssignableFrom(c2) ? superClass : assignableSuperClass(superClass, c2);
}
private static boolean isCallerSensitive(final AccessibleObject e) {
return e.isAnnotationPresent(CallerSensitive.class);
}
}