/*
* 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.
Notes:
- The map is used to identify properties in the object.
- 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.
- Modifications of the map include adding/deleting attributes or changing a
function field value.
/**
* 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. /** __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" /** 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" /** 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? /** Per ScriptObject flag - is this an array object? */
public static final int IS_ARRAY = 1 << 0;
Per ScriptObject flag - is this an arguments object? /** Per ScriptObject flag - is this an arguments object? */
public static final int IS_ARGUMENTS = 1 << 1;
Is length property not-writable? /** Is length property not-writable? */
public static final int IS_LENGTH_NOT_WRITABLE = 1 << 2;
Is this a builtin object? /** 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? /** 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 primitiveSpill
and objectSpill
when full /**
* 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. /** Map to property information and accessor functions. Ordered by insertion. */
private PropertyMap map;
objects proto. /** objects proto. */
private ScriptObject proto;
Object flags. /** Object flags. */
private int flags;
Area for primitive properties added to object after instantiation, see AccessorProperty
/** 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 AccessorProperty
/** Area for reference properties added to object after instantiation, see {@link AccessorProperty} */
protected Object[] objectSpill;
Indexed array data. /** Indexed array data. */
private ArrayData arrayData;
Method handle to retrieve prototype of this object /** 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 /** 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 /** 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 /** 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 /** 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 /** 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 /** 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 /** 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 /** 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 /** 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 /** 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
/**
* Constructor
*/
public ScriptObject() {
this(null);
}
Constructor
Params: - map –
PropertyMap
used to create the initial object
/**
* 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 proto
and property map to map
without invalidating the map as calling setProto(ScriptObject)
would do. This should only be used for objects that are always constructed with the same combination of prototype and property map. Params: - proto – the prototype object
- map – initial
PropertyMap
/**
* 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.
Params: - map – property maps
- primitiveSpill – primitive spills
- objectSpill – reference spills
/**
* 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
Returns: true if global
/**
* 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
Params: - nProperties – number of properties
Returns: property buffer length, a multiple of SPILL_RATE
/**
* 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
Params: - source – The source object to copy from.
/**
* 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.
Params: - source – The source object to copy from.
- properties – The array of properties to copy.
/**
* 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 source
, using the interim property map propMap
, and return the new interim property map. Params: - propMap – the property map
- source – the source object
- property – the property to be added
- extensible – whether the current object is extensible or not
Returns: the new property map
/**
* 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.
Params: - source – The source object to copy from.
- properties – The collection of accessor properties to copy.
/**
* 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).
Params: - methodHandle – Method handle to bind to.
- receiver – Object to bind.
Returns: Bound method handle.
/**
* 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.
Returns: Property iterator.
/**
* Return a property iterator.
* @return Property iterator.
*/
public Iterator<String> propertyIterator() {
return new KeyIterator(this);
}
Return a property value iterator.
Returns: Property value iterator.
/**
* Return a property value iterator.
* @return Property value iterator.
*/
public Iterator<Object> valueIterator() {
return new ValueIterator(this);
}
ECMA 8.10.1 IsAccessorDescriptor ( Desc )
Returns: true if this has a AccessorPropertyDescriptor
with a getter or a setter
/**
* 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 )
Returns: true if this has a DataPropertyDescriptor
, i.e. the object has a property value and is writable
/**
* 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 )
Returns: property descriptor
/**
* 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 )
Params: - global – global scope object
- obj – object to create property descriptor from
Returns: property descriptor
/**
* 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)
Params: - key – property key
Returns: Returns the Property Descriptor of the named own property of this
object, or undefined if absent.
/**
* 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)
Params: - key – property key
Returns: Returns the fully populated Property Descriptor of the named property
of this object, or undefined if absent.
/**
* 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 key
. Params: - key – the property name
/**
* 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)
Params: - key – the property key
- propertyDesc – the property descriptor
- reject – is the property extensible - true means new definitions are rejected
Returns: true if property was successfully defined
/**
* 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).
Params: - index – key for property
- value – value to define
/**
* 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.
Params: - key – property key
- propertyDesc – property descriptor for property
/**
* 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)
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.NativeArraygetProperty(String)
or {jdk.nashorn.internal.objects.NativeArraygetPropertyDescriptor(String)
as the overriding way to find array properties
Params: - key – Property key.
- deep – Whether the search should look up proto chain.
See Also: Returns: FindPropertyData or null if not found.
/**
* 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)
Find a property in the prototype hierarchy. Note: this is not a good idea to override except as it was done in WithObject
. If you have to, use {jdk.nashorn.internal.objects.NativeArraygetProperty(String)
or {jdk.nashorn.internal.objects.NativeArraygetPropertyDescriptor(String)
as the overriding way to find array properties
Params: - key – Property key.
- deep – true if the search should look up proto chain
- isScope – true if this is a scope access
- start – the object on which the lookup was originally initiated
See Also: Returns: FindPropertyData or null if not found.
/**
* 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 findProperty(Object, boolean)
but returns a boolean
value instead of a FindProperty
object. Params: - key – Property key.
- deep – Whether the search should look up proto chain.
Returns: true if the property was found.
/**
* 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.
This a more "low level" way that doesn't involve PropertyDescriptor
s
Params: - key – Property key.
- propertyFlags – Property flags.
- getter – Property getter, or null if not defined
- setter – Property setter, or null if not defined
Returns: New property.
/**
* 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.
This a more "low level" way that doesn't involve PropertyDescriptor
s
Params: - key – Property key.
- propertyFlags – Property flags.
- value – Value of property
Returns: New property.
/**
* 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.
This a more "low level" way that doesn't involve PropertyDescriptor
s
Params: - newProperty – property to add
Returns: New property.
/**
* 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.
Params: - property – Property to delete.
Returns: true if deleted.
/**
* 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"
Params: - key – property key
- propertyFlags – flags
- getter – getter for
UserAccessorProperty
, null if not present or N/A - setter – setter for
UserAccessorProperty
, null if not present or N/A
/**
* 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
Params: - oldProperty – property to modify
- propertyFlags – new property flags
- getter – getter for
UserAccessorProperty
, null if not present or N/A - setter – setter for
UserAccessorProperty
, null if not present or N/A
Returns: new property
/**
* 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
Params: - oldProperty – property to modify
- propertyFlags – new property flags
Returns: new property
/**
* 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
Params: - oldProperty – property to replace
- newProperty – property to replace it with
Returns: new property
/**
* 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.
Params: - key – Property key.
- getter –
UserAccessorProperty
defined getter, or null if none - setter –
UserAccessorProperty
defined setter, or null if none
/**
* 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.
Params: - find – data from find property.
- type – method type of function.
- bindName – null or name to bind to second argument (property not found method.)
Returns: value of property as a MethodHandle or null.
/**
* 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.
Params: - value – value of receiver, it not a
ScriptFunction
this will return null. - type – method type of function.
- bindName – null or name to bind to second argument (property not found method.)
Returns: value of property as a MethodHandle or null.
/**
* 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.
Params: - property – Found property.
Returns: Value of property.
/**
* 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
Params: - key – property key
Returns: property for key
/**
* 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 NativeArguments
class (internal use.) Used for argument access in a vararg function using parameter name. Returns the argument at a given key (index) Params: - key – argument index
Returns: the argument at the given position, or undefined if not present
/**
* 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 NativeArguments
class (internal use.) Used for argument access in a vararg function using parameter name. Returns the argument at a given key (index) Params: - key – argument index
- value – the value to write at the given index
/**
* 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.
Returns: Current context.
/**
* 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.
Returns: PropertyMap object.
/**
* Return the map of an object.
* @return PropertyMap object.
*/
public final PropertyMap getMap() {
return map;
}
Set the initial map.
Params: - map – Initial 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.
Params: - oldMap – Map prior to manipulation.
- newMap – Replacement map.
Returns: true if the operation succeeded.
/**
* 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.
Returns: __proto__ object.
/**
* Return the __proto__ of an object.
* @return __proto__ object.
*/
public final ScriptObject getProto() {
return proto;
}
Get the proto of a specific depth
Params: - n – depth
Returns: proto at given depth
/**
* 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.
Params: - newProto – new __proto__ to set.
/**
* 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 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. Params: - initialProto – the initial __proto__ to set.
/**
* 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.
Params: - obj – the object literal that needs to have its prototype initialized to the global Object prototype.
/**
* 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)
Params: - newProto – Prototype to set.
/**
* 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__".
Params: - newProto – Prototype to set.
/**
* 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.
Returns: Array of keys, order of properties is undefined.
/**
* 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.
Params: - all – True if to include non-enumerable keys.
Returns: Array of keys.
/**
* 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.
Params: - all – True if to include non-enumerable keys.
Returns: Array of keys.
/**
* 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.
Params: - type – the type of keys to return, either
String.class
or Symbol.class
. - all – True if to include non-enumerable keys.
- nonEnumerable – set of non-enumerable properties seen already. Used to
filter out shadowed, but enumerable properties from proto children.
Type parameters: - <T> – the type returned keys.
Returns: Array of keys.
/**
* 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.
Returns: true if array entries exists.
/**
* 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
Returns: "Object"
/**
* Return the valid JavaScript type name descriptor
*
* @return "Object"
*/
public String getClassName() {
return "Object";
}
length
is a well known property. This is its getter. Note that this *may* be optimized by other classes Returns: length property value for this ScriptObject
/**
* {@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.
Returns: string description of this object, e.g. [object Object]
/**
* 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.
A hint
of null means "no hint".
ECMA 8.12.8 [[DefaultValue]](hint)
Params: - typeHint – the preferred type hint
Returns: the default value
/**
* 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 ScriptFunction
for hasInstance implementation, walks the proto chain Params: - instance – instance to check
Returns: true if 'instance' is an instance of this object
/**
* 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
Returns: the object after being made non extensible
/**
* 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
Params: - obj – object to check
Returns: true if array
/**
* 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
Returns: true if array
/**
* 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
/**
* Flag this ScriptObject as being an array
*/
public final void setIsArray() {
flags |= IS_ARRAY;
}
Check if this ScriptObject is an arguments
vector Returns: true if arguments vector
/**
* 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 arguments
vector /**
* 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
Returns: true
if 'length' property is non-writable
/**
* 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.
/**
* Flag this object as having non-writable length property.
*/
public void setIsLengthNotWritable() {
flags |= IS_LENGTH_NOT_WRITABLE;
}
Get the ArrayData
, for this ScriptObject, ensuring it is of a type that can handle elementType Params: - elementType – elementType
Returns: array data
/**
* 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 ArrayData
for this ScriptObject if it is an array Returns: array data
/**
* Get the {@link ArrayData} for this ScriptObject if it is an array
* @return array data
*/
public final ArrayData getArray() {
return arrayData;
}
Set the ArrayData
for this ScriptObject if it is to be an array Params: - arrayData – the array data
/**
* 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
Returns: true if extensible
/**
* Check if this ScriptObject is extensible
* @return true if extensible
*/
public boolean isExtensible() {
return getMap().isExtensible();
}
ECMAScript 15.2.3.8 - seal implementation
Returns: the sealed ScriptObject
/**
* 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
Returns: true if sealed
/**
* 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
Returns: the frozen ScriptObject
/**
* 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
Returns: true if frozen
/**
* Check whether this ScriptObject is frozen
* @return true if frozen
*/
public boolean isFrozen() {
return getMap().isFrozen();
}
Check whether this ScriptObject is scope
Returns: true if scope
/**
* Check whether this ScriptObject is scope
* @return true if scope
*/
public boolean isScope() {
return false;
}
Tag this script object as built in
/**
* Tag this script object as built in
*/
public final void setIsBuiltin() {
flags |= IS_BUILTIN;
}
Check if this script object is built in
Returns: true if build in
/**
* 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.
/**
* 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.
Returns: true if 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)
Params: - strict – strict mode or not
/**
* 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)
Params: - key – the key to check for
Returns: true if a property with the given key exists, false otherwise
/**
* 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)
Params: - value – value to check for
Returns: true if a property with the given value exists, false otherwise
/**
* 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 <property, value> entries that make up this ScriptObject's properties (java.util.Map-like method to help ScriptObjectMirror implementation) Returns: an entry set of all the properties in this object
/**
* 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)
Returns: true if object has no properties
/**
* 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)
Returns: keySet of this ScriptObject
/**
* 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)
Params: - key – property key
- value – property value
- strict – strict mode or not
Returns: oldValue if property with same key existed already
/**
* 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)
Params: - otherMap – a <key,value> map of properties to add
- strict – strict mode or not
/**
* 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)
Params: - key – the key of the property
- strict – strict mode or not
Returns: the oldValue of the removed property
/**
* 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)
Returns: number of properties in ScriptObject
/**
* 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)
Returns: collection of values for the properties in this ScriptObject
/**
* 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).
Params: - desc – call site descriptor
- request – the link request
Returns: GuardedInvocation for the callsite
/**
* 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.
Params: - desc – The invoke dynamic call site descriptor.
- request – The link request
Returns: GuardedInvocation to be invoked at call site.
/**
* 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
Params: - desc – the call site descriptor.
- request – the link request
Returns: GuardedInvocation to be invoked at call site.
/**
* 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.
Returns: true if a with-object was found
/**
* 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;
}
Params: - methodHandle – a method handle
- depth – distance to target prototype
Returns: the filtered method handle
/**
* 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.
Params: - desc – the call site descriptor
- request – the link request
Returns: GuardedInvocation to be invoked at call site.
/**
* 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.
Params: - desc – the call site descriptor
- request – the link request
Returns: GuardedInvocation to be invoked at call site.
/**
* 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
Params: - returnType – return type for getter
- name – name
- elementType – index type for getter
- desc – call site descriptor
Returns: method handle for getter
/**
* 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 name
that will be invalidated when the property definition is changed in this object's prototype chain. Returns null
if the property is defined in this object itself. Params: - name – the property name
- owner – the property owner, null if property is not defined
Returns: a SwitchPoint or null
/**
* 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.
Params: - desc – the call site descriptor
- request – the link request
Returns: GuardedInvocation to be invoked at call site.
/**
* 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. NativeArray
provides special quick accessor linkage for continuous arrays that are represented as Java arrays Params: - desc – call site descriptor
- request – link request
Returns: GuardedInvocation to be invoked at call site.
/**
* 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.
Params: - desc – the call site descriptor
- explicitInstanceOfCheck – add an explicit instanceof check?
- callType – the method type at the call site
Returns: GuardedInvocation to be invoked at call site.
/**
* 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.
Params: - desc – The call site descriptor
- request – the link request
Returns: GuardedInvocation to be invoked at call site.
/**
* 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.
Params: - desc – the call site descriptor.
- request – the link request
Returns: GuardedInvocation to be invoked at call site.
/**
* 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.
Params: - key – Name of property.
- isScope – is this a scope access?
- programPoint – program point
Returns: Result from call.
/**
* 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 name
if it is defined. Params: - name – the method name
- isScope – is this a scope access?
Returns: the bound function, or undefined
/**
* 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.
Params: - key – Property key.
- flags – Property flags.
Returns: Added property.
/**
* 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.
Params: - key – Property key.
Returns: Setter method handle.
/**
* 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.
Params: - methodHandle – method handle for invoke
- callType – type of the call
Returns: method handle with adjusted arguments
/**
* 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.
Params: - methodHandle – MethodHandle to adjust.
- callType – MethodType of the call site.
- callerVarArg – true if the caller is vararg, false otherwise, null if it should be inferred from the
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 LinkerCallSite.ARGLIMIT
parameters.
Returns: method handle with adjusted arguments
/**
* 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
Params: - newLength – new length to set
/**
* 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
Params: - find – found property
- callSiteFlags – callsite flags
- key – property key
- value – property value
/**
* 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.
Returns: a shallow copy.
/**
* 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.
Params: - key – the property name
- propertyFlags – attribute flags of the property
- getter – getter function for the property
- setter – setter function for the property
Returns: the newly created UserAccessorProperty
/**
* 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 true
if properties for this object should use dual field mode, false
otherwise. Returns: true
if dual fields should be used.
/**
* 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 ScriptObject
instances created /** 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 ScriptObject
instances created. If not running in debug mode this is always 0 Returns: number of ScriptObjects created
/**
* 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();
}
}