PrimitiveLookup.java revision 1551:f3b883bec2d0
1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.runtime.linker;
27
28import static jdk.nashorn.internal.lookup.Lookup.MH;
29import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
30
31import java.lang.invoke.MethodHandle;
32import java.lang.invoke.MethodHandles;
33import java.lang.invoke.MethodType;
34import java.lang.invoke.SwitchPoint;
35import jdk.dynalink.linker.GuardedInvocation;
36import jdk.dynalink.linker.LinkRequest;
37import jdk.dynalink.linker.support.Guards;
38import jdk.nashorn.internal.runtime.Context;
39import jdk.nashorn.internal.runtime.FindProperty;
40import jdk.nashorn.internal.runtime.GlobalConstants;
41import jdk.nashorn.internal.runtime.JSType;
42import jdk.nashorn.internal.runtime.ScriptObject;
43import jdk.nashorn.internal.runtime.ScriptRuntime;
44import jdk.nashorn.internal.runtime.UserAccessorProperty;
45
46/**
47 * Implements lookup of methods to link for dynamic operations on JavaScript primitive values (booleans, strings, and
48 * numbers). This class is only public so it can be accessed by classes in the {@code jdk.nashorn.internal.objects}
49 * package.
50 */
51public final class PrimitiveLookup {
52
53    /** Method handle to link setters on primitive base. See ES5 8.7.2. */
54    private static final MethodHandle PRIMITIVE_SETTER = findOwnMH("primitiveSetter",
55            MH.type(void.class, ScriptObject.class, Object.class, Object.class, boolean.class, Object.class));
56
57
58    private PrimitiveLookup() {
59    }
60
61    /**
62     * Returns a guarded invocation representing the linkage for a dynamic operation on a primitive Java value.
63     * @param request the link request for the dynamic call site.
64     * @param receiverClass the class of the receiver value (e.g., {@link java.lang.Boolean}, {@link java.lang.String} etc.)
65     * @param wrappedReceiver a transient JavaScript native wrapper object created as the object proxy for the primitive
66     * value; see ECMAScript 5.1, section 8.7.1 for discussion of using {@code [[Get]]} on a property reference with a
67     * primitive base value. This instance will be used to delegate actual lookup.
68     * @param wrapFilter A method handle that takes a primitive value of type specified in the {@code receiverClass} and
69     * creates a transient native wrapper of the same type as {@code wrappedReceiver} for subsequent invocations of the
70     * method - it will be combined into the returned invocation as an argument filter on the receiver.
71     * @return a guarded invocation representing the operation at the call site when performed on a JavaScript primitive
72     * @param protoFilter A method handle that walks up the proto chain of this receiver object
73     * type {@code receiverClass}.
74     */
75    public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Class<?> receiverClass,
76                                                    final ScriptObject wrappedReceiver, final MethodHandle wrapFilter,
77                                                    final MethodHandle protoFilter) {
78        return lookupPrimitive(request, Guards.getInstanceOfGuard(receiverClass), wrappedReceiver, wrapFilter, protoFilter);
79    }
80
81    /**
82     * Returns a guarded invocation representing the linkage for a dynamic operation on a primitive Java value.
83     * @param request the link request for the dynamic call site.
84     * @param guard an explicit guard that will be used for the returned guarded invocation.
85     * @param wrappedReceiver a transient JavaScript native wrapper object created as the object proxy for the primitive
86     * value; see ECMAScript 5.1, section 8.7.1 for discussion of using {@code [[Get]]} on a property reference with a
87     * primitive base value. This instance will be used to delegate actual lookup.
88     * @param wrapFilter A method handle that takes a primitive value of type guarded by the {@code guard} and
89     * creates a transient native wrapper of the same type as {@code wrappedReceiver} for subsequent invocations of the
90     * method - it will be combined into the returned invocation as an argument filter on the receiver.
91     * @param protoFilter A method handle that walks up the proto chain of this receiver object
92     * @return a guarded invocation representing the operation at the call site when performed on a JavaScript primitive
93     * type (that is implied by both {@code guard} and {@code wrappedReceiver}).
94     */
95    public static GuardedInvocation lookupPrimitive(final LinkRequest request, final MethodHandle guard,
96                                                    final ScriptObject wrappedReceiver, final MethodHandle wrapFilter,
97                                                    final MethodHandle protoFilter) {
98        // lookupPrimitive is only ever invoked from NashornPrimitiveLinker,
99        // which is a linker private to Nashorn, therefore the call site
100        // descriptor class will always be NashornCallSiteDescriptor.
101        final NashornCallSiteDescriptor desc = (NashornCallSiteDescriptor)request.getCallSiteDescriptor();
102        final String name = desc.getOperand();
103        final FindProperty find = name != null ? wrappedReceiver.findProperty(name, true) : null;
104
105        switch (desc.getFirstOperation()) {
106        case GET_PROPERTY:
107        case GET_ELEMENT:
108        case GET_METHOD:
109            //checks whether the property name is hard-coded in the call-site (i.e. a getProp vs a getElem, or setProp vs setElem)
110            //if it is we can make assumptions on the property: that if it is not defined on primitive wrapper itself it never will be.
111            //so in that case we can skip creation of primitive wrapper and start our search with the prototype.
112            if (name != null) {
113                if (find == null) {
114                    // Give up early, give chance to BeanLinker and NashornBottomLinker to deal with it.
115                    return null;
116                }
117
118                final SwitchPoint sp = find.getProperty().getBuiltinSwitchPoint(); //can use this instead of proto filter
119                if (sp instanceof Context.BuiltinSwitchPoint && !sp.hasBeenInvalidated()) {
120                    return new GuardedInvocation(GlobalConstants.staticConstantGetter(find.getObjectValue()), guard, sp, null);
121                }
122
123                if (find.isInherited() && !(find.getProperty() instanceof UserAccessorProperty)) {
124                    // If property is found in the prototype object bind the method handle directly to
125                    // the proto filter instead of going through wrapper instantiation below.
126                    final ScriptObject proto = wrappedReceiver.getProto();
127                    final GuardedInvocation link = proto.lookup(desc, request);
128
129                    if (link != null) {
130                        final MethodHandle invocation = link.getInvocation(); //this contains the builtin switchpoint
131                        final MethodHandle adaptedInvocation = MH.asType(invocation, invocation.type().changeParameterType(0, Object.class));
132                        final MethodHandle method = MH.filterArguments(adaptedInvocation, 0, protoFilter);
133                        final MethodHandle protoGuard = MH.filterArguments(link.getGuard(), 0, protoFilter);
134                        return new GuardedInvocation(method, NashornGuards.combineGuards(guard, protoGuard));
135                    }
136                }
137            }
138            break;
139        case SET_PROPERTY:
140        case SET_ELEMENT:
141            return getPrimitiveSetter(name, guard, wrapFilter, NashornCallSiteDescriptor.isStrict(desc));
142        default:
143            break;
144        }
145
146        final GuardedInvocation link = wrappedReceiver.lookup(desc, request);
147        if (link != null) {
148            MethodHandle method = link.getInvocation();
149            final Class<?> receiverType = method.type().parameterType(0);
150            if (receiverType != Object.class) {
151                final MethodType wrapType = wrapFilter.type();
152                assert receiverType.isAssignableFrom(wrapType.returnType());
153                method = MH.filterArguments(method, 0, MH.asType(wrapFilter, wrapType.changeReturnType(receiverType)));
154            }
155
156            return new GuardedInvocation(method, guard, link.getSwitchPoints(), null);
157        }
158
159        return null;
160    }
161
162    private static GuardedInvocation getPrimitiveSetter(final String name, final MethodHandle guard,
163                                                        final MethodHandle wrapFilter, final boolean isStrict) {
164        MethodHandle filter = MH.asType(wrapFilter, wrapFilter.type().changeReturnType(ScriptObject.class));
165        final MethodHandle target;
166
167        if (name == null) {
168            filter = MH.dropArguments(filter, 1, Object.class, Object.class);
169            target = MH.insertArguments(PRIMITIVE_SETTER, 3, isStrict);
170        } else {
171            filter = MH.dropArguments(filter, 1, Object.class);
172            target = MH.insertArguments(PRIMITIVE_SETTER, 2, name, isStrict);
173        }
174
175        return new GuardedInvocation(MH.foldArguments(target, filter), guard);
176    }
177
178
179    @SuppressWarnings("unused")
180    private static void primitiveSetter(final ScriptObject wrappedSelf, final Object self, final Object key,
181                                        final boolean strict, final Object value) {
182        // See ES5.1 8.7.2 PutValue (V, W)
183        final String name = JSType.toString(key);
184        final FindProperty find = wrappedSelf.findProperty(name, true);
185        if (find == null || !(find.getProperty() instanceof UserAccessorProperty) || !find.getProperty().isWritable()) {
186            if (strict) {
187                throw typeError("property.not.writable", name, ScriptRuntime.safeToString(self));
188            }
189            return;
190        }
191        // property found and is a UserAccessorProperty
192        find.setValue(value, strict);
193    }
194
195    private static MethodHandle findOwnMH(final String name, final MethodType type) {
196        return MH.findStatic(MethodHandles.lookup(), PrimitiveLookup.class, name, type);
197    }
198}
199