NashornLinker.java revision 1481:e6bb9489faac
11541Srgrimes/*
21541Srgrimes * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
31541Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
41541Srgrimes *
551138Salfred * This code is free software; you can redistribute it and/or modify it
6107914Sdillon * under the terms of the GNU General Public License version 2 only, as
71541Srgrimes * published by the Free Software Foundation.  Oracle designates this
81541Srgrimes * particular file as subject to the "Classpath" exception as provided
91541Srgrimes * by Oracle in the LICENSE file that accompanied this code.
1064002Speter *
111541Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT
121541Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
131541Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
141541Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
151541Srgrimes * accompanied this code).
161541Srgrimes *
171541Srgrimes * You should have received a copy of the GNU General Public License version
181541Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
191541Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
201541Srgrimes *
211541Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
221541Srgrimes * or visit www.oracle.com if you need additional information or have any
231541Srgrimes * questions.
241541Srgrimes */
251541Srgrimes
261541Srgrimespackage jdk.nashorn.internal.runtime.linker;
271541Srgrimes
281541Srgrimesimport static jdk.nashorn.internal.lookup.Lookup.MH;
291541Srgrimes
301541Srgrimesimport java.lang.invoke.MethodHandle;
311541Srgrimesimport java.lang.invoke.MethodHandles;
321541Srgrimesimport java.lang.invoke.MethodType;
331541Srgrimesimport java.lang.reflect.Modifier;
341541Srgrimesimport java.security.AccessControlContext;
351541Srgrimesimport java.security.AccessController;
361541Srgrimesimport java.security.PrivilegedAction;
371541Srgrimesimport java.util.Collection;
381541Srgrimesimport java.util.Deque;
391541Srgrimesimport java.util.List;
401541Srgrimesimport java.util.Map;
411541Srgrimesimport java.util.Queue;
421541Srgrimesimport java.util.function.Supplier;
431541Srgrimesimport javax.script.Bindings;
441541Srgrimesimport jdk.internal.dynalink.CallSiteDescriptor;
451541Srgrimesimport jdk.internal.dynalink.linker.ConversionComparator;
461541Srgrimesimport jdk.internal.dynalink.linker.GuardedInvocation;
471541Srgrimesimport jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
481541Srgrimesimport jdk.internal.dynalink.linker.LinkRequest;
491541Srgrimesimport jdk.internal.dynalink.linker.LinkerServices;
501541Srgrimesimport jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
511541Srgrimesimport jdk.internal.dynalink.linker.support.Guards;
521541Srgrimesimport jdk.internal.dynalink.linker.support.Lookup;
531541Srgrimesimport jdk.nashorn.api.scripting.JSObject;
541541Srgrimesimport jdk.nashorn.api.scripting.ScriptObjectMirror;
5552150Smarcelimport jdk.nashorn.api.scripting.ScriptUtils;
561541Srgrimesimport jdk.nashorn.internal.objects.NativeArray;
5752150Smarcelimport jdk.nashorn.internal.runtime.AccessControlContextFactory;
581541Srgrimesimport jdk.nashorn.internal.runtime.JSType;
591541Srgrimesimport jdk.nashorn.internal.runtime.ListAdapter;
601541Srgrimesimport jdk.nashorn.internal.runtime.ScriptFunction;
6152150Smarcelimport jdk.nashorn.internal.runtime.ScriptObject;
621541Srgrimesimport jdk.nashorn.internal.runtime.Undefined;
631541Srgrimes
641541Srgrimes/**
651541Srgrimes * This is the main dynamic linker for Nashorn. It is used for linking all {@link ScriptObject} and its subclasses (this
661541Srgrimes * includes {@link ScriptFunction} and its subclasses) as well as {@link Undefined}.
671541Srgrimes */
681541Srgrimesfinal class NashornLinker implements TypeBasedGuardingDynamicLinker, GuardingTypeConverterFactory, ConversionComparator {
691541Srgrimes    private static final AccessControlContext GET_LOOKUP_PERMISSION_CONTEXT =
701541Srgrimes            AccessControlContextFactory.createAccessControlContext(CallSiteDescriptor.GET_LOOKUP_PERMISSION);
711541Srgrimes
721541Srgrimes    private static final ClassValue<MethodHandle> ARRAY_CONVERTERS = new ClassValue<MethodHandle>() {
731541Srgrimes        @Override
741541Srgrimes        protected MethodHandle computeValue(final Class<?> type) {
751541Srgrimes            return createArrayConverter(type);
761541Srgrimes        }
771541Srgrimes    };
781541Srgrimes
791541Srgrimes    /**
801541Srgrimes     * Returns true if {@code ScriptObject} is assignable from {@code type}, or it is {@code Undefined}.
811541Srgrimes     */
821541Srgrimes    @Override
831541Srgrimes    public boolean canLinkType(final Class<?> type) {
841541Srgrimes        return canLinkTypeStatic(type);
851541Srgrimes    }
861541Srgrimes
871541Srgrimes    static boolean canLinkTypeStatic(final Class<?> type) {
881541Srgrimes        return ScriptObject.class.isAssignableFrom(type) || Undefined.class == type;
891541Srgrimes    }
901541Srgrimes
911541Srgrimes    @Override
921541Srgrimes    public GuardedInvocation getGuardedInvocation(final LinkRequest request, final LinkerServices linkerServices) throws Exception {
931541Srgrimes        final Object self = request.getReceiver();
941541Srgrimes        final CallSiteDescriptor desc = request.getCallSiteDescriptor();
951541Srgrimes
961541Srgrimes        if (desc.getNameTokenCount() < 2 || !"dyn".equals(desc.getNameToken(CallSiteDescriptor.SCHEME))) {
971541Srgrimes            // We only support standard "dyn:*[:*]" operations
981541Srgrimes            return null;
991541Srgrimes        }
1001541Srgrimes
1011541Srgrimes        return Bootstrap.asTypeSafeReturn(getGuardedInvocation(self,  request, desc), linkerServices, desc);
1021541Srgrimes    }
1031541Srgrimes
1041541Srgrimes    private static GuardedInvocation getGuardedInvocation(final Object self, final LinkRequest request, final CallSiteDescriptor desc) {
1051541Srgrimes        final GuardedInvocation inv;
1061541Srgrimes        if (self instanceof ScriptObject) {
1071541Srgrimes            inv = ((ScriptObject)self).lookup(desc, request);
1081541Srgrimes        } else if (self instanceof Undefined) {
1091541Srgrimes            inv = Undefined.lookup(desc);
110105950Speter        } else {
1111541Srgrimes            throw new AssertionError(self.getClass().getName()); // Should never reach here.
1121541Srgrimes        }
1131541Srgrimes
1141541Srgrimes        return inv;
1151541Srgrimes    }
1161541Srgrimes
1171541Srgrimes    @Override
11852150Smarcel    public GuardedInvocation convertToType(final Class<?> sourceType, final Class<?> targetType, final Supplier<MethodHandles.Lookup> lookupSupplier) throws Exception {
1191541Srgrimes        GuardedInvocation gi = convertToTypeNoCast(sourceType, targetType);
1201541Srgrimes        if(gi == null) {
1211541Srgrimes            gi = getSamTypeConverter(sourceType, targetType, lookupSupplier);
1221541Srgrimes        }
1231541Srgrimes        return gi == null ? null : gi.asType(MH.type(targetType, sourceType));
1241541Srgrimes    }
1251541Srgrimes
1261541Srgrimes    /**
1271541Srgrimes     * Main part of the implementation of {@link GuardingTypeConverterFactory#convertToType(Class, Class)} that doesn't
1281541Srgrimes     * care about adapting the method signature; that's done by the invoking method. Returns either a built-in
1291541Srgrimes     * conversion to primitive (or primitive wrapper) Java types or to String, or a just-in-time generated converter to
1301541Srgrimes     * a SAM type (if the target type is a SAM type).
1311541Srgrimes     * @param sourceType the source type
1328019Sache     * @param targetType the target type
1338019Sache     * @return a guarded invocation that converts from the source type to the target type.
1341541Srgrimes     * @throws Exception if something goes wrong
1351541Srgrimes     */
1361541Srgrimes    private static GuardedInvocation convertToTypeNoCast(final Class<?> sourceType, final Class<?> targetType) throws Exception {
1371541Srgrimes        final MethodHandle mh = JavaArgumentConverters.getConverter(targetType);
1381541Srgrimes        if (mh != null) {
1391541Srgrimes            return new GuardedInvocation(mh, canLinkTypeStatic(sourceType) ? null : IS_NASHORN_OR_UNDEFINED_TYPE);
1401541Srgrimes        }
1411541Srgrimes
1421541Srgrimes        final GuardedInvocation arrayConverter = getArrayConverter(sourceType, targetType);
1431541Srgrimes        if(arrayConverter != null) {
1441541Srgrimes            return arrayConverter;
1451541Srgrimes        }
1461541Srgrimes
1471541Srgrimes        return getMirrorConverter(sourceType, targetType);
1481541Srgrimes    }
1491541Srgrimes
1501541Srgrimes    /**
1511541Srgrimes     * Returns a guarded invocation that converts from a source type that is ScriptFunction, or a subclass or a
1521541Srgrimes     * superclass of it) to a SAM type.
1531541Srgrimes     * @param sourceType the source type (presumably ScriptFunction or a subclass or a superclass of it)
1541541Srgrimes     * @param targetType the target type (presumably a SAM type)
1551541Srgrimes     * @return a guarded invocation that converts from the source type to the target SAM type. null is returned if
1561541Srgrimes     * either the source type is neither ScriptFunction, nor a subclass, nor a superclass of it, or if the target type
1571541Srgrimes     * is not a SAM type.
1581541Srgrimes     * @throws Exception if something goes wrong; generally, if there's an issue with creation of the SAM proxy type
1591541Srgrimes     * constructor.
1601541Srgrimes     */
1611541Srgrimes    private static GuardedInvocation getSamTypeConverter(final Class<?> sourceType, final Class<?> targetType, final Supplier<MethodHandles.Lookup> lookupSupplier) throws Exception {
1621549Srgrimes        // If source type is more generic than ScriptFunction class, we'll need to use a guard
1631549Srgrimes        final boolean isSourceTypeGeneric = sourceType.isAssignableFrom(ScriptFunction.class);
1641549Srgrimes
1651549Srgrimes        if ((isSourceTypeGeneric || ScriptFunction.class.isAssignableFrom(sourceType)) && isAutoConvertibleFromFunction(targetType)) {
1662442Sdg            final MethodHandle ctor = JavaAdapterFactory.getConstructor(ScriptFunction.class, targetType, getCurrentLookup(lookupSupplier));
1672729Sdfr            assert ctor != null; // if isAutoConvertibleFromFunction() returned true, then ctor must exist.
1682729Sdfr            return new GuardedInvocation(ctor, isSourceTypeGeneric ? IS_SCRIPT_FUNCTION : null);
1691541Srgrimes        }
17045065Salc        return null;
17145065Salc    }
1722297Swollman
1731541Srgrimes    private static MethodHandles.Lookup getCurrentLookup(final Supplier<MethodHandles.Lookup> lookupSupplier) {
1741541Srgrimes        return AccessController.doPrivileged(new PrivilegedAction<MethodHandles.Lookup>() {
1751541Srgrimes            @Override
1761541Srgrimes            public MethodHandles.Lookup run() {
1771541Srgrimes                return lookupSupplier.get();
1781541Srgrimes            }
1791541Srgrimes        }, GET_LOOKUP_PERMISSION_CONTEXT);
1801541Srgrimes    }
1811541Srgrimes
1821541Srgrimes    /**
1831541Srgrimes     * Returns a guarded invocation that converts from a source type that is NativeArray to a Java array or List or
1841541Srgrimes     * Queue or Deque or Collection type.
1851541Srgrimes     * @param sourceType the source type (presumably NativeArray a superclass of it)
1861541Srgrimes     * @param targetType the target type (presumably an array type, or List or Queue, or Deque, or Collection)
1871541Srgrimes     * @return a guarded invocation that converts from the source type to the target type. null is returned if
1881541Srgrimes     * either the source type is neither NativeArray, nor a superclass of it, or if the target type is not an array
1891541Srgrimes     * type, List, Queue, Deque, or Collection.
1901541Srgrimes     */
1911541Srgrimes    private static GuardedInvocation getArrayConverter(final Class<?> sourceType, final Class<?> targetType) {
19235938Sdyson        final boolean isSourceTypeNativeArray = sourceType == NativeArray.class;
19335938Sdyson        // If source type is more generic than NativeArray class, we'll need to use a guard
19428400Speter        final boolean isSourceTypeGeneric = !isSourceTypeNativeArray && sourceType.isAssignableFrom(NativeArray.class);
19529349Speter
19612865Speter        if (isSourceTypeNativeArray || isSourceTypeGeneric) {
19712865Speter            final MethodHandle guard = isSourceTypeGeneric ? IS_NATIVE_ARRAY : null;
19812865Speter            if(targetType.isArray()) {
19912865Speter                return new GuardedInvocation(ARRAY_CONVERTERS.get(targetType), guard);
20012865Speter            } else if(targetType == List.class) {
20112865Speter                return new GuardedInvocation(TO_LIST, guard);
20212865Speter            } else if(targetType == Deque.class) {
20312865Speter                return new GuardedInvocation(TO_DEQUE, guard);
20412865Speter            } else if(targetType == Queue.class) {
20512865Speter                return new GuardedInvocation(TO_QUEUE, guard);
20612865Speter            } else if(targetType == Collection.class) {
20725582Speter                return new GuardedInvocation(TO_COLLECTION, guard);
20825582Speter            }
20925582Speter        }
21025582Speter        return null;
21114220Speter    }
21214220Speter
21329349Speter    private static MethodHandle createArrayConverter(final Class<?> type) {
21424452Speter        assert type.isArray();
21524440Speter        final MethodHandle converter = MH.insertArguments(JSType.TO_JAVA_ARRAY.methodHandle(), 1, type.getComponentType());
21635938Sdyson        return MH.asType(converter, converter.type().changeReturnType(type));
21735938Sdyson    }
21835938Sdyson
21935938Sdyson    private static GuardedInvocation getMirrorConverter(final Class<?> sourceType, final Class<?> targetType) {
22035938Sdyson        // Could've also used (targetType.isAssignableFrom(ScriptObjectMirror.class) && targetType != Object.class) but
22135938Sdyson        // it's probably better to explicitly spell out the supported target types
22235938Sdyson        if (targetType == Map.class || targetType == Bindings.class || targetType == JSObject.class || targetType == ScriptObjectMirror.class) {
22335938Sdyson            if (ScriptObject.class.isAssignableFrom(sourceType)) {
22451138Salfred                return new GuardedInvocation(CREATE_MIRROR);
22551138Salfred            } else if (sourceType.isAssignableFrom(ScriptObject.class) || sourceType.isInterface()) {
22651138Salfred                return new GuardedInvocation(CREATE_MIRROR, IS_SCRIPT_OBJECT);
22725537Sdfr            }
22825537Sdfr        }
22925537Sdfr        return null;
23025537Sdfr    }
23125537Sdfr
23225537Sdfr    private static boolean isAutoConvertibleFromFunction(final Class<?> clazz) {
23325537Sdfr        return isAbstractClass(clazz) && !ScriptObject.class.isAssignableFrom(clazz) &&
23425537Sdfr                JavaAdapterFactory.isAutoConvertibleFromFunction(clazz);
23525537Sdfr    }
23625537Sdfr
23728400Speter    /**
23856115Speter     * Utility method used by few other places in the code. Tests if the class has the abstract modifier and is not an
23956115Speter     * array class. For some reason, array classes have the abstract modifier set in HotSpot JVM, and we don't want to
24036034Speter     * treat array classes as abstract.
24126671Sdyson     * @param clazz the inspected class
24226671Sdyson     * @return true if the class is abstract and is not an array type.
24326671Sdyson     */
24426671Sdyson    static boolean isAbstractClass(final Class<?> clazz) {
24526671Sdyson        return Modifier.isAbstract(clazz.getModifiers()) && !clazz.isArray();
24626671Sdyson    }
24726671Sdyson
24826671Sdyson
24969514Sjake    @Override
25069514Sjake    public Comparison compareConversion(final Class<?> sourceType, final Class<?> targetType1, final Class<?> targetType2) {
25126671Sdyson        if(sourceType == NativeArray.class) {
25226671Sdyson            // Prefer lists, as they're less costly to create than arrays.
25329391Sphk            if(isList(targetType1)) {
25434925Sdufault                if(!isList(targetType2)) {
25534925Sdufault                    return Comparison.TYPE_1_BETTER;
25634925Sdufault                }
25734925Sdufault            } else if(isList(targetType2)) {
25834925Sdufault                return Comparison.TYPE_2_BETTER;
25934925Sdufault            }
26034925Sdufault            // Then prefer arrays
26134925Sdufault            if(targetType1.isArray()) {
26235938Sdyson                if(!targetType2.isArray()) {
26399856Salfred                    return Comparison.TYPE_1_BETTER;
26441089Speter                }
26546155Sphk            } else if(targetType2.isArray()) {
26651791Smarcel                return Comparison.TYPE_2_BETTER;
26751791Smarcel            }
268105950Speter        }
26951791Smarcel        if(ScriptObject.class.isAssignableFrom(sourceType)) {
270105950Speter            // Prefer interfaces
27156271Srwatson            if(targetType1.isInterface()) {
27256271Srwatson                if(!targetType2.isInterface()) {
27356271Srwatson                    return Comparison.TYPE_1_BETTER;
27456271Srwatson                }
27556271Srwatson            } else if(targetType2.isInterface()) {
27656271Srwatson                return Comparison.TYPE_2_BETTER;
27756271Srwatson            }
27856271Srwatson        }
27954803Srwatson        return Comparison.INDETERMINATE;
28054803Srwatson    }
28154803Srwatson
28254803Srwatson    private static boolean isList(final Class<?> clazz) {
28355943Sjasone        return clazz == List.class || clazz == Deque.class;
28456115Speter    }
28556115Speter
28659288Sjlemon    private static final MethodHandle IS_SCRIPT_OBJECT = Guards.isInstance(ScriptObject.class, MH.type(Boolean.TYPE, Object.class));
28759288Sjlemon    private static final MethodHandle IS_SCRIPT_FUNCTION = Guards.isInstance(ScriptFunction.class, MH.type(Boolean.TYPE, Object.class));
28875039Srwatson    private static final MethodHandle IS_NATIVE_ARRAY = Guards.isOfClass(NativeArray.class, MH.type(Boolean.TYPE, Object.class));
28975039Srwatson
29075039Srwatson    private static final MethodHandle IS_NASHORN_OR_UNDEFINED_TYPE = findOwnMH("isNashornTypeOrUndefined", Boolean.TYPE, Object.class);
29175427Srwatson    private static final MethodHandle CREATE_MIRROR = findOwnMH("createMirror", Object.class, Object.class);
29283652Speter
29383796Srwatson    private static final MethodHandle TO_COLLECTION;
29485891Sphk    private static final MethodHandle TO_DEQUE;
29590889Sjulian    private static final MethodHandle TO_LIST;
29690889Sjulian    private static final MethodHandle TO_QUEUE;
297103972Sarchie    static {
298103972Sarchie        final MethodHandle listAdapterCreate = new Lookup(MethodHandles.lookup()).findStatic(
299103972Sarchie                ListAdapter.class, "create", MethodType.methodType(ListAdapter.class, Object.class));
300100897Srwatson        TO_COLLECTION = asReturning(listAdapterCreate, Collection.class);
301100897Srwatson        TO_DEQUE = asReturning(listAdapterCreate, Deque.class);
302100897Srwatson        TO_LIST = asReturning(listAdapterCreate, List.class);
303100897Srwatson        TO_QUEUE = asReturning(listAdapterCreate, Queue.class);
304100897Srwatson    }
305100897Srwatson
30694936Smux    private static MethodHandle asReturning(final MethodHandle mh, final Class<?> nrtype) {
30796084Smux        return mh.asType(mh.type().changeReturnType(nrtype));
30897372Smarcel    }
30999856Salfred
310101426Srwatson    @SuppressWarnings("unused")
311103575Salfred    private static boolean isNashornTypeOrUndefined(final Object obj) {
312103575Salfred        return obj instanceof ScriptObject || obj instanceof Undefined;
313103575Salfred    }
314103575Salfred
315103575Salfred    @SuppressWarnings("unused")
316103575Salfred    private static Object createMirror(final Object obj) {
317103575Salfred        return obj instanceof ScriptObject? ScriptUtils.wrap((ScriptObject)obj) : obj;
318103575Salfred    }
319103575Salfred
320105692Srwatson    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
321105692Srwatson        return MH.findStatic(MethodHandles.lookup(), NashornLinker.class, name, MH.type(rtype, types));
322105692Srwatson    }
323104731Srwatson}
324104731Srwatson
325104731Srwatson