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