WithObject.java revision 1416:a750a66640e0
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;
27
28import static jdk.nashorn.internal.lookup.Lookup.MH;
29
30import java.lang.invoke.MethodHandle;
31import java.lang.invoke.MethodHandles;
32import java.lang.invoke.MethodType;
33import java.lang.invoke.SwitchPoint;
34import jdk.internal.dynalink.CallSiteDescriptor;
35import jdk.internal.dynalink.linker.GuardedInvocation;
36import jdk.internal.dynalink.linker.LinkRequest;
37import jdk.internal.dynalink.support.CallSiteDescriptorFactory;
38import jdk.nashorn.api.scripting.AbstractJSObject;
39import jdk.nashorn.api.scripting.ScriptObjectMirror;
40import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
41import jdk.nashorn.internal.runtime.linker.NashornGuards;
42
43/**
44 * This class supports the handling of scope in a with body.
45 *
46 */
47public final class WithObject extends Scope {
48    private static final MethodHandle WITHEXPRESSIONGUARD    = findOwnMH("withExpressionGuard",  boolean.class, Object.class, PropertyMap.class, SwitchPoint.class);
49    private static final MethodHandle WITHEXPRESSIONFILTER   = findOwnMH("withFilterExpression", Object.class, Object.class);
50    private static final MethodHandle WITHSCOPEFILTER        = findOwnMH("withFilterScope",      Object.class, Object.class);
51    private static final MethodHandle BIND_TO_EXPRESSION_OBJ = findOwnMH("bindToExpression",     Object.class, Object.class, Object.class);
52    private static final MethodHandle BIND_TO_EXPRESSION_FN  = findOwnMH("bindToExpression",     Object.class, ScriptFunction.class, Object.class);
53
54    /** With expression object. */
55    private final ScriptObject expression;
56
57    /**
58     * Constructor
59     *
60     * @param scope scope object
61     * @param expression with expression
62     */
63    WithObject(final ScriptObject scope, final ScriptObject expression) {
64        super(scope, null);
65        this.expression = expression;
66    }
67
68    /**
69     * Delete a property based on a key.
70     * @param key Any valid JavaScript value.
71     * @param strict strict mode execution.
72     * @return True if deleted.
73     */
74    @Override
75    public boolean delete(final Object key, final boolean strict) {
76        final ScriptObject self = expression;
77        final String propName = JSType.toString(key);
78
79        final FindProperty find = self.findProperty(propName, true);
80
81        if (find != null) {
82            return self.delete(propName, strict);
83        }
84
85        return false;
86    }
87
88
89    @Override
90    public GuardedInvocation lookup(final CallSiteDescriptor desc, final LinkRequest request) {
91        if (request.isCallSiteUnstable()) {
92            // Fall back to megamorphic invocation which performs a complete lookup each time without further relinking.
93            return super.lookup(desc, request);
94        }
95
96        // With scopes can never be observed outside of Nashorn code, so all call sites that can address it will of
97        // necessity have a Nashorn descriptor - it is safe to cast.
98        final NashornCallSiteDescriptor ndesc = (NashornCallSiteDescriptor)desc;
99        FindProperty find = null;
100        GuardedInvocation link = null;
101        ScriptObject self;
102
103        final boolean isNamedOperation;
104        final String name;
105        if (desc.getNameTokenCount() > 2) {
106            isNamedOperation = true;
107            name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
108        } else {
109            isNamedOperation = false;
110            name = null;
111        }
112
113        self = expression;
114        if (isNamedOperation) {
115             find = self.findProperty(name, true);
116        }
117
118        if (find != null) {
119            link = self.lookup(desc, request);
120            if (link != null) {
121                return fixExpressionCallSite(ndesc, link);
122            }
123        }
124
125        final ScriptObject scope = getProto();
126        if (isNamedOperation) {
127            find = scope.findProperty(name, true);
128        }
129
130        if (find != null) {
131            return fixScopeCallSite(scope.lookup(desc, request), name, find.getOwner());
132        }
133
134        // the property is not found - now check for
135        // __noSuchProperty__ and __noSuchMethod__ in expression
136        if (self != null) {
137            final String fallBack;
138
139            final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0);
140
141            switch (operator) {
142            case "callMethod":
143                throw new AssertionError(); // Nashorn never emits callMethod
144            case "getMethod":
145                fallBack = NO_SUCH_METHOD_NAME;
146                break;
147            case "getProp":
148            case "getElem":
149                fallBack = NO_SUCH_PROPERTY_NAME;
150                break;
151            default:
152                fallBack = null;
153                break;
154            }
155
156            if (fallBack != null) {
157                find = self.findProperty(fallBack, true);
158                if (find != null) {
159                    switch (operator) {
160                    case "getMethod":
161                        link = self.noSuchMethod(desc, request);
162                        break;
163                    case "getProp":
164                    case "getElem":
165                        link = self.noSuchProperty(desc, request);
166                        break;
167                    default:
168                        break;
169                    }
170                }
171            }
172
173            if (link != null) {
174                return fixExpressionCallSite(ndesc, link);
175            }
176        }
177
178        // still not found, may be scope can handle with it's own
179        // __noSuchProperty__, __noSuchMethod__ etc.
180        link = scope.lookup(desc, request);
181
182        if (link != null) {
183            return fixScopeCallSite(link, name, null);
184        }
185
186        return null;
187    }
188
189    /**
190     * Overridden to try to find the property first in the expression object (and its prototypes), and only then in this
191     * object (and its prototypes).
192     *
193     * @param key  Property key.
194     * @param deep Whether the search should look up proto chain.
195     * @param start the object on which the lookup was originally initiated
196     *
197     * @return FindPropertyData or null if not found.
198     */
199    @Override
200    protected FindProperty findProperty(final String key, final boolean deep, final ScriptObject start) {
201        // We call findProperty on 'expression' with 'expression' itself as start parameter.
202        // This way in ScriptObject.setObject we can tell the property is from a 'with' expression
203        // (as opposed from another non-scope object in the proto chain such as Object.prototype).
204        final FindProperty exprProperty = expression.findProperty(key, true, expression);
205        if (exprProperty != null) {
206             return exprProperty;
207        }
208        return super.findProperty(key, deep, start);
209    }
210
211    @Override
212    protected Object invokeNoSuchProperty(final String name, final int programPoint) {
213        FindProperty find = expression.findProperty(NO_SUCH_PROPERTY_NAME, true);
214        if (find != null) {
215            final Object func = find.getObjectValue();
216            if (func instanceof ScriptFunction) {
217                return ScriptRuntime.apply((ScriptFunction)func, expression, name);
218            }
219        }
220
221        return getProto().invokeNoSuchProperty(name, programPoint);
222    }
223
224    @Override
225    public void setSplitState(final int state) {
226        ((Scope) getNonWithParent()).setSplitState(state);
227    }
228
229    @Override
230    public int getSplitState() {
231        return ((Scope) getNonWithParent()).getSplitState();
232    }
233
234    @Override
235    public void addBoundProperties(final ScriptObject source, final Property[] properties) {
236        // Declared variables in nested eval go to first normal (non-with) parent scope.
237        getNonWithParent().addBoundProperties(source, properties);
238    }
239
240    /**
241     * Get first parent scope that is not an instance of WithObject.
242     */
243    private ScriptObject getNonWithParent() {
244        ScriptObject proto = getProto();
245
246        while (proto != null && proto instanceof WithObject) {
247            proto = proto.getProto();
248        }
249
250        return proto;
251    }
252
253    private static GuardedInvocation fixReceiverType(final GuardedInvocation link, final MethodHandle filter) {
254        // The receiver may be an Object or a ScriptObject.
255        final MethodType invType = link.getInvocation().type();
256        final MethodType newInvType = invType.changeParameterType(0, filter.type().returnType());
257        return link.asType(newInvType);
258    }
259
260    private static GuardedInvocation fixExpressionCallSite(final NashornCallSiteDescriptor desc, final GuardedInvocation link) {
261        // If it's not a getMethod, just add an expression filter that converts WithObject in "this" position to its
262        // expression.
263        if (!"getMethod".equals(desc.getFirstOperator())) {
264            return fixReceiverType(link, WITHEXPRESSIONFILTER).filterArguments(0, WITHEXPRESSIONFILTER);
265        }
266
267        final MethodHandle linkInvocation      = link.getInvocation();
268        final MethodType   linkType            = linkInvocation.type();
269        final boolean      linkReturnsFunction = ScriptFunction.class.isAssignableFrom(linkType.returnType());
270
271        return link.replaceMethods(
272                // Make sure getMethod will bind the script functions it receives to WithObject.expression
273                MH.foldArguments(
274                        linkReturnsFunction ?
275                                BIND_TO_EXPRESSION_FN :
276                                BIND_TO_EXPRESSION_OBJ,
277                        filterReceiver(
278                                linkInvocation.asType(
279                                        linkType.changeReturnType(
280                                                linkReturnsFunction ?
281                                                        ScriptFunction.class :
282                                                        Object.class).
283                                                            changeParameterType(
284                                                                    0,
285                                                                    Object.class)),
286                                        WITHEXPRESSIONFILTER)),
287                         filterGuardReceiver(link, WITHEXPRESSIONFILTER));
288     // No clever things for the guard -- it is still identically filtered.
289
290    }
291
292    private GuardedInvocation fixScopeCallSite(final GuardedInvocation link, final String name, final ScriptObject owner) {
293        final GuardedInvocation newLink             = fixReceiverType(link, WITHSCOPEFILTER);
294        final MethodHandle      expressionGuard     = expressionGuard(name, owner);
295        final MethodHandle      filterGuardReceiver = filterGuardReceiver(newLink, WITHSCOPEFILTER);
296        return link.replaceMethods(
297                filterReceiver(
298                        newLink.getInvocation(),
299                        WITHSCOPEFILTER),
300                NashornGuards.combineGuards(
301                        expressionGuard,
302                        filterGuardReceiver));
303    }
304
305    private static MethodHandle filterGuardReceiver(final GuardedInvocation link, final MethodHandle receiverFilter) {
306        final MethodHandle test = link.getGuard();
307        if (test == null) {
308            return null;
309        }
310
311        final Class<?> receiverType = test.type().parameterType(0);
312        final MethodHandle filter = MH.asType(receiverFilter,
313                receiverFilter.type().changeParameterType(0, receiverType).
314                changeReturnType(receiverType));
315
316        return filterReceiver(test, filter);
317    }
318
319    private static MethodHandle filterReceiver(final MethodHandle mh, final MethodHandle receiverFilter) {
320        //With expression filter == receiverFilter, i.e. receiver is cast to withobject and its expression returned
321        return MH.filterArguments(mh, 0, receiverFilter.asType(receiverFilter.type().changeReturnType(mh.type().parameterType(0))));
322    }
323
324    /**
325     * Drops the WithObject wrapper from the expression.
326     * @param receiver WithObject wrapper.
327     * @return The with expression.
328     */
329    public static Object withFilterExpression(final Object receiver) {
330        return ((WithObject)receiver).expression;
331    }
332
333    @SuppressWarnings("unused")
334    private static Object bindToExpression(final Object fn, final Object receiver) {
335        if (fn instanceof ScriptFunction) {
336            return bindToExpression((ScriptFunction) fn, receiver);
337        } else if (fn instanceof ScriptObjectMirror) {
338            final ScriptObjectMirror mirror = (ScriptObjectMirror)fn;
339            if (mirror.isFunction()) {
340                // We need to make sure correct 'this' is used for calls with Ident call
341                // expressions. We do so here using an AbstractJSObject instance.
342                return new AbstractJSObject() {
343                    @Override
344                    public Object call(final Object thiz, final Object... args) {
345                        return mirror.call(withFilterExpression(receiver), args);
346                    }
347                };
348            }
349        }
350
351        return fn;
352    }
353
354    private static Object bindToExpression(final ScriptFunction fn, final Object receiver) {
355        return fn.createBound(withFilterExpression(receiver), ScriptRuntime.EMPTY_ARRAY);
356    }
357
358    private MethodHandle expressionGuard(final String name, final ScriptObject owner) {
359        final PropertyMap map = expression.getMap();
360        final SwitchPoint sp = expression.getProtoSwitchPoint(name, owner);
361        return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp);
362    }
363
364    @SuppressWarnings("unused")
365    private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint sp) {
366        return ((WithObject)receiver).expression.getMap() == map && (sp == null || !sp.hasBeenInvalidated());
367    }
368
369    /**
370     * Drops the WithObject wrapper from the scope.
371     * @param receiver WithObject wrapper.
372     * @return The with scope.
373     */
374    public static Object withFilterScope(final Object receiver) {
375        return ((WithObject)receiver).getProto();
376    }
377
378    /**
379     * Get the with expression for this {@code WithObject}
380     * @return the with expression
381     */
382    public ScriptObject getExpression() {
383        return expression;
384    }
385
386    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
387        return MH.findStatic(MethodHandles.lookup(), WithObject.class, name, MH.type(rtype, types));
388    }
389}
390