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