# HG changeset patch # User attila # Date 1365083606 -7200 # Node ID 5eb1427b6a6db17f98e1cb6bba20c1320aee1284 # Parent 0548c134b9ac6a02ecaa25893bbedf2c6b625020 8011544: Allow subclassing Java classes from script without creating instances Reviewed-by: jlaskey, sundar diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/objects/NativeJava.java --- a/src/jdk/nashorn/internal/objects/NativeJava.java Thu Apr 04 13:54:51 2013 +0530 +++ b/src/jdk/nashorn/internal/objects/NativeJava.java Thu Apr 04 15:53:26 2013 +0200 @@ -394,22 +394,56 @@ * * We can see several important concepts in the above example: * * @param self not used * @param types the original types. The caller must pass at least one Java type object of class {@link StaticClass} * representing either a public interface or a non-final public class with at least one public or protected * constructor. If more than one type is specified, at most one can be a class and the rest have to be interfaces. - * Invoking the method twice with exactly the same types in the same order will return the same adapter - * class, any reordering of types or even addition or removal of redundant types (i.e. interfaces that other types - * in the list already implement/extend, or {@code java.lang.Object} in a list of types consisting purely of - * interfaces) will result in a different adapter class, even though those adapter classes are functionally - * identical; we deliberately don't want to incur the additional processing cost of canonicalizing type lists. + * Invoking the method twice with exactly the same types in the same order - in absence of class-level overrides - + * will return the same adapter class, any reordering of types or even addition or removal of redundant types (i.e. + * interfaces that other types in the list already implement/extend, or {@code java.lang.Object} in a list of types + * consisting purely of interfaces) will result in a different adapter class, even though those adapter classes are + * functionally identical; we deliberately don't want to incur the additional processing cost of canonicalizing type + * lists. As a special case, the last argument can be a {@code ScriptObject} instead of a type. In this case, a + * separate adapter class is generated - new one for each invocation - that will use the passed script object as its + * implementation for all instances. Instances of such adapter classes can then be created without passing another + * script object in the constructor, as the class has a class-level behavior defined by the script object. However, + * you can still pass a script object (or if it's a SAM type, a function) to the constructor to provide further + * instance-level overrides. + * * @return a new {@link StaticClass} that represents the adapter for the original types. */ @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) @@ -417,14 +451,27 @@ if(types == null || types.length == 0) { throw typeError("extend.expects.at.least.one.argument"); } - final Class[] stypes = new Class[types.length]; + final int l = types.length; + final int typesLen; + final ScriptObject classOverrides; + if(types[l - 1] instanceof ScriptObject) { + classOverrides = (ScriptObject)types[l - 1]; + typesLen = l - 1; + if(typesLen == 0) { + throw typeError("extend.expects.at.least.one.type.argument"); + } + } else { + classOverrides = null; + typesLen = l; + } + final Class[] stypes = new Class[typesLen]; try { - for(int i = 0; i < types.length; ++i) { + for(int i = 0; i < typesLen; ++i) { stypes[i] = ((StaticClass)types[i]).getRepresentedClass(); } } catch(final ClassCastException e) { throw typeError("extend.expects.java.types"); } - return JavaAdapterFactory.getAdapterClassFor(stypes); + return JavaAdapterFactory.getAdapterClassFor(stypes, classOverrides); } } diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/AdaptationException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/runtime/linker/AdaptationException.java Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,39 @@ +/* + * 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; + +@SuppressWarnings("serial") +class AdaptationException extends Exception { + private final AdaptationResult adaptationResult; + + AdaptationException(final AdaptationResult.Outcome outcome, final String classList) { + this.adaptationResult = new AdaptationResult(outcome, classList); + } + + AdaptationResult getAdaptationResult() { + return adaptationResult; + } +} diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/AdaptationResult.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/runtime/linker/AdaptationResult.java Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,74 @@ +/* + * 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 jdk.nashorn.internal.runtime.ECMAErrors; +import jdk.nashorn.internal.runtime.ECMAException; + +/** + * A result of generating an adapter for a class. A tuple of an outcome and - in case of an error outcome - a list of + * classes that caused the error. + */ +class AdaptationResult { + /** + * Contains various outcomes for attempting to generate an adapter class. These are stored in AdapterInfo instances. + * We have a successful outcome (adapter class was generated) and four possible error outcomes: superclass is final, + * superclass is not public, superclass has no public or protected constructor, more than one superclass was + * specified. We don't throw exceptions when we try to generate the adapter, but rather just record these error + * conditions as they are still useful as partial outcomes, as Nashorn's linker can still successfully check whether + * the class can be autoconverted from a script function even when it is not possible to generate an adapter for it. + */ + enum Outcome { + SUCCESS, + ERROR_FINAL_CLASS, + ERROR_NON_PUBLIC_CLASS, + ERROR_NO_ACCESSIBLE_CONSTRUCTOR, + ERROR_MULTIPLE_SUPERCLASSES, + ERROR_NO_COMMON_LOADER + } + + static final AdaptationResult SUCCESSFUL_RESULT = new AdaptationResult(Outcome.SUCCESS, ""); + + private final Outcome outcome; + private final String classList; + + AdaptationResult(final Outcome outcome, final String classList) { + this.outcome = outcome; + this.classList = classList; + } + + Outcome getOutcome() { + return outcome; + } + + String getClassList() { + return classList; + } + + ECMAException typeError() { + return ECMAErrors.typeError("extend." + outcome, classList); + } +} diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,188 @@ +/* + * 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.nashorn.internal.runtime.ECMAErrors.typeError; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its + * equals/hashCode is defined in terms of the identity of the class loader. The rationale for this class is that it + * couples a class loader with a random representative class coming from that loader - this representative class is then + * used to determine if one loader can see the other loader's classes. + */ +final class ClassAndLoader { + private final Class representativeClass; + // Don't access this directly; most of the time, use getRetrievedLoader(), or if you know what you're doing, + // getLoader(). + private ClassLoader loader; + // We have mild affinity against eagerly retrieving the loader, as we need to do it in a privileged block. For + // the most basic case of looking up an already-generated adapter info for a single type, we avoid it. + private boolean loaderRetrieved; + + ClassAndLoader(final Class representativeClass, final boolean retrieveLoader) { + this.representativeClass = representativeClass; + if(retrieveLoader) { + retrieveLoader(); + } + } + + Class getRepresentativeClass() { + return representativeClass; + } + + boolean canSee(ClassAndLoader other) { + try { + final Class otherClass = other.getRepresentativeClass(); + return Class.forName(otherClass.getName(), false, getLoader()) == otherClass; + } catch (final ClassNotFoundException e) { + return false; + } + } + + ClassLoader getLoader() { + if(!loaderRetrieved) { + retrieveLoader(); + } + return getRetrievedLoader(); + } + + ClassLoader getRetrievedLoader() { + assert loaderRetrieved; + return loader; + } + + private void retrieveLoader() { + loader = representativeClass.getClassLoader(); + loaderRetrieved = true; + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof ClassAndLoader && ((ClassAndLoader)obj).getRetrievedLoader() == getRetrievedLoader(); + } + + @Override + public int hashCode() { + return System.identityHashCode(getRetrievedLoader()); + } + + /** + * Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the + * list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a + * class loader that can also see all other types is returned. If there is no such loader, an exception is thrown. + * @param types the input types + * @return the first type from the array that is defined in a class loader that can also see all other types. + */ + static ClassAndLoader getDefiningClassAndLoader(final Class[] types) { + // Short circuit the cheap case + if(types.length == 1) { + return new ClassAndLoader(types[0], false); + } + + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ClassAndLoader run() { + return getDefiningClassAndLoaderPrivileged(types); + } + }); + } + + static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class[] types) { + final Collection maximumVisibilityLoaders = getMaximumVisibilityLoaders(types); + + final Iterator it = maximumVisibilityLoaders.iterator(); + if(maximumVisibilityLoaders.size() == 1) { + // Fortunate case - single maximally specific class loader; return its representative class. + return it.next(); + } + + // Ambiguity; throw an error. + assert maximumVisibilityLoaders.size() > 1; // basically, can't be zero + final StringBuilder b = new StringBuilder(); + b.append(it.next().getRepresentativeClass().getCanonicalName()); + while(it.hasNext()) { + b.append(", ").append(it.next().getRepresentativeClass().getCanonicalName()); + } + throw typeError("extend.ambiguous.defining.class", b.toString()); + } + + /** + * Given an array of types, return a subset of their class loaders that are maximal according to the + * "can see other loaders' classes" relation, which is presumed to be a partial ordering. + * @param types types + * @return a collection of maximum visibility class loaders. It is guaranteed to have at least one element. + */ + private static Collection getMaximumVisibilityLoaders(final Class[] types) { + final List maximumVisibilityLoaders = new LinkedList<>(); + outer: for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) { + final Iterator it = maximumVisibilityLoaders.iterator(); + while(it.hasNext()) { + final ClassAndLoader existingMax = it.next(); + final boolean candidateSeesExisting = maxCandidate.canSee(existingMax); + final boolean exitingSeesCandidate = existingMax.canSee(maxCandidate); + if(candidateSeesExisting) { + if(!exitingSeesCandidate) { + // The candidate sees the the existing maximum, so drop the existing one as it's no longer maximal. + it.remove(); + } + // NOTE: there's also the anomalous case where both loaders see each other. Not sure what to do + // about that one, as two distinct class loaders both seeing each other's classes is weird and + // violates the assumption that the relation "sees others' classes" is a partial ordering. We'll + // just not do anything, and treat them as incomparable; hopefully some later class loader that + // comes along can eliminate both of them, if it can not, we'll end up with ambiguity anyway and + // throw an error at the end. + } else if(exitingSeesCandidate) { + // Existing sees the candidate, so drop the candidate. + continue outer; + } + } + // If we get here, no existing maximum visibility loader could see the candidate, so the candidate is a new + // maximum. + maximumVisibilityLoaders.add(maxCandidate); + } + return maximumVisibilityLoaders; + } + + private static Collection getClassLoadersForTypes(final Class[] types) { + final Map classesAndLoaders = new LinkedHashMap<>(); + for(final Class c: types) { + final ClassAndLoader cl = new ClassAndLoader(c, true); + if(!classesAndLoaders.containsKey(cl)) { + classesAndLoaders.put(cl, cl); + } + } + return classesAndLoaders.keySet(); + } +} \ No newline at end of file diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,884 @@ +/* + * 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.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Set; +import jdk.internal.org.objectweb.asm.ClassWriter; +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.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; + +/** + * Generates bytecode for a Java adapter class. Used by the {@link JavaAdapterFactory}. + *

