8021122: Not all callables are handled for toString and other function valued properties
authorsundar
Tue, 23 Jul 2013 18:17:25 +0530
changeset 19088 153f268bfa72
parent 19087 47d02733b128
child 19089 51cfdcf21d35
8021122: Not all callables are handled for toString and other function valued properties Reviewed-by: attila, hannesw, jlaskey
nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java
nashorn/src/jdk/nashorn/internal/objects/Global.java
nashorn/src/jdk/nashorn/internal/objects/NativeArray.java
nashorn/src/jdk/nashorn/internal/objects/NativeDate.java
nashorn/src/jdk/nashorn/internal/objects/NativeJSON.java
nashorn/src/jdk/nashorn/internal/objects/NativeObject.java
nashorn/src/jdk/nashorn/internal/runtime/Context.java
nashorn/src/jdk/nashorn/internal/runtime/ListAdapter.java
nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java
nashorn/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java
nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java
nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java
nashorn/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java
nashorn/test/script/basic/JDK-8021122.js
nashorn/test/script/basic/JDK-8021122.js.EXPECTED
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java	Tue Jul 23 18:17:25 2013 +0530
@@ -102,7 +102,7 @@
             preorder.add(node);
         }
 
-        final boolean isReference = field != null && field.getAnnotation(Reference.class) != null;
+        final boolean isReference = field != null && field.isAnnotationPresent(Reference.class);
 
         Class<?> clazz = node.getClass();
         String   type  = clazz.getName();
