NashornBeansLinker.java revision 1186:4a2dfd2ec3f3
11556Srgrimes/*
21556Srgrimes * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
31556Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
41556Srgrimes *
51556Srgrimes * This code is free software; you can redistribute it and/or modify it
61556Srgrimes * under the terms of the GNU General Public License version 2 only, as
71556Srgrimes * published by the Free Software Foundation.  Oracle designates this
81556Srgrimes * particular file as subject to the "Classpath" exception as provided
91556Srgrimes * by Oracle in the LICENSE file that accompanied this code.
101556Srgrimes *
111556Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT
121556Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
131556Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
141556Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
151556Srgrimes * accompanied this code).
161556Srgrimes *
171556Srgrimes * You should have received a copy of the GNU General Public License version
181556Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
191556Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
201556Srgrimes *
211556Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
221556Srgrimes * or visit www.oracle.com if you need additional information or have any
231556Srgrimes * questions.
241556Srgrimes */
251556Srgrimes
261556Srgrimespackage jdk.nashorn.internal.runtime.linker;
271556Srgrimes
281556Srgrimesimport static jdk.nashorn.internal.lookup.Lookup.MH;
291556Srgrimesimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
301556Srgrimes
311556Srgrimesimport java.lang.invoke.MethodHandle;
321556Srgrimesimport java.lang.invoke.MethodHandles;
331556Srgrimesimport java.lang.invoke.MethodType;
341556Srgrimesimport java.lang.reflect.Method;
351556Srgrimesimport java.lang.reflect.Modifier;
361556Srgrimesimport jdk.internal.dynalink.CallSiteDescriptor;
371556Srgrimesimport jdk.internal.dynalink.beans.BeansLinker;
3836150Scharnierimport jdk.internal.dynalink.linker.ConversionComparator.Comparison;
3936150Scharnierimport jdk.internal.dynalink.linker.GuardedInvocation;
4036150Scharnierimport jdk.internal.dynalink.linker.GuardingDynamicLinker;
4136150Scharnierimport jdk.internal.dynalink.linker.LinkRequest;
4250471Speterimport jdk.internal.dynalink.linker.LinkerServices;
431556Srgrimesimport jdk.internal.dynalink.linker.MethodHandleTransformer;
441556Srgrimesimport jdk.internal.dynalink.support.DefaultInternalObjectFilter;
4517987Speterimport jdk.internal.dynalink.support.Guards;
4617987Speterimport jdk.internal.dynalink.support.Lookup;
4717987Speterimport jdk.nashorn.api.scripting.ScriptUtils;
4817987Speterimport jdk.nashorn.internal.runtime.ConsString;
4917987Speterimport jdk.nashorn.internal.runtime.Context;
5017987Speterimport jdk.nashorn.internal.runtime.ScriptObject;
5117987Speterimport jdk.nashorn.internal.runtime.ScriptRuntime;
5217987Speterimport jdk.nashorn.internal.runtime.options.Options;
5317987Speter
5417987Speter/**
5569793Sobrien * This linker delegates to a {@code BeansLinker} but passes it a special linker services object that has a modified
5617987Speter * {@code compareConversion} method that favors conversion of {@link ConsString} to either {@link String} or
5718018Speter * {@link CharSequence}. It also provides a {@link #createHiddenObjectFilter()} method for use with bootstrap that will
5817987Speter * ensure that we never pass internal engine objects that should not be externally observable (currently ConsString and
591556Srgrimes * ScriptObject) to Java APIs, but rather that we flatten it into a String. We can't just add this functionality as
601556Srgrimes * custom converters via {@code GuaardingTypeConverterFactory}, since they are not consulted when
6125222Ssteve * the target method handle parameter signature is {@code Object}. This linker also makes sure that primitive
621556Srgrimes * {@link String} operations can be invoked on a {@link ConsString}, and allows invocation of objects implementing
6317987Speter * the {@link FunctionalInterface} attribute.
6417987Speter */
6517987Speterpublic class NashornBeansLinker implements GuardingDynamicLinker {
661556Srgrimes    // System property to control whether to wrap ScriptObject->ScriptObjectMirror for
671556Srgrimes    // Object type arguments of Java method calls, field set and array set.
6817987Speter    private static final boolean MIRROR_ALWAYS = Options.getBooleanProperty("nashorn.mirror.always", true);
6917987Speter
701556Srgrimes    private static final MethodHandle EXPORT_ARGUMENT;
711556Srgrimes    private static final MethodHandle IMPORT_RESULT;
721556Srgrimes    private static final MethodHandle FILTER_CONSSTRING;
731556Srgrimes
741556Srgrimes    static {
751556Srgrimes        final Lookup lookup  = new Lookup(MethodHandles.lookup());
761556Srgrimes        EXPORT_ARGUMENT      = lookup.findOwnStatic("exportArgument", Object.class, Object.class);
771556Srgrimes        IMPORT_RESULT        = lookup.findOwnStatic("importResult", Object.class, Object.class);
781556Srgrimes        FILTER_CONSSTRING    = lookup.findOwnStatic("consStringFilter", Object.class, Object.class);
791556Srgrimes    }
801556Srgrimes
811556Srgrimes    // cache of @FunctionalInterface method of implementor classes
821556Srgrimes    private static final ClassValue<Method> FUNCTIONAL_IFACE_METHOD = new ClassValue<Method>() {
831556Srgrimes        @Override
841556Srgrimes        protected Method computeValue(final Class<?> type) {
851556Srgrimes            return findFunctionalInterfaceMethod(type);
8628346Ssteve        }
871556Srgrimes    };
881556Srgrimes
8928346Ssteve    private final BeansLinker beansLinker = new BeansLinker();
901556Srgrimes
9138536Scracauer    @Override
9238950Scracauer    public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
9338536Scracauer        final Object self = linkRequest.getReceiver();
941556Srgrimes        final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor();
9520425Ssteve        if (self instanceof ConsString) {
9690111Simp            // In order to treat ConsString like a java.lang.String we need a link request with a string receiver.
9720425Ssteve            final Object[] arguments = linkRequest.getArguments();
9890111Simp            arguments[0] = "";
9990111Simp            final LinkRequest forgedLinkRequest = linkRequest.replaceArguments(desc, arguments);
10090111Simp            final GuardedInvocation invocation = getGuardedInvocation(beansLinker, forgedLinkRequest, linkerServices);
10120425Ssteve            // If an invocation is found we add a filter that makes it work for both Strings and ConsStrings.
10290111Simp            return invocation == null ? null : invocation.filterArguments(0, FILTER_CONSSTRING);
10320425Ssteve        }
10490111Simp
10590111Simp        if (self != null && "call".equals(desc.getNameToken(CallSiteDescriptor.OPERATOR))) {
10690111Simp            // Support dyn:call on any object that supports some @FunctionalInterface
1071556Srgrimes            // annotated interface. This way Java method, constructor references or
1081556Srgrimes            // implementations of java.util.function.* interfaces can be called as though
1091556Srgrimes            // those are script functions.
1101556Srgrimes            final Method m = getFunctionalInterfaceMethod(self.getClass());
1111556Srgrimes            if (m != null) {
1121556Srgrimes                final MethodType callType = desc.getMethodType();
1131556Srgrimes                // 'callee' and 'thiz' passed from script + actual arguments
1141556Srgrimes                if (callType.parameterCount() != m.getParameterCount() + 2) {
1151556Srgrimes                    throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self));
1161556Srgrimes                }
1171556Srgrimes                return new GuardedInvocation(
1181556Srgrimes                        // drop 'thiz' passed from the script.
11920425Ssteve                        MH.dropArguments(linkerServices.filterInternalObjects(desc.getLookup().unreflect(m)), 1,
1201556Srgrimes                                callType.parameterType(1)), Guards.getInstanceOfGuard(
12190111Simp                                        m.getDeclaringClass())).asTypeSafeReturn(
12217987Speter                                                new NashornBeansLinkerServices(linkerServices), callType);
1231556Srgrimes            }
1241556Srgrimes        }
1251556Srgrimes        return getGuardedInvocation(beansLinker, linkRequest, linkerServices);
1261556Srgrimes    }
1271556Srgrimes
1281556Srgrimes    /**
1291556Srgrimes     * Delegates to the specified linker but injects its linker services wrapper so that it will apply all special
1301556Srgrimes     * conversions that this class does.
13120425Ssteve     * @param delegateLinker the linker to which the actual work is delegated to.
1321556Srgrimes     * @param linkRequest the delegated link request
13320425Ssteve     * @param linkerServices the original link services that will be augmented with special conversions
13420425Ssteve     * @return the guarded invocation from the delegate, possibly augmented with special conversions
13520425Ssteve     * @throws Exception if the delegate throws an exception
13620425Ssteve     */
1371556Srgrimes    public static GuardedInvocation getGuardedInvocation(final GuardingDynamicLinker delegateLinker, final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
1381556Srgrimes        return delegateLinker.getGuardedInvocation(linkRequest, new NashornBeansLinkerServices(linkerServices));
1391556Srgrimes    }
1401556Srgrimes
1411556Srgrimes    @SuppressWarnings("unused")
14217987Speter    private static Object exportArgument(final Object arg) {
14317987Speter        return exportArgument(arg, MIRROR_ALWAYS);
1441556Srgrimes    }
1451556Srgrimes
1461556Srgrimes    static Object exportArgument(final Object arg, final boolean mirrorAlways) {
1471556Srgrimes        if (arg instanceof ConsString) {
1481556Srgrimes            return arg.toString();
1491556Srgrimes        } else if (mirrorAlways && arg instanceof ScriptObject) {
1501556Srgrimes            return ScriptUtils.wrap((ScriptObject)arg);
1511556Srgrimes        } else {
1521556Srgrimes            return arg;
1531556Srgrimes        }
1541556Srgrimes    }
1551556Srgrimes
1561556Srgrimes    @SuppressWarnings("unused")
1571556Srgrimes    private static Object importResult(final Object arg) {
15817987Speter        return ScriptUtils.unwrap(arg);
15920425Ssteve    }
1601556Srgrimes
16120425Ssteve    @SuppressWarnings("unused")
16220425Ssteve    private static Object consStringFilter(final Object arg) {
16320425Ssteve        return arg instanceof ConsString ? arg.toString() : arg;
1641556Srgrimes    }
16517987Speter
16620425Ssteve    private static Method findFunctionalInterfaceMethod(final Class<?> clazz) {
1671556Srgrimes        if (clazz == null) {
16820425Ssteve            return null;
16920425Ssteve        }
17020425Ssteve
1711556Srgrimes        for (final Class<?> iface : clazz.getInterfaces()) {
1721556Srgrimes            // check accessiblity up-front
1731556Srgrimes            if (! Context.isAccessibleClass(iface)) {
1741556Srgrimes                continue;
1751556Srgrimes            }
1761556Srgrimes
17720425Ssteve            // check for @FunctionalInterface
1781556Srgrimes            if (iface.isAnnotationPresent(FunctionalInterface.class)) {
1791556Srgrimes                // return the first abstract method
1801556Srgrimes                for (final Method m : iface.getMethods()) {
18128346Ssteve                    if (Modifier.isAbstract(m.getModifiers())) {
18217987Speter                        return m;
1831556Srgrimes                    }
1841556Srgrimes                }
1851556Srgrimes            }
1861556Srgrimes        }
1871556Srgrimes
1881556Srgrimes        // did not find here, try super class
1891556Srgrimes        return findFunctionalInterfaceMethod(clazz.getSuperclass());
1901556Srgrimes    }
1911556Srgrimes
1921556Srgrimes    // Returns @FunctionalInterface annotated interface's single abstract
1931556Srgrimes    // method. If not found, returns null.
1941556Srgrimes    static Method getFunctionalInterfaceMethod(final Class<?> clazz) {
1951556Srgrimes        return FUNCTIONAL_IFACE_METHOD.get(clazz);
19617987Speter    }
19790111Simp
19817987Speter    static MethodHandleTransformer createHiddenObjectFilter() {
1991556Srgrimes        return new DefaultInternalObjectFilter(EXPORT_ARGUMENT, MIRROR_ALWAYS ? IMPORT_RESULT : null);
2001556Srgrimes    }
2011556Srgrimes
2021556Srgrimes    private static class NashornBeansLinkerServices implements LinkerServices {
2031556Srgrimes        private final LinkerServices linkerServices;
2041556Srgrimes
2051556Srgrimes        NashornBeansLinkerServices(final LinkerServices linkerServices) {
2061556Srgrimes            this.linkerServices = linkerServices;
20720425Ssteve        }
2081556Srgrimes
20920425Ssteve        @Override
21020425Ssteve        public MethodHandle asType(final MethodHandle handle, final MethodType fromType) {
21120425Ssteve            return linkerServices.asType(handle, fromType);
2121556Srgrimes        }
2131556Srgrimes
21445916Scracauer        @Override
2151556Srgrimes        public MethodHandle asTypeLosslessReturn(final MethodHandle handle, final MethodType fromType) {
2161556Srgrimes            return Implementation.asTypeLosslessReturn(this, handle, fromType);
2171556Srgrimes        }
2181556Srgrimes
2191556Srgrimes        @Override
22017987Speter        public MethodHandle getTypeConverter(final Class<?> sourceType, final Class<?> targetType) {
22190111Simp            return linkerServices.getTypeConverter(sourceType, targetType);
22217987Speter        }
2231556Srgrimes
2241556Srgrimes        @Override
2251556Srgrimes        public boolean canConvert(final Class<?> from, final Class<?> to) {
2261556Srgrimes            return linkerServices.canConvert(from, to);
2271556Srgrimes        }
2281556Srgrimes
2291556Srgrimes        @Override
2301556Srgrimes        public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest) throws Exception {
2311556Srgrimes            return linkerServices.getGuardedInvocation(linkRequest);
2321556Srgrimes        }
2331556Srgrimes
2341556Srgrimes        @Override
2351556Srgrimes        public Comparison compareConversion(final Class<?> sourceType, final Class<?> targetType1, final Class<?> targetType2) {
23690111Simp            if (sourceType == ConsString.class) {
23717987Speter                if (String.class == targetType1 || CharSequence.class == targetType1) {
2381556Srgrimes                    return Comparison.TYPE_1_BETTER;
2391556Srgrimes                }
2401556Srgrimes
2411556Srgrimes                if (String.class == targetType2 || CharSequence.class == targetType2) {
2421556Srgrimes                    return Comparison.TYPE_2_BETTER;
2431556Srgrimes                }
2441556Srgrimes            }
2451556Srgrimes            return linkerServices.compareConversion(sourceType, targetType1, targetType2);
24626104Ssteve        }
2471556Srgrimes
2481556Srgrimes        @Override
2491556Srgrimes        public MethodHandle filterInternalObjects(MethodHandle target) {
2501556Srgrimes            return linkerServices.filterInternalObjects(target);
2511556Srgrimes        }
2521556Srgrimes    }
2531556Srgrimes}
2541556Srgrimes