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