NashornBottomLinker.java revision 1477:dd36e980905b
155682Smarkm/*
257416Smarkm * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
355682Smarkm * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
455682Smarkm *
555682Smarkm * This code is free software; you can redistribute it and/or modify it
655682Smarkm * under the terms of the GNU General Public License version 2 only, as
755682Smarkm * published by the Free Software Foundation.  Oracle designates this
855682Smarkm * particular file as subject to the "Classpath" exception as provided
955682Smarkm * by Oracle in the LICENSE file that accompanied this code.
1055682Smarkm *
1155682Smarkm * This code is distributed in the hope that it will be useful, but WITHOUT
1255682Smarkm * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1355682Smarkm * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1455682Smarkm * version 2 for more details (a copy is included in the LICENSE file that
1555682Smarkm * accompanied this code).
1655682Smarkm *
1755682Smarkm * You should have received a copy of the GNU General Public License version
1855682Smarkm * 2 along with this work; if not, write to the Free Software Foundation,
1955682Smarkm * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2055682Smarkm *
2155682Smarkm * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2255682Smarkm * or visit www.oracle.com if you need additional information or have any
2355682Smarkm * questions.
2455682Smarkm */
2555682Smarkm
2655682Smarkmpackage jdk.nashorn.internal.runtime.linker;
2755682Smarkm
2855682Smarkmimport static jdk.nashorn.internal.lookup.Lookup.MH;
2955682Smarkmimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
3055682Smarkmimport static jdk.nashorn.internal.runtime.JSType.GET_UNDEFINED;
3155682Smarkmimport static jdk.nashorn.internal.runtime.JSType.TYPE_OBJECT_INDEX;
3255682Smarkmimport static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
3355682Smarkm
3455682Smarkmimport java.lang.invoke.MethodHandle;
3555682Smarkmimport java.lang.invoke.MethodHandles;
3657416Smarkmimport java.util.HashMap;
3755682Smarkmimport java.util.Map;
3855682Smarkmimport java.util.function.Supplier;
3955682Smarkmimport jdk.internal.dynalink.CallSiteDescriptor;
4055682Smarkmimport jdk.internal.dynalink.beans.BeansLinker;
4155682Smarkmimport jdk.internal.dynalink.linker.GuardedInvocation;
4255682Smarkmimport jdk.internal.dynalink.linker.GuardingDynamicLinker;
4355682Smarkmimport jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
4455682Smarkmimport jdk.internal.dynalink.linker.LinkRequest;
4555682Smarkmimport jdk.internal.dynalink.linker.LinkerServices;
4655682Smarkmimport jdk.internal.dynalink.linker.support.Guards;
4755682Smarkmimport jdk.nashorn.internal.codegen.types.Type;
4855682Smarkmimport jdk.nashorn.internal.runtime.JSType;
4955682Smarkmimport jdk.nashorn.internal.runtime.ScriptRuntime;
5055682Smarkmimport jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
5155682Smarkm
5255682Smarkm/**
5355682Smarkm * Nashorn bottom linker; used as a last-resort catch-all linker for all linking requests that fall through all other
5455682Smarkm * linkers (see how {@link Bootstrap} class configures the dynamic linker in its static initializer). It will throw
5555682Smarkm * appropriate ECMAScript errors for attempts to invoke operations on {@code null}, link no-op property getters and
5655682Smarkm * setters for Java objects that couldn't be linked by any other linker, and throw appropriate ECMAScript errors for
5755682Smarkm * attempts to invoke arbitrary Java objects as functions or constructors.
5855682Smarkm */
5955682Smarkmfinal class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeConverterFactory {
6055682Smarkm
6155682Smarkm    @Override
6255682Smarkm    public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices)
6355682Smarkm            throws Exception {
6455682Smarkm        final Object self = linkRequest.getReceiver();
6555682Smarkm
6655682Smarkm        if (self == null) {
6755682Smarkm            return linkNull(linkRequest);
6855682Smarkm        }
6955682Smarkm
7055682Smarkm        // None of the objects that can be linked by NashornLinker should ever reach here. Basically, anything below
7155682Smarkm        // this point is a generic Java bean. Therefore, reaching here with a ScriptObject is a Nashorn bug.
7255682Smarkm        assert isExpectedObject(self) : "Couldn't link " + linkRequest.getCallSiteDescriptor() + " for " + self.getClass().getName();
7355682Smarkm
7455682Smarkm        return linkBean(linkRequest, linkerServices);
7555682Smarkm    }
7655682Smarkm
7755682Smarkm    private static final MethodHandle EMPTY_PROP_GETTER =
7855682Smarkm            MH.dropArguments(MH.constant(Object.class, UNDEFINED), 0, Object.class);
7955682Smarkm    private static final MethodHandle EMPTY_ELEM_GETTER =
8055682Smarkm            MH.dropArguments(EMPTY_PROP_GETTER, 0, Object.class);
8155682Smarkm    private static final MethodHandle EMPTY_PROP_SETTER =
8255682Smarkm            MH.asType(EMPTY_ELEM_GETTER, EMPTY_ELEM_GETTER.type().changeReturnType(void.class));
8355682Smarkm    private static final MethodHandle EMPTY_ELEM_SETTER =
8455682Smarkm            MH.dropArguments(EMPTY_PROP_SETTER, 0, Object.class);
8555682Smarkm
8655682Smarkm    private static GuardedInvocation linkBean(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
8755682Smarkm        final NashornCallSiteDescriptor desc = (NashornCallSiteDescriptor)linkRequest.getCallSiteDescriptor();
8855682Smarkm        final Object self = linkRequest.getReceiver();
8955682Smarkm        final String operator = desc.getFirstOperator();
9055682Smarkm        switch (operator) {
9155682Smarkm        case "new":
9255682Smarkm            if(BeansLinker.isDynamicConstructor(self)) {
9355682Smarkm                throw typeError("no.constructor.matches.args", ScriptRuntime.safeToString(self));
9455682Smarkm            }
9555682Smarkm            if(BeansLinker.isDynamicMethod(self)) {
9655682Smarkm                throw typeError("method.not.constructor", ScriptRuntime.safeToString(self));
9755682Smarkm            }
9855682Smarkm            throw typeError("not.a.function", desc.getFunctionErrorMessage(self));
9955682Smarkm        case "call":
10055682Smarkm            if(BeansLinker.isDynamicConstructor(self)) {
10155682Smarkm                throw typeError("constructor.requires.new", ScriptRuntime.safeToString(self));
10255682Smarkm            }
10355682Smarkm            if(BeansLinker.isDynamicMethod(self)) {
10455682Smarkm                throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self));
10555682Smarkm            }
10655682Smarkm            throw typeError("not.a.function", desc.getFunctionErrorMessage(self));
10755682Smarkm        case "callMethod":
10855682Smarkm            throw typeError("no.such.function", getArgument(linkRequest), ScriptRuntime.safeToString(self));
10955682Smarkm        case "getMethod":
11055682Smarkm            // evaluate to undefined, later on Undefined will take care of throwing TypeError
11155682Smarkm            return getInvocation(MH.dropArguments(GET_UNDEFINED.get(TYPE_OBJECT_INDEX), 0, Object.class), self, linkerServices, desc);
11255682Smarkm        case "getProp":
11355682Smarkm        case "getElem":
11455682Smarkm            if(NashornCallSiteDescriptor.isOptimistic(desc)) {
11555682Smarkm                throw new UnwarrantedOptimismException(UNDEFINED, NashornCallSiteDescriptor.getProgramPoint(desc), Type.OBJECT);
11655682Smarkm            }
11755682Smarkm            if (desc.getOperand() != null) {
11855682Smarkm                return getInvocation(EMPTY_PROP_GETTER, self, linkerServices, desc);
11955682Smarkm            }
12055682Smarkm            return getInvocation(EMPTY_ELEM_GETTER, self, linkerServices, desc);
12155682Smarkm        case "setProp":
12255682Smarkm        case "setElem": {
12355682Smarkm            final boolean strict = NashornCallSiteDescriptor.isStrict(desc);
12455682Smarkm            if (strict) {
12555682Smarkm                throw typeError("cant.set.property", getArgument(linkRequest), ScriptRuntime.safeToString(self));
12655682Smarkm            }
12755682Smarkm            if (desc.getOperand() != null) {
12855682Smarkm                return getInvocation(EMPTY_PROP_SETTER, self, linkerServices, desc);
12955682Smarkm            }
13055682Smarkm            return getInvocation(EMPTY_ELEM_SETTER, self, linkerServices, desc);
13155682Smarkm        }
13255682Smarkm        default:
13355682Smarkm            break;
13455682Smarkm        }
13555682Smarkm        throw new AssertionError("unknown call type " + desc);
13655682Smarkm    }
13755682Smarkm
13855682Smarkm    @Override
13955682Smarkm    public GuardedInvocation convertToType(final Class<?> sourceType, final Class<?> targetType, final Supplier<MethodHandles.Lookup> lookupSupplier) throws Exception {
14055682Smarkm        final GuardedInvocation gi = convertToTypeNoCast(sourceType, targetType);
14155682Smarkm        return gi == null ? null : gi.asType(MH.type(targetType, sourceType));
14255682Smarkm    }
14355682Smarkm
14455682Smarkm    /**
14555682Smarkm     * Main part of the implementation of {@link GuardingTypeConverterFactory#convertToType(Class, Class)} that doesn't
14655682Smarkm     * care about adapting the method signature; that's done by the invoking method. Returns conversion from Object to String/number/boolean (JS primitive types).
14755682Smarkm     * @param sourceType the source type
14855682Smarkm     * @param targetType the target type
14955682Smarkm     * @return a guarded invocation that converts from the source type to the target type.
15055682Smarkm     * @throws Exception if something goes wrong
15157416Smarkm     */
15257416Smarkm    private static GuardedInvocation convertToTypeNoCast(final Class<?> sourceType, final Class<?> targetType) throws Exception {
15355682Smarkm        final MethodHandle mh = CONVERTERS.get(targetType);
15455682Smarkm        if (mh != null) {
15555682Smarkm            return new GuardedInvocation(mh);
15655682Smarkm        }
15755682Smarkm
15857416Smarkm        return null;
15955682Smarkm    }
16055682Smarkm
16155682Smarkm    private static GuardedInvocation getInvocation(final MethodHandle handle, final Object self, final LinkerServices linkerServices, final CallSiteDescriptor desc) {
16255682Smarkm        return Bootstrap.asTypeSafeReturn(new GuardedInvocation(handle, Guards.getClassGuard(self.getClass())), linkerServices, desc);
16355682Smarkm    }
16455682Smarkm
16555682Smarkm    // Used solely in an assertion to figure out if the object we get here is something we in fact expect. Objects
16655682Smarkm    // linked by NashornLinker should never reach here.
16755682Smarkm    private static boolean isExpectedObject(final Object obj) {
16855682Smarkm        return !(NashornLinker.canLinkTypeStatic(obj.getClass()));
16955682Smarkm    }
17055682Smarkm
17155682Smarkm    private static GuardedInvocation linkNull(final LinkRequest linkRequest) {
17255682Smarkm        final NashornCallSiteDescriptor desc = (NashornCallSiteDescriptor)linkRequest.getCallSiteDescriptor();
17355682Smarkm        final String operator = desc.getFirstOperator();
17455682Smarkm        switch (operator) {
17555682Smarkm        case "new":
17655682Smarkm        case "call":
17755682Smarkm            throw typeError("not.a.function", "null");
17855682Smarkm        case "callMethod":
17955682Smarkm        case "getMethod":
18055682Smarkm            throw typeError("no.such.function", getArgument(linkRequest), "null");
18155682Smarkm        case "getProp":
18257416Smarkm        case "getElem":
18355682Smarkm            throw typeError("cant.get.property", getArgument(linkRequest), "null");
18455682Smarkm        case "setProp":
18555682Smarkm        case "setElem":
18655682Smarkm            throw typeError("cant.set.property", getArgument(linkRequest), "null");
18755682Smarkm        default:
18855682Smarkm            break;
18955682Smarkm        }
19055682Smarkm        throw new AssertionError("unknown call type " + desc);
19155682Smarkm    }
19255682Smarkm
19355682Smarkm    private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>();
19455682Smarkm    static {
19555682Smarkm        CONVERTERS.put(boolean.class, JSType.TO_BOOLEAN.methodHandle());
19655682Smarkm        CONVERTERS.put(double.class, JSType.TO_NUMBER.methodHandle());
197        CONVERTERS.put(int.class, JSType.TO_INTEGER.methodHandle());
198        CONVERTERS.put(long.class, JSType.TO_LONG.methodHandle());
199        CONVERTERS.put(String.class, JSType.TO_STRING.methodHandle());
200    }
201
202    private static String getArgument(final LinkRequest linkRequest) {
203        final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor();
204        if (desc.getNameTokenCount() > 2) {
205            return desc.getNameToken(2);
206        }
207        return ScriptRuntime.safeToString(linkRequest.getArguments()[1]);
208    }
209}
210