/*
 * 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.runtime.PropertyHashMap.EMPTY_HASHMAP;
import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex;
import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.invoke.SwitchPoint;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.LongAdder;
import jdk.nashorn.internal.runtime.options.Options;
import jdk.nashorn.internal.scripts.JO;

Map of object properties. The PropertyMap is the "template" for JavaScript object layouts. It contains a map with prototype names as keys and Property instances as values. A PropertyMap is typically passed to the ScriptObject constructor to form the seed map for the ScriptObject.

All property maps are immutable. If a property is added, modified or removed, the mutator will return a new map.

/** * Map of object properties. The PropertyMap is the "template" for JavaScript object * layouts. It contains a map with prototype names as keys and {@link Property} instances * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor * to form the seed map for the ScriptObject. * <p> * All property maps are immutable. If a property is added, modified or removed, the mutator * will return a new map. */
public class PropertyMap implements Iterable<Object>, Serializable { private static final int INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT = Math.max(0, Options.getIntProperty("nashorn.propertyMap.softReferenceDerivationLimit", 32));
Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See ScriptObject.preventExtensions()
/** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */
private static final int NOT_EXTENSIBLE = 0b0000_0001;
Does this map contain valid array keys?
/** Does this map contain valid array keys? */
private static final int CONTAINS_ARRAY_KEYS = 0b0000_0010;
Map status flags.
/** Map status flags. */
private final int flags;
Map of properties.
/** Map of properties. */
private transient PropertyHashMap properties;
Number of fields in use.
/** Number of fields in use. */
private final int fieldCount;
Number of fields available.
/** Number of fields available. */
private final int fieldMaximum;
Length of spill in use.
/** Length of spill in use. */
private final int spillLength;
Structure class name
/** Structure class name */
private final String className;
Countdown of number of times this property map has been derived from another property map. When it reaches zero, the property map will start using weak references instead of soft references to hold on to its history elements.
/** * Countdown of number of times this property map has been derived from another property map. When it * reaches zero, the property map will start using weak references instead of soft references to hold on * to its history elements. */
private final int softReferenceDerivationLimit;
A reference to the expected shared prototype property map. If this is set this property map should only be used if it the same as the actual prototype map.
/** A reference to the expected shared prototype property map. If this is set this * property map should only be used if it the same as the actual prototype map. */
private transient SharedPropertyMap sharedProtoMap;
SwitchPoints for gets on inherited properties.
/** {@link SwitchPoint}s for gets on inherited properties. */
private transient HashMap<Object, SwitchPoint> protoSwitches;
History of maps, used to limit map duplication.
/** History of maps, used to limit map duplication. */
private transient WeakHashMap<Property, Reference<PropertyMap>> history;
History of prototypes, used to limit map duplication.
/** History of prototypes, used to limit map duplication. */
private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory;
property listeners
/** property listeners */
private transient PropertyListeners listeners; private transient BitSet freeSlots; private static final long serialVersionUID = -7041836752008732533L;
Constructs a new property map.
Params:
  • properties – A PropertyHashMap with initial contents.
  • fieldCount – Number of fields in use.
  • fieldMaximum – Number of fields available.
  • spillLength – Number of spill slots used.
/** * Constructs a new property map. * * @param properties A {@link PropertyHashMap} with initial contents. * @param fieldCount Number of fields in use. * @param fieldMaximum Number of fields available. * @param spillLength Number of spill slots used. */
private PropertyMap(final PropertyHashMap properties, final int flags, final String className, final int fieldCount, final int fieldMaximum, final int spillLength) { this.properties = properties; this.className = className; this.fieldCount = fieldCount; this.fieldMaximum = fieldMaximum; this.spillLength = spillLength; this.flags = flags; this.softReferenceDerivationLimit = INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT; if (Context.DEBUG) { count.increment(); } }
Constructs a clone of propertyMap with changed properties, flags, or boundaries.
Params:
  • propertyMap – Existing property map.
  • properties – A PropertyHashMap with a new set of properties.
/** * Constructs a clone of {@code propertyMap} with changed properties, flags, or boundaries. * * @param propertyMap Existing property map. * @param properties A {@link PropertyHashMap} with a new set of properties. */
private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength, final int softReferenceDerivationLimit) { this.properties = properties; this.flags = flags; this.spillLength = spillLength; this.fieldCount = fieldCount; this.fieldMaximum = propertyMap.fieldMaximum; this.className = propertyMap.className; // We inherit the parent property listeners instance. It will be cloned when a new listener is added. this.listeners = propertyMap.listeners; this.freeSlots = propertyMap.freeSlots; this.sharedProtoMap = propertyMap.sharedProtoMap; this.softReferenceDerivationLimit = softReferenceDerivationLimit; if (Context.DEBUG) { count.increment(); clonedCount.increment(); } }
Constructs an exact clone of propertyMap.
Params:
  • propertyMap – Existing property map.
/** * Constructs an exact clone of {@code propertyMap}. * * @param propertyMap Existing property map. */
protected PropertyMap(final PropertyMap propertyMap) { this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength, propertyMap.softReferenceDerivationLimit); } private void writeObject(final ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeObject(properties.getProperties()); } private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); final Property[] props = (Property[]) in.readObject(); this.properties = EMPTY_HASHMAP.immutableAdd(props); assert className != null; final Class<?> structure = Context.forStructureClass(className); for (final Property prop : props) { prop.initMethodHandles(structure); } }
Public property map allocator.

