Tue, 13 Jan 2015 16:38:29 +0100
8068889: Calling a @FunctionalInterface from JS leaks internal objects
Reviewed-by: jlaskey, sundar
1.1 --- a/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java Wed Jan 14 16:26:32 2015 -0800 1.2 +++ b/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java Tue Jan 13 16:38:29 2015 +0100 1.3 @@ -189,7 +189,7 @@ 1.4 * @return true if the obj is an instance of @FunctionalInterface interface 1.5 */ 1.6 public static boolean isFunctionalInterfaceObject(final Object obj) { 1.7 - return !JSType.isPrimitive(obj) && (NashornBottomLinker.getFunctionalInterfaceMethod(obj.getClass()) != null); 1.8 + return !JSType.isPrimitive(obj) && (NashornBeansLinker.getFunctionalInterfaceMethod(obj.getClass()) != null); 1.9 } 1.10 1.11 /**
2.1 --- a/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java Wed Jan 14 16:26:32 2015 -0800 2.2 +++ b/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java Tue Jan 13 16:38:29 2015 +0100 2.3 @@ -25,20 +25,29 @@ 2.4 2.5 package jdk.nashorn.internal.runtime.linker; 2.6 2.7 +import static jdk.nashorn.internal.lookup.Lookup.MH; 2.8 +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 2.9 + 2.10 import java.lang.invoke.MethodHandle; 2.11 import java.lang.invoke.MethodHandles; 2.12 import java.lang.invoke.MethodType; 2.13 +import java.lang.reflect.Method; 2.14 +import java.lang.reflect.Modifier; 2.15 +import jdk.internal.dynalink.CallSiteDescriptor; 2.16 import jdk.internal.dynalink.beans.BeansLinker; 2.17 import jdk.internal.dynalink.linker.ConversionComparator.Comparison; 2.18 import jdk.internal.dynalink.linker.GuardedInvocation; 2.19 import jdk.internal.dynalink.linker.GuardingDynamicLinker; 2.20 import jdk.internal.dynalink.linker.LinkRequest; 2.21 import jdk.internal.dynalink.linker.LinkerServices; 2.22 +import jdk.internal.dynalink.support.Guards; 2.23 import jdk.internal.dynalink.support.Lookup; 2.24 import jdk.nashorn.api.scripting.ScriptUtils; 2.25 import jdk.nashorn.internal.objects.NativeArray; 2.26 import jdk.nashorn.internal.runtime.ConsString; 2.27 +import jdk.nashorn.internal.runtime.Context; 2.28 import jdk.nashorn.internal.runtime.ScriptObject; 2.29 +import jdk.nashorn.internal.runtime.ScriptRuntime; 2.30 import jdk.nashorn.internal.runtime.options.Options; 2.31 2.32 /** 2.33 @@ -68,19 +77,49 @@ 2.34 FILTER_CONSSTRING = lookup.findOwnStatic("consStringFilter", Object.class, Object.class); 2.35 } 2.36 2.37 + // cache of @FunctionalInterface method of implementor classes 2.38 + private static final ClassValue<Method> FUNCTIONAL_IFACE_METHOD = new ClassValue<Method>() { 2.39 + @Override 2.40 + protected Method computeValue(final Class<?> type) { 2.41 + return findFunctionalInterfaceMethod(type); 2.42 + } 2.43 + }; 2.44 + 2.45 private final BeansLinker beansLinker = new BeansLinker(); 2.46 2.47 @Override 2.48 public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception { 2.49 - if (linkRequest.getReceiver() instanceof ConsString) { 2.50 + final Object self = linkRequest.getReceiver(); 2.51 + final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor(); 2.52 + if (self instanceof ConsString) { 2.53 // In order to treat ConsString like a java.lang.String we need a link request with a string receiver. 2.54 final Object[] arguments = linkRequest.getArguments(); 2.55 arguments[0] = ""; 2.56 - final LinkRequest forgedLinkRequest = linkRequest.replaceArguments(linkRequest.getCallSiteDescriptor(), arguments); 2.57 + final LinkRequest forgedLinkRequest = linkRequest.replaceArguments(desc, arguments); 2.58 final GuardedInvocation invocation = getGuardedInvocation(beansLinker, forgedLinkRequest, linkerServices); 2.59 // If an invocation is found we add a filter that makes it work for both Strings and ConsStrings. 2.60 return invocation == null ? null : invocation.filterArguments(0, FILTER_CONSSTRING); 2.61 } 2.62 + 2.63 + if ("call".equals(desc.getNameToken(CallSiteDescriptor.OPERATOR))) { 2.64 + // Support dyn:call on any object that supports some @FunctionalInterface 2.65 + // annotated interface. This way Java method, constructor references or 2.66 + // implementations of java.util.function.* interfaces can be called as though 2.67 + // those are script functions. 2.68 + final Method m = getFunctionalInterfaceMethod(self.getClass()); 2.69 + if (m != null) { 2.70 + final MethodType callType = desc.getMethodType(); 2.71 + // 'callee' and 'thiz' passed from script + actual arguments 2.72 + if (callType.parameterCount() != m.getParameterCount() + 2) { 2.73 + throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self)); 2.74 + } 2.75 + return new GuardedInvocation( 2.76 + // drop 'thiz' passed from the script. 2.77 + MH.dropArguments(desc.getLookup().unreflect(m), 1, callType.parameterType(1)), 2.78 + Guards.getInstanceOfGuard(m.getDeclaringClass())).asTypeSafeReturn( 2.79 + new NashornBeansLinkerServices(linkerServices), callType); 2.80 + } 2.81 + } 2.82 return getGuardedInvocation(beansLinker, linkRequest, linkerServices); 2.83 } 2.84 2.85 @@ -137,6 +176,38 @@ 2.86 return arg instanceof ConsString ? arg.toString() : arg; 2.87 } 2.88 2.89 + private static Method findFunctionalInterfaceMethod(final Class<?> clazz) { 2.90 + if (clazz == null) { 2.91 + return null; 2.92 + } 2.93 + 2.94 + for (final Class<?> iface : clazz.getInterfaces()) { 2.95 + // check accessiblity up-front 2.96 + if (! Context.isAccessibleClass(iface)) { 2.97 + continue; 2.98 + } 2.99 + 2.100 + // check for @FunctionalInterface 2.101 + if (iface.isAnnotationPresent(FunctionalInterface.class)) { 2.102 + // return the first abstract method 2.103 + for (final Method m : iface.getMethods()) { 2.104 + if (Modifier.isAbstract(m.getModifiers())) { 2.105 + return m; 2.106 + } 2.107 + } 2.108 + } 2.109 + } 2.110 + 2.111 + // did not find here, try super class 2.112 + return findFunctionalInterfaceMethod(clazz.getSuperclass()); 2.113 + } 2.114 + 2.115 + // Returns @FunctionalInterface annotated interface's single abstract 2.116 + // method. If not found, returns null. 2.117 + static Method getFunctionalInterfaceMethod(final Class<?> clazz) { 2.118 + return FUNCTIONAL_IFACE_METHOD.get(clazz); 2.119 + } 2.120 + 2.121 private static class NashornBeansLinkerServices implements LinkerServices { 2.122 private final LinkerServices linkerServices; 2.123
3.1 --- a/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java Wed Jan 14 16:26:32 2015 -0800 3.2 +++ b/src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java Tue Jan 13 16:38:29 2015 +0100 3.3 @@ -30,9 +30,6 @@ 3.4 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 3.5 3.6 import java.lang.invoke.MethodHandle; 3.7 -import java.lang.invoke.MethodType; 3.8 -import java.lang.reflect.Method; 3.9 -import java.lang.reflect.Modifier; 3.10 import java.util.HashMap; 3.11 import java.util.Map; 3.12 import jdk.internal.dynalink.CallSiteDescriptor; 3.13 @@ -45,7 +42,6 @@ 3.14 import jdk.internal.dynalink.linker.LinkerServices; 3.15 import jdk.internal.dynalink.support.Guards; 3.16 import jdk.nashorn.internal.codegen.types.Type; 3.17 -import jdk.nashorn.internal.runtime.Context; 3.18 import jdk.nashorn.internal.runtime.JSType; 3.19 import jdk.nashorn.internal.runtime.ScriptRuntime; 3.20 import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; 3.21 @@ -95,22 +91,6 @@ 3.22 } 3.23 throw typeError("not.a.function", ScriptRuntime.safeToString(self)); 3.24 case "call": 3.25 - // Support dyn:call on any object that supports some @FunctionalInterface 3.26 - // annotated interface. This way Java method, constructor references or 3.27 - // implementations of java.util.function.* interfaces can be called as though 3.28 - // those are script functions. 3.29 - final Method m = getFunctionalInterfaceMethod(self.getClass()); 3.30 - if (m != null) { 3.31 - final MethodType callType = desc.getMethodType(); 3.32 - // 'callee' and 'thiz' passed from script + actual arguments 3.33 - if (callType.parameterCount() != m.getParameterCount() + 2) { 3.34 - throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self)); 3.35 - } 3.36 - return Bootstrap.asTypeSafeReturn(new GuardedInvocation( 3.37 - // drop 'thiz' passed from the script. 3.38 - MH.dropArguments(desc.getLookup().unreflect(m), 1, callType.parameterType(1)), 3.39 - Guards.getInstanceOfGuard(m.getDeclaringClass())), linkerServices, desc); 3.40 - } 3.41 if(BeansLinker.isDynamicConstructor(self)) { 3.42 throw typeError("constructor.requires.new", ScriptRuntime.safeToString(self)); 3.43 } 3.44 @@ -218,44 +198,4 @@ 3.45 } 3.46 return ScriptRuntime.safeToString(linkRequest.getArguments()[1]); 3.47 } 3.48 - 3.49 - // cache of @FunctionalInterface method of implementor classes 3.50 - private static final ClassValue<Method> FUNCTIONAL_IFACE_METHOD = new ClassValue<Method>() { 3.51 - @Override 3.52 - protected Method computeValue(final Class<?> type) { 3.53 - return findFunctionalInterfaceMethod(type); 3.54 - } 3.55 - 3.56 - private Method findFunctionalInterfaceMethod(final Class<?> clazz) { 3.57 - if (clazz == null) { 3.58 - return null; 3.59 - } 3.60 - 3.61 - for (final Class<?> iface : clazz.getInterfaces()) { 3.62 - // check accessiblity up-front 3.63 - if (! Context.isAccessibleClass(iface)) { 3.64 - continue; 3.65 - } 3.66 - 3.67 - // check for @FunctionalInterface 3.68 - if (iface.isAnnotationPresent(FunctionalInterface.class)) { 3.69 - // return the first abstract method 3.70 - for (final Method m : iface.getMethods()) { 3.71 - if (Modifier.isAbstract(m.getModifiers())) { 3.72 - return m; 3.73 - } 3.74 - } 3.75 - } 3.76 - } 3.77 - 3.78 - // did not find here, try super class 3.79 - return findFunctionalInterfaceMethod(clazz.getSuperclass()); 3.80 - } 3.81 - }; 3.82 - 3.83 - // Returns @FunctionalInterface annotated interface's single abstract 3.84 - // method. If not found, returns null. 3.85 - static Method getFunctionalInterfaceMethod(final Class<?> clazz) { 3.86 - return FUNCTIONAL_IFACE_METHOD.get(clazz); 3.87 - } 3.88 }
4.1 --- a/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java Wed Jan 14 16:26:32 2015 -0800 4.2 +++ b/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java Tue Jan 13 16:38:29 2015 +0100 4.3 @@ -29,12 +29,16 @@ 4.4 import static org.testng.Assert.assertNotNull; 4.5 import static org.testng.Assert.assertTrue; 4.6 import static org.testng.Assert.fail; 4.7 + 4.8 import java.io.StringReader; 4.9 import java.io.StringWriter; 4.10 import java.lang.reflect.InvocationHandler; 4.11 import java.lang.reflect.Method; 4.12 import java.lang.reflect.Proxy; 4.13 import java.util.concurrent.Callable; 4.14 +import java.util.concurrent.atomic.AtomicBoolean; 4.15 +import java.util.function.Consumer; 4.16 +import java.util.function.Function; 4.17 import javax.script.Compilable; 4.18 import javax.script.CompiledScript; 4.19 import javax.script.Invocable; 4.20 @@ -668,6 +672,41 @@ 4.21 assertEquals("helloworld", inv.invokeMethod(ctx.get(), "join", "")); 4.22 } 4.23 4.24 + // @bug JDK-8068889: ConsString arguments to a functional interface wasn't converted to string. 4.25 + @Test 4.26 + public void functionalInterfaceStringTest() throws Exception { 4.27 + final ScriptEngineManager manager = new ScriptEngineManager(); 4.28 + final ScriptEngine e = manager.getEngineByName("nashorn"); 4.29 + final AtomicBoolean invoked = new AtomicBoolean(false); 4.30 + e.put("f", new Function<String, String>() { 4.31 + @Override 4.32 + public String apply(String t) { 4.33 + invoked.set(true); 4.34 + return t; 4.35 + } 4.36 + }); 4.37 + assertEquals(e.eval("var x = 'a'; x += 'b'; f(x)"), "ab"); 4.38 + assertTrue(invoked.get()); 4.39 + } 4.40 + 4.41 + // @bug JDK-8068889: ScriptObject arguments to a functional interface wasn't converted to a mirror. 4.42 + @Test 4.43 + public void functionalInterfaceObjectTest() throws Exception { 4.44 + final ScriptEngineManager manager = new ScriptEngineManager(); 4.45 + final ScriptEngine e = manager.getEngineByName("nashorn"); 4.46 + final AtomicBoolean invoked = new AtomicBoolean(false); 4.47 + e.put("c", new Consumer<Object>() { 4.48 + @Override 4.49 + public void accept(Object t) { 4.50 + assertTrue(t instanceof ScriptObjectMirror); 4.51 + assertEquals(((ScriptObjectMirror)t).get("a"), "xyz"); 4.52 + invoked.set(true); 4.53 + } 4.54 + }); 4.55 + e.eval("var x = 'xy'; x += 'z';c({a:x})"); 4.56 + assertTrue(invoked.get()); 4.57 + } 4.58 + 4.59 private static void checkProperty(final ScriptEngine e, final String name) 4.60 throws ScriptException { 4.61 final String value = System.getProperty(name);