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