8162839: JavaAdapters do not work with ScriptObjectMirror objects
authorhannesw
Thu, 17 Nov 2016 13:39:30 +0100
changeset 42238 4d6b6b542dde
parent 42237 7ac7543bdd5e
child 42239 4a0120be6265
8162839: JavaAdapters do not work with ScriptObjectMirror objects Reviewed-by: sundar, jlaskey
nashorn/make/build.xml
nashorn/src/jdk.dynalink/share/classes/jdk/dynalink/beans/ClassString.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornLinker.java
nashorn/test/src/jdk/nashorn/internal/runtime/linker/test/JavaAdapterTest.java
--- a/nashorn/make/build.xml	Wed Nov 16 12:35:19 2016 -0800
+++ b/nashorn/make/build.xml	Thu Nov 17 13:39:30 2016 +0100
@@ -524,17 +524,19 @@
   <!-- only to be invoked as dependency of "test" target -->
   <target name="-test-classes-all" depends="jar" unless="test.class">
       <fileset id="test.classes" dir="${build.test.classes.dir}">
+          <include name="**/dynalink/beans/test/*Test.class"/>
+          <include name="**/dynalink/linker/support/test/*Test.class"/>
+          <include name="**/dynalink/support/test/*Test.class"/>
           <include name="**/dynalink/test/*Test.class"/>
-          <include name="**/dynalink/beans/test/*Test.class"/>
           <include name="**/api/javaaccess/test/*Test.class"/>
           <include name="**/api/scripting/test/*Test.class"/>
           <include name="**/api/tree/test/*Test.class"/>
           <include name="**/codegen/test/*Test.class"/>
           <include name="**/parser/test/*Test.class"/>
           <include name="**/runtime/test/*Test.class"/>
+          <include name="**/runtime/doubleconv/test/*Test.class"/>
           <include name="**/runtime/regexp/test/*Test.class"/>
           <include name="**/runtime/regexp/joni/test/*Test.class"/>
-          <include name="**/runtime/doubleconv/test/*Test.class"/>
           <include name="**/framework/*Test.class"/>
      </fileset>
   </target>
@@ -550,6 +552,7 @@
   <target name="-test-nosecurity" unless="test.class">
     <fileset id="test.nosecurity.classes" dir="${build.test.classes.dir}">
       <include name="**/framework/ScriptTest.class"/>
+      <include name="**/runtime/linker/test/*Test.class"/>
     </fileset>
     <testng outputdir="${build.nosecurity.test.results.dir}/${testResultsSubDir}" classfilesetref="test.nosecurity.classes"
        verbose="${testng.verbose}" haltonfailure="true" useDefaultListeners="false" listeners="${testng.listeners}" workingDir="${basedir}">
--- a/nashorn/src/jdk.dynalink/share/classes/jdk/dynalink/beans/ClassString.java	Wed Nov 16 12:35:19 2016 -0800
+++ b/nashorn/src/jdk.dynalink/share/classes/jdk/dynalink/beans/ClassString.java	Thu Nov 17 13:39:30 2016 +0100
@@ -88,6 +88,7 @@
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 import jdk.dynalink.internal.AccessControlContextFactory;
@@ -149,6 +150,11 @@
         return hashCode;
     }
 
