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