WithObject.java revision 1728:2e53f4d1445d
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.dynalink.CallSiteDescriptor; 36import jdk.dynalink.NamedOperation; 37import jdk.dynalink.Operation; 38import jdk.dynalink.StandardOperation; 39import jdk.dynalink.linker.GuardedInvocation; 40import jdk.dynalink.linker.LinkRequest; 41import jdk.nashorn.api.scripting.AbstractJSObject; 42import jdk.nashorn.api.scripting.ScriptObjectMirror; 43import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 44import jdk.nashorn.internal.runtime.linker.NashornGuards; 45 46/** 47 * This class supports the handling of scope in a with body. 48 * 49 */ 50public final class WithObject extends Scope { 51 private static final MethodHandle WITHEXPRESSIONGUARD = findOwnMH("withExpressionGuard", boolean.class, Object.class, PropertyMap.class, SwitchPoint[].class); 52 private static final MethodHandle WITHEXPRESSIONFILTER = findOwnMH("withFilterExpression", Object.class, Object.class); 53 private static final MethodHandle WITHSCOPEFILTER = findOwnMH("withFilterScope", Object.class, Object.class); 54 private static final MethodHandle BIND_TO_EXPRESSION_OBJ = findOwnMH("bindToExpression", Object.class, Object.class, Object.class); 55 private static final MethodHandle BIND_TO_EXPRESSION_FN = findOwnMH("bindToExpression", Object.class, ScriptFunction.class, Object.class); 56 57 /** With expression object. */ 58 private final ScriptObject expression; 59 60 /** 61 * Constructor 62 * 63 * @param scope scope object 64 * @param expression with expression 65 */ 66 WithObject(final ScriptObject scope, final ScriptObject expression) { 67 super(scope, null); 68 this.expression = expression; 69 } 70 71 /** 72 * Delete a property based on a key. 73 * @param key Any valid JavaScript value. 74 * @param strict strict mode execution. 75 * @return True if deleted. 76 */ 77 @Override 78 public boolean delete(final Object key, final boolean strict) { 79 final ScriptObject self = expression; 80 final String propName = JSType.toString(key); 81 82 final FindProperty find = self.findProperty(propName, true); 83 84 if (find != null) { 85 return self.delete(propName, strict); 86 } 87 88 return false; 89 } 90 91 92 @Override 93 public GuardedInvocation lookup(final CallSiteDescriptor desc, final LinkRequest request) { 94 if (request.isCallSiteUnstable()) { 95 // Fall back to megamorphic invocation which performs a complete lookup each time without further relinking. 96 return super.lookup(desc, request); 97 } 98 99 // With scopes can never be observed outside of Nashorn code, so all call sites that can address it will of 100 // necessity have a Nashorn descriptor - it is safe to cast. 101 final NashornCallSiteDescriptor ndesc = (NashornCallSiteDescriptor)desc; 102 FindProperty find = null; 103 GuardedInvocation link = null; 104 ScriptObject self; 105 106 final boolean isNamedOperation; 107 final String name; 108 final Operation op = desc.getOperation(); 109 if (op instanceof NamedOperation) { 110 isNamedOperation = true; 111 name = ((NamedOperation)op).getName().toString(); 112 } else { 113 isNamedOperation = false; 114 name = null; 115 } 116 117 self = expression; 118 if (isNamedOperation) { 119 find = self.findProperty(name, true); 120 } 121 122 if (find != null) { 123 link = self.lookup(desc, request); 124 if (link != null) { 125 return fixExpressionCallSite(ndesc, link); 126 } 127 } 128 129 final ScriptObject scope = getProto(); 130 if (isNamedOperation) { 131 find = scope.findProperty(name, true); 132 } 133 134 if (find != null) { 135 return fixScopeCallSite(scope.lookup(desc, request), name, find.getOwner()); 136 } 137 138 // the property is not found - now check for 139 // __noSuchProperty__ and __noSuchMethod__ in expression 140 if (self != null) { 141 final String fallBack; 142 143 final StandardOperation firstOp = ndesc.getFirstOperation(); 144 switch (firstOp) { 145 case GET_METHOD: 146 fallBack = NO_SUCH_METHOD_NAME; 147 break; 148 case GET_PROPERTY: 149 case GET_ELEMENT: 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 (firstOp) { 161 case GET_METHOD: 162 link = self.noSuchMethod(desc, request); 163 break; 164 case GET_PROPERTY: 165 case GET_ELEMENT: 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 isScope true if is this a scope access 197 * @param start the object on which the lookup was originally initiated 198 * @return FindPropertyData or null if not found. 199 */ 200 @Override 201 protected FindProperty findProperty(final Object key, final boolean deep, boolean isScope, 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, false, expression); 206 if (exprProperty != null) { 207 return exprProperty; 208 } 209 return super.findProperty(key, deep, isScope, start); 210 } 211 212 @Override 213 protected Object invokeNoSuchProperty(final Object key, final boolean isScope, final int programPoint) { 214 final 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 final ScriptFunction sfunc = (ScriptFunction)func; 219 final Object self = isScope && sfunc.isStrict()? UNDEFINED : expression; 220 return ScriptRuntime.apply(sfunc, self, key); 221 } 222 } 223 224 return getProto().invokeNoSuchProperty(key, isScope, programPoint); 225 } 226 227 @Override 228 public void setSplitState(final int state) { 229 ((Scope) getNonWithParent()).setSplitState(state); 230 } 231 232 @Override 233 public int getSplitState() { 234 return ((Scope) getNonWithParent()).getSplitState(); 235 } 236 237 @Override 238 public void addBoundProperties(final ScriptObject source, final Property[] properties) { 239 // Declared variables in nested eval go to first normal (non-with) parent scope. 240 getNonWithParent().addBoundProperties(source, properties); 241 } 242 243 /** 244 * Get first parent scope that is not an instance of WithObject. 245 */ 246 private ScriptObject getNonWithParent() { 247 ScriptObject proto = getProto(); 248 249 while (proto != null && proto instanceof WithObject) { 250 proto = proto.getProto(); 251 } 252 253 return proto; 254 } 255 256 private static GuardedInvocation fixReceiverType(final GuardedInvocation link, final MethodHandle filter) { 257 // The receiver may be an Object or a ScriptObject. 258 final MethodType invType = link.getInvocation().type(); 259 final MethodType newInvType = invType.changeParameterType(0, filter.type().returnType()); 260 return link.asType(newInvType); 261 } 262 263 private static GuardedInvocation fixExpressionCallSite(final NashornCallSiteDescriptor desc, final GuardedInvocation link) { 264 // If it's not a getMethod, just add an expression filter that converts WithObject in "this" position to its 265 // expression. 266 if (desc.getFirstOperation() != StandardOperation.GET_METHOD) { 267 return fixReceiverType(link, WITHEXPRESSIONFILTER).filterArguments(0, WITHEXPRESSIONFILTER); 268 } 269 270 final MethodHandle linkInvocation = link.getInvocation(); 271 final MethodType linkType = linkInvocation.type(); 272 final boolean linkReturnsFunction = ScriptFunction.class.isAssignableFrom(linkType.returnType()); 273 274 return link.replaceMethods( 275 // Make sure getMethod will bind the script functions it receives to WithObject.expression 276 MH.foldArguments( 277 linkReturnsFunction ? 278 BIND_TO_EXPRESSION_FN : 279 BIND_TO_EXPRESSION_OBJ, 280 filterReceiver( 281 linkInvocation.asType( 282 linkType.changeReturnType( 283 linkReturnsFunction ? 284 ScriptFunction.class : 285 Object.class). 286 changeParameterType( 287 0, 288 Object.class)), 289 WITHEXPRESSIONFILTER)), 290 filterGuardReceiver(link, WITHEXPRESSIONFILTER)); 291 // No clever things for the guard -- it is still identically filtered. 292 293 } 294 295 private GuardedInvocation fixScopeCallSite(final GuardedInvocation link, final String name, final ScriptObject owner) { 296 final GuardedInvocation newLink = fixReceiverType(link, WITHSCOPEFILTER); 297 final MethodHandle expressionGuard = expressionGuard(name, owner); 298 final MethodHandle filterGuardReceiver = filterGuardReceiver(newLink, WITHSCOPEFILTER); 299 return link.replaceMethods( 300 filterReceiver( 301 newLink.getInvocation(), 302 WITHSCOPEFILTER), 303 NashornGuards.combineGuards( 304 expressionGuard, 305 filterGuardReceiver)); 306 } 307 308 private static MethodHandle filterGuardReceiver(final GuardedInvocation link, final MethodHandle receiverFilter) { 309 final MethodHandle test = link.getGuard(); 310 if (test == null) { 311 return null; 312 } 313 314 final Class<?> receiverType = test.type().parameterType(0); 315 final MethodHandle filter = MH.asType(receiverFilter, 316 receiverFilter.type().changeParameterType(0, receiverType). 317 changeReturnType(receiverType)); 318 319 return filterReceiver(test, filter); 320 } 321 322 private static MethodHandle filterReceiver(final MethodHandle mh, final MethodHandle receiverFilter) { 323 //With expression filter == receiverFilter, i.e. receiver is cast to withobject and its expression returned 324 return MH.filterArguments(mh, 0, receiverFilter.asType(receiverFilter.type().changeReturnType(mh.type().parameterType(0)))); 325 } 326 327 /** 328 * Drops the WithObject wrapper from the expression. 329 * @param receiver WithObject wrapper. 330 * @return The with expression. 331 */ 332 public static Object withFilterExpression(final Object receiver) { 333 return ((WithObject)receiver).expression; 334 } 335 336 @SuppressWarnings("unused") 337 private static Object bindToExpression(final Object fn, final Object receiver) { 338 if (fn instanceof ScriptFunction) { 339 return bindToExpression((ScriptFunction) fn, receiver); 340 } else if (fn instanceof ScriptObjectMirror) { 341 final ScriptObjectMirror mirror = (ScriptObjectMirror)fn; 342 if (mirror.isFunction()) { 343 // We need to make sure correct 'this' is used for calls with Ident call 344 // expressions. We do so here using an AbstractJSObject instance. 345 return new AbstractJSObject() { 346 @Override 347 public Object call(final Object thiz, final Object... args) { 348 return mirror.call(withFilterExpression(receiver), args); 349 } 350 }; 351 } 352 } 353 354 return fn; 355 } 356 357 private static Object bindToExpression(final ScriptFunction fn, final Object receiver) { 358 return fn.createBound(withFilterExpression(receiver), ScriptRuntime.EMPTY_ARRAY); 359 } 360 361 private MethodHandle expressionGuard(final String name, final ScriptObject owner) { 362 final PropertyMap map = expression.getMap(); 363 final SwitchPoint[] sp = expression.getProtoSwitchPoints(name, owner); 364 return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp); 365 } 366 367 @SuppressWarnings("unused") 368 private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint[] sp) { 369 return ((WithObject)receiver).expression.getMap() == map && !hasBeenInvalidated(sp); 370 } 371 372 private static boolean hasBeenInvalidated(final SwitchPoint[] switchPoints) { 373 if (switchPoints != null) { 374 for (final SwitchPoint switchPoint : switchPoints) { 375 if (switchPoint.hasBeenInvalidated()) { 376 return true; 377 } 378 } 379 } 380 return false; 381 } 382 383 /** 384 * Drops the WithObject wrapper from the scope. 385 * @param receiver WithObject wrapper. 386 * @return The with scope. 387 */ 388 public static Object withFilterScope(final Object receiver) { 389 return ((WithObject)receiver).getProto(); 390 } 391 392 /** 393 * Get the with expression for this {@code WithObject} 394 * @return the with expression 395 */ 396 public ScriptObject getExpression() { 397 return expression; 398 } 399 400 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 401 return MH.findStatic(MethodHandles.lookup(), WithObject.class, name, MH.type(rtype, types)); 402 } 403} 404