+    @Override
+    public String toString() {
+        return "ClassString[" + Arrays.toString(classes) + "]";
+    }
+
     boolean isVisibleFrom(final ClassLoader classLoader) {
         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
             @Override
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java	Wed Nov 16 12:35:19 2016 -0800
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java	Thu Nov 17 13:39:30 2016 +0100
@@ -31,24 +31,13 @@
 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.AALOAD;
 import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
-import static jdk.internal.org.objectweb.asm.Opcodes.ARRAYLENGTH;
 import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE;
 import static jdk.internal.org.objectweb.asm.Opcodes.D2F;
-import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC;
-import static jdk.internal.org.objectweb.asm.Opcodes.GOTO;
 import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC;
-import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_0;
-import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPGE;
-import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL;
-import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
-import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE;
 import static jdk.internal.org.objectweb.asm.Opcodes.I2B;
 import static jdk.internal.org.objectweb.asm.Opcodes.I2S;
-import static jdk.internal.org.objectweb.asm.Opcodes.POP;
-import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC;
 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
 import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup;
 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
@@ -66,6 +55,7 @@
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
@@ -73,20 +63,14 @@
 import java.util.List;
 import java.util.Set;
 import jdk.internal.org.objectweb.asm.ClassWriter;
-import jdk.internal.org.objectweb.asm.FieldVisitor;
-import jdk.internal.org.objectweb.asm.Handle;
-import jdk.internal.org.objectweb.asm.Label;
-import jdk.internal.org.objectweb.asm.MethodVisitor;
-import jdk.internal.org.objectweb.asm.Opcodes;
-import jdk.internal.org.objectweb.asm.Type;
 import jdk.internal.org.objectweb.asm.Handle;
 import jdk.internal.org.objectweb.asm.Label;
 import jdk.internal.org.objectweb.asm.Opcodes;
 import jdk.internal.org.objectweb.asm.Type;
 import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
+import jdk.nashorn.api.scripting.ScriptObjectMirror;
 import jdk.nashorn.api.scripting.ScriptUtils;
 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
-import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome;
@@ -139,7 +123,8 @@
  * to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>.
  * </p><p>
  * It is possible to create two different adapter classes: those that can have class-level overrides, and those that can
- * have instance-level overrides. When {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject)} is invoked
+ * have instance-level overrides. When {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject, ProtectionDomain)}
+ * or {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject, Lookup)} 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
@@ -168,6 +153,7 @@
     private static final Type OBJECT_TYPE = Type.getType(Object.class);
     private static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class);
     private static final Type SCRIPT_FUNCTION_TYPE = Type.getType(ScriptFunction.class);
+    private static final Type SCRIPT_OBJECT_MIRROR_TYPE = Type.getType(ScriptObjectMirror.class);
 
     // JavaAdapterServices methods used in generated bytecode
     private static final Call CHECK_FUNCTION = lookupServiceMethod("checkFunction", ScriptFunction.class, Object.class, String.class);
@@ -182,6 +168,7 @@
     private static final Call TO_CHAR_PRIMITIVE = lookupServiceMethod("toCharPrimitive", char.class, Object.class);
     private static final Call UNSUPPORTED = lookupServiceMethod("unsupported", UnsupportedOperationException.class);
     private static final Call WRAP_THROWABLE = lookupServiceMethod("wrapThrowable", RuntimeException.class, Throwable.class);
+    private static final Call UNWRAP_MIRROR = lookupServiceMethod("unwrapMirror", ScriptObject.class, Object.class, boolean.class);
 
     // Other methods invoked by the generated bytecode
     private static final Call UNWRAP = staticCallNoLookup(ScriptUtils.class, "unwrap", Object.class, Object.class);
@@ -216,8 +203,7 @@
     private static final String GET_METHOD_PROPERTY_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE, SCRIPT_OBJECT_TYPE);
     private static final String VOID_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE);
 
-    static final String ADAPTER_PACKAGE_INTERNAL = "jdk/nashorn/javaadapters/";
-    static final String ADAPTER_PACKAGE = "jdk.nashorn.javaadapters";
+    private static final String ADAPTER_PACKAGE_INTERNAL = "jdk/nashorn/javaadapters/";
     private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 255;
 
     // Method name prefix for invoking super-methods
