| /* |
| * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.runtime; |
| |
| import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; |
| import static jdk.nashorn.internal.lookup.Lookup.MH; |
| import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; |
| import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; |
| import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_DOUBLE; |
| import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_INT; |
| import static jdk.nashorn.internal.runtime.PropertyDescriptor.CONFIGURABLE; |
| import static jdk.nashorn.internal.runtime.PropertyDescriptor.ENUMERABLE; |
| import static jdk.nashorn.internal.runtime.PropertyDescriptor.GET; |
| import static jdk.nashorn.internal.runtime.PropertyDescriptor.SET; |
| import static jdk.nashorn.internal.runtime.PropertyDescriptor.VALUE; |
| import static jdk.nashorn.internal.runtime.PropertyDescriptor.WRITABLE; |
| import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; |
| import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; |
| import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; |
| import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; |
| import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; |
| import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.isScopeFlag; |
| import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.isStrictFlag; |
| import static jdk.nashorn.internal.runtime.linker.NashornGuards.explicitInstanceOfCheck; |
| |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.lang.invoke.SwitchPoint; |
| import java.lang.reflect.Array; |
| import java.util.AbstractMap; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.LongAdder; |
| import jdk.dynalink.CallSiteDescriptor; |
| import jdk.dynalink.NamedOperation; |
| import jdk.dynalink.linker.GuardedInvocation; |
| import jdk.dynalink.linker.LinkRequest; |
| import jdk.nashorn.internal.codegen.CompilerConstants.Call; |
| import jdk.nashorn.internal.codegen.ObjectClassGenerator; |
| import jdk.nashorn.internal.codegen.types.Type; |
| import jdk.nashorn.internal.lookup.Lookup; |
| import jdk.nashorn.internal.objects.AccessorPropertyDescriptor; |
| import jdk.nashorn.internal.objects.DataPropertyDescriptor; |
| import jdk.nashorn.internal.objects.Global; |
| import jdk.nashorn.internal.objects.NativeArray; |
| import jdk.nashorn.internal.runtime.arrays.ArrayData; |
| import jdk.nashorn.internal.runtime.arrays.ArrayIndex; |
| import jdk.nashorn.internal.runtime.linker.LinkerCallSite; |
| import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; |
| import jdk.nashorn.internal.runtime.linker.NashornGuards; |
| |
| /** |
| * Base class for generic JavaScript objects. |
| * <p> |
| * Notes: |
| * <ul> |
| * <li>The map is used to identify properties in the object.</li> |
| * <li>If the map is modified then it must be cloned and replaced. This notifies |
| * any code that made assumptions about the object that things have changed. |
| * Ex. CallSites that have been validated must check to see if the map has |
| * changed (or a map from a different object type) and hence relink the method |
| * to call.</li> |
| * <li>Modifications of the map include adding/deleting attributes or changing a |
| * function field value.</li> |
| * </ul> |
| */ |
| |
| public abstract class ScriptObject implements PropertyAccess, Cloneable { |
| /** __proto__ special property name inside object literals. ES6 draft. */ |
| public static final String PROTO_PROPERTY_NAME = "__proto__"; |
| |
| /** Search fall back routine name for "no such method" */ |
| public static final String NO_SUCH_METHOD_NAME = "__noSuchMethod__"; |
| |
| /** Search fall back routine name for "no such property" */ |
| public static final String NO_SUCH_PROPERTY_NAME = "__noSuchProperty__"; |
| |
| /** Per ScriptObject flag - is this an array object? */ |
| public static final int IS_ARRAY = 1 << 0; |
| |
| /** Per ScriptObject flag - is this an arguments object? */ |
| public static final int IS_ARGUMENTS = 1 << 1; |
| |
| /** Is length property not-writable? */ |
| public static final int IS_LENGTH_NOT_WRITABLE = 1 << 2; |
| |
| /** Is this a builtin object? */ |
| public static final int IS_BUILTIN = 1 << 3; |
| |
| /** Is this an internal object that should not be visible to scripts? */ |
| public static final int IS_INTERNAL = 1 << 4; |
| |
| /** |
| * Spill growth rate - by how many elements does {@link ScriptObject#primitiveSpill} and |
| * {@link ScriptObject#objectSpill} when full |
| */ |
| public static final int SPILL_RATE = 8; |
| |
| /** Map to property information and accessor functions. Ordered by insertion. */ |
| private PropertyMap map; |
| |
| /** objects proto. */ |
| private ScriptObject proto; |
| |
| /** Object flags. */ |
| private int flags; |
| |
| /** Area for primitive properties added to object after instantiation, see {@link AccessorProperty} */ |
| protected long[] primitiveSpill; |
| |
| /** Area for reference properties added to object after instantiation, see {@link AccessorProperty} */ |
| protected Object[] objectSpill; |
| |
| /** Indexed array data. */ |
| private ArrayData arrayData; |
| |
| /** Method handle to retrieve prototype of this object */ |
| public static final MethodHandle GETPROTO = findOwnMH_V("getProto", ScriptObject.class); |
| |
| static final MethodHandle MEGAMORPHIC_GET = findOwnMH_V("megamorphicGet", Object.class, String.class, boolean.class, boolean.class); |
| static final MethodHandle GLOBALFILTER = findOwnMH_S("globalFilter", Object.class, Object.class); |
| static final MethodHandle DECLARE_AND_SET = findOwnMH_V("declareAndSet", void.class, String.class, Object.class); |
| |
| private static final MethodHandle TRUNCATINGFILTER = findOwnMH_S("truncatingFilter", Object[].class, int.class, Object[].class); |
| private static final MethodHandle KNOWNFUNCPROPGUARDSELF = findOwnMH_S("knownFunctionPropertyGuardSelf", boolean.class, Object.class, PropertyMap.class, MethodHandle.class, ScriptFunction.class); |
| private static final MethodHandle KNOWNFUNCPROPGUARDPROTO = findOwnMH_S("knownFunctionPropertyGuardProto", boolean.class, Object.class, PropertyMap.class, MethodHandle.class, int.class, ScriptFunction.class); |
| |
| private static final ArrayList<MethodHandle> PROTO_FILTERS = new ArrayList<>(); |
| |
| /** Method handle for getting the array data */ |
| public static final Call GET_ARRAY = virtualCall(MethodHandles.lookup(), ScriptObject.class, "getArray", ArrayData.class); |
| |
| /** Method handle for getting a function argument at a given index. Used from MapCreator */ |
| public static final Call GET_ARGUMENT = virtualCall(MethodHandles.lookup(), ScriptObject.class, "getArgument", Object.class, int.class); |
| |
| /** Method handle for setting a function argument at a given index. Used from MapCreator */ |
| public static final Call SET_ARGUMENT = virtualCall(MethodHandles.lookup(), ScriptObject.class, "setArgument", void.class, int.class, Object.class); |
| |
| /** Method handle for getting the proto of a ScriptObject */ |
| public static final Call GET_PROTO = virtualCallNoLookup(ScriptObject.class, "getProto", ScriptObject.class); |
| |
| /** Method handle for getting the proto of a ScriptObject */ |
| public static final Call GET_PROTO_DEPTH = virtualCallNoLookup(ScriptObject.class, "getProto", ScriptObject.class, int.class); |
| |
| /** Method handle for setting the proto of a ScriptObject */ |
| public static final Call SET_GLOBAL_OBJECT_PROTO = staticCallNoLookup(ScriptObject.class, "setGlobalObjectProto", void.class, ScriptObject.class); |
| |
| /** Method handle for setting the proto of a ScriptObject after checking argument */ |
| public static final Call SET_PROTO_FROM_LITERAL = virtualCallNoLookup(ScriptObject.class, "setProtoFromLiteral", void.class, Object.class); |
| |
| /** Method handle for setting the user accessors of a ScriptObject */ |
| //TODO fastpath this |
| public static final Call SET_USER_ACCESSORS = virtualCallNoLookup(ScriptObject.class, "setUserAccessors", void.class, Object.class, ScriptFunction.class, ScriptFunction.class); |
| |
| /** Method handle for generic property setter */ |
| public static final Call GENERIC_SET = virtualCallNoLookup(ScriptObject.class, "set", void.class, Object.class, Object.class, int.class); |
| |
| static final MethodHandle[] SET_SLOW = new MethodHandle[] { |
| findOwnMH_V("set", void.class, Object.class, int.class, int.class), |
| findOwnMH_V("set", void.class, Object.class, double.class, int.class), |
| findOwnMH_V("set", void.class, Object.class, Object.class, int.class) |
| }; |
| |
| /** Method handle to reset the map of this ScriptObject */ |
| public static final Call SET_MAP = virtualCallNoLookup(ScriptObject.class, "setMap", void.class, PropertyMap.class); |
| |
| static final MethodHandle CAS_MAP = findOwnMH_V("compareAndSetMap", boolean.class, PropertyMap.class, PropertyMap.class); |
| static final MethodHandle EXTENSION_CHECK = findOwnMH_V("extensionCheck", boolean.class, boolean.class, String.class); |
| static final MethodHandle ENSURE_SPILL_SIZE = findOwnMH_V("ensureSpillSize", Object.class, int.class); |
| |
| /** |
| * Constructor |
| */ |
| public ScriptObject() { |
| this(null); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param map {@link PropertyMap} used to create the initial object |
| */ |
| public ScriptObject(final PropertyMap map) { |
| if (Context.DEBUG) { |
| ScriptObject.count.increment(); |
| } |
| this.arrayData = ArrayData.EMPTY_ARRAY; |
| this.setMap(map == null ? PropertyMap.newMap() : map); |
| } |
| |
| /** |
| * Constructor that directly sets the prototype to {@code proto} and property map to |
| * {@code map} without invalidating the map as calling {@link #setProto(ScriptObject)} |
| * would do. This should only be used for objects that are always constructed with the |
| * same combination of prototype and property map. |
| * |
| * @param proto the prototype object |
| * @param map initial {@link PropertyMap} |
| */ |
| protected ScriptObject(final ScriptObject proto, final PropertyMap map) { |
| this(map); |
| this.proto = proto; |
| } |
| |
| /** |
| * Constructor used to instantiate spill properties directly. Used from |
| * SpillObjectCreator. |
| * |
| * @param map property maps |
| * @param primitiveSpill primitive spills |
| * @param objectSpill reference spills |
| */ |
| public ScriptObject(final PropertyMap map, final long[] primitiveSpill, final Object[] objectSpill) { |
| this(map); |
| this.primitiveSpill = primitiveSpill; |
| this.objectSpill = objectSpill; |
| assert primitiveSpill == null || primitiveSpill.length == objectSpill.length : " primitive spill pool size is not the same length as object spill pool size"; |
| } |
| |
| /** |
| * Check whether this is a global object |
| * @return true if global |
| */ |
| protected boolean isGlobal() { |
| return false; |
| } |
| |
| private static int alignUp(final int size, final int alignment) { |
| return size + alignment - 1 & ~(alignment - 1); |
| } |
| |
| /** |
| * Given a number of properties, return the aligned to SPILL_RATE |
| * buffer size required for the smallest spill pool needed to |
| * house them |
| * @param nProperties number of properties |
| * @return property buffer length, a multiple of SPILL_RATE |
| */ |
| public static int spillAllocationLength(final int nProperties) { |
| return alignUp(nProperties, SPILL_RATE); |
| } |
| |
| /** |
| * Copy all properties from the source object with their receiver bound to the source. |
| * This function was known as mergeMap |
| * |
| * @param source The source object to copy from. |
| */ |
| public void addBoundProperties(final ScriptObject source) { |
| addBoundProperties(source, source.getMap().getProperties()); |
| } |
| |
| /** |
| * Copy all properties from the array with their receiver bound to the source. |
| * |
| * @param source The source object to copy from. |
| * @param properties The array of properties to copy. |
| */ |
| public void addBoundProperties(final ScriptObject source, final Property[] properties) { |
| PropertyMap newMap = this.getMap(); |
| final boolean extensible = newMap.isExtensible(); |
| |
| for (final Property property : properties) { |
| newMap = addBoundProperty(newMap, source, property, extensible); |
| } |
| |
| this.setMap(newMap); |
| } |
| |
| /** |
| * Add a bound property from {@code source}, using the interim property map {@code propMap}, and return the |
| * new interim property map. |
| * |
| * @param propMap the property map |
| * @param source the source object |
| * @param property the property to be added |
| * @param extensible whether the current object is extensible or not |
| * @return the new property map |
| */ |
| protected PropertyMap addBoundProperty(final PropertyMap propMap, final ScriptObject source, final Property property, final boolean extensible) { |
| PropertyMap newMap = propMap; |
| final Object key = property.getKey(); |
| final Property oldProp = newMap.findProperty(key); |
| if (oldProp == null) { |
| if (! extensible) { |
| throw typeError("object.non.extensible", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| |
| if (property instanceof UserAccessorProperty) { |
| // Note: we copy accessor functions to this object which is semantically different from binding. |
| final UserAccessorProperty prop = this.newUserAccessors(key, property.getFlags(), property.getGetterFunction(source), property.getSetterFunction(source)); |
| newMap = newMap.addPropertyNoHistory(prop); |
| } else { |
| newMap = newMap.addPropertyBind((AccessorProperty)property, source); |
| } |
| } else { |
| // See ECMA section 10.5 Declaration Binding Instantiation |
| // step 5 processing each function declaration. |
| if (property.isFunctionDeclaration() && !oldProp.isConfigurable()) { |
| if (oldProp instanceof UserAccessorProperty || |
| !(oldProp.isWritable() && oldProp.isEnumerable())) { |
| throw typeError("cant.redefine.property", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| } |
| } |
| return newMap; |
| } |
| |
| /** |
| * Copy all properties from the array with their receiver bound to the source. |
| * |
| * @param source The source object to copy from. |
| * @param properties The collection of accessor properties to copy. |
| */ |
| public void addBoundProperties(final Object source, final AccessorProperty[] properties) { |
| PropertyMap newMap = this.getMap(); |
| final boolean extensible = newMap.isExtensible(); |
| |
| for (final AccessorProperty property : properties) { |
| final Object key = property.getKey(); |
| |
| if (newMap.findProperty(key) == null) { |
| if (! extensible) { |
| throw typeError("object.non.extensible", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| newMap = newMap.addPropertyBind(property, source); |
| } |
| } |
| |
| this.setMap(newMap); |
| } |
| |
| /** |
| * Bind the method handle to the specified receiver, while preserving its original type (it will just ignore the |
| * first argument in lieu of the bound argument). |
| * @param methodHandle Method handle to bind to. |
| * @param receiver Object to bind. |
| * @return Bound method handle. |
| */ |
| static MethodHandle bindTo(final MethodHandle methodHandle, final Object receiver) { |
| return MH.dropArguments(MH.bindTo(methodHandle, receiver), 0, methodHandle.type().parameterType(0)); |
| } |
| |
| /** |
| * Return a property iterator. |
| * @return Property iterator. |
| */ |
| public Iterator<String> propertyIterator() { |
| return new KeyIterator(this); |
| } |
| |
| /** |
| * Return a property value iterator. |
| * @return Property value iterator. |
| */ |
| public Iterator<Object> valueIterator() { |
| return new ValueIterator(this); |
| } |
| |
| /** |
| * ECMA 8.10.1 IsAccessorDescriptor ( Desc ) |
| * @return true if this has a {@link AccessorPropertyDescriptor} with a getter or a setter |
| */ |
| public final boolean isAccessorDescriptor() { |
| return has(GET) || has(SET); |
| } |
| |
| /** |
| * ECMA 8.10.2 IsDataDescriptor ( Desc ) |
| * @return true if this has a {@link DataPropertyDescriptor}, i.e. the object has a property value and is writable |
| */ |
| public final boolean isDataDescriptor() { |
| return has(VALUE) || has(WRITABLE); |
| } |
| |
| /** |
| * ECMA 8.10.5 ToPropertyDescriptor ( Obj ) |
| * |
| * @return property descriptor |
| */ |
| public final PropertyDescriptor toPropertyDescriptor() { |
| final Global global = Context.getGlobal(); |
| |
| final PropertyDescriptor desc; |
| if (isDataDescriptor()) { |
| if (has(SET) || has(GET)) { |
| throw typeError(global, "inconsistent.property.descriptor"); |
| } |
| |
| desc = global.newDataDescriptor(UNDEFINED, false, false, false); |
| } else if (isAccessorDescriptor()) { |
| if (has(VALUE) || has(WRITABLE)) { |
| throw typeError(global, "inconsistent.property.descriptor"); |
| } |
| |
| desc = global.newAccessorDescriptor(UNDEFINED, UNDEFINED, false, false); |
| } else { |
| desc = global.newGenericDescriptor(false, false); |
| } |
| |
| return desc.fillFrom(this); |
| } |
| |
| /** |
| * ECMA 8.10.5 ToPropertyDescriptor ( Obj ) |
| * |
| * @param global global scope object |
| * @param obj object to create property descriptor from |
| * |
| * @return property descriptor |
| */ |
| public static PropertyDescriptor toPropertyDescriptor(final Global global, final Object obj) { |
| if (obj instanceof ScriptObject) { |
| return ((ScriptObject)obj).toPropertyDescriptor(); |
| } |
| |
| throw typeError(global, "not.an.object", ScriptRuntime.safeToString(obj)); |
| } |
| |
| /** |
| * ECMA 8.12.1 [[GetOwnProperty]] (P) |
| * |
| * @param key property key |
| * |
| * @return Returns the Property Descriptor of the named own property of this |
| * object, or undefined if absent. |
| */ |
| public Object getOwnPropertyDescriptor(final Object key) { |
| final Property property = getMap().findProperty(key); |
| |
| final Global global = Context.getGlobal(); |
| |
| if (property != null) { |
| final ScriptFunction get = property.getGetterFunction(this); |
| final ScriptFunction set = property.getSetterFunction(this); |
| |
| final boolean configurable = property.isConfigurable(); |
| final boolean enumerable = property.isEnumerable(); |
| final boolean writable = property.isWritable(); |
| |
| if (property.isAccessorProperty()) { |
| return global.newAccessorDescriptor( |
| get != null ? |
| get : |
| UNDEFINED, |
| set != null ? |
| set : |
| UNDEFINED, |
| configurable, |
| enumerable); |
| } |
| |
| return global.newDataDescriptor(getWithProperty(property), configurable, enumerable, writable); |
| } |
| |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return array.getDescriptor(global, index); |
| } |
| |
| return UNDEFINED; |
| } |
| |
| /** |
| * ECMA 8.12.2 [[GetProperty]] (P) |
| * |
| * @param key property key |
| * |
| * @return Returns the fully populated Property Descriptor of the named property |
| * of this object, or undefined if absent. |
| */ |
| public Object getPropertyDescriptor(final String key) { |
| final Object res = getOwnPropertyDescriptor(key); |
| |
| if (res != UNDEFINED) { |
| return res; |
| } else if (getProto() != null) { |
| return getProto().getOwnPropertyDescriptor(key); |
| } else { |
| return UNDEFINED; |
| } |
| } |
| |
| /** |
| * Invalidate any existing global constant method handles that may exist for {@code key}. |
| * @param key the property name |
| */ |
| protected void invalidateGlobalConstant(final Object key) { |
| final GlobalConstants globalConstants = getGlobalConstants(); |
| if (globalConstants != null) { |
| globalConstants.delete(key); |
| } |
| } |
| |
| /** |
| * ECMA 8.12.9 [[DefineOwnProperty]] (P, Desc, Throw) |
| * |
| * @param key the property key |
| * @param propertyDesc the property descriptor |
| * @param reject is the property extensible - true means new definitions are rejected |
| * |
| * @return true if property was successfully defined |
| */ |
| public boolean defineOwnProperty(final Object key, final Object propertyDesc, final boolean reject) { |
| final Global global = Context.getGlobal(); |
| final PropertyDescriptor desc = toPropertyDescriptor(global, propertyDesc); |
| final Object current = getOwnPropertyDescriptor(key); |
| |
| invalidateGlobalConstant(key); |
| |
| if (current == UNDEFINED) { |
| if (isExtensible()) { |
| // add a new own property |
| addOwnProperty(key, desc); |
| return true; |
| } |
| // new property added to non-extensible object |
| if (reject) { |
| throw typeError(global, "object.non.extensible", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| return false; |
| } |
| |
| // modifying an existing property |
| final PropertyDescriptor currentDesc = (PropertyDescriptor)current; |
| final PropertyDescriptor newDesc = desc; |
| |
| if (newDesc.type() == PropertyDescriptor.GENERIC && !newDesc.has(CONFIGURABLE) && !newDesc.has(ENUMERABLE)) { |
| // every descriptor field is absent |
| return true; |
| } |
| |
| if (newDesc.hasAndEquals(currentDesc)) { |
| // every descriptor field of the new is same as the current |
| return true; |
| } |
| |
| if (!currentDesc.isConfigurable()) { |
| if (newDesc.has(CONFIGURABLE) && newDesc.isConfigurable()) { |
| // not configurable can not be made configurable |
| if (reject) { |
| throw typeError(global, "cant.redefine.property", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| return false; |
| } |
| |
| if (newDesc.has(ENUMERABLE) && |
| currentDesc.isEnumerable() != newDesc.isEnumerable()) { |
| // cannot make non-enumerable as enumerable or vice-versa |
| if (reject) { |
| throw typeError(global, "cant.redefine.property", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| return false; |
| } |
| } |
| |
| int propFlags = Property.mergeFlags(currentDesc, newDesc); |
| Property property = getMap().findProperty(key); |
| |
| if (currentDesc.type() == PropertyDescriptor.DATA && |
| (newDesc.type() == PropertyDescriptor.DATA || |
| newDesc.type() == PropertyDescriptor.GENERIC)) { |
| if (!currentDesc.isConfigurable() && !currentDesc.isWritable()) { |
| if (newDesc.has(WRITABLE) && newDesc.isWritable() || |
| newDesc.has(VALUE) && !ScriptRuntime.sameValue(currentDesc.getValue(), newDesc.getValue())) { |
| if (reject) { |
| throw typeError(global, "cant.redefine.property", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| return false; |
| } |
| } |
| |
| final boolean newValue = newDesc.has(VALUE); |
| final Object value = newValue ? newDesc.getValue() : currentDesc.getValue(); |
| |
| if (newValue && property != null) { |
| // Temporarily clear flags. |
| property = modifyOwnProperty(property, 0); |
| set(key, value, 0); |
| //this might change the map if we change types of the property |
| //hence we need to read it again. note that we should probably |
| //have the setter return the new property throughout and in |
| //general respect Property return values from modify and add |
| //functions - which we don't seem to do at all here :-( |
| //There is already a bug filed to generify PropertyAccess so we |
| //can have the setter return e.g. a Property |
| property = getMap().findProperty(key); |
| } |
| |
| if (property == null) { |
| // promoting an arrayData value to actual property |
| addOwnProperty(key, propFlags, value); |
| checkIntegerKey(key); |
| } else { |
| // Now set the new flags |
| modifyOwnProperty(property, propFlags); |
| } |
| } else if (currentDesc.type() == PropertyDescriptor.ACCESSOR && |
| (newDesc.type() == PropertyDescriptor.ACCESSOR || |
| newDesc.type() == PropertyDescriptor.GENERIC)) { |
| if (!currentDesc.isConfigurable()) { |
| if (newDesc.has(PropertyDescriptor.GET) && !ScriptRuntime.sameValue(currentDesc.getGetter(), newDesc.getGetter()) || |
| newDesc.has(PropertyDescriptor.SET) && !ScriptRuntime.sameValue(currentDesc.getSetter(), newDesc.getSetter())) { |
| if (reject) { |
| throw typeError(global, "cant.redefine.property", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| return false; |
| } |
| } |
| // New set the new features. |
| modifyOwnProperty(property, propFlags, |
| newDesc.has(GET) ? newDesc.getGetter() : currentDesc.getGetter(), |
| newDesc.has(SET) ? newDesc.getSetter() : currentDesc.getSetter()); |
| } else { |
| // changing descriptor type |
| if (!currentDesc.isConfigurable()) { |
| // not configurable can not be made configurable |
| if (reject) { |
| throw typeError(global, "cant.redefine.property", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| return false; |
| } |
| |
| propFlags = 0; |
| |
| // Preserve only configurable and enumerable from current desc |
| // if those are not overridden in the new property descriptor. |
| boolean value = newDesc.has(CONFIGURABLE) ? newDesc.isConfigurable() : currentDesc.isConfigurable(); |
| if (!value) { |
| propFlags |= Property.NOT_CONFIGURABLE; |
| } |
| value = newDesc.has(ENUMERABLE)? newDesc.isEnumerable() : currentDesc.isEnumerable(); |
| if (!value) { |
| propFlags |= Property.NOT_ENUMERABLE; |
| } |
| |
| final int type = newDesc.type(); |
| if (type == PropertyDescriptor.DATA) { |
| // get writable from the new descriptor |
| value = newDesc.has(WRITABLE) && newDesc.isWritable(); |
| if (!value) { |
| propFlags |= Property.NOT_WRITABLE; |
| } |
| |
| // delete the old property |
| deleteOwnProperty(property); |
| // add new data property |
| addOwnProperty(key, propFlags, newDesc.getValue()); |
| } else if (type == PropertyDescriptor.ACCESSOR) { |
| if (property == null) { |
| addOwnProperty(key, propFlags, |
| newDesc.has(GET) ? newDesc.getGetter() : null, |
| newDesc.has(SET) ? newDesc.getSetter() : null); |
| } else { |
| // Modify old property with the new features. |
| modifyOwnProperty(property, propFlags, |
| newDesc.has(GET) ? newDesc.getGetter() : null, |
| newDesc.has(SET) ? newDesc.getSetter() : null); |
| } |
| } |
| } |
| |
| checkIntegerKey(key); |
| |
| return true; |
| } |
| |
| /** |
| * Almost like defineOwnProperty(int,Object) for arrays this one does |
| * not add 'gap' elements (like the array one does). |
| * |
| * @param index key for property |
| * @param value value to define |
| */ |
| public void defineOwnProperty(final int index, final Object value) { |
| assert isValidArrayIndex(index) : "invalid array index"; |
| final long longIndex = ArrayIndex.toLongIndex(index); |
| final long oldLength = getArray().length(); |
| if (longIndex >= oldLength) { |
| setArray(getArray().ensure(longIndex).safeDelete(oldLength, longIndex - 1, false)); |
| } |
| setArray(getArray().set(index, value, false)); |
| } |
| |
| private void checkIntegerKey(final Object key) { |
| final int index = getArrayIndex(key); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| |
| if (data.has(index)) { |
| setArray(data.delete(index)); |
| } |
| } |
| } |
| |
| /** |
| * Add a new property to the object. |
| * |
| * @param key property key |
| * @param propertyDesc property descriptor for property |
| */ |
| public final void addOwnProperty(final Object key, final PropertyDescriptor propertyDesc) { |
| // Already checked that there is no own property with that key. |
| PropertyDescriptor pdesc = propertyDesc; |
| |
| final int propFlags = Property.toFlags(pdesc); |
| |
| if (pdesc.type() == PropertyDescriptor.GENERIC) { |
| final Global global = Context.getGlobal(); |
| final PropertyDescriptor dDesc = global.newDataDescriptor(UNDEFINED, false, false, false); |
| |
| dDesc.fillFrom((ScriptObject)pdesc); |
| pdesc = dDesc; |
| } |
| |
| final int type = pdesc.type(); |
| if (type == PropertyDescriptor.DATA) { |
| addOwnProperty(key, propFlags, pdesc.getValue()); |
| } else if (type == PropertyDescriptor.ACCESSOR) { |
| addOwnProperty(key, propFlags, |
| pdesc.has(GET) ? pdesc.getGetter() : null, |
| pdesc.has(SET) ? pdesc.getSetter() : null); |
| } |
| |
| checkIntegerKey(key); |
| } |
| |
| /** |
| * Low level property API (not using property descriptors) |
| * <p> |
| * Find a property in the prototype hierarchy. Note: this is final and not |
| * a good idea to override. If you have to, use |
| * {jdk.nashorn.internal.objects.NativeArray{@link #getProperty(String)} or |
| * {jdk.nashorn.internal.objects.NativeArray{@link #getPropertyDescriptor(String)} as the |
| * overriding way to find array properties |
| * |
| * @see jdk.nashorn.internal.objects.NativeArray |
| * |
| * @param key Property key. |
| * @param deep Whether the search should look up proto chain. |
| * |
| * @return FindPropertyData or null if not found. |
| */ |
| public final FindProperty findProperty(final Object key, final boolean deep) { |
| return findProperty(key, deep, false, this); |
| } |
| |
| /** |
| * Low level property API (not using property descriptors) |
| * <p> |
| * Find a property in the prototype hierarchy. Note: this is not a good idea |
| * to override except as it was done in {@link WithObject}. |
| * If you have to, use |
| * {jdk.nashorn.internal.objects.NativeArray{@link #getProperty(String)} or |
| * {jdk.nashorn.internal.objects.NativeArray{@link #getPropertyDescriptor(String)} as the |
| * overriding way to find array properties |
| * |
| * @see jdk.nashorn.internal.objects.NativeArray |
| * |
| * @param key Property key. |
| * @param deep true if the search should look up proto chain |
| * @param isScope true if this is a scope access |
| * @param start the object on which the lookup was originally initiated |
| * @return FindPropertyData or null if not found. |
| */ |
| protected FindProperty findProperty(final Object key, final boolean deep, final boolean isScope, final ScriptObject start) { |
| |
| final PropertyMap selfMap = getMap(); |
| final Property property = selfMap.findProperty(key); |
| |
| if (property != null) { |
| return new FindProperty(start, this, property); |
| } |
| |
| if (deep) { |
| final ScriptObject myProto = getProto(); |
| final FindProperty find = myProto == null ? null : myProto.findProperty(key, true, isScope, start); |
| // checkSharedProtoMap must be invoked after myProto.checkSharedProtoMap to propagate |
| // shared proto invalidation up the prototype chain. It also must be invoked when prototype is null. |
| checkSharedProtoMap(); |
| return find; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Low level property API. This is similar to {@link #findProperty(Object, boolean)} but returns a |
| * {@code boolean} value instead of a {@link FindProperty} object. |
| * @param key Property key. |
| * @param deep Whether the search should look up proto chain. |
| * @return true if the property was found. |
| */ |
| boolean hasProperty(final Object key, final boolean deep) { |
| if (getMap().findProperty(key) != null) { |
| return true; |
| } |
| |
| if (deep) { |
| final ScriptObject myProto = getProto(); |
| if (myProto != null) { |
| return myProto.hasProperty(key, true); |
| } |
| } |
| |
| return false; |
| } |
| |
| private SwitchPoint findBuiltinSwitchPoint(final Object key) { |
| for (ScriptObject myProto = getProto(); myProto != null; myProto = myProto.getProto()) { |
| final Property prop = myProto.getMap().findProperty(key); |
| if (prop != null) { |
| final SwitchPoint sp = prop.getBuiltinSwitchPoint(); |
| if (sp != null && !sp.hasBeenInvalidated()) { |
| return sp; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Add a new property to the object. |
| * <p> |
| * This a more "low level" way that doesn't involve {@link PropertyDescriptor}s |
| * |
| * @param key Property key. |
| * @param propertyFlags Property flags. |
| * @param getter Property getter, or null if not defined |
| * @param setter Property setter, or null if not defined |
| * |
| * @return New property. |
| */ |
| public final Property addOwnProperty(final Object key, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) { |
| return addOwnProperty(newUserAccessors(key, propertyFlags, getter, setter)); |
| } |
| |
| /** |
| * Add a new property to the object. |
| * <p> |
| * This a more "low level" way that doesn't involve {@link PropertyDescriptor}s |
| * |
| * @param key Property key. |
| * @param propertyFlags Property flags. |
| * @param value Value of property |
| * |
| * @return New property. |
| */ |
| public final Property addOwnProperty(final Object key, final int propertyFlags, final Object value) { |
| return addSpillProperty(key, propertyFlags, value, true); |
| } |
| |
| /** |
| * Add a new property to the object. |
| * <p> |
| * This a more "low level" way that doesn't involve {@link PropertyDescriptor}s |
| * |
| * @param newProperty property to add |
| * |
| * @return New property. |
| */ |
| public final Property addOwnProperty(final Property newProperty) { |
| PropertyMap oldMap = getMap(); |
| while (true) { |
| final PropertyMap newMap = oldMap.addProperty(newProperty); |
| if (!compareAndSetMap(oldMap, newMap)) { |
| oldMap = getMap(); |
| final Property oldProperty = oldMap.findProperty(newProperty.getKey()); |
| |
| if (oldProperty != null) { |
| return oldProperty; |
| } |
| } else { |
| return newProperty; |
| } |
| } |
| } |
| |
| private void erasePropertyValue(final Property property) { |
| // Erase the property field value with undefined. If the property is an accessor property |
| // we don't want to call the setter!! |
| if (property != null && !property.isAccessorProperty()) { |
| property.setValue(this, this, UNDEFINED, false); |
| } |
| } |
| |
| /** |
| * Delete a property from the object. |
| * |
| * @param property Property to delete. |
| * |
| * @return true if deleted. |
| */ |
| public final boolean deleteOwnProperty(final Property property) { |
| erasePropertyValue(property); |
| PropertyMap oldMap = getMap(); |
| |
| while (true) { |
| final PropertyMap newMap = oldMap.deleteProperty(property); |
| |
| if (newMap == null) { |
| return false; |
| } |
| |
| if (!compareAndSetMap(oldMap, newMap)) { |
| oldMap = getMap(); |
| } else { |
| // delete getter and setter function references so that we don't leak |
| if (property instanceof UserAccessorProperty) { |
| ((UserAccessorProperty)property).setAccessors(this, getMap(), null); |
| } |
| |
| invalidateGlobalConstant(property.getKey()); |
| return true; |
| } |
| } |
| |
| } |
| |
| /** |
| * Fast initialization functions for ScriptFunctions that are strict, to avoid |
| * creating setters that probably aren't used. Inject directly into the spill pool |
| * the defaults for "arguments" and "caller" |
| * |
| * @param key property key |
| * @param propertyFlags flags |
| * @param getter getter for {@link UserAccessorProperty}, null if not present or N/A |
| * @param setter setter for {@link UserAccessorProperty}, null if not present or N/A |
| */ |
| protected final void initUserAccessors(final String key, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) { |
| final PropertyMap oldMap = getMap(); |
| final int slot = oldMap.getFreeSpillSlot(); |
| ensureSpillSize(slot); |
| objectSpill[slot] = new UserAccessorProperty.Accessors(getter, setter); |
| Property newProperty; |
| PropertyMap newMap; |
| do { |
| newProperty = new UserAccessorProperty(key, propertyFlags, slot); |
| newMap = oldMap.addProperty(newProperty); |
| } while (!compareAndSetMap(oldMap, newMap)); |
| } |
| |
| /** |
| * Modify a property in the object |
| * |
| * @param oldProperty property to modify |
| * @param propertyFlags new property flags |
| * @param getter getter for {@link UserAccessorProperty}, null if not present or N/A |
| * @param setter setter for {@link UserAccessorProperty}, null if not present or N/A |
| * |
| * @return new property |
| */ |
| public final Property modifyOwnProperty(final Property oldProperty, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) { |
| Property newProperty; |
| |
| if (oldProperty instanceof UserAccessorProperty) { |
| final UserAccessorProperty uc = (UserAccessorProperty)oldProperty; |
| final int slot = uc.getSlot(); |
| |
| assert uc.getLocalType() == Object.class; |
| final UserAccessorProperty.Accessors gs = uc.getAccessors(this); //this crashes |
| assert gs != null; |
| //reuse existing getter setter for speed |
| gs.set(getter, setter); |
| if (uc.getFlags() == (propertyFlags | Property.IS_ACCESSOR_PROPERTY)) { |
| return oldProperty; |
| } |
| newProperty = new UserAccessorProperty(uc.getKey(), propertyFlags, slot); |
| } else { |
| // erase old property value and create new user accessor property |
| erasePropertyValue(oldProperty); |
| newProperty = newUserAccessors(oldProperty.getKey(), propertyFlags, getter, setter); |
| } |
| |
| return modifyOwnProperty(oldProperty, newProperty); |
| } |
| |
| /** |
| * Modify a property in the object |
| * |
| * @param oldProperty property to modify |
| * @param propertyFlags new property flags |
| * |
| * @return new property |
| */ |
| public final Property modifyOwnProperty(final Property oldProperty, final int propertyFlags) { |
| return modifyOwnProperty(oldProperty, oldProperty.setFlags(propertyFlags)); |
| } |
| |
| /** |
| * Modify a property in the object, replacing a property with a new one |
| * |
| * @param oldProperty property to replace |
| * @param newProperty property to replace it with |
| * |
| * @return new property |
| */ |
| private Property modifyOwnProperty(final Property oldProperty, final Property newProperty) { |
| if (oldProperty == newProperty) { |
| return newProperty; //nop |
| } |
| |
| assert newProperty.getKey().equals(oldProperty.getKey()) : "replacing property with different key"; |
| |
| PropertyMap oldMap = getMap(); |
| |
| while (true) { |
| final PropertyMap newMap = oldMap.replaceProperty(oldProperty, newProperty); |
| |
| if (!compareAndSetMap(oldMap, newMap)) { |
| oldMap = getMap(); |
| final Property oldPropertyLookup = oldMap.findProperty(oldProperty.getKey()); |
| |
| if (oldPropertyLookup != null && oldPropertyLookup.equals(newProperty)) { |
| return oldPropertyLookup; |
| } |
| } else { |
| return newProperty; |
| } |
| } |
| } |
| |
| /** |
| * Update getter and setter in an object literal. |
| * |
| * @param key Property key. |
| * @param getter {@link UserAccessorProperty} defined getter, or null if none |
| * @param setter {@link UserAccessorProperty} defined setter, or null if none |
| */ |
| public final void setUserAccessors(final Object key, final ScriptFunction getter, final ScriptFunction setter) { |
| final Object realKey = JSType.toPropertyKey(key); |
| final Property oldProperty = getMap().findProperty(realKey); |
| if (oldProperty instanceof UserAccessorProperty) { |
| modifyOwnProperty(oldProperty, oldProperty.getFlags(), getter, setter); |
| } else { |
| addOwnProperty(newUserAccessors(realKey, oldProperty != null ? oldProperty.getFlags() : 0, getter, setter)); |
| } |
| } |
| |
| private static int getIntValue(final FindProperty find, final int programPoint) { |
| final MethodHandle getter = find.getGetter(int.class, programPoint, null); |
| if (getter != null) { |
| try { |
| return (int)getter.invokeExact((Object)find.getGetterReceiver()); |
| } catch (final Error|RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| return UNDEFINED_INT; |
| } |
| |
| private static double getDoubleValue(final FindProperty find, final int programPoint) { |
| final MethodHandle getter = find.getGetter(double.class, programPoint, null); |
| if (getter != null) { |
| try { |
| return (double)getter.invokeExact((Object)find.getGetterReceiver()); |
| } catch (final Error|RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| return UNDEFINED_DOUBLE; |
| } |
| |
| /** |
| * Return methodHandle of value function for call. |
| * |
| * @param find data from find property. |
| * @param type method type of function. |
| * @param bindName null or name to bind to second argument (property not found method.) |
| * |
| * @return value of property as a MethodHandle or null. |
| */ |
| protected static MethodHandle getCallMethodHandle(final FindProperty find, final MethodType type, final String bindName) { |
| return getCallMethodHandle(find.getObjectValue(), type, bindName); |
| } |
| |
| /** |
| * Return methodHandle of value function for call. |
| * |
| * @param value value of receiver, it not a {@link ScriptFunction} this will return null. |
| * @param type method type of function. |
| * @param bindName null or name to bind to second argument (property not found method.) |
| * |
| * @return value of property as a MethodHandle or null. |
| */ |
| private static MethodHandle getCallMethodHandle(final Object value, final MethodType type, final String bindName) { |
| return value instanceof ScriptFunction ? ((ScriptFunction)value).getCallMethodHandle(type, bindName) : null; |
| } |
| |
| /** |
| * Get value using found property. |
| * |
| * @param property Found property. |
| * |
| * @return Value of property. |
| */ |
| public final Object getWithProperty(final Property property) { |
| return new FindProperty(this, this, property).getObjectValue(); |
| } |
| |
| /** |
| * Get a property given a key |
| * |
| * @param key property key |
| * |
| * @return property for key |
| */ |
| public final Property getProperty(final String key) { |
| return getMap().findProperty(key); |
| } |
| |
| /** |
| * Overridden by {@link jdk.nashorn.internal.objects.NativeArguments} class (internal use.) |
| * Used for argument access in a vararg function using parameter name. |
| * Returns the argument at a given key (index) |
| * |
| * @param key argument index |
| * |
| * @return the argument at the given position, or undefined if not present |
| */ |
| public Object getArgument(final int key) { |
| return get(key); |
| } |
| |
| /** |
| * Overridden by {@link jdk.nashorn.internal.objects.NativeArguments} class (internal use.) |
| * Used for argument access in a vararg function using parameter name. |
| * Returns the argument at a given key (index) |
| * |
| * @param key argument index |
| * @param value the value to write at the given index |
| */ |
| public void setArgument(final int key, final Object value) { |
| set(key, value, 0); |
| } |
| |
| /** |
| * Return the current context from the object's map. |
| * @return Current context. |
| */ |
| protected Context getContext() { |
| return Context.fromClass(getClass()); |
| } |
| |
| /** |
| * Return the map of an object. |
| * @return PropertyMap object. |
| */ |
| public final PropertyMap getMap() { |
| return map; |
| } |
| |
| /** |
| * Set the initial map. |
| * @param map Initial map. |
| */ |
| public final void setMap(final PropertyMap map) { |
| this.map = map; |
| } |
| |
| /** |
| * Conditionally set the new map if the old map is the same. |
| * @param oldMap Map prior to manipulation. |
| * @param newMap Replacement map. |
| * @return true if the operation succeeded. |
| */ |
| protected final boolean compareAndSetMap(final PropertyMap oldMap, final PropertyMap newMap) { |
| if (oldMap == this.map) { |
| this.map = newMap; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Return the __proto__ of an object. |
| * @return __proto__ object. |
| */ |
| public final ScriptObject getProto() { |
| return proto; |
| } |
| |
| /** |
| * Get the proto of a specific depth |
| * @param n depth |
| * @return proto at given depth |
| */ |
| public final ScriptObject getProto(final int n) { |
| assert n > 0; |
| ScriptObject p = getProto(); |
| for (int i = n; --i > 0;) { |
| p = p.getProto(); |
| } |
| return p; |
| } |
| |
| /** |
| * Set the __proto__ of an object. |
| * @param newProto new __proto__ to set. |
| */ |
| public final void setProto(final ScriptObject newProto) { |
| final ScriptObject oldProto = proto; |
| |
| if (oldProto != newProto) { |
| proto = newProto; |
| |
| // Let current listeners know that the prototype has changed |
| getMap().protoChanged(true); |
| // Replace our current allocator map with one that is associated with the new prototype. |
| setMap(getMap().changeProto(newProto)); |
| } |
| } |
| |
| /** |
| * Set the initial __proto__ of this object. This should be used instead of |
| * {@link #setProto} if it is known that the current property map will not be |
| * used on a new object with any other parent property map, so we can pass over |
| * property map invalidation/evolution. |
| * |
| * @param initialProto the initial __proto__ to set. |
| */ |
| public void setInitialProto(final ScriptObject initialProto) { |
| this.proto = initialProto; |
| } |
| |
| /** |
| * Invoked from generated bytecode to initialize the prototype of object literals to the global Object prototype. |
| * @param obj the object literal that needs to have its prototype initialized to the global Object prototype. |
| */ |
| public static void setGlobalObjectProto(final ScriptObject obj) { |
| obj.setInitialProto(Global.objectPrototype()); |
| } |
| |
| /** |
| * Set the __proto__ of an object with checks. |
| * This is the built-in operation [[SetPrototypeOf]] |
| * See ES6 draft spec: 9.1.2 [[SetPrototypeOf]] (V) |
| * |
| * @param newProto Prototype to set. |
| */ |
| public final void setPrototypeOf(final Object newProto) { |
| if (newProto == null || newProto instanceof ScriptObject) { |
| if (! isExtensible()) { |
| // okay to set same proto again - even if non-extensible |
| |
| if (newProto == getProto()) { |
| return; |
| } |
| throw typeError("__proto__.set.non.extensible", ScriptRuntime.safeToString(this)); |
| } |
| |
| // check for circularity |
| ScriptObject p = (ScriptObject)newProto; |
| while (p != null) { |
| if (p == this) { |
| throw typeError("circular.__proto__.set", ScriptRuntime.safeToString(this)); |
| } |
| p = p.getProto(); |
| } |
| setProto((ScriptObject) newProto); |
| } else { |
| throw typeError("cant.set.proto.to.non.object", ScriptRuntime.safeToString(this), ScriptRuntime.safeToString(newProto)); |
| } |
| } |
| |
| /** |
| * Set the __proto__ of an object from an object literal. |
| * See ES6 draft spec: B.3.1 __proto__ Property Names in |
| * Object Initializers. Step 6 handling of "__proto__". |
| * |
| * @param newProto Prototype to set. |
| */ |
| public final void setProtoFromLiteral(final Object newProto) { |
| if (newProto == null || newProto instanceof ScriptObject) { |
| setPrototypeOf(newProto); |
| } else { |
| // Some non-object, non-null. Then, we need to set |
| // Object.prototype as the new __proto__ |
| // |
| // var obj = { __proto__ : 34 }; |
| // print(obj.__proto__ === Object.prototype); // => true |
| setPrototypeOf(Global.objectPrototype()); |
| } |
| } |
| |
| /** |
| * return an array of all property keys - all inherited, non-enumerable included. |
| * This is meant for source code completion by interactive shells or editors. |
| * |
| * @return Array of keys, order of properties is undefined. |
| */ |
| public String[] getAllKeys() { |
| final Set<String> keys = new HashSet<>(); |
| final Set<String> nonEnumerable = new HashSet<>(); |
| for (ScriptObject self = this; self != null; self = self.getProto()) { |
| keys.addAll(Arrays.asList(self.getOwnKeys(String.class, true, nonEnumerable))); |
| } |
| return keys.toArray(new String[0]); |
| } |
| |
| /** |
| * Return an array of own property keys associated with the object. |
| * |
| * @param all True if to include non-enumerable keys. |
| * @return Array of keys. |
| */ |
| public final String[] getOwnKeys(final boolean all) { |
| return getOwnKeys(String.class, all, null); |
| } |
| |
| /** |
| * Return an array of own property keys associated with the object. |
| * |
| * @param all True if to include non-enumerable keys. |
| * @return Array of keys. |
| */ |
| public final Symbol[] getOwnSymbols(final boolean all) { |
| return getOwnKeys(Symbol.class, all, null); |
| } |
| |
| /** |
| * return an array of own property keys associated with the object. |
| * |
| * @param <T> the type returned keys. |
| * @param type the type of keys to return, either {@code String.class} or {@code Symbol.class}. |
| * @param all True if to include non-enumerable keys. |
| * @param nonEnumerable set of non-enumerable properties seen already. Used to |
| * filter out shadowed, but enumerable properties from proto children. |
| * @return Array of keys. |
| */ |
| @SuppressWarnings("unchecked") |
| protected <T> T[] getOwnKeys(final Class<T> type, final boolean all, final Set<T> nonEnumerable) { |
| final List<Object> keys = new ArrayList<>(); |
| final PropertyMap selfMap = this.getMap(); |
| |
| final ArrayData array = getArray(); |
| |
| if (type == String.class) { |
| for (final Iterator<Long> iter = array.indexIterator(); iter.hasNext(); ) { |
| keys.add(JSType.toString(iter.next().longValue())); |
| } |
| } |
| |
| for (final Property property : selfMap.getProperties()) { |
| final boolean enumerable = property.isEnumerable(); |
| final Object key = property.getKey(); |
| if (!type.isInstance(key)) { |
| continue; |
| } |
| if (all) { |
| keys.add(key); |
| } else if (enumerable) { |
| // either we don't have non-enumerable filter set or filter set |
| // does not contain the current property. |
| if (nonEnumerable == null || !nonEnumerable.contains(key)) { |
| keys.add(key); |
| } |
| } else { |
| // store this non-enumerable property for later proto walk |
| if (nonEnumerable != null) { |
| nonEnumerable.add((T) key); |
| } |
| } |
| } |
| |
| return keys.toArray((T[]) Array.newInstance(type, keys.size())); |
| } |
| |
| /** |
| * Check if this ScriptObject has array entries. This means that someone has |
| * set values with numeric keys in the object. |
| * |
| * @return true if array entries exists. |
| */ |
| public boolean hasArrayEntries() { |
| return getArray().length() > 0 || getMap().containsArrayKeys(); |
| } |
| |
| /** |
| * Return the valid JavaScript type name descriptor |
| * |
| * @return "Object" |
| */ |
| public String getClassName() { |
| return "Object"; |
| } |
| |
| /** |
| * {@code length} is a well known property. This is its getter. |
| * Note that this *may* be optimized by other classes |
| * |
| * @return length property value for this ScriptObject |
| */ |
| public Object getLength() { |
| return get("length"); |
| } |
| |
| /** |
| * Stateless toString for ScriptObjects. |
| * |
| * @return string description of this object, e.g. {@code [object Object]} |
| */ |
| public String safeToString() { |
| return "[object " + getClassName() + "]"; |
| } |
| |
| /** |
| * Return the default value of the object with a given preferred type hint. |
| * The preferred type hints are String.class for type String, Number.class |
| * for type Number. <p> |
| * |
| * A <code>hint</code> of null means "no hint". |
| * |
| * ECMA 8.12.8 [[DefaultValue]](hint) |
| * |
| * @param typeHint the preferred type hint |
| * @return the default value |
| */ |
| public Object getDefaultValue(final Class<?> typeHint) { |
| // We delegate to Global, as the implementation uses dynamic call sites to invoke object's "toString" and |
| // "valueOf" methods, and in order to avoid those call sites from becoming megamorphic when multiple contexts |
| // are being executed in a long-running program, we move the code and their associated dynamic call sites |
| // (Global.TO_STRING and Global.VALUE_OF) into per-context code. |
| return Context.getGlobal().getDefaultValue(this, typeHint); |
| } |
| |
| /** |
| * Checking whether a script object is an instance of another. Used |
| * in {@link ScriptFunction} for hasInstance implementation, walks |
| * the proto chain |
| * |
| * @param instance instance to check |
| * @return true if 'instance' is an instance of this object |
| */ |
| public boolean isInstance(final ScriptObject instance) { |
| return false; |
| } |
| |
| /** |
| * Flag this ScriptObject as non extensible |
| * |
| * @return the object after being made non extensible |
| */ |
| public ScriptObject preventExtensions() { |
| PropertyMap oldMap = getMap(); |
| while (!compareAndSetMap(oldMap, getMap().preventExtensions())) { |
| oldMap = getMap(); |
| } |
| |
| //invalidate any fast array setters |
| final ArrayData array = getArray(); |
| assert array != null; |
| setArray(ArrayData.preventExtension(array)); |
| return this; |
| } |
| |
| /** |
| * Check whether if an Object (not just a ScriptObject) represents JavaScript array |
| * |
| * @param obj object to check |
| * |
| * @return true if array |
| */ |
| public static boolean isArray(final Object obj) { |
| return obj instanceof ScriptObject && ((ScriptObject)obj).isArray(); |
| } |
| |
| /** |
| * Check if this ScriptObject is an array |
| * @return true if array |
| */ |
| public final boolean isArray() { |
| return (flags & IS_ARRAY) != 0; |
| } |
| |
| /** |
| * Flag this ScriptObject as being an array |
| */ |
| public final void setIsArray() { |
| flags |= IS_ARRAY; |
| } |
| |
| /** |
| * Check if this ScriptObject is an {@code arguments} vector |
| * @return true if arguments vector |
| */ |
| public final boolean isArguments() { |
| return (flags & IS_ARGUMENTS) != 0; |
| } |
| |
| /** |
| * Flag this ScriptObject as being an {@code arguments} vector |
| */ |
| public final void setIsArguments() { |
| flags |= IS_ARGUMENTS; |
| } |
| |
| /** |
| * Check if this object has non-writable length property |
| * |
| * @return {@code true} if 'length' property is non-writable |
| */ |
| public boolean isLengthNotWritable() { |
| return (flags & IS_LENGTH_NOT_WRITABLE) != 0; |
| } |
| |
| /** |
| * Flag this object as having non-writable length property. |
| */ |
| public void setIsLengthNotWritable() { |
| flags |= IS_LENGTH_NOT_WRITABLE; |
| } |
| |
| /** |
| * Get the {@link ArrayData}, for this ScriptObject, ensuring it is of a type |
| * that can handle elementType |
| * @param elementType elementType |
| * @return array data |
| */ |
| public final ArrayData getArray(final Class<?> elementType) { |
| if (elementType == null) { |
| return arrayData; |
| } |
| final ArrayData newArrayData = arrayData.convert(elementType); |
| if (newArrayData != arrayData) { |
| arrayData = newArrayData; |
| } |
| return newArrayData; |
| } |
| |
| /** |
| * Get the {@link ArrayData} for this ScriptObject if it is an array |
| * @return array data |
| */ |
| public final ArrayData getArray() { |
| return arrayData; |
| } |
| |
| /** |
| * Set the {@link ArrayData} for this ScriptObject if it is to be an array |
| * @param arrayData the array data |
| */ |
| public final void setArray(final ArrayData arrayData) { |
| this.arrayData = arrayData; |
| } |
| |
| /** |
| * Check if this ScriptObject is extensible |
| * @return true if extensible |
| */ |
| public boolean isExtensible() { |
| return getMap().isExtensible(); |
| } |
| |
| /** |
| * ECMAScript 15.2.3.8 - seal implementation |
| * @return the sealed ScriptObject |
| */ |
| public ScriptObject seal() { |
| PropertyMap oldMap = getMap(); |
| |
| while (true) { |
| final PropertyMap newMap = getMap().seal(); |
| |
| if (!compareAndSetMap(oldMap, newMap)) { |
| oldMap = getMap(); |
| } else { |
| setArray(ArrayData.seal(getArray())); |
| return this; |
| } |
| } |
| } |
| |
| /** |
| * Check whether this ScriptObject is sealed |
| * @return true if sealed |
| */ |
| public boolean isSealed() { |
| return getMap().isSealed(); |
| } |
| |
| /** |
| * ECMA 15.2.39 - freeze implementation. Freeze this ScriptObject |
| * @return the frozen ScriptObject |
| */ |
| public ScriptObject freeze() { |
| PropertyMap oldMap = getMap(); |
| |
| while (true) { |
| final PropertyMap newMap = getMap().freeze(); |
| |
| if (!compareAndSetMap(oldMap, newMap)) { |
| oldMap = getMap(); |
| } else { |
| setArray(ArrayData.freeze(getArray())); |
| return this; |
| } |
| } |
| } |
| |
| /** |
| * Check whether this ScriptObject is frozen |
| * @return true if frozen |
| */ |
| public boolean isFrozen() { |
| return getMap().isFrozen(); |
| } |
| |
| /** |
| * Check whether this ScriptObject is scope |
| * @return true if scope |
| */ |
| public boolean isScope() { |
| return false; |
| } |
| |
| /** |
| * Tag this script object as built in |
| */ |
| public final void setIsBuiltin() { |
| flags |= IS_BUILTIN; |
| } |
| |
| /** |
| * Check if this script object is built in |
| * @return true if build in |
| */ |
| public final boolean isBuiltin() { |
| return (flags & IS_BUILTIN) != 0; |
| } |
| |
| /** |
| * Tag this script object as internal object that should not be visible to script code. |
| */ |
| public final void setIsInternal() { |
| flags |= IS_INTERNAL; |
| } |
| |
| /** |
| * Check if this script object is an internal object that should not be visible to script code. |
| * @return true if internal |
| */ |
| public final boolean isInternal() { |
| return (flags & IS_INTERNAL) != 0; |
| } |
| |
| /** |
| * Clears the properties from a ScriptObject |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @param strict strict mode or not |
| */ |
| public void clear(final boolean strict) { |
| final Iterator<String> iter = propertyIterator(); |
| while (iter.hasNext()) { |
| delete(iter.next(), strict); |
| } |
| } |
| |
| /** |
| * Checks if a property with a given key is present in a ScriptObject |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @param key the key to check for |
| * @return true if a property with the given key exists, false otherwise |
| */ |
| public boolean containsKey(final Object key) { |
| return has(key); |
| } |
| |
| /** |
| * Checks if a property with a given value is present in a ScriptObject |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @param value value to check for |
| * @return true if a property with the given value exists, false otherwise |
| */ |
| public boolean containsValue(final Object value) { |
| final Iterator<Object> iter = valueIterator(); |
| while (iter.hasNext()) { |
| if (iter.next().equals(value)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the set of {@literal <property, value>} entries that make up this |
| * ScriptObject's properties |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @return an entry set of all the properties in this object |
| */ |
| public Set<Map.Entry<Object, Object>> entrySet() { |
| final Iterator<String> iter = propertyIterator(); |
| final Set<Map.Entry<Object, Object>> entries = new HashSet<>(); |
| while (iter.hasNext()) { |
| final Object key = iter.next(); |
| entries.add(new AbstractMap.SimpleImmutableEntry<>(key, get(key))); |
| } |
| return Collections.unmodifiableSet(entries); |
| } |
| |
| /** |
| * Check whether a ScriptObject contains no properties |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @return true if object has no properties |
| */ |
| public boolean isEmpty() { |
| return !propertyIterator().hasNext(); |
| } |
| |
| /** |
| * Return the set of keys (property names) for all properties |
| * in this ScriptObject |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @return keySet of this ScriptObject |
| */ |
| public Set<Object> keySet() { |
| final Iterator<String> iter = propertyIterator(); |
| final Set<Object> keySet = new HashSet<>(); |
| while (iter.hasNext()) { |
| keySet.add(iter.next()); |
| } |
| return Collections.unmodifiableSet(keySet); |
| } |
| |
| /** |
| * Put a property in the ScriptObject |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @param key property key |
| * @param value property value |
| * @param strict strict mode or not |
| * @return oldValue if property with same key existed already |
| */ |
| public Object put(final Object key, final Object value, final boolean strict) { |
| final Object oldValue = get(key); |
| final int scriptObjectFlags = strict ? NashornCallSiteDescriptor.CALLSITE_STRICT : 0; |
| set(key, value, scriptObjectFlags); |
| return oldValue; |
| } |
| |
| /** |
| * Put several properties in the ScriptObject given a mapping |
| * of their keys to their values |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @param otherMap a {@literal <key,value>} map of properties to add |
| * @param strict strict mode or not |
| */ |
| public void putAll(final Map<?, ?> otherMap, final boolean strict) { |
| final int scriptObjectFlags = strict ? NashornCallSiteDescriptor.CALLSITE_STRICT : 0; |
| for (final Map.Entry<?, ?> entry : otherMap.entrySet()) { |
| set(entry.getKey(), entry.getValue(), scriptObjectFlags); |
| } |
| } |
| |
| /** |
| * Remove a property from the ScriptObject. |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @param key the key of the property |
| * @param strict strict mode or not |
| * @return the oldValue of the removed property |
| */ |
| public Object remove(final Object key, final boolean strict) { |
| final Object oldValue = get(key); |
| delete(key, strict); |
| return oldValue; |
| } |
| |
| /** |
| * Return the size of the ScriptObject - i.e. the number of properties |
| * it contains |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @return number of properties in ScriptObject |
| */ |
| public int size() { |
| int n = 0; |
| for (final Iterator<String> iter = propertyIterator(); iter.hasNext(); iter.next()) { |
| n++; |
| } |
| return n; |
| } |
| |
| /** |
| * Return the values of the properties in the ScriptObject |
| * (java.util.Map-like method to help ScriptObjectMirror implementation) |
| * |
| * @return collection of values for the properties in this ScriptObject |
| */ |
| public Collection<Object> values() { |
| final List<Object> values = new ArrayList<>(size()); |
| final Iterator<Object> iter = valueIterator(); |
| while (iter.hasNext()) { |
| values.add(iter.next()); |
| } |
| return Collections.unmodifiableList(values); |
| } |
| |
| /** |
| * Lookup method that, given a CallSiteDescriptor, looks up the target |
| * MethodHandle and creates a GuardedInvocation |
| * with the appropriate guard(s). |
| * |
| * @param desc call site descriptor |
| * @param request the link request |
| * |
| * @return GuardedInvocation for the callsite |
| */ |
| public GuardedInvocation lookup(final CallSiteDescriptor desc, final LinkRequest request) { |
| // NOTE: we support GET:ELEMENT and SET:ELEMENT as JavaScript doesn't distinguish items from properties. Nashorn itself |
| // emits "GET:PROPERTY|ELEMENT|METHOD:identifier" for "<expr>.<identifier>" and "GET:ELEMENT|PROPERTY|METHOD" for "<expr>[<expr>]", but we are |
| // more flexible here and dispatch not on operation name (getProp vs. getElem), but rather on whether the |
| // operation has an associated name or not. |
| switch (NashornCallSiteDescriptor.getStandardOperation(desc)) { |
| case GET: |
| return desc.getOperation() instanceof NamedOperation |
| ? findGetMethod(desc, request) |
| : findGetIndexMethod(desc, request); |
| case SET: |
| return desc.getOperation() instanceof NamedOperation |
| ? findSetMethod(desc, request) |
| : findSetIndexMethod(desc, request); |
| case CALL: |
| return findCallMethod(desc, request); |
| case NEW: |
| return findNewMethod(desc, request); |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Find the appropriate New method for an invoke dynamic call. |
| * |
| * @param desc The invoke dynamic call site descriptor. |
| * @param request The link request |
| * |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| return notAFunction(desc); |
| } |
| |
| /** |
| * Find the appropriate CALL method for an invoke dynamic call. |
| * This generates "not a function" always |
| * |
| * @param desc the call site descriptor. |
| * @param request the link request |
| * |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| return notAFunction(desc); |
| } |
| |
| private GuardedInvocation notAFunction(final CallSiteDescriptor desc) { |
| throw typeError("not.a.function", NashornCallSiteDescriptor.getFunctionErrorMessage(desc, this)); |
| } |
| |
| /** |
| * Test whether this object contains in its prototype chain or is itself a with-object. |
| * @return true if a with-object was found |
| */ |
| boolean hasWithScope() { |
| return false; |
| } |
| |
| /** |
| * Add a filter to the first argument of {@code methodHandle} that calls its {@link #getProto()} method |
| * {@code depth} times. |
| * @param methodHandle a method handle |
| * @param depth distance to target prototype |
| * @return the filtered method handle |
| */ |
| static MethodHandle addProtoFilter(final MethodHandle methodHandle, final int depth) { |
| if (depth == 0) { |
| return methodHandle; |
| } |
| final int listIndex = depth - 1; // We don't need 0-deep walker |
| MethodHandle filter = listIndex < PROTO_FILTERS.size() ? PROTO_FILTERS.get(listIndex) : null; |
| |
| if (filter == null) { |
| filter = addProtoFilter(GETPROTO, depth - 1); |
| PROTO_FILTERS.add(null); |
| PROTO_FILTERS.set(listIndex, filter); |
| } |
| |
| return MH.filterArguments(methodHandle, 0, filter.asType(filter.type().changeReturnType(methodHandle.type().parameterType(0)))); |
| } |
| |
| /** |
| * Find the appropriate GET method for an invoke dynamic call. |
| * |
| * @param desc the call site descriptor |
| * @param request the link request |
| * |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| final boolean explicitInstanceOfCheck = explicitInstanceOfCheck(desc, request); |
| |
| String name = NashornCallSiteDescriptor.getOperand(desc); |
| if (NashornCallSiteDescriptor.isApplyToCall(desc)) { |
| if (Global.isBuiltinFunctionPrototypeApply()) { |
| name = "call"; |
| } |
| } |
| |
| if (request.isCallSiteUnstable() || hasWithScope()) { |
| return findMegaMorphicGetMethod(desc, name, NashornCallSiteDescriptor.isMethodFirstOperation(desc)); |
| } |
| |
| final FindProperty find = findProperty(name, true, NashornCallSiteDescriptor.isScope(desc), this); |
| MethodHandle mh; |
| |
| if (find == null) { |
| if (!NashornCallSiteDescriptor.isMethodFirstOperation(desc)) { |
| return noSuchProperty(desc, request); |
| } else { |
| return noSuchMethod(desc, request); |
| } |
| } |
| |
| final GlobalConstants globalConstants = getGlobalConstants(); |
| if (globalConstants != null) { |
| final GuardedInvocation cinv = globalConstants.findGetMethod(find, this, desc); |
| if (cinv != null) { |
| return cinv; |
| } |
| } |
| |
| final Class<?> returnType = desc.getMethodType().returnType(); |
| final Property property = find.getProperty(); |
| |
| final int programPoint = NashornCallSiteDescriptor.isOptimistic(desc) ? |
| NashornCallSiteDescriptor.getProgramPoint(desc) : |
| UnwarrantedOptimismException.INVALID_PROGRAM_POINT; |
| |
| mh = find.getGetter(returnType, programPoint, request); |
| // Get the appropriate guard for this callsite and property. |
| final MethodHandle guard = NashornGuards.getGuard(this, property, desc, explicitInstanceOfCheck); |
| final ScriptObject owner = find.getOwner(); |
| final Class<ClassCastException> exception = explicitInstanceOfCheck ? null : ClassCastException.class; |
| |
| final SwitchPoint[] protoSwitchPoints; |
| |
| if (mh == null) { |
| mh = Lookup.emptyGetter(returnType); |
| protoSwitchPoints = getProtoSwitchPoints(name, owner); |
| } else if (!find.isSelf()) { |
| assert mh.type().returnType().equals(returnType) : |
| "return type mismatch for getter " + mh.type().returnType() + " != " + returnType; |
| if (!property.isAccessorProperty()) { |
| // Add a filter that replaces the self object with the prototype owning the property. |
| mh = addProtoFilter(mh, find.getProtoChainLength()); |
| } |
| protoSwitchPoints = getProtoSwitchPoints(name, owner); |
| } else { |
| protoSwitchPoints = null; |
| } |
| |
| final GuardedInvocation inv = new GuardedInvocation(mh, guard, protoSwitchPoints, exception); |
| return inv.addSwitchPoint(findBuiltinSwitchPoint(name)); |
| } |
| |
| private static GuardedInvocation findMegaMorphicGetMethod(final CallSiteDescriptor desc, final String name, final boolean isMethod) { |
| Context.getContextTrusted().getLogger(ObjectClassGenerator.class).warning("Megamorphic getter: ", desc, " ", name + " ", isMethod); |
| final MethodHandle invoker = MH.insertArguments(MEGAMORPHIC_GET, 1, name, isMethod, NashornCallSiteDescriptor.isScope(desc)); |
| final MethodHandle guard = getScriptObjectGuard(desc.getMethodType(), true); |
| return new GuardedInvocation(invoker, guard); |
| } |
| |
| @SuppressWarnings("unused") |
| private Object megamorphicGet(final String key, final boolean isMethod, final boolean isScope) { |
| final FindProperty find = findProperty(key, true, isScope, this); |
| if (find != null) { |
| // If this is a method invocation, and found property has a different self object then this, |
| // then return a function bound to the self object. This is the case for functions in with expressions. |
| final Object value = find.getObjectValue(); |
| if (isMethod && value instanceof ScriptFunction && find.getSelf() != this && !find.getSelf().isInternal()) { |
| return ((ScriptFunction) value).createBound(find.getSelf(), ScriptRuntime.EMPTY_ARRAY); |
| } |
| return value; |
| } |
| |
| return isMethod ? getNoSuchMethod(key, isScope, INVALID_PROGRAM_POINT) : invokeNoSuchProperty(key, isScope, INVALID_PROGRAM_POINT); |
| } |
| |
| // Marks a property as declared and sets its value. Used as slow path for block-scoped LET and CONST |
| @SuppressWarnings("unused") |
| private void declareAndSet(final String key, final Object value) { |
| final PropertyMap oldMap = getMap(); |
| final FindProperty find = findProperty(key, false); |
| assert find != null; |
| |
| final Property property = find.getProperty(); |
| assert property != null; |
| assert property.needsDeclaration(); |
| |
| final PropertyMap newMap = oldMap.replaceProperty(property, property.removeFlags(Property.NEEDS_DECLARATION)); |
| setMap(newMap); |
| set(key, value, 0); |
| } |
| |
| /** |
| * Find the appropriate GETINDEX method for an invoke dynamic call. |
| * |
| * @param desc the call site descriptor |
| * @param request the link request |
| * |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| protected GuardedInvocation findGetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| final MethodType callType = desc.getMethodType(); |
| final Class<?> returnType = callType.returnType(); |
| final Class<?> returnClass = returnType.isPrimitive() ? returnType : Object.class; |
| final Class<?> keyClass = callType.parameterType(1); |
| final boolean explicitInstanceOfCheck = explicitInstanceOfCheck(desc, request); |
| |
| final String name; |
| if (returnClass.isPrimitive()) { |
| //turn e.g. get with a double into getDouble |
| final String returnTypeName = returnClass.getName(); |
| name = "get" + Character.toUpperCase(returnTypeName.charAt(0)) + returnTypeName.substring(1, returnTypeName.length()); |
| } else { |
| name = "get"; |
| } |
| |
| final MethodHandle mh = findGetIndexMethodHandle(returnClass, name, keyClass, desc); |
| return new GuardedInvocation(mh, getScriptObjectGuard(callType, explicitInstanceOfCheck), (SwitchPoint)null, explicitInstanceOfCheck ? null : ClassCastException.class); |
| } |
| |
| private static MethodHandle getScriptObjectGuard(final MethodType type, final boolean explicitInstanceOfCheck) { |
| return ScriptObject.class.isAssignableFrom(type.parameterType(0)) ? null : NashornGuards.getScriptObjectGuard(explicitInstanceOfCheck); |
| } |
| |
| /** |
| * Find a handle for a getIndex method |
| * @param returnType return type for getter |
| * @param name name |
| * @param elementType index type for getter |
| * @param desc call site descriptor |
| * @return method handle for getter |
| */ |
| private static MethodHandle findGetIndexMethodHandle(final Class<?> returnType, final String name, final Class<?> elementType, final CallSiteDescriptor desc) { |
| if (!returnType.isPrimitive()) { |
| return findOwnMH_V(name, returnType, elementType); |
| } |
| |
| return MH.insertArguments( |
| findOwnMH_V(name, returnType, elementType, int.class), |
| 2, |
| NashornCallSiteDescriptor.isOptimistic(desc) ? |
| NashornCallSiteDescriptor.getProgramPoint(desc) : |
| INVALID_PROGRAM_POINT); |
| } |
| |
| /** |
| * Get a switch point for a property with the given {@code name} that will be invalidated when |
| * the property definition is changed in this object's prototype chain. Returns {@code null} if |
| * the property is defined in this object itself. |
| * |
| * @param name the property name |
| * @param owner the property owner, null if property is not defined |
| * @return a SwitchPoint or null |
| */ |
| public final SwitchPoint[] getProtoSwitchPoints(final String name, final ScriptObject owner) { |
| if (owner == this || getProto() == null) { |
| return null; |
| } |
| |
| final List<SwitchPoint> switchPoints = new ArrayList<>(); |
| for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) { |
| final ScriptObject parent = obj.getProto(); |
| parent.getMap().addListener(name, obj.getMap()); |
| final SwitchPoint sp = parent.getMap().getSharedProtoSwitchPoint(); |
| if (sp != null && !sp.hasBeenInvalidated()) { |
| switchPoints.add(sp); |
| } |
| } |
| |
| switchPoints.add(getMap().getSwitchPoint(name)); |
| return switchPoints.toArray(new SwitchPoint[0]); |
| } |
| |
| // Similar to getProtoSwitchPoints method above, but used for additional prototype switchpoints of |
| // properties that are known not to exist, e.g. the original property name in a __noSuchProperty__ invocation. |
| private SwitchPoint getProtoSwitchPoint(final String name) { |
| if (getProto() == null) { |
| return null; |
| } |
| |
| for (ScriptObject obj = this; obj.getProto() != null; obj = obj.getProto()) { |
| final ScriptObject parent = obj.getProto(); |
| parent.getMap().addListener(name, obj.getMap()); |
| } |
| |
| return getMap().getSwitchPoint(name); |
| } |
| |
| private void checkSharedProtoMap() { |
| // Check if our map has an expected shared prototype property map. If it has, make sure that |
| // the prototype map has not been invalidated, and that it does match the actual map of the prototype. |
| if (getMap().isInvalidSharedMapFor(getProto())) { |
| // Change our own map to one that does not assume a shared prototype map. |
| setMap(getMap().makeUnsharedCopy()); |
| } |
| } |
| |
| /** |
| * Find the appropriate SET method for an invoke dynamic call. |
| * |
| * @param desc the call site descriptor |
| * @param request the link request |
| * |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| final String name = NashornCallSiteDescriptor.getOperand(desc); |
| |
| if (request.isCallSiteUnstable() || hasWithScope()) { |
| return findMegaMorphicSetMethod(desc, name); |
| } |
| |
| final boolean explicitInstanceOfCheck = explicitInstanceOfCheck(desc, request); |
| |
| /* |
| * If doing property set on a scope object, we should stop proto search on the first |
| * non-scope object. Without this, for example, when assigning "toString" on global scope, |
| * we'll end up assigning it on it's proto - which is Object.prototype.toString !! |
| * |
| * toString = function() { print("global toString"); } // don't affect Object.prototype.toString |
| */ |
| FindProperty find = findProperty(name, true, NashornCallSiteDescriptor.isScope(desc), this); |
| |
| // If it's not a scope search, then we don't want any inherited properties except those with user defined accessors. |
| if (find != null && find.isInheritedOrdinaryProperty()) { |
| // We should still check if inherited data property is not writable |
| if (isExtensible() && !find.getProperty().isWritable()) { |
| return createEmptySetMethod(desc, explicitInstanceOfCheck, "property.not.writable", true); |
| } |
| // Otherwise, forget the found property unless this is a scope callsite and the owner is a scope object as well. |
| if (!NashornCallSiteDescriptor.isScope(desc) || !find.getOwner().isScope()) { |
| find = null; |
| } |
| } |
| |
| if (find != null) { |
| if (!find.getProperty().isWritable() && !NashornCallSiteDescriptor.isDeclaration(desc)) { |
| if (NashornCallSiteDescriptor.isScope(desc) && find.getProperty().isLexicalBinding()) { |
| throw typeError("assign.constant", name); // Overwriting ES6 const should throw also in non-strict mode. |
| } |
| // Existing, non-writable data property |
| return createEmptySetMethod(desc, explicitInstanceOfCheck, "property.not.writable", true); |
| } |
| if (!find.getProperty().hasNativeSetter()) { |
| // Existing accessor property without setter |
| return createEmptySetMethod(desc, explicitInstanceOfCheck, "property.has.no.setter", true); |
| } |
| } else { |
| if (!isExtensible()) { |
| return createEmptySetMethod(desc, explicitInstanceOfCheck, "object.non.extensible", false); |
| } |
| } |
| |
| final GuardedInvocation inv = new SetMethodCreator(this, find, desc, request).createGuardedInvocation(findBuiltinSwitchPoint(name)); |
| |
| final GlobalConstants globalConstants = getGlobalConstants(); |
| if (globalConstants != null) { |
| final GuardedInvocation cinv = globalConstants.findSetMethod(find, this, inv, desc, request); |
| if (cinv != null) { |
| return cinv; |
| } |
| } |
| |
| return inv; |
| } |
| |
| private GlobalConstants getGlobalConstants() { |
| // Avoid hitting getContext() which might be costly for a non-Global unless needed. |
| return GlobalConstants.GLOBAL_ONLY && !isGlobal() ? null : getContext().getGlobalConstants(); |
| } |
| |
| private GuardedInvocation createEmptySetMethod(final CallSiteDescriptor desc, final boolean explicitInstanceOfCheck, final String strictErrorMessage, final boolean canBeFastScope) { |
| final String name = NashornCallSiteDescriptor.getOperand(desc); |
| if (NashornCallSiteDescriptor.isStrict(desc)) { |
| throw typeError(strictErrorMessage, name, ScriptRuntime.safeToString(this)); |
| } |
| assert canBeFastScope || !NashornCallSiteDescriptor.isFastScope(desc); |
| return new GuardedInvocation( |
| Lookup.EMPTY_SETTER, |
| NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck), |
| getProtoSwitchPoints(name, null), |
| explicitInstanceOfCheck ? null : ClassCastException.class); |
| } |
| |
| @SuppressWarnings("unused") |
| private boolean extensionCheck(final boolean isStrict, final String name) { |
| if (isExtensible()) { |
| return true; //go on and do the set. this is our guard |
| } else if (isStrict) { |
| //throw an error for attempting to do the set in strict mode |
| throw typeError("object.non.extensible", name, ScriptRuntime.safeToString(this)); |
| } else { |
| //not extensible, non strict - this is a nop |
| return false; |
| } |
| } |
| |
| private static GuardedInvocation findMegaMorphicSetMethod(final CallSiteDescriptor desc, final String name) { |
| Context.getContextTrusted().getLogger(ObjectClassGenerator.class).warning("Megamorphic setter: ", desc, " ", name); |
| final MethodType type = desc.getMethodType().insertParameterTypes(1, Object.class); |
| //never bother with ClassCastExceptionGuard for megamorphic callsites |
| final GuardedInvocation inv = findSetIndexMethod(desc, false, type); |
| return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard()); |
| } |
| |
| @SuppressWarnings("unused") |
| private static Object globalFilter(final Object object) { |
| ScriptObject sobj = (ScriptObject) object; |
| while (sobj != null && !(sobj instanceof Global)) { |
| sobj = sobj.getProto(); |
| } |
| return sobj; |
| } |
| |
| /** |
| * Lookup function for the set index method, available for subclasses as well, e.g. {@link NativeArray} |
| * provides special quick accessor linkage for continuous arrays that are represented as Java arrays |
| * |
| * @param desc call site descriptor |
| * @param request link request |
| * |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| protected GuardedInvocation findSetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) { // array, index, value |
| return findSetIndexMethod(desc, explicitInstanceOfCheck(desc, request), desc.getMethodType()); |
| } |
| |
| /** |
| * Find the appropriate SETINDEX method for an invoke dynamic call. |
| * |
| * @param desc the call site descriptor |
| * @param explicitInstanceOfCheck add an explicit instanceof check? |
| * @param callType the method type at the call site |
| * |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| private static GuardedInvocation findSetIndexMethod(final CallSiteDescriptor desc, final boolean explicitInstanceOfCheck, final MethodType callType) { |
| assert callType.parameterCount() == 3; |
| final Class<?> keyClass = callType.parameterType(1); |
| final Class<?> valueClass = callType.parameterType(2); |
| |
| MethodHandle methodHandle = findOwnMH_V("set", void.class, keyClass, valueClass, int.class); |
| methodHandle = MH.insertArguments(methodHandle, 3, NashornCallSiteDescriptor.getFlags(desc)); |
| |
| return new GuardedInvocation(methodHandle, getScriptObjectGuard(callType, explicitInstanceOfCheck), (SwitchPoint)null, explicitInstanceOfCheck ? null : ClassCastException.class); |
| } |
| |
| /** |
| * Fall back if a function property is not found. |
| * @param desc The call site descriptor |
| * @param request the link request |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| public GuardedInvocation noSuchMethod(final CallSiteDescriptor desc, final LinkRequest request) { |
| final String name = NashornCallSiteDescriptor.getOperand(desc); |
| final FindProperty find = findProperty(NO_SUCH_METHOD_NAME, true); |
| final boolean scopeCall = isScope() && NashornCallSiteDescriptor.isScope(desc); |
| |
| if (find == null) { |
| return noSuchProperty(desc, request) |
| // Add proto switchpoint to switch from no-such-property to no-such-method if it is ever defined. |
| .addSwitchPoint(getProtoSwitchPoint(NO_SUCH_METHOD_NAME)); |
| } |
| |
| final boolean explicitInstanceOfCheck = explicitInstanceOfCheck(desc, request); |
| |
| final Object value = find.getObjectValue(); |
| if (!(value instanceof ScriptFunction)) { |
| return createEmptyGetter(desc, explicitInstanceOfCheck, name); |
| } |
| |
| final ScriptFunction func = (ScriptFunction)value; |
| final Object thiz = scopeCall && func.isStrict() ? UNDEFINED : this; |
| // TODO: It'd be awesome if we could bind "name" without binding "this". |
| // Since we're binding this we must use an identity guard here. |
| return new GuardedInvocation( |
| MH.dropArguments( |
| MH.constant( |
| ScriptFunction.class, |
| func.createBound(thiz, new Object[] { name })), |
| 0, |
| Object.class), |
| NashornGuards.combineGuards( |
| NashornGuards.getIdentityGuard(this), |
| NashornGuards.getMapGuard(getMap(), true))) |
| // Add a protoype switchpoint for the original name so this gets invalidated if it is ever defined. |
| .addSwitchPoint(getProtoSwitchPoint(name)); |
| } |
| |
| /** |
| * Fall back if a property is not found. |
| * @param desc the call site descriptor. |
| * @param request the link request |
| * @return GuardedInvocation to be invoked at call site. |
| */ |
| public GuardedInvocation noSuchProperty(final CallSiteDescriptor desc, final LinkRequest request) { |
| final String name = NashornCallSiteDescriptor.getOperand(desc); |
| final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); |
| final boolean scopeAccess = isScope() && NashornCallSiteDescriptor.isScope(desc); |
| |
| if (find != null) { |
| final Object value = find.getObjectValue(); |
| ScriptFunction func = null; |
| MethodHandle mh = null; |
| |
| if (value instanceof ScriptFunction) { |
| func = (ScriptFunction)value; |
| mh = getCallMethodHandle(func, desc.getMethodType(), name); |
| } |
| |
| if (mh != null) { |
| assert func != null; |
| if (scopeAccess && func.isStrict()) { |
| mh = bindTo(mh, UNDEFINED); |
| } |
| |
| return new GuardedInvocation( |
| mh, |
| find.isSelf()? |
| getKnownFunctionPropertyGuardSelf( |
| getMap(), |
| find.getGetter(Object.class, INVALID_PROGRAM_POINT, request), |
| func) |
| : |
| //TODO this always does a scriptobject check |
| getKnownFunctionPropertyGuardProto( |
| getMap(), |
| find.getGetter(Object.class, INVALID_PROGRAM_POINT, request), |
| find.getProtoChainLength(), |
| func), |
| getProtoSwitchPoints(NO_SUCH_PROPERTY_NAME, find.getOwner()), |
| //TODO this doesn't need a ClassCastException as guard always checks script object |
| null) |
| // Add a protoype switchpoint for the original name so this gets invalidated if it is ever defined. |
| .addSwitchPoint(getProtoSwitchPoint(name)); |
| } |
| } |
| |
| if (scopeAccess) { |
| throw referenceError("not.defined", name); |
| } |
| |
| return createEmptyGetter(desc, explicitInstanceOfCheck(desc, request), name); |
| } |
| |
| /** |
| * Invoke fall back if a property is not found. |
| * @param key Name of property. |
| * @param isScope is this a scope access? |
| * @param programPoint program point |
| * @return Result from call. |
| */ |
| protected Object invokeNoSuchProperty(final Object key, final boolean isScope, final int programPoint) { |
| final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); |
| final Object func = (find != null)? find.getObjectValue() : null; |
| |
| Object ret = UNDEFINED; |
| if (func instanceof ScriptFunction) { |
| final ScriptFunction sfunc = (ScriptFunction)func; |
| final Object self = isScope && sfunc.isStrict()? UNDEFINED : this; |
| ret = ScriptRuntime.apply(sfunc, self, key); |
| } else if (isScope) { |
| throw referenceError("not.defined", key.toString()); |
| } |
| |
| if (isValid(programPoint)) { |
| throw new UnwarrantedOptimismException(ret, programPoint); |
| } |
| |
| return ret; |
| } |
| |
| |
| /** |
| * Get __noSuchMethod__ as a function bound to this object and {@code name} if it is defined. |
| * @param name the method name |
| * @param isScope is this a scope access? |
| * @return the bound function, or undefined |
| */ |
| private Object getNoSuchMethod(final String name, final boolean isScope, final int programPoint) { |
| final FindProperty find = findProperty(NO_SUCH_METHOD_NAME, true); |
| |
| if (find == null) { |
| return invokeNoSuchProperty(name, isScope, programPoint); |
| } |
| |
| final Object value = find.getObjectValue(); |
| if (!(value instanceof ScriptFunction)) { |
| if (isScope) { |
| throw referenceError("not.defined", name); |
| } |
| return UNDEFINED; |
| } |
| |
| final ScriptFunction func = (ScriptFunction)value; |
| final Object self = isScope && func.isStrict()? UNDEFINED : this; |
| return func.createBound(self, new Object[] {name}); |
| } |
| |
| private GuardedInvocation createEmptyGetter(final CallSiteDescriptor desc, final boolean explicitInstanceOfCheck, final String name) { |
| if (NashornCallSiteDescriptor.isOptimistic(desc)) { |
| throw new UnwarrantedOptimismException(UNDEFINED, NashornCallSiteDescriptor.getProgramPoint(desc), Type.OBJECT); |
| } |
| |
| return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), |
| NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck), getProtoSwitchPoints(name, null), |
| explicitInstanceOfCheck ? null : ClassCastException.class); |
| } |
| |
| private abstract static class ScriptObjectIterator <T extends Object> implements Iterator<T> { |
| protected T[] values; |
| protected final ScriptObject object; |
| private int index; |
| |
| ScriptObjectIterator(final ScriptObject object) { |
| this.object = object; |
| } |
| |
| protected abstract void init(); |
| |
| @Override |
| public boolean hasNext() { |
| if (values == null) { |
| init(); |
| } |
| return index < values.length; |
| } |
| |
| @Override |
| public T next() { |
| if (values == null) { |
| init(); |
| } |
| return values[index++]; |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException("remove"); |
| } |
| } |
| |
| private static class KeyIterator extends ScriptObjectIterator<String> { |
| KeyIterator(final ScriptObject object) { |
| super(object); |
| } |
| |
| @Override |
| protected void init() { |
| final Set<String> keys = new LinkedHashSet<>(); |
| final Set<String> nonEnumerable = new HashSet<>(); |
| for (ScriptObject self = object; self != null; self = self.getProto()) { |
| keys.addAll(Arrays.asList(self.getOwnKeys(String.class, false, nonEnumerable))); |
| } |
| this.values = keys.toArray(new String[0]); |
| } |
| } |
| |
| private static class ValueIterator extends ScriptObjectIterator<Object> { |
| ValueIterator(final ScriptObject object) { |
| super(object); |
| } |
| |
| @Override |
| protected void init() { |
| final ArrayList<Object> valueList = new ArrayList<>(); |
| final Set<String> nonEnumerable = new HashSet<>(); |
| for (ScriptObject self = object; self != null; self = self.getProto()) { |
| for (final String key : self.getOwnKeys(String.class, false, nonEnumerable)) { |
| valueList.add(self.get(key)); |
| } |
| } |
| this.values = valueList.toArray(new Object[0]); |
| } |
| } |
| |
| /** |
| * Add a spill property for the given key. |
| * @param key Property key. |
| * @param flags Property flags. |
| * @return Added property. |
| */ |
| private Property addSpillProperty(final Object key, final int flags, final Object value, final boolean hasInitialValue) { |
| final PropertyMap propertyMap = getMap(); |
| final int fieldSlot = propertyMap.getFreeFieldSlot(); |
| final int propertyFlags = flags | (useDualFields() ? Property.DUAL_FIELDS : 0); |
| |
| Property property; |
| if (fieldSlot > -1) { |
| property = hasInitialValue ? |
| new AccessorProperty(key, propertyFlags, fieldSlot, this, value) : |
| new AccessorProperty(key, propertyFlags, getClass(), fieldSlot); |
| property = addOwnProperty(property); |
| } else { |
| final int spillSlot = propertyMap.getFreeSpillSlot(); |
| property = hasInitialValue ? |
| new SpillProperty(key, propertyFlags, spillSlot, this, value) : |
| new SpillProperty(key, propertyFlags, spillSlot); |
| property = addOwnProperty(property); |
| ensureSpillSize(property.getSlot()); |
| } |
| return property; |
| } |
| |
| /** |
| * Add a spill entry for the given key. |
| * @param key Property key. |
| * @return Setter method handle. |
| */ |
| MethodHandle addSpill(final Class<?> type, final String key) { |
| return addSpillProperty(key, 0, null, false).getSetter(type, getMap()); |
| } |
| |
| /** |
| * Make sure arguments are paired correctly, with respect to more parameters than declared, |
| * fewer parameters than declared and other things that JavaScript allows. This might involve |
| * creating collectors. |
| * |
| * @param methodHandle method handle for invoke |
| * @param callType type of the call |
| * |
| * @return method handle with adjusted arguments |
| */ |
| protected static MethodHandle pairArguments(final MethodHandle methodHandle, final MethodType callType) { |
| return pairArguments(methodHandle, callType, null); |
| } |
| |
| /** |
| * Make sure arguments are paired correctly, with respect to more parameters than declared, |
| * fewer parameters than declared and other things that JavaScript allows. This might involve |
| * creating collectors. |
| * |
| * Make sure arguments are paired correctly. |
| * @param methodHandle MethodHandle to adjust. |
| * @param callType MethodType of the call site. |
| * @param callerVarArg true if the caller is vararg, false otherwise, null if it should be inferred from the |
| * {@code callType}; basically, if the last parameter type of the call site is an array, it'll be considered a |
| * variable arity call site. These are ordinarily rare; Nashorn code generator creates variable arity call sites |
| * when the call has more than {@link LinkerCallSite#ARGLIMIT} parameters. |
| * |
| * @return method handle with adjusted arguments |
| */ |
| public static MethodHandle pairArguments(final MethodHandle methodHandle, final MethodType callType, final Boolean callerVarArg) { |
| final MethodType methodType = methodHandle.type(); |
| if (methodType.equals(callType.changeReturnType(methodType.returnType()))) { |
| return methodHandle; |
| } |
| |
| final int parameterCount = methodType.parameterCount(); |
| final int callCount = callType.parameterCount(); |
| |
| final boolean isCalleeVarArg = parameterCount > 0 && methodType.parameterType(parameterCount - 1).isArray(); |
| final boolean isCallerVarArg = callerVarArg != null ? callerVarArg : callCount > 0 && |
| callType.parameterType(callCount - 1).isArray(); |
| |
| if (isCalleeVarArg) { |
| return isCallerVarArg ? |
| methodHandle : |
| MH.asCollector(methodHandle, Object[].class, callCount - parameterCount + 1); |
| } |
| |
| if (isCallerVarArg) { |
| return adaptHandleToVarArgCallSite(methodHandle, callCount); |
| } |
| |
| if (callCount < parameterCount) { |
| final int missingArgs = parameterCount - callCount; |
| final Object[] fillers = new Object[missingArgs]; |
| |
| Arrays.fill(fillers, UNDEFINED); |
| |
| if (isCalleeVarArg) { |
| fillers[missingArgs - 1] = ScriptRuntime.EMPTY_ARRAY; |
| } |
| |
| return MH.insertArguments( |
| methodHandle, |
| parameterCount - missingArgs, |
| fillers); |
| } |
| |
| if (callCount > parameterCount) { |
| final int discardedArgs = callCount - parameterCount; |
| |
| final Class<?>[] discards = new Class<?>[discardedArgs]; |
| Arrays.fill(discards, Object.class); |
| |
| return MH.dropArguments(methodHandle, callCount - discardedArgs, discards); |
| } |
| |
| return methodHandle; |
| } |
| |
| static MethodHandle adaptHandleToVarArgCallSite(final MethodHandle mh, final int callSiteParamCount) { |
| final int spreadArgs = mh.type().parameterCount() - callSiteParamCount + 1; |
| return MH.filterArguments( |
| MH.asSpreader( |
| mh, |
| Object[].class, |
| spreadArgs), |
| callSiteParamCount - 1, |
| MH.insertArguments( |
| TRUNCATINGFILTER, |
| 0, |
| spreadArgs) |
| ); |
| } |
| |
| @SuppressWarnings("unused") |
| private static Object[] truncatingFilter(final int n, final Object[] array) { |
| final int length = array == null ? 0 : array.length; |
| if (n == length) { |
| return array == null ? ScriptRuntime.EMPTY_ARRAY : array; |
| } |
| |
| final Object[] newArray = new Object[n]; |
| |
| if (array != null) { |
| System.arraycopy(array, 0, newArray, 0, Math.min(n, length)); |
| } |
| |
| if (length < n) { |
| final Object fill = UNDEFINED; |
| |
| for (int i = length; i < n; i++) { |
| newArray[i] = fill; |
| } |
| } |
| |
| return newArray; |
| } |
| |
| /** |
| * Numeric length setter for length property |
| * |
| * @param newLength new length to set |
| */ |
| public final void setLength(final long newLength) { |
| final ArrayData data = getArray(); |
| final long arrayLength = data.length(); |
| if (newLength == arrayLength) { |
| return; |
| } |
| |
| if (newLength > arrayLength) { |
| setArray(data.ensure(newLength - 1).safeDelete(arrayLength, newLength - 1, false)); |
| return; |
| } |
| |
| if (newLength < arrayLength) { |
| long actualLength = newLength; |
| |
| // Check for numeric keys in property map and delete them or adjust length, depending on whether |
| // they're defined as configurable. See ES5 #15.4.5.2 |
| if (getMap().containsArrayKeys()) { |
| |
| for (long l = arrayLength - 1; l >= newLength; l--) { |
| final FindProperty find = findProperty(JSType.toString(l), false); |
| |
| if (find != null) { |
| |
| if (find.getProperty().isConfigurable()) { |
| deleteOwnProperty(find.getProperty()); |
| } else { |
| actualLength = l + 1; |
| break; |
| } |
| } |
| } |
| } |
| |
| setArray(data.shrink(actualLength)); |
| data.setLength(actualLength); |
| } |
| } |
| |
| private int getInt(final int index, final Object key, final int programPoint) { |
| if (isValidArrayIndex(index)) { |
| for (ScriptObject object = this; ; ) { |
| if (object.getMap().containsArrayKeys()) { |
| final FindProperty find = object.findProperty(key, false); |
| |
| if (find != null) { |
| return getIntValue(find, programPoint); |
| } |
| } |
| |
| if ((object = object.getProto()) == null) { |
| break; |
| } |
| |
| final ArrayData array = object.getArray(); |
| |
| if (array.has(index)) { |
| return isValid(programPoint) ? |
| array.getIntOptimistic(index, programPoint) : |
| array.getInt(index); |
| } |
| } |
| } else { |
| final FindProperty find = findProperty(key, true); |
| |
| if (find != null) { |
| return getIntValue(find, programPoint); |
| } |
| } |
| |
| return JSType.toInt32(invokeNoSuchProperty(key, false, programPoint)); |
| } |
| |
| @Override |
| public int getInt(final Object key, final int programPoint) { |
| final Object primitiveKey = JSType.toPrimitive(key, String.class); |
| final int index = getArrayIndex(primitiveKey); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return isValid(programPoint) ? array.getIntOptimistic(index, programPoint) : array.getInt(index); |
| } |
| |
| return getInt(index, JSType.toPropertyKey(primitiveKey), programPoint); |
| } |
| |
| @Override |
| public int getInt(final double key, final int programPoint) { |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return isValid(programPoint) ? array.getIntOptimistic(index, programPoint) : array.getInt(index); |
| } |
| |
| return getInt(index, JSType.toString(key), programPoint); |
| } |
| |
| @Override |
| public int getInt(final int key, final int programPoint) { |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return isValid(programPoint) ? array.getIntOptimistic(key, programPoint) : array.getInt(key); |
| } |
| |
| return getInt(index, JSType.toString(key), programPoint); |
| } |
| |
| private double getDouble(final int index, final Object key, final int programPoint) { |
| if (isValidArrayIndex(index)) { |
| for (ScriptObject object = this; ; ) { |
| if (object.getMap().containsArrayKeys()) { |
| final FindProperty find = object.findProperty(key, false); |
| if (find != null) { |
| return getDoubleValue(find, programPoint); |
| } |
| } |
| |
| if ((object = object.getProto()) == null) { |
| break; |
| } |
| |
| final ArrayData array = object.getArray(); |
| |
| if (array.has(index)) { |
| return isValid(programPoint) ? |
| array.getDoubleOptimistic(index, programPoint) : |
| array.getDouble(index); |
| } |
| } |
| } else { |
| final FindProperty find = findProperty(key, true); |
| |
| if (find != null) { |
| return getDoubleValue(find, programPoint); |
| } |
| } |
| |
| return JSType.toNumber(invokeNoSuchProperty(key, false, INVALID_PROGRAM_POINT)); |
| } |
| |
| @Override |
| public double getDouble(final Object key, final int programPoint) { |
| final Object primitiveKey = JSType.toPrimitive(key, String.class); |
| final int index = getArrayIndex(primitiveKey); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return isValid(programPoint) ? array.getDoubleOptimistic(index, programPoint) : array.getDouble(index); |
| } |
| |
| return getDouble(index, JSType.toPropertyKey(primitiveKey), programPoint); |
| } |
| |
| @Override |
| public double getDouble(final double key, final int programPoint) { |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return isValid(programPoint) ? array.getDoubleOptimistic(index, programPoint) : array.getDouble(index); |
| } |
| |
| return getDouble(index, JSType.toString(key), programPoint); |
| } |
| |
| @Override |
| public double getDouble(final int key, final int programPoint) { |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return isValid(programPoint) ? array.getDoubleOptimistic(key, programPoint) : array.getDouble(key); |
| } |
| |
| return getDouble(index, JSType.toString(key), programPoint); |
| } |
| |
| private Object get(final int index, final Object key) { |
| if (isValidArrayIndex(index)) { |
| for (ScriptObject object = this; ; ) { |
| if (object.getMap().containsArrayKeys()) { |
| final FindProperty find = object.findProperty(key, false); |
| |
| if (find != null) { |
| return find.getObjectValue(); |
| } |
| } |
| |
| if ((object = object.getProto()) == null) { |
| break; |
| } |
| |
| final ArrayData array = object.getArray(); |
| |
| if (array.has(index)) { |
| return array.getObject(index); |
| } |
| } |
| } else { |
| final FindProperty find = findProperty(key, true); |
| |
| if (find != null) { |
| return find.getObjectValue(); |
| } |
| } |
| |
| return invokeNoSuchProperty(key, false, INVALID_PROGRAM_POINT); |
| } |
| |
| @Override |
| public Object get(final Object key) { |
| final Object primitiveKey = JSType.toPrimitive(key, String.class); |
| final int index = getArrayIndex(primitiveKey); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return array.getObject(index); |
| } |
| |
| return get(index, JSType.toPropertyKey(primitiveKey)); |
| } |
| |
| @Override |
| public Object get(final double key) { |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return array.getObject(index); |
| } |
| |
| return get(index, JSType.toString(key)); |
| } |
| |
| @Override |
| public Object get(final int key) { |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| return array.getObject(index); |
| } |
| |
| return get(index, JSType.toString(key)); |
| } |
| |
| private boolean doesNotHaveCheckArrayKeys(final long longIndex, final int value, final int callSiteFlags) { |
| if (getMap().containsArrayKeys()) { |
| final String key = JSType.toString(longIndex); |
| final FindProperty find = findProperty(key, true); |
| if (find != null) { |
| setObject(find, callSiteFlags, key, value); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean doesNotHaveCheckArrayKeys(final long longIndex, final double value, final int callSiteFlags) { |
| if (getMap().containsArrayKeys()) { |
| final String key = JSType.toString(longIndex); |
| final FindProperty find = findProperty(key, true); |
| if (find != null) { |
| setObject(find, callSiteFlags, key, value); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean doesNotHaveCheckArrayKeys(final long longIndex, final Object value, final int callSiteFlags) { |
| if (getMap().containsArrayKeys()) { |
| final String key = JSType.toString(longIndex); |
| final FindProperty find = findProperty(key, true); |
| if (find != null) { |
| setObject(find, callSiteFlags, key, value); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| //value agnostic |
| private boolean doesNotHaveEnsureLength(final long longIndex, final long oldLength, final int callSiteFlags) { |
| if (longIndex >= oldLength) { |
| if (!isExtensible()) { |
| if (isStrictFlag(callSiteFlags)) { |
| throw typeError("object.non.extensible", JSType.toString(longIndex), ScriptRuntime.safeToString(this)); |
| } |
| return true; |
| } |
| setArray(getArray().ensure(longIndex)); |
| } |
| return false; |
| } |
| |
| private void doesNotHave(final int index, final int value, final int callSiteFlags) { |
| final long oldLength = getArray().length(); |
| final long longIndex = ArrayIndex.toLongIndex(index); |
| if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) { |
| final boolean strict = isStrictFlag(callSiteFlags); |
| setArray(getArray().set(index, value, strict).safeDelete(oldLength, longIndex - 1, strict)); |
| } |
| } |
| |
| private void doesNotHave(final int index, final double value, final int callSiteFlags) { |
| final long oldLength = getArray().length(); |
| final long longIndex = ArrayIndex.toLongIndex(index); |
| if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) { |
| final boolean strict = isStrictFlag(callSiteFlags); |
| setArray(getArray().set(index, value, strict).safeDelete(oldLength, longIndex - 1, strict)); |
| } |
| } |
| |
| private void doesNotHave(final int index, final Object value, final int callSiteFlags) { |
| final long oldLength = getArray().length(); |
| final long longIndex = ArrayIndex.toLongIndex(index); |
| if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) { |
| final boolean strict = isStrictFlag(callSiteFlags); |
| setArray(getArray().set(index, value, strict).safeDelete(oldLength, longIndex - 1, strict)); |
| } |
| } |
| |
| /** |
| * This is the most generic of all Object setters. Most of the others use this in some form. |
| * TODO: should be further specialized |
| * |
| * @param find found property |
| * @param callSiteFlags callsite flags |
| * @param key property key |
| * @param value property value |
| */ |
| public final void setObject(final FindProperty find, final int callSiteFlags, final Object key, final Object value) { |
| FindProperty f = find; |
| |
| invalidateGlobalConstant(key); |
| |
| if (f != null && f.isInheritedOrdinaryProperty()) { |
| final boolean isScope = isScopeFlag(callSiteFlags); |
| // If the start object of the find is not this object it means the property was found inside a |
| // 'with' statement expression (see WithObject.findProperty()). In this case we forward the 'set' |
| // to the 'with' object. |
| // Note that although a 'set' operation involving a with statement follows scope rules outside |
| // the 'with' expression (the 'set' operation is performed on the owning prototype if it exists), |
| // it follows non-scope rules inside the 'with' expression (set is performed on the top level object). |
| // This is why we clear the callsite flags and FindProperty in the forward call to the 'with' object. |
| if (isScope && f.getSelf() != this) { |
| f.getSelf().setObject(null, 0, key, value); |
| return; |
| } |
| // Setting a property should not modify the property in prototype unless this is a scope callsite |
| // and the owner is a scope object as well (with the exception of 'with' statement handled above). |
| if (!isScope || !f.getOwner().isScope()) { |
| f = null; |
| } |
| } |
| |
| if (f != null) { |
| if (!f.getProperty().isWritable() || !f.getProperty().hasNativeSetter()) { |
| if (isScopeFlag(callSiteFlags) && f.getProperty().isLexicalBinding()) { |
| throw typeError("assign.constant", key.toString()); // Overwriting ES6 const should throw also in non-strict mode. |
| } |
| if (isStrictFlag(callSiteFlags)) { |
| throw typeError( |
| f.getProperty().isAccessorProperty() ? "property.has.no.setter" : "property.not.writable", |
| key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| return; |
| } |
| |
| f.setValue(value, isStrictFlag(callSiteFlags)); |
| |
| } else if (!isExtensible()) { |
| if (isStrictFlag(callSiteFlags)) { |
| throw typeError("object.non.extensible", key.toString(), ScriptRuntime.safeToString(this)); |
| } |
| } else { |
| ScriptObject sobj = this; |
| // undefined scope properties are set in the global object. |
| if (isScope()) { |
| while (sobj != null && !(sobj instanceof Global)) { |
| sobj = sobj.getProto(); |
| } |
| assert sobj != null : "no parent global object in scope"; |
| } |
| //this will unbox any Number object to its primitive type in case the |
| //property supports primitive types, so it doesn't matter that it comes |
| //in as an Object. |
| sobj.addSpillProperty(key, 0, value, true); |
| } |
| } |
| |
| @Override |
| public void set(final Object key, final int value, final int callSiteFlags) { |
| final Object primitiveKey = JSType.toPrimitive(key, String.class); |
| final int index = getArrayIndex(primitiveKey); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| if (data.has(index)) { |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| |
| return; |
| } |
| |
| final Object propName = JSType.toPropertyKey(primitiveKey); |
| setObject(findProperty(propName, true), callSiteFlags, propName, JSType.toObject(value)); |
| } |
| |
| @Override |
| public void set(final Object key, final double value, final int callSiteFlags) { |
| final Object primitiveKey = JSType.toPrimitive(key, String.class); |
| final int index = getArrayIndex(primitiveKey); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| if (data.has(index)) { |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| |
| return; |
| } |
| |
| final Object propName = JSType.toPropertyKey(primitiveKey); |
| setObject(findProperty(propName, true), callSiteFlags, propName, JSType.toObject(value)); |
| } |
| |
| @Override |
| public void set(final Object key, final Object value, final int callSiteFlags) { |
| final Object primitiveKey = JSType.toPrimitive(key, String.class); |
| final int index = getArrayIndex(primitiveKey); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| if (data.has(index)) { |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| |
| return; |
| } |
| |
| final Object propName = JSType.toPropertyKey(primitiveKey); |
| setObject(findProperty(propName, true), callSiteFlags, propName, value); |
| } |
| |
| @Override |
| public void set(final double key, final int value, final int callSiteFlags) { |
| final int index = getArrayIndex(key); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| if (data.has(index)) { |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| |
| return; |
| } |
| |
| final String propName = JSType.toString(key); |
| setObject(findProperty(propName, true), callSiteFlags, propName, JSType.toObject(value)); |
| } |
| |
| @Override |
| public void set(final double key, final double value, final int callSiteFlags) { |
| final int index = getArrayIndex(key); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| if (data.has(index)) { |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| |
| return; |
| } |
| |
| final String propName = JSType.toString(key); |
| setObject(findProperty(propName, true), callSiteFlags, propName, JSType.toObject(value)); |
| } |
| |
| @Override |
| public void set(final double key, final Object value, final int callSiteFlags) { |
| final int index = getArrayIndex(key); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| if (data.has(index)) { |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| |
| return; |
| } |
| |
| final String propName = JSType.toString(key); |
| setObject(findProperty(propName, true), callSiteFlags, propName, value); |
| } |
| |
| @Override |
| public void set(final int key, final int value, final int callSiteFlags) { |
| final int index = getArrayIndex(key); |
| if (isValidArrayIndex(index)) { |
| if (getArray().has(index)) { |
| final ArrayData data = getArray(); |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| return; |
| } |
| |
| final String propName = JSType.toString(key); |
| setObject(findProperty(propName, true), callSiteFlags, propName, JSType.toObject(value)); |
| } |
| |
| @Override |
| public void set(final int key, final double value, final int callSiteFlags) { |
| final int index = getArrayIndex(key); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| if (data.has(index)) { |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| |
| return; |
| } |
| |
| final String propName = JSType.toString(key); |
| setObject(findProperty(propName, true), callSiteFlags, propName, JSType.toObject(value)); |
| } |
| |
| @Override |
| public void set(final int key, final Object value, final int callSiteFlags) { |
| final int index = getArrayIndex(key); |
| |
| if (isValidArrayIndex(index)) { |
| final ArrayData data = getArray(); |
| if (data.has(index)) { |
| setArray(data.set(index, value, isStrictFlag(callSiteFlags))); |
| } else { |
| doesNotHave(index, value, callSiteFlags); |
| } |
| |
| return; |
| } |
| |
| final String propName = JSType.toString(key); |
| setObject(findProperty(propName, true), callSiteFlags, propName, value); |
| } |
| |
| @Override |
| public boolean has(final Object key) { |
| final Object primitiveKey = JSType.toPrimitive(key); |
| final int index = getArrayIndex(primitiveKey); |
| return isValidArrayIndex(index) ? hasArrayProperty(index) : hasProperty(JSType.toPropertyKey(primitiveKey), true); |
| } |
| |
| @Override |
| public boolean has(final double key) { |
| final int index = getArrayIndex(key); |
| return isValidArrayIndex(index) ? hasArrayProperty(index) : hasProperty(JSType.toString(key), true); |
| } |
| |
| @Override |
| public boolean has(final int key) { |
| final int index = getArrayIndex(key); |
| return isValidArrayIndex(index) ? hasArrayProperty(index) : hasProperty(JSType.toString(key), true); |
| } |
| |
| private boolean hasArrayProperty(final int index) { |
| boolean hasArrayKeys = false; |
| |
| for (ScriptObject self = this; self != null; self = self.getProto()) { |
| if (self.getArray().has(index)) { |
| return true; |
| } |
| hasArrayKeys = hasArrayKeys || self.getMap().containsArrayKeys(); |
| } |
| |
| return hasArrayKeys && hasProperty(ArrayIndex.toKey(index), true); |
| } |
| |
| @Override |
| public boolean hasOwnProperty(final Object key) { |
| final Object primitiveKey = JSType.toPrimitive(key, String.class); |
| final int index = getArrayIndex(primitiveKey); |
| return isValidArrayIndex(index) ? hasOwnArrayProperty(index) : hasProperty(JSType.toPropertyKey(primitiveKey), false); |
| } |
| |
| @Override |
| public boolean hasOwnProperty(final int key) { |
| final int index = getArrayIndex(key); |
| return isValidArrayIndex(index) ? hasOwnArrayProperty(index) : hasProperty(JSType.toString(key), false); |
| } |
| |
| @Override |
| public boolean hasOwnProperty(final double key) { |
| final int index = getArrayIndex(key); |
| return isValidArrayIndex(index) ? hasOwnArrayProperty(index) : hasProperty(JSType.toString(key), false); |
| } |
| |
| private boolean hasOwnArrayProperty(final int index) { |
| return getArray().has(index) || getMap().containsArrayKeys() && hasProperty(ArrayIndex.toKey(index), false); |
| } |
| |
| @Override |
| public boolean delete(final int key, final boolean strict) { |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| if (array.canDelete(index, strict)) { |
| setArray(array.delete(index)); |
| return true; |
| } |
| return false; |
| } |
| return deleteObject(JSType.toObject(key), strict); |
| } |
| |
| @Override |
| public boolean delete(final double key, final boolean strict) { |
| final int index = getArrayIndex(key); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| if (array.canDelete(index, strict)) { |
| setArray(array.delete(index)); |
| return true; |
| } |
| return false; |
| } |
| |
| return deleteObject(JSType.toObject(key), strict); |
| } |
| |
| @Override |
| public boolean delete(final Object key, final boolean strict) { |
| final Object primitiveKey = JSType.toPrimitive(key, String.class); |
| final int index = getArrayIndex(primitiveKey); |
| final ArrayData array = getArray(); |
| |
| if (array.has(index)) { |
| if (array.canDelete(index, strict)) { |
| setArray(array.delete(index)); |
| return true; |
| } |
| return false; |
| } |
| |
| return deleteObject(primitiveKey, strict); |
| } |
| |
| private boolean deleteObject(final Object key, final boolean strict) { |
| final Object propName = JSType.toPropertyKey(key); |
| final FindProperty find = findProperty(propName, false); |
| |
| if (find == null) { |
| return true; |
| } |
| |
| if (!find.getProperty().isConfigurable()) { |
| if (strict) { |
| throw typeError("cant.delete.property", propName.toString(), ScriptRuntime.safeToString(this)); |
| } |
| return false; |
| } |
| |
| final Property prop = find.getProperty(); |
| deleteOwnProperty(prop); |
| |
| return true; |
| } |
| |
| /** |
| * Return a shallow copy of this ScriptObject. |
| * @return a shallow copy. |
| */ |
| public final ScriptObject copy() { |
| try { |
| return clone(); |
| } catch (final CloneNotSupportedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| protected ScriptObject clone() throws CloneNotSupportedException { |
| final ScriptObject clone = (ScriptObject) super.clone(); |
| if (objectSpill != null) { |
| clone.objectSpill = objectSpill.clone(); |
| if (primitiveSpill != null) { |
| clone.primitiveSpill = primitiveSpill.clone(); |
| } |
| } |
| clone.arrayData = arrayData.copy(); |
| return clone; |
| } |
| |
| /** |
| * Make a new UserAccessorProperty property. getter and setter functions are stored in |
| * this ScriptObject and slot values are used in property object. |
| * |
| * @param key the property name |
| * @param propertyFlags attribute flags of the property |
| * @param getter getter function for the property |
| * @param setter setter function for the property |
| * @return the newly created UserAccessorProperty |
| */ |
| protected final UserAccessorProperty newUserAccessors(final Object key, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) { |
| final UserAccessorProperty uc = getMap().newUserAccessors(key, propertyFlags); |
| //property.getSetter(Object.class, getMap()); |
| uc.setAccessors(this, getMap(), new UserAccessorProperty.Accessors(getter, setter)); |
| return uc; |
| } |
| |
| /** |
| * Returns {@code true} if properties for this object should use dual field mode, {@code false} otherwise. |
| * @return {@code true} if dual fields should be used. |
| */ |
| protected boolean useDualFields() { |
| return !StructureLoader.isSingleFieldStructure(getClass().getName()); |
| } |
| |
| Object ensureSpillSize(final int slot) { |
| final int oldLength = objectSpill == null ? 0 : objectSpill.length; |
| if (slot < oldLength) { |
| return this; |
| } |
| final int newLength = alignUp(slot + 1, SPILL_RATE); |
| final Object[] newObjectSpill = new Object[newLength]; |
| final long[] newPrimitiveSpill = useDualFields() ? new long[newLength] : null; |
| |
| if (objectSpill != null) { |
| System.arraycopy(objectSpill, 0, newObjectSpill, 0, oldLength); |
| if (primitiveSpill != null && newPrimitiveSpill != null) { |
| System.arraycopy(primitiveSpill, 0, newPrimitiveSpill, 0, oldLength); |
| } |
| } |
| |
| this.primitiveSpill = newPrimitiveSpill; |
| this.objectSpill = newObjectSpill; |
| |
| return this; |
| } |
| |
| private static MethodHandle findOwnMH_V(final String name, final Class<?> rtype, final Class<?>... types) { |
| return MH.findVirtual(MethodHandles.lookup(), ScriptObject.class, name, MH.type(rtype, types)); |
| } |
| |
| private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) { |
| return MH.findStatic(MethodHandles.lookup(), ScriptObject.class, name, MH.type(rtype, types)); |
| } |
| |
| private static MethodHandle getKnownFunctionPropertyGuardSelf(final PropertyMap map, final MethodHandle getter, final ScriptFunction func) { |
| return MH.insertArguments(KNOWNFUNCPROPGUARDSELF, 1, map, getter, func); |
| } |
| |
| @SuppressWarnings("unused") |
| private static boolean knownFunctionPropertyGuardSelf(final Object self, final PropertyMap map, final MethodHandle getter, final ScriptFunction func) { |
| if (self instanceof ScriptObject && ((ScriptObject)self).getMap() == map) { |
| try { |
| return getter.invokeExact(self) == func; |
| } catch (final RuntimeException | Error e) { |
| throw e; |
| } catch (final Throwable t) { |
| throw new RuntimeException(t); |
| } |
| } |
| |
| return false; |
| } |
| |
| private static MethodHandle getKnownFunctionPropertyGuardProto(final PropertyMap map, final MethodHandle getter, final int depth, final ScriptFunction func) { |
| return MH.insertArguments(KNOWNFUNCPROPGUARDPROTO, 1, map, getter, depth, func); |
| } |
| |
| private static ScriptObject getProto(final ScriptObject self, final int depth) { |
| ScriptObject proto = self; |
| for (int d = 0; d < depth; d++) { |
| proto = proto.getProto(); |
| if (proto == null) { |
| return null; |
| } |
| } |
| |
| return proto; |
| } |
| |
| @SuppressWarnings("unused") |
| private static boolean knownFunctionPropertyGuardProto(final Object self, final PropertyMap map, final MethodHandle getter, final int depth, final ScriptFunction func) { |
| if (self instanceof ScriptObject && ((ScriptObject)self).getMap() == map) { |
| final ScriptObject proto = getProto((ScriptObject)self, depth); |
| if (proto == null) { |
| return false; |
| } |
| try { |
| return getter.invokeExact((Object)proto) == func; |
| } catch (final RuntimeException | Error e) { |
| throw e; |
| } catch (final Throwable t) { |
| throw new RuntimeException(t); |
| } |
| } |
| |
| return false; |
| } |
| |
| /** This is updated only in debug mode - counts number of {@code ScriptObject} instances created */ |
| private static LongAdder count; |
| |
| static { |
| if (Context.DEBUG) { |
| count = new LongAdder(); |
| } |
| } |
| /** |
| * Get number of {@code ScriptObject} instances created. If not running in debug |
| * mode this is always 0 |
| * |
| * @return number of ScriptObjects created |
| */ |
| public static long getCount() { |
| return count.longValue(); |
| } |
| } |