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