NashornBottomLinker.java revision 1369:a3c6abd88eb4
1336809Sdim/*
2336809Sdim * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3353358Sdim * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4353358Sdim *
5353358Sdim * This code is free software; you can redistribute it and/or modify it
6336809Sdim * under the terms of the GNU General Public License version 2 only, as
7336809Sdim * published by the Free Software Foundation.  Oracle designates this
8336809Sdim * particular file as subject to the "Classpath" exception as provided
9336809Sdim * by Oracle in the LICENSE file that accompanied this code.
10336809Sdim *
11336809Sdim * This code is distributed in the hope that it will be useful, but WITHOUT
12336809Sdim * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13336809Sdim * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14336809Sdim * version 2 for more details (a copy is included in the LICENSE file that
15336809Sdim * accompanied this code).
16336809Sdim *
17336809Sdim * You should have received a copy of the GNU General Public License version
18336809Sdim * 2 along with this work; if not, write to the Free Software Foundation,
19336809Sdim * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20336809Sdim *
21336809Sdim * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22336809Sdim * or visit www.oracle.com if you need additional information or have any
23336809Sdim * questions.
24336809Sdim */
25336809Sdim
26336809Sdimpackage jdk.nashorn.internal.runtime.linker;
27336809Sdim
28336809Sdimimport static jdk.nashorn.internal.lookup.Lookup.MH;
29336809Sdimimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
30336809Sdimimport static jdk.nashorn.internal.runtime.JSType.GET_UNDEFINED;
31336809Sdimimport static jdk.nashorn.internal.runtime.JSType.TYPE_OBJECT_INDEX;
32336809Sdimimport static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
33336809Sdim
34336809Sdimimport java.lang.invoke.MethodHandle;
35336809Sdimimport java.util.HashMap;
36336809Sdimimport java.util.Map;
37336809Sdimimport jdk.internal.dynalink.CallSiteDescriptor;
38360784Sdimimport jdk.internal.dynalink.beans.BeansLinker;
39336809Sdimimport jdk.internal.dynalink.linker.GuardedInvocation;
40336809Sdimimport jdk.internal.dynalink.linker.GuardedTypeConversion;
41336809Sdimimport jdk.internal.dynalink.linker.GuardingDynamicLinker;
42336809Sdimimport jdk.internal.dynalink.linker.GuardingTypeConverterFactory;
43336809Sdimimport jdk.internal.dynalink.linker.LinkRequest;
44336809Sdimimport jdk.internal.dynalink.linker.LinkerServices;
45336809Sdimimport jdk.internal.dynalink.support.Guards;
46336809Sdimimport jdk.nashorn.internal.codegen.types.Type;
47336809Sdimimport jdk.nashorn.internal.runtime.JSType;
48336809Sdimimport jdk.nashorn.internal.runtime.ScriptRuntime;
49360784Sdimimport jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
50336809Sdim
51336809Sdim/**
52336809Sdim * Nashorn bottom linker; used as a last-resort catch-all linker for all linking requests that fall through all other
53336809Sdim * linkers (see how {@link Bootstrap} class configures the dynamic linker in its static initializer). It will throw
54336809Sdim * appropriate ECMAScript errors for attempts to invoke operations on {@code null}, link no-op property getters and
55336809Sdim * setters for Java objects that couldn't be linked by any other linker, and throw appropriate ECMAScript errors for
56336809Sdim * attempts to invoke arbitrary Java objects as functions or constructors.
57336809Sdim */
58336809Sdimfinal class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeConverterFactory {
59336809Sdim
60336809Sdim    @Override
61336809Sdim    public GuardedInvocation getGuardedInvocation(final LinkRequest linkRequest, final LinkerServices linkerServices)
62336809Sdim            throws Exception {
63336809Sdim        final Object self = linkRequest.getReceiver();
64336809Sdim
65336809Sdim        if (self == null) {
66336809Sdim            return linkNull(linkRequest);
67336809Sdim        }
68336809Sdim
69336809Sdim        // None of the objects that can be linked by NashornLinker should ever reach here. Basically, anything below
70336809Sdim        // this point is a generic Java bean. Therefore, reaching here with a ScriptObject is a Nashorn bug.
71336809Sdim        assert isExpectedObject(self) : "Couldn't link " + linkRequest.getCallSiteDescriptor() + " for " + self.getClass().getName();
72336809Sdim
73353358Sdim        return linkBean(linkRequest, linkerServices);
74336809Sdim    }
75336809Sdim
76336809Sdim    private static final MethodHandle EMPTY_PROP_GETTER =
77336809Sdim            MH.dropArguments(MH.constant(Object.class, UNDEFINED), 0, Object.class);
78336809Sdim    private static final MethodHandle EMPTY_ELEM_GETTER =
79336809Sdim            MH.dropArguments(EMPTY_PROP_GETTER, 0, Object.class);
80336809Sdim    private static final MethodHandle EMPTY_PROP_SETTER =
81336809Sdim            MH.asType(EMPTY_ELEM_GETTER, EMPTY_ELEM_GETTER.type().changeReturnType(void.class));
82336809Sdim    private static final MethodHandle EMPTY_ELEM_SETTER =
83336809Sdim            MH.dropArguments(EMPTY_PROP_SETTER, 0, Object.class);
84336809Sdim
85336809Sdim    private static GuardedInvocation linkBean(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
86336809Sdim        final NashornCallSiteDescriptor desc = (NashornCallSiteDescriptor)linkRequest.getCallSiteDescriptor();
87360784Sdim        final Object self = linkRequest.getReceiver();
88360784Sdim        final String operator = desc.getFirstOperator();
89360784Sdim        switch (operator) {
90336809Sdim        case "new":
91336809Sdim            if(BeansLinker.isDynamicConstructor(self)) {
92336809Sdim                throw typeError("no.constructor.matches.args", ScriptRuntime.safeToString(self));
93360784Sdim            }
94336809Sdim            if(BeansLinker.isDynamicMethod(self)) {
95336809Sdim                throw typeError("method.not.constructor", ScriptRuntime.safeToString(self));
96336809Sdim            }
97336809Sdim            throw typeError("not.a.function", desc.getFunctionErrorMessage(self));
98336809Sdim        case "call":
99336809Sdim            if(BeansLinker.isDynamicConstructor(self)) {
100336809Sdim                throw typeError("constructor.requires.new", ScriptRuntime.safeToString(self));
101336809Sdim            }
102336809Sdim            if(BeansLinker.isDynamicMethod(self)) {
103336809Sdim                throw typeError("no.method.matches.args", ScriptRuntime.safeToString(self));
104336809Sdim            }
105336809Sdim            throw typeError("not.a.function", desc.getFunctionErrorMessage(self));
106336809Sdim        case "callMethod":
107336809Sdim            throw typeError("no.such.function", getArgument(linkRequest), ScriptRuntime.safeToString(self));
108336809Sdim        case "getMethod":
109336809Sdim            // evaluate to undefined, later on Undefined will take care of throwing TypeError
110336809Sdim            return getInvocation(MH.dropArguments(GET_UNDEFINED.get(TYPE_OBJECT_INDEX), 0, Object.class), self, linkerServices, desc);
111336809Sdim        case "getProp":
112336809Sdim        case "getElem":
113336809Sdim            if(NashornCallSiteDescriptor.isOptimistic(desc)) {
114336809Sdim                throw new UnwarrantedOptimismException(UNDEFINED, NashornCallSiteDescriptor.getProgramPoint(desc), Type.OBJECT);
115336809Sdim            }
116336809Sdim            if (desc.getOperand() != null) {
117336809Sdim                return getInvocation(EMPTY_PROP_GETTER, self, linkerServices, desc);
118336809Sdim            }
119336809Sdim            return getInvocation(EMPTY_ELEM_GETTER, self, linkerServices, desc);
120336809Sdim        case "setProp":
121336809Sdim        case "setElem": {
122336809Sdim            final boolean strict = NashornCallSiteDescriptor.isStrict(desc);
123336809Sdim            if (strict) {
124336809Sdim                throw typeError("cant.set.property", getArgument(linkRequest), ScriptRuntime.safeToString(self));
125336809Sdim            }
126336809Sdim            if (desc.getOperand() != null) {
127336809Sdim                return getInvocation(EMPTY_PROP_SETTER, self, linkerServices, desc);
128336809Sdim            }
129336809Sdim            return getInvocation(EMPTY_ELEM_SETTER, self, linkerServices, desc);
130336809Sdim        }
131336809Sdim        default:
132336809Sdim            break;
133336809Sdim        }
134336809Sdim        throw new AssertionError("unknown call type " + desc);
135336809Sdim    }
136336809Sdim
137336809Sdim    @Override
138336809Sdim    public GuardedTypeConversion convertToType(final Class<?> sourceType, final Class<?> targetType) throws Exception {
139336809Sdim        final GuardedInvocation gi = convertToTypeNoCast(sourceType, targetType);
140336809Sdim        return gi == null ? null : new GuardedTypeConversion(gi.asType(MH.type(targetType, sourceType)), true);
141336809Sdim    }
142336809Sdim
143336809Sdim    /**
144336809Sdim     * Main part of the implementation of {@link GuardingTypeConverterFactory#convertToType(Class, Class)} that doesn't
145336809Sdim     * care about adapting the method signature; that's done by the invoking method. Returns conversion from Object to String/number/boolean (JS primitive types).
146336809Sdim     * @param sourceType the source type
147336809Sdim     * @param targetType the target type
148336809Sdim     * @return a guarded invocation that converts from the source type to the target type.
149336809Sdim     * @throws Exception if something goes wrong
150336809Sdim     */
151336809Sdim    private static GuardedInvocation convertToTypeNoCast(final Class<?> sourceType, final Class<?> targetType) throws Exception {
152336809Sdim        final MethodHandle mh = CONVERTERS.get(targetType);
153336809Sdim        if (mh != null) {
154336809Sdim            return new GuardedInvocation(mh);
155336809Sdim        }
156336809Sdim
157336809Sdim        return null;
158336809Sdim    }
159336809Sdim
160336809Sdim    private static GuardedInvocation getInvocation(final MethodHandle handle, final Object self, final LinkerServices linkerServices, final CallSiteDescriptor desc) {
161336809Sdim        return Bootstrap.asTypeSafeReturn(new GuardedInvocation(handle, Guards.getClassGuard(self.getClass())), linkerServices, desc);
162336809Sdim    }
163336809Sdim
164336809Sdim    // Used solely in an assertion to figure out if the object we get here is something we in fact expect. Objects
165336809Sdim    // linked by NashornLinker should never reach here.
166336809Sdim    private static boolean isExpectedObject(final Object obj) {
167336809Sdim        return !(NashornLinker.canLinkTypeStatic(obj.getClass()));
168336809Sdim    }
169336809Sdim
170336809Sdim    private static GuardedInvocation linkNull(final LinkRequest linkRequest) {
171336809Sdim        final NashornCallSiteDescriptor desc = (NashornCallSiteDescriptor)linkRequest.getCallSiteDescriptor();
172336809Sdim        final String operator = desc.getFirstOperator();
173336809Sdim        switch (operator) {
174336809Sdim        case "new":
175336809Sdim        case "call":
176336809Sdim            throw typeError("not.a.function", "null");
177336809Sdim        case "callMethod":
178336809Sdim        case "getMethod":
179336809Sdim            throw typeError("no.such.function", getArgument(linkRequest), "null");
180336809Sdim        case "getProp":
181336809Sdim        case "getElem":
182336809Sdim            throw typeError("cant.get.property", getArgument(linkRequest), "null");
183336809Sdim        case "setProp":
184336809Sdim        case "setElem":
185336809Sdim            throw typeError("cant.set.property", getArgument(linkRequest), "null");
186336809Sdim        default:
187336809Sdim            break;
188336809Sdim        }
189336809Sdim        throw new AssertionError("unknown call type " + desc);
190336809Sdim    }
191336809Sdim
192336809Sdim    private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>();
193336809Sdim    static {
194336809Sdim        CONVERTERS.put(boolean.class, JSType.TO_BOOLEAN.methodHandle());
195336809Sdim        CONVERTERS.put(double.class, JSType.TO_NUMBER.methodHandle());
196336809Sdim        CONVERTERS.put(int.class, JSType.TO_INTEGER.methodHandle());
197336809Sdim        CONVERTERS.put(long.class, JSType.TO_LONG.methodHandle());
198336809Sdim        CONVERTERS.put(String.class, JSType.TO_STRING.methodHandle());
199336809Sdim    }
200336809Sdim
201336809Sdim    private static String getArgument(final LinkRequest linkRequest) {
202336809Sdim        final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor();
203336809Sdim        if (desc.getNameTokenCount() > 2) {
204336809Sdim            return desc.getNameToken(2);
205336809Sdim        }
206336809Sdim        return ScriptRuntime.safeToString(linkRequest.getArguments()[1]);
207336809Sdim    }
208336809Sdim}
209336809Sdim