+ * For every protected or public constructor in the extended class, the adapter class will have between one to three + * public constructors (visibility of protected constructors in the extended class is promoted to public). + *

    + *
  • In every case, 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. + *
  • + *
  • + * If the original types collectively have only one abstract method, or have several of them, but all share the + * same name, an additional constructor 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 {@code null} as its "this". + *
  • + *
  • + * If the adapter being generated can have class-level overrides, constructors taking same arguments as the superclass + * constructors are also created. These constructors simply delegate to the superclass constructor. They are used to + * create instances of the adapter class with no instance-level overrides. + *
  • + *
+ *

+ * 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. + *

+ * 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 new X(a, b) { ... } (which is a proprietary syntax extension Nashorn uses + * to resemble Java anonymous classes) is actually equivalent to new X(a, b, { ... }). + *

+ * It is possible to create two different classes: those that can have both class-level and instance-level overrides, + * and those that can only 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. Of course, when + * instances of such a class are being created, they can still take another object (or possibly a function) in their + * constructor's trailing position and thus provide further instance-specific overrides. The order of invocation is + * always instance-specified method, then a class-specified method, and finally the superclass method. + */ +final class JavaAdapterBytecodeGenerator extends JavaAdapterGeneratorBase { + 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, Type.BOOLEAN_TYPE); + private static final String GET_HANDLE_FUNCTION_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE, + SCRIPT_FUNCTION_TYPE, METHOD_TYPE_TYPE, Type.BOOLEAN_TYPE); + private static final String GET_CLASS_INITIALIZER_DESCRIPTOR = Type.getMethodDescriptor(SCRIPT_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(SCRIPT_OBJECT_TYPE); + private static final String GET_CLASS_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.getType(Class.class)); + + // 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/internal/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 = 238; //255 - 17; 17 is the maximum possible length for the global setter inner class suffix + + private static final String CLASS_INIT = ""; + private static final String STATIC_GLOBAL_FIELD_NAME = "staticGlobal"; + + /** + * Collection of methods we never override: Object.clone(), Object.finalize(). + */ + private static final Collection EXCLUDED = getExcludedMethods(); + + private static final Random random = new SecureRandom(); + + // This is the superclass for our generated adapter. + private final Class superClass; + // 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; + // Binary name of the PrivilegedAction inner class that is used to + private final String globalSetterClassName; + private final Set usedFieldNames = new HashSet<>(); + private final Set abstractMethodNames = new HashSet<>(); + private final String samName; + private final Set finalMethods = new HashSet<>(EXCLUDED); + private final Set methodInfos = new HashSet<>(); + private boolean autoConvertibleFromFunction = false; + + 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 both class-level and instance-level + * overrides, false to generate the bytecode for the adapter that only has instance-level overrides. + * @throws AdaptationException if the adapter can not be generated for some reason. + */ + JavaAdapterBytecodeGenerator(final Class superClass, final List> interfaces, + final ClassLoader commonLoader, final boolean classOverride) throws AdaptationException { + assert superClass != null && !superClass.isInterface(); + assert interfaces != null; + + this.superClass = superClass; + 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); + + // Randomize the name of the privileged global setter, to make it non-feasible to find. + final long l; + synchronized(random) { + l = random.nextLong(); + } + + // NOTE: they way this class name is calculated affects the value of MAX_GENERATED_TYPE_NAME_LENGTH constant. If + // you change the calculation of globalSetterClassName, adjust the constant too. + globalSetterClassName = generatedClassName.concat("$" + Long.toHexString(l & Long.MAX_VALUE)); + cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, generatedClassName, null, superClassName, getInternalTypeNames(interfaces)); + + generateGlobalFields(); + + gatherMethods(superClass); + gatherMethods(interfaces); + samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null; + generateHandleFields(); + if(classOverride) { + generateClassInit(); + } + generateConstructors(); + generateMethods(); + // } + cw.visitEnd(); + } + + private void generateGlobalFields() { + cw.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); + usedFieldNames.add(GLOBAL_FIELD_NAME); + if(classOverride) { + cw.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); + usedFieldNames.add(STATIC_GLOBAL_FIELD_NAME); + } + } + + JavaAdapterClassLoader createAdapterClassLoader() { + return new JavaAdapterClassLoader(generatedClassName, cw.toByteArray(), globalSetterClassName); + } + + boolean isAutoConvertibleFromFunction() { + return autoConvertibleFromFunction; + } + + private static String getGeneratedClassName(final Class superType, final List> 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> 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> 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() { + for (final MethodInfo mi: methodInfos) { + cw.visitField(ACC_PRIVATE | ACC_FINAL, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd(); + if(classOverride) { + cw.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, mi.methodHandleClassFieldName, 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); + // Assign MethodHandle fields through invoking getHandle() + for (final MethodInfo mi : methodInfos) { + mv.dup(); + mv.aconst(mi.getName()); + mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString())); + mv.iconst(mi.method.isVarArgs() ? 1 : 0); + mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_OBJECT_DESCRIPTOR); + mv.putstatic(generatedClassName, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); + } + + // Assign "staticGlobal = Context.getGlobal()" + invokeGetGlobalWithNullCheck(mv); + mv.putstatic(generatedClassName, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + + endInitMethod(mv); + } + + private static void invokeGetGlobalWithNullCheck(final InstructionAdapter mv) { + invokeGetGlobal(mv); + mv.dup(); + mv.invokevirtual(OBJECT_TYPE_NAME, "getClass", GET_CLASS_METHOD_DESCRIPTOR); // 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) { + 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); + } + + // 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, 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 (Type argType: argTypes) { + mv.load(offset, argType); + offset += argType.getSize(); + } + mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor()); + + endInitMethod(mv); + } + + /** + * Generates a constructor for the 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|Object as the last argument to the constructor + final Type extraArgumentType = fromFunction ? SCRIPT_FUNCTION_TYPE : 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 (this, args...) triggers generating (this, scriptObj, args...). + 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 + // (this, args..., scriptFn), then we're invoking super.(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()); + + // 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 overriden 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()); + } + mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString())); + mv.iconst(mi.method.isVarArgs() ? 1 : 0); + mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor); + } + mv.putfield(generatedClassName, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); + } + + // Assign "this.global = Context.getGlobal()" + mv.visitVarInsn(ALOAD, 0); + invokeGetGlobalWithNullCheck(mv); + mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + + endInitMethod(mv); + } + + private static void endInitMethod(final InstructionAdapter mv) { + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + private static void invokeGetGlobal(final InstructionAdapter mv) { + mv.invokestatic(CONTEXT_TYPE_NAME, "getGlobal", GET_GLOBAL_METHOD_DESCRIPTOR); + } + + private void invokeSetGlobal(final InstructionAdapter mv) { + mv.invokestatic(globalSetterClassName, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR); + } + + /** + * 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 methodHandleInstanceFieldName; + private String methodHandleClassFieldName; + + 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 Set usedFieldNames, boolean classOverride) { + methodHandleInstanceFieldName = nextName(usedFieldNames); + if(classOverride) { + methodHandleClassFieldName = nextName(usedFieldNames); + } + } + + String nextName(final Set usedFieldNames) { + int i = 0; + final String name = getName(); + 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 int mod = method.getModifiers(); + final int access = ACC_PUBLIC | (method.isVarArgs() ? ACC_VARARGS : 0); + final Class[] exceptions = method.getExceptionTypes(); + final String[] exceptionNames = new String[exceptions.length]; + for (int i = 0; i < exceptions.length; ++i) { + exceptionNames[i] = Type.getInternalName(exceptions[i]); + } + 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(); + + // Determine the first index for a local variable + int nextLocalVar = 1; // this + for(final Type t: asmArgTypes) { + nextLocalVar += t.getSize(); + } + + final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(access, name, methodDesc, null, + exceptionNames)); + mv.visitCode(); + + final Label instanceHandleDefined = new Label(); + final Label classHandleDefined = new Label(); + + final Type asmReturnType = Type.getType(type.returnType()); + + // See if we have instance handle defined + mv.visitVarInsn(ALOAD, 0); + mv.getfield(generatedClassName, mi.methodHandleInstanceFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); + // stack: [instanceHandle] + jumpIfNonNullKeepOperand(mv, instanceHandleDefined); + + if(classOverride) { + // See if we have the static handle + mv.getstatic(generatedClassName, mi.methodHandleClassFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); + // stack: [classHandle] + jumpIfNonNullKeepOperand(mv, classHandleDefined); + } + + // No handle is available, fall back to default behavior + if(Modifier.isAbstract(mod)) { + // 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); + mv.athrow(); + } else { + // If the super method is not abstract, delegate to it. + mv.visitVarInsn(ALOAD, 0); + int nextParam = 1; + for(final Type t: asmArgTypes) { + mv.load(nextParam, t); + nextParam += t.getSize(); + } + mv.invokespecial(superClassName, name, methodDesc); + mv.areturn(asmReturnType); + } + + final Label setupGlobal = new Label(); + + if(classOverride) { + mv.visitLabel(classHandleDefined); + // If class handle is defined, load the static defining global + mv.getstatic(generatedClassName, STATIC_GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + // stack: [creatingGlobal := classGlobal, classHandle] + mv.goTo(setupGlobal); + } + + mv.visitLabel(instanceHandleDefined); + // If instance handle is defined, load the instance defining global + mv.visitVarInsn(ALOAD, 0); + mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + // stack: [creatingGlobal := instanceGlobal, instanceHandle] + + // fallthrough to setupGlobal + + // stack: [creatingGlobal, someHandle] + mv.visitLabel(setupGlobal); + + final int currentGlobalVar = nextLocalVar++; + final int globalsDifferVar = nextLocalVar++; + + mv.dup(); + // stack: [creatingGlobal, creatingGlobal, someHandle] + + // Emit code for switching to the creating global + // ScriptObject currentGlobal = Context.getGlobal(); + invokeGetGlobal(mv); + mv.dup(); + mv.visitVarInsn(ASTORE, currentGlobalVar); + // stack: [currentGlobal, creatingGlobal, creatingGlobal, someHandle] + // if(definingGlobal == currentGlobal) { + final Label globalsDiffer = new Label(); + mv.ifacmpne(globalsDiffer); + // stack: [someGlobal, someHandle] + // globalsDiffer = false + mv.pop(); + // stack: [someHandle] + mv.iconst(0); // false + // stack: [false, someHandle] + final Label invokeHandle = new Label(); + mv.goTo(invokeHandle); + mv.visitLabel(globalsDiffer); + // } else { + // Context.setGlobal(definingGlobal); + // stack: [someGlobal, someHandle] + invokeSetGlobal(mv); + // stack: [someHandle] + // globalsDiffer = true + mv.iconst(1); + // stack: [true, someHandle] + + mv.visitLabel(invokeHandle); + mv.visitVarInsn(ISTORE, globalsDifferVar); + // stack: [someHandle] + + // Load all parameters back on stack for dynamic invocation. + int varOffset = 1; + for (final Type t : asmArgTypes) { + mv.load(varOffset, 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()); + 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)); + // 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", SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, setupGlobal, methodEnd, currentGlobalVar); + mv.visitLocalVariable("globalsDiffer", Type.INT_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); + } + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + /** + * 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); + } + + /** + * 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 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; + } + + /** + * 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) { + if (Modifier.isPublic(type.getModifiers())) { + final Method[] typeMethods = type.isInterface() ? type.getMethods() : type.getDeclaredMethods(); + + for (final Method typeMethod: typeMethods) { + final int m = typeMethod.getModifiers(); + if (Modifier.isStatic(m)) { + continue; + } + if (Modifier.isPublic(m) || Modifier.isProtected(m)) { + final MethodInfo mi = new MethodInfo(typeMethod); + if (Modifier.isFinal(m)) { + finalMethods.add(mi); + } else if (!finalMethods.contains(mi) && methodInfos.add(mi)) { + if (Modifier.isAbstract(m)) { + abstractMethodNames.add(mi.getName()); + } + mi.setIsCanonical(usedFieldNames, classOverride); + } + } + } + } + // 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> classes) { + for(final Class c: classes) { + gatherMethods(c); + } + } + + /** + * 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 getExcludedMethods() { + return AccessController.doPrivileged(new PrivilegedAction>() { + @Override + public Collection run() { + try { + return Arrays.asList( + new MethodInfo(Object.class, "finalize"), + new MethodInfo(Object.class, "clone")); + } catch (final NoSuchMethodException e) { + throw new AssertionError(e); + } + } + }); + } + + 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); + } +} diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,225 @@ +/* + * 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.ACONST_NULL; +import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; +import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN; +import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; + +import java.security.AccessController; +import java.security.AllPermission; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.security.Permissions; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.security.SecureClassLoader; +import jdk.internal.dynalink.beans.StaticClass; +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.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptObject; + +/** + * This class encapsulates the bytecode of the adapter class and can be used to load it into the JVM as an actual Class. + * It can be invoked repeatedly to create multiple adapter classes from the same bytecode; adapter classes that have + * class-level overrides must be re-created for every set of such overrides. Note that while this class is named + * "class loader", it does not, in fact, extend {@code ClassLoader}, but rather uses them internally. Instances of this + * class are normally created by {@link JavaAdapterBytecodeGenerator}. + */ +class JavaAdapterClassLoader extends JavaAdapterGeneratorBase { + private static final Type PRIVILEGED_ACTION_TYPE = Type.getType(PrivilegedAction.class); + + private static final String PRIVILEGED_ACTION_TYPE_NAME = PRIVILEGED_ACTION_TYPE.getInternalName(); + private static final String PRIVILEGED_RUN_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE); + + private static final ProtectionDomain GENERATED_PROTECTION_DOMAIN = createGeneratedProtectionDomain(); + + private final String className; + private final byte[] classBytes; + private final String globalSetterClassName; + + JavaAdapterClassLoader(String className, byte[] classBytes, String globalSetterClassName) { + this.className = className.replace('/', '.'); + this.classBytes = classBytes; + this.globalSetterClassName = globalSetterClassName.replace('/', '.'); + } + + /** + * Loads the generated adapter class into the JVM. + * @param parentLoader the parent class loader for the generated class loader + * @return the generated adapter class + */ + StaticClass generateClass(final ClassLoader parentLoader) { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public StaticClass run() { + try { + return StaticClass.forClass(Class.forName(className, true, createClassLoader(parentLoader))); + } catch (final ClassNotFoundException e) { + throw new AssertionError(e); // cannot happen + } + } + }); + } + + private static class AdapterLoader extends SecureClassLoader { + AdapterLoader(ClassLoader parent) { + super(parent); + } + } + + static boolean isAdapterClass(Class clazz) { + return clazz.getClassLoader() instanceof AdapterLoader; + } + + // Note that the adapter class is created in the protection domain of the class/interface being + // extended/implemented, and only the privileged global setter action class is generated in the protection domain + // of Nashorn itself. Also note that the creation and loading of the global setter is deferred until it is + // required by JVM linker, which will only happen on first invocation of any of the adapted method. We could defer + // it even more by separating its invocation into a separate static method on the adapter class, but then someone + // with ability to introspect on the class and use setAccessible(true) on it could invoke the method. It's a + // security tradeoff... + private ClassLoader createClassLoader(final ClassLoader parentLoader) { + return new AdapterLoader(parentLoader) { + private final ClassLoader myLoader = getClass().getClassLoader(); + private final ProtectionDomain myProtectionDomain = getClass().getProtectionDomain(); + + @Override + public Class loadClass(final String name, final boolean resolve) throws ClassNotFoundException { + try { + return super.loadClass(name, resolve); + } catch (final SecurityException se) { + // we may be implementing an interface or extending a class that was + // loaded by a loader that prevents package.access. If so, it'd throw + // SecurityException for nashorn's classes!. For adapter's to work, we + // should be able to refer to nashorn classes. + if (name.startsWith("jdk.nashorn.internal.")) { + return myLoader.loadClass(name); + } + throw se; + } + } + + @Override + protected Class findClass(final String name) throws ClassNotFoundException { + if(name.equals(className)) { + return defineClass(name, classBytes, 0, classBytes.length, GENERATED_PROTECTION_DOMAIN); + } else if(name.equals(globalSetterClassName)) { + final byte[] bytes = generatePrivilegedActionClassBytes(globalSetterClassName.replace('.', '/')); + return defineClass(name, bytes, 0, bytes.length, myProtectionDomain); + } else { + throw new ClassNotFoundException(name); + } + } + }; + } + + private static ProtectionDomain createGeneratedProtectionDomain() { + // Generated classes need to have AllPermission. Since we require the "createClassLoader" RuntimePermission, we + // can create a class loader that'll load new classes with any permissions. Our generated classes are just + // delegating adapters, so having AllPermission can't cause anything wrong; the effective set of permissions for + // the executing script functions will still be limited by the permissions of the caller and the permissions of + // the script. + final Permissions permissions = new Permissions(); + permissions.add(new AllPermission()); + return new ProtectionDomain(new CodeSource(null, (CodeSigner[])null), permissions); + } + + /** + * Generates a PrivilegedAction implementation class for invoking {@link Context#setGlobal(ScriptObject)} from the + * adapter class. + */ + private static byte[] generatePrivilegedActionClassBytes(final String className) { + final ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + // class GlobalSetter implements PrivilegedAction { + w.visit(Opcodes.V1_7, ACC_SUPER | ACC_FINAL, className, null, OBJECT_TYPE_NAME, new String[] { + PRIVILEGED_ACTION_TYPE_NAME + }); + + // private final ScriptObject global; + w.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); + + // private GlobalSetter(ScriptObject global) { + InstructionAdapter mv = new InstructionAdapter(w.visitMethod(ACC_PRIVATE, INIT, + SET_GLOBAL_METHOD_DESCRIPTOR, null, new String[0])); + mv.visitCode(); + // super(); + mv.visitVarInsn(ALOAD, 0); + mv.invokespecial(OBJECT_TYPE_NAME, INIT, VOID_NOARG_METHOD_DESCRIPTOR); + // this.global = global; + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.putfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + + mv.visitInsn(RETURN); + mv.visitEnd(); + mv.visitMaxs(0, 0); + + // public Object run() { + mv = new InstructionAdapter(w.visitMethod(ACC_PUBLIC, "run", PRIVILEGED_RUN_METHOD_DESCRIPTOR, null, + new String[0])); + mv.visitCode(); + // Context.setGlobal(this.global); + mv.visitVarInsn(ALOAD, 0); + mv.getfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); + mv.invokestatic(CONTEXT_TYPE_NAME, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR); + // return null; + mv.visitInsn(ACONST_NULL); + mv.visitInsn(ARETURN); + + mv.visitEnd(); + mv.visitMaxs(0, 0); + + // static void setGlobal(ScriptObject global) { + mv = new InstructionAdapter(w.visitMethod(ACC_STATIC, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR, null, + new String[0])); + mv.visitCode(); + // new GlobalSetter(ScriptObject global) + mv.anew(Type.getType("L" + className + ";")); + mv.dup(); + mv.visitVarInsn(ALOAD, 0); + mv.invokespecial(className, INIT, SET_GLOBAL_METHOD_DESCRIPTOR); + // AccessController.doPrivileged(...) + mv.invokestatic(Type.getInternalName(AccessController.class), "doPrivileged", Type.getMethodDescriptor( + OBJECT_TYPE, PRIVILEGED_ACTION_TYPE)); + mv.pop(); + mv.visitInsn(RETURN); + + mv.visitEnd(); + mv.visitMaxs(0, 0); + + return w.toByteArray(); + } +} diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java --- a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java Thu Apr 04 13:54:51 2013 +0530 +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java Thu Apr 04 15:53:26 2013 +0200 @@ -25,110 +25,39 @@ 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.ARETURN; -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.RETURN; -import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.lookup.Lookup.MH; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; -import java.security.AllPermission; -import java.security.CodeSigner; -import java.security.CodeSource; -import java.security.Permissions; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; -import java.security.ProtectionDomain; -import java.security.SecureClassLoader; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Random; -import java.util.Set; import jdk.internal.dynalink.beans.StaticClass; import jdk.internal.dynalink.support.LinkRequestImpl; -import jdk.internal.org.objectweb.asm.ClassWriter; -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.internal.objects.NativeJava; -import jdk.nashorn.internal.runtime.Context; -import jdk.nashorn.internal.runtime.ECMAErrors; import jdk.nashorn.internal.runtime.ECMAException; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; -import jdk.nashorn.internal.runtime.ScriptRuntime; -import jdk.nashorn.internal.runtime.Undefined; /** *

