WithObject.java revision 1470:04ed602df062
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; 29import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 30 31import java.lang.invoke.MethodHandle; 32import java.lang.invoke.MethodHandles; 33import java.lang.invoke.MethodType; 34import java.lang.invoke.SwitchPoint; 35import jdk.internal.dynalink.CallSiteDescriptor; 36import jdk.internal.dynalink.linker.GuardedInvocation; 37import jdk.internal.dynalink.linker.LinkRequest; 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 = desc.tokenizeOperators().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 boolean isScope, final int programPoint) { 213 final 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 final ScriptFunction sfunc = (ScriptFunction)func; 218 final Object self = isScope && sfunc.isStrict()? UNDEFINED : expression; 219 return ScriptRuntime.apply(sfunc, self, name); 220 } 221 } 222 223 return getProto().invokeNoSuchProperty(name, isScope, programPoint); 224 } 225 226 @Override 227 public void setSplitState(final int state) { 228 ((Scope) getNonWithParent()).setSplitState(state); 229 } 230 231 @Override 232 public int getSplitState() { 233 return ((Scope) getNonWithParent()).getSplitState(); 234 } 235 236 @Override 237 public void addBoundProperties(final ScriptObject source, final Property[] properties) { 238 // Declared variables in nested eval go to first normal (non-with) parent scope. 239 getNonWithParent().addBoundProperties(source, properties); 240 } 241 242 /** 243 * Get first parent scope that is not an instance of WithObject. 244 */ 245 private ScriptObject getNonWithParent() { 246 ScriptObject proto = getProto(); 247 248 while (proto != null && proto instanceof WithObject) { 249 proto = proto.getProto(); 250 } 251 252 return proto; 253 } 254 255 private static GuardedInvocation fixReceiverType(final GuardedInvocation link, final MethodHandle filter) { 256 // The receiver may be an Object or a ScriptObject. 257 final MethodType invType = link.getInvocation().type(); 258 final MethodType newInvType = invType.changeParameterType(0, filter.type().returnType()); 259 return link.asType(newInvType); 260 } 261 262 private static GuardedInvocation fixExpressionCallSite(final NashornCallSiteDescriptor desc, final GuardedInvocation link) { 263 // If it's not a getMethod, just add an expression filter that converts WithObject in "this" position to its 264 // expression. 265 if (!"getMethod".equals(desc.getFirstOperator())) { 266 return fixReceiverType(link, WITHEXPRESSIONFILTER).filterArguments(0, WITHEXPRESSIONFILTER); 267 } 268 269 final MethodHandle linkInvocation = link.getInvocation(); 270 final MethodType linkType = linkInvocation.type(); 271 final boolean linkReturnsFunction = ScriptFunction.class.isAssignableFrom(linkType.returnType()); 272 273 return link.replaceMethods( 274 // Make sure getMethod will bind the script functions it receives to WithObject.expression 275 MH.foldArguments( 276 linkReturnsFunction ? 277 BIND_TO_EXPRESSION_FN : 278 BIND_TO_EXPRESSION_OBJ, 279 filterReceiver( 280 linkInvocation.asType( 281 linkType.changeReturnType( 282 linkReturnsFunction ? 283 ScriptFunction.class : 284 Object.class). 285 changeParameterType( 286 0, 287 Object.class)), 288 WITHEXPRESSIONFILTER)), 289 filterGuardReceiver(link, WITHEXPRESSIONFILTER)); 290 // No clever things for the guard -- it is still identically filtered. 291 292 } 293 294 private GuardedInvocation fixScopeCallSite(final GuardedInvocation link, final String name, final ScriptObject owner) { 295 final GuardedInvocation newLink = fixReceiverType(link, WITHSCOPEFILTER); 296 final MethodHandle expressionGuard = expressionGuard(name, owner); 297 final MethodHandle filterGuardReceiver = filterGuardReceiver(newLink, WITHSCOPEFILTER); 298 return link.replaceMethods( 299 filterReceiver( 300 newLink.getInvocation(), 301 WITHSCOPEFILTER), 302 NashornGuards.combineGuards( 303 expressionGuard, 304 filterGuardReceiver)); 305 } 306 307 private static MethodHandle filterGuardReceiver(final GuardedInvocation link, final MethodHandle receiverFilter) { 308 final MethodHandle test = link.getGuard(); 309 if (test == null) { 310 return null; 311 } 312 313 final Class<?> receiverType = test.type().parameterType(0); 314 final MethodHandle filter = MH.asType(receiverFilter, 315 receiverFilter.type().changeParameterType(0, receiverType). 316 changeReturnType(receiverType)); 317 318 return filterReceiver(test, filter); 319 } 320 321 private static MethodHandle filterReceiver(final MethodHandle mh, final MethodHandle receiverFilter) { 322 //With expression filter == receiverFilter, i.e. receiver is cast to withobject and its expression returned 323 return MH.filterArguments(mh, 0, receiverFilter.asType(receiverFilter.type().changeReturnType(mh.type().parameterType(0)))); 324 } 325 326 /** 327 * Drops the WithObject wrapper from the expression. 328 * @param receiver WithObject wrapper. 329 * @return The with expression. 330 */ 331 public static Object withFilterExpression(final Object receiver) { 332 return ((WithObject)receiver).expression; 333 } 334 335 @SuppressWarnings("unused") 336 private static Object bindToExpression(final Object fn, final Object receiver) { 337 if (fn instanceof ScriptFunction) { 338 return bindToExpression((ScriptFunction) fn, receiver); 339 } else if (fn instanceof ScriptObjectMirror) { 340 final ScriptObjectMirror mirror = (ScriptObjectMirror)fn; 341 if (mirror.isFunction()) { 342 // We need to make sure correct 'this' is used for calls with Ident call 343 // expressions. We do so here using an AbstractJSObject instance. 344 return new AbstractJSObject() { 345 @Override 346 public Object call(final Object thiz, final Object... args) { 347 return mirror.call(withFilterExpression(receiver), args); 348 } 349 }; 350 } 351 } 352 353 return fn; 354 } 355 356 private static Object bindToExpression(final ScriptFunction fn, final Object receiver) { 357 return fn.createBound(withFilterExpression(receiver), ScriptRuntime.EMPTY_ARRAY); 358 } 359 360 private MethodHandle expressionGuard(final String name, final ScriptObject owner) { 361 final PropertyMap map = expression.getMap(); 362 final SwitchPoint[] sp = expression.getProtoSwitchPoints(name, owner); 363 return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp); 364 } 365 366 @SuppressWarnings("unused") 367 private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint[] sp) { 368 return ((WithObject)receiver).expression.getMap() == map && !hasBeenInvalidated(sp); 369 } 370 371 private static boolean hasBeenInvalidated(final SwitchPoint[] switchPoints) { 372 if (switchPoints != null) { 373 for (final SwitchPoint switchPoint : switchPoints) { 374 if (switchPoint.hasBeenInvalidated()) { 375 return true; 376 } 377 } 378 } 379 return false; 380 } 381 382 /** 383 * Drops the WithObject wrapper from the scope. 384 * @param receiver WithObject wrapper. 385 * @return The with scope. 386 */ 387 public static Object withFilterScope(final Object receiver) { 388 return ((WithObject)receiver).getProto(); 389 } 390 391 /** 392 * Get the with expression for this {@code WithObject} 393 * @return the with expression 394 */ 395 public ScriptObject getExpression() { 396 return expression; 397 } 398 399 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 400 return MH.findStatic(MethodHandles.lookup(), WithObject.class, name, MH.type(rtype, types)); 401 } 402} 403