# HG changeset patch # User hannesw # Date 1442407352 -7200 # Node ID b4eb53200105c514579a33b8cb1ca562cbbf4c3a # Parent c209abbe9b24ae3659ca94eb79850972ebaef525 8134609: Allow constructors with same prototoype map to share the allocator map Reviewed-by: attila, sundar diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJSAdapter.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJSAdapter.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJSAdapter.java Wed Sep 16 14:42:32 2015 +0200 @@ -627,7 +627,7 @@ return new GuardedInvocation(MH.dropArguments(MH.constant(Object.class, func.createBound(this, new Object[] { name })), 0, Object.class), testJSAdaptor(adaptee, null, null, null), - adaptee.getProtoSwitchPoint(__call__, find.getOwner())); + adaptee.getProtoSwitchPoints(__call__, find.getOwner()), null); } } throw typeError("no.such.function", desc.getNameToken(2), ScriptRuntime.safeToString(this)); @@ -698,7 +698,7 @@ return new GuardedInvocation( methodHandle, testJSAdaptor(adaptee, findData.getGetter(Object.class, INVALID_PROGRAM_POINT, null), findData.getOwner(), func), - adaptee.getProtoSwitchPoint(hook, findData.getOwner())); + adaptee.getProtoSwitchPoints(hook, findData.getOwner()), null); } } } @@ -710,7 +710,7 @@ final MethodHandle methodHandle = hook.equals(__put__) ? MH.asType(Lookup.EMPTY_SETTER, type) : Lookup.emptyGetter(type.returnType()); - return new GuardedInvocation(methodHandle, testJSAdaptor(adaptee, null, null, null), adaptee.getProtoSwitchPoint(hook, null)); + return new GuardedInvocation(methodHandle, testJSAdaptor(adaptee, null, null, null), adaptee.getProtoSwitchPoints(hook, null), null); } } diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AllocationStrategy.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AllocationStrategy.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AllocationStrategy.java Wed Sep 16 14:42:32 2015 +0200 @@ -29,6 +29,7 @@ import java.io.Serializable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.ref.WeakReference; import jdk.nashorn.internal.codegen.Compiler; import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.ObjectClassGenerator; @@ -53,6 +54,9 @@ /** lazily generated allocator */ private transient MethodHandle allocator; + /** Last used allocator map */ + private transient AllocatorMap lastMap; + /** * Construct an allocation strategy with the given map and class name. * @param fieldCount number of fields in the allocated object @@ -71,11 +75,49 @@ return allocatorClassName; } - PropertyMap getAllocatorMap() { - // Create a new map for each function instance - return PropertyMap.newMap(null, getAllocatorClassName(), 0, fieldCount, 0); + /** + * Get the property map for the allocated object. + * @param prototype the prototype object + * @return the property map + */ + synchronized PropertyMap getAllocatorMap(final ScriptObject prototype) { + assert prototype != null; + final PropertyMap protoMap = prototype.getMap(); + + if (lastMap != null) { + if (!lastMap.hasSharedProtoMap()) { + if (lastMap.hasSamePrototype(prototype)) { + return lastMap.allocatorMap; + } + if (lastMap.hasSameProtoMap(protoMap) && lastMap.hasUnchangedProtoMap()) { + // Convert to shared prototype map. Allocated objects will use the same property map + // that can be used as long as none of the prototypes modify the shared proto map. + final PropertyMap allocatorMap = PropertyMap.newMap(null, getAllocatorClassName(), 0, fieldCount, 0); + final SharedPropertyMap sharedProtoMap = new SharedPropertyMap(protoMap); + allocatorMap.setSharedProtoMap(sharedProtoMap); + prototype.setMap(sharedProtoMap); + lastMap = new AllocatorMap(prototype, protoMap, allocatorMap); + return allocatorMap; + } + } + + if (lastMap.hasValidSharedProtoMap() && lastMap.hasSameProtoMap(protoMap)) { + prototype.setMap(lastMap.getSharedProtoMap()); + return lastMap.allocatorMap; + } + } + + final PropertyMap allocatorMap = PropertyMap.newMap(null, getAllocatorClassName(), 0, fieldCount, 0); + lastMap = new AllocatorMap(prototype, protoMap, allocatorMap); + + return allocatorMap; } + /** + * Allocate an object with the given property map + * @param map the property map + * @return the allocated object + */ ScriptObject allocate(final PropertyMap map) { try { if (allocator == null) { @@ -94,4 +136,43 @@ public String toString() { return "AllocationStrategy[fieldCount=" + fieldCount + "]"; } + + static class AllocatorMap { + final private WeakReference prototype; + final private WeakReference prototypeMap; + + private PropertyMap allocatorMap; + + AllocatorMap(final ScriptObject prototype, final PropertyMap protoMap, final PropertyMap allocMap) { + this.prototype = new WeakReference<>(prototype); + this.prototypeMap = new WeakReference<>(protoMap); + this.allocatorMap = allocMap; + } + + boolean hasSamePrototype(final ScriptObject proto) { + return prototype.get() == proto; + } + + boolean hasSameProtoMap(final PropertyMap protoMap) { + return prototypeMap.get() == protoMap || allocatorMap.getSharedProtoMap() == protoMap; + } + + boolean hasUnchangedProtoMap() { + final ScriptObject proto = prototype.get(); + return proto != null && proto.getMap() == prototypeMap.get(); + } + + boolean hasSharedProtoMap() { + return getSharedProtoMap() != null; + } + + boolean hasValidSharedProtoMap() { + return hasSharedProtoMap() && getSharedProtoMap().isValidSharedProtoMap(); + } + + PropertyMap getSharedProtoMap() { + return allocatorMap.getSharedProtoMap(); + } + + } } diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Debug.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Debug.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Debug.java Wed Sep 16 14:42:32 2015 +0200 @@ -26,6 +26,8 @@ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.parser.TokenType.EOF; + +import jdk.nashorn.api.scripting.NashornException; import jdk.nashorn.internal.parser.Lexer; import jdk.nashorn.internal.parser.Token; import jdk.nashorn.internal.parser.TokenStream; @@ -63,6 +65,15 @@ } /** + * Return a formatted script stack trace string with frames information separated by '\n'. + * This is a shortcut for {@code NashornException.getScriptStackString(new Throwable())}. + * @return formatted stack trace string + */ + public static String scriptStack() { + return NashornException.getScriptStackString(new Throwable()); + } + + /** * Return the system identity hashcode for an object as a human readable * string * diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyListeners.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyListeners.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyListeners.java Wed Sep 16 14:42:32 2015 +0200 @@ -75,16 +75,20 @@ } /** - * Return listeners added to this ScriptObject. + * Return number of listeners added to a ScriptObject. * @param obj the object * @return the listener count */ public static int getListenerCount(final ScriptObject obj) { - final PropertyListeners propertyListeners = obj.getMap().getListeners(); - if (propertyListeners != null) { - return propertyListeners.listeners == null ? 0 : propertyListeners.listeners.size(); - } - return 0; + return obj.getMap().getListenerCount(); + } + + /** + * Return the number of listeners added to this PropertyListeners instance. + * @return the listener count; + */ + public int getListenerCount() { + return listeners == null ? 0 : listeners.size(); } // Property listener management methods @@ -156,7 +160,7 @@ final WeakPropertyMapSet set = listeners.get(prop.getKey()); if (set != null) { for (final PropertyMap propertyMap : set.elements()) { - propertyMap.propertyAdded(prop); + propertyMap.propertyAdded(prop, false); } listeners.remove(prop.getKey()); if (Context.DEBUG) { @@ -176,7 +180,7 @@ final WeakPropertyMapSet set = listeners.get(prop.getKey()); if (set != null) { for (final PropertyMap propertyMap : set.elements()) { - propertyMap.propertyDeleted(prop); + propertyMap.propertyDeleted(prop, false); } listeners.remove(prop.getKey()); if (Context.DEBUG) { @@ -198,7 +202,7 @@ final WeakPropertyMapSet set = listeners.get(oldProp.getKey()); if (set != null) { for (final PropertyMap propertyMap : set.elements()) { - propertyMap.propertyModified(oldProp, newProp); + propertyMap.propertyModified(oldProp, newProp, false); } listeners.remove(oldProp.getKey()); if (Context.DEBUG) { @@ -215,7 +219,7 @@ if (listeners != null) { for (final WeakPropertyMapSet set : listeners.values()) { for (final PropertyMap propertyMap : set.elements()) { - propertyMap.protoChanged(); + propertyMap.protoChanged(false); } } listeners.clear(); diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java Wed Sep 16 14:42:32 2015 +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, Serializable { +public class PropertyMap implements Iterable, 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 protoGetSwitches; + private transient HashMap protoSwitches; /** History of maps, used to limit map duplication. */ private transient WeakHashMap> 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 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 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. diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 16 14:42:32 2015 +0200 @@ -368,8 +368,8 @@ } @Override - PropertyMap getAllocatorMap() { - return allocationStrategy.getAllocatorMap(); + PropertyMap getAllocatorMap(final ScriptObject prototype) { + return allocationStrategy.getAllocatorMap(prototype); } @Override diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunction.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunction.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunction.java Wed Sep 16 14:42:32 2015 +0200 @@ -521,35 +521,39 @@ assert !isBoundFunction(); // allocate never invoked on bound functions - final ScriptObject object = data.allocate(getAllocatorMap()); + final ScriptObject prototype = getAllocatorPrototype(); + final ScriptObject object = data.allocate(getAllocatorMap(prototype)); if (object != null) { - final Object prototype = getPrototype(); - if (prototype instanceof ScriptObject) { - object.setInitialProto((ScriptObject) prototype); - } - - if (object.getProto() == null) { - object.setInitialProto(getObjectPrototype()); - } + object.setInitialProto(prototype); } return object; } - private PropertyMap getAllocatorMap() { - if (allocatorMap == null) { - allocatorMap = data.getAllocatorMap(); + /** + * Get the property map used by "allocate" + * @param prototype actual prototype object + * @return property map + */ + private synchronized PropertyMap getAllocatorMap(final ScriptObject prototype) { + if (allocatorMap == null || allocatorMap.isInvalidSharedMapFor(prototype)) { + // The prototype map has changed since this function was last used as constructor. + // Get a new allocator map. + allocatorMap = data.getAllocatorMap(prototype); } return allocatorMap; } /** - * Return Object.prototype - used by "allocate" - * - * @return Object.prototype + * Return the actual prototype used by "allocate" + * @return allocator prototype */ - protected final ScriptObject getObjectPrototype() { + private ScriptObject getAllocatorPrototype() { + final Object prototype = getPrototype(); + if (prototype instanceof ScriptObject) { + return (ScriptObject) prototype; + } return Global.objectPrototype(); } @@ -591,10 +595,10 @@ * * @param newPrototype new prototype object */ - public final void setPrototype(final Object newPrototype) { + public synchronized final void setPrototype(final Object newPrototype) { if (newPrototype instanceof ScriptObject && newPrototype != this.prototype && allocatorMap != null) { - // Replace our current allocator map with one that is associated with the new prototype. - allocatorMap = allocatorMap.changeProto((ScriptObject) newPrototype); + // Unset allocator map to be replaced with one matching the new prototype. + allocatorMap = null; } this.prototype = newPrototype; } diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunctionData.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunctionData.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunctionData.java Wed Sep 16 14:42:32 2015 +0200 @@ -389,9 +389,10 @@ /** * Get the property map to use for objects allocated by this function. * + * @param prototype the prototype of the allocated object * @return the property map for allocated objects. */ - PropertyMap getAllocatorMap() { + PropertyMap getAllocatorMap(final ScriptObject prototype) { return null; } diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java Wed Sep 16 14:42:32 2015 +0200 @@ -809,9 +809,11 @@ if (deep) { final ScriptObject myProto = getProto(); - if (myProto != null) { - return myProto.findProperty(key, deep, start); - } + final FindProperty find = myProto == null ? null : myProto.findProperty(key, true, start); + // checkSharedProtoMap must be invoked after myProto.checkSharedProtoMap to propagate + // shared proto invalidation up the prototype chain. It also must be invoked when prototype is null. + checkSharedProtoMap(); + return find; } return null; @@ -832,7 +834,7 @@ if (deep) { final ScriptObject myProto = getProto(); if (myProto != null) { - return myProto.hasProperty(key, deep); + return myProto.hasProperty(key, true); } } @@ -1258,11 +1260,8 @@ if (oldProto != newProto) { proto = newProto; - // Let current listeners know that the prototype has changed and set our map - final PropertyListeners listeners = getMap().getListeners(); - if (listeners != null) { - listeners.protoChanged(); - } + // Let current listeners know that the prototype has changed + getMap().protoChanged(true); // Replace our current allocator map with one that is associated with the new prototype. setMap(getMap().changeProto(newProto)); } @@ -1314,7 +1313,7 @@ } p = p.getProto(); } - setProto((ScriptObject)newProto); + setProto((ScriptObject) newProto); } else { throw typeError("cant.set.proto.to.non.object", ScriptRuntime.safeToString(this), ScriptRuntime.safeToString(newProto)); } @@ -2012,11 +2011,11 @@ final ScriptObject owner = find.getOwner(); final Class exception = explicitInstanceOfCheck ? null : ClassCastException.class; - final SwitchPoint protoSwitchPoint; + final SwitchPoint[] protoSwitchPoints; if (mh == null) { mh = Lookup.emptyGetter(returnType); - protoSwitchPoint = getProtoSwitchPoint(name, owner); + protoSwitchPoints = getProtoSwitchPoints(name, owner); } else if (!find.isSelf()) { assert mh.type().returnType().equals(returnType) : "return type mismatch for getter " + mh.type().returnType() + " != " + returnType; @@ -2024,12 +2023,12 @@ // Add a filter that replaces the self object with the prototype owning the property. mh = addProtoFilter(mh, find.getProtoChainLength()); } - protoSwitchPoint = getProtoSwitchPoint(name, owner); + protoSwitchPoints = getProtoSwitchPoints(name, owner); } else { - protoSwitchPoint = null; + protoSwitchPoints = null; } - final GuardedInvocation inv = new GuardedInvocation(mh, guard, protoSwitchPoint, exception); + final GuardedInvocation inv = new GuardedInvocation(mh, guard, protoSwitchPoints, exception); return inv.addSwitchPoint(findBuiltinSwitchPoint(name)); } @@ -2128,17 +2127,32 @@ * @param owner the property owner, null if property is not defined * @return a SwitchPoint or null */ - public final SwitchPoint getProtoSwitchPoint(final String name, final ScriptObject owner) { + public final SwitchPoint[] getProtoSwitchPoints(final String name, final ScriptObject owner) { if (owner == this || getProto() == null) { return null; } + final List switchPoints = new ArrayList<>(); for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) { final ScriptObject parent = obj.getProto(); parent.getMap().addListener(name, obj.getMap()); + final SwitchPoint sp = parent.getMap().getSharedProtoSwitchPoint(); + if (sp != null && !sp.hasBeenInvalidated()) { + switchPoints.add(sp); + } } - return getMap().getSwitchPoint(name); + switchPoints.add(getMap().getSwitchPoint(name)); + return switchPoints.toArray(new SwitchPoint[switchPoints.size()]); + } + + private void checkSharedProtoMap() { + // Check if our map has an expected shared prototype property map. If it has, make sure that + // the prototype map has not been invalidated, and that it does match the actual map of the prototype. + if (getMap().isInvalidSharedMapFor(getProto())) { + // Change our own map to one that does not assume a shared prototype map. + setMap(getMap().makeUnsharedCopy()); + } } /** @@ -2220,7 +2234,7 @@ return new GuardedInvocation( Lookup.EMPTY_SETTER, NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck), - getProtoSwitchPoint(name, null), + getProtoSwitchPoints(name, null), explicitInstanceOfCheck ? null : ClassCastException.class); } @@ -2366,7 +2380,7 @@ find.getGetter(Object.class, INVALID_PROGRAM_POINT, request), find.getProtoChainLength(), func), - getProtoSwitchPoint(NO_SUCH_PROPERTY_NAME, find.getOwner()), + getProtoSwitchPoints(NO_SUCH_PROPERTY_NAME, find.getOwner()), //TODO this doesn't need a ClassCastException as guard always checks script object null); } @@ -2438,7 +2452,7 @@ } return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), - NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck), getProtoSwitchPoint(name, null), + NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck), getProtoSwitchPoints(name, null), explicitInstanceOfCheck ? null : ClassCastException.class); } diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SetMethodCreator.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SetMethodCreator.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SetMethodCreator.java Wed Sep 16 14:42:32 2015 +0200 @@ -186,10 +186,7 @@ private SetMethod createNewPropertySetter(final SwitchPoint builtinSwitchPoint) { final SetMethod sm = map.getFreeFieldSlot() > -1 ? createNewFieldSetter(builtinSwitchPoint) : createNewSpillPropertySetter(builtinSwitchPoint); - final PropertyListeners listeners = map.getListeners(); - if (listeners != null) { - listeners.propertyAdded(sm.property); - } + map.propertyAdded(sm.property, true); return sm; } @@ -204,7 +201,7 @@ //fast type specific setter final MethodHandle fastSetter = property.getSetter(type, newMap); //0 sobj, 1 value, slot folded for spill property already - //slow setter, that calls ScriptObject.set with appropraite type and key name + //slow setter, that calls ScriptObject.set with appropriate type and key name MethodHandle slowSetter = ScriptObject.SET_SLOW[getAccessorTypeIndex(type)]; slowSetter = MH.insertArguments(slowSetter, 3, NashornCallSiteDescriptor.getFlags(desc)); slowSetter = MH.insertArguments(slowSetter, 1, name); diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SharedPropertyMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SharedPropertyMap.java Wed Sep 16 14:42:32 2015 +0200 @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015, 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 java.lang.invoke.SwitchPoint; + +/** + * This class represents a property map that can be shared among multiple prototype objects, allowing all inheriting + * top-level objects to also share one property map. This is class is only used for prototype objects, the + * top-level objects use ordinary {@link PropertyMap}s with the {@link PropertyMap#sharedProtoMap} field + * set to the expected shared prototype map. + * + *