A factory class that generates adapter classes. Adapter classes allow implementation of Java interfaces and * extending of Java classes from JavaScript. For every combination of a superclass to extend and interfaces to * implement (collectively: "original types"), exactly one adapter class is generated that extends the specified - * superclass and implements the specified interfaces. + * superclass and implements the specified interfaces. (But see the discussion of class-based overrides for exceptions.) *

* The adapter class is generated in a new secure class loader that inherits Nashorn's protection domain, and has either * one of the original types' class loader or the Nashorn's class loader as its parent - the parent class loader * is chosen so that all the original types and the Nashorn core classes are visible from it (as the adapter will have * constant pool references to ScriptObject and ScriptFunction classes). In case none of the candidate class loaders has - * visibility of all the required types, an error is thrown. - *

- * For every protected or public constructor in the extended class, the adapter class will have one or two public - * constructors (visibility of protected constructors in the extended class is promoted to public). In every case, for - * every original constructor, a new constructor taking a trailing ScriptObject argument preceded by original - * constructor arguments is present 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. - *

- * If the original types collectively have only one abstract method, or have several of them, but all share the - * same name, an additional constructor 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 {@code null} as its "this". - *

- * 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. - *

- * 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 new X(a, b) { ... } (which is a proprietary syntax extension Nashorn uses - * to resemble Java anonymous classes) is actually equivalent to new X(a, b, { ... }). + * visibility of all the required types, an error is thrown. The class uses {@link JavaAdapterBytecodeGenerator} to + * generate the adapter class itself; see its documentation for details about the generated class. *

