8011544: Allow subclassing Java classes from script without creating instances
Reviewed-by: jlaskey, sundar
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java Thu Apr 04 13:54:51 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java Thu Apr 04 15:53:26 2013 +0200
@@ -394,22 +394,56 @@
* </pre>
* We can see several important concepts in the above example:
* <ul>
- * <li>Every Java class will have exactly one extender subclass in Nashorn - repeated invocations of {@code extend}
- * for the same type will yield the same extender type. It's a generic adapter that delegates to whatever JavaScript
- * functions its implementation object has on a per-instance basis.</li>
+ * <li>Every specified list of Java types will have exactly one extender subclass in Nashorn - repeated invocations
+ * of {@code extend} for the same list of types will yield the same extender type. It's a generic adapter that
+ * delegates to whatever JavaScript functions its implementation object has on a per-instance basis.</li>
* <li>If the Java method is overloaded (as in the above example {@code List.add()}), then your JavaScript adapter
* must be prepared to deal with all overloads.</li>
* <li>You can't invoke {@code super.*()} from adapters for now.</li>
+ * <li>It is also possible to specify an ordinary JavaScript object as the last argument to {@code extend}. In that
+ * case, it is treated as a class-level override. {@code extend} will return an extender class where all instances
+ * will have the methods implemented by functions on that object, just as if that object were passed as the last
+ * argument to their constructor. Example:
+ * <pre>
+ * var Runnable = Java.type("java.lang.Runnable")
+ * var R1 = Java.extend(Runnable, {
+ * run: function() {
+ * print("R1.run() invoked!")
+ * }
+ * })
+ * var r1 = new R1
+ * var t = new java.lang.Thread(r1)
+ * t.start()
+ * t.join()
+ * </pre>
+ * As you can see, you don't have to pass any object when you create a new instance of {@code R1} as its
+ * {@code run()} function was defined already when extending the class. Of course, you can still provide
+ * instance-level overrides on these objects. The order of precedence is instance-level method, class-level method,
+ * superclass method, or {@code UnsupportedOperationException} if the superclass method is abstract. If we continue
+ * our previous example:
+ * <pre>
+ * var r2 = new R1(function() { print("r2.run() invoked!") })
+ * r2.run()
+ * </pre>
+ * We'll see it'll print {@code "r2.run() invoked!"}, thus overriding on instance-level the class-level behavior.
+ * </li>
* </ul>
* @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);
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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<ClassAndLoader>() {
+ @Override
+ public ClassAndLoader run() {
+ return getDefiningClassAndLoaderPrivileged(types);
+ }
+ });
+ }
+
+ static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class<?>[] types) {
+ final Collection<ClassAndLoader> maximumVisibilityLoaders = getMaximumVisibilityLoaders(types);
+
+ final Iterator<ClassAndLoader> 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<ClassAndLoader> getMaximumVisibilityLoaders(final Class<?>[] types) {
+ final List<ClassAndLoader> maximumVisibilityLoaders = new LinkedList<>();
+ outer: for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) {
+ final Iterator<ClassAndLoader> 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<ClassAndLoader> getClassLoadersForTypes(final Class<?>[] types) {
+ final Map<ClassAndLoader, ClassAndLoader> 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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}.
+ * </p><p>
+ * 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).
+ * <ul>
+ * <li>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.
+ * </li>
+ * <li>
+ * If the original types collectively have only one abstract method, or have several of them, but all share the
+ * same name, an additional constructor 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".
+ * </li>
+ * <li>
+ * 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.
+ * </li>
+ * </ul>
+ * </p><p>
+ * For adapter methods that return values, all the JavaScript-to-Java conversions supported by Nashorn will be in effect
+ * to coerce the JavaScript function return value to the expected Java return type.
+ * </p><p>
+ * Since we are adding a trailing argument to the generated constructors in the adapter class, they will never be
+ * declared as variable arity, even if the original constructor in the superclass was declared as variable arity. The
+ * reason we are passing the additional argument at the end of the argument list instead at the front is that the
+ * source-level script expression <code>new X(a, b) { ... }</code> (which is a proprietary syntax extension Nashorn uses
+ * to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>.
+ * </p><p>
+ * It is possible to create two different 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 = "<clinit>";
+ private static final String STATIC_GLOBAL_FIELD_NAME = "staticGlobal";
+
+ /**
+ * Collection of methods we never override: Object.clone(), Object.finalize().
+ */
+ private static final Collection<MethodInfo> 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<String> usedFieldNames = new HashSet<>();
+ private final Set<String> abstractMethodNames = new HashSet<>();
+ private final String samName;
+ private final Set<MethodInfo> finalMethods = new HashSet<>(EXCLUDED);
+ private final Set<MethodInfo> methodInfos = new HashSet<>();
+ private boolean autoConvertibleFromFunction = false;
+
+ private final 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<Class<?>> 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<Class<?>> interfaces) {
+ // The class we use to primarily name our adapter is either the superclass, or if it is Object (meaning we're
+ // just implementing interfaces or extending Object), then the first implemented interface or Object.
+ final Class<?> namingType = superType == Object.class ? (interfaces.isEmpty()? Object.class : interfaces.get(0)) : superType;
+ final Package pkg = namingType.getPackage();
+ final String namingTypeName = Type.getInternalName(namingType);
+ final StringBuilder buf = new StringBuilder();
+ if (namingTypeName.startsWith(JAVA_PACKAGE_PREFIX) || pkg == null || pkg.isSealed()) {
+ // Can't define new classes in java.* packages
+ buf.append(ADAPTER_PACKAGE_PREFIX).append(namingTypeName);
+ } else {
+ buf.append(namingTypeName).append(ADAPTER_CLASS_NAME_SUFFIX);
+ }
+ final Iterator<Class<?>> it = interfaces.iterator();
+ if(superType == Object.class && it.hasNext()) {
+ it.next(); // Skip first interface, it was used to primarily name the adapter
+ }
+ // Append interface names to the adapter name
+ while(it.hasNext()) {
+ buf.append("$$").append(it.next().getSimpleName());
+ }
+ return buf.toString().substring(0, Math.min(MAX_GENERATED_TYPE_NAME_LENGTH, buf.length()));
+ }
+
+ /**
+ * Given a list of class objects, return an array with their binary names. Used to generate the array of interface
+ * names to implement.
+ * @param classes the classes
+ * @return an array of names
+ */
+ private static String[] getInternalTypeNames(final List<Class<?>> classes) {
+ final int interfaceCount = classes.size();
+ final String[] interfaceNames = new String[interfaceCount];
+ for(int i = 0; i < interfaceCount; ++i) {
+ interfaceNames[i] = Type.getInternalName(classes.get(i));
+ }
+ return interfaceNames;
+ }
+
+ private void generateHandleFields() {
+ 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 <init>(this, args...) triggers generating <init>(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
+ // <init>(this, args..., scriptFn), then we're invoking super.<init>(this, args...).
+ mv.visitVarInsn(ALOAD, 0);
+ final Class<?>[] argTypes = ctor.getParameterTypes();
+ int offset = 1; // First arg is at position 1, after this.
+ for (int i = 0; i < argLen; ++i) {
+ final Type argType = Type.getType(argTypes[i]);
+ mv.load(offset, argType);
+ offset += argType.getSize();
+ }
+ mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor());
+
+ // 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<String> usedFieldNames, boolean classOverride) {
+ methodHandleInstanceFieldName = nextName(usedFieldNames);
+ if(classOverride) {
+ methodHandleClassFieldName = nextName(usedFieldNames);
+ }
+ }
+
+ String nextName(final Set<String> 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<Class<?>> 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<MethodInfo> getExcludedMethods() {
+ return AccessController.doPrivileged(new PrivilegedAction<Collection<MethodInfo>>() {
+ @Override
+ public Collection<MethodInfo> run() {
+ try {
+ return Arrays.asList(
+ new MethodInfo(Object.class, "finalize"),
+ new MethodInfo(Object.class, "clone"));
+ } catch (final NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+ });
+ }
+
+ 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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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<StaticClass>() {
+ @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();
+ }
+}
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java Thu Apr 04 13:54:51 2013 +0530
+++ b/nashorn/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;
/**
* <p>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.)
* </p><p>
* 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.
- * </p><p>
- * 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.
- * </p><p>
- * 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".
- * </p><p>
- * For adapter methods that return values, all the JavaScript-to-Java conversions supported by Nashorn will be in effect
- * to coerce the JavaScript function return value to the expected Java return type.
- * </p><p>
- * Since we are adding a trailing argument to the generated constructors in the adapter class, they will never be
- * declared as variable arity, even if the original constructor in the superclass was declared as variable arity. The
- * reason we are passing the additional argument at the end of the argument list instead at the front is that the
- * source-level script expression <code>new X(a, b) { ... }</code> (which is a proprietary syntax extension Nashorn uses
- * to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>.
+ * 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.
* </p><p>
* 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 = "<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<MethodInfo> 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<String> usedFieldNames = new HashSet<>();
- private final Set<String> abstractMethodNames = new HashSet<>();
- private final String samName;
- private final Set<MethodInfo> finalMethods = new HashSet<>(EXCLUDED);
- private final Set<MethodInfo> methodInfos = new HashSet<>();
- private boolean autoConvertibleFromFunction = false;
-
- private final 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<Class<?>> 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<Class<?>> interfaces) {
- // The class we use to primarily name our adapter is either the superclass, or if it is Object (meaning we're
- // just implementing interfaces or extending Object), then the first implemented interface or Object.
- final Class<?> namingType = superType == Object.class ? (interfaces.isEmpty()? Object.class : interfaces.get(0)) : superType;
- final Package pkg = namingType.getPackage();
- final String namingTypeName = Type.getInternalName(namingType);
- final StringBuilder buf = new StringBuilder();
- if (namingTypeName.startsWith(JAVA_PACKAGE_PREFIX) || pkg == null || pkg.isSealed()) {
- // Can't define new classes in java.* packages
- buf.append(ADAPTER_PACKAGE_PREFIX).append(namingTypeName);
- } else {
- buf.append(namingTypeName).append(ADAPTER_CLASS_NAME_SUFFIX);
- }
- final Iterator<Class<?>> it = interfaces.iterator();
- if(superType == Object.class && it.hasNext()) {
- it.next(); // Skip first interface, it was used to primarily name the adapter
- }
- // Append interface names to the adapter name
- while(it.hasNext()) {
- buf.append("$$").append(it.next().getSimpleName());
- }
- return buf.toString().substring(0, Math.min(MAX_GENERATED_TYPE_NAME_LENGTH, buf.length()));
- }
-
- /**
- * Given a list of class objects, return an array with their binary names. Used to generate the array of interface
- * names to implement.
- * @param classes the classes
- * @return an array of names
- */
- private static String[] getInternalTypeNames(final List<Class<?>> classes) {
- final int interfaceCount = classes.size();
- final String[] interfaceNames = new String[interfaceCount];
- for(int i = 0; i < interfaceCount; ++i) {
- interfaceNames[i] = Type.getInternalName(classes.get(i));
- }
- return interfaceNames;
- }
-
- /**
- * 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<MethodHandle>() {
+ @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<List<Class<?>>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.clazz);
+ final Map<List<Class<?>>, AdapterInfo> adapterInfoMap = ADAPTER_INFO_MAPS.get(definingClassAndLoader.getRepresentativeClass());
final List<Class<?>> 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<MethodHandle>() {
- @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 <init>(this, args...) triggers generating <init>(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
- // <init>(this, args..., scriptFn), then we're invoking super.<init>(this, args...).
- mv.visitVarInsn(ALOAD, 0);
- final Class<?>[] argTypes = ctor.getParameterTypes();
- int offset = 1; // First arg is at position 1, after this.
- for (int i = 0; i < argLen; ++i) {
- final Type argType = Type.getType(argTypes[i]);
- mv.load(offset, argType);
- offset += argType.getSize();
- }
- mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor());
-
- // 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<String> 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<Class<?>> 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<MethodInfo> getExcludedMethods() {
- return AccessController.doPrivileged(new PrivilegedAction<Collection<MethodInfo>>() {
- @Override
- public Collection<MethodInfo> run() {
- try {
- return Arrays.asList(
- new MethodInfo(Object.class, "finalize"),
- new MethodInfo(Object.class, "clone"));
- } catch (final NoSuchMethodException e) {
- throw new AssertionError(e);
- }
- }
- });
- }
-
- 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<Class<?>> 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<ClassAndLoader>() {
- @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<ClassAndLoader> maximumVisibilityLoaders = getMaximumVisibilityLoaders(types);
-
- final Iterator<ClassAndLoader> 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<ClassAndLoader> getMaximumVisibilityLoaders(final Class<?>[] types) {
- final List<ClassAndLoader> maximumVisibilityLoaders = new LinkedList<>();
- outer: for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) {
- final Iterator<ClassAndLoader> 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<ClassAndLoader> getClassLoadersForTypes(final Class<?>[] types) {
- final Map<ClassAndLoader, ClassAndLoader> 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());
}
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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 = "<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() {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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<ScriptObject> 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);
+ }
+}
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java Thu Apr 04 13:54:51 2013 +0530
+++ b/nashorn/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)) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornStaticClassLinker.java Thu Apr 04 13:54:51 2013 +0530
+++ b/nashorn/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.
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties Thu Apr 04 13:54:51 2013 +0530
+++ b/nashorn/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}.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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