8068889: Calling a @FunctionalInterface from JS leaks internal objects

Tue, 13 Jan 2015 16:38:29 +0100

author
attila
date
Tue, 13 Jan 2015 16:38:29 +0100
changeset 1181
cff6eb75ba9b
parent 1131
c822b6dd240c
child 1182
3903ddaab26a

8068889: Calling a @FunctionalInterface from JS leaks internal objects
Reviewed-by: jlaskey, sundar

src/jdk/nashorn/internal/runtime/linker/Bootstrap.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java file | annotate | diff | comparison | revisions
test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java file | annotate | diff | comparison | revisions
     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);

mercurial