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 }