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