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