It is the caller's responsibility to make sure that properties does not contain properties with keys that are valid array indices.

Params:
  • properties – Collection of initial properties.
  • className – class name
  • fieldCount – Number of fields in use.
  • fieldMaximum – Number of fields available.
  • spillLength – Number of used spill slots.
Returns:New PropertyMap.
/** * Public property map allocator. * * <p>It is the caller's responsibility to make sure that {@code properties} does not contain * properties with keys that are valid array indices.</p> * * @param properties Collection of initial properties. * @param className class name * @param fieldCount Number of fields in use. * @param fieldMaximum Number of fields available. * @param spillLength Number of used spill slots. * @return New {@link PropertyMap}. */
public static PropertyMap newMap(final Collection<Property> properties, final String className, final int fieldCount, final int fieldMaximum, final int spillLength) { final PropertyHashMap newProperties = EMPTY_HASHMAP.immutableAdd(properties); return new PropertyMap(newProperties, 0, className, fieldCount, fieldMaximum, spillLength); }
Public property map allocator. Used by nasgen generated code.

It is the caller's responsibility to make sure that properties does not contain properties with keys that are valid array indices.

Params:
  • properties – Collection of initial properties.
Returns:New PropertyMap.
/** * Public property map allocator. Used by nasgen generated code. * * <p>It is the caller's responsibility to make sure that {@code properties} does not contain * properties with keys that are valid array indices.</p> * * @param properties Collection of initial properties. * @return New {@link PropertyMap}. */
public static PropertyMap newMap(final Collection<Property> properties) { return properties == null || properties.isEmpty()? newMap() : newMap(properties, JO.class.getName(), 0, 0, 0); }
Return a sharable empty map for the given object class.
Params:
  • clazz – the base object class
Returns:New empty PropertyMap.
/** * Return a sharable empty map for the given object class. * @param clazz the base object class * @return New empty {@link PropertyMap}. */
public static PropertyMap newMap(final Class<? extends ScriptObject> clazz) { return new PropertyMap(EMPTY_HASHMAP, 0, clazz.getName(), 0, 0, 0); }
Return a sharable empty map.
Returns:New empty PropertyMap.
/** * Return a sharable empty map. * * @return New empty {@link PropertyMap}. */
public static PropertyMap newMap() { return newMap(JO.class); }
Return number of properties in the map.
Returns:Number of properties.
/** * Return number of properties in the map. * * @return Number of properties. */
public int size() { return properties.size(); }
Get the number of listeners of this map
Returns:the number of listeners
/** * Get the number of listeners of this map * * @return the number of listeners */
public int getListenerCount() { return listeners == null ? 0 : listeners.getListenerCount(); }
Add listenerMap as a listener to this property map for the given key.
Params:
  • key – the property name
  • listenerMap – the listener map
