8021122: Not all callables are handled for toString and other function valued properties
Reviewed-by: attila, hannesw, jlaskey
--- 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