PrimitiveLookup.java revision 1805:7caf1f762f1d
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.CallSiteDescriptor;
36import jdk.dynalink.linker.GuardedInvocation;
37import jdk.dynalink.linker.LinkRequest;
38import jdk.dynalink.linker.support.Guards;
39import jdk.nashorn.internal.runtime.Context;
40import jdk.nashorn.internal.runtime.FindProperty;
41import jdk.nashorn.internal.runtime.GlobalConstants;
42import jdk.nashorn.internal.runtime.JSType;
43import jdk.nashorn.internal.runtime.ScriptObject;
44import jdk.nashorn.internal.runtime.ScriptRuntime;
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        final CallSiteDescriptor desc = request.getCallSiteDescriptor();
99        final String name = NashornCallSiteDescriptor.getOperand(desc);
100        final FindProperty find = name != null ? wrappedReceiver.findProperty(name, true) : null;
101
102        switch (NashornCallSiteDescriptor.getStandardOperation(desc)) {
103        case GET:
104            //checks whether the property name is hard-coded in the call-site (i.e. a getProp vs a getElem, or setProp vs setElem)
105            //if it is we can make assumptions on the property: that if it is not defined on primitive wrapper itself it never will be.
106            //so in that case we can skip creation of primitive wrapper and start our search with the prototype.
107            if (name != null) {
108                if (find == null) {
109                    // Give up early, give chance to BeanLinker and NashornBottomLinker to deal with it.
110                    return null;
111                }
112
113                final SwitchPoint sp = find.getProperty().getBuiltinSwitchPoint(); //can use this instead of proto filter
114                if (sp instanceof Context.BuiltinSwitchPoint && !sp.hasBeenInvalidated()) {
115                    return new GuardedInvocation(GlobalConstants.staticConstantGetter(find.getObjectValue()), guard, sp, null);
116                }
117
118                if (find.isInheritedOrdinaryProperty()) {
119                    // If property is found in the prototype object bind the method handle directly to
120                    // the proto filter instead of going through wrapper instantiation below.
121                    final ScriptObject proto = wrappedReceiver.getProto();
122                    final GuardedInvocation link = proto.lookup(desc, request);
123
124                    if (link != null) {
125                        final MethodHandle invocation = link.getInvocation(); //this contains the builtin switchpoint
126                        final MethodHandle adaptedInvocation = MH.asType(invocation, invocation.type().changeParameterType(0, Object.class));
127                        final MethodHandle method = MH.filterArguments(adaptedInvocation, 0, protoFilter);
128                        final MethodHandle protoGuard = MH.filterArguments(link.getGuard(), 0, protoFilter);
129                        return new GuardedInvocation(method, NashornGuards.combineGuards(guard, protoGuard));
130                    }
131                }
132            }
133            break;
134        case SET:
135            return getPrimitiveSetter(name, guard, wrapFilter, NashornCallSiteDescriptor.isStrict(desc));
136        default:
137            break;
138        }
139
140        final GuardedInvocation link = wrappedReceiver.lookup(desc, request);
141        if (link != null) {
142            MethodHandle method = link.getInvocation();
143            final Class<?> receiverType = method.type().parameterType(0);
144            if (receiverType != Object.class) {
145                final MethodType wrapType = wrapFilter.type();
146                assert receiverType.isAssignableFrom(wrapType.returnType());
147                method = MH.filterArguments(method, 0, MH.asType(wrapFilter, wrapType.changeReturnType(receiverType)));
148            }
149
150            return new GuardedInvocation(method, guard, link.getSwitchPoints(), null);
151        }
152
153        return null;
154    }
155
156    private static GuardedInvocation getPrimitiveSetter(final String name, final MethodHandle guard,
157                                                        final MethodHandle wrapFilter, final boolean isStrict) {
158        MethodHandle filter = MH.asType(wrapFilter, wrapFilter.type().changeReturnType(ScriptObject.class));
159        final MethodHandle target;
160
161        if (name == null) {
162            filter = MH.dropArguments(filter, 1, Object.class, Object.class);
163            target = MH.insertArguments(PRIMITIVE_SETTER, 3, isStrict);
164        } else {
165            filter = MH.dropArguments(filter, 1, Object.class);
166            target = MH.insertArguments(PRIMITIVE_SETTER, 2, name, isStrict);
167        }
168
169        return new GuardedInvocation(MH.foldArguments(target, filter), guard);
170    }
171
172
173    @SuppressWarnings("unused")
174    private static void primitiveSetter(final ScriptObject wrappedSelf, final Object self, final Object key,
175                                        final boolean strict, final Object value) {
176        // See ES5.1 8.7.2 PutValue (V, W)
177        final String name = JSType.toString(key);
178        final FindProperty find = wrappedSelf.findProperty(name, true);
179        if (find == null || !find.getProperty().isAccessorProperty() || !find.getProperty().hasNativeSetter()) {
180            if (strict) {
181                if (find == null || !find.getProperty().isAccessorProperty()) {
182                    throw typeError("property.not.writable", name, ScriptRuntime.safeToString(self));
183                } else {
184                    throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self));
185                }
186            }
187            return;
188        }
189        // property found and is a UserAccessorProperty
190        find.setValue(value, strict);
191    }
192
193    private static MethodHandle findOwnMH(final String name, final MethodType type) {
194        return MH.findStatic(MethodHandles.lookup(), PrimitiveLookup.class, name, type);
195    }
196}
197