--- old/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java	2015-09-16 09:54:57.921929513 +0200
+++ new/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java	2015-09-16 09:54:57.833929515 +0200
@@ -54,32 +54,36 @@
  * All property maps are immutable. If a property is added, modified or removed, the mutator
  * will return a new map.
  */
-public final class PropertyMap implements Iterable<Object>, Serializable {
+public class PropertyMap implements Iterable<Object>, Serializable {
     /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */
-    public static final int NOT_EXTENSIBLE        = 0b0000_0001;
+    private static final int NOT_EXTENSIBLE         = 0b0000_0001;
     /** Does this map contain valid array keys? */
-    public static final int CONTAINS_ARRAY_KEYS   = 0b0000_0010;
+    private static final int CONTAINS_ARRAY_KEYS    = 0b0000_0010;
 
     /** Map status flags. */
-    private int flags;
+    private final int flags;
 
     /** Map of properties. */
     private transient PropertyHashMap properties;
 
     /** Number of fields in use. */
-    private int fieldCount;
+    private final int fieldCount;
 
     /** Number of fields available. */
     private final int fieldMaximum;
 
     /** Length of spill in use. */
-    private int spillLength;
+    private final int spillLength;
 
     /** Structure class name */
-    private String className;
+    private final String className;
+
+    /** 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;
 
     /** {@link SwitchPoint}s for gets on inherited properties. */
-    private transient HashMap<String, SwitchPoint> protoGetSwitches;
+    private transient HashMap<String, SwitchPoint> protoSwitches;
 
     /** History of maps, used to limit map duplication. */
     private transient WeakHashMap<Property, SoftReference<PropertyMap>> history;
@@ -95,24 +99,21 @@
     private static final long serialVersionUID = -7041836752008732533L;
 
     /**
-     * Constructor.
+     * 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.
-     * @param containsArrayKeys True if properties contain numeric keys
      */
-    private PropertyMap(final PropertyHashMap properties, final String className, final int fieldCount,
-                        final int fieldMaximum, final int spillLength, final boolean containsArrayKeys) {
+    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;
-        if (containsArrayKeys) {
-            setContainsArrayKeys();
-        }
+        this.flags        = flags;
 
         if (Context.DEBUG) {
             count.increment();
@@ -120,20 +121,22 @@
     }
 
     /**
-     * Cloning constructor.
+     * 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) {
+    private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength) {
         this.properties   = properties;
-        this.flags        = propertyMap.flags;
-        this.spillLength  = propertyMap.spillLength;
-        this.fieldCount   = propertyMap.fieldCount;
+        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;
 
         if (Context.DEBUG) {
             count.increment();
@@ -142,12 +145,12 @@
     }
 
     /**
-     * Cloning constructor.
+     * Constructs an exact clone of {@code propertyMap}.
      *
      * @param propertyMap Existing property map.
       */
-    private PropertyMap(final PropertyMap propertyMap) {
-        this(propertyMap, propertyMap.properties);
+    protected PropertyMap(final PropertyMap propertyMap) {
+        this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength);
     }
 
     private void writeObject(final ObjectOutputStream out) throws IOException {
@@ -183,7 +186,7 @@
      */
     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, className, fieldCount, fieldMaximum, spillLength, false);
+        return new PropertyMap(newProperties, 0, className, fieldCount, fieldMaximum, spillLength);
     }
 
     /**
@@ -205,7 +208,7 @@
      * @return New empty {@link PropertyMap}.
      */
     public static PropertyMap newMap(final Class<? extends ScriptObject> clazz) {
-        return new PropertyMap(EMPTY_HASHMAP, clazz.getName(), 0, 0, 0, false);
+        return new PropertyMap(EMPTY_HASHMAP, 0, clazz.getName(), 0, 0, 0);
     }
 
     /**
@@ -227,12 +230,12 @@
     }
 
     /**
-     * Get the listeners of this map, or null if none exists
+     * Get the number of listeners of this map
      *
-     * @return the listeners
+     * @return the number of listeners
      */
-    public PropertyListeners getListeners() {
-        return listeners;
+    public int getListenerCount() {
+        return listeners == null ? 0 : listeners.getListenerCount();
     }
 
     /**
@@ -253,9 +256,12 @@
      * 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) {
-        invalidateProtoGetSwitchPoint(property);
+    public void propertyAdded(final Property property, final boolean isSelf) {
+        if (!isSelf) {
+            invalidateProtoSwitchPoint(property.getKey());
+        }
         if (listeners != null) {
             listeners.propertyAdded(property);
         }
@@ -265,9 +271,12 @@
      * 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) {
-        invalidateProtoGetSwitchPoint(property);
+    public void propertyDeleted(final Property property, final boolean isSelf) {
+        if (!isSelf) {
+            invalidateProtoSwitchPoint(property.getKey());
+        }
         if (listeners != null) {
             listeners.propertyDeleted(property);
         }
@@ -278,9 +287,12 @@
      *
      * @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) {
-        invalidateProtoGetSwitchPoint(oldProperty);
+    public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) {
+        if (!isSelf) {
+            invalidateProtoSwitchPoint(oldProperty.getKey());
+        }
         if (listeners != null) {
             listeners.propertyModified(oldProperty, newProperty);
         }
@@ -288,9 +300,15 @@
 
     /**
      * 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() {
-        invalidateAllProtoGetSwitchPoints();
+    public void protoChanged(final boolean isSelf) {
+        if (!isSelf) {
+            invalidateAllProtoSwitchPoints();
+        } else if (sharedProtoMap != null) {
+            sharedProtoMap.invalidateSwitchPoint();
+        }
         if (listeners != null) {
             listeners.protoChanged();
         }
@@ -303,14 +321,14 @@
      * @return A shared {@link SwitchPoint} for the property.
      */
     public synchronized SwitchPoint getSwitchPoint(final String key) {
-        if (protoGetSwitches == null) {
-            protoGetSwitches = new HashMap<>();
+        if (protoSwitches == null) {
+            protoSwitches = new HashMap<>();
         }
 
-        SwitchPoint switchPoint = protoGetSwitches.get(key);
+        SwitchPoint switchPoint = protoSwitches.get(key);
         if (switchPoint == null) {
             switchPoint = new SwitchPoint();
-            protoGetSwitches.put(key, switchPoint);
+            protoSwitches.put(key, switchPoint);
         }
 
         return switchPoint;
@@ -319,19 +337,17 @@
     /**
      * Indicate that a prototype property has changed.
      *
-     * @param property {@link Property} to invalidate.
+     * @param key {@link Property} key to invalidate.
      */
-    synchronized void invalidateProtoGetSwitchPoint(final Property property) {
-        if (protoGetSwitches != null) {
-
-            final String key = property.getKey();
-            final SwitchPoint sp = protoGetSwitches.get(key);
+    synchronized void invalidateProtoSwitchPoint(final String key) {
+        if (protoSwitches != null) {
+            final SwitchPoint sp = protoSwitches.get(key);
             if (sp != null) {
-                protoGetSwitches.remove(key);
+                protoSwitches.remove(key);
                 if (Context.DEBUG) {
                     protoInvalidations.increment();
                 }
-                SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
+                SwitchPoint.invalidateAll(new SwitchPoint[]{sp});
             }
         }
     }
@@ -339,15 +355,15 @@
     /**
      * Indicate that proto itself has changed in hierarchy somewhere.
      */
-    synchronized void invalidateAllProtoGetSwitchPoints() {
-        if (protoGetSwitches != null) {
-            final int size = protoGetSwitches.size();
+    synchronized void invalidateAllProtoSwitchPoints() {
+        if (protoSwitches != null) {
+            final int size = protoSwitches.size();
             if (size > 0) {
                 if (Context.DEBUG) {
                     protoInvalidations.add(size);
                 }
-                SwitchPoint.invalidateAll(protoGetSwitches.values().toArray(new SwitchPoint[size]));
-                protoGetSwitches.clear();
+                SwitchPoint.invalidateAll(protoSwitches.values().toArray(new SwitchPoint[size]));
+                protoSwitches.clear();
             }
         }
     }
@@ -363,7 +379,7 @@
      * @return New {@link PropertyMap} with {@link Property} added.
      */
     PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) {
-        // No need to store bound property in the history as bound properties can't be reused.
+        // We must not store bound property in the history as bound properties can't be reused.
         return addPropertyNoHistory(new AccessorProperty(property, bindTo));
     }
 
@@ -376,16 +392,16 @@
         return property.isSpill() ? slot + fieldMaximum : slot;
     }
 
-    // Update boundaries and flags after a property has been added
-    private void updateFlagsAndBoundaries(final Property newProperty) {
-        if(newProperty.isSpill()) {
-            spillLength = Math.max(spillLength, newProperty.getSlot() + 1);
-        } else {
-            fieldCount = Math.max(fieldCount, newProperty.getSlot() + 1);
-        }
-        if (isValidArrayIndex(getArrayIndex(newProperty.getKey()))) {
-            setContainsArrayKeys();
-        }
+    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
@@ -420,13 +436,10 @@
      * @param property {@link Property} being added.
      * @return New {@link PropertyMap} with {@link Property} added.
      */
-    public PropertyMap addPropertyNoHistory(final Property property) {
-        if (listeners != null) {
-            listeners.propertyAdded(property);
-        }
+    public final PropertyMap addPropertyNoHistory(final Property property) {
+        propertyAdded(property, true);
         final PropertyHashMap newProperties = properties.immutableAdd(property);
-        final PropertyMap newMap = new PropertyMap(this, newProperties);
-        newMap.updateFlagsAndBoundaries(property);
+        final PropertyMap newMap = new PropertyMap(this, newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
         newMap.updateFreeSlots(null, property);
 
         return newMap;
@@ -439,16 +452,13 @@
      *
      * @return New {@link PropertyMap} with {@link Property} added.
      */
-    public synchronized PropertyMap addProperty(final Property property) {
-        if (listeners != null) {
-            listeners.propertyAdded(property);
-        }
+    public final synchronized PropertyMap addProperty(final Property property) {
+        propertyAdded(property, true);
         PropertyMap newMap = checkHistory(property);
 
         if (newMap == null) {
             final PropertyHashMap newProperties = properties.immutableAdd(property);
-            newMap = new PropertyMap(this, newProperties);
-            newMap.updateFlagsAndBoundaries(property);
+            newMap = new PropertyMap(this, newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
             newMap.updateFreeSlots(null, property);
             addToHistory(property, newMap);
         }
@@ -463,10 +473,8 @@
      *
      * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found.
      */
-    public synchronized PropertyMap deleteProperty(final Property property) {
-        if (listeners != null) {
-            listeners.propertyDeleted(property);
-        }
+    public final synchronized PropertyMap deleteProperty(final Property property) {
+        propertyDeleted(property, true);
         PropertyMap newMap = checkHistory(property);
         final String key = property.getKey();
 
@@ -477,13 +485,13 @@
             // 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 = new PropertyMap(newProperties, className, fieldCount, fieldMaximum, spillLength - 1, containsArrayKeys());
+                newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength - 1);
                 newMap.freeSlots = freeSlots;
             } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) {
-                newMap = new PropertyMap(newProperties, className, fieldCount - 1, fieldMaximum, spillLength, containsArrayKeys());
+                newMap = new PropertyMap(this, newProperties, flags, fieldCount - 1, spillLength);
                 newMap.freeSlots = freeSlots;
             } else {
-                newMap = new PropertyMap(this, newProperties);
+                newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength);
                 newMap.updateFreeSlots(property, null);
             }
             addToHistory(property, newMap);
@@ -500,13 +508,8 @@
      *
      * @return New {@link PropertyMap} with {@link Property} replaced.
      */
-    public PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
-        if (listeners != null) {
-            listeners.propertyModified(oldProperty, newProperty);
-        }
-        // Add replaces existing property.
-        final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty);
-        final PropertyMap newMap = new PropertyMap(this, newProperties);
+    public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
+        propertyModified(oldProperty, newProperty, true);
         /*
          * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods.
          *
@@ -528,14 +531,17 @@
                 newProperty instanceof UserAccessorProperty :
             "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]";
 
-        newMap.flags = flags;
-
         /*
          * 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 = new PropertyMap(this, newProperties, flags, fieldCount, newSpillLength);
+
         if (!sameType) {
-            newMap.spillLength = Math.max(spillLength, newProperty.getSlot() + 1);
             newMap.updateFreeSlots(oldProperty, newProperty);
         }
         return newMap;
@@ -551,7 +557,7 @@
      * @param propertyFlags attribute flags of the property
      * @return the newly created UserAccessorProperty
      */
-    public UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) {
+    public final UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) {
         return new UserAccessorProperty(key, propertyFlags, getFreeSpillSlot());
     }
 
@@ -562,7 +568,7 @@
      *
      * @return {@link Property} matching key.
      */
-    public Property findProperty(final String key) {
+    public final Property findProperty(final String key) {
         return properties.find(key);
     }
 
@@ -573,12 +579,12 @@
      *
      * @return New {@link PropertyMap} with added properties.
      */
-    public PropertyMap addAll(final PropertyMap other) {
+    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 = new PropertyMap(this, newProperties);
+        final PropertyMap newMap = new PropertyMap(this, 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;
@@ -593,7 +599,7 @@
      *
      * @return Properties as an array.
      */
-    public Property[] getProperties() {
+    public final Property[] getProperties() {
         return properties.getProperties();
     }
 
@@ -602,7 +608,7 @@
      *
      * @return class name of owner objects.
      */
-    public String getClassName() {
+    public final String getClassName() {
         return className;
     }
 
@@ -612,9 +618,7 @@
      * @return New map with {@link #NOT_EXTENSIBLE} flag set.
      */
     PropertyMap preventExtensions() {
-        final PropertyMap newMap = new PropertyMap(this);
-        newMap.flags |= NOT_EXTENSIBLE;
-        return newMap;
+        return new PropertyMap(this, properties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
     }
 
     /**
@@ -630,10 +634,7 @@
             newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE));
         }
 
-        final PropertyMap newMap = new PropertyMap(this, newProperties);
-        newMap.flags |= NOT_EXTENSIBLE;
-
-        return newMap;
+        return new PropertyMap(this, newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
     }
 
     /**
@@ -655,10 +656,7 @@
             newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags));
         }
 
-        final PropertyMap newMap = new PropertyMap(this, newProperties);
-        newMap.flags |= NOT_EXTENSIBLE;
-
-        return newMap;
+        return new PropertyMap(this, newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
     }
 
     /**
@@ -830,13 +828,6 @@
     }
 
     /**
-     * Flag this object as having array keys in defined properties
-     */
-    private void setContainsArrayKeys() {
-        flags |= CONTAINS_ARRAY_KEYS;
-    }
-
-    /**
      * Test to see if {@link PropertyMap} is extensible.
      *
      * @return {@code true} if {@link PropertyMap} can be added to.
@@ -914,12 +905,72 @@
             setProtoNewMapCount.increment();
         }
 
-        final PropertyMap newMap = new PropertyMap(this);
+        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
+     * {@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.
+     *
+     * @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.
+     *
+     * @return parent map or null
+     */
+    public PropertyMap getSharedProtoMap() {
+        return sharedProtoMap;
+    }
+
+    /**
+     * 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.
+     * @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 {@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());
+    }
 
     /**
      * {@link PropertyMap} iterator.