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