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 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.makeBoundFunction(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 }
--- EOF ---