@@ -265,7 +251,7 @@
      * @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 {
+                                 final ClassLoader commonLoader, final boolean classOverride) throws AdaptationException {
         assert superClass != null && !superClass.isInterface();
         assert interfaces != null;
 
@@ -369,7 +355,7 @@
             // If the class is a SAM, allow having ScriptFunction passed as class overrides
             mv.dup();
             mv.instanceOf(SCRIPT_FUNCTION_TYPE);
-                    mv.dup();
+            mv.dup();
             mv.putstatic(generatedClassName, IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
             final Label notFunction = new Label();
             mv.ifeq(notFunction);
@@ -389,9 +375,9 @@
     private void emitInitCallThis(final InstructionAdapter mv) {
         loadField(mv, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
         GET_CALL_THIS.invoke(mv);
-            if(classOverride) {
+        if(classOverride) {
             mv.putstatic(generatedClassName, CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
-            } else {
+        } else {
             // It is presumed ALOAD 0 was already executed
             mv.putfield(generatedClassName, CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
         }
@@ -427,10 +413,10 @@
 
         if (samName == null) {
             return false;
-                }
-                // If all our abstract methods have a single name, generate an additional constructor, one that takes a
-                // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
-                generateOverridingConstructor(ctor, true);
+        }
+        // If all our abstract methods have a single name, generate an additional constructor, one that takes a
+        // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
+        generateOverridingConstructor(ctor, true);
         // If the original type only has a single abstract method name, as well as a default ctor, then it can
         // be automatically converted from JS function.
         return ctor.getParameterTypes().length == 0;
@@ -456,14 +442,9 @@
      * 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
+     * function itself, if that's what's passed). Additionally, it will create another constructor with an additional
+     * Object type parameter that can be used for ScriptObjectMirror objects.
+     * 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.
@@ -524,14 +505,59 @@
         }
     }
 
-    // Object additional param accepting constructor - generated to handle null and undefined value
-    // for script adapters. This is effectively to throw TypeError on such script adapters. See
-    // JavaAdapterServices.getHandle as well.
+    // Object additional param accepting constructor for handling ScriptObjectMirror objects, which are
+    // unwrapped to work as ScriptObjects or ScriptFunctions. This also handles null and undefined values for
+    // script adapters by throwing TypeError on such script adapters.
     private void generateOverridingConstructorWithObjectParam(final InstructionAdapter mv, final String ctorDescriptor) {
         mv.visitCode();
         final int extraArgOffset = emitSuperConstructorCall(mv, ctorDescriptor);
+
+        // Check for ScriptObjectMirror
+        mv.visitVarInsn(ALOAD, extraArgOffset);
+        mv.instanceOf(SCRIPT_OBJECT_MIRROR_TYPE);
+        final Label notMirror = new Label();
+        mv.ifeq(notMirror);
+
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitVarInsn(ALOAD, extraArgOffset);
+        mv.iconst(0);
+        UNWRAP_MIRROR.invoke(mv);
+        mv.putfield(generatedClassName, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitVarInsn(ALOAD, extraArgOffset);
+        mv.iconst(1);
+        UNWRAP_MIRROR.invoke(mv);
+        mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+        final Label done = new Label();
+
+        if (samName != null) {
+            mv.visitVarInsn(ALOAD, 0);
+            mv.getfield(generatedClassName, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+            mv.instanceOf(SCRIPT_FUNCTION_TYPE);
+            mv.ifeq(done);
+
+            // Assign "isFunction = true"
+            mv.visitVarInsn(ALOAD, 0);
+            mv.iconst(1);
+            mv.putfield(generatedClassName, IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+
+            mv.visitVarInsn(ALOAD, 0);
+            mv.dup();
+            mv.getfield(generatedClassName, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+            mv.checkcast(SCRIPT_FUNCTION_TYPE);
+            emitInitCallThis(mv);
+            mv.goTo(done);
+        }
+
+        mv.visitLabel(notMirror);
+
+        // Throw error if not a ScriptObject
         mv.visitVarInsn(ALOAD, extraArgOffset);
         NOT_AN_OBJECT.invoke(mv);
+
+        mv.visitLabel(done);
         endInitMethod(mv);
     }
 
@@ -678,7 +704,7 @@
                 // stack: [callThis, delegate]
                 mv.goTo(callCallee);
                 mv.visitLabel(notFunction);
-        } else {
+            } else {
                 // If it's not a SAM method, and the delegate is a function,
                 // it'll fall back to default behavior
                 mv.ifne(defaultBehavior);
@@ -818,7 +844,7 @@
         if (isVarArgCall) {
             // Variable arity calls are always (Object callee, Object this, Object[] params)
             callParamTypes = new Class<?>[] { Object.class, Object.class, Object[].class };
-            } else {
+        } else {
             // Adjust invocation type signature for conversions we instituted in
             // convertParam; also, byte and short get passed as ints.
             final Class<?>[] origParamTypes = type.parameterArray();
@@ -868,13 +894,13 @@
 
 
     private void loadField(final InstructionAdapter mv, final String name, final String desc) {
-                if(classOverride) {
+        if(classOverride) {
             mv.getstatic(generatedClassName, name, desc);
-                } else {
-                    mv.visitVarInsn(ALOAD, 0);
+        } else {
+            mv.visitVarInsn(ALOAD, 0);
             mv.getfield(generatedClassName, name, desc);
-                }
-            }
+        }
+    }
 
     private static void convertReturnValue(final InstructionAdapter mv, final Class<?> origReturnType) {
         if (origReturnType == void.class) {
@@ -948,6 +974,7 @@
         }
         return len;
     }
+
     /**
      * Emit code to restore the previous Nashorn Context when needed.
      * @param mv the instruction adapter
@@ -999,9 +1026,9 @@
         }
 
         for (final Class<?> iface : interfaces) {
-             if (cl.isAssignableFrom(iface)) {
-                 return iface;
-             }
+            if (cl.isAssignableFrom(iface)) {
+                return iface;
+            }
         }
 
         // we better that interface that extends the given interface!
@@ -1122,8 +1149,8 @@
                     if (Modifier.isFinal(m) || isCallerSensitive(typeMethod)) {
                         finalMethods.add(mi);
                     } else if (!finalMethods.contains(mi) && methodInfos.add(mi) && Modifier.isAbstract(m)) {
-                            abstractMethodNames.add(mi.getName());
-                        }
+                        abstractMethodNames.add(mi.getName());
+                    }
                 }
             }
         }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java	Wed Nov 16 12:35:19 2016 -0800
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java	Thu Nov 17 13:39:30 2016 +0100
@@ -234,7 +234,7 @@
    /**
      * For a given class, create its adapter class and associated info.
      *
-     * @param type the class for which the adapter is created
+     * @param types the class and interfaces for which the adapter is created
      *
      * @return the adapter info for the class.
      */
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java	Wed Nov 16 12:35:19 2016 -0800
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java	Thu Nov 17 13:39:30 2016 +0100
@@ -39,6 +39,7 @@
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
 import java.security.AccessController;
 import java.security.CodeSigner;
 import java.security.CodeSource;
@@ -51,6 +52,7 @@
 import jdk.internal.org.objectweb.asm.Opcodes;
 import jdk.internal.org.objectweb.asm.Type;
 import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
+import jdk.nashorn.api.scripting.ScriptObjectMirror;
 import jdk.nashorn.internal.objects.Global;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ECMAException;
@@ -176,6 +178,23 @@
     }
 
     /**
+     * Returns the ScriptObject or Global field value from a ScriptObjectMirror using reflection.
+     *
+     * @param mirror the mirror object
+     * @param getGlobal true if we want the global object, false to return the script object
+     * @return the script object or global object
+     */
+    public static ScriptObject unwrapMirror(final Object mirror, final boolean getGlobal) {
+        assert mirror instanceof ScriptObjectMirror;
+        try {
+            final Field field = getGlobal ? MirrorFieldHolder.GLOBAL_FIELD : MirrorFieldHolder.SOBJ_FIELD;
+            return (ScriptObject) field.get(mirror);
+        } catch (final IllegalAccessException x) {
+            throw new RuntimeException(x);
+        }
+    }
+
+    /**
      * Delegate to {@link Bootstrap#bootstrap(Lookup, String, MethodType, int)}.
      * @param lookup MethodHandle lookup.
      * @param opDesc Dynalink dynamic operation descriptor.
@@ -291,4 +310,24 @@
                 .asCollector(Object[].class, type.parameterCount())
                 .asType(type));
     }
+
+    // Initialization on demand holder for accessible ScriptObjectMirror fields
+    private static class MirrorFieldHolder {
+
+        private static final Field SOBJ_FIELD   = getMirrorField("sobj");
+        private static final Field GLOBAL_FIELD = getMirrorField("global");
+
+        private static Field getMirrorField(final String fieldName) {
+            try {
+                final Field field = ScriptObjectMirror.class.getDeclaredField(fieldName);
+                AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
+                    field.setAccessible(true);
+                    return null;
+                });
+                return field;
+            } catch (final NoSuchFieldException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornLinker.java	Wed Nov 16 12:35:19 2016 -0800
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornLinker.java	Thu Nov 17 13:39:30 2016 +0100
@@ -157,12 +157,17 @@
      */
     private static GuardedInvocation getSamTypeConverter(final Class<?> sourceType, final Class<?> targetType, final Supplier<MethodHandles.Lookup> lookupSupplier) throws Exception {
         // If source type is more generic than ScriptFunction class, we'll need to use a guard
-        final boolean isSourceTypeGeneric = sourceType.isAssignableFrom(ScriptFunction.class);
+        final boolean isSourceTypeGeneric = sourceType.isAssignableFrom(ScriptObject.class);
 
         if ((isSourceTypeGeneric || ScriptFunction.class.isAssignableFrom(sourceType)) && isAutoConvertibleFromFunction(targetType)) {
-            final MethodHandle ctor = JavaAdapterFactory.getConstructor(ScriptFunction.class, targetType, getCurrentLookup(lookupSupplier));
+            final Class<?> paramType = isSourceTypeGeneric ? Object.class : ScriptFunction.class;
+            // Using Object.class as constructor source type means we're getting an overloaded constructor handle,
+            // which is safe but slower than a single constructor handle. If the actual argument is a ScriptFunction it
+            // would be nice if we could change the formal parameter to ScriptFunction.class and add a guard for it
+            // in the main invocation.
+            final MethodHandle ctor = JavaAdapterFactory.getConstructor(paramType, targetType, getCurrentLookup(lookupSupplier));
             assert ctor != null; // if isAutoConvertibleFromFunction() returned true, then ctor must exist.
-            return new GuardedInvocation(ctor, isSourceTypeGeneric ? IS_SCRIPT_FUNCTION : null);
+            return new GuardedInvocation(ctor, isSourceTypeGeneric ? IS_FUNCTION : null);
         }
         return null;
     }
@@ -315,7 +320,7 @@
     }
 
     private static final MethodHandle IS_SCRIPT_OBJECT = Guards.isInstance(ScriptObject.class, MH.type(Boolean.TYPE, Object.class));
-    private static final MethodHandle IS_SCRIPT_FUNCTION = Guards.isInstance(ScriptFunction.class, MH.type(Boolean.TYPE, Object.class));
+    private static final MethodHandle IS_FUNCTION = findOwnMH("isFunction", boolean.class, Object.class);
     private static final MethodHandle IS_NATIVE_ARRAY = Guards.isOfClass(NativeArray.class, MH.type(Boolean.TYPE, Object.class));
 
     private static final MethodHandle IS_NASHORN_OR_UNDEFINED_TYPE = findOwnMH("isNashornTypeOrUndefined", Boolean.TYPE, Object.class);
@@ -348,6 +353,11 @@
         return obj instanceof ScriptObject? ScriptUtils.wrap((ScriptObject)obj) : obj;
     }
 
+    @SuppressWarnings("unused")
+    private static boolean isFunction(final Object obj) {
+        return obj instanceof ScriptFunction || obj instanceof ScriptObjectMirror && ((ScriptObjectMirror) obj).isFunction();
+    }
+
     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
         return MH.findStatic(MethodHandles.lookup(), NashornLinker.class, name, MH.type(rtype, types));
     }
--- a/nashorn/test/src/jdk/nashorn/internal/runtime/linker/test/JavaAdapterTest.java	Wed Nov 16 12:35:19 2016 -0800
+++ b/nashorn/test/src/jdk/nashorn/internal/runtime/linker/test/JavaAdapterTest.java	Thu Nov 17 13:39:30 2016 +0100
@@ -29,11 +29,14 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import javax.script.Bindings;
+import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
 import javax.script.ScriptException;
 import jdk.nashorn.api.scripting.JSObject;
+import jdk.nashorn.api.scripting.NashornScriptEngine;
 import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
 import jdk.nashorn.api.scripting.ScriptObjectMirror;
 import jdk.nashorn.internal.runtime.Context;
@@ -277,4 +280,37 @@
         Assert.assertEquals(tla.getQueue().peek(), "Queue");
         Assert.assertEquals(tla.getDequeue().peek(), "Dequeue");
     }
+
+    @Test
+    public static void testMirrorAdapter() throws ScriptException {
+        final NashornScriptEngine e = (NashornScriptEngine) createEngine();
+        e.setBindings(e.createBindings(), ScriptContext.GLOBAL_SCOPE); // Null by default
+
+        // Referencing functions from across scopes causes them to be wrapped in ScriptObjectMirrors
+        e.eval("function convertObjectFromEngineScope(){ return new java.util.concurrent.Callable(o).call(); }", e.getBindings(ScriptContext.ENGINE_SCOPE));
+        e.eval("function convertObjectFromGlobalScope(){ return new java.util.concurrent.Callable(o).call(); }", e.getBindings(ScriptContext.GLOBAL_SCOPE));
+        e.eval("function convertFuncFromEngineScope(){ return new java.util.concurrent.Callable(g).call(); }", e.getBindings(ScriptContext.ENGINE_SCOPE));
+        e.eval("function convertFuncFromGlobalScope(){ return new java.util.concurrent.Callable(g).call(); }", e.getBindings(ScriptContext.GLOBAL_SCOPE));
+        e.eval("function convertParamFromEngineScope(){ return Java.type('jdk.nashorn.internal.runtime.linker.test.JavaAdapterTest').m(f);}", e.getBindings(ScriptContext.ENGINE_SCOPE));
+        e.eval("function convertParamFromGlobalScope(){ return Java.type('jdk.nashorn.internal.runtime.linker.test.JavaAdapterTest').m(f);}", e.getBindings(ScriptContext.GLOBAL_SCOPE));
+
+        e.eval("var o = { call: function () { return 'ok from o'; } }", e.getBindings(ScriptContext.ENGINE_SCOPE));
+        e.eval("function g() { return 'ok from g'; }", e.getBindings(ScriptContext.ENGINE_SCOPE));
+        e.eval("function f(a) { return a.toUpperCase(); }", e.getBindings(ScriptContext.ENGINE_SCOPE));
+
+        try {
+            Assert.assertEquals(e.invokeFunction("convertObjectFromEngineScope"), "ok from o");
+            Assert.assertEquals(e.invokeFunction("convertObjectFromGlobalScope"), "ok from o");
+            Assert.assertEquals(e.invokeFunction("convertFuncFromEngineScope"), "ok from g");
+            Assert.assertEquals(e.invokeFunction("convertFuncFromGlobalScope"), "ok from g");
+            Assert.assertEquals(e.invokeFunction("convertParamFromEngineScope"), "OK");
+            Assert.assertEquals(e.invokeFunction("convertParamFromGlobalScope"), "OK");
+        } catch (final NoSuchMethodException x) {
+            throw new RuntimeException(x);
+        }
+    }
+
+    public static String m(final Function<String, String> f){
+        return f.apply("ok");
+    }
 }