When an instance of this class is evolved because a property is added, removed, or modified in an object + * using it, the {@link #invalidateSwitchPoint()} method is invoked to signal to all callsites and inheriting + * objects that the assumption of a single shared prototype map is no longer valid. The property map resulting + * from the modification will no longer be an instance of this class.

+ */ +public final class SharedPropertyMap extends PropertyMap { + + private SwitchPoint switchPoint; + + private static final long serialVersionUID = 2166297719721778876L; + + /** + * Create a new shared property map from the given {@code map}. + * @param map property map to copy + */ + public SharedPropertyMap(final PropertyMap map) { + super(map); + this.switchPoint = new SwitchPoint(); + } + + @Override + public void propertyAdded(final Property property, final boolean isSelf) { + if (isSelf) { + invalidateSwitchPoint(); + } + super.propertyAdded(property, isSelf); + } + + @Override + public void propertyDeleted(final Property property, final boolean isSelf) { + if (isSelf) { + invalidateSwitchPoint(); + } + super.propertyDeleted(property, isSelf); + } + + @Override + public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) { + if (isSelf) { + invalidateSwitchPoint(); + } + super.propertyModified(oldProperty, newProperty, isSelf); + } + + @Override + synchronized boolean isValidSharedProtoMap() { + return switchPoint != null; + } + + @Override + synchronized SwitchPoint getSharedProtoSwitchPoint() { + return switchPoint; + } + + /** + * Invalidate the shared prototype switch point if this is a shared prototype map. + */ + synchronized void invalidateSwitchPoint() { + if (switchPoint != null) { + assert !switchPoint.hasBeenInvalidated(); + SwitchPoint.invalidateAll(new SwitchPoint[]{ switchPoint }); + switchPoint = null; + } + } +} diff -r c209abbe9b24 -r b4eb53200105 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/WithObject.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/WithObject.java Wed Sep 16 16:26:30 2015 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/WithObject.java Wed Sep 16 14:42:32 2015 +0200 @@ -46,7 +46,7 @@ * */ public final class WithObject extends Scope { - private static final MethodHandle WITHEXPRESSIONGUARD = findOwnMH("withExpressionGuard", boolean.class, Object.class, PropertyMap.class, SwitchPoint.class); + private static final MethodHandle WITHEXPRESSIONGUARD = findOwnMH("withExpressionGuard", boolean.class, Object.class, PropertyMap.class, SwitchPoint[].class); private static final MethodHandle WITHEXPRESSIONFILTER = findOwnMH("withFilterExpression", Object.class, Object.class); private static final MethodHandle WITHSCOPEFILTER = findOwnMH("withFilterScope", Object.class, Object.class); private static final MethodHandle BIND_TO_EXPRESSION_OBJ = findOwnMH("bindToExpression", Object.class, Object.class, Object.class); @@ -360,13 +360,24 @@ private MethodHandle expressionGuard(final String name, final ScriptObject owner) { final PropertyMap map = expression.getMap(); - final SwitchPoint sp = expression.getProtoSwitchPoint(name, owner); + final SwitchPoint[] sp = expression.getProtoSwitchPoints(name, owner); return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp); } @SuppressWarnings("unused") - private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint sp) { - return ((WithObject)receiver).expression.getMap() == map && (sp == null || !sp.hasBeenInvalidated()); + private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint[] sp) { + return ((WithObject)receiver).expression.getMap() == map && !hasBeenInvalidated(sp); + } + + private static boolean hasBeenInvalidated(final SwitchPoint[] switchPoints) { + if (switchPoints != null) { + for (final SwitchPoint switchPoint : switchPoints) { + if (switchPoint.hasBeenInvalidated()) { + return true; + } + } + } + return false; } /** diff -r c209abbe9b24 -r b4eb53200105 test/script/basic/JDK-8134569.js --- a/test/script/basic/JDK-8134569.js Wed Sep 16 16:26:30 2015 +0530 +++ b/test/script/basic/JDK-8134569.js Wed Sep 16 14:42:32 2015 +0200 @@ -62,67 +62,146 @@ return new C(); } +function createDeeper() { + function C() { + this.i1 = 1; + this.i2 = 2; + this.i3 = 3; + return this; + } + function D() { + this.p1 = 1; + this.p2 = 2; + this.p3 = 3; + return this; + } + function E() { + this.e1 = 1; + this.e2 = 2; + this.e3 = 3; + return this; + } + D.prototype = new E(); + C.prototype = new D(); + return new C(); +} + function createEval() { return eval("Object.create({})"); } function p(o) { print(o.x) } -var a, b; +function e(o) { print(o.e1) } + +var a, b, c; create(); a = create(); b = create(); +c = create(); a.__proto__.x = 123; p(a); p(b); +p(c); a = create(); b = create(); +c = create(); b.__proto__.x = 123; p(a); p(b); +p(c); a = createEmpty(); b = createEmpty(); +c = createEmpty(); a.__proto__.x = 123; p(a); p(b); +p(c); a = createEmpty(); b = createEmpty(); +c = createEmpty(); b.__proto__.x = 123; p(a); p(b); +p(c); a = createDeep(); b = createDeep(); +c = createDeep(); a.__proto__.__proto__.x = 123; p(a); p(b); +p(c); a = createDeep(); b = createDeep(); +c = createDeep(); b.__proto__.__proto__.x = 123; p(a); p(b); +p(c); + +a = createDeeper(); +b = createDeeper(); +c = createDeeper(); +a.__proto__.__proto__.__proto__.x = 123; + +p(a); +p(b); +p(c); + +a = createDeeper(); +b = createDeeper(); +c = createDeeper(); +b.__proto__.__proto__.__proto__.x = 123; + +p(a); +p(b); +p(c); + +a = createDeeper(); +b = createDeeper(); +c = createDeeper(); +a.__proto__.__proto__ = null; + +e(a); +e(b); +e(c); + +a = createDeeper(); +b = createDeeper(); +c = createDeeper(); +b.__proto__.__proto__ = null; + +e(a); +e(b); +e(c); + a = createEval(); b = createEval(); +c = createEval(); a.__proto__.x = 123; p(a); p(b); +p(c); a = createEval(); b = createEval(); +c = createEval(); b.__proto__.x = 123; p(a); p(b); +p(c); diff -r c209abbe9b24 -r b4eb53200105 test/script/basic/JDK-8134569.js.EXPECTED --- a/test/script/basic/JDK-8134569.js.EXPECTED Wed Sep 16 16:26:30 2015 +0530 +++ b/test/script/basic/JDK-8134569.js.EXPECTED Wed Sep 16 14:42:32 2015 +0200 @@ -1,16 +1,36 @@ 123 undefined undefined +undefined 123 +undefined 123 undefined undefined +undefined +123 +undefined +123 +undefined +undefined +undefined 123 +undefined 123 undefined undefined +undefined 123 +undefined +undefined +1 +1 +1 +undefined +1 123 undefined undefined +undefined 123 +undefined diff -r c209abbe9b24 -r b4eb53200105 test/script/basic/JDK-8134609.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/script/basic/JDK-8134609.js Wed Sep 16 14:42:32 2015 +0200 @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2015, 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. + * + * 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. + */ + +/** + * JDK-8134609: Allow constructors with same prototoype map to share the allocator map + * + * @test + * @run + * @fork + * @option -Dnashorn.debug + */ + +function createProto(members) { + function P() { + for (var id in members) { + if (members.hasOwnProperty(id)) { + this[id] = members[id]; + } + } + return this; + } + return new P(); +} + +function createSubclass(prototype, members) { + function C() { + for (var id in members) { + if (members.hasOwnProperty(id)) { + this[id] = members[id]; + } + } + return this; + } + + C.prototype = prototype; + + return new C(); +} + +function assertP1(object, value) { + Assert.assertTrue(object.p1 === value); +} + +// First prototype will have non-shared proto-map. Second and third will be shared. +var proto0 = createProto({p1: 0, p2: 1}); +var proto1 = createProto({p1: 1, p2: 2}); +var proto2 = createProto({p1: 2, p2: 3}); + +Assert.assertTrue(Debug.map(proto1) === Debug.map(proto2)); + +assertP1(proto1, 1); +assertP1(proto2, 2); + +// First instantiation will have a non-shared prototype map, from the second one +// maps will be shared until a different proto map comes along. +var child0 = createSubclass(proto1, {c1: 1, c2: 2}); +var child1 = createSubclass(proto2, {c1: 2, c2: 3}); +var child2 = createSubclass(proto1, {c1: 3, c2: 4}); +var child3 = createSubclass(proto2, {c1: 1, c2: 2}); +var child4 = createSubclass(proto0, {c1: 3, c2: 2}); + +Assert.assertTrue(Debug.map(child1) === Debug.map(child2)); +Assert.assertTrue(Debug.map(child1) === Debug.map(child3)); +Assert.assertTrue(Debug.map(child3) !== Debug.map(child4)); + +assertP1(child1, 2); +assertP1(child2, 1); +assertP1(child3, 2); +assertP1(child4, 0); + +Assert.assertTrue(delete proto2.p1); + +assertP1(child3, undefined); +assertP1(child2, 1); +Assert.assertTrue(Debug.map(child1) !== Debug.map(child3));