/** * Add {@code listenerMap} as a listener to this property map for the given {@code key}. * * @param key the property name * @param listenerMap the listener map */
public void addListener(final String key, final PropertyMap listenerMap) { if (listenerMap != this) { // We need to clone listener instance when adding a new listener since we share // the listeners instance with our parent maps that don't need to see the new listener. listeners = PropertyListeners.addListener(listeners, key, listenerMap); } }
A new property is being added.
Params:
  • property – The new Property added.
  • isSelf – was the property added to this map?
/** * A new property is being added. * * @param property The new Property added. * @param isSelf was the property added to this map? */
public void propertyAdded(final Property property, final boolean isSelf) { if (!isSelf) { invalidateProtoSwitchPoint(property.getKey()); } if (listeners != null) { listeners.propertyAdded(property); } }
An existing property is being deleted.
Params:
  • property – The property being deleted.
  • isSelf – was the property deleted from this map?
/** * An existing property is being deleted. * * @param property The property being deleted. * @param isSelf was the property deleted from this map? */
public void propertyDeleted(final Property property, final boolean isSelf) { if (!isSelf) { invalidateProtoSwitchPoint(property.getKey()); } if (listeners != null) { listeners.propertyDeleted(property); } }
An existing property is being redefined.
Params:
  • oldProperty – The old property
  • newProperty – The new property
  • isSelf – was the property modified on this map?
/** * An existing property is being redefined. * * @param oldProperty The old property * @param newProperty The new property * @param isSelf was the property modified on this map? */
public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) { if (!isSelf) { invalidateProtoSwitchPoint(oldProperty.getKey()); } if (listeners != null) { listeners.propertyModified(oldProperty, newProperty); } }
The prototype of an object associated with this PropertyMap is changed.
Params:
  • isSelf – was the prototype changed on the object using this map?
/** * The prototype of an object associated with this {@link PropertyMap} is changed. * * @param isSelf was the prototype changed on the object using this map? */
public void protoChanged(final boolean isSelf) { if (!isSelf) { invalidateAllProtoSwitchPoints(); } else if (sharedProtoMap != null) { sharedProtoMap.invalidateSwitchPoint(); } if (listeners != null) { listeners.protoChanged(); } }
Return a SwitchPoint used to track changes of a property in a prototype.
Params:
  • key – Property key.
Returns:A shared SwitchPoint for the property.
/** * Return a SwitchPoint used to track changes of a property in a prototype. * * @param key Property key. * @return A shared {@link SwitchPoint} for the property. */
public synchronized SwitchPoint getSwitchPoint(final String key) { if (protoSwitches == null) { protoSwitches = new HashMap<>(); } SwitchPoint switchPoint = protoSwitches.get(key); if (switchPoint == null) { switchPoint = new SwitchPoint(); protoSwitches.put(key, switchPoint); } return switchPoint; }
Indicate that a prototype property has changed.
Params:
/** * Indicate that a prototype property has changed. * * @param key {@link Property} key to invalidate. */
synchronized void invalidateProtoSwitchPoint(final Object key) { if (protoSwitches != null) { final SwitchPoint sp = protoSwitches.get(key); if (sp != null) { protoSwitches.remove(key); if (Context.DEBUG) { protoInvalidations.increment(); } SwitchPoint.invalidateAll(new SwitchPoint[]{sp}); } } }
Indicate that proto itself has changed in hierarchy somewhere.
/** * Indicate that proto itself has changed in hierarchy somewhere. */
synchronized void invalidateAllProtoSwitchPoints() { if (protoSwitches != null) { final int size = protoSwitches.size(); if (size > 0) { if (Context.DEBUG) { protoInvalidations.add(size); } SwitchPoint.invalidateAll(protoSwitches.values().toArray(new SwitchPoint[0])); protoSwitches.clear(); } } }
Add a property to the map, re-binding its getters and setters, if available, to a given receiver. This is typically the global scope. See ScriptObject.addBoundProperties(ScriptObject)
Params:
  • property – Property being added.
  • bindTo – Object to bind to.
Returns:New PropertyMap with Property added.
/** * Add a property to the map, re-binding its getters and setters, * if available, to a given receiver. This is typically the global scope. See * {@link ScriptObject#addBoundProperties(ScriptObject)} * * @param property {@link Property} being added. * @param bindTo Object to bind to. * * @return New {@link PropertyMap} with {@link Property} added. */
PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) { // We must not store bound property in the history as bound properties can't be reused. return addPropertyNoHistory(new AccessorProperty(property, bindTo)); } // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum. private int logicalSlotIndex(final Property property) { final int slot = property.getSlot(); if (slot < 0) { return -1; } return property.isSpill() ? slot + fieldMaximum : slot; } private int newSpillLength(final Property newProperty) { return newProperty.isSpill() ? Math.max(spillLength, newProperty.getSlot() + 1) : spillLength; } private int newFieldCount(final Property newProperty) { return !newProperty.isSpill() ? Math.max(fieldCount, newProperty.getSlot() + 1) : fieldCount; } private int newFlags(final Property newProperty) { return isValidArrayIndex(getArrayIndex(newProperty.getKey())) ? flags | CONTAINS_ARRAY_KEYS : flags; } // Update the free slots bitmap for a property that has been deleted and/or added. This method is not synchronized // as it is always invoked on a newly created instance. private void updateFreeSlots(final Property oldProperty, final Property newProperty) { // Free slots bitset is possibly shared with parent map, so we must clone it before making modifications. boolean freeSlotsCloned = false; if (oldProperty != null) { final int slotIndex = logicalSlotIndex(oldProperty); if (slotIndex >= 0) { final BitSet newFreeSlots = freeSlots == null ? new BitSet() : (BitSet)freeSlots.clone(); assert !newFreeSlots.get(slotIndex); newFreeSlots.set(slotIndex); freeSlots = newFreeSlots; freeSlotsCloned = true; } } if (freeSlots != null && newProperty != null) { final int slotIndex = logicalSlotIndex(newProperty); if (slotIndex > -1 && freeSlots.get(slotIndex)) { final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone()); newFreeSlots.clear(slotIndex); freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots; } } }
Add a property to the map without adding it to the history. This should be used for properties that can't be shared such as bound properties, or properties that are expected to be added only once.
Params:
Returns:New PropertyMap with Property added.
/** * Add a property to the map without adding it to the history. This should be used for properties that * can't be shared such as bound properties, or properties that are expected to be added only once. * * @param property {@link Property} being added. * @return New {@link PropertyMap} with {@link Property} added. */
public final PropertyMap addPropertyNoHistory(final Property property) { propertyAdded(property, true); return addPropertyInternal(property); }
Add a property to the map. Cloning or using an existing map if available.
Params:
Returns:New PropertyMap with Property added.
/** * Add a property to the map. Cloning or using an existing map if available. * * @param property {@link Property} being added. * * @return New {@link PropertyMap} with {@link Property} added. */
public final synchronized PropertyMap addProperty(final Property property) { propertyAdded(property, true); PropertyMap newMap = checkHistory(property); if (newMap == null) { newMap = addPropertyInternal(property); addToHistory(property, newMap); } return newMap; } private PropertyMap deriveMap(final PropertyHashMap newProperties, final int newFlags, final int newFieldCount, final int newSpillLength) { return new PropertyMap(this, newProperties, newFlags, newFieldCount, newSpillLength, softReferenceDerivationLimit == 0 ? 0 : softReferenceDerivationLimit - 1); } private PropertyMap addPropertyInternal(final Property property) { final PropertyHashMap newProperties = properties.immutableAdd(property); final PropertyMap newMap = deriveMap(newProperties, newFlags(property), newFieldCount(property), newSpillLength(property)); newMap.updateFreeSlots(null, property); return newMap; }
Remove a property from a map. Cloning or using an existing map if available.
Params:
Returns:New PropertyMap with Property removed or null if not found.
/** * Remove a property from a map. Cloning or using an existing map if available. * * @param property {@link Property} being removed. * * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found. */
public final synchronized PropertyMap deleteProperty(final Property property) { propertyDeleted(property, true); PropertyMap newMap = checkHistory(property); final Object key = property.getKey(); if (newMap == null && properties.containsKey(key)) { final PropertyHashMap newProperties = properties.immutableRemove(key); final boolean isSpill = property.isSpill(); final int slot = property.getSlot(); // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count. // Otherwise mark it as free in free slots bitset. if (isSpill && slot >= 0 && slot == spillLength - 1) { newMap = deriveMap(newProperties, flags, fieldCount, spillLength - 1); newMap.freeSlots = freeSlots; } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) { newMap = deriveMap(newProperties, flags, fieldCount - 1, spillLength); newMap.freeSlots = freeSlots; } else { newMap = deriveMap(newProperties, flags, fieldCount, spillLength); newMap.updateFreeSlots(property, null); } addToHistory(property, newMap); } return newMap; }
Replace an existing property with a new one.
Params:
  • oldProperty – Property to replace.
  • newProperty – New Property.
Returns:New PropertyMap with Property replaced.
/** * Replace an existing property with a new one. * * @param oldProperty Property to replace. * @param newProperty New {@link Property}. * * @return New {@link PropertyMap} with {@link Property} replaced. */
public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) { propertyModified(oldProperty, newProperty, true); /* * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods. * * This replaceProperty method is called only for the following three cases: * * 1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots. * 2. To change one UserAccessor property with another - user getter or setter changed via * Object.defineProperty function. Again, same spill slots are re-used. * 3. Via ScriptObject.setUserAccessors method to set user getter and setter functions * replacing the dummy AccessorProperty with null method handles (added during map init). * * In case (1) and case(2), the property type of old and new property is same. For case (3), * the old property is an AccessorProperty and the new one is a UserAccessorProperty property. */ final boolean sameType = oldProperty.getClass() == newProperty.getClass(); assert sameType || oldProperty instanceof AccessorProperty && newProperty instanceof UserAccessorProperty : "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]"; /* * spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need * to add spill count of the newly added UserAccessorProperty property. */ final int newSpillLength = sameType ? spillLength : Math.max(spillLength, newProperty.getSlot() + 1); // Add replaces existing property. final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty); final PropertyMap newMap = deriveMap(newProperties, flags, fieldCount, newSpillLength); if (!sameType) { newMap.updateFreeSlots(oldProperty, newProperty); } return newMap; }
Make a new UserAccessorProperty property. getter and setter functions are stored in this ScriptObject and slot values are used in property object. Note that slots are assigned speculatively and should be added to map before adding other properties.
Params:
  • key – the property name
  • propertyFlags – attribute flags of 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. Note that slots * are assigned speculatively and should be added to map before adding other * properties. * * @param key the property name * @param propertyFlags attribute flags of the property * @return the newly created UserAccessorProperty */
public final UserAccessorProperty newUserAccessors(final Object key, final int propertyFlags) { return new UserAccessorProperty(key, propertyFlags, getFreeSpillSlot()); }
Find a property in the map.
Params:
  • key – Key to search for.
Returns:Property matching key.
/** * Find a property in the map. * * @param key Key to search for. * * @return {@link Property} matching key. */
public final Property findProperty(final Object key) { return properties.find(key); }
Adds all map properties from another map.
Params:
  • other – The source of properties.
Returns:New PropertyMap with added properties.
/** * Adds all map properties from another map. * * @param other The source of properties. * * @return New {@link PropertyMap} with added properties. */
public final PropertyMap addAll(final PropertyMap other) { assert this != other : "adding property map to itself"; final Property[] otherProperties = other.properties.getProperties(); final PropertyHashMap newProperties = properties.immutableAdd(otherProperties); final PropertyMap newMap = deriveMap(newProperties, flags, fieldCount, spillLength); for (final Property property : otherProperties) { // This method is only safe to use with non-slotted, native getter/setter properties assert property.getSlot() == -1; assert !(isValidArrayIndex(getArrayIndex(property.getKey()))); } return newMap; }
Return an array of all properties.
Returns:Properties as an array.
/** * Return an array of all properties. * * @return Properties as an array. */
public final Property[] getProperties() { return properties.getProperties(); }
Return the name of the class of objects using this property map.
Returns:class name of owner objects.
/** * Return the name of the class of objects using this property map. * * @return class name of owner objects. */
public final String getClassName() { return className; }
Prevents the map from having additional properties.
Returns:New map with NOT_EXTENSIBLE flag set.
/** * Prevents the map from having additional properties. * * @return New map with {@link #NOT_EXTENSIBLE} flag set. */
PropertyMap preventExtensions() { return deriveMap(properties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); }
Prevents properties in map from being modified.
Returns:New map with NOT_EXTENSIBLE flag set and properties with Property.NOT_CONFIGURABLE set.
/** * Prevents properties in map from being modified. * * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with * {@link Property#NOT_CONFIGURABLE} set. */
PropertyMap seal() { PropertyHashMap newProperties = EMPTY_HASHMAP; for (final Property oldProperty : properties.getProperties()) { newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE)); } return deriveMap(newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); }
Prevents properties in map from being modified or written to.
Returns:New map with NOT_EXTENSIBLE flag set and properties with Property.NOT_CONFIGURABLE and Property.NOT_WRITABLE set.
/** * Prevents properties in map from being modified or written to. * * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with * {@link Property#NOT_CONFIGURABLE} and {@link Property#NOT_WRITABLE} set. */
PropertyMap freeze() { PropertyHashMap newProperties = EMPTY_HASHMAP; for (final Property oldProperty : properties.getProperties()) { int propertyFlags = Property.NOT_CONFIGURABLE; if (!(oldProperty instanceof UserAccessorProperty)) { propertyFlags |= Property.NOT_WRITABLE; } newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags)); } return deriveMap(newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); }
Check for any configurable properties.
Returns:true if any configurable.
/** * Check for any configurable properties. * * @return {@code true} if any configurable. */
private boolean anyConfigurable() { for (final Property property : properties.getProperties()) { if (property.isConfigurable()) { return true; } } return false; }
Check if all properties are frozen.
Returns:true if all are frozen.
/** * Check if all properties are frozen. * * @return {@code true} if all are frozen. */
private boolean allFrozen() { for (final Property property : properties.getProperties()) { // check if it is a data descriptor if (!property.isAccessorProperty() && property.isWritable()) { return false; } if (property.isConfigurable()) { return false; } } return true; }
Check prototype history for an existing property map with specified prototype.
Params:
  • proto – New prototype object.
Returns:Existing PropertyMap or null if not found.
/** * Check prototype history for an existing property map with specified prototype. * * @param proto New prototype object. * * @return Existing {@link PropertyMap} or {@code null} if not found. */
private PropertyMap checkProtoHistory(final ScriptObject proto) { final PropertyMap cachedMap; if (protoHistory != null) { final SoftReference<PropertyMap> weakMap = protoHistory.get(proto); cachedMap = (weakMap != null ? weakMap.get() : null); } else { cachedMap = null; } if (Context.DEBUG && cachedMap != null) { protoHistoryHit.increment(); } return cachedMap; }
Add a map to the prototype history.
Params:
  • newProto – Prototype to add (key.)
  • newMap – PropertyMap associated with prototype.
/** * Add a map to the prototype history. * * @param newProto Prototype to add (key.) * @param newMap {@link PropertyMap} associated with prototype. */
private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { if (protoHistory == null) { protoHistory = new WeakHashMap<>(); } protoHistory.put(newProto, new SoftReference<>(newMap)); }
Track the modification of the map.
Params:
  • property – Mapping property.
  • newMap – Modified PropertyMap.
/** * Track the modification of the map. * * @param property Mapping property. * @param newMap Modified {@link PropertyMap}. */
private void addToHistory(final Property property, final PropertyMap newMap) { if (history == null) { history = new WeakHashMap<>(); } history.put(property, softReferenceDerivationLimit == 0 ? new WeakReference<>(newMap) : new SoftReference<>(newMap)); }
Check the history for a map that already has the given property added.
Params:
Returns:Existing map or null if not found.
/** * Check the history for a map that already has the given property added. * * @param property {@link Property} to add. * * @return Existing map or {@code null} if not found. */
private PropertyMap checkHistory(final Property property) { if (history != null) { final Reference<PropertyMap> ref = history.get(property); final PropertyMap historicMap = ref == null ? null : ref.get(); if (historicMap != null) { if (Context.DEBUG) { historyHit.increment(); } return historicMap; } } return null; }
Returns true if the two maps have identical properties in the same order, but allows the properties to differ in their types. This method is mostly useful for tests.
Params:
  • otherMap – the other map
Returns:true if this map has identical properties in the same order as the other map, allowing the properties to differ in type.
/** * Returns true if the two maps have identical properties in the same order, but allows the properties to differ in * their types. This method is mostly useful for tests. * @param otherMap the other map * @return true if this map has identical properties in the same order as the other map, allowing the properties to * differ in type. */
public boolean equalsWithoutType(final PropertyMap otherMap) { if (properties.size() != otherMap.properties.size()) { return false; } final Iterator<Property> iter = properties.values().iterator(); final Iterator<Property> otherIter = otherMap.properties.values().iterator(); while (iter.hasNext() && otherIter.hasNext()) { if (!iter.next().equalsWithoutType(otherIter.next())) { return false; } } return true; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(Debug.id(this)); sb.append(" = {\n"); for (final Property property : getProperties()) { sb.append('\t'); sb.append(property); sb.append('\n'); } sb.append('}'); return sb.toString(); } @Override public Iterator<Object> iterator() { return new PropertyMapIterator(this); }
Check if this map contains properties with valid array keys
Returns:true if this map contains properties with valid array keys
/** * Check if this map contains properties with valid array keys * * @return {@code true} if this map contains properties with valid array keys */
public final boolean containsArrayKeys() { return (flags & CONTAINS_ARRAY_KEYS) != 0; }
Test to see if PropertyMap is extensible.
Returns:true if PropertyMap can be added to.
/** * Test to see if {@link PropertyMap} is extensible. * * @return {@code true} if {@link PropertyMap} can be added to. */
boolean isExtensible() { return (flags & NOT_EXTENSIBLE) == 0; }
Test to see if PropertyMap is not extensible or any properties can not be modified.
Returns:true if PropertyMap is sealed.
/** * Test to see if {@link PropertyMap} is not extensible or any properties * can not be modified. * * @return {@code true} if {@link PropertyMap} is sealed. */
boolean isSealed() { return !isExtensible() && !anyConfigurable(); }
Test to see if PropertyMap is not extensible or all properties can not be modified.
Returns:true if PropertyMap is frozen.
/** * Test to see if {@link PropertyMap} is not extensible or all properties * can not be modified. * * @return {@code true} if {@link PropertyMap} is frozen. */
boolean isFrozen() { return !isExtensible() && allFrozen(); }
Return a free field slot for this map, or -1 if none is available.
Returns:free field slot or -1
/** * Return a free field slot for this map, or {@code -1} if none is available. * * @return free field slot or -1 */
int getFreeFieldSlot() { if (freeSlots != null) { final int freeSlot = freeSlots.nextSetBit(0); if (freeSlot > -1 && freeSlot < fieldMaximum) { return freeSlot; } } if (fieldCount < fieldMaximum) { return fieldCount; } return -1; }
Get a free spill slot for this map.
Returns:free spill slot
/** * Get a free spill slot for this map. * * @return free spill slot */
int getFreeSpillSlot() { if (freeSlots != null) { final int freeSlot = freeSlots.nextSetBit(fieldMaximum); if (freeSlot > -1) { return freeSlot - fieldMaximum; } } return spillLength; }
Return a property map with the same layout that is associated with the new prototype object.
Params:
  • newProto – New prototype object to replace oldProto.
Returns:New PropertyMap with prototype changed.
/** * Return a property map with the same layout that is associated with the new prototype object. * * @param newProto New prototype object to replace oldProto. * @return New {@link PropertyMap} with prototype changed. */
public synchronized PropertyMap changeProto(final ScriptObject newProto) { final PropertyMap nextMap = checkProtoHistory(newProto); if (nextMap != null) { return nextMap; } if (Context.DEBUG) { setProtoNewMapCount.increment(); } final PropertyMap newMap = makeUnsharedCopy(); addToProtoHistory(newProto, newMap); return newMap; }
Make a copy of this property map with the shared prototype field set to null. Note that this is only necessary for shared maps of top-level objects. Shared prototype maps represented by SharedPropertyMap are automatically converted to plain property maps when they evolve.
Returns:a copy with the shared proto map unset
/** * Make a copy of this property map with the shared prototype field set to null. Note that this is * only necessary for shared maps of top-level objects. Shared prototype maps represented by * {@link SharedPropertyMap} are automatically converted to plain property maps when they evolve. * * @return a copy with the shared proto map unset */
PropertyMap makeUnsharedCopy() { final PropertyMap newMap = new PropertyMap(this); newMap.sharedProtoMap = null; return newMap; }
Set a reference to the expected parent prototype map. This is used for class-like structures where we only want to use a top-level property map if all of the prototype property maps have not been modified.
Params:
  • protoMap – weak reference to the prototype property map
/** * Set a reference to the expected parent prototype map. This is used for class-like * structures where we only want to use a top-level property map if all of the * prototype property maps have not been modified. * * @param protoMap weak reference to the prototype property map */
void setSharedProtoMap(final SharedPropertyMap protoMap) { sharedProtoMap = protoMap; }
Get the expected prototype property map if it is known, or null.
Returns:parent map or null
/** * Get the expected prototype property map if it is known, or null. * * @return parent map or null */
public PropertyMap getSharedProtoMap() { return sharedProtoMap; }
Returns true if this map has been used as a shared prototype map (i.e. as a prototype for a JavaScript constructor function) and has not had properties added, deleted or replaced since then.
Returns:true if this is a valid shared prototype map
/** * Returns {@code true} if this map has been used as a shared prototype map (i.e. as a prototype * for a JavaScript constructor function) and has not had properties added, deleted or replaced since then. * @return true if this is a valid shared prototype map */
boolean isValidSharedProtoMap() { return false; }
Returns the shared prototype switch point, or null if this is not a shared prototype map.
Returns:the shared prototype switch point, or null
/** * Returns the shared prototype switch point, or null if this is not a shared prototype map. * @return the shared prototype switch point, or null */
SwitchPoint getSharedProtoSwitchPoint() { return null; }
Return true if this map has a shared prototype map which has either been invalidated or does not match the map of proto.
Params:
  • prototype – the prototype object
Returns:true if this is an invalid shared map for prototype
/** * Return true if this map has a shared prototype map which has either been invalidated or does * not match the map of {@code proto}. * @param prototype the prototype object * @return true if this is an invalid shared map for {@code prototype} */
boolean isInvalidSharedMapFor(final ScriptObject prototype) { return sharedProtoMap != null && (!sharedProtoMap.isValidSharedProtoMap() || prototype == null || sharedProtoMap != prototype.getMap()); }
PropertyMap iterator.
/** * {@link PropertyMap} iterator. */
private static class PropertyMapIterator implements Iterator<Object> {
Property iterator.
/** Property iterator. */
final Iterator<Property> iter;
Current Property.
/** Current Property. */
Property property;
Constructor.
Params:
/** * Constructor. * * @param propertyMap {@link PropertyMap} to iterate over. */
PropertyMapIterator(final PropertyMap propertyMap) { iter = Arrays.asList(propertyMap.properties.getProperties()).iterator(); property = iter.hasNext() ? iter.next() : null; skipNotEnumerable(); }
Ignore properties that are not enumerable.
/** * Ignore properties that are not enumerable. */
private void skipNotEnumerable() { while (property != null && !property.isEnumerable()) { property = iter.hasNext() ? iter.next() : null; } } @Override public boolean hasNext() { return property != null; } @Override public Object next() { if (property == null) { throw new NoSuchElementException(); } final Object key = property.getKey(); property = iter.hasNext() ? iter.next() : null; skipNotEnumerable(); return key; } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } /* * Debugging and statistics. */
Debug helper function that returns the diff of two property maps, only displaying the information that is different and in which map it exists compared to the other map. Can be used to e.g. debug map guards and investigate why they fail, causing relink
Params:
  • map0 – the first property map
  • map1 – the second property map
Returns:property map diff as string
/** * Debug helper function that returns the diff of two property maps, only * displaying the information that is different and in which map it exists * compared to the other map. Can be used to e.g. debug map guards and * investigate why they fail, causing relink * * @param map0 the first property map * @param map1 the second property map * * @return property map diff as string */
public static String diff(final PropertyMap map0, final PropertyMap map1) { final StringBuilder sb = new StringBuilder(); if (map0 != map1) { sb.append(">>> START: Map diff"); boolean found = false; for (final Property p : map0.getProperties()) { final Property p2 = map1.findProperty(p.getKey()); if (p2 == null) { sb.append("FIRST ONLY : [").append(p).append("]"); found = true; } else if (p2 != p) { sb.append("DIFFERENT : [").append(p).append("] != [").append(p2).append("]"); found = true; } } for (final Property p2 : map1.getProperties()) { final Property p1 = map0.findProperty(p2.getKey()); if (p1 == null) { sb.append("SECOND ONLY: [").append(p2).append("]"); found = true; } } //assert found; if (!found) { sb.append(map0). append("!="). append(map1); } sb.append("<<< END: Map diff\n"); } return sb.toString(); } // counters updated only in debug mode private static LongAdder count; private static LongAdder clonedCount; private static LongAdder historyHit; private static LongAdder protoInvalidations; private static LongAdder protoHistoryHit; private static LongAdder setProtoNewMapCount; static { if (Context.DEBUG) { count = new LongAdder(); clonedCount = new LongAdder(); historyHit = new LongAdder(); protoInvalidations = new LongAdder(); protoHistoryHit = new LongAdder(); setProtoNewMapCount = new LongAdder(); } }
Returns:Total number of maps.
/** * @return Total number of maps. */
public static long getCount() { return count.longValue(); }
Returns:The number of maps that were cloned.
/** * @return The number of maps that were cloned. */
public static long getClonedCount() { return clonedCount.longValue(); }
Returns:The number of times history was successfully used.
/** * @return The number of times history was successfully used. */
public static long getHistoryHit() { return historyHit.longValue(); }
Returns:The number of times prototype changes caused invalidation.
/** * @return The number of times prototype changes caused invalidation. */
public static long getProtoInvalidations() { return protoInvalidations.longValue(); }
Returns:The number of times proto history was successfully used.
/** * @return The number of times proto history was successfully used. */
public static long getProtoHistoryHit() { return protoHistoryHit.longValue(); }
Returns:The number of times prototypes were modified.
/** * @return The number of times prototypes were modified. */
public static long getSetProtoNewMapCount() { return setProtoNewMapCount.longValue(); } }