* You normally don't use this class directly, but rather either create adapters from script using * {@link NativeJava#extend(Object, Object...)}, using the {@code new} operator on abstract classes and interfaces (see @@ -138,72 +67,6 @@ */ public final class JavaAdapterFactory { - private static final Type SCRIPT_FUNCTION_TYPE = Type.getType(ScriptFunction.class); - private static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class); - private static final Type OBJECT_TYPE = Type.getType(Object.class); - private static final Type STRING_TYPE = Type.getType(String.class); - private static final Type CONTEXT_TYPE = Type.getType(Context.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, Type.BOOLEAN_TYPE); - private static final String GET_HANDLE_FUNCTION_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE, - SCRIPT_FUNCTION_TYPE, METHOD_TYPE_TYPE, Type.BOOLEAN_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 PRIVILEGED_ACTION_TYPE = Type.getType(PrivilegedAction.class); - private static final Type UNSUPPORTED_OPERATION_TYPE = Type.getType(UnsupportedOperationException.class); - - private static final String THIS_CLASS_TYPE_NAME = Type.getInternalName(JavaAdapterFactory.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 CONTEXT_TYPE_NAME = CONTEXT_TYPE.getInternalName(); - private static final String OBJECT_TYPE_NAME = OBJECT_TYPE.getInternalName(); - private static final String PRIVILEGED_ACTION_TYPE_NAME = PRIVILEGED_ACTION_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 SCRIPT_OBJECT_TYPE_DESCRIPTOR = SCRIPT_OBJECT_TYPE.getDescriptor(); - private static final String GET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(SCRIPT_OBJECT_TYPE); - private static final String SET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, SCRIPT_OBJECT_TYPE); - private static final String GET_CLASS_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.getType(Class.class)); - private static final String PRIVILEGED_RUN_METHOD_DESCRIPTOR = Type.getMethodDescriptor(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/internal/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 = 238; //255 - 17; 17 is the maximum possible length for the global setter inner class suffix - - private static final String INIT = ""; - private static final String VOID_NOARG = Type.getMethodDescriptor(Type.VOID_TYPE); - private static final String GLOBAL_FIELD_NAME = "global"; - - /** - * Contains various outcomes for attempting to generate an adapter class. These are stored in AdapterInfo instances. - * We have a successful outcome (adapter class was generated) and four possible error outcomes: superclass is final, - * superclass is not public, superclass has no public or protected constructor, more than one superclass was - * specified. We don't throw exceptions when we try to generate the adapter, but rather just record these error - * conditions as they are still useful as partial outcomes, as Nashorn's linker can still successfully check whether - * the class can be autoconverted from a script function even when it is not possible to generate an adapter for it. - */ - private enum AdaptationOutcome { - SUCCESS, - ERROR_FINAL_CLASS, - ERROR_NON_PUBLIC_CLASS, - ERROR_NO_ACCESSIBLE_CONSTRUCTOR, - ERROR_MULTIPLE_SUPERCLASSES, - ERROR_NO_COMMON_LOADER - } - - /** - * Collection of methods we never override: Object.clone(), Object.finalize(). - */ - private static final Collection EXCLUDED = getExcludedMethods(); - /** * A mapping from an original Class object to AdapterInfo representing the adapter for the class it represents. */ @@ -214,127 +77,6 @@ } }; - private static final Random random = new SecureRandom(); - private static final ProtectionDomain GENERATED_PROTECTION_DOMAIN = createGeneratedProtectionDomain(); - - // This is the superclass for our generated adapter. - private final Class superClass; - // 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; - - // Binary name of the superClass - private final String superClassName; - // Binary name of the generated class. - private final String generatedClassName; - // Binary name of the PrivilegedAction inner class that is used to - private final String globalSetterClassName; - private final Set usedFieldNames = new HashSet<>(); - private final Set abstractMethodNames = new HashSet<>(); - private final String samName; - private final Set finalMethods = new HashSet<>(EXCLUDED); - private final Set methodInfos = new HashSet<>(); - private boolean autoConvertibleFromFunction = false; - - private final ClassWriter cw; - - /** - * Creates a factory that will produce the adapter type for the specified original type. - * @param originalType the type for which this factory will generate the adapter type. - * @param definingClassAndLoader the class in whose ClassValue we'll store the generated adapter, and its class loader. - * @throws AdaptationException if the adapter can not be generated for some reason. - */ - private JavaAdapterFactory(final Class superType, final List> interfaces, final ClassAndLoader definingClassAndLoader) throws AdaptationException { - assert superType != null && !superType.isInterface(); - assert interfaces != null; - assert definingClassAndLoader != null; - - this.superClass = superType; - this.commonLoader = findCommonLoader(definingClassAndLoader); - 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 JavaAdapterFactory.this.getCommonSuperClass(type1, type2); - } - }; - superClassName = Type.getInternalName(superType); - generatedClassName = getGeneratedClassName(superType, interfaces); - - // Randomize the name of the privileged global setter, to make it non-feasible to find. - final long l; - synchronized(random) { - l = random.nextLong(); - } - // NOTE: they way this class name is calculated affects the value of MAX_GENERATED_TYPE_NAME_LENGTH constant. If - // you change the calculation of globalSetterClassName, adjust the constant too. - globalSetterClassName = generatedClassName.concat("$" + Long.toHexString(l & Long.MAX_VALUE)); - cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, generatedClassName, null, superClassName, getInternalTypeNames(interfaces)); - cw.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); - usedFieldNames.add(GLOBAL_FIELD_NAME); - - gatherMethods(superType); - gatherMethods(interfaces); - samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null; - generateFields(); - generateConstructors(); - generateMethods(); - // } - cw.visitEnd(); - } - - private static String getGeneratedClassName(final Class superType, final List> 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> 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> 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; - } - - /** - * Utility method used by few other places in the code. Tests if the class has the abstract modifier and is not an - * array class. For some reason, array classes have the abstract modifier set in HotSpot JVM, and we don't want to - * treat array classes as abstract. - * @param clazz the inspected class - * @return true if the class is abstract and is not an array type. - */ - static boolean isAbstractClass(final Class clazz) { - return Modifier.isAbstract(clazz.getModifiers()) && !clazz.isArray(); - } - /** * Returns an adapter class for the specified original types. The adapter class extends/implements the original * class/interfaces. @@ -346,27 +88,68 @@ * in the list already implement/extend, or {@code java.lang.Object} in a list of types consisting purely of * interfaces) will result in a different adapter class, even though those adapter classes are functionally * identical; we deliberately don't want to incur the additional processing cost of canonicalizing type lists. + * @param classOverrides a JavaScript object with functions serving as the class-level overrides and + * implementations. These overrides are defined for all instances of the class, and can be further overridden on a + * per-instance basis by passing additional objects in the constructor. * @return an adapter class. See this class' documentation for details on the generated adapter class. * @throws ECMAException with a TypeError if the adapter class can not be generated because the original class is * final, non-public, or has no public or protected constructors. */ - public static StaticClass getAdapterClassFor(final Class[] types) { + public static StaticClass getAdapterClassFor(final Class[] types, ScriptObject classOverrides) { assert types != null && types.length > 0; - final AdapterInfo adapterInfo = getAdapterInfo(types); + return getAdapterInfo(types).getAdapterClassFor(classOverrides); + } - final StaticClass clazz = adapterInfo.adapterClass; - if (clazz != null) { - return clazz; - } - adapterInfo.adaptationOutcome.typeError(); + /** + * Returns a method handle representing a constructor that takes a single argument of the source type (which, + * really, should be one of {@link ScriptObject}, {@link ScriptFunction}, or {@link Object}, and returns an instance + * of the adapter for the target type. Used to implement the function autoconverters as well as the Nashorn's + * JSR-223 script engine's {@code getInterface()} method. + * @param sourceType the source type; should be either {@link ScriptObject}, {@link ScriptFunction}, or + * {@link Object}. In case of {@code Object}, it will return a method handle that dispatches to either the script + * object or function constructor at invocation based on the actual argument. + * @param targetType the target type, for which adapter instances will be created + * @return the constructor method handle. + * @throws Exception if anything goes wrong + */ + public static MethodHandle getConstructor(final Class sourceType, final Class targetType) throws Exception { + final StaticClass adapterClass = getAdapterClassFor(new Class[] { targetType }, null); + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public MethodHandle run() throws Exception { + return MH.bindTo(Bootstrap.getLinkerServices().getGuardedInvocation(new LinkRequestImpl(NashornCallSiteDescriptor.get( + "dyn:new", MethodType.methodType(targetType, StaticClass.class, sourceType), 0), false, + adapterClass, null)).getInvocation(), adapterClass); + } + }); + } - throw new AssertionError(); + /** + * Tells if the given Class is an adapter or support class + * @param clazz Class object + * @return true if the Class given is adapter or support class + */ + public static boolean isAdapterClass(Class clazz) { + return JavaAdapterClassLoader.isAdapterClass(clazz); + } + + /** + * Returns whether an instance of the specified class/interface can be generated from a ScriptFunction. Returns true + * iff: the adapter for the class/interface can be created, it is abstract (this includes interfaces), it has at + * least one abstract method, all the abstract methods share the same name, and it has a public or protected default + * constructor. Note that invoking this class will most likely result in the adapter class being defined in the JVM + * if it hasn't been already. + * @param clazz the inspected class + * @return true iff an instance of the specified class/interface can be generated from a ScriptFunction. + */ + static boolean isAutoConvertibleFromFunction(final Class clazz) { + return getAdapterInfo(new Class[] { clazz }).autoConvertibleFromFunction; } private static AdapterInfo getAdapterInfo(final Class[] types) { - final ClassAndLoader definingClassAndLoader = getDefiningClassAndLoader(types); + final ClassAndLoader definingClassAndLoader = ClassAndLoader.getDefiningClassAndLoader(types); - final Map>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.clazz); + final Map>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.getRepresentativeClass()); final List> typeList = types.length == 1 ? getSingletonClassList(types[0]) : Arrays.asList(types.clone()); AdapterInfo adapterInfo; synchronized(adapterInfoMap) { @@ -385,740 +168,6 @@ } /** - * Returns whether an instance of the specified class/interface can be generated from a ScriptFunction. Returns true - * iff: the adapter for the class/interface can be created, it is abstract (this includes interfaces), it has at - * least one abstract method, all the abstract methods share the same name, and it has a public or protected default - * constructor. Note that invoking this class will most likely result in the adapter class being defined in the JVM - * if it hasn't been already. - * @param clazz the inspected class - * @return true iff an instance of the specified class/interface can be generated from a ScriptFunction. - */ - static boolean isAutoConvertibleFromFunction(final Class clazz) { - return getAdapterInfo(new Class[] { clazz }).autoConvertibleFromFunction; - } - - /** - * Returns a method handle representing a constructor that takes a single argument of the source type (which, - * really, should be one of {@link ScriptObject}, {@link ScriptFunction}, or {@link Object}, and returns an instance - * of the adapter for the target type. Used to implement the function autoconverters as well as the Nashorn's - * JSR-223 script engine's {@code getInterface()} method. - * @param sourceType the source type; should be either {@link ScriptObject}, {@link ScriptFunction}, or - * {@link Object}. In case of {@code Object}, it will return a method handle that dispatches to either the script - * object or function constructor at invocation based on the actual argument. - * @param targetType the target type, for which adapter instances will be created - * @return the constructor method handle. - * @throws Exception if anything goes wrong - */ - public static MethodHandle getConstructor(final Class sourceType, final Class targetType) throws Exception { - final StaticClass adapterClass = getAdapterClassFor(new Class[] { targetType }); - return AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public MethodHandle run() throws Exception { - return MH.bindTo(Bootstrap.getLinkerServices().getGuardedInvocation(new LinkRequestImpl(NashornCallSiteDescriptor.get( - "dyn:new", MethodType.methodType(targetType, StaticClass.class, sourceType), 0), false, - adapterClass, null)).getInvocation(), adapterClass); - } - }); - } - - /** - * Finishes the bytecode generation for the adapter class that was started in the constructor, and loads the - * bytecode as a new class into the JVM. - * @return the generated adapter class - */ - private Class generateClass() { - final String binaryName = generatedClassName.replace('/', '.'); - try { - return Class.forName(binaryName, true, createClassLoader(commonLoader, binaryName, cw.toByteArray(), - globalSetterClassName.replace('/', '.'))); - } catch (final ClassNotFoundException e) { - throw new AssertionError(e); // cannot happen - } - } - - /** - * Tells if the given Class is an adapter or support class - * @param clazz Class object - * @return true if the Class given is adapter or support class - */ - public static boolean isAdapterClass(Class clazz) { - return clazz.getClassLoader() instanceof AdapterLoader; - } - - private static class AdapterLoader extends SecureClassLoader { - AdapterLoader(ClassLoader parent) { - super(parent); - } - } - - // Creation of class loader is in a separate static method so that it doesn't retain a reference to the factory - // instance. Note that the adapter class is created in the protection domain of the class/interface being - // extended/implemented, and only the privileged global setter action class is generated in the protection domain - // of Nashorn itself. Also note that the creation and loading of the global setter is deferred until it is - // required by JVM linker, which will only happen on first invocation of any of the adapted method. We could defer - // it even more by separating its invocation into a separate static method on the adapter class, but then someone - // with ability to introspect on the class and use setAccessible(true) on it could invoke the method. It's a - // security tradeoff... - private static ClassLoader createClassLoader(final ClassLoader parentLoader, final String className, - final byte[] classBytes, final String privilegedActionClassName) { - return new AdapterLoader(parentLoader) { - private final ClassLoader myLoader = getClass().getClassLoader(); - private final ProtectionDomain myProtectionDomain = getClass().getProtectionDomain(); - - @Override - public Class loadClass(final String name, final boolean resolve) throws ClassNotFoundException { - try { - return super.loadClass(name, resolve); - } catch (final SecurityException se) { - // we may be implementing an interface or extending a class that was - // loaded by a loader that prevents package.access. If so, it'd throw - // SecurityException for nashorn's classes!. For adapter's to work, we - // should be able to refer to nashorn classes. - if (name.startsWith("jdk.nashorn.internal.")) { - return myLoader.loadClass(name); - } - throw se; - } - } - - @Override - protected Class findClass(final String name) throws ClassNotFoundException { - if(name.equals(className)) { - final byte[] bytes = classBytes; - return defineClass(name, bytes, 0, bytes.length, GENERATED_PROTECTION_DOMAIN); - } else if(name.equals(privilegedActionClassName)) { - final byte[] bytes = generatePrivilegedActionClassBytes(privilegedActionClassName.replace('.', '/')); - return defineClass(name, bytes, 0, bytes.length, myProtectionDomain); - } else { - throw new ClassNotFoundException(name); - } - } - }; - } - - /** - * Generates a PrivilegedAction implementation class for invoking {@link Context#setGlobal(ScriptObject)} from the - * adapter class. - */ - private static byte[] generatePrivilegedActionClassBytes(final String className) { - final ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - // class GlobalSetter implements PrivilegedAction { - w.visit(Opcodes.V1_7, ACC_SUPER | ACC_FINAL, className, null, OBJECT_TYPE_NAME, new String[] { - PRIVILEGED_ACTION_TYPE_NAME - }); - - // private final ScriptObject global; - w.visitField(ACC_PRIVATE | ACC_FINAL, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, null).visitEnd(); - - // private GlobalSetter(ScriptObject global) { - InstructionAdapter mv = new InstructionAdapter(w.visitMethod(ACC_PRIVATE, INIT, - SET_GLOBAL_METHOD_DESCRIPTOR, null, new String[0])); - mv.visitCode(); - // super(); - mv.visitVarInsn(ALOAD, 0); - mv.invokespecial(OBJECT_TYPE_NAME, INIT, VOID_NOARG); - // this.global = global; - mv.visitVarInsn(ALOAD, 0); - mv.visitVarInsn(ALOAD, 1); - mv.putfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); - - mv.visitInsn(RETURN); - mv.visitEnd(); - mv.visitMaxs(0, 0); - - // public Object run() { - mv = new InstructionAdapter(w.visitMethod(ACC_PUBLIC, "run", PRIVILEGED_RUN_METHOD_DESCRIPTOR, null, - new String[0])); - mv.visitCode(); - // Context.setGlobal(this.global); - mv.visitVarInsn(ALOAD, 0); - mv.getfield(className, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); - mv.invokestatic(CONTEXT_TYPE_NAME, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR); - // return null; - mv.visitInsn(ACONST_NULL); - mv.visitInsn(ARETURN); - - mv.visitEnd(); - mv.visitMaxs(0, 0); - - // static void setGlobal(ScriptObject global) { - mv = new InstructionAdapter(w.visitMethod(ACC_STATIC, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR, null, - new String[0])); - mv.visitCode(); - // new GlobalSetter(ScriptObject global) - mv.anew(Type.getType("L" + className + ";")); - mv.dup(); - mv.visitVarInsn(ALOAD, 0); - mv.invokespecial(className, INIT, SET_GLOBAL_METHOD_DESCRIPTOR); - // AccessController.doPrivileged(...) - mv.invokestatic(Type.getInternalName(AccessController.class), "doPrivileged", Type.getMethodDescriptor( - OBJECT_TYPE, PRIVILEGED_ACTION_TYPE)); - mv.pop(); - mv.visitInsn(RETURN); - - mv.visitEnd(); - mv.visitMaxs(0, 0); - - return w.toByteArray(); - } - - private void generateFields() { - for (final MethodInfo mi: methodInfos) { - cw.visitField(ACC_PRIVATE | ACC_FINAL, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd(); - } - } - - 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) { - generateConstructor(ctor); - gotCtor = true; - } - } - if(!gotCtor) { - throw new AdaptationException(AdaptationOutcome.ERROR_NO_ACCESSIBLE_CONSTRUCTOR, superClass.getCanonicalName()); - } - } - - boolean isAutoConvertibleFromFunction() { - return autoConvertibleFromFunction; - } - - private void generateConstructor(final Constructor ctor) { - // Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the - // beginning of its parameter list. - generateConstructor(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. - generateConstructor(ctor, true); - } - } - - /** - * Generates a constructor for the 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 generateConstructor(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|Object as the last argument to the constructor - final Type extraArgumentType = fromFunction ? SCRIPT_FUNCTION_TYPE : 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 (this, args...) triggers generating (this, scriptObj, args...). - 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 - // (this, args..., scriptFn), then we're invoking super.(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()); - - // 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 overriden 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()); - } - mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString())); - mv.iconst(mi.method.isVarArgs() ? 1 : 0); - mv.invokestatic(THIS_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor); - } - mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); - } - - // Assign "this.global = Context.getGlobal()" - mv.visitVarInsn(ALOAD, 0); - invokeGetGlobal(mv); - mv.dup(); - mv.invokevirtual(OBJECT_TYPE_NAME, "getClass", GET_CLASS_METHOD_DESCRIPTOR); // check against null Context - mv.pop(); - mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); - - // Wrap up - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - private static void invokeGetGlobal(final InstructionAdapter mv) { - mv.invokestatic(CONTEXT_TYPE_NAME, "getGlobal", GET_GLOBAL_METHOD_DESCRIPTOR); - } - - private void invokeSetGlobal(final InstructionAdapter mv) { - mv.invokestatic(globalSetterClassName, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR); - } - - /** - * 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 - * @param varArg if the Java method for which the function is being adapted is a variable arity method - * @return the appropriately adapted method handle for invoking the script function. - */ - public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type, final boolean varArg) { - // JS "this" will be null for SAMs - return adaptHandle(fn.getBoundInvokeHandle(null), type, varArg); - } - - /** - * 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 - * @param varArg if the Java method for which the function is being adapted is a variable arity method - * @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. - */ - public static MethodHandle getHandle(final Object obj, final String name, final MethodType type, final boolean varArg) { - 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")) { - return null; - } - - final Object fnObj = sobj.get(name); - if (fnObj instanceof ScriptFunction) { - return adaptHandle(((ScriptFunction)fnObj).getBoundInvokeHandle(sobj), type, varArg); - } else if(fnObj == null || fnObj instanceof Undefined) { - return null; - } else { - throw typeError("not.a.function", name); - } - } - - private static MethodHandle adaptHandle(final MethodHandle handle, final MethodType type, final boolean varArg) { - return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(handle, type, varArg), type); - } - - /** - * 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 Set usedFieldNames) { - int i = 0; - String fieldName = getName(); - while(!usedFieldNames.add(fieldName)) { - fieldName = getName() + (i++); - } - methodHandleFieldName = fieldName; - } - } - - 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 int mod = method.getModifiers(); - final int access = ACC_PUBLIC | (method.isVarArgs() ? ACC_VARARGS : 0); - final Class[] exceptions = method.getExceptionTypes(); - final String[] exceptionNames = new String[exceptions.length]; - for (int i = 0; i < exceptions.length; ++i) { - exceptionNames[i] = Type.getInternalName(exceptions[i]); - } - 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(); - - // Determine the first index for a local variable - int nextLocalVar = 1; // this - for(final Type t: asmArgTypes) { - nextLocalVar += t.getSize(); - } - - final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(access, name, methodDesc, null, - exceptionNames)); - mv.visitCode(); - - final Label methodHandleNotNull = new Label(); - final Label methodEnd = new Label(); - - final Type returnType = Type.getType(type.returnType()); - - // Get the method handle - mv.visitVarInsn(ALOAD, 0); - mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR); - mv.visitInsn(DUP); // It'll remain on the stack all the way until the invocation - // Check if the method handle is null - mv.visitJumpInsn(IFNONNULL, methodHandleNotNull); - if(Modifier.isAbstract(mod)) { - // If it's null, and the method is abstract, throw an exception - mv.anew(UNSUPPORTED_OPERATION_TYPE); - mv.dup(); - mv.invokespecial(UNSUPPORTED_OPERATION_TYPE_NAME, INIT, VOID_NOARG); - mv.athrow(); - } else { - // If it's null, and the method is not abstract, delegate to super method. - mv.visitVarInsn(ALOAD, 0); - int nextParam = 1; - for(final Type t: asmArgTypes) { - mv.load(nextParam, t); - nextParam += t.getSize(); - } - mv.invokespecial(superClassName, name, methodDesc); - mv.areturn(returnType); - } - - mv.visitLabel(methodHandleNotNull); - final int currentGlobalVar = nextLocalVar++; - final int globalsDifferVar = nextLocalVar++; - - // Emit code for switching to the creating global - // ScriptObject currentGlobal = Context.getGlobal(); - invokeGetGlobal(mv); - mv.dup(); - mv.visitVarInsn(ASTORE, currentGlobalVar); - // if(this.global == currentGlobal) { - loadGlobalOnStack(mv); - final Label globalsDiffer = new Label(); - mv.ifacmpne(globalsDiffer); - // globalsDiffer = false - mv.iconst(0); // false - final Label proceed = new Label(); - mv.goTo(proceed); - mv.visitLabel(globalsDiffer); - // } else { - // Context.setGlobal(this.global); - loadGlobalOnStack(mv); - invokeSetGlobal(mv); - // globalsDiffer = true - mv.iconst(1); - - mv.visitLabel(proceed); - mv.visitVarInsn(ISTORE, globalsDifferVar); - - // Load all parameters back on stack for dynamic invocation. - int varOffset = 1; - for (final Type t : asmArgTypes) { - mv.load(varOffset, 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()); - final Label tryBlockEnd = new Label(); - mv.visitLabel(tryBlockEnd); - emitFinally(mv, currentGlobalVar, globalsDifferVar); - mv.areturn(returnType); - - // 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)); - // 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(); - mv.visitLabel(methodEnd); - - mv.visitLocalVariable("currentGlobal", SCRIPT_OBJECT_TYPE_DESCRIPTOR, null, methodHandleNotNull, methodEnd, currentGlobalVar); - mv.visitLocalVariable("globalsDiffer", Type.INT_TYPE.getDescriptor(), null, methodHandleNotNull, 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); - } - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - /** - * 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 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 void loadGlobalOnStack(final InstructionAdapter mv) { - mv.visitVarInsn(ALOAD, 0); - mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR); - } - - private static boolean isThrowableDeclared(final Class[] exceptions) { - for (final Class exception : exceptions) { - if (exception == Throwable.class) { - return true; - } - } - return false; - } - - /** - * 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) { - if (Modifier.isPublic(type.getModifiers())) { - final Method[] typeMethods = type.isInterface() ? type.getMethods() : type.getDeclaredMethods(); - - for (final Method typeMethod: typeMethods) { - final int m = typeMethod.getModifiers(); - if (Modifier.isStatic(m)) { - continue; - } - if (Modifier.isPublic(m) || Modifier.isProtected(m)) { - final MethodInfo mi = new MethodInfo(typeMethod); - if (Modifier.isFinal(m)) { - finalMethods.add(mi); - } else if (!finalMethods.contains(mi) && methodInfos.add(mi)) { - if (Modifier.isAbstract(m)) { - abstractMethodNames.add(mi.getName()); - } - mi.setIsCanonical(usedFieldNames); - } - } - } - } - // 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> classes) { - for(final Class c: classes) { - gatherMethods(c); - } - } - - /** - * 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 getExcludedMethods() { - return AccessController.doPrivileged(new PrivilegedAction>() { - @Override - public Collection run() { - try { - return Arrays.asList( - new MethodInfo(Object.class, "finalize"), - new MethodInfo(Object.class, "clone")); - } catch (final NoSuchMethodException e) { - throw new AssertionError(e); - } - } - }); - } - - private static ProtectionDomain createGeneratedProtectionDomain() { - // Generated classes need to have AllPermission. Since we require the "createClassLoader" RuntimePermission, we - // can create a class loader that'll load new classes with any permissions. Our generated classes are just - // delegating adapters, so having AllPermission can't cause anything wrong; the effective set of permissions for - // the executing script functions will still be limited by the permissions of the caller and the permissions of - // the script. - final Permissions permissions = new Permissions(); - permissions.add(new AllPermission()); - return new ProtectionDomain(new CodeSource(null, (CodeSigner[])null), permissions); - } - - private static class AdapterInfo { - final StaticClass adapterClass; - final boolean autoConvertibleFromFunction; - final AnnotatedAdaptationOutcome adaptationOutcome; - - AdapterInfo(final StaticClass adapterClass, final boolean autoConvertibleFromFunction) { - this.adapterClass = adapterClass; - this.autoConvertibleFromFunction = autoConvertibleFromFunction; - this.adaptationOutcome = AnnotatedAdaptationOutcome.SUCCESS; - } - - AdapterInfo(final AdaptationOutcome outcome, final String classList) { - this(new AnnotatedAdaptationOutcome(outcome, classList)); - } - - AdapterInfo(final AnnotatedAdaptationOutcome adaptationOutcome) { - this.adapterClass = null; - this.autoConvertibleFromFunction = false; - this.adaptationOutcome = adaptationOutcome; - } - } - - /** - * An adaptation outcome accompanied with a name of a class (or a list of multiple class names) that are the reason - * an adapter could not be generated. - */ - private static class AnnotatedAdaptationOutcome { - static final AnnotatedAdaptationOutcome SUCCESS = new AnnotatedAdaptationOutcome(AdaptationOutcome.SUCCESS, ""); - - private final AdaptationOutcome adaptationOutcome; - private final String classList; - - AnnotatedAdaptationOutcome(final AdaptationOutcome adaptationOutcome, final String classList) { - this.adaptationOutcome = adaptationOutcome; - this.classList = classList; - } - - void typeError() { - assert adaptationOutcome != AdaptationOutcome.SUCCESS; - throw ECMAErrors.typeError("extend." + adaptationOutcome, classList); - } - } - - /** * For a given class, create its adapter class and associated info. * @param type the class for which the adapter is created * @return the adapter info for the class. @@ -1130,17 +179,17 @@ final int mod = t.getModifiers(); if(!t.isInterface()) { if(superClass != null) { - return new AdapterInfo(AdaptationOutcome.ERROR_MULTIPLE_SUPERCLASSES, t.getCanonicalName() + " and " + superClass.getCanonicalName()); + return new AdapterInfo(AdaptationResult.Outcome.ERROR_MULTIPLE_SUPERCLASSES, t.getCanonicalName() + " and " + superClass.getCanonicalName()); } if (Modifier.isFinal(mod)) { - return new AdapterInfo(AdaptationOutcome.ERROR_FINAL_CLASS, t.getCanonicalName()); + return new AdapterInfo(AdaptationResult.Outcome.ERROR_FINAL_CLASS, t.getCanonicalName()); } superClass = t; } else { interfaces.add(t); } if(!Modifier.isPublic(mod)) { - return new AdapterInfo(AdaptationOutcome.ERROR_NON_PUBLIC_CLASS, t.getCanonicalName()); + return new AdapterInfo(AdaptationResult.Outcome.ERROR_NON_PUBLIC_CLASS, t.getCanonicalName()); } } final Class effectiveSuperClass = superClass == null ? Object.class : superClass; @@ -1148,211 +197,78 @@ @Override public AdapterInfo run() { try { - final JavaAdapterFactory factory = new JavaAdapterFactory(effectiveSuperClass, interfaces, definingClassAndLoader); - return new AdapterInfo(StaticClass.forClass(factory.generateClass()), - factory.isAutoConvertibleFromFunction()); + return new AdapterInfo(effectiveSuperClass, interfaces, definingClassAndLoader); } catch (final AdaptationException e) { - return new AdapterInfo(e.outcome); + return new AdapterInfo(e.getAdaptationResult()); } } }); } - @SuppressWarnings("serial") - private static class AdaptationException extends Exception { - private final AnnotatedAdaptationOutcome outcome; - AdaptationException(final AdaptationOutcome outcome, final String classList) { - this.outcome = new AnnotatedAdaptationOutcome(outcome, classList); - } - } - - 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 "java/lang/Object"; - } - 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 class AdapterInfo { + private static final ClassAndLoader SCRIPT_OBJECT_LOADER = new ClassAndLoader(ScriptObject.class, true); - /** - * Choose between the passed class loader and the class loader that defines the ScriptObject class, based on which - * of the two can see the classes in both. - * @param classAndLoader the loader and a representative class from it that will be used to add the generated - * adapter to its ADAPTER_INFO_MAPS. - * @return the class loader that sees both the specified class and Nashorn classes. - * @throws IllegalStateException if no such class loader is found. - */ - private static ClassLoader findCommonLoader(final ClassAndLoader classAndLoader) throws AdaptationException { - final ClassLoader loader = classAndLoader.getLoader(); - if (canSeeClass(loader, ScriptObject.class)) { - return loader; - } - - final ClassLoader nashornLoader = ScriptObject.class.getClassLoader(); - if(canSeeClass(nashornLoader, classAndLoader.clazz)) { - return nashornLoader; - } + private final ClassLoader commonLoader; + private final JavaAdapterClassLoader adapterGenerator; + // Cacheable adapter class that is shared by all adapter instances that don't have class overrides, only + // instance overrides. + final StaticClass instanceAdapterClass; + final boolean autoConvertibleFromFunction; + final AdaptationResult adaptationResult; - throw new AdaptationException(AdaptationOutcome.ERROR_NO_COMMON_LOADER, classAndLoader.clazz.getCanonicalName()); - } - - private static boolean canSeeClass(final ClassLoader cl, final Class clazz) { - try { - return Class.forName(clazz.getName(), false, cl) == clazz; - } catch (final ClassNotFoundException e) { - return false; - } - } - - /** - * Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the - * list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a - * class loader that can also see all other types is returned. If there is no such loader, an exception is thrown. - * @param types the input types - * @return the first type from the array that is defined in a class loader that can also see all other types. - */ - private static ClassAndLoader getDefiningClassAndLoader(final Class[] types) { - // Short circuit the cheap case - if(types.length == 1) { - return new ClassAndLoader(types[0], false); + AdapterInfo(Class superClass, List> interfaces, ClassAndLoader definingLoader) throws AdaptationException { + this.commonLoader = findCommonLoader(definingLoader); + final JavaAdapterBytecodeGenerator gen = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, false); + this.autoConvertibleFromFunction = gen.isAutoConvertibleFromFunction(); + this.instanceAdapterClass = gen.createAdapterClassLoader().generateClass(commonLoader); + this.adapterGenerator = new JavaAdapterBytecodeGenerator(superClass, interfaces, commonLoader, true).createAdapterClassLoader(); + this.adaptationResult = AdaptationResult.SUCCESSFUL_RESULT; } - return AccessController.doPrivileged(new PrivilegedAction() { - @Override - public ClassAndLoader run() { - return getDefiningClassAndLoaderPrivileged(types); - } - }); - } + AdapterInfo(final AdaptationResult.Outcome outcome, final String classList) { + this(new AdaptationResult(outcome, classList)); + } - private static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class[] types) { - final Collection maximumVisibilityLoaders = getMaximumVisibilityLoaders(types); - - final Iterator it = maximumVisibilityLoaders.iterator(); - if(maximumVisibilityLoaders.size() == 1) { - // Fortunate case - single maximally specific class loader; return its representative class. - return it.next(); + AdapterInfo(final AdaptationResult adaptationResult) { + this.commonLoader = null; + this.adapterGenerator = null; + this.instanceAdapterClass = null; + this.autoConvertibleFromFunction = false; + this.adaptationResult = adaptationResult; } - // Ambiguity; throw an error. - assert maximumVisibilityLoaders.size() > 1; // basically, can't be zero - final StringBuilder b = new StringBuilder(); - b.append(it.next().clazz.getCanonicalName()); - while(it.hasNext()) { - b.append(", ").append(it.next().clazz.getCanonicalName()); - } - throw typeError("extend.ambiguous.defining.class", b.toString()); - } - - /** - * Given an array of types, return a subset of their class loaders that are maximal according to the - * "can see other loaders' classes" relation, which is presumed to be a partial ordering. - * @param types types - * @return a collection of maximum visibility class loaders. It is guaranteed to have at least one element. - */ - private static Collection getMaximumVisibilityLoaders(final Class[] types) { - final List maximumVisibilityLoaders = new LinkedList<>(); - outer: for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) { - final Iterator it = maximumVisibilityLoaders.iterator(); - while(it.hasNext()) { - final ClassAndLoader existingMax = it.next(); - final boolean candidateSeesExisting = canSeeClass(maxCandidate.getRetrievedLoader(), existingMax.clazz); - final boolean exitingSeesCandidate = canSeeClass(existingMax.getRetrievedLoader(), maxCandidate.clazz); - if(candidateSeesExisting) { - if(!exitingSeesCandidate) { - // The candidate sees the the existing maximum, so drop the existing one as it's no longer maximal. - it.remove(); - } - // NOTE: there's also the anomalous case where both loaders see each other. Not sure what to do - // about that one, as two distinct class loaders both seeing each other's classes is weird and - // violates the assumption that the relation "sees others' classes" is a partial ordering. We'll - // just not do anything, and treat them as incomparable; hopefully some later class loader that - // comes along can eliminate both of them, if it can not, we'll end up with ambiguity anyway and - // throw an error at the end. - } else if(exitingSeesCandidate) { - // Existing sees the candidate, so drop the candidate. - continue outer; - } + StaticClass getAdapterClassFor(ScriptObject classOverrides) { + if(adaptationResult.getOutcome() != AdaptationResult.Outcome.SUCCESS) { + throw adaptationResult.typeError(); } - // If we get here, no existing maximum visibility loader could see the candidate, so the candidate is a new - // maximum. - maximumVisibilityLoaders.add(maxCandidate); - } - return maximumVisibilityLoaders; - } - - private static Collection getClassLoadersForTypes(final Class[] types) { - final Map classesAndLoaders = new LinkedHashMap<>(); - for(final Class c: types) { - final ClassAndLoader cl = new ClassAndLoader(c, true); - if(!classesAndLoaders.containsKey(cl)) { - classesAndLoaders.put(cl, cl); + if(classOverrides == null) { + return instanceAdapterClass; } - } - return classesAndLoaders.keySet(); - } - - /** - * A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its - * equals/hashCode is defined in terms of the identity of the class loader. - */ - private static final class ClassAndLoader { - private final Class clazz; - // Don't access this directly; most of the time, use getRetrievedLoader(), or if you know what you're doing, - // getLoader(). - private ClassLoader loader; - // We have mild affinity against eagerly retrieving the loader, as we need to do it in a privileged block. For - // the most basic case of looking up an already-generated adapter info for a single type, we avoid it. - private boolean loaderRetrieved; - - ClassAndLoader(final Class clazz, final boolean retrieveLoader) { - this.clazz = clazz; - if(retrieveLoader) { - retrieveLoader(); + JavaAdapterServices.setClassOverrides(classOverrides); + try { + return adapterGenerator.generateClass(commonLoader); + } finally { + JavaAdapterServices.setClassOverrides(null); } } - ClassLoader getLoader() { - if(!loaderRetrieved) { - retrieveLoader(); + /** + * Choose between the passed class loader and the class loader that defines the ScriptObject class, based on which + * of the two can see the classes in both. + * @param classAndLoader the loader and a representative class from it that will be used to add the generated + * adapter to its ADAPTER_INFO_MAPS. + * @return the class loader that sees both the specified class and Nashorn classes. + * @throws IllegalStateException if no such class loader is found. + */ + private static ClassLoader findCommonLoader(final ClassAndLoader classAndLoader) throws AdaptationException { + if(classAndLoader.canSee(SCRIPT_OBJECT_LOADER)) { + return classAndLoader.getLoader(); } - return getRetrievedLoader(); - } - - ClassLoader getRetrievedLoader() { - assert loaderRetrieved; - return loader; - } + if (SCRIPT_OBJECT_LOADER.canSee(classAndLoader)) { + return SCRIPT_OBJECT_LOADER.getLoader(); + } - private void retrieveLoader() { - loader = clazz.getClassLoader(); - loaderRetrieved = true; - } - - @Override - public boolean equals(final Object obj) { - return obj instanceof ClassAndLoader && ((ClassAndLoader)obj).getRetrievedLoader() == getRetrievedLoader(); - } - - @Override - public int hashCode() { - return System.identityHashCode(getRetrievedLoader()); + throw new AdaptationException(AdaptationResult.Outcome.ERROR_NO_COMMON_LOADER, classAndLoader.getRepresentativeClass().getCanonicalName()); } } } diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/JavaAdapterGeneratorBase.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterGeneratorBase.java Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,55 @@ +/* + * 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 jdk.internal.org.objectweb.asm.Type; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptObject; + +/** + * Base class for both {@link JavaAdapterBytecodeGenerator} and {@link JavaAdapterClassLoader}, containing those + * bytecode types, type names and method descriptor that are used by both. + */ +abstract class JavaAdapterGeneratorBase { + static final Type CONTEXT_TYPE = Type.getType(Context.class); + static final Type OBJECT_TYPE = Type.getType(Object.class); + static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class); + + static final String CONTEXT_TYPE_NAME = CONTEXT_TYPE.getInternalName(); + static final String OBJECT_TYPE_NAME = OBJECT_TYPE.getInternalName(); + + static final String INIT = ""; + + static final String GLOBAL_FIELD_NAME = "global"; + + static final String SCRIPT_OBJECT_TYPE_DESCRIPTOR = SCRIPT_OBJECT_TYPE.getDescriptor(); + + static final String SET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, SCRIPT_OBJECT_TYPE); + static final String VOID_NOARG_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE); + + protected JavaAdapterGeneratorBase() { + } +} diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,114 @@ +/* + * 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.nashorn.internal.runtime.ECMAErrors.typeError; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +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. + */ +public class JavaAdapterServices { + private static final ThreadLocal classOverrides = new ThreadLocal<>(); + + private JavaAdapterServices() { + } + + /** + * 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 + * @param varArg if the Java method for which the function is being adapted is a variable arity method + * @return the appropriately adapted method handle for invoking the script function. + */ + public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type, final boolean varArg) { + // JS "this" will be null for SAMs + return adaptHandle(fn.getBoundInvokeHandle(null), type, varArg); + } + + /** + * 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 + * @param varArg if the Java method for which the function is being adapted is a variable arity method + * @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. + */ + public static MethodHandle getHandle(final Object obj, final String name, final MethodType type, final boolean varArg) { + 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")) { + return null; + } + + final Object fnObj = sobj.get(name); + if (fnObj instanceof ScriptFunction) { + return adaptHandle(((ScriptFunction)fnObj).getBoundInvokeHandle(sobj), type, varArg); + } else if(fnObj == null || fnObj instanceof Undefined) { + return null; + } else { + throw typeError("not.a.function", name); + } + } + + /** + * Returns a thread-local JS object used to define methods for the adapter class being initialized on the current + * thread. This method is public solely for implementation reasons, so the adapter classes can invoke it from their + * static initializers. + * @return the thread-local JS object used to define methods for the class being initialized. + */ + public static ScriptObject getClassOverrides() { + final ScriptObject overrides = classOverrides.get(); + assert overrides != null; + return overrides; + } + + static void setClassOverrides(ScriptObject overrides) { + classOverrides.set(overrides); + } + + private static MethodHandle adaptHandle(final MethodHandle handle, final MethodType type, final boolean varArg) { + return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(handle, type, varArg), type); + } +} diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/NashornLinker.java --- a/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java Thu Apr 04 13:54:51 2013 +0530 +++ b/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java Thu Apr 04 15:53:26 2013 +0200 @@ -29,6 +29,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.reflect.Modifier; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.ConversionComparator; import jdk.internal.dynalink.linker.GuardedInvocation; @@ -131,10 +132,22 @@ } private static boolean isAutoConvertibleFromFunction(final Class clazz) { - return JavaAdapterFactory.isAbstractClass(clazz) && !ScriptObject.class.isAssignableFrom(clazz) && + return isAbstractClass(clazz) && !ScriptObject.class.isAssignableFrom(clazz) && JavaAdapterFactory.isAutoConvertibleFromFunction(clazz); } + /** + * Utility method used by few other places in the code. Tests if the class has the abstract modifier and is not an + * array class. For some reason, array classes have the abstract modifier set in HotSpot JVM, and we don't want to + * treat array classes as abstract. + * @param clazz the inspected class + * @return true if the class is abstract and is not an array type. + */ + static boolean isAbstractClass(final Class clazz) { + return Modifier.isAbstract(clazz.getModifiers()) && !clazz.isArray(); + } + + @Override public Comparison compareConversion(final Class sourceType, final Class targetType1, final Class targetType2) { if(ScriptObject.class.isAssignableFrom(sourceType)) { diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/linker/NashornStaticClassLinker.java --- a/src/jdk/nashorn/internal/runtime/linker/NashornStaticClassLinker.java Thu Apr 04 13:54:51 2013 +0530 +++ b/src/jdk/nashorn/internal/runtime/linker/NashornStaticClassLinker.java Thu Apr 04 15:53:26 2013 +0200 @@ -68,10 +68,10 @@ if ("new".equals(desc.getNameToken(CallSiteDescriptor.OPERATOR))) { final Class receiverClass = ((StaticClass) self).getRepresentedClass(); // Is the class abstract? (This includes interfaces.) - if (JavaAdapterFactory.isAbstractClass(receiverClass)) { + if (NashornLinker.isAbstractClass(receiverClass)) { // Change this link request into a link request on the adapter class. final Object[] args = request.getArguments(); - args[0] = JavaAdapterFactory.getAdapterClassFor(new Class[] { receiverClass }); + args[0] = JavaAdapterFactory.getAdapterClassFor(new Class[] { receiverClass }, null); final LinkRequest adapterRequest = request.replaceArguments(request.getCallSiteDescriptor(), args); final GuardedInvocation gi = checkNullConstructor(delegate(linkerServices, adapterRequest), receiverClass); // Finally, modify the guard to test for the original abstract class. diff -r 0548c134b9ac -r 5eb1427b6a6d src/jdk/nashorn/internal/runtime/resources/Messages.properties --- a/src/jdk/nashorn/internal/runtime/resources/Messages.properties Thu Apr 04 13:54:51 2013 +0530 +++ b/src/jdk/nashorn/internal/runtime/resources/Messages.properties Thu Apr 04 15:53:26 2013 +0200 @@ -113,6 +113,7 @@ type.error.cant.convert.to.java.number=Cannot convert object of type {0} to a Java argument of number type type.error.cant.convert.to.javascript.array=Can only convert Java arrays and lists to JavaScript arrays. Can't convert object of type {0}. type.error.extend.expects.at.least.one.argument=Java.extend needs at least one argument. +type.error.extend.expects.at.least.one.type.argument=Java.extend needs at least one type argument. type.error.extend.expects.java.types=Java.extend needs Java types as its arguments. type.error.extend.ambiguous.defining.class=There is no class loader that can see all of {0} at once. type.error.extend.ERROR_FINAL_CLASS=Can not extend final class {0}. diff -r 0548c134b9ac -r 5eb1427b6a6d test/script/basic/javaclassoverrides.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/script/basic/javaclassoverrides.js Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,86 @@ +/* + * 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. + * + * 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. + */ + +/** + * Check behavior of class-level overrides. + * + * @test + * @run + */ + + +// Make two classes with class overrides + +var R1 = Java.extend(java.lang.Runnable, { + run: function() { + print("R1.run() invoked") + } +}) + +var R2 = Java.extend(java.lang.Runnable, { + run: function() { + print("R2.run() invoked") + } +}) + +var r1 = new R1 +var r2 = new R2 +// Create one with an instance-override too +var r3 = new R2(function() { print("r3.run() invoked") }) + +// Run 'em - we're passing them through a Thread to make sure they indeed +// are full-blown Runnables +function runInThread(r) { + var t = new java.lang.Thread(r) + t.start() + t.join() +} +runInThread(r1) +runInThread(r2) +runInThread(r3) + +// Two class-override classes differ +print("r1.class != r2.class: " + (r1.class != r2.class)) +// However, adding instance-overrides doesn't change the class +print("r2.class == r3.class: " + (r2.class == r3.class)) + +function checkAbstract(r) { + try { + r.run() + print("Expected to fail!") + } catch(e) { + print("Got exception: " + e) + } +} + +// Check we're hitting UnsupportedOperationException if neither class +// overrides nor instance overrides are present +var RAbstract = Java.extend(java.lang.Runnable, {}) +checkAbstract(new RAbstract()) // class override (empty) +checkAbstract(new RAbstract() {}) // class+instance override (empty) + +// Check we delegate to superclass if neither class +// overrides nor instance overrides are present +var ExtendsList = Java.extend(java.util.ArrayList, {}) +print("(new ExtendsList).size() = " + (new ExtendsList).size()) +print("(new ExtendsList(){}).size() = " + (new ExtendsList(){}).size()) \ No newline at end of file diff -r 0548c134b9ac -r 5eb1427b6a6d test/script/basic/javaclassoverrides.js.EXPECTED --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/script/basic/javaclassoverrides.js.EXPECTED Thu Apr 04 15:53:26 2013 +0200 @@ -0,0 +1,9 @@ +R1.run() invoked +R2.run() invoked +r3.run() invoked +r1.class != r2.class: true +r2.class == r3.class: true +Got exception: java.lang.UnsupportedOperationException +Got exception: java.lang.UnsupportedOperationException +(new ExtendsList).size() = 0 +(new ExtendsList(){}).size() = 0