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