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.internal.runtime.linker.NashornCallSiteDescriptor;
39 import jdk.nashorn.internal.runtime.linker.NashornGuards;
40
41 /**
42 * This class supports the handling of scope in a with body.
43 *
44 */
45 public final class WithObject extends ScriptObject implements Scope {
46 private static final MethodHandle WITHEXPRESSIONGUARD = findOwnMH("withExpressionGuard", boolean.class, Object.class, PropertyMap.class, SwitchPoint.class);
47 private static final MethodHandle WITHEXPRESSIONFILTER = findOwnMH("withFilterExpression", Object.class, Object.class);
48 private static final MethodHandle WITHSCOPEFILTER = findOwnMH("withFilterScope", Object.class, Object.class);
49 private static final MethodHandle BIND_TO_EXPRESSION_OBJ = findOwnMH("bindToExpression", Object.class, Object.class, Object.class);
50 private static final MethodHandle BIND_TO_EXPRESSION_FN = findOwnMH("bindToExpression", Object.class, ScriptFunction.class, Object.class);
51
52 /** With expression object. */
53 private final ScriptObject expression;
54
55 /**
56 * Constructor
57 *
58 * @param scope scope object
59 * @param expression with expression
60 */
61 WithObject(final ScriptObject scope, final ScriptObject expression) {
62 super(scope, null);
63 setIsScope();
64 this.expression = expression;
65 }
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 // With scopes can never be observed outside of Nashorn code, so all call sites that can address it will of
92 // necessity have a Nashorn descriptor - it is safe to cast.
93 final NashornCallSiteDescriptor ndesc = (NashornCallSiteDescriptor)desc;
94 FindProperty find = null;
95 GuardedInvocation link = null;
96 ScriptObject self = null;
97
98 final boolean isNamedOperation;
99 final String name;
100 if(desc.getNameTokenCount() > 2) {
101 isNamedOperation = true;
102 name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
103 } else {
104 isNamedOperation = false;
105 name = null;
106 }
107
108 self = expression;
109 if (isNamedOperation) {
110 find = self.findProperty(name, true);
111 }
112
113 if (find != null) {
114 link = self.lookup(desc, request);
115
116 if (link != null) {
117 return fixExpressionCallSite(ndesc, link);
118 }
119 }
120
121 final ScriptObject scope = getProto();
122 if (isNamedOperation) {
123 find = scope.findProperty(name, true);
124 }
125
126 if (find != null) {
127 return fixScopeCallSite(scope.lookup(desc, request), name, find.getOwner());
128 }
129
130 // the property is not found - now check for
131 // __noSuchProperty__ and __noSuchMethod__ in expression
132 if (self != null) {
133 final String fallBack;
134
135 final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0);
136
137 switch (operator) {
138 case "callMethod":
139 throw new AssertionError(); // Nashorn never emits callMethod
140 case "getMethod":
141 fallBack = NO_SUCH_METHOD_NAME;
142 break;
143 case "getProp":
144 case "getElem":
145 fallBack = NO_SUCH_PROPERTY_NAME;
146 break;
147 default:
148 fallBack = null;
149 break;
150 }
151
152 if (fallBack != null) {
153 find = self.findProperty(fallBack, true);
154 if (find != null) {
155 switch (operator) {
156 case "getMethod":
157 link = self.noSuchMethod(desc, request);
158 break;
159 case "getProp":
160 case "getElem":
161 link = self.noSuchProperty(desc, request);
162 break;
163 default:
164 break;
165 }
166 }
167 }
168
169 if (link != null) {
170 return fixExpressionCallSite(ndesc, link);
171 }
172 }
173
174 // still not found, may be scope can handle with it's own
175 // __noSuchProperty__, __noSuchMethod__ etc.
176 link = scope.lookup(desc, request);
177
178 if (link != null) {
179 return fixScopeCallSite(link, name, null);
180 }
181
182 return null;
183 }
184
185 /**
186 * Overridden to try to find the property first in the expression object (and its prototypes), and only then in this
187 * object (and its prototypes).
188 *
189 * @param key Property key.
190 * @param deep Whether the search should look up proto chain.
191 * @param stopOnNonScope should a deep search stop on the first non-scope object?
192 * @param start the object on which the lookup was originally initiated
193 *
194 * @return FindPropertyData or null if not found.
195 */
196 @Override
197 FindProperty findProperty(final String key, final boolean deep, final boolean stopOnNonScope, final ScriptObject start) {
198 final FindProperty exprProperty = expression.findProperty(key, deep, stopOnNonScope, start);
199 if (exprProperty != null) {
200 return exprProperty;
201 }
202 return super.findProperty(key, deep, stopOnNonScope, start);
203 }
204
205 @Override
206 public void setSplitState(final int state) {
207 getNonWithParent().setSplitState(state);
208 }
209
210 @Override
211 public int getSplitState() {
212 return getNonWithParent().getSplitState();
213 }
214
215 /**
216 * Get first parent scope that is not an instance of WithObject.
217 */
218 private Scope getNonWithParent() {
219 ScriptObject proto = getParentScope();
220
221 while (proto != null && proto instanceof WithObject) {
222 proto = ((WithObject)proto).getParentScope();
223 }
224
225 assert proto instanceof Scope : "with scope without parent scope";
226 return (Scope) proto;
227 }
228
229
230 private static GuardedInvocation fixReceiverType(final GuardedInvocation link, final MethodHandle filter) {
231 // The receiver may be an Object or a ScriptObject.
232 final MethodType invType = link.getInvocation().type();
233 final MethodType newInvType = invType.changeParameterType(0, filter.type().returnType());
234 return link.asType(newInvType);
235 }
236
237 private static GuardedInvocation fixExpressionCallSite(final NashornCallSiteDescriptor desc, final GuardedInvocation link) {
238 // If it's not a getMethod, just add an expression filter that converts WithObject in "this" position to its
239 // expression.
240 if(!"getMethod".equals(desc.getFirstOperator())) {
241 return fixReceiverType(link, WITHEXPRESSIONFILTER).filterArguments(0, WITHEXPRESSIONFILTER);
242 }
243
244 final MethodHandle linkInvocation = link.getInvocation();
245 final MethodType linkType = linkInvocation.type();
246 final boolean linkReturnsFunction = ScriptFunction.class.isAssignableFrom(linkType.returnType());
247 return link.replaceMethods(
248 // Make sure getMethod will bind the script functions it receives to WithObject.expression
249 MH.foldArguments(linkReturnsFunction ? BIND_TO_EXPRESSION_FN : BIND_TO_EXPRESSION_OBJ,
250 filter(linkInvocation.asType(linkType.changeReturnType(
251 linkReturnsFunction ? ScriptFunction.class : Object.class)), WITHEXPRESSIONFILTER)),
252 // No clever things for the guard -- it is still identically filtered.
253 filterGuard(link, WITHEXPRESSIONFILTER));
254 }
255
256 private GuardedInvocation fixScopeCallSite(final GuardedInvocation link, final String name, final ScriptObject owner) {
257 final GuardedInvocation newLink = fixReceiverType(link, WITHSCOPEFILTER);
258 return link.replaceMethods(filter(newLink.getInvocation(), WITHSCOPEFILTER),
259 NashornGuards.combineGuards(expressionGuard(name, owner), filterGuard(newLink, WITHSCOPEFILTER)));
260 }
261
262 private static MethodHandle filterGuard(final GuardedInvocation link, final MethodHandle filter) {
263 final MethodHandle test = link.getGuard();
264 return test == null ? null : filter(test, filter);
265 }
266
267 private static MethodHandle filter(final MethodHandle mh, final MethodHandle filter) {
268 return MH.filterArguments(mh, 0, filter);
269 }
270
271 /**
272 * Drops the WithObject wrapper from the expression.
273 * @param receiver WithObject wrapper.
274 * @return The with expression.
275 */
276 public static Object withFilterExpression(final Object receiver) {
277 return ((WithObject)receiver).expression;
278 }
279
280 @SuppressWarnings("unused")
281 private static Object bindToExpression(final Object fn, final Object receiver) {
282 return fn instanceof ScriptFunction ? bindToExpression((ScriptFunction) fn, receiver) : fn;
283 }
284
285 private static Object bindToExpression(final ScriptFunction fn, final Object receiver) {
286 return fn.makeBoundFunction(withFilterExpression(receiver), new Object[0]);
287 }
288
289 private MethodHandle expressionGuard(final String name, final ScriptObject owner) {
290 final PropertyMap map = expression.getMap();
291 final SwitchPoint sp = expression.getProtoSwitchPoint(name, owner);
292 return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp);
293 }
294
295 @SuppressWarnings("unused")
296 private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint sp) {
297 return ((WithObject)receiver).expression.getMap() == map && (sp == null || !sp.hasBeenInvalidated());
298 }
299
300 /**
301 * Drops the WithObject wrapper from the scope.
302 * @param receiver WithObject wrapper.
303 * @return The with scope.
304 */
305 public static Object withFilterScope(final Object receiver) {
306 return ((WithObject)receiver).getProto();
307 }
308
309 /**
310 * Get the with expression for this {@code WithObject}
311 * @return the with expression
312 */
313 public ScriptObject getExpression() {
314 return expression;
315 }
316
317 /**
318 * Get the parent scope for this {@code WithObject}
319 * @return the parent scope
320 */
321 public ScriptObject getParentScope() {
322 return getProto();
323 }
324
325 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
326 return MH.findStatic(MethodHandles.lookup(), WithObject.class, name, MH.type(rtype, types));
327 }
328 }
--- EOF ---