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 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 30 31 import java.lang.invoke.MethodHandle; 32 import java.lang.invoke.MethodHandles; 33 import java.lang.invoke.MethodType; 34 import java.lang.invoke.SwitchPoint; 35 import jdk.dynalink.CallSiteDescriptor; 36 import jdk.dynalink.NamedOperation; 37 import jdk.dynalink.Operation; 38 import jdk.dynalink.StandardOperation; 39 import jdk.dynalink.linker.GuardedInvocation; 40 import jdk.dynalink.linker.LinkRequest; 41 import jdk.nashorn.api.scripting.AbstractJSObject; 42 import jdk.nashorn.api.scripting.ScriptObjectMirror; 43 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 44 import jdk.nashorn.internal.runtime.linker.NashornGuards; 45 46 /** 47 * This class supports the handling of scope in a with body. 48 * 49 */ 50 public 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 setIsInternal(); 70 } 71 72 /** 73 * Delete a property based on a key. 74 * @param key Any valid JavaScript value. 75 * @param strict strict mode execution. 76 * @return True if deleted. 77 */ 78 @Override 79 public boolean delete(final Object key, final boolean strict) { 80 final ScriptObject self = expression; 81 final String propName = JSType.toString(key); 82 83 final FindProperty find = self.findProperty(propName, true); 84 85 if (find != null) { 86 return self.delete(propName, strict); 87 } 88 89 return false; 90 } 91 92 93 @Override 94 public GuardedInvocation lookup(final CallSiteDescriptor desc, final LinkRequest request) { 95 if (request.isCallSiteUnstable()) { 96 // Fall back to megamorphic invocation which performs a complete lookup each time without further relinking. 97 return super.lookup(desc, request); 98 } 99 100 GuardedInvocation link = null; 101 final Operation op = desc.getOperation(); 102 103 assert op instanceof NamedOperation; // WithObject is a scope object, access is always named 104 final String name = ((NamedOperation)op).getName().toString(); 105 106 FindProperty find = expression.findProperty(name, true); 107 108 if (find != null) { 109 link = expression.lookup(desc, request); 110 if (link != null) { 111 return fixExpressionCallSite(desc, link); 112 } 113 } 114 115 final ScriptObject scope = getProto(); 116 find = scope.findProperty(name, true); 117 118 if (find != null) { 119 return fixScopeCallSite(scope.lookup(desc, request), name, find.getOwner()); 120 } 121 122 // the property is not found - now check for 123 // __noSuchProperty__ and __noSuchMethod__ in expression 124 final String fallBack; 125 126 final Operation firstOp = NashornCallSiteDescriptor.getBaseOperation(desc); 127 if (firstOp == StandardOperation.GET) { 128 if (NashornCallSiteDescriptor.isMethodFirstOperation(desc)) { 129 fallBack = NO_SUCH_METHOD_NAME; 130 } else { 131 fallBack = NO_SUCH_PROPERTY_NAME; 132 } 133 } else { 134 fallBack = null; 135 } 136 137 if (fallBack != null) { 138 find = expression.findProperty(fallBack, true); 139 if (find != null) { 140 if (NO_SUCH_METHOD_NAME.equals(fallBack)) { 141 link = expression.noSuchMethod(desc, request).addSwitchPoint(getProtoSwitchPoint(name)); 142 } else if (NO_SUCH_PROPERTY_NAME.equals(fallBack)) { 143 link = expression.noSuchProperty(desc, request).addSwitchPoint(getProtoSwitchPoint(name)); 144 } 145 } 146 } 147 148 if (link != null) { 149 return fixExpressionCallSite(desc, link); 150 } 151 152 // still not found, may be scope can handle with it's own 153 // __noSuchProperty__, __noSuchMethod__ etc. 154 link = scope.lookup(desc, request); 155 156 if (link != null) { 157 return fixScopeCallSite(link, name, null); 158 } 159 160 return null; 161 } 162 163 /** 164 * Overridden to try to find the property first in the expression object (and its prototypes), and only then in this 165 * object (and its prototypes). 166 * 167 * @param key Property key. 168 * @param deep Whether the search should look up proto chain. 169 * @param isScope true if is this a scope access 170 * @param start the object on which the lookup was originally initiated 171 * @return FindPropertyData or null if not found. 172 */ 173 @Override 174 protected FindProperty findProperty(final Object key, final boolean deep, final boolean isScope, final ScriptObject start) { 175 // We call findProperty on 'expression' with 'expression' itself as start parameter. 176 // This way in ScriptObject.setObject we can tell the property is from a 'with' expression 177 // (as opposed from another non-scope object in the proto chain such as Object.prototype). 178 final FindProperty exprProperty = expression.findProperty(key, true, false, expression); 179 if (exprProperty != null) { 180 return exprProperty; 181 } 182 return super.findProperty(key, deep, isScope, start); 183 } 184 185 @Override 186 protected Object invokeNoSuchProperty(final Object key, final boolean isScope, final int programPoint) { 187 final FindProperty find = expression.findProperty(NO_SUCH_PROPERTY_NAME, true); 188 if (find != null) { 189 final Object func = find.getObjectValue(); 190 if (func instanceof ScriptFunction) { 191 final ScriptFunction sfunc = (ScriptFunction)func; 192 final Object self = isScope && sfunc.isStrict()? UNDEFINED : expression; 193 return ScriptRuntime.apply(sfunc, self, key); 194 } 195 } 196 197 return getProto().invokeNoSuchProperty(key, isScope, programPoint); 198 } 199 200 @Override 201 public void setSplitState(final int state) { 202 ((Scope) getNonWithParent()).setSplitState(state); 203 } 204 205 @Override 206 public int getSplitState() { 207 return ((Scope) getNonWithParent()).getSplitState(); 208 } 209 210 @Override 211 public void addBoundProperties(final ScriptObject source, final Property[] properties) { 212 // Declared variables in nested eval go to first normal (non-with) parent scope. 213 getNonWithParent().addBoundProperties(source, properties); 214 } 215 216 /** 217 * Get first parent scope that is not an instance of WithObject. 218 */ 219 private ScriptObject getNonWithParent() { 220 ScriptObject proto = getProto(); 221 222 while (proto != null && proto instanceof WithObject) { 223 proto = proto.getProto(); 224 } 225 226 return proto; 227 } 228 229 private static GuardedInvocation fixReceiverType(final GuardedInvocation link, final MethodHandle filter) { 230 // The receiver may be an Object or a ScriptObject. 231 final MethodType invType = link.getInvocation().type(); 232 final MethodType newInvType = invType.changeParameterType(0, filter.type().returnType()); 233 return link.asType(newInvType); 234 } 235 236 private static GuardedInvocation fixExpressionCallSite(final CallSiteDescriptor desc, final GuardedInvocation link) { 237 // If it's not a getMethod, just add an expression filter that converts WithObject in "this" position to its 238 // expression. 239 if (NashornCallSiteDescriptor.getBaseOperation(desc) != StandardOperation.GET || !NashornCallSiteDescriptor.isMethodFirstOperation(desc)) { 240 return fixReceiverType(link, WITHEXPRESSIONFILTER).filterArguments(0, WITHEXPRESSIONFILTER); 241 } 242 243 final MethodHandle linkInvocation = link.getInvocation(); 244 final MethodType linkType = linkInvocation.type(); 245 final boolean linkReturnsFunction = ScriptFunction.class.isAssignableFrom(linkType.returnType()); 246 247 return link.replaceMethods( 248 // Make sure getMethod will bind the script functions it receives to WithObject.expression 249 MH.foldArguments( 250 linkReturnsFunction ? 251 BIND_TO_EXPRESSION_FN : 252 BIND_TO_EXPRESSION_OBJ, 253 filterReceiver( 254 linkInvocation.asType( 255 linkType.changeReturnType( 256 linkReturnsFunction ? 257 ScriptFunction.class : 258 Object.class). 259 changeParameterType( 260 0, 261 Object.class)), 262 WITHEXPRESSIONFILTER)), 263 filterGuardReceiver(link, WITHEXPRESSIONFILTER)); 264 // No clever things for the guard -- it is still identically filtered. 265 266 } 267 268 private GuardedInvocation fixScopeCallSite(final GuardedInvocation link, final String name, final ScriptObject owner) { 269 final GuardedInvocation newLink = fixReceiverType(link, WITHSCOPEFILTER); 270 final MethodHandle expressionGuard = expressionGuard(name, owner); 271 final MethodHandle filteredGuard = filterGuardReceiver(newLink, WITHSCOPEFILTER); 272 return link.replaceMethods( 273 filterReceiver( 274 newLink.getInvocation(), 275 WITHSCOPEFILTER), 276 NashornGuards.combineGuards( 277 expressionGuard, 278 filteredGuard)); 279 } 280 281 private static MethodHandle filterGuardReceiver(final GuardedInvocation link, final MethodHandle receiverFilter) { 282 final MethodHandle test = link.getGuard(); 283 if (test == null) { 284 return null; 285 } 286 287 final Class<?> receiverType = test.type().parameterType(0); 288 final MethodHandle filter = MH.asType(receiverFilter, 289 receiverFilter.type().changeParameterType(0, receiverType). 290 changeReturnType(receiverType)); 291 292 return filterReceiver(test, filter); 293 } 294 295 private static MethodHandle filterReceiver(final MethodHandle mh, final MethodHandle receiverFilter) { 296 //With expression filter == receiverFilter, i.e. receiver is cast to withobject and its expression returned 297 return MH.filterArguments(mh, 0, receiverFilter.asType(receiverFilter.type().changeReturnType(mh.type().parameterType(0)))); 298 } 299 300 /** 301 * Drops the WithObject wrapper from the expression. 302 * @param receiver WithObject wrapper. 303 * @return The with expression. 304 */ 305 public static Object withFilterExpression(final Object receiver) { 306 return ((WithObject)receiver).expression; 307 } 308 309 @SuppressWarnings("unused") 310 private static Object bindToExpression(final Object fn, final Object receiver) { 311 if (fn instanceof ScriptFunction) { 312 return bindToExpression((ScriptFunction) fn, receiver); 313 } else if (fn instanceof ScriptObjectMirror) { 314 final ScriptObjectMirror mirror = (ScriptObjectMirror)fn; 315 if (mirror.isFunction()) { 316 // We need to make sure correct 'this' is used for calls with Ident call 317 // expressions. We do so here using an AbstractJSObject instance. 318 return new AbstractJSObject() { 319 @Override 320 public Object call(final Object thiz, final Object... args) { 321 return mirror.call(withFilterExpression(receiver), args); 322 } 323 }; 324 } 325 } 326 327 return fn; 328 } 329 330 private static Object bindToExpression(final ScriptFunction fn, final Object receiver) { 331 return fn.createBound(withFilterExpression(receiver), ScriptRuntime.EMPTY_ARRAY); 332 } 333 334 private MethodHandle expressionGuard(final String name, final ScriptObject owner) { 335 final PropertyMap map = expression.getMap(); 336 final SwitchPoint[] sp = expression.getProtoSwitchPoints(name, owner); 337 return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp); 338 } 339 340 @SuppressWarnings("unused") 341 private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint[] sp) { 342 return ((WithObject)receiver).expression.getMap() == map && !hasBeenInvalidated(sp); 343 } 344 345 private static boolean hasBeenInvalidated(final SwitchPoint[] switchPoints) { 346 if (switchPoints != null) { 347 for (final SwitchPoint switchPoint : switchPoints) { 348 if (switchPoint.hasBeenInvalidated()) { 349 return true; 350 } 351 } 352 } 353 return false; 354 } 355 356 /** 357 * Drops the WithObject wrapper from the scope. 358 * @param receiver WithObject wrapper. 359 * @return The with scope. 360 */ 361 public static Object withFilterScope(final Object receiver) { 362 return ((WithObject)receiver).getProto(); 363 } 364 365 /** 366 * Get the with expression for this {@code WithObject} 367 * @return the with expression 368 */ 369 public ScriptObject getExpression() { 370 return expression; 371 } 372 373 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 374 return MH.findStatic(MethodHandles.lookup(), WithObject.class, name, MH.type(rtype, types)); 375 } 376 }