/* * 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. *

* 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. *

* 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. * *

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.

* * @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; } }