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