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 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 }