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