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.codegen.CompilerConstants.staticCall; 29 import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall; 30 import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; 31 import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; 32 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 33 import static jdk.nashorn.internal.runtime.PropertyDescriptor.CONFIGURABLE; 34 import static jdk.nashorn.internal.runtime.PropertyDescriptor.ENUMERABLE; 35 import static jdk.nashorn.internal.runtime.PropertyDescriptor.GET; 36 import static jdk.nashorn.internal.runtime.PropertyDescriptor.SET; 37 import static jdk.nashorn.internal.runtime.PropertyDescriptor.VALUE; 38 import static jdk.nashorn.internal.runtime.PropertyDescriptor.WRITABLE; 39 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 40 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndexNoThrow; 41 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; 42 import static jdk.nashorn.internal.runtime.linker.Lookup.MH; 43 44 import java.lang.invoke.MethodHandle; 45 import java.lang.invoke.MethodHandles; 46 import java.lang.invoke.MethodType; 47 import java.util.AbstractMap; 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.Collection; 51 import java.util.Collections; 52 import java.util.HashSet; 53 import java.util.Iterator; 54 import java.util.LinkedHashSet; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Set; 58 import jdk.nashorn.internal.codegen.CompilerConstants.Call; 59 import jdk.nashorn.internal.codegen.objects.ObjectClassGenerator; 60 import jdk.nashorn.internal.objects.AccessorPropertyDescriptor; 61 import jdk.nashorn.internal.objects.DataPropertyDescriptor; 62 import jdk.nashorn.internal.runtime.arrays.ArrayData; 63 import jdk.nashorn.internal.runtime.linker.Bootstrap; 64 import jdk.nashorn.internal.runtime.linker.Lookup; 65 import jdk.nashorn.internal.runtime.linker.MethodHandleFactory; 66 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 67 import jdk.nashorn.internal.runtime.linker.NashornGuardedInvocation; 68 import jdk.nashorn.internal.runtime.linker.NashornGuards; 69 import org.dynalang.dynalink.CallSiteDescriptor; 70 import org.dynalang.dynalink.linker.GuardedInvocation; 71 import org.dynalang.dynalink.support.CallSiteDescriptorFactory; 72 73 /** 74 * Base class for generic JavaScript objects. 75 * <p> 76 * Notes: 77 * <ul> 78 * <li>The map is used to identify properties in the object.</li> 79 * <li>If the map is modified then it must be cloned and replaced. This notifies 80 * any code that made assumptions about the object that things have changed. 81 * Ex. CallSites that have been validated must check to see if the map has 82 * changed (or a map from a different object type) and hence relink the method 83 * to call.</li> 84 * <li>Modifications of the map include adding/deleting attributes or changing a 85 * function field value.</li> 86 * </ul> 87 */ 88 89 90 public abstract class ScriptObject extends PropertyListenerManager implements PropertyAccess { 91 92 /** Search fall back routine name for "no such method" */ 93 static final String NO_SUCH_METHOD_NAME = "__noSuchMethod__"; 94 95 /** Search fall back routine name for "no such property" */ 96 static final String NO_SUCH_PROPERTY_NAME = "__noSuchProperty__"; 97 98 /** Per ScriptObject flag - is this a scope object? */ 99 public static final int IS_SCOPE = 0b0000_0001; 100 101 /** Per ScriptObject flag - is this an array object? */ 102 public static final int IS_ARRAY = 0b0000_0010; 103 104 /** Per ScriptObject flag - is this an arguments object? */ 105 public static final int IS_ARGUMENTS = 0b0000_0100; 106 107 /** Spill growth rate - by how many elements does {@link ScriptObject#spill} when full */ 108 public static final int SPILL_RATE = 8; 109 110 /** Map to property information and accessor functions. Ordered by insertion. */ 111 private PropertyMap map; 112 113 /** Object flags. */ 114 private int flags; 115 116 /** Area for properties added to object after instantiation, see {@link SpillProperty} */ 117 public Object[] spill; 118 119 /** Local embed area position 0 - used for {@link SpillProperty} before {@link ScriptObject#spill} */ 120 public Object embed0; 121 122 /** Local embed area position 1 - used for {@link SpillProperty} before {@link ScriptObject#spill} */ 123 public Object embed1; 124 125 /** Local embed area position 2 - used for {@link SpillProperty} before {@link ScriptObject#spill} */ 126 public Object embed2; 127 128 /** Local embed area position 3 - used for {@link SpillProperty} before {@link ScriptObject#spill} */ 129 public Object embed3; 130 131 /** Indexed array data. */ 132 private ArrayData arrayData; 133 134 static final MethodHandle SETEMBED = findOwnMH("setEmbed", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, MethodHandle.class, int.class, Object.class, Object.class); 135 static final MethodHandle SETSPILL = findOwnMH("setSpill", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, Object.class, Object.class); 136 static final MethodHandle SETSPILLWITHNEW = findOwnMH("setSpillWithNew", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, Object.class, Object.class); 137 static final MethodHandle SETSPILLWITHGROW = findOwnMH("setSpillWithGrow", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, int.class, Object.class, Object.class); 138 139 private static final MethodHandle TRUNCATINGFILTER = findOwnMH("truncatingFilter", Object[].class, int.class, Object[].class); 140 private static final MethodHandle KNOWNFUNCPROPGUARD = findOwnMH("knownFunctionPropertyGuard", boolean.class, Object.class, PropertyMap.class, MethodHandle.class, Object.class, ScriptFunction.class); 141 142 /** Method handle for getting a function argument at a given index. Used from MapCreator */ 143 public static final Call GET_ARGUMENT = virtualCall(ScriptObject.class, "getArgument", Object.class, int.class); 144 145 /** Method handle for setting a function argument at a given index. Used from MapCreator */ 146 public static final Call SET_ARGUMENT = virtualCall(ScriptObject.class, "setArgument", void.class, int.class, Object.class); 147 148 /** Method handle for getting the proto of a ScriptObject - used by {@link jdk.nashorn.internal.codegen.CodeGenerator} */ 149 public static final Call GET_PROTO = virtualCallNoLookup(ScriptObject.class, "getProto", ScriptObject.class); 150 151 /** Method handle for setting the proto of a ScriptObject - used by {@link jdk.nashorn.internal.codegen.CodeGenerator} */ 152 public static final Call SET_PROTO = virtualCallNoLookup(ScriptObject.class, "setProto", void.class, ScriptObject.class); 153 154 /** Method handle for setting the user accessors of a ScriptObject - used by {@link jdk.nashorn.internal.codegen.CodeGenerator} */ 155 public static final Call SET_USER_ACCESSORS = virtualCall(ScriptObject.class, "setUserAccessors", void.class, String.class, ScriptFunction.class, ScriptFunction.class); 156 157 /** Method handle for getter for {@link UserAccessorProperty}, given a slot */ 158 static final Call USER_ACCESSOR_GETTER = staticCall(MethodHandles.lookup(), ScriptObject.class, "userAccessorGetter", Object.class, ScriptObject.class, int.class, Object.class); 159 160 /** Method handle for setter for {@link UserAccessorProperty}, given a slot */ 161 static final Call USER_ACCESSOR_SETTER = staticCall(MethodHandles.lookup(), ScriptObject.class, "userAccessorSetter", void.class, ScriptObject.class, int.class, String.class, Object.class, Object.class); 162 163 private static final MethodHandle INVOKE_UA_GETTER = Bootstrap.createDynamicInvoker("dyn:call", Object.class, 164 Object.class, Object.class); 165 private static final MethodHandle INVOKE_UA_SETTER = Bootstrap.createDynamicInvoker("dyn:call", void.class, 166 Object.class, Object.class, Object.class); 167 168 /** 169 * Constructor 170 */ 171 public ScriptObject() { 172 this(null); 173 } 174 175 /** 176 * Constructor 177 * 178 * @param map {@link PropertyMap} used to create the initial object 179 */ 180 public ScriptObject(final PropertyMap map) { 181 if (Context.DEBUG) { 182 ScriptObject.count++; 183 } 184 185 this.arrayData = ArrayData.EMPTY_ARRAY; 186 187 if (map == null) { 188 this.setMap(PropertyMap.newMap(getClass())); 189 return; 190 } 191 192 this.setMap(map); 193 } 194 195 /** 196 * Copy all properties from the source object with their receiver bound to the source. 197 * This function was known as mergeMap 198 * 199 * @param source The source object to copy from. 200 */ 201 public void addBoundProperties(final ScriptObject source) { 202 PropertyMap newMap = this.getMap(); 203 204 for (final Property property : source.getMap().getProperties()) { 205 final String key = property.getKey(); 206 207 if (newMap.findProperty(key) == null) { 208 if (property instanceof UserAccessorProperty) { 209 final UserAccessorProperty prop = this.newUserAccessors(key, property.getFlags(), property.getGetterFunction(source), property.getSetterFunction(source)); 210 newMap = newMap.addProperty(prop); 211 } else { 212 newMap = newMap.newPropertyBind((AccessorProperty)property, source); 213 } 214 } 215 } 216 217 this.setMap(newMap); 218 } 219 220 /** 221 * Bind the method handle to the specified receiver, while preserving its original type (it will just ignore the 222 * first argument in lieu of the bound argument). 223 * @param methodHandle Method handle to bind to. 224 * @param receiver Object to bind. 225 * @return Bound method handle. 226 */ 227 static MethodHandle bindTo(final MethodHandle methodHandle, final Object receiver) { 228 return MH.dropArguments(MH.bindTo(methodHandle, receiver), 0, methodHandle.type().parameterType(0)); 229 } 230 231 /** 232 * Return a property iterator. 233 * @return Property iterator. 234 */ 235 public Iterator<String> propertyIterator() { 236 return new KeyIterator(this); 237 } 238 239 /** 240 * Return a property value iterator. 241 * @return Property value iterator. 242 */ 243 public Iterator<Object> valueIterator() { 244 return new ValueIterator(this); 245 } 246 247 /** 248 * ECMA 8.10.1 IsAccessorDescriptor ( Desc ) 249 * @return true if this has a {@link AccessorPropertyDescriptor} with a getter or a setter 250 */ 251 public final boolean isAccessorDescriptor() { 252 return has(GET) || has(SET); 253 } 254 255 /** 256 * ECMA 8.10.2 IsDataDescriptor ( Desc ) 257 * @return true if this has a {@link DataPropertyDescriptor}, i.e. the object has a property value and is writable 258 */ 259 public final boolean isDataDescriptor() { 260 return has(VALUE) || has(WRITABLE); 261 } 262 263 /** 264 * ECMA 8.10.3 IsGenericDescriptor ( Desc ) 265 * @return true if this has a descriptor describing an {@link AccessorPropertyDescriptor} or {@link DataPropertyDescriptor} 266 */ 267 public final boolean isGenericDescriptor() { 268 return isAccessorDescriptor() || isDataDescriptor(); 269 } 270 271 /** 272 * ECMA 8.10.5 ToPropertyDescriptor ( Obj ) 273 * 274 * @return property descriptor 275 */ 276 public final PropertyDescriptor toPropertyDescriptor() { 277 final GlobalObject global = (GlobalObject) Context.getGlobalTrusted(); 278 279 final PropertyDescriptor desc; 280 if (isDataDescriptor()) { 281 if (has(SET) || has(GET)) { 282 typeError((ScriptObject)global, "inconsistent.property.descriptor"); 283 } 284 285 desc = global.newDataDescriptor(UNDEFINED, false, false, false); 286 } else if (isAccessorDescriptor()) { 287 if (has(VALUE) || has(WRITABLE)) { 288 typeError((ScriptObject)global, "inconsistent.property.descriptor"); 289 } 290 291 desc = global.newAccessorDescriptor(UNDEFINED, UNDEFINED, false, false); 292 } else { 293 desc = global.newGenericDescriptor(false, false); 294 } 295 296 return desc.fillFrom(this); 297 } 298 299 /** 300 * ECMA 8.10.5 ToPropertyDescriptor ( Obj ) 301 * 302 * @param global global scope object 303 * @param obj object to create property descriptor from 304 * 305 * @return property descriptor 306 */ 307 public static PropertyDescriptor toPropertyDescriptor(final ScriptObject global, final Object obj) { 308 if (obj instanceof ScriptObject) { 309 return ((ScriptObject)obj).toPropertyDescriptor(); 310 } 311 312 typeError(global, "not.an.object", ScriptRuntime.safeToString(obj)); 313 return null; 314 } 315 316 /** 317 * ECMA 8.12.1 [[GetOwnProperty]] (P) 318 * 319 * @param key property key 320 * 321 * @return Returns the Property Descriptor of the named own property of this 322 * object, or undefined if absent. 323 */ 324 public Object getOwnPropertyDescriptor(final String key) { 325 final Property property = getMap().findProperty(key); 326 327 final GlobalObject global = (GlobalObject)Context.getGlobalTrusted(); 328 329 if (property != null) { 330 final ScriptFunction get = property.getGetterFunction(this); 331 final ScriptFunction set = property.getSetterFunction(this); 332 333 final boolean configurable = property.isConfigurable(); 334 final boolean enumerable = property.isEnumerable(); 335 final boolean writable = property.isWritable(); 336 337 if (property instanceof UserAccessorProperty) { 338 return global.newAccessorDescriptor( 339 (get != null) ? 340 get : 341 UNDEFINED, 342 (set != null) ? 343 set : 344 UNDEFINED, 345 configurable, 346 enumerable); 347 } 348 349 return global.newDataDescriptor(getWithProperty(property), configurable, enumerable, writable); 350 } 351 352 final int index = getArrayIndexNoThrow(key); 353 final ArrayData array = getArray(); 354 355 if (array.has(index)) { 356 return array.getDescriptor(global, index); 357 } 358 359 return UNDEFINED; 360 } 361 362 /** 363 * ECMA 8.12.2 [[GetProperty]] (P) 364 * 365 * @param key property key 366 * 367 * @return Returns the fully populated Property Descriptor of the named property 368 * of this object, or undefined if absent. 369 */ 370 public Object getPropertyDescriptor(final String key) { 371 final Object res = getOwnPropertyDescriptor(key); 372 373 if (res != UNDEFINED) { 374 return res; 375 } else if (getProto() != null) { 376 return getProto().getOwnPropertyDescriptor(key); 377 } else { 378 return UNDEFINED; 379 } 380 } 381 382 /** 383 * ECMA 8.12.9 [[DefineOwnProperty]] (P, Desc, Throw) 384 * 385 * @param key the property key 386 * @param propertyDesc the property descriptor 387 * @param reject is the property extensible - true means new definitions are rejected 388 * 389 * @return true if property was successfully defined 390 */ 391 public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) { 392 final ScriptObject global = Context.getGlobalTrusted(); 393 final PropertyDescriptor desc = toPropertyDescriptor(global, propertyDesc); 394 final Object current = getOwnPropertyDescriptor(key); 395 final String name = JSType.toString(key); 396 397 if (current == UNDEFINED) { 398 if (isExtensible()) { 399 // add a new own property 400 addOwnProperty(key, desc); 401 return true; 402 } 403 // new property added to non-extensible object 404 if (reject) { 405 typeError(global, "object.non.extensible", name, ScriptRuntime.safeToString(this)); 406 } 407 return false; 408 } 409 // modifying an existing property 410 final PropertyDescriptor currentDesc = (PropertyDescriptor) current; 411 final PropertyDescriptor newDesc = desc; 412 413 if (newDesc.type() == PropertyDescriptor.GENERIC && 414 ! newDesc.has(CONFIGURABLE) && ! newDesc.has(ENUMERABLE)) { 415 // every descriptor field is absent 416 return true; 417 } 418 419 if (currentDesc.equals(newDesc)) { 420 // every descriptor field of the new is same as the current 421 return true; 422 } 423 424 if (! currentDesc.isConfigurable()) { 425 if (newDesc.has(CONFIGURABLE) && newDesc.isConfigurable()) { 426 // not configurable can not be made configurable 427 if (reject) { 428 typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this)); 429 } 430 return false; 431 } 432 433 if (newDesc.has(ENUMERABLE) && 434 currentDesc.isEnumerable() != newDesc.isEnumerable()) { 435 // cannot make non-enumerable as enumerable or vice-versa 436 if (reject) { 437 typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this)); 438 } 439 return false; 440 } 441 } 442 443 int propFlags = Property.mergeFlags(currentDesc, newDesc); 444 Property property = getMap().findProperty(key); 445 446 if (currentDesc.type() == PropertyDescriptor.DATA && 447 (newDesc.type() == PropertyDescriptor.DATA || newDesc.type() == PropertyDescriptor.GENERIC)) { 448 if (! currentDesc.isConfigurable() && ! currentDesc.isWritable()) { 449 if (newDesc.has(WRITABLE) && newDesc.isWritable() || 450 newDesc.has(VALUE) && ! ScriptRuntime.sameValue(currentDesc.getValue(), newDesc.getValue())) { 451 if (reject) { 452 typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this)); 453 } 454 return false; 455 } 456 } 457 458 final boolean newValue = newDesc.has(VALUE); 459 final Object value = newValue? newDesc.getValue() : currentDesc.getValue(); 460 if (newValue && property != null) { 461 // Temporarily clear flags. 462 property = modifyOwnProperty(property, 0); 463 set(key, value, getContext()._strict); 464 } 465 466 if (property == null) { 467 // promoting an arrayData value to actual property 468 addOwnProperty(key, propFlags, value); 469 removeArraySlot(key); 470 } else { 471 // Now set the new flags 472 modifyOwnProperty(property, propFlags); 473 } 474 } else if (currentDesc.type() == PropertyDescriptor.ACCESSOR && 475 (newDesc.type() == PropertyDescriptor.ACCESSOR || 476 newDesc.type() == PropertyDescriptor.GENERIC)) { 477 if (! currentDesc.isConfigurable()) { 478 if (newDesc.has(PropertyDescriptor.GET) && ! ScriptRuntime.sameValue(currentDesc.getGetter(), newDesc.getGetter()) || 479 newDesc.has(PropertyDescriptor.SET) && ! ScriptRuntime.sameValue(currentDesc.getSetter(), newDesc.getSetter())) { 480 if (reject) { 481 typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this)); 482 } 483 return false; 484 } 485 } 486 487 // New set the new features. 488 modifyOwnProperty(property, propFlags, 489 newDesc.has(GET) ? newDesc.getGetter() : currentDesc.getGetter(), 490 newDesc.has(SET) ? newDesc.getSetter() : currentDesc.getSetter()); 491 } else { 492 // changing descriptor type 493 if (! currentDesc.isConfigurable()) { 494 // not configurable can not be made configurable 495 if (reject) { 496 typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this)); 497 } 498 return false; 499 } 500 501 propFlags = 0; 502 503 // Preserve only configurable and enumerable from current desc 504 // if those are not overridden in the new property descriptor. 505 boolean value = newDesc.has(CONFIGURABLE)? newDesc.isConfigurable() : currentDesc.isConfigurable(); 506 if (!value) { 507 propFlags |= Property.NOT_CONFIGURABLE; 508 } 509 value = newDesc.has(ENUMERABLE)? newDesc.isEnumerable() : currentDesc.isEnumerable(); 510 if (!value) { 511 propFlags |= Property.NOT_ENUMERABLE; 512 } 513 514 final int type = newDesc.type(); 515 if (type == PropertyDescriptor.DATA) { 516 // get writable from the new descriptor 517 value = newDesc.has(WRITABLE) && newDesc.isWritable(); 518 if (! value) { 519 propFlags |= Property.NOT_WRITABLE; 520 } 521 522 // delete the old property 523 deleteOwnProperty(property); 524 // add new data property 525 addOwnProperty(key, propFlags, newDesc.getValue()); 526 } else if (type == PropertyDescriptor.ACCESSOR) { 527 if (property == null) { 528 addOwnProperty(key, propFlags, 529 newDesc.has(GET) ? newDesc.getGetter() : null, 530 newDesc.has(SET) ? newDesc.getSetter() : null); 531 } else { 532 // Modify old property with the new features. 533 modifyOwnProperty(property, propFlags, 534 newDesc.has(GET) ? newDesc.getGetter() : null, 535 newDesc.has(SET) ? newDesc.getSetter() : null); 536 } 537 } 538 } 539 540 checkIntegerKey(key); 541 542 return true; 543 } 544 545 /** 546 * Spec. mentions use of [[DefineOwnProperty]] for indexed properties in 547 * certain places (eg. Array.prototype.map, filter). We can not use ScriptObject.set 548 * method in such cases. This is because set method uses inherited setters (if any) 549 * from any object in proto chain such as Array.prototype, Object.prototype. 550 * This method directly sets a particular element value in the current object. 551 * 552 * @param index index key for property 553 * @param value value to define 554 */ 555 protected final void defineOwnProperty(final int index, final Object value) { 556 if (index >= getArray().length()) { 557 // make array big enough to hold.. 558 setArray(getArray().ensure(index)); 559 } 560 setArray(getArray().set(index, value, false)); 561 } 562 563 private void checkIntegerKey(final String key) { 564 final int index = getArrayIndexNoThrow(key); 565 566 if (isValidArrayIndex(index)) { 567 final ArrayData data = getArray(); 568 569 if (data.has(index)) { 570 setArray(data.delete(index)); 571 } 572 } 573 } 574 575 private void removeArraySlot(final String key) { 576 final int index = getArrayIndexNoThrow(key); 577 final ArrayData array = getArray(); 578 579 if (array.has(index)) { 580 setArray(array.delete(index)); 581 } 582 } 583 584 /** 585 * Add a new property to the object. 586 * 587 * @param key property key 588 * @param propertyDesc property descriptor for property 589 */ 590 public final void addOwnProperty(final String key, final PropertyDescriptor propertyDesc) { 591 // Already checked that there is no own property with that key. 592 PropertyDescriptor pdesc = propertyDesc; 593 594 final int propFlags = Property.toFlags(pdesc); 595 596 if (pdesc.type() == PropertyDescriptor.GENERIC) { 597 final GlobalObject global = (GlobalObject) Context.getGlobalTrusted(); 598 final PropertyDescriptor dDesc = global.newDataDescriptor(UNDEFINED, false, false, false); 599 600 dDesc.fillFrom((ScriptObject)pdesc); 601 pdesc = dDesc; 602 } 603 604 final int type = pdesc.type(); 605 if (type == PropertyDescriptor.DATA) { 606 addOwnProperty(key, propFlags, pdesc.getValue()); 607 } else if (type == PropertyDescriptor.ACCESSOR) { 608 addOwnProperty(key, propFlags, 609 pdesc.has(GET) ? pdesc.getGetter() : null, 610 pdesc.has(SET) ? pdesc.getSetter() : null); 611 } 612 613 checkIntegerKey(key); 614 } 615 616 /** 617 * Low level property API (not using property descriptors) 618 * <p> 619 * Find a property in the prototype hierarchy. Note: this is final and not 620 * a good idea to override. If you have to, use 621 * {jdk.nashorn.internal.objects.NativeArray{@link #getProperty(String)} or 622 * {jdk.nashorn.internal.objects.NativeArray{@link #getPropertyDescriptor(String)} as the 623 * overriding way to find array properties 624 * 625 * @see jdk.nashorn.internal.objects.NativeArray 626 * 627 * @param key Property key. 628 * @param deep Whether the search should look up proto chain. 629 * 630 * @return FindPropertyData or null if not found. 631 */ 632 public final FindProperty findProperty(final String key, final boolean deep) { 633 return findProperty(key, deep, false); 634 } 635 636 /** 637 * Low level property API (not using property descriptors) 638 * <p> 639 * Find a property in the prototype hierarchy. Note: this is final and not 640 * a good idea to override. If you have to, use 641 * {jdk.nashorn.internal.objects.NativeArray{@link #getProperty(String)} or 642 * {jdk.nashorn.internal.objects.NativeArray{@link #getPropertyDescriptor(String)} as the 643 * overriding way to find array properties 644 * 645 * @see jdk.nashorn.internal.objects.NativeArray 646 * 647 * @param key Property key. 648 * @param deep Whether the search should look up proto chain. 649 * @param stopOnNonScope should a deep search stop on the first non-scope object? 650 * 651 * @return FindPropertyData or null if not found. 652 */ 653 public final FindProperty findProperty(final String key, final boolean deep, final boolean stopOnNonScope) { 654 int depth = 0; 655 656 for (ScriptObject self = this; self != null; self = self.getProto()) { 657 // if doing deep search, stop search on the first non-scope object if asked to do so 658 if (stopOnNonScope && depth != 0 && !self.isScope()) { 659 break; 660 } 661 final PropertyMap selfMap = self.getMap(); 662 final Property property = selfMap.findProperty(key); 663 664 if (property != null) { 665 return new FindProperty(this, self, selfMap, property, depth); 666 } else if (!deep) { 667 return null; 668 } 669 670 depth++; 671 } 672 673 return null; 674 } 675 676 /** 677 * Add a new property to the object. 678 * <p> 679 * This a more "low level" way that doesn't involve {@link PropertyDescriptor}s 680 * 681 * @param key Property key. 682 * @param propertyFlags Property flags. 683 * @param getter Property getter, or null if not defined 684 * @param setter Property setter, or null if not defined 685 * 686 * @return New property. 687 */ 688 public final Property addOwnProperty(final String key, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) { 689 return addOwnProperty(newUserAccessors(key, propertyFlags, getter, setter)); 690 } 691 692 /** 693 * Add a new property to the object. 694 * <p> 695 * This a more "low level" way that doesn't involve {@link PropertyDescriptor}s 696 * 697 * @param key Property key. 698 * @param propertyFlags Property flags. 699 * @param value Value of property 700 * 701 * @return New property. 702 */ 703 public final Property addOwnProperty(final String key, final int propertyFlags, final Object value) { 704 final MethodHandle setter = addSpill(key, propertyFlags); 705 706 try { 707 setter.invokeExact((Object)this, value); 708 } catch (final Error|RuntimeException e) { 709 throw e; 710 } catch (final Throwable e) { 711 throw new RuntimeException(e); 712 } 713 714 return getMap().findProperty(key); 715 } 716 717 /** 718 * Add a new property to the object. 719 * <p> 720 * This a more "low level" way that doesn't involve {@link PropertyDescriptor}s 721 * 722 * @param newProperty property to add 723 * 724 * @return New property. 725 */ 726 public final Property addOwnProperty(final Property newProperty) { 727 PropertyMap oldMap = getMap(); 728 729 while (true) { 730 final PropertyMap newMap = oldMap.addProperty(newProperty); 731 732 if (!compareAndSetMap(oldMap, newMap)) { 733 oldMap = getMap(); 734 final Property oldProperty = oldMap.findProperty(newProperty.getKey()); 735 736 if (oldProperty != null) { 737 return oldProperty; 738 } 739 } else { 740 return newProperty; 741 } 742 } 743 } 744 745 private void erasePropertyValue(final Property property) { 746 // Erase the property field value with undefined. If the property is defined 747 // by user-defined accessors, we don't want to call the setter!! 748 if (!(property instanceof UserAccessorProperty)) { 749 try { 750 // make the property value to be undefined 751 //TODO specproperties 752 property.getSetter(Object.class, getMap()).invokeExact((Object)this, (Object)UNDEFINED); 753 } catch (final RuntimeException | Error e) { 754 throw e; 755 } catch (final Throwable t) { 756 throw new RuntimeException(t); 757 } 758 } 759 } 760 761 /** 762 * Delete a property from the object. 763 * 764 * @param property Property to delete. 765 * 766 * @return true if deleted. 767 */ 768 public final boolean deleteOwnProperty(final Property property) { 769 erasePropertyValue(property); 770 PropertyMap oldMap = getMap(); 771 772 while (true) { 773 final PropertyMap newMap = oldMap.deleteProperty(property); 774 775 if (newMap == null) { 776 return false; 777 } 778 779 if (!compareAndSetMap(oldMap, newMap)) { 780 oldMap = getMap(); 781 } else { 782 // delete getter and setter function references so that we don't leak 783 if (property instanceof UserAccessorProperty) { 784 final UserAccessorProperty uc = (UserAccessorProperty) property; 785 setEmbedOrSpill(uc.getGetterSlot(), null); 786 setEmbedOrSpill(uc.getSetterSlot(), null); 787 } 788 return true; 789 } 790 } 791 } 792 793 /** 794 * Modify a property in the object 795 * 796 * @param oldProperty property to modify 797 * @param propertyFlags new property flags 798 * @param getter getter for {@link UserAccessorProperty}, null if not present or N/A 799 * @param setter setter for {@link UserAccessorProperty}, null if not present or N/A 800 * 801 * @return new property 802 */ 803 public final Property modifyOwnProperty(final Property oldProperty, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) { 804 Property newProperty; 805 if (oldProperty instanceof UserAccessorProperty) { 806 // re-use the slots of the old user accessor property. 807 final UserAccessorProperty uc = (UserAccessorProperty) oldProperty; 808 809 int getterSlot = uc.getGetterSlot(); 810 // clear the old getter and set the new getter 811 setEmbedOrSpill(getterSlot, getter); 812 // if getter function is null, flag the slot to be negative (less by 1) 813 if (getter == null) { 814 getterSlot = -getterSlot - 1; 815 } 816 817 int setterSlot = uc.getSetterSlot(); 818 // clear the old setter and set the new setter 819 setEmbedOrSpill(setterSlot, setter); 820 // if setter function is null, flag the slot to be negative (less by 1) 821 if (setter == null) { 822 setterSlot = -setterSlot - 1; 823 } 824 825 newProperty = new UserAccessorProperty(oldProperty.getKey(), propertyFlags, getterSlot, setterSlot); 826 // if just flipping getter and setter with new functions, no need to change property or map 827 if (oldProperty.equals(newProperty)) { 828 return oldProperty; 829 } 830 } else { 831 // erase old property value and create new user accessor property 832 erasePropertyValue(oldProperty); 833 newProperty = newUserAccessors(oldProperty.getKey(), propertyFlags, getter, setter); 834 } 835 836 notifyPropertyModified(this, oldProperty, newProperty); 837 838 return modifyOwnProperty(oldProperty, newProperty); 839 } 840 841 /** 842 * Modify a property in the object 843 * 844 * @param oldProperty property to modify 845 * @param propertyFlags new property flags 846 * 847 * @return new property 848 */ 849 public final Property modifyOwnProperty(final Property oldProperty, final int propertyFlags) { 850 return modifyOwnProperty(oldProperty, oldProperty.setFlags(propertyFlags)); 851 } 852 853 /** 854 * Modify a property in the object, replacing a property with a new one 855 * 856 * @param oldProperty property to replace 857 * @param newProperty property to replace it with 858 * 859 * @return new property 860 */ 861 private Property modifyOwnProperty(final Property oldProperty, final Property newProperty) { 862 assert newProperty.getKey().equals(oldProperty.getKey()) : "replacing property with different key"; 863 864 PropertyMap oldMap = getMap(); 865 866 while (true) { 867 final PropertyMap newMap = oldMap.replaceProperty(oldProperty, newProperty); 868 869 if (!compareAndSetMap(oldMap, newMap)) { 870 oldMap = getMap(); 871 final Property oldPropertyLookup = oldMap.findProperty(oldProperty.getKey()); 872 873 if (oldPropertyLookup != null && oldPropertyLookup.equals(newProperty)) { 874 return oldPropertyLookup; 875 } 876 } else { 877 return newProperty; 878 } 879 } 880 } 881 882 /** 883 * Update getter and setter in an object literal. 884 * 885 * @param key Property key. 886 * @param getter {@link UserAccessorProperty} defined getter, or null if none 887 * @param setter {@link UserAccessorProperty} defined setter, or null if none 888 */ 889 public final void setUserAccessors(final String key, final ScriptFunction getter, final ScriptFunction setter) { 890 final Property oldProperty = getMap().findProperty(key); 891 if (oldProperty != null) { 892 final UserAccessorProperty newProperty = newUserAccessors(oldProperty.getKey(), oldProperty.getFlags(), getter, setter); 893 modifyOwnProperty(oldProperty, newProperty); 894 } else { 895 final UserAccessorProperty newProperty = newUserAccessors(key, 0, getter, setter); 896 addOwnProperty(newProperty); 897 } 898 } 899 900 private static int getIntValue(final FindProperty find) { 901 final MethodHandle getter = find.getGetter(int.class); 902 if (getter != null) { 903 try { 904 return (int)getter.invokeExact((Object)find.getOwner()); 905 } catch (final Error|RuntimeException e) { 906 throw e; 907 } catch (final Throwable e) { 908 throw new RuntimeException(e); 909 } 910 } 911 912 return ObjectClassGenerator.UNDEFINED_INT; 913 } 914 915 private static long getLongValue(final FindProperty find) { 916 final MethodHandle getter = find.getGetter(long.class); 917 if (getter != null) { 918 try { 919 return (long)getter.invokeExact((Object)find.getOwner()); 920 } catch (final Error|RuntimeException e) { 921 throw e; 922 } catch (final Throwable e) { 923 throw new RuntimeException(e); 924 } 925 } 926 927 return ObjectClassGenerator.UNDEFINED_LONG; 928 } 929 930 private static double getDoubleValue(final FindProperty find) { 931 final MethodHandle getter = find.getGetter(double.class); 932 if (getter != null) { 933 try { 934 return (double)getter.invokeExact((Object)find.getOwner()); 935 } catch (final Error|RuntimeException e) { 936 throw e; 937 } catch (final Throwable e) { 938 throw new RuntimeException(e); 939 } 940 } 941 942 return ObjectClassGenerator.UNDEFINED_DOUBLE; 943 } 944 945 /** 946 * Get the object value of a property 947 * 948 * @param find {@link FindProperty} lookup result 949 * 950 * @return the value of the property 951 */ 952 protected static Object getObjectValue(final FindProperty find) { 953 final MethodHandle getter = find.getGetter(Object.class); 954 if (getter != null) { 955 try { 956 return getter.invokeExact((Object)find.getOwner()); 957 } catch (final Error|RuntimeException e) { 958 throw e; 959 } catch (final Throwable e) { 960 throw new RuntimeException(e); 961 } 962 } 963 964 return UNDEFINED; 965 } 966 967 /** 968 * Return methodHandle of value function for call. 969 * 970 * @param find data from find property. 971 * @param type method type of function. 972 * @param bindName null or name to bind to second argument (property not found method.) 973 * 974 * @return value of property as a MethodHandle or null. 975 * 976 */ 977 @SuppressWarnings("static-method") 978 protected MethodHandle getCallMethodHandle(final FindProperty find, final MethodType type, final String bindName) { 979 return getCallMethodHandle(getObjectValue(find), type, bindName); 980 } 981 982 /** 983 * Return methodHandle of value function for call. 984 * 985 * @param value value of receiver, it not a {@link ScriptFunction} this will return null. 986 * @param type method type of function. 987 * @param bindName null or name to bind to second argument (property not found method.) 988 * 989 * @return value of property as a MethodHandle or null. 990 */ 991 protected static MethodHandle getCallMethodHandle(final Object value, final MethodType type, final String bindName) { 992 return value instanceof ScriptFunction ? ((ScriptFunction)value).getCallMethodHandle(type, bindName) : null; 993 } 994 995 /** 996 * Get value using found property. 997 * 998 * @param property Found property. 999 * 1000 * @return Value of property. 1001 */ 1002 public final Object getWithProperty(final Property property) { 1003 return getObjectValue(new FindProperty(this, this, getMap(), property, 0)); 1004 } 1005 1006 /** 1007 * Get a property given a key 1008 * 1009 * @param key property key 1010 * 1011 * @return property for key 1012 */ 1013 public final Property getProperty(final String key) { 1014 return getMap().findProperty(key); 1015 } 1016 1017 static String convertKey(final Object key) { 1018 return (key instanceof String) ? (String)key : JSType.toString(key); 1019 } 1020 1021 /** 1022 * Overridden by {@link jdk.nashorn.internal.objects.NativeArguments} class (internal use.) 1023 * Used for argument access in a vararg function using parameter name. 1024 * Returns the argument at a given key (index) 1025 * 1026 * @param key argument index 1027 * 1028 * @return the argument at the given position, or undefined if not present 1029 */ 1030 public Object getArgument(final int key) { 1031 return get(key); 1032 } 1033 1034 /** 1035 * Overridden by {@link jdk.nashorn.internal.objects.NativeArguments} class (internal use.) 1036 * Used for argument access in a vararg function using parameter name. 1037 * Returns the argument at a given key (index) 1038 * 1039 * @param key argument index 1040 * @param value the value to write at the given index 1041 */ 1042 public void setArgument(final int key, final Object value) { 1043 set(key, value, getContext()._strict); 1044 } 1045 1046 public final boolean isStrictContext() { 1047 return getContext()._strict; 1048 } 1049 1050 /** 1051 * Return the current context from the object's map. 1052 * @return Current context. 1053 */ 1054 protected final Context getContext() { 1055 return getMap().getContext(); 1056 } 1057 1058 /** 1059 * Return the map of an object. 1060 * @return PropertyMap object. 1061 */ 1062 public final PropertyMap getMap() { 1063 return map; 1064 } 1065 1066 /** 1067 * Set the initial map. 1068 * @param map Initial map. 1069 */ 1070 public final void setMap(final PropertyMap map) { 1071 this.map = map; 1072 } 1073 1074 /** 1075 * Conditionally set the new map if the old map is the same. 1076 * @param oldMap Map prior to manipulation. 1077 * @param newMap Replacement map. 1078 * @return true if the operation succeeded. 1079 */ 1080 protected synchronized final boolean compareAndSetMap(final PropertyMap oldMap, final PropertyMap newMap) { 1081 final boolean update = oldMap == this.map; 1082 1083 if (update) { 1084 this.map = newMap; 1085 } 1086 1087 return update; 1088 } 1089 1090 /** 1091 * Return the __proto__ of an object. 1092 * @return __proto__ object. 1093 */ 1094 public final ScriptObject getProto() { 1095 return getMap().getProto(); 1096 } 1097 1098 /** 1099 * Check if this is a prototype 1100 * @return true if {@link PropertyMap#isPrototype()} is true for this ScriptObject 1101 */ 1102 public final boolean isPrototype() { 1103 return getMap().isPrototype(); 1104 } 1105 1106 /** 1107 * Set the __proto__ of an object. 1108 * @param newProto new __proto__ to set. 1109 */ 1110 public final void setProto(final ScriptObject newProto) { 1111 PropertyMap oldMap = getMap(); 1112 ScriptObject oldProto = getProto(); 1113 1114 while (oldProto != newProto) { 1115 final PropertyMap newMap = oldMap.setProto(newProto); 1116 1117 if (!compareAndSetMap(oldMap, newMap)) { 1118 oldMap = getMap(); 1119 oldProto = getProto(); 1120 } else { 1121 if (isPrototype()) { 1122 1123 if (oldProto != null) { 1124 oldProto.removePropertyListener(this); 1125 } 1126 1127 if (newProto != null) { 1128 newProto.addPropertyListener(this); 1129 } 1130 } 1131 1132 return; 1133 } 1134 } 1135 } 1136 1137 /** 1138 * Set the __proto__ of an object with checks. 1139 * @param newProto Prototype to set. 1140 */ 1141 public final void setProtoCheck(final Object newProto) { 1142 if (newProto == null || newProto instanceof ScriptObject) { 1143 setProto((ScriptObject)newProto); 1144 } else { 1145 final ScriptObject global = Context.getGlobalTrusted(); 1146 final Object newProtoObject = JSType.toScriptObject(global, newProto); 1147 1148 if (newProtoObject instanceof ScriptObject) { 1149 setProto((ScriptObject)newProtoObject); 1150 } else { 1151 typeError(global, "cant.set.proto.to.non.object", ScriptRuntime.safeToString(this), ScriptRuntime.safeToString(newProto)); 1152 } 1153 } 1154 } 1155 1156 /** 1157 * return a List of own keys associated with the object. 1158 * @param all True if to include non-enumerable keys. 1159 * @return Array of keys. 1160 */ 1161 public String[] getOwnKeys(final boolean all) { 1162 final List<Object> keys = new ArrayList<>(); 1163 final PropertyMap selfMap = this.getMap(); 1164 1165 final ArrayData array = getArray(); 1166 final long length = array.length(); 1167 1168 for (long i = 0; i < length; i = array.nextIndex(i)) { 1169 if (array.has((int)i)) { 1170 keys.add(JSType.toString(i)); 1171 } 1172 } 1173 1174 for (final Property property : selfMap.getProperties()) { 1175 if (all || property.isEnumerable()) { 1176 keys.add(property.getKey()); 1177 } 1178 } 1179 1180 return keys.toArray(new String[keys.size()]); 1181 } 1182 1183 /** 1184 * Check if this ScriptObject has array entries. This means that someone has 1185 * set values with numeric keys in the object. 1186 * 1187 * Note: this can be O(n) up to the array length 1188 * 1189 * @return true if array entries exists. 1190 */ 1191 public boolean hasArrayEntries() { 1192 final ArrayData array = getArray(); 1193 final long length = array.length(); 1194 1195 for (long i = 0; i < length; i++) { 1196 if (array.has((int)i)) { 1197 return true; 1198 } 1199 } 1200 1201 return false; 1202 } 1203 1204 /** 1205 * Return the valid JavaScript type name descriptor 1206 * 1207 * @return "Object" 1208 */ 1209 public String getClassName() { 1210 return "Object"; 1211 } 1212 1213 /** 1214 * {@code length} is a well known property. This is its getter. 1215 * Note that this *may* be optimized by other classes 1216 * 1217 * @return length property value for this ScriptObject 1218 */ 1219 public Object getLength() { 1220 return get("length"); 1221 } 1222 1223 /** 1224 * Stateless toString for ScriptObjects. 1225 * 1226 * @return string description of this object, e.g. {@code [object Object]} 1227 */ 1228 public String safeToString() { 1229 return "[object " + getClassName() + "]"; 1230 } 1231 1232 /** 1233 * Return the default value of the object with a given preferred type hint. 1234 * The preferred type hints are String.class for type String, Number.class 1235 * for type Number. <p> 1236 * 1237 * A <code>hint</code> of null means "no hint". 1238 * 1239 * ECMA 8.12.8 [[DefaultValue]](hint) 1240 * 1241 * @param typeHint the preferred type hint 1242 * @return the default value 1243 */ 1244 public Object getDefaultValue(final Class<?> typeHint) { 1245 // We delegate to GlobalObject, as the implementation uses dynamic call sites to invoke object's "toString" and 1246 // "valueOf" methods, and in order to avoid those call sites from becoming megamorphic when multiple contexts 1247 // are being executed in a long-running program, we move the code and their associated dynamic call sites 1248 // (Global.TO_STRING and Global.VALUE_OF) into per-context code. 1249 return ((GlobalObject)Context.getGlobalTrusted()).getDefaultValue(this, typeHint); 1250 } 1251 1252 /** 1253 * Checking whether a script object is an instance of another. Used 1254 * in {@link ScriptFunction} for hasInstance implementation, walks 1255 * the proto chain 1256 * 1257 * @param instance instace to check 1258 * @return true if instance of instance 1259 */ 1260 public boolean isInstance(final ScriptObject instance) { 1261 return false; 1262 } 1263 1264 /** 1265 * Flag this ScriptObject as non extensible 1266 * 1267 * @return the object after being made non extensible 1268 */ 1269 public ScriptObject preventExtensions() { 1270 PropertyMap oldMap = getMap(); 1271 1272 while (true) { 1273 final PropertyMap newMap = getMap().preventExtensions(); 1274 1275 if (!compareAndSetMap(oldMap, newMap)) { 1276 oldMap = getMap(); 1277 } else { 1278 return this; 1279 } 1280 } 1281 } 1282 1283 /** 1284 * Check whether if an Object (not just a ScriptObject) represents JavaScript array 1285 * 1286 * @param obj object to check 1287 * 1288 * @return true if array 1289 */ 1290 public static boolean isArray(final Object obj) { 1291 return (obj instanceof ScriptObject) && ((ScriptObject)obj).isArray(); 1292 } 1293 1294 /** 1295 * Check if this ScriptObject is an array 1296 * @return true if array 1297 */ 1298 public final boolean isArray() { 1299 return (flags & IS_ARRAY) != 0; 1300 } 1301 1302 /** 1303 * Flag this ScriptObject as being an array 1304 */ 1305 public final void setIsArray() { 1306 flags |= IS_ARRAY; 1307 } 1308 1309 /** 1310 * Check if this ScriptObject is an {@code arguments} vector 1311 * @return true if arguments vector 1312 */ 1313 public final boolean isArguments() { 1314 return (flags & IS_ARGUMENTS) != 0; 1315 } 1316 1317 /** 1318 * Flag this ScriptObject as being an {@code arguments} vector 1319 */ 1320 public final void setIsArguments() { 1321 flags |= IS_ARGUMENTS; 1322 } 1323 1324 /** 1325 * Get the {@link ArrayData} for this ScriptObject if it is an array 1326 * @return array data 1327 */ 1328 public final ArrayData getArray() { 1329 return arrayData; 1330 } 1331 1332 /** 1333 * Set the {@link ArrayData} for this ScriptObject if it is to be an array 1334 * @param arrayData the array data 1335 */ 1336 public final void setArray(final ArrayData arrayData) { 1337 this.arrayData = arrayData; 1338 } 1339 1340 /** 1341 * Check if this ScriptObject is extensible 1342 * @return true if extensible 1343 */ 1344 public boolean isExtensible() { 1345 return getMap().isExtensible(); 1346 } 1347 1348 /** 1349 * ECMAScript 15.2.3.8 - seal implementation 1350 * @return the sealed ScriptObject 1351 */ 1352 public ScriptObject seal() { 1353 PropertyMap oldMap = getMap(); 1354 1355 while (true) { 1356 final PropertyMap newMap = getMap().seal(); 1357 1358 if (!compareAndSetMap(oldMap, newMap)) { 1359 oldMap = getMap(); 1360 } else { 1361 setArray(ArrayData.seal(getArray())); 1362 return this; 1363 } 1364 } 1365 } 1366 1367 /** 1368 * Check whether this ScriptObject is sealed 1369 * @return true if sealed 1370 */ 1371 public boolean isSealed() { 1372 return getMap().isSealed(); 1373 } 1374 1375 /** 1376 * ECMA 15.2.39 - freeze implementation. Freeze this ScriptObject 1377 * @return the frozen ScriptObject 1378 */ 1379 public ScriptObject freeze() { 1380 PropertyMap oldMap = getMap(); 1381 1382 while (true) { 1383 final PropertyMap newMap = getMap().freeze(); 1384 1385 if (!compareAndSetMap(oldMap, newMap)) { 1386 oldMap = getMap(); 1387 } else { 1388 setArray(ArrayData.freeze(getArray())); 1389 return this; 1390 } 1391 } 1392 } 1393 1394 /** 1395 * Check whether this ScriptObject is frozen 1396 * @return true if frozed 1397 */ 1398 public boolean isFrozen() { 1399 return getMap().isFrozen(); 1400 } 1401 1402 1403 /** 1404 * Flag this ScriptObject as scope 1405 */ 1406 public final void setIsScope() { 1407 if (Context.DEBUG) { 1408 scopeCount++; 1409 } 1410 flags |= IS_SCOPE; 1411 } 1412 1413 /** 1414 * Check whether this ScriptObject is scope 1415 * @return true if scope 1416 */ 1417 public final boolean isScope() { 1418 return (flags & IS_SCOPE) != 0; 1419 } 1420 1421 // java.util.Map-like methods to help ScriptObjectMirror implementation 1422 public void clear() { 1423 final boolean strict = getContext()._strict; 1424 final Iterator<String> iter = propertyIterator(); 1425 while (iter.hasNext()) { 1426 delete(iter.next(), strict); 1427 } 1428 } 1429 1430 public boolean containsKey(final Object key) { 1431 return has(key); 1432 } 1433 1434 public boolean containsValue(final Object value) { 1435 final Iterator<Object> iter = valueIterator(); 1436 while (iter.hasNext()) { 1437 if (iter.next().equals(value)) { 1438 return true; 1439 } 1440 } 1441 return false; 1442 } 1443 1444 public Set<Map.Entry<Object, Object>> entrySet() { 1445 final Iterator<String> iter = propertyIterator(); 1446 final Set<Map.Entry<Object, Object>> entries = new HashSet<>(); 1447 while (iter.hasNext()) { 1448 final Object key = iter.next(); 1449 entries.add(new AbstractMap.SimpleImmutableEntry<>(key, get(key))); 1450 } 1451 return Collections.unmodifiableSet(entries); 1452 } 1453 1454 public boolean isEmpty() { 1455 return !propertyIterator().hasNext(); 1456 } 1457 1458 public Set<Object> keySet() { 1459 final Iterator<String> iter = propertyIterator(); 1460 final Set<Object> keySet = new HashSet<>(); 1461 while (iter.hasNext()) { 1462 keySet.add(iter.next()); 1463 } 1464 return Collections.unmodifiableSet(keySet); 1465 } 1466 1467 public Object put(final Object key, final Object value) { 1468 final Object oldValue = get(key); 1469 set(key, value, getContext()._strict); 1470 return oldValue; 1471 } 1472 1473 public void putAll(final Map<?, ?> otherMap) { 1474 final boolean strict = getContext()._strict; 1475 for (final Map.Entry<?, ?> entry : otherMap.entrySet()) { 1476 set(entry.getKey(), entry.getValue(), strict); 1477 } 1478 } 1479 1480 public Object remove(final Object key) { 1481 final Object oldValue = get(key); 1482 delete(key, getContext()._strict); 1483 return oldValue; 1484 } 1485 1486 public int size() { 1487 int n = 0; 1488 for (final Iterator<String> iter = propertyIterator(); iter.hasNext(); iter.next()) { 1489 n++; 1490 } 1491 return n; 1492 } 1493 1494 public Collection<Object> values() { 1495 final List<Object> values = new ArrayList<>(size()); 1496 final Iterator<Object> iter = valueIterator(); 1497 while (iter.hasNext()) { 1498 values.add(iter.next()); 1499 } 1500 return Collections.unmodifiableList(values); 1501 } 1502 1503 /** 1504 * Lookup method that, given a CallSiteDescriptor, looks up the target 1505 * MethodHandle and creates a GuardedInvocation 1506 * with the appropriate guard(s). 1507 * 1508 * @param desc call site descriptor 1509 * 1510 * @return GuardedInvocation for the callsite 1511 */ 1512 public final GuardedInvocation lookup(final CallSiteDescriptor desc) { 1513 return lookup(desc, false); 1514 } 1515 1516 /** 1517 * Lookup the appropriate method for an invoke dynamic call. 1518 * 1519 * @param desc The descriptor of the call site. 1520 * @param megaMorphic if the call site is considered megamorphic 1521 * 1522 * @return GuardedInvocation to be invoked at call site. 1523 */ 1524 public GuardedInvocation lookup(final CallSiteDescriptor desc, final boolean megaMorphic) { 1525 final int c = desc.getNameTokenCount(); 1526 // JavaScript is "immune" to all currently defined Dynalink composite operation - getProp is the same as getElem 1527 // is the same as getMethod as JavaScript objects have a single namespace for all three. Therefore, we don't 1528 // care about them, and just link to whatever is the first operation. 1529 final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0); 1530 // NOTE: we support getElem and setItem as JavaScript doesn't distinguish items from properties. Nashorn itself 1531 // emits "dyn:getProp:identifier" for "<expr>.<identifier>" and "dyn:getElem" for "<expr>[<expr>]", but we are 1532 // more flexible here and dispatch not on operation name (getProp vs. getElem), but rather on whether the 1533 // operation has an associated name or not. 1534 switch (operator) { 1535 case "getProp": 1536 case "getElem": 1537 case "getMethod": 1538 return c > 2 ? findGetMethod(desc, megaMorphic, operator) : findGetIndexMethod(desc); 1539 case "setProp": 1540 case "setElem": 1541 return c > 2 ? findSetMethod(desc, megaMorphic) : findSetIndexMethod(desc); 1542 case "call": 1543 return findCallMethod(desc, megaMorphic); 1544 case "new": 1545 return findNewMethod(desc); 1546 case "callMethod": 1547 return findCallMethodMethod(desc, megaMorphic); 1548 default: 1549 return null; 1550 } 1551 } 1552 1553 /** 1554 * Find the appropriate New method for an invoke dynamic call. 1555 * 1556 * @param desc The invoke dynamic call site descriptor. 1557 * 1558 * @return GuardedInvocation to be invoked at call site. 1559 */ 1560 protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc) { 1561 return notAFunction(); 1562 } 1563 1564 /** 1565 * Find the appropriate CALL method for an invoke dynamic call. 1566 * This generates "not a function" always 1567 * 1568 * @param desc the call site descriptor. 1569 * @param megaMorphic is this call site megaMorphic, as reported by Dynalink - then just do apply 1570 * 1571 * @return GuardedInvocation to be invoed at call site. 1572 */ 1573 protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final boolean megaMorphic) { 1574 return notAFunction(); 1575 } 1576 1577 private GuardedInvocation notAFunction() { 1578 typeError("not.a.function", ScriptRuntime.safeToString(this)); 1579 return null; 1580 } 1581 1582 /** 1583 * Find an implementation for a "dyn:callMethod" operation. Note that Nashorn internally never uses 1584 * "dyn:callMethod", but instead always emits two call sites in bytecode, one for "dyn:getMethod", and then another 1585 * one for "dyn:call". Explicit support for "dyn:callMethod" is provided for the benefit of potential external 1586 * callers. The implementation itself actually folds a "dyn:getMethod" method handle into a "dyn:call" method handle. 1587 * 1588 * @param desc The call site descriptor. 1589 * @param megaMorphic is this call site megaMorphic, as reported by Dynalink - then just do apply 1590 * 1591 * @return GuardedInvocation to be invoked at call site. 1592 */ 1593 protected GuardedInvocation findCallMethodMethod(final CallSiteDescriptor desc, final boolean megaMorphic) { 1594 // R(P0, P1, ...) 1595 final MethodType callType = desc.getMethodType(); 1596 // use type Object(P0) for the getter 1597 final CallSiteDescriptor getterType = desc.changeMethodType(MethodType.methodType(Object.class, callType.parameterType(0))); 1598 final GuardedInvocation getter = findGetMethod(getterType, megaMorphic, "getMethod"); 1599 1600 // Object(P0) => Object(P0, P1, ...) 1601 final MethodHandle argDroppingGetter = MH.dropArguments(getter.getInvocation(), 1, callType.parameterList().subList(1, callType.parameterCount())); 1602 // R(Object, P0, P1, ...) 1603 final MethodHandle invoker = Bootstrap.createDynamicInvoker("dyn:call", callType.insertParameterTypes(0, argDroppingGetter.type().returnType())); 1604 // Fold Object(P0, P1, ...) into R(Object, P0, P1, ...) => R(P0, P1, ...) 1605 return getter.replaceMethods(MH.foldArguments(invoker, argDroppingGetter), getter.getGuard()); 1606 } 1607 1608 /** 1609 * Find the appropriate GET method for an invoke dynamic call. 1610 * 1611 * @param desc the call site descriptor 1612 * @param megaMorphic is this call site megaMorphic, as reported by Dynalink - then just do apply 1613 * @param operator operator for get: getProp, getMethod, getElem etc 1614 * 1615 * @return GuardedInvocation to be invoked at call site. 1616 */ 1617 protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final boolean megaMorphic, final String operator) { 1618 final String name = desc.getNameToken(2); 1619 1620 if (megaMorphic) { 1621 return findMegaMorphicGetMethod(desc, name); 1622 } 1623 1624 final FindProperty find = findProperty(name, true); 1625 1626 MethodHandle methodHandle; 1627 1628 if (find == null) { 1629 if ("getProp".equals(operator)) { 1630 return noSuchProperty(desc); 1631 } else if ("getMethod".equals(operator)) { 1632 return noSuchMethod(desc); 1633 } else if ("getElem".equals(operator)) { 1634 return createEmptyGetter(desc, name); 1635 } 1636 throw new AssertionError(); // never invoked with any other operation 1637 } 1638 1639 final Class<?> returnType = desc.getMethodType().returnType(); 1640 final Property property = find.getProperty(); 1641 methodHandle = find.getGetter(returnType); 1642 1643 // getMap() is fine as we have the prototype switchpoint depending on where the property was found 1644 final MethodHandle guard = NashornGuards.getMapGuard(getMap()); 1645 1646 if (methodHandle != null) { 1647 assert methodHandle.type().returnType().equals(returnType); 1648 final ScriptFunction getter = find.getGetterFunction(); 1649 final boolean nonStrict = getter != null && getter.isNonStrictFunction(); 1650 if (find.isSelf()) { 1651 return new NashornGuardedInvocation(methodHandle, null, ObjectClassGenerator.OBJECT_FIELDS_ONLY && 1652 NashornCallSiteDescriptor.isFastScope(desc) && !property.canChangeType() ? null : guard, 1653 nonStrict); 1654 } 1655 1656 final ScriptObject prototype = find.getOwner(); 1657 1658 if (!property.hasGetterFunction()) { 1659 methodHandle = bindTo(methodHandle, prototype); 1660 } 1661 return new NashornGuardedInvocation(methodHandle, getMap().getProtoGetSwitchPoint(name), guard, nonStrict); 1662 } 1663 1664 assert !NashornCallSiteDescriptor.isFastScope(desc); 1665 return new GuardedInvocation(Lookup.emptyGetter(returnType), getMap().getProtoGetSwitchPoint(name), guard); 1666 } 1667 1668 private static GuardedInvocation findMegaMorphicGetMethod(final CallSiteDescriptor desc, final String name) { 1669 final GuardedInvocation inv = findGetIndexMethod(desc.getMethodType().insertParameterTypes(1, Object.class)); 1670 return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard()); 1671 } 1672 1673 /** 1674 * Find the appropriate GETINDEX method for an invoke dynamic call. 1675 * 1676 * @param desc the call site descriptor 1677 * 1678 * @return GuardedInvocation to be invoked at call site. 1679 */ 1680 private static GuardedInvocation findGetIndexMethod(final CallSiteDescriptor desc) { 1681 return findGetIndexMethod(desc.getMethodType()); 1682 } 1683 1684 /** 1685 * Find the appropriate GETINDEX method for an invoke dynamic call. 1686 * 1687 * @param callType the call site method type 1688 * @return GuardedInvocation to be invoked at call site. 1689 */ 1690 private static GuardedInvocation findGetIndexMethod(final MethodType callType) { 1691 final Class<?> returnClass = callType.returnType(); 1692 final Class<?> keyClass = callType.parameterType(1); 1693 1694 String name = "get"; 1695 if (returnClass.isPrimitive()) { 1696 //turn e.g. get with a double into getDouble 1697 final String returnTypeName = returnClass.getName(); 1698 name += Character.toUpperCase(returnTypeName.charAt(0)) + returnTypeName.substring(1, returnTypeName.length()); 1699 } 1700 1701 return new GuardedInvocation(findOwnMH(name, returnClass, keyClass), getScriptObjectGuard(callType)); 1702 } 1703 1704 private static MethodHandle getScriptObjectGuard(final MethodType type) { 1705 return ScriptObject.class.isAssignableFrom(type.parameterType(0)) ? null : NashornGuards.getScriptObjectGuard(); 1706 } 1707 1708 /** 1709 * Find the appropriate SET method for an invoke dynamic call. 1710 * 1711 * @param desc the call site descriptor 1712 * @param megaMorphic is this call site megaMorphic, as reported by Dynalink - then just do apply 1713 * 1714 * @return GuardedInvocation to be invoked at call site. 1715 */ 1716 protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final boolean megaMorphic) { 1717 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 1718 if(megaMorphic) { 1719 return findMegaMorphicSetMethod(desc, name); 1720 } 1721 1722 final boolean scope = isScope(); 1723 /* 1724 * If doing property set on a scope object, we should stop proto search on the first 1725 * non-scope object. Without this, for exmaple, when assigning "toString" on global scope, 1726 * we'll end up assigning it on it's proto - which is Object.prototype.toString !! 1727 * 1728 * toString = function() { print("global toString"); } // don't affect Object.prototype.toString 1729 */ 1730 FindProperty find = findProperty(name, true, scope); 1731 // If it's not a scope search, then we don't want any inherited properties except those with user defined accessors. 1732 if (!scope && find != null && find.isInherited() && !(find.getProperty() instanceof UserAccessorProperty)) { 1733 // We should still check if inherited data property is not writable 1734 if (isExtensible() && !find.isWritable()) { 1735 return createEmptySetMethod(desc, "property.not.writable", false); 1736 } 1737 // Otherwise, forget the found property 1738 find = null; 1739 } 1740 1741 if (find != null) { 1742 if(!find.isWritable()) { 1743 // Existing, non-writable property 1744 return createEmptySetMethod(desc, "property.not.writable", true); 1745 } 1746 } else if (!isExtensible()) { 1747 // Non-existing property on a non-extensible object 1748 return createEmptySetMethod(desc, "object.non.extensible", false); 1749 } 1750 1751 return new SetMethodCreator(this, find, desc).createGuardedInvocation(); 1752 } 1753 1754 private GuardedInvocation createEmptySetMethod(final CallSiteDescriptor desc, String strictErrorMessage, boolean canBeFastScope) { 1755 final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); 1756 if (NashornCallSiteDescriptor.isStrict(desc)) { 1757 typeError(strictErrorMessage, name, ScriptRuntime.safeToString((this))); 1758 } 1759 assert canBeFastScope || !NashornCallSiteDescriptor.isFastScope(desc); 1760 final PropertyMap myMap = getMap(); 1761 return new GuardedInvocation(Lookup.EMPTY_SETTER, myMap.getProtoGetSwitchPoint(name), NashornGuards.getMapGuard(myMap)); 1762 } 1763 1764 @SuppressWarnings("unused") 1765 private static void setEmbed(final CallSiteDescriptor desc, final PropertyMap oldMap, final PropertyMap newMap, final MethodHandle setter, final int i, final Object self, final Object value) throws Throwable { 1766 final ScriptObject obj = (ScriptObject)self; 1767 if (obj.trySetEmbedOrSpill(desc, oldMap, newMap, value)) { 1768 obj.useEmbed(i); 1769 setter.invokeExact(self, value); 1770 } 1771 } 1772 1773 @SuppressWarnings("unused") 1774 private static void setSpill(final CallSiteDescriptor desc, final PropertyMap oldMap, final PropertyMap newMap, final int index, final Object self, final Object value) { 1775 final ScriptObject obj = (ScriptObject)self; 1776 if (obj.trySetEmbedOrSpill(desc, oldMap, newMap, value)) { 1777 obj.spill[index] = value; 1778 } 1779 } 1780 1781 private boolean trySetEmbedOrSpill(final CallSiteDescriptor desc, final PropertyMap oldMap, final PropertyMap newMap, final Object value) { 1782 final boolean isStrict = NashornCallSiteDescriptor.isStrict(desc); 1783 if (!isExtensible() && isStrict) { 1784 typeError("object.non.extensible", desc.getNameToken(2), ScriptRuntime.safeToString(this)); 1785 throw new AssertionError(); // never reached 1786 } else if (compareAndSetMap(oldMap, newMap)) { 1787 return true; 1788 } else { 1789 set(desc.getNameToken(CallSiteDescriptor.NAME_OPERAND), value, isStrict); 1790 return false; 1791 } 1792 } 1793 1794 @SuppressWarnings("unused") 1795 private static void setSpillWithNew(final CallSiteDescriptor desc, final PropertyMap oldMap, final PropertyMap newMap, final int index, final Object self, final Object value) { 1796 final ScriptObject obj = (ScriptObject)self; 1797 final boolean isStrict = NashornCallSiteDescriptor.isStrict(desc); 1798 1799 if (!obj.isExtensible()) { 1800 if (isStrict) { 1801 typeError("object.non.extensible", desc.getNameToken(2), ScriptRuntime.safeToString(obj)); 1802 } 1803 } else if (obj.compareAndSetMap(oldMap, newMap)) { 1804 obj.spill = new Object[SPILL_RATE]; 1805 obj.spill[index] = value; 1806 } else { 1807 obj.set(desc.getNameToken(2), value, isStrict); 1808 } 1809 } 1810 1811 @SuppressWarnings("unused") 1812 private static void setSpillWithGrow(final CallSiteDescriptor desc, final PropertyMap oldMap, final PropertyMap newMap, final int index, final int newLength, final Object self, final Object value) { 1813 final ScriptObject obj = (ScriptObject)self; 1814 final boolean isStrict = NashornCallSiteDescriptor.isStrict(desc); 1815 1816 if (!obj.isExtensible()) { 1817 if (isStrict) { 1818 typeError("object.non.extensible", desc.getNameToken(2), ScriptRuntime.safeToString(obj)); 1819 } 1820 } else if (obj.compareAndSetMap(oldMap, newMap)) { 1821 final int oldLength = obj.spill.length; 1822 final Object[] newSpill = new Object[newLength]; 1823 System.arraycopy(obj.spill, 0, newSpill, 0, oldLength); 1824 obj.spill = newSpill; 1825 obj.spill[index] = value; 1826 } else { 1827 obj.set(desc.getNameToken(2), value, isStrict); 1828 } 1829 } 1830 1831 private static GuardedInvocation findMegaMorphicSetMethod(final CallSiteDescriptor desc, final String name) { 1832 final GuardedInvocation inv = findSetIndexMethod(desc.getMethodType().insertParameterTypes(1, Object.class), 1833 NashornCallSiteDescriptor.isStrict(desc)); 1834 return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard()); 1835 } 1836 1837 private static GuardedInvocation findSetIndexMethod(final CallSiteDescriptor desc) { // array, index, value 1838 return findSetIndexMethod(desc.getMethodType(), NashornCallSiteDescriptor.isStrict(desc)); 1839 } 1840 1841 /** 1842 * Find the appropriate SETINDEX method for an invoke dynamic call. 1843 * 1844 * @param callType the method type at the call site 1845 * @param isStrict are we in strict mode? 1846 * 1847 * @return GuardedInvocation to be invoked at call site. 1848 */ 1849 private static GuardedInvocation findSetIndexMethod(final MethodType callType, final boolean isStrict) { 1850 assert callType.parameterCount() == 3; 1851 1852 final Class<?> keyClass = callType.parameterType(1); 1853 final Class<?> valueClass = callType.parameterType(2); 1854 1855 MethodHandle methodHandle = findOwnMH("set", void.class, keyClass, valueClass, boolean.class); 1856 methodHandle = MH.insertArguments(methodHandle, 3, isStrict); 1857 1858 return new GuardedInvocation(methodHandle, getScriptObjectGuard(callType)); 1859 } 1860 1861 /** 1862 * Fall back if a function property is not found. 1863 * @param desc The call site descriptor 1864 * @return GuardedInvocation to be invoked at call site. 1865 */ 1866 public GuardedInvocation noSuchMethod(final CallSiteDescriptor desc) { 1867 final String name = desc.getNameToken(2); 1868 final FindProperty find = findProperty(NO_SUCH_METHOD_NAME, true); 1869 final boolean scopeCall = isScope() && NashornCallSiteDescriptor.isScope(desc); 1870 1871 if (find == null) { 1872 if (scopeCall) { 1873 ECMAErrors.referenceError("not.defined", name); 1874 throw new AssertionError(); // never reached 1875 } 1876 return createEmptyGetter(desc, name); 1877 } 1878 1879 final ScriptFunction func = (ScriptFunction)getObjectValue(find); 1880 final Object thiz = scopeCall && func.isStrict() ? ScriptRuntime.UNDEFINED : this; 1881 // TODO: It'd be awesome if we could bind "name" without binding "this". 1882 return new GuardedInvocation(MH.dropArguments(MH.constant(ScriptFunction.class, 1883 func.makeBoundFunction(thiz, new Object[] { name })), 0, Object.class), 1884 null, NashornGuards.getMapGuard(getMap())); 1885 } 1886 1887 /** 1888 * Fall back if a property is not found. 1889 * @param desc the call site descriptor. 1890 * @return GuardedInvocation to be invoked at call site. 1891 */ 1892 public GuardedInvocation noSuchProperty(final CallSiteDescriptor desc) { 1893 final String name = desc.getNameToken(2); 1894 final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); 1895 final boolean scopeAccess = isScope() && NashornCallSiteDescriptor.isScope(desc); 1896 1897 if (find != null) { 1898 final ScriptFunction func = (ScriptFunction)getObjectValue(find); 1899 MethodHandle methodHandle = getCallMethodHandle(func, desc.getMethodType(), name); 1900 1901 if (methodHandle != null) { 1902 if (scopeAccess && func.isStrict()) { 1903 methodHandle = bindTo(methodHandle, UNDEFINED); 1904 } 1905 return new GuardedInvocation(methodHandle, 1906 find.isInherited()? getMap().getProtoGetSwitchPoint(NO_SUCH_PROPERTY_NAME) : null, 1907 getKnownFunctionPropertyGuard(getMap(), find.getGetter(Object.class), find.getOwner(), func)); 1908 } 1909 } 1910 1911 if (scopeAccess) { 1912 referenceError("not.defined", name); 1913 } 1914 1915 return createEmptyGetter(desc, name); 1916 } 1917 1918 private GuardedInvocation createEmptyGetter(final CallSiteDescriptor desc, final String name) { 1919 return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), getMap().getProtoGetSwitchPoint(name), NashornGuards.getMapGuard(getMap())); 1920 } 1921 1922 private abstract static class ScriptObjectIterator <T extends Object> implements Iterator<T> { 1923 protected T[] values; 1924 protected final ScriptObject object; 1925 private int index; 1926 1927 ScriptObjectIterator(final ScriptObject object) { 1928 this.object = object; 1929 } 1930 1931 protected abstract void init(); 1932 1933 @Override 1934 public boolean hasNext() { 1935 if (values == null) { 1936 init(); 1937 } 1938 return index < values.length; 1939 } 1940 1941 @Override 1942 public T next() { 1943 if (values == null) { 1944 init(); 1945 } 1946 return values[index++]; 1947 } 1948 1949 @Override 1950 public void remove() { 1951 throw new UnsupportedOperationException(); 1952 } 1953 } 1954 1955 private static class KeyIterator extends ScriptObjectIterator<String> { 1956 KeyIterator(final ScriptObject object) { 1957 super(object); 1958 } 1959 1960 @Override 1961 protected void init() { 1962 final Set<String> keys = new LinkedHashSet<>(); 1963 for (ScriptObject self = object; self != null; self = self.getProto()) { 1964 keys.addAll(Arrays.asList(self.getOwnKeys(false))); 1965 } 1966 this.values = keys.toArray(new String[keys.size()]); 1967 } 1968 } 1969 1970 private static class ValueIterator extends ScriptObjectIterator<Object> { 1971 ValueIterator(final ScriptObject object) { 1972 super(object); 1973 } 1974 1975 @Override 1976 protected void init() { 1977 final ArrayList<Object> valueList = new ArrayList<>(); 1978 for (ScriptObject self = object; self != null; self = self.getProto()) { 1979 for (final String key : self.getOwnKeys(false)) { 1980 valueList.add(self.get(key)); 1981 } 1982 } 1983 this.values = valueList.toArray(new Object[valueList.size()]); 1984 } 1985 } 1986 1987 /** 1988 * Add a spill property for the given key. 1989 * @param key Property key. 1990 * @param propertyFlags Property flags. 1991 * @return Added property. 1992 */ 1993 private Property addSpillProperty(final String key, final int propertyFlags) { 1994 int i = findEmbed(); 1995 Property spillProperty; 1996 1997 if (i >= EMBED_SIZE) { 1998 i = getMap().getSpillLength(); 1999 MethodHandle getter = MH.arrayElementGetter(Object[].class); 2000 MethodHandle setter = MH.arrayElementSetter(Object[].class); 2001 getter = MH.asType(MH.insertArguments(getter, 1, i), Lookup.GET_OBJECT_TYPE); 2002 setter = MH.asType(MH.insertArguments(setter, 1, i), Lookup.SET_OBJECT_TYPE); 2003 spillProperty = new SpillProperty(key, propertyFlags | Property.IS_SPILL, i, getter, setter); 2004 notifyPropertyAdded(this, spillProperty); 2005 spillProperty = addOwnProperty(spillProperty); 2006 i = spillProperty.getSlot(); 2007 2008 final int newLength = (i + SPILL_RATE) / SPILL_RATE * SPILL_RATE; 2009 final Object[] newSpill = new Object[newLength]; 2010 2011 if (spill != null) { 2012 System.arraycopy(spill, 0, newSpill, 0, spill.length); 2013 } 2014 2015 spill = newSpill; 2016 } else { 2017 useEmbed(i); 2018 spillProperty = new SpillProperty(key, propertyFlags, i, GET_EMBED[i], SET_EMBED[i]); 2019 notifyPropertyAdded(this, spillProperty); 2020 spillProperty = addOwnProperty(spillProperty); 2021 } 2022 2023 return spillProperty; 2024 } 2025 2026 2027 /** 2028 * Add a spill entry for the given key. 2029 * @param key Property key. 2030 * @param propertyFlags Property flags. 2031 * @return Setter method handle. 2032 */ 2033 private MethodHandle addSpill(final String key, final int propertyFlags) { 2034 final Property spillProperty = addSpillProperty(key, propertyFlags); 2035 final Class<?> type = Object.class; 2036 return spillProperty.getSetter(type, getMap()); //TODO specfields 2037 } 2038 2039 MethodHandle addSpill(final String key) { 2040 return addSpill(key, 0); 2041 } 2042 2043 /** 2044 * Make sure arguments are paired correctly, with respect to more parameters than declared, 2045 * fewer parameters than declared and other things that JavaScript allows. This might involve 2046 * creating collectors. 2047 * 2048 * @param methodHandle method handle for invoke 2049 * @param callType type of the call 2050 * 2051 * @return method handle with adjusted arguments 2052 */ 2053 protected static MethodHandle pairArguments(final MethodHandle methodHandle, final MethodType callType) { 2054 return pairArguments(methodHandle, callType, null); 2055 } 2056 2057 /** 2058 * Make sure arguments are paired correctly, with respect to more parameters than declared, 2059 * fewer parameters than declared and other things that JavaScript allows. This might involve 2060 * creating collectors. 2061 * 2062 * Make sure arguments are paired correctly. 2063 * @param methodHandle MethodHandle to adjust. 2064 * @param callType MethodType of caller. 2065 * @param callerVarArg true if the caller is vararg, false otherwise, null if it should be inferred. 2066 * 2067 * @return method handle with adjusted arguments 2068 */ 2069 public static MethodHandle pairArguments(final MethodHandle methodHandle, final MethodType callType, final Boolean callerVarArg) { 2070 2071 final MethodType methodType = methodHandle.type(); 2072 if (methodType.equals(callType)) { 2073 return methodHandle; 2074 } 2075 2076 final int parameterCount = methodType.parameterCount(); 2077 final int callCount = callType.parameterCount(); 2078 2079 final boolean isCalleeVarArg = parameterCount > 0 && methodType.parameterType(parameterCount - 1).isArray(); 2080 final boolean isCallerVarArg = callerVarArg != null ? callerVarArg.booleanValue() : (callCount > 1 && 2081 callType.parameterType(callCount - 1).isArray()); 2082 2083 if (callCount < parameterCount) { 2084 final int missingArgs = parameterCount - callCount; 2085 final Object[] fillers = new Object[missingArgs]; 2086 2087 Arrays.fill(fillers, UNDEFINED); 2088 2089 if (isCalleeVarArg) { 2090 fillers[missingArgs - 1] = new Object[0]; 2091 } 2092 2093 return MH.insertArguments( 2094 methodHandle, 2095 parameterCount - missingArgs, 2096 fillers); 2097 } 2098 2099 if (isCalleeVarArg) { 2100 return isCallerVarArg ? 2101 methodHandle : 2102 MH.asCollector(methodHandle, Object[].class, callCount - parameterCount + 1); 2103 } 2104 2105 if (isCallerVarArg) { 2106 final int spreadArgs = parameterCount - callCount + 1; 2107 return MH.filterArguments( 2108 MH.asSpreader( 2109 methodHandle, 2110 Object[].class, 2111 spreadArgs), 2112 callCount - 1, 2113 MH.insertArguments( 2114 TRUNCATINGFILTER, 2115 0, 2116 spreadArgs) 2117 ); 2118 } 2119 2120 if (callCount > parameterCount) { 2121 final int discardedArgs = callCount - parameterCount; 2122 2123 final Class<?>[] discards = new Class<?>[discardedArgs]; 2124 Arrays.fill(discards, Object.class); 2125 2126 return MH.dropArguments(methodHandle, callCount - discardedArgs, discards); 2127 } 2128 2129 return methodHandle; 2130 } 2131 2132 @SuppressWarnings("unused") 2133 private static Object[] truncatingFilter(final int n, final Object[] array) { 2134 final int length = array == null ? 0 : array.length; 2135 if (n == length) { 2136 return array == null ? new Object[0] : array; 2137 } 2138 2139 final Object[] newArray = new Object[n]; 2140 2141 if (array != null) { 2142 for (int i = 0; i < n && i < length; i++) { 2143 newArray[i] = array[i]; 2144 } 2145 } 2146 2147 if (length < n) { 2148 final Object fill = UNDEFINED; 2149 2150 for (int i = length; i < n; i++) { 2151 newArray[i] = fill; 2152 } 2153 } 2154 2155 return newArray; 2156 } 2157 2158 /** 2159 * Numeric length setter for length property 2160 * 2161 * @param newLength new length to set 2162 */ 2163 public final void setLength(final long newLength) { 2164 final long arrayLength = getArray().length(); 2165 if (newLength == arrayLength) { 2166 return; 2167 } 2168 2169 final boolean isStrict = getContext()._strict; 2170 2171 if (newLength > arrayLength) { 2172 setArray(getArray().ensure(newLength - 1)); 2173 if (getArray().canDelete(arrayLength, (newLength - 1), isStrict)) { 2174 setArray(getArray().delete(arrayLength, (newLength - 1))); 2175 } 2176 return; 2177 } 2178 2179 if (newLength < arrayLength) { 2180 setArray(getArray().shrink(newLength)); 2181 getArray().setLength(newLength); 2182 } 2183 } 2184 2185 @Override 2186 public int getInt(final Object key) { 2187 final int index = getArrayIndexNoThrow(key); 2188 2189 if (getArray().has(index)) { 2190 return getArray().getInt(index); 2191 } 2192 2193 final FindProperty find = findProperty(convertKey(key), false); 2194 2195 if (find != null) { 2196 return getIntValue(find); 2197 } 2198 2199 final ScriptObject proto = this.getProto(); 2200 2201 return proto != null ? proto.getInt(key) : 0; 2202 } 2203 2204 @Override 2205 public int getInt(final double key) { 2206 final int index = getArrayIndexNoThrow(key); 2207 2208 if (getArray().has(index)) { 2209 return getArray().getInt(index); 2210 } 2211 2212 final FindProperty find = findProperty(convertKey(key), false); 2213 2214 if (find != null) { 2215 return getIntValue(find); 2216 } 2217 2218 final ScriptObject proto = this.getProto(); 2219 2220 return proto != null ? proto.getInt(key) : 0; 2221 } 2222 2223 @Override 2224 public int getInt(final long key) { 2225 final int index = getArrayIndexNoThrow(key); 2226 2227 if (getArray().has(index)) { 2228 return getArray().getInt(index); 2229 } 2230 2231 final FindProperty find = findProperty(convertKey(key), false); 2232 2233 if (find != null) { 2234 return getIntValue(find); 2235 } 2236 2237 final ScriptObject proto = this.getProto(); 2238 2239 return proto != null ? proto.getInt(key) : 0; 2240 } 2241 2242 @Override 2243 public int getInt(final int key) { 2244 final int index = getArrayIndexNoThrow(key); 2245 2246 if (getArray().has(index)) { 2247 return getArray().getInt(index); 2248 } 2249 2250 final FindProperty find = findProperty(convertKey(key), false); 2251 2252 if (find != null) { 2253 return getIntValue(find); 2254 } 2255 2256 final ScriptObject proto = this.getProto(); 2257 2258 return proto != null ? proto.getInt(key) : 0; 2259 } 2260 2261 @Override 2262 public long getLong(final Object key) { 2263 final int index = getArrayIndexNoThrow(key); 2264 2265 if (getArray().has(index)) { 2266 return getArray().getLong(index); 2267 } 2268 2269 final FindProperty find = findProperty(convertKey(key), false); 2270 2271 if (find != null) { 2272 return getLongValue(find); 2273 } 2274 2275 final ScriptObject proto = this.getProto(); 2276 2277 return proto != null ? proto.getLong(key) : 0L; 2278 } 2279 2280 @Override 2281 public long getLong(final double key) { 2282 final int index = getArrayIndexNoThrow(key); 2283 2284 if (getArray().has(index)) { 2285 return getArray().getLong(index); 2286 } 2287 2288 final FindProperty find = findProperty(convertKey(key), false); 2289 2290 if (find != null) { 2291 return getLongValue(find); 2292 } 2293 2294 final ScriptObject proto = this.getProto(); 2295 2296 return proto != null ? proto.getLong(key) : 0L; 2297 } 2298 2299 @Override 2300 public long getLong(final long key) { 2301 final int index = getArrayIndexNoThrow(key); 2302 2303 if (getArray().has(index)) { 2304 return getArray().getLong(index); 2305 } 2306 2307 final FindProperty find = findProperty(convertKey(key), false); 2308 2309 if (find != null) { 2310 return getLongValue(find); 2311 } 2312 2313 final ScriptObject proto = this.getProto(); 2314 2315 return proto != null ? proto.getLong(key) : 0L; 2316 } 2317 2318 @Override 2319 public long getLong(final int key) { 2320 final int index = getArrayIndexNoThrow(key); 2321 2322 if (getArray().has(index)) { 2323 return getArray().getLong(index); 2324 } 2325 2326 final FindProperty find = findProperty(convertKey(key), false); 2327 2328 if (find != null) { 2329 return getLongValue(find); 2330 } 2331 2332 final ScriptObject proto = this.getProto(); 2333 2334 return proto != null ? proto.getLong(key) : 0L; 2335 } 2336 2337 @Override 2338 public double getDouble(final Object key) { 2339 final int index = getArrayIndexNoThrow(key); 2340 2341 if (getArray().has(index)) { 2342 return getArray().getDouble(index); 2343 } 2344 2345 final FindProperty find = findProperty(convertKey(key), false); 2346 2347 if (find != null) { 2348 return getDoubleValue(find); 2349 } 2350 2351 final ScriptObject proto = this.getProto(); 2352 2353 return proto != null ? proto.getDouble(key) : Double.NaN; 2354 } 2355 2356 @Override 2357 public double getDouble(final double key) { 2358 final int index = getArrayIndexNoThrow(key); 2359 2360 if (getArray().has(index)) { 2361 return getArray().getDouble(index); 2362 } 2363 2364 final FindProperty find = findProperty(convertKey(key), false); 2365 2366 if (find != null) { 2367 return getDoubleValue(find); 2368 } 2369 2370 final ScriptObject proto = this.getProto(); 2371 2372 return proto != null ? proto.getDouble(key) : Double.NaN; 2373 } 2374 2375 @Override 2376 public double getDouble(final long key) { 2377 final int index = getArrayIndexNoThrow(key); 2378 2379 if (getArray().has(index)) { 2380 return getArray().getDouble(index); 2381 } 2382 2383 final FindProperty find = findProperty(convertKey(key), false); 2384 2385 if (find != null) { 2386 return getDoubleValue(find); 2387 } 2388 2389 final ScriptObject proto = this.getProto(); 2390 2391 return proto != null ? proto.getDouble(key) : Double.NaN; 2392 } 2393 2394 @Override 2395 public double getDouble(final int key) { 2396 final int index = getArrayIndexNoThrow(key); 2397 2398 if (getArray().has(index)) { 2399 return getArray().getDouble(index); 2400 } 2401 2402 final FindProperty find = findProperty(convertKey(key), false); 2403 2404 if (find != null) { 2405 return getDoubleValue(find); 2406 } 2407 2408 final ScriptObject proto = this.getProto(); 2409 2410 return proto != null ? proto.getDouble(key) : Double.NaN; 2411 } 2412 2413 @Override 2414 public Object get(final Object key) { 2415 final int index = getArrayIndexNoThrow(key); 2416 2417 if (getArray().has(index)) { 2418 return getArray().getObject(index); 2419 } 2420 2421 final FindProperty find = findProperty(convertKey(key), false); 2422 2423 if (find != null) { 2424 return getObjectValue(find); 2425 } 2426 2427 final ScriptObject proto = this.getProto(); 2428 2429 return proto != null ? proto.get(key) : UNDEFINED; 2430 } 2431 2432 @Override 2433 public Object get(final double key) { 2434 final int index = getArrayIndexNoThrow(key); 2435 2436 if (getArray().has(index)) { 2437 return getArray().getObject(index); 2438 } 2439 2440 final FindProperty find = findProperty(convertKey(key), false); 2441 2442 if (find != null) { 2443 return getObjectValue(find); 2444 } 2445 2446 final ScriptObject proto = this.getProto(); 2447 2448 return proto != null ? proto.get(key) : UNDEFINED; 2449 } 2450 2451 @Override 2452 public Object get(final long key) { 2453 final int index = getArrayIndexNoThrow(key); 2454 2455 if (getArray().has(index)) { 2456 return getArray().getObject(index); 2457 } 2458 2459 final FindProperty find = findProperty(convertKey(key), false); 2460 2461 if (find != null) { 2462 return getObjectValue(find); 2463 } 2464 2465 final ScriptObject proto = this.getProto(); 2466 2467 return proto != null ? proto.get(key) : UNDEFINED; 2468 } 2469 2470 @Override 2471 public Object get(final int key) { 2472 final int index = getArrayIndexNoThrow(key); 2473 2474 if (getArray().has(index)) { 2475 return getArray().getObject(index); 2476 } 2477 2478 final FindProperty find = findProperty(convertKey(key), false); 2479 2480 if (find != null) { 2481 return getObjectValue(find); 2482 } 2483 2484 final ScriptObject proto = this.getProto(); 2485 2486 return proto != null ? proto.get(key) : UNDEFINED; 2487 } 2488 2489 /** 2490 * Handle when an array doesn't have a slot - possibly grow and/or convert array. 2491 * 2492 * @param index key as index 2493 * @param value element value 2494 * @param strict are we in strict mode 2495 */ 2496 private void doesNotHave(final int index, final Object value, final boolean strict) { 2497 final long oldLength = getArray().length(); 2498 final long longIndex = index & 0xffff_ffffL; 2499 2500 if (!getArray().has(index)) { 2501 final String key = convertKey(longIndex); 2502 final FindProperty find = findProperty(key, true); 2503 2504 if (find != null) { 2505 setObject(find, strict, key, value); 2506 return; 2507 } 2508 } 2509 2510 if (longIndex >= oldLength) { 2511 if (!isExtensible()) { 2512 if (strict) { 2513 typeError("object.non.extensible", JSType.toString(index), ScriptRuntime.safeToString(this)); 2514 } 2515 return; 2516 } 2517 setArray(getArray().ensure(longIndex)); 2518 } 2519 2520 if (value instanceof Integer) { 2521 setArray(getArray().set(index, (int)value, strict)); 2522 } else if (value instanceof Long) { 2523 setArray(getArray().set(index, (long)value, strict)); 2524 } else if (value instanceof Double) { 2525 setArray(getArray().set(index, (double)value, strict)); 2526 } else { 2527 setArray(getArray().set(index, value, strict)); 2528 } 2529 2530 if (longIndex > oldLength) { 2531 ArrayData array = getArray(); 2532 2533 if (array.canDelete(oldLength, (longIndex - 1), strict)) { 2534 array = array.delete(oldLength, (longIndex - 1)); 2535 } 2536 2537 setArray(array); 2538 } 2539 } 2540 2541 /** 2542 * This is the most generic of all Object setters. Most of the others use this in some form. 2543 * TODO: should be further specialized 2544 * 2545 * @param find found property 2546 * @param strict are we in strict mode 2547 * @param key property key 2548 * @param value property value 2549 */ 2550 public final void setObject(final FindProperty find, final boolean strict, final String key, final Object value) { 2551 FindProperty f = find; 2552 2553 if (f != null && f.isInherited() && !(f.getProperty() instanceof UserAccessorProperty)) { 2554 f = null; 2555 } 2556 2557 MethodHandle setter; 2558 2559 if (f != null) { 2560 if (!f.isWritable()) { 2561 if (strict) { 2562 typeError("property.not.writable", key, ScriptRuntime.safeToString(this)); 2563 } 2564 2565 return; 2566 } 2567 2568 setter = f.getSetter(Object.class, strict); //TODO specfields 2569 try { 2570 setter.invokeExact((Object)f.getOwner(), value); 2571 } catch (final Error|RuntimeException e) { 2572 throw e; 2573 } catch (final Throwable e) { 2574 throw new RuntimeException(e); 2575 } 2576 } else if (!isExtensible()) { 2577 if (strict) { 2578 typeError("object.non.extensible", key, ScriptRuntime.safeToString(this)); 2579 } 2580 } else { 2581 spill(key, value); 2582 } 2583 } 2584 2585 private void spill(final String key, final Object value) { 2586 try { 2587 addSpill(key).invokeExact((Object)this, value); 2588 } catch (final Error|RuntimeException e) { 2589 throw e; 2590 } catch (final Throwable e) { 2591 throw new RuntimeException(e); 2592 } 2593 } 2594 2595 2596 @Override 2597 public void set(final Object key, final int value, final boolean strict) { 2598 final int index = getArrayIndexNoThrow(key); 2599 2600 if (isValidArrayIndex(index)) { 2601 if (getArray().has(index)) { 2602 setArray(getArray().set(index, value, strict)); 2603 } else { 2604 doesNotHave(index, value, strict); 2605 } 2606 2607 return; 2608 } 2609 2610 set(key, JSType.toObject(value), strict); 2611 } 2612 2613 @Override 2614 public void set(final Object key, final long value, final boolean strict) { 2615 final int index = getArrayIndexNoThrow(key); 2616 2617 if (isValidArrayIndex(index)) { 2618 if (getArray().has(index)) { 2619 setArray(getArray().set(index, value, strict)); 2620 } else { 2621 doesNotHave(index, value, strict); 2622 } 2623 2624 return; 2625 } 2626 2627 set(key, JSType.toObject(value), strict); 2628 } 2629 2630 @Override 2631 public void set(final Object key, final double value, final boolean strict) { 2632 final int index = getArrayIndexNoThrow(key); 2633 2634 if (isValidArrayIndex(index)) { 2635 if (getArray().has(index)) { 2636 setArray(getArray().set(index, value, strict)); 2637 } else { 2638 doesNotHave(index, value, strict); 2639 } 2640 2641 return; 2642 } 2643 2644 set(key, JSType.toObject(value), strict); 2645 } 2646 2647 @Override 2648 public void set(final Object key, final Object value, final boolean strict) { 2649 final int index = getArrayIndexNoThrow(key); 2650 2651 if (isValidArrayIndex(index)) { 2652 if (getArray().has(index)) { 2653 setArray(getArray().set(index, value, strict)); 2654 } else { 2655 doesNotHave(index, value, strict); 2656 } 2657 2658 return; 2659 } 2660 2661 final String propName = convertKey(key); 2662 final FindProperty find = findProperty(propName, true); 2663 2664 setObject(find, strict, propName, value); 2665 } 2666 2667 @Override 2668 public void set(final double key, final int value, final boolean strict) { 2669 final int index = getArrayIndexNoThrow(key); 2670 2671 if (isValidArrayIndex(index)) { 2672 if (getArray().has(index)) { 2673 setArray(getArray().set(index, value, strict)); 2674 } else { 2675 doesNotHave(index, value, strict); 2676 } 2677 2678 return; 2679 } 2680 2681 set(JSType.toObject(key), JSType.toObject(value), strict); 2682 } 2683 2684 @Override 2685 public void set(final double key, final long value, final boolean strict) { 2686 final int index = getArrayIndexNoThrow(key); 2687 2688 if (isValidArrayIndex(index)) { 2689 if (getArray().has(index)) { 2690 setArray(getArray().set(index, value, strict)); 2691 } else { 2692 doesNotHave(index, value, strict); 2693 } 2694 2695 return; 2696 } 2697 2698 set(JSType.toObject(key), JSType.toObject(value), strict); 2699 } 2700 2701 @Override 2702 public void set(final double key, final double value, final boolean strict) { 2703 final int index = getArrayIndexNoThrow(key); 2704 2705 if (isValidArrayIndex(index)) { 2706 if (getArray().has(index)) { 2707 setArray(getArray().set(index, value, strict)); 2708 } else { 2709 doesNotHave(index, value, strict); 2710 } 2711 2712 return; 2713 } 2714 2715 set(JSType.toObject(key), JSType.toObject(value), strict); 2716 } 2717 2718 @Override 2719 public void set(final double key, final Object value, final boolean strict) { 2720 final int index = getArrayIndexNoThrow(key); 2721 2722 if (isValidArrayIndex(index)) { 2723 if (getArray().has(index)) { 2724 setArray(getArray().set(index, value, strict)); 2725 } else { 2726 doesNotHave(index, value, strict); 2727 } 2728 2729 return; 2730 } 2731 2732 set(JSType.toObject(key), value, strict); 2733 } 2734 2735 @Override 2736 public void set(final long key, final int value, final boolean strict) { 2737 final int index = getArrayIndexNoThrow(key); 2738 2739 if (isValidArrayIndex(index)) { 2740 if (getArray().has(index)) { 2741 setArray(getArray().set(index, value, strict)); 2742 } else { 2743 doesNotHave(index, value, strict); 2744 } 2745 2746 return; 2747 } 2748 2749 set(JSType.toObject(key), JSType.toObject(value), strict); 2750 } 2751 2752 @Override 2753 public void set(final long key, final long value, final boolean strict) { 2754 final int index = getArrayIndexNoThrow(key); 2755 2756 if (isValidArrayIndex(index)) { 2757 if (getArray().has(index)) { 2758 setArray(getArray().set(index, value, strict)); 2759 } else { 2760 doesNotHave(index, value, strict); 2761 } 2762 2763 return; 2764 } 2765 2766 set(JSType.toObject(key), JSType.toObject(value), strict); 2767 } 2768 2769 @Override 2770 public void set(final long key, final double value, final boolean strict) { 2771 final int index = getArrayIndexNoThrow(key); 2772 2773 if (isValidArrayIndex(index)) { 2774 if (getArray().has(index)) { 2775 setArray(getArray().set(index, value, strict)); 2776 } else { 2777 doesNotHave(index, value, strict); 2778 } 2779 2780 return; 2781 } 2782 2783 set(JSType.toObject(key), JSType.toObject(value), strict); 2784 } 2785 2786 @Override 2787 public void set(final long key, final Object value, final boolean strict) { 2788 final int index = getArrayIndexNoThrow(key); 2789 2790 if (isValidArrayIndex(index)) { 2791 if (getArray().has(index)) { 2792 setArray(getArray().set(index, value, strict)); 2793 } else { 2794 doesNotHave(index, value, strict); 2795 } 2796 2797 return; 2798 } 2799 2800 set(JSType.toObject(key), value, strict); 2801 } 2802 2803 @Override 2804 public void set(final int key, final int value, final boolean strict) { 2805 final int index = getArrayIndexNoThrow(key); 2806 2807 if (isValidArrayIndex(index)) { 2808 if (getArray().has(index)) { 2809 setArray(getArray().set(index, value, strict)); 2810 } else { 2811 doesNotHave(index, value, strict); 2812 } 2813 2814 return; 2815 } 2816 2817 set(JSType.toObject(key), JSType.toObject(value), strict); 2818 } 2819 2820 @Override 2821 public void set(final int key, final long value, final boolean strict) { 2822 final int index = getArrayIndexNoThrow(key); 2823 2824 if (isValidArrayIndex(index)) { 2825 if (getArray().has(index)) { 2826 setArray(getArray().set(index, value, strict)); 2827 } else { 2828 doesNotHave(index, value, strict); 2829 } 2830 2831 return; 2832 } 2833 2834 set(JSType.toObject(key), JSType.toObject(value), strict); 2835 } 2836 2837 @Override 2838 public void set(final int key, final double value, final boolean strict) { 2839 final int index = getArrayIndexNoThrow(key); 2840 2841 if (isValidArrayIndex(index)) { 2842 if (getArray().has(index)) { 2843 setArray(getArray().set(index, value, strict)); 2844 } else { 2845 doesNotHave(index, value, strict); 2846 } 2847 2848 return; 2849 } 2850 2851 set(JSType.toObject(key), JSType.toObject(value), strict); 2852 } 2853 2854 @Override 2855 public void set(final int key, final Object value, final boolean strict) { 2856 final int index = getArrayIndexNoThrow(key); 2857 2858 if (isValidArrayIndex(index)) { 2859 if (getArray().has(index)) { 2860 setArray(getArray().set(index, value, strict)); 2861 } else { 2862 doesNotHave(index, value, strict); 2863 } 2864 2865 return; 2866 } 2867 2868 set(JSType.toObject(key), value, strict); 2869 } 2870 2871 @Override 2872 public boolean has(final Object key) { 2873 final int index = getArrayIndexNoThrow(key); 2874 2875 if (isValidArrayIndex(index)) { 2876 for (ScriptObject self = this; self != null; self = self.getProto()) { 2877 if (self.getArray().has(index)) { 2878 return true; 2879 } 2880 } 2881 } 2882 2883 final FindProperty find = findProperty(convertKey(key), true); 2884 2885 return find != null; 2886 } 2887 2888 @Override 2889 public boolean has(final double key) { 2890 final int index = getArrayIndexNoThrow(key); 2891 2892 if (isValidArrayIndex(index)) { 2893 for (ScriptObject self = this; self != null; self = self.getProto()) { 2894 if (self.getArray().has(index)) { 2895 return true; 2896 } 2897 } 2898 } 2899 2900 final FindProperty find = findProperty(convertKey(key), true); 2901 2902 return find != null; 2903 } 2904 2905 @Override 2906 public boolean has(final long key) { 2907 final int index = getArrayIndexNoThrow(key); 2908 2909 if (isValidArrayIndex(index)) { 2910 for (ScriptObject self = this; self != null; self = self.getProto()) { 2911 if (self.getArray().has(index)) { 2912 return true; 2913 } 2914 } 2915 } 2916 2917 final FindProperty find = findProperty(convertKey(key), true); 2918 2919 return find != null; 2920 } 2921 2922 @Override 2923 public boolean has(final int key) { 2924 final int index = getArrayIndexNoThrow(key); 2925 2926 if (isValidArrayIndex(index)) { 2927 for (ScriptObject self = this; self != null; self = self.getProto()) { 2928 if (self.getArray().has(index)) { 2929 return true; 2930 } 2931 } 2932 } 2933 2934 final FindProperty find = findProperty(convertKey(key), true); 2935 2936 return find != null; 2937 } 2938 2939 @Override 2940 public boolean hasOwnProperty(final Object key) { 2941 final int index = getArrayIndexNoThrow(key); 2942 2943 if (getArray().has(index)) { 2944 return true; 2945 } 2946 2947 final FindProperty find = findProperty(convertKey(key), false); 2948 2949 return find != null; 2950 } 2951 2952 @Override 2953 public boolean hasOwnProperty(final int key) { 2954 final int index = getArrayIndexNoThrow(key); 2955 2956 if (getArray().has(index)) { 2957 return true; 2958 } 2959 2960 final FindProperty find = findProperty(convertKey(key), false); 2961 2962 return find != null; 2963 } 2964 2965 @Override 2966 public boolean hasOwnProperty(final long key) { 2967 final int index = getArrayIndexNoThrow(key); 2968 2969 if (getArray().has(index)) { 2970 return true; 2971 } 2972 2973 final FindProperty find = findProperty(convertKey(key), false); 2974 2975 return find != null; 2976 } 2977 2978 @Override 2979 public boolean hasOwnProperty(final double key) { 2980 final int index = getArrayIndexNoThrow(key); 2981 2982 if (getArray().has(index)) { 2983 return true; 2984 } 2985 2986 final FindProperty find = findProperty(convertKey(key), false); 2987 2988 return find != null; 2989 } 2990 2991 @Override 2992 public boolean delete(final int key, final boolean strict) { 2993 final int index = getArrayIndexNoThrow(key); 2994 final ArrayData array = getArray(); 2995 2996 if (array.has(index)) { 2997 if (array.canDelete(index, strict)) { 2998 setArray(array.delete(index)); 2999 return true; 3000 } 3001 return false; 3002 } 3003 3004 return deleteObject(JSType.toObject(key), strict); 3005 } 3006 3007 @Override 3008 public boolean delete(final long key, final boolean strict) { 3009 final int index = getArrayIndexNoThrow(key); 3010 final ArrayData array = getArray(); 3011 3012 if (array.has(index)) { 3013 if (array.canDelete(index, strict)) { 3014 setArray(array.delete(index)); 3015 return true; 3016 } 3017 return false; 3018 } 3019 3020 return deleteObject(JSType.toObject(key), strict); 3021 } 3022 3023 @Override 3024 public boolean delete(final double key, final boolean strict) { 3025 final int index = getArrayIndexNoThrow(key); 3026 final ArrayData array = getArray(); 3027 3028 if (array.has(index)) { 3029 if (array.canDelete(index, strict)) { 3030 setArray(array.delete(index)); 3031 return true; 3032 } 3033 return false; 3034 } 3035 3036 return deleteObject(JSType.toObject(key), strict); 3037 } 3038 3039 @Override 3040 public boolean delete(final Object key, final boolean strict) { 3041 final int index = getArrayIndexNoThrow(key); 3042 final ArrayData array = getArray(); 3043 3044 if (array.has(index)) { 3045 if (array.canDelete(index, strict)) { 3046 setArray(array.delete(index)); 3047 return true; 3048 } 3049 return false; 3050 } 3051 3052 return deleteObject(key, strict); 3053 } 3054 3055 private boolean deleteObject(final Object key, final boolean strict) { 3056 final String propName = convertKey(key); 3057 final FindProperty find = findProperty(propName, false); 3058 3059 if (find == null) { 3060 return true; 3061 } 3062 3063 if (!find.isConfigurable()) { 3064 if (strict) { 3065 typeError("cant.delete.property", propName, ScriptRuntime.safeToString(this)); 3066 } 3067 return false; 3068 } 3069 3070 final Property prop = find.getProperty(); 3071 notifyPropertyDeleted(this, prop); 3072 deleteOwnProperty(prop); 3073 3074 return true; 3075 } 3076 3077 /* 3078 * Embed management 3079 */ 3080 3081 /** Number of embed slots */ 3082 public static final int EMBED_SIZE = 4; 3083 /** Embed offset */ 3084 public static final int EMBED_OFFSET = 32 - EMBED_SIZE; 3085 3086 static final MethodHandle[] GET_EMBED; 3087 static final MethodHandle[] SET_EMBED; 3088 3089 static { 3090 GET_EMBED = new MethodHandle[EMBED_SIZE]; 3091 SET_EMBED = new MethodHandle[EMBED_SIZE]; 3092 3093 for (int i = 0; i < EMBED_SIZE; i++) { 3094 final String name = "embed" + i; 3095 GET_EMBED[i] = MH.asType(MH.getter(MethodHandles.lookup(), ScriptObject.class, name, Object.class), Lookup.GET_OBJECT_TYPE); 3096 SET_EMBED[i] = MH.asType(MH.setter(MethodHandles.lookup(), ScriptObject.class, name, Object.class), Lookup.SET_OBJECT_TYPE); 3097 } 3098 } 3099 3100 void useEmbed(final int i) { 3101 flags |= 1 << (EMBED_OFFSET + i); 3102 } 3103 3104 int findEmbed() { 3105 final int bits = ~(flags >>> EMBED_OFFSET); 3106 final int least = bits ^ -bits; 3107 final int index = Integer.numberOfTrailingZeros(least) - 1; 3108 3109 return index; 3110 } 3111 3112 /* 3113 * Make a new UserAccessorProperty property. getter and setter functions are stored in 3114 * this ScriptObject and slot values are used in property object. 3115 */ 3116 private UserAccessorProperty newUserAccessors(final String key, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) { 3117 int oldSpillLength = getMap().getSpillLength(); 3118 3119 int getterSlot = findEmbed(); 3120 if (getterSlot >= EMBED_SIZE) { 3121 getterSlot = oldSpillLength + EMBED_SIZE; 3122 ++oldSpillLength; 3123 } else { 3124 useEmbed(getterSlot); 3125 } 3126 setEmbedOrSpill(getterSlot, getter); 3127 // if getter function is null, flag the slot to be negative (less by 1) 3128 if (getter == null) { 3129 getterSlot = -getterSlot - 1; 3130 } 3131 3132 int setterSlot = findEmbed(); 3133 if (setterSlot >= EMBED_SIZE) { 3134 setterSlot = oldSpillLength + EMBED_SIZE; 3135 } else { 3136 useEmbed(setterSlot); 3137 } 3138 setEmbedOrSpill(setterSlot, setter); 3139 // if setter function is null, flag the slot to be negative (less by 1) 3140 if (setter == null) { 3141 setterSlot = -setterSlot - 1; 3142 } 3143 3144 return new UserAccessorProperty(key, propertyFlags, getterSlot, setterSlot); 3145 } 3146 3147 private void setEmbedOrSpill(final int slot, final Object value) { 3148 switch (slot) { 3149 case 0: 3150 embed0 = value; 3151 break; 3152 case 1: 3153 embed1 = value; 3154 break; 3155 case 2: 3156 embed2 = value; 3157 break; 3158 case 3: 3159 embed3 = value; 3160 break; 3161 default: 3162 if (slot >= 0) { 3163 final int index = (slot - EMBED_SIZE); 3164 if (spill == null) { 3165 // create new spill. 3166 spill = new Object[Math.max(index + 1, SPILL_RATE)]; 3167 } else if (index >= spill.length) { 3168 // grow spill as needed 3169 final Object[] newSpill = new Object[index + 1]; 3170 System.arraycopy(spill, 0, newSpill, 0, spill.length); 3171 spill = newSpill; 3172 } 3173 3174 spill[index] = value; 3175 } 3176 break; 3177 } 3178 } 3179 3180 // user accessors are either stored in embed fields or spill array slots 3181 // get the accessor value using slot number. Note that slot is either embed 3182 // field number or (spill array index + embedSize). 3183 Object getEmbedOrSpill(final int slot) { 3184 switch (slot) { 3185 case 0: 3186 return embed0; 3187 case 1: 3188 return embed1; 3189 case 2: 3190 return embed2; 3191 case 3: 3192 return embed3; 3193 default: 3194 final int index = (slot - EMBED_SIZE); 3195 return (index < 0 || (index >= spill.length)) ? null : spill[index]; 3196 } 3197 } 3198 3199 // User defined getter and setter are always called by "dyn:call". Note that the user 3200 // getter/setter may be inherited. If so, proto is bound during lookup. In either 3201 // inherited or self case, slot is also bound during lookup. Actual ScriptFunction 3202 // to be called is retrieved everytime and applied. 3203 @SuppressWarnings("unused") 3204 private static Object userAccessorGetter(final ScriptObject proto, final int slot, final Object self) { 3205 final ScriptObject container = (proto != null) ? proto : (ScriptObject)self; 3206 final Object func = container.getEmbedOrSpill(slot); 3207 3208 if (func instanceof ScriptFunction) { 3209 try { 3210 return INVOKE_UA_GETTER.invokeExact(func, self); 3211 } catch(final Error|RuntimeException t) { 3212 throw t; 3213 } catch(final Throwable t) { 3214 throw new RuntimeException(t); 3215 } 3216 } 3217 3218 return UNDEFINED; 3219 } 3220 3221 @SuppressWarnings("unused") 3222 private static void userAccessorSetter(final ScriptObject proto, final int slot, final String name, final Object self, final Object value) { 3223 final ScriptObject container = (proto != null) ? proto : (ScriptObject)self; 3224 final Object func = container.getEmbedOrSpill(slot); 3225 3226 if (func instanceof ScriptFunction) { 3227 try { 3228 INVOKE_UA_SETTER.invokeExact(func, self, value); 3229 } catch(final Error|RuntimeException t) { 3230 throw t; 3231 } catch(final Throwable t) { 3232 throw new RuntimeException(t); 3233 } 3234 } else if (name != null) { 3235 typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self)); 3236 } 3237 } 3238 3239 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 3240 final Class<?> own = ScriptObject.class; 3241 final MethodType mt = MH.type(rtype, types); 3242 try { 3243 return MH.findStatic(MethodHandles.lookup(), own, name, mt); 3244 } catch (final MethodHandleFactory.LookupException e) { 3245 return MH.findVirtual(MethodHandles.lookup(), own, name, mt); 3246 } 3247 } 3248 3249 private static MethodHandle getKnownFunctionPropertyGuard(final PropertyMap map, final MethodHandle getter, final Object where, final ScriptFunction func) { 3250 return MH.insertArguments(KNOWNFUNCPROPGUARD, 1, map, getter, where, func); 3251 } 3252 3253 @SuppressWarnings("unused") 3254 private static boolean knownFunctionPropertyGuard(final Object self, final PropertyMap map, final MethodHandle getter, final Object where, final ScriptFunction func) { 3255 if (self instanceof ScriptObject && ((ScriptObject)self).getMap() == map) { 3256 try { 3257 return getter.invokeExact(where) == func; 3258 } catch (final RuntimeException | Error e) { 3259 throw e; 3260 } catch (final Throwable t) { 3261 throw new RuntimeException(t); 3262 } 3263 } 3264 3265 return false; 3266 } 3267 3268 /** This is updated only in debug mode - counts number of {@code ScriptObject} instances created */ 3269 protected static int count; 3270 3271 /** This is updated only in debug mode - counts number of {@code ScriptObject} instances created that are scope */ 3272 protected static int scopeCount; 3273 3274 /** 3275 * Get number of {@code ScriptObject} instances created. If not running in debug 3276 * mode this is always 0 3277 * 3278 * @return number of ScriptObjects created 3279 */ 3280 public static int getCount() { 3281 return count; 3282 } 3283 3284 /** 3285 * Get number of scope {@code ScriptObject} instances created. If not running in debug 3286 * mode this is always 0 3287 * 3288 * @return number of scope ScriptObjects created 3289 */ 3290 public static int getScopeCount() { 3291 return scopeCount; 3292 } 3293 3294 }