@@ -183,7 +183,7 @@
                 append('\n');
 
             for (final Field child : children) {
-                if (child.getAnnotation(Ignore.class) != null) {
+                if (child.isAnnotationPresent(Ignore.class)) {
                     continue;
                 }
 
--- a/nashorn/src/jdk/nashorn/internal/objects/Global.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java	Tue Jul 23 18:17:25 2013 +0530
@@ -63,6 +63,7 @@
 import jdk.nashorn.internal.runtime.ScriptRuntime;
 import jdk.nashorn.internal.runtime.ScriptingFunctions;
 import jdk.nashorn.internal.runtime.Source;
+import jdk.nashorn.internal.runtime.linker.Bootstrap;
 import jdk.nashorn.internal.runtime.linker.InvokeByName;
 import jdk.nashorn.internal.scripts.JO;
 
@@ -548,7 +549,8 @@
             if (hint == String.class) {
 
                 final Object toString = TO_STRING.getGetter().invokeExact(sobj);
-                if (toString instanceof ScriptFunction) {
+
+                if (Bootstrap.isCallable(toString)) {
                     final Object value = TO_STRING.getInvoker().invokeExact(toString, sobj);
                     if (JSType.isPrimitive(value)) {
                         return value;
@@ -556,7 +558,7 @@
                 }
 
                 final Object valueOf = VALUE_OF.getGetter().invokeExact(sobj);
-                if (valueOf instanceof ScriptFunction) {
+                if (Bootstrap.isCallable(valueOf)) {
                     final Object value = VALUE_OF.getInvoker().invokeExact(valueOf, sobj);
                     if (JSType.isPrimitive(value)) {
                         return value;
@@ -567,7 +569,7 @@
 
             if (hint == Number.class) {
                 final Object valueOf = VALUE_OF.getGetter().invokeExact(sobj);
-                if (valueOf instanceof ScriptFunction) {
+                if (Bootstrap.isCallable(valueOf)) {
                     final Object value = VALUE_OF.getInvoker().invokeExact(valueOf, sobj);
                     if (JSType.isPrimitive(value)) {
                         return value;
@@ -575,7 +577,7 @@
                 }
 
                 final Object toString = TO_STRING.getGetter().invokeExact(sobj);
-                if (toString instanceof ScriptFunction) {
+                if (Bootstrap.isCallable(toString)) {
                     final Object value = TO_STRING.getInvoker().invokeExact(toString, sobj);
                     if (JSType.isPrimitive(value)) {
                         return value;
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeArray.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeArray.java	Tue Jul 23 18:17:25 2013 +0530
@@ -360,7 +360,7 @@
             final ScriptObject sobj = (ScriptObject)obj;
             try {
                 final Object join = JOIN.getGetter().invokeExact(sobj);
-                if (join instanceof ScriptFunction) {
+                if (Bootstrap.isCallable(join)) {
                     return JOIN.getInvoker().invokeExact(join, sobj);
                 }
             } catch (final RuntimeException | Error e) {
@@ -396,7 +396,7 @@
                         final ScriptObject sobj           = (ScriptObject)val;
                         final Object       toLocaleString = TO_LOCALE_STRING.getGetter().invokeExact(sobj);
 
-                        if (toLocaleString instanceof ScriptFunction) {
+                        if (Bootstrap.isCallable(toLocaleString)) {
                             sb.append((String)TO_LOCALE_STRING.getInvoker().invokeExact(toLocaleString, sobj));
                         } else {
                             throw typeError("not.a.function", "toLocaleString");
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java	Tue Jul 23 18:17:25 2013 +0530
@@ -47,6 +47,7 @@
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
+import jdk.nashorn.internal.runtime.linker.Bootstrap;
 import jdk.nashorn.internal.runtime.linker.InvokeByName;
 
 /**
@@ -862,7 +863,7 @@
 
         try {
             final Object func = TO_ISO_STRING.getGetter().invokeExact(sobj);
-            if (func instanceof ScriptFunction) {
+            if (Bootstrap.isCallable(func)) {
                 return TO_ISO_STRING.getInvoker().invokeExact(func, sobj, key);
             }
             throw typeError("not.a.function", ScriptRuntime.safeToString(func));
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeJSON.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeJSON.java	Tue Jul 23 18:17:25 2013 +0530
@@ -189,7 +189,7 @@
             if (value instanceof ScriptObject) {
                 final ScriptObject svalue = (ScriptObject)value;
                 final Object toJSON = TO_JSON.getGetter().invokeExact(svalue);
-                if (toJSON instanceof ScriptFunction) {
+                if (Bootstrap.isCallable(toJSON)) {
                     value = TO_JSON.getInvoker().invokeExact(toJSON, svalue, key);
                 }
             }
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeObject.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeObject.java	Tue Jul 23 18:17:25 2013 +0530
@@ -407,7 +407,7 @@
             try {
                 final Object toString = TO_STRING.getGetter().invokeExact(sobj);
 
-                if (toString instanceof ScriptFunction) {
+                if (Bootstrap.isCallable(toString)) {
                     return TO_STRING.getInvoker().invokeExact(toString, sobj);
                 }
             } catch (final RuntimeException | Error e) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java	Tue Jul 23 18:17:25 2013 +0530
@@ -36,6 +36,7 @@
 import java.io.PrintWriter;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Modifier;
 import java.util.concurrent.atomic.AtomicLong;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -555,6 +556,7 @@
      * Checks that the given package can be accessed from current call stack.
      *
      * @param fullName fully qualified package name
+     * @throw SecurityException if not accessible
      */
     public static void checkPackageAccess(final String fullName) {
         final int index = fullName.lastIndexOf('.');
@@ -567,6 +569,31 @@
     }
 
     /**
+     * Checks that the given package can be accessed from current call stack.
+     *
+     * @param fullName fully qualified package name
+     * @return true if package is accessible, false otherwise
+     */
+    public static boolean isAccessiblePackage(final String fullName) {
+        try {
+            checkPackageAccess(fullName);
+            return true;
+        } catch (final SecurityException se) {
+            return false;
+        }
+    }
+
+    /**
+     * Checks that the given Class can be accessed from current call stack and is public.
+     *
+     * @param clazz Class object to check
+     * @return true if Class is accessible, false otherwise
+     */
+    public static boolean isAccessibleClass(final Class<?> clazz) {
+        return Modifier.isPublic(clazz.getModifiers()) && Context.isAccessiblePackage(clazz.getName());
+    }
+
+    /**
      * Lookup a Java class. This is used for JSR-223 stuff linking in from
      * {@code jdk.nashorn.internal.objects.NativeJava} and {@code jdk.nashorn.internal.runtime.NativeJavaPackage}
      *
--- a/nashorn/src/jdk/nashorn/internal/runtime/ListAdapter.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ListAdapter.java	Tue Jul 23 18:17:25 2013 +0530
@@ -31,6 +31,7 @@
 import java.util.ListIterator;
 import java.util.NoSuchElementException;
 import java.util.RandomAccess;
+import jdk.nashorn.internal.runtime.linker.Bootstrap;
 import jdk.nashorn.internal.runtime.linker.InvokeByName;
 
 /**
@@ -157,7 +158,7 @@
         }
     }
     private static void checkFunction(Object fn, InvokeByName invoke) {
-        if(!(fn instanceof ScriptFunction)) {
+        if(!(Bootstrap.isCallable(fn))) {
             throw new UnsupportedOperationException("The script object doesn't have a function named " + invoke.getName());
         }
     }
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java	Tue Jul 23 18:17:25 2013 +0530
@@ -381,9 +381,7 @@
      */
     public static Object checkAndConstruct(final ScriptFunction target, final Object... args) {
         final ScriptObject global = Context.getGlobalTrusted();
-        if (! (global instanceof GlobalObject)) {
-            throw new IllegalStateException("No current global set");
-        }
+        assert (global instanceof GlobalObject): "No current global set";
 
         if (target.getContext() != global.getContext()) {
             throw new IllegalArgumentException("'target' function is not from current Context");
--- a/nashorn/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java	Tue Jul 23 18:17:25 2013 +0530
@@ -31,6 +31,7 @@
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
+import jdk.nashorn.internal.runtime.linker.Bootstrap;
 
 /**
  * Helper class for the various map/apply functions in {@link jdk.nashorn.internal.objects.NativeArray}.
@@ -103,6 +104,8 @@
         } else if (callbackfn instanceof ScriptObjectMirror &&
             ((ScriptObjectMirror)callbackfn).isFunction()) {
             strict = ((ScriptObjectMirror)callbackfn).isStrictFunction();
+        } else if (Bootstrap.isDynamicMethod(callbackfn) || Bootstrap.isFunctionalInterfaceObject(callbackfn)) {
+            strict = false;
         } else {
             throw typeError("not.a.function", ScriptRuntime.safeToString(callbackfn));
         }
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java	Tue Jul 23 18:17:25 2013 +0530
@@ -38,8 +38,11 @@
 import jdk.internal.dynalink.beans.BeansLinker;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.LinkerServices;
+import jdk.nashorn.api.scripting.ScriptObjectMirror;
 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
 import jdk.nashorn.internal.codegen.RuntimeCallSite;
+import jdk.nashorn.internal.runtime.ScriptFunction;
+import jdk.nashorn.internal.runtime.ScriptRuntime;
 import jdk.nashorn.internal.runtime.options.Options;
 
 /**
@@ -68,6 +71,41 @@
     }
 
     /**
+     * Returns if the given object is a "callable"
+     * @param obj object to be checked for callability
+     * @return true if the obj is callable
+     */
+    public static boolean isCallable(final Object obj) {
+        if (obj == ScriptRuntime.UNDEFINED || obj == null) {
+            return false;
+        }
+
+        return obj instanceof ScriptFunction ||
+            ((obj instanceof ScriptObjectMirror) && ((ScriptObjectMirror)obj).isFunction()) ||
+            isDynamicMethod(obj) ||
+            isFunctionalInterfaceObject(obj);
+    }
+
+    /**
+     * Returns if the given object is a dynalink Dynamic method
+     * @param obj object to be checked
+     * @return true if the obj is a dynamic method
+     */
+    public static boolean isDynamicMethod(final Object obj) {
+        return obj instanceof BoundDynamicMethod || BeansLinker.isDynamicMethod(obj);
+    }
+
+    /**
+     * Returns if the given object is an instance of an interface annotated with
+     * java.lang.FunctionalInterface
+     * @param obj object to be checked
+     * @return true if the obj is an instance of @FunctionalInterface interface
+     */
+    public static boolean isFunctionalInterfaceObject(final Object obj) {
+        return obj != null && (NashornBottomLinker.getFunctionalInterfaceMethod(obj.getClass()) != null);
+    }
+
+    /**
      * Create a call site and link it for Nashorn. This version of the method conforms to the invokedynamic bootstrap
      * method expected signature and is referenced from Nashorn generated bytecode as the bootstrap method for all
      * invokedynamic instructions.
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java	Tue Jul 23 18:17:25 2013 +0530
@@ -924,6 +924,6 @@
     }
 
     private static boolean isCallerSensitive(final AccessibleObject e) {
-        return e.getAnnotation(CallerSensitive.class) != null;
+        return e.isAnnotationPresent(CallerSensitive.class);
     }
 }
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java	Thu Jul 18 16:47:45 2013 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java	Tue Jul 23 18:17:25 2013 +0530
@@ -30,6 +30,9 @@
 import static jdk.nashorn.internal.lookup.Lookup.MH;
 
 import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import jdk.internal.dynalink.CallSiteDescriptor;
 import jdk.internal.dynalink.beans.BeansLinker;
 import jdk.internal.dynalink.linker.GuardedInvocation;
@@ -37,6 +40,7 @@
 import jdk.internal.dynalink.linker.LinkRequest;
 import jdk.internal.dynalink.linker.LinkerServices;
 import jdk.internal.dynalink.support.Guards;
+import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
 
 /**
@@ -73,7 +77,7 @@
     private static final MethodHandle EMPTY_ELEM_SETTER =
             MH.dropArguments(EMPTY_PROP_SETTER, 0, Object.class);
 
-    private static GuardedInvocation linkBean(final LinkRequest linkRequest, final LinkerServices linkerServices) {
+    private static GuardedInvocation linkBean(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
         final NashornCallSiteDescriptor desc = (NashornCallSiteDescriptor)linkRequest.getCallSiteDescriptor();
         final Object self = linkRequest.getReceiver();
         final String operator = desc.getFirstOperator();
@@ -84,6 +88,22 @@
             }
             throw typeError("not.a.function", ScriptRuntime.safeToString(self));
         case "call":
+            // Support dyn:call on any object that supports some @FunctionalInterface
+            // annotated interface. This way Java method, constructor references or
+            // implementations of java.util.function.* interfaces can be called as though
+            // those are script functions.
+            final Method m = getFunctionalInterfaceMethod(self.getClass());
+            if (m != null) {
+                final MethodType callType = desc.getMethodType();
+                // 'callee' and 'thiz' passed from script + actual arguments
+                if (callType.parameterCount() != m.getParameterCount() + 2) {
+                    throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self));
+                }
+                return new GuardedInvocation(
+                        // drop 'thiz' passed from the script.
+                        MH.dropArguments(desc.getLookup().unreflect(m), 1, callType.parameterType(1)),
+                        Guards.getInstanceOfGuard(m.getDeclaringClass())).asType(callType);
+            }
             if(BeansLinker.isDynamicMethod(self)) {
                 throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self));
             }
@@ -148,4 +168,32 @@
         }
         return ScriptRuntime.safeToString(linkRequest.getArguments()[1]);
     }
+
+    // Returns @FunctionalInterface annotated interface's single abstract method.
+    // If not found, returns null
+    static Method getFunctionalInterfaceMethod(final Class<?> clazz) {
+        if (clazz == null) {
+            return null;
+        }
+
+        for (Class<?> iface : clazz.getInterfaces()) {
+            // check accessiblity up-front
+            if (! Context.isAccessibleClass(iface)) {
+                continue;
+            }
+
+            // check for @FunctionalInterface
+            if (iface.isAnnotationPresent(FunctionalInterface.class)) {
+                // return the first abstract method
+                for (final Method m : iface.getMethods()) {
+                    if (Modifier.isAbstract(m.getModifiers())) {
+                        return m;
+                    }
+                }
+            }
+        }
+
+        // did not find here, try super class
+        return getFunctionalInterfaceMethod(clazz.getSuperclass());
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8021122.js	Tue Jul 23 18:17:25 2013 +0530
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+/**
+ * JDK-8021122: Not all callables are handled for toString and other function valued properties
+ *
+ * @test
+ * @run
+ */
+
+var a = {}
+var obj = new java.util.HashMap();
+Object.bindProperties(a, obj);
+try {
+    print(a);
+} catch (e) {
+    print(e);
+}
+
+var a = {}
+var global = loadWithNewGlobal({ name:"xx", script: "this" });
+var obj = global.eval("({ toString: function() { return 'hello'; } })");
+Object.bindProperties(a, obj);
+try {
+    print(a);
+} catch (e) {
+    print(e);
+}
+
+function runLambdaTests() {
+    var r = new java.lang.Runnable() {
+        run: function() { print("I am runnable"); }
+    };
+
+    // call any @FunctionalInterface object as though it is a function
+    r();
+
+    var twice = new java.util.function.Function() {
+        apply: function(x) 2*x
+    };
+
+    print(twice(34));
+
+    var sum = new java.util.function.BiFunction() {
+        apply: function(x, y) x + y
+    };
+
+    print(sum(32, 12))
+
+    // make toString to be a @FunctionalInterface object
+    var a = {};
+    a.toString = new java.util.function.Supplier() {
+        get: function() { return "MyString"; }
+    };
+
+    try {
+        print(a);
+    } catch (e) {
+        print(e);
+    }
+}
+
+try {
+    // check for java.util.function.Function class
+    Java.type("java.util.function.Function");
+    runLambdaTests();
+} catch (e) {
+    // fake output to match .EXPECTED values
+    print("I am runnable");
+    print("68");
+    print("44");
+    print("MyString");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8021122.js.EXPECTED	Tue Jul 23 18:17:25 2013 +0530
@@ -0,0 +1,6 @@
+{}
+hello
+I am runnable
+68
+44
+MyString