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 40 /** 41 * This class supports the handling of scope in a with body. 42 * 43 */ 44 public final class WithObject extends ScriptObject implements Scope { 45 private static final MethodHandle WITHEXPRESSIONGUARD = findOwnMH("withExpressionGuard", boolean.class, Object.class, PropertyMap.class, SwitchPoint.class); 46 private static final MethodHandle WITHEXPRESSIONFILTER = findOwnMH("withFilterExpression", Object.class, Object.class); 47 private static final MethodHandle WITHSCOPEFILTER = findOwnMH("withFilterScope", Object.class, Object.class); 48 private static final MethodHandle BIND_TO_EXPRESSION_OBJ = findOwnMH("bindToExpression", Object.class, Object.class, Object.class); 49 private static final MethodHandle BIND_TO_EXPRESSION_FN = findOwnMH("bindToExpression", Object.class, ScriptFunction.class, Object.class); 50 51 /** With expression object. */ 52 private final ScriptObject expression; 53 54 /** 55 * Constructor 56 * 57 * @param scope scope object 58 * @param expression with expression 59 */ 60 WithObject(final ScriptObject scope, final ScriptObject expression) { 61 super(scope, null); 62 setIsScope(); 63 this.expression = expression; 64 } 65 66 67 /** 68 * Delete a property based on a key. 69 * @param key Any valid JavaScript value. 70 * @param strict strict mode execution. 71 * @return True if deleted. 72 */ 73 @Override 74 public boolean delete(final Object key, final boolean strict) { 75 final ScriptObject self = expression; 76 final String propName = JSType.toString(key); 77 78 final FindProperty find = self.findProperty(propName, true); 79 80 if (find != null) { 81 return self.delete(propName, strict); 82 } 83 84 return false; 85 } 86 87 88 @Override 89 public GuardedInvocation lookup(final CallSiteDescriptor desc, final LinkRequest request) { 90 // With scopes can never be observed outside of Nashorn code, so all call sites that can address it will of 91 // necessity have a Nashorn descriptor - it is safe to cast. 92 final NashornCallSiteDescriptor ndesc = (NashornCallSiteDescriptor)desc; 93 FindProperty find = null; 94 GuardedInvocation link = null; 95 ScriptObject self = null; 96 97 final boolean isNamedOperation; 98 final String name; 99 if(desc.getNameTokenCount() > 2) { 100 isNamedOperation = true; 101 name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 102 } else { 103 isNamedOperation = false; 104 name = null; 105 } 106 107 self = expression; 108 if (isNamedOperation) { 109 find = self.findProperty(name, true); 110 } 111 112 if (find != null) { 113 link = self.lookup(desc, request); 114 115 if (link != null) { 116 return fixExpressionCallSite(ndesc, link); 117 } 118 } 119 120 final ScriptObject scope = getProto(); 121 if (isNamedOperation) { 122 find = scope.findProperty(name, true); 123 } 124 125 if (find != null) { 126 return fixScopeCallSite(scope.lookup(desc, request), name); 127 } 128 129 // the property is not found - now check for 130 // __noSuchProperty__ and __noSuchMethod__ in expression 131 if (self != null) { 132 final String fallBack; 133 134 final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0); 135 136 switch (operator) { 137 case "callMethod": 138 throw new AssertionError(); // Nashorn never emits callMethod 139 case "getMethod": 140 fallBack = NO_SUCH_METHOD_NAME; 141 break; 142 case "getProp": 143 case "getElem": 144 fallBack = NO_SUCH_PROPERTY_NAME; 145 break; 146 default: 147 fallBack = null; 148 break; 149 } 150 151 if (fallBack != null) { 152 find = self.findProperty(fallBack, true); 153 if (find != null) { 154 switch (operator) { 155 case "getMethod": 156 link = self.noSuchMethod(desc, request); 157 break; 158 case "getProp": 159 case "getElem": 160 link = self.noSuchProperty(desc, request); 161 break; 162 default: 163 break; 164 } 165 } 166 } 167 168 if (link != null) { 169 return fixExpressionCallSite(ndesc, link); 170 } 171 } 172 173 // still not found, may be scope can handle with it's own 174 // __noSuchProperty__, __noSuchMethod__ etc. 175 link = scope.lookup(desc, request); 176 177 if (link != null) { 178 return fixScopeCallSite(link, name); 179 } 180 181 return null; 182 } 183 184 /** 185 * Overridden to try to find the property first in the expression object (and its prototypes), and only then in this 186 * object (and its prototypes). 187 * 188 * @param key Property key. 189 * @param deep Whether the search should look up proto chain. 190 * @param stopOnNonScope should a deep search stop on the first non-scope object? 191 * @param start the object on which the lookup was originally initiated 192 * 193 * @return FindPropertyData or null if not found. 194 */ 195 @Override 196 FindProperty findProperty(final String key, final boolean deep, final boolean stopOnNonScope, final ScriptObject start) { 197 final FindProperty exprProperty = expression.findProperty(key, deep, stopOnNonScope, start); 198 if (exprProperty != null) { 199 return exprProperty; 200 } 201 return super.findProperty(key, deep, stopOnNonScope, start); 202 } 203 204 @Override 205 public void setSplitState(final int state) { 206 getNonWithParent().setSplitState(state); 207 } 208 209 @Override 210 public int getSplitState() { 211 return getNonWithParent().getSplitState(); 212 } 213 214 /** 215 * Get first parent scope that is not an instance of WithObject. 216 */ 217 private Scope getNonWithParent() { 218 ScriptObject proto = getParentScope(); 219 220 while (proto != null && proto instanceof WithObject) { 221 proto = ((WithObject)proto).getParentScope(); 222 } 223 224 assert proto instanceof Scope : "with scope without parent scope"; 225 return (Scope) proto; 226 } 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 NashornCallSiteDescriptor 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(!"getMethod".equals(desc.getFirstOperator())) { 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 return link.replaceMethods( 247 // Make sure getMethod will bind the script functions it receives to WithObject.expression 248 MH.foldArguments(linkReturnsFunction ? BIND_TO_EXPRESSION_FN : BIND_TO_EXPRESSION_OBJ, 249 filter(linkInvocation.asType(linkType.changeReturnType( 250 linkReturnsFunction ? ScriptFunction.class : Object.class)), WITHEXPRESSIONFILTER)), 251 // No clever things for the guard -- it is still identically filtered. 252 filterGuard(link, WITHEXPRESSIONFILTER)); 253 } 254 255 private GuardedInvocation fixScopeCallSite(final GuardedInvocation link, final String name) { 256 final GuardedInvocation newLink = fixReceiverType(link, WITHSCOPEFILTER); 257 return link.replaceMethods(filter(newLink.getInvocation(), WITHSCOPEFILTER), 258 MH.guardWithTest( 259 expressionGuard(name), 260 filterGuard(newLink, WITHSCOPEFILTER), 261 MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class))); 262 } 263 264 private static MethodHandle filterGuard(final GuardedInvocation link, final MethodHandle filter) { 265 final MethodHandle test = link.getGuard(); 266 return test == null ? null : filter(test, filter); 267 } 268 269 private static MethodHandle filter(final MethodHandle mh, final MethodHandle filter) { 270 return MH.filterArguments(mh, 0, filter); 271 } 272 273 /** 274 * Drops the WithObject wrapper from the expression. 275 * @param receiver WithObject wrapper. 276 * @return The with expression. 277 */ 278 public static Object withFilterExpression(final Object receiver) { 279 return ((WithObject)receiver).expression; 280 } 281 282 @SuppressWarnings("unused") 283 private static Object bindToExpression(final Object fn, final Object receiver) { 284 return fn instanceof ScriptFunction ? bindToExpression((ScriptFunction) fn, receiver) : fn; 285 } 286 287 private static Object bindToExpression(final ScriptFunction fn, final Object receiver) { 288 return fn.makeBoundFunction(withFilterExpression(receiver), new Object[0]); 289 } 290 291 private MethodHandle expressionGuard(final String name) { 292 final PropertyMap map = expression.getMap(); 293 final SwitchPoint sp = map.getProtoGetSwitchPoint(expression.getProto(), name); 294 return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp); 295 } 296 297 @SuppressWarnings("unused") 298 private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint sp) { 299 return ((WithObject)receiver).expression.getMap() == map && (sp == null || !sp.hasBeenInvalidated()); 300 } 301 302 /** 303 * Drops the WithObject wrapper from the scope. 304 * @param receiver WithObject wrapper. 305 * @return The with scope. 306 */ 307 public static Object withFilterScope(final Object receiver) { 308 return ((WithObject)receiver).getProto(); 309 } 310 311 /** 312 * Get the with expression for this {@code WithObject} 313 * @return the with expression 314 */ 315 public ScriptObject getExpression() { 316 return expression; 317 } 318 319 /** 320 * Get the parent scope for this {@code WithObject} 321 * @return the parent scope 322 */ 323 public ScriptObject getParentScope() { 324 return getProto(); 325 } 326 327 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 328 return MH.findStatic(MethodHandles.lookup(), WithObject.class, name, MH.type(rtype, types)); 329 } 330 }