blob: 676a2b2c9b2f1e0b464acd27360b833cf3cea77e [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, 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.runtime.PropertyDescriptor.CONFIGURABLE;
import static jdk.nashorn.internal.runtime.PropertyDescriptor.ENUMERABLE;
import static jdk.nashorn.internal.runtime.PropertyDescriptor.WRITABLE;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.SwitchPoint;
import java.util.Objects;
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
/**
* This is the abstract superclass representing a JavaScript Property.
* The {@link PropertyMap} map links keys to properties, and consequently
* instances of this class make up the values in the PropertyMap
*
* @see PropertyMap
* @see AccessorProperty
* @see UserAccessorProperty
*/
public abstract class Property implements Serializable {
/*
* ECMA 8.6.1 Property Attributes
*
* We use negative flags because most properties are expected to
* be 'writable', 'configurable' and 'enumerable'. With negative flags,
* we can use leave flag byte initialized with (the default) zero value.
*/
/** Mask for property being both writable, enumerable and configurable */
public static final int WRITABLE_ENUMERABLE_CONFIGURABLE = 0b0000_0000_0000;
/** ECMA 8.6.1 - Is this property not writable? */
public static final int NOT_WRITABLE = 1 << 0;
/** ECMA 8.6.1 - Is this property not enumerable? */
public static final int NOT_ENUMERABLE = 1 << 1;
/** ECMA 8.6.1 - Is this property not configurable? */
public static final int NOT_CONFIGURABLE = 1 << 2;
private static final int MODIFY_MASK = NOT_WRITABLE | NOT_ENUMERABLE | NOT_CONFIGURABLE;
/** Is this a function parameter? */
public static final int IS_PARAMETER = 1 << 3;
/** Is parameter accessed thru arguments? */
public static final int HAS_ARGUMENTS = 1 << 4;
/** Is this a function declaration property ? */
public static final int IS_FUNCTION_DECLARATION = 1 << 5;
/**
* Is this is a primitive field given to us by Nasgen, i.e.
* something we can be sure remains a constant whose type
* is narrower than object, e.g. Math.PI which is declared
* as a double
*/
public static final int IS_NASGEN_PRIMITIVE = 1 << 6;
/** Is this a builtin property, e.g. Function.prototype.apply */
public static final int IS_BUILTIN = 1 << 7;
/** Is this property bound to a receiver? This means get/set operations will be delegated to
* a statically defined object instead of the object passed as callsite parameter. */
public static final int IS_BOUND = 1 << 8;
/** Is this a lexically scoped LET or CONST variable that is dead until it is declared. */
public static final int NEEDS_DECLARATION = 1 << 9;
/** Is this property an ES6 lexical binding? */
public static final int IS_LEXICAL_BINDING = 1 << 10;
/** Does this property support dual field representation? */
public static final int DUAL_FIELDS = 1 << 11;
/** Property key. */
private final String key;
/** Property flags. */
private int flags;
/** Property field number or spill slot. */
private final int slot;
/**
* Current type of this object, in object only mode, this is an Object.class. In dual-fields mode
* null means undefined, and primitive types are allowed. The reason a special type is used for
* undefined, is that are no bits left to represent it in primitive types
*/
private Class<?> type;
/** SwitchPoint that is invalidated when property is changed, optional */
protected transient SwitchPoint builtinSwitchPoint;
private static final long serialVersionUID = 2099814273074501176L;
/**
* Constructor
*
* @param key property key
* @param flags property flags
* @param slot property field number or spill slot
*/
Property(final String key, final int flags, final int slot) {
assert key != null;
this.key = key;
this.flags = flags;
this.slot = slot;
}
/**
* Copy constructor
*
* @param property source property
*/
Property(final Property property, final int flags) {
this.key = property.key;
this.slot = property.slot;
this.builtinSwitchPoint = property.builtinSwitchPoint;
this.flags = flags;
}
/**
* Copy function
*
* @return cloned property
*/
public abstract Property copy();
/**
* Copy function
*
* @param newType new type
* @return cloned property with new type
*/
public abstract Property copy(final Class<?> newType);
/**
* Property flag utility method for {@link PropertyDescriptor}s. Given two property descriptors,
* return the result of merging their flags.
*
* @param oldDesc first property descriptor
* @param newDesc second property descriptor
* @return merged flags.
*/
static int mergeFlags(final PropertyDescriptor oldDesc, final PropertyDescriptor newDesc) {
int propFlags = 0;
boolean value;
value = newDesc.has(CONFIGURABLE) ? newDesc.isConfigurable() : oldDesc.isConfigurable();
if (!value) {
propFlags |= NOT_CONFIGURABLE;
}
value = newDesc.has(ENUMERABLE) ? newDesc.isEnumerable() : oldDesc.isEnumerable();
if (!value) {
propFlags |= NOT_ENUMERABLE;
}
value = newDesc.has(WRITABLE) ? newDesc.isWritable() : oldDesc.isWritable();
if (!value) {
propFlags |= NOT_WRITABLE;
}
return propFlags;
}
/**
* Set the change callback for this property, i.e. a SwitchPoint
* that will be invalidated when the value of the property is
* changed
* @param sp SwitchPoint to use for change callback
*/
public final void setBuiltinSwitchPoint(final SwitchPoint sp) {
this.builtinSwitchPoint = sp;
}
/**
* Builtin properties have an invalidation switchpoint that is
* invalidated when they are set, this is a getter for it
* @return builtin switchpoint, or null if none
*/
public final SwitchPoint getBuiltinSwitchPoint() {
return builtinSwitchPoint;
}
/**
* Checks if this is a builtin property, this means that it has
* a builtin switchpoint that hasn't been invalidated by a setter
* @return true if builtin, untouched (unset) property
*/
public boolean isBuiltin() {
return builtinSwitchPoint != null && !builtinSwitchPoint.hasBeenInvalidated();
}
/**
* Property flag utility method for {@link PropertyDescriptor}. Get the property flags
* conforming to any Property using this PropertyDescriptor
*
* @param desc property descriptor
* @return flags for properties that conform to property descriptor
*/
static int toFlags(final PropertyDescriptor desc) {
int propFlags = 0;
if (!desc.isConfigurable()) {
propFlags |= NOT_CONFIGURABLE;
}
if (!desc.isEnumerable()) {
propFlags |= NOT_ENUMERABLE;
}
if (!desc.isWritable()) {
propFlags |= NOT_WRITABLE;
}
return propFlags;
}
/**
* Check whether this property has a user defined getter function. See {@link UserAccessorProperty}
* @param obj object containing getter
* @return true if getter function exists, false is default
*/
public boolean hasGetterFunction(final ScriptObject obj) {
return false;
}
/**
* Check whether this property has a user defined setter function. See {@link UserAccessorProperty}
* @param obj object containing setter
* @return true if getter function exists, false is default
*/
public boolean hasSetterFunction(final ScriptObject obj) {
return false;
}
/**
* Check whether this property is writable (see ECMA 8.6.1)
* @return true if writable
*/
public boolean isWritable() {
return (flags & NOT_WRITABLE) == 0;
}
/**
* Check whether this property is writable (see ECMA 8.6.1)
* @return true if configurable
*/
public boolean isConfigurable() {
return (flags & NOT_CONFIGURABLE) == 0;
}
/**
* Check whether this property is enumerable (see ECMA 8.6.1)
* @return true if enumerable
*/
public boolean isEnumerable() {
return (flags & NOT_ENUMERABLE) == 0;
}
/**
* Check whether this property is used as a function parameter
* @return true if parameter
*/
public boolean isParameter() {
return (flags & IS_PARAMETER) != 0;
}
/**
* Check whether this property is in an object with arguments field
* @return true if has arguments
*/
public boolean hasArguments() {
return (flags & HAS_ARGUMENTS) != 0;
}
/**
* Check whether this is a spill property, i.e. one that will not
* be stored in a specially generated field in the property class.
* The spill pool is maintained separately, as a growing Object array
* in the {@link ScriptObject}.
*
* @return true if spill property
*/
public boolean isSpill() {
return false;
}
/**
* Is this property bound to a receiver? If this method returns {@code true} get and set operations
* will be delegated to a statically bound object instead of the object passed as parameter.
*
* @return true if this is a bound property
*/
public boolean isBound() {
return (flags & IS_BOUND) != 0;
}
/**
* Is this a LET or CONST property that needs to see its declaration before being usable?
*
* @return true if this is a block-scoped variable
*/
public boolean needsDeclaration() {
return (flags & NEEDS_DECLARATION) != 0;
}
/**
* Add more property flags to the property. Properties are immutable here,
* so any property change that results in a larger flag set results in the
* property being cloned. Use only the return value
*
* @param propertyFlags flags to be OR:ed to the existing property flags
* @return new property if property set was changed, {@code this} otherwise
*/
public Property addFlags(final int propertyFlags) {
if ((this.flags & propertyFlags) != propertyFlags) {
final Property cloned = this.copy();
cloned.flags |= propertyFlags;
return cloned;
}
return this;
}
/**
* Get the flags for this property
* @return property flags
*/
public int getFlags() {
return flags;
}
/**
* Remove property flags from the property. Properties are immutable here,
* so any property change that results in a smaller flag set results in the
* property being cloned. Use only the return value
*
* @param propertyFlags flags to be subtracted from the existing property flags
* @return new property if property set was changed, {@code this} otherwise
*/
public Property removeFlags(final int propertyFlags) {
if ((this.flags & propertyFlags) != 0) {
final Property cloned = this.copy();
cloned.flags &= ~propertyFlags;
return cloned;
}
return this;
}
/**
* Reset the property for this property. Properties are immutable here,
* so any property change that results in a different flag sets results in the
* property being cloned. Use only the return value
*
* @param propertyFlags flags that are replacing from the existing property flags
* @return new property if property set was changed, {@code this} otherwise
*/
public Property setFlags(final int propertyFlags) {
if (this.flags != propertyFlags) {
final Property cloned = this.copy();
cloned.flags &= ~MODIFY_MASK;
cloned.flags |= propertyFlags & MODIFY_MASK;
return cloned;
}
return this;
}
/**
* Abstract method for retrieving the getter for the property. We do not know
* anything about the internal representation when we request the getter, we only
* know that the getter will return the property as the given type.
*
* @param type getter return value type
* @return a getter for this property as {@code type}
*/
public abstract MethodHandle getGetter(final Class<?> type);
/**
* Get an optimistic getter that throws an exception if type is not the known given one
* @param type type
* @param programPoint program point
* @return getter
*/
public abstract MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint);
/**
* Hook to initialize method handles after deserialization.
*
* @param structure the structure class
*/
abstract void initMethodHandles(final Class<?> structure);
/**
* Get the key for this property. This key is an ordinary string. The "name".
* @return key for property
*/
public String getKey() {
return key;
}
/**
* Get the field number or spill slot
* @return number/slot, -1 if none exists
*/
public int getSlot() {
return slot;
}
/**
* get the Object value of this property from {@code owner}. This allows to bypass creation of the
* getter MethodHandle for spill and user accessor properties.
*
* @param self the this object
* @param owner the owner of the property
* @return the property value
*/
public abstract int getIntValue(final ScriptObject self, final ScriptObject owner);
/**
* get the Object value of this property from {@code owner}. This allows to bypass creation of the
* getter MethodHandle for spill and user accessor properties.
*
* @param self the this object
* @param owner the owner of the property
* @return the property value
*/
public abstract double getDoubleValue(final ScriptObject self, final ScriptObject owner);
/**
* get the Object value of this property from {@code owner}. This allows to bypass creation of the
* getter MethodHandle for spill and user accessor properties.
*
* @param self the this object
* @param owner the owner of the property
* @return the property value
*/
public abstract Object getObjectValue(final ScriptObject self, final ScriptObject owner);
/**
* Set the value of this property in {@code owner}. This allows to bypass creation of the
* setter MethodHandle for spill and user accessor properties.
*
* @param self the this object
* @param owner the owner object
* @param value the new property value
* @param strict is this a strict setter?
*/
public abstract void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict);
/**
* Set the value of this property in {@code owner}. This allows to bypass creation of the
* setter MethodHandle for spill and user accessor properties.
*
* @param self the this object
* @param owner the owner object
* @param value the new property value
* @param strict is this a strict setter?
*/
public abstract void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict);
/**
* Set the value of this property in {@code owner}. This allows to bypass creation of the
* setter MethodHandle for spill and user accessor properties.
*
* @param self the this object
* @param owner the owner object
* @param value the new property value
* @param strict is this a strict setter?
*/
public abstract void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict);
/**
* Abstract method for retrieving the setter for the property. We do not know
* anything about the internal representation when we request the setter, we only
* know that the setter will take the property as a parameter of the given type.
* <p>
* Note that we have to pass the current property map from which we retrieved
* the property here. This is necessary for map guards if, e.g. the internal
* representation of the field, and consequently also the setter, changes. Then
* we automatically get a map guard that relinks the call site so that the
* older setter will never be used again.
* <p>
* see {@link ObjectClassGenerator#createSetter(Class, Class, MethodHandle, MethodHandle)}
* if you are interested in the internal details of this. Note that if you
* are running with {@code -Dnashorn.fields.objects=true}, the setters
* will currently never change, as all properties are represented as Object field,
* the Object fields are Initialized to {@code ScriptRuntime.UNDEFINED} and primitives are
* boxed/unboxed upon every access, which is not necessarily optimal
*
* @param type setter parameter type
* @param currentMap current property map for property
* @return a getter for this property as {@code type}
*/
public abstract MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap);
/**
* Get the user defined getter function if one exists. Only {@link UserAccessorProperty} instances
* can have user defined getters
* @param obj the script object
* @return user defined getter function, or {@code null} if none exists
*/
public ScriptFunction getGetterFunction(final ScriptObject obj) {
return null;
}
/**
* Get the user defined setter function if one exists. Only {@link UserAccessorProperty} instances
* can have user defined getters
* @param obj the script object
* @return user defined getter function, or {@code null} if none exists
*/
public ScriptFunction getSetterFunction(final ScriptObject obj) {
return null;
}
@Override
public int hashCode() {
final Class<?> t = getLocalType();
return Objects.hashCode(this.key) ^ flags ^ getSlot() ^ (t == null ? 0 : t.hashCode());
}
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
}
if (other == null || this.getClass() != other.getClass()) {
return false;
}
final Property otherProperty = (Property)other;
return equalsWithoutType(otherProperty) &&
getLocalType() == otherProperty.getLocalType();
}
boolean equalsWithoutType(final Property otherProperty) {
return getFlags() == otherProperty.getFlags() &&
getSlot() == otherProperty.getSlot() &&
getKey().equals(otherProperty.getKey());
}
private static String type(final Class<?> type) {
if (type == null) {
return "undef";
} else if (type == int.class) {
return "i";
} else if (type == double.class) {
return "d";
} else {
return "o";
}
}
/**
* Short toString version
* @return short toString
*/
public final String toStringShort() {
final StringBuilder sb = new StringBuilder();
final Class<?> t = getLocalType();
sb.append(getKey()).append(" (").append(type(t)).append(')');
return sb.toString();
}
private static String indent(final String str, final int indent) {
final StringBuilder sb = new StringBuilder();
sb.append(str);
for (int i = 0; i < indent - str.length(); i++) {
sb.append(' ');
}
return sb.toString();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
final Class<?> t = getLocalType();
sb.append(indent(getKey(), 20)).
append(" id=").
append(Debug.id(this)).
append(" (0x").
append(indent(Integer.toHexString(flags), 4)).
append(") ").
append(getClass().getSimpleName()).
append(" {").
append(indent(type(t), 5)).
append('}');
if (slot != -1) {
sb.append(" [").
append("slot=").
append(slot).
append(']');
}
return sb.toString();
}
/**
* Get the current type of this property. If you are running with object fields enabled,
* this will always be Object.class. See the value representation explanation in
* {@link Property#getSetter(Class, PropertyMap)} and {@link ObjectClassGenerator}
* for more information.
*
* <p>Note that for user accessor properties, this returns the type of the last observed
* value passed to or returned by a user accessor. Use {@link #getLocalType()} to always get
* the type of the actual value stored in the property slot.</p>
*
* @return current type of property, null means undefined
*/
public final Class<?> getType() {
return type;
}
/**
* Set the type of this property.
* @param type new type
*/
public final void setType(final Class<?> type) {
assert type != boolean.class : "no boolean storage support yet - fix this";
this.type = type == null ? null : type.isPrimitive() ? type : Object.class;
}
/**
* Get the type of the value in the local property slot. This returns the same as
* {@link #getType()} for normal properties, but always returns {@code Object.class}
* for {@link UserAccessorProperty}s as their local type is a pair of accessor references.
*
* @return the local property type
*/
protected Class<?> getLocalType() {
return getType();
}
/**
* Check whether this Property can ever change its type. The default is false, and if
* you are not running with dual fields, the type is always object and can never change
* @return true if this property can change types
*/
public boolean canChangeType() {
return false;
}
/**
* Check whether this property represents a function declaration.
* @return whether this property is a function declaration or not.
*/
public boolean isFunctionDeclaration() {
return (flags & IS_FUNCTION_DECLARATION) != 0;
}
/**
* Is this a property defined by ES6 let or const?
* @return true if this property represents a lexical binding.
*/
public boolean isLexicalBinding() {
return (flags & IS_LEXICAL_BINDING) != 0;
}
/**
* Does this property support dual fields for both primitive and object values?
* @return true if supports dual fields
*/
public boolean hasDualFields() {
return (flags & DUAL_FIELDS) != 0;
}
}