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