diff options
author | hannesw <none@none> | 2014-03-03 15:23:01 +0100 |
---|---|---|
committer | hannesw <none@none> | 2014-03-03 15:23:01 +0100 |
commit | 29a489a0e7ea64249200f420c8e5466822acc702 (patch) | |
tree | a1c1df7e851b92c5bd7cfc2476fd0dc22df3639d /src/jdk/nashorn/internal/runtime | |
parent | 05b944f823cb1b4a5c92908faa84b9d957d8aeef (diff) |
8035948: Redesign property listeners for shared classes
Reviewed-by: sundar, lagergren
Diffstat (limited to 'src/jdk/nashorn/internal/runtime')
11 files changed, 479 insertions, 540 deletions
diff --git a/src/jdk/nashorn/internal/runtime/PropertyListener.java b/src/jdk/nashorn/internal/runtime/PropertyListener.java deleted file mode 100644 index 307d58b4..00000000 --- a/src/jdk/nashorn/internal/runtime/PropertyListener.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.nashorn.internal.runtime; - -/** - * Property change listener gets notified whenever properties are added/deleted/modified. - */ -public interface PropertyListener { - /** - * A new property is being added. - * - * @param object The ScriptObject to which property was added. - * @param prop The new Property added. - */ - public void propertyAdded(ScriptObject object, Property prop); - - /** - * An existing property is being deleted. - * - * @param object The ScriptObject whose property is being deleted. - * @param prop The property being deleted. - */ - public void propertyDeleted(ScriptObject object, Property prop); - - /** - * An existing Property is being replaced with a new Property. - * - * @param object The ScriptObject whose property is being modified. - * @param oldProp The old property that is being replaced. - * @param newProp The new property that replaces the old property. - * - */ - public void propertyModified(ScriptObject object, Property oldProp, Property newProp); - - /** - * Given object's __proto__ has changed. - * - * @param object object whose __proto__ has changed. - * @param oldProto old __proto__ - * @param newProto new __proto__ - */ - public void protoChanged(ScriptObject object, ScriptObject oldProto, ScriptObject newProto); -} diff --git a/src/jdk/nashorn/internal/runtime/PropertyListenerManager.java b/src/jdk/nashorn/internal/runtime/PropertyListenerManager.java deleted file mode 100644 index 27ec2c97..00000000 --- a/src/jdk/nashorn/internal/runtime/PropertyListenerManager.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.nashorn.internal.runtime; - -import java.util.Map; -import java.util.WeakHashMap; - -/** - * Helper class to manage property listeners and notification. - */ -public class PropertyListenerManager implements PropertyListener { - PropertyListenerManager() {} - - /** property listeners for this object. */ - private Map<PropertyListener,Boolean> listeners; - - // These counters are updated in debug mode - private static int listenersAdded; - private static int listenersRemoved; - - /** - * Return aggregate listeners added to all PropertyListenerManagers - * @return the listenersAdded - */ - public static int getListenersAdded() { - return listenersAdded; - } - - /** - * Return aggregate listeners removed from all PropertyListenerManagers - * @return the listenersRemoved - */ - public static int getListenersRemoved() { - return listenersRemoved; - } - - /** - * Return listeners added to this PropertyListenerManager. - * @return the listener count - */ - public final int getListenerCount() { - return listeners != null? listeners.size() : 0; - } - - // Property listener management methods - - /** - * Add a property listener to this object. - * - * @param listener The property listener that is added. - */ - public synchronized final void addPropertyListener(final PropertyListener listener) { - if (listeners == null) { - listeners = new WeakHashMap<>(); - } - - if (Context.DEBUG) { - listenersAdded++; - } - listeners.put(listener, Boolean.TRUE); - } - - /** - * Remove a property listener from this object. - * - * @param listener The property listener that is removed. - */ - public synchronized final void removePropertyListener(final PropertyListener listener) { - if (listeners != null) { - if (Context.DEBUG) { - listenersRemoved++; - } - listeners.remove(listener); - } - } - - /** - * This method can be called to notify property addition to this object's listeners. - * - * @param object The ScriptObject to which property was added. - * @param prop The property being added. - */ - protected synchronized final void notifyPropertyAdded(final ScriptObject object, final Property prop) { - if (listeners != null) { - for (PropertyListener listener : listeners.keySet()) { - listener.propertyAdded(object, prop); - } - } - } - - /** - * This method can be called to notify property deletion to this object's listeners. - * - * @param object The ScriptObject from which property was deleted. - * @param prop The property being deleted. - */ - protected synchronized final void notifyPropertyDeleted(final ScriptObject object, final Property prop) { - if (listeners != null) { - for (PropertyListener listener : listeners.keySet()) { - listener.propertyDeleted(object, prop); - } - } - } - - /** - * This method can be called to notify property modification to this object's listeners. - * - * @param object The ScriptObject to which property was modified. - * @param oldProp The old property being replaced. - * @param newProp The new property that replaces the old property. - */ - protected synchronized final void notifyPropertyModified(final ScriptObject object, final Property oldProp, final Property newProp) { - if (listeners != null) { - for (PropertyListener listener : listeners.keySet()) { - listener.propertyModified(object, oldProp, newProp); - } - } - } - - /** - * This method can be called to notify __proto__ modification to this object's listeners. - * - * @param object The ScriptObject whose __proto__ was changed. - * @param oldProto old __proto__ - * @param newProto new __proto__ - */ - protected synchronized final void notifyProtoChanged(final ScriptObject object, final ScriptObject oldProto, final ScriptObject newProto) { - if (listeners != null) { - for (PropertyListener listener : listeners.keySet()) { - listener.protoChanged(object, oldProto, newProto); - } - } - } - - // PropertyListener methods - - @Override - public final void propertyAdded(final ScriptObject object, final Property prop) { - notifyPropertyAdded(object, prop); - } - - @Override - public final void propertyDeleted(final ScriptObject object, final Property prop) { - notifyPropertyDeleted(object, prop); - } - - @Override - public final void propertyModified(final ScriptObject object, final Property oldProp, final Property newProp) { - notifyPropertyModified(object, oldProp, newProp); - } - - @Override - public final void protoChanged(final ScriptObject object, final ScriptObject oldProto, final ScriptObject newProto) { - notifyProtoChanged(object, oldProto, newProto); - } -} diff --git a/src/jdk/nashorn/internal/runtime/PropertyListeners.java b/src/jdk/nashorn/internal/runtime/PropertyListeners.java new file mode 100644 index 00000000..532969cc --- /dev/null +++ b/src/jdk/nashorn/internal/runtime/PropertyListeners.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2010, 2014, 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.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Helper class to manage property listeners and notification. + */ +public class PropertyListeners { + + private Map<String, WeakPropertyMapSet> listeners; + + // These counters are updated in debug mode + private static int listenersAdded; + private static int listenersRemoved; + + /** + * Copy constructor + * @param listener listener to copy + */ + PropertyListeners(final PropertyListeners listener) { + if (listener != null && listener.listeners != null) { + this.listeners = new WeakHashMap<>(listener.listeners); + } + } + + /** + * Return aggregate listeners added to all PropertyListenerManagers + * @return the listenersAdded + */ + public static int getListenersAdded() { + return listenersAdded; + } + + /** + * Return aggregate listeners removed from all PropertyListenerManagers + * @return the listenersRemoved + */ + public static int getListenersRemoved() { + return listenersRemoved; + } + + /** + * Return listeners added to this 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; + } + + // Property listener management methods + + /** + * Add {@code propertyMap} as property listener to {@code listeners} using key {@code key} by + * creating and returning a new {@code PropertyListeners} instance. + * + * @param listeners the original property listeners instance, may be null + * @param key the property key + * @param propertyMap the property map + * @return the new property map + */ + public static PropertyListeners addListener(final PropertyListeners listeners, final String key, final PropertyMap propertyMap) { + final PropertyListeners newListeners; + if (listeners == null || !listeners.containsListener(key, propertyMap)) { + newListeners = new PropertyListeners(listeners); + newListeners.addListener(key, propertyMap); + return newListeners; + } + return listeners; + } + + /** + * Checks whether {@code propertyMap} is registered as listener with {@code key}. + * + * @param key the property key + * @param propertyMap the property map + * @return true if property map is registered with property key + */ + synchronized boolean containsListener(final String key, final PropertyMap propertyMap) { + if (listeners == null) { + return false; + } + WeakPropertyMapSet set = listeners.get(key); + return set != null && set.contains(propertyMap); + } + + /** + * Add a property listener to this object. + * + * @param propertyMap The property listener that is added. + */ + synchronized final void addListener(final String key, final PropertyMap propertyMap) { + if (Context.DEBUG) { + listenersAdded++; + } + if (listeners == null) { + listeners = new WeakHashMap<>(); + } + + WeakPropertyMapSet set = listeners.get(key); + if (set == null) { + set = new WeakPropertyMapSet(); + listeners.put(key, set); + } + if (!set.contains(propertyMap)) { + set.add(propertyMap); + } + } + + /** + * A new property is being added. + * + * @param prop The new Property added. + */ + public synchronized void propertyAdded(final Property prop) { + if (listeners != null) { + WeakPropertyMapSet set = listeners.get(prop.getKey()); + if (set != null) { + for (PropertyMap propertyMap : set.elements()) { + propertyMap.propertyAdded(prop); + } + listeners.remove(prop.getKey()); + } + } + } + + /** + * An existing property is being deleted. + * + * @param prop The property being deleted. + */ + public synchronized void propertyDeleted(final Property prop) { + if (listeners != null) { + WeakPropertyMapSet set = listeners.get(prop.getKey()); + if (set != null) { + for (PropertyMap propertyMap : set.elements()) { + propertyMap.propertyDeleted(prop); + } + listeners.remove(prop.getKey()); + } + } + } + + /** + * An existing Property is being replaced with a new Property. + * + * @param oldProp The old property that is being replaced. + * @param newProp The new property that replaces the old property. + * + */ + public synchronized void propertyModified(final Property oldProp, final Property newProp) { + if (listeners != null) { + WeakPropertyMapSet set = listeners.get(oldProp.getKey()); + if (set != null) { + for (PropertyMap propertyMap : set.elements()) { + propertyMap.propertyModified(oldProp, newProp); + } + listeners.remove(oldProp.getKey()); + } + } + } + + public synchronized void protoChanged() { + if (listeners != null) { + for (WeakPropertyMapSet set : listeners.values()) { + for (PropertyMap propertyMap : set.elements()) { + propertyMap.protoChanged(); + } + } + listeners.clear(); + } + } + + private static class WeakPropertyMapSet { + + private WeakHashMap<PropertyMap, Boolean> map = new WeakHashMap<>(); + + void add(final PropertyMap propertyMap) { + map.put(propertyMap, Boolean.TRUE); + } + + boolean contains(final PropertyMap propertyMap) { + return map.containsKey(propertyMap); + } + + Set<PropertyMap> elements() { + return map.keySet(); + } + + } +} diff --git a/src/jdk/nashorn/internal/runtime/PropertyMap.java b/src/jdk/nashorn/internal/runtime/PropertyMap.java index 03c4978a..77bcc83f 100644 --- a/src/jdk/nashorn/internal/runtime/PropertyMap.java +++ b/src/jdk/nashorn/internal/runtime/PropertyMap.java @@ -30,13 +30,11 @@ import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; import java.lang.invoke.SwitchPoint; -import java.lang.ref.WeakReference; +import java.lang.ref.SoftReference; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.NoSuchElementException; import java.util.WeakHashMap; @@ -49,17 +47,11 @@ import java.util.WeakHashMap; * 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>, PropertyListener { +public final class PropertyMap implements Iterable<Object> { /** 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; /** Does this map contain valid array keys? */ public static final int CONTAINS_ARRAY_KEYS = 0b0000_0010; - /** This mask is used to preserve certain flags when cloning the PropertyMap. Others should not be copied */ - private static final int CLONEABLE_FLAGS_MASK = 0b0000_1111; - /** Has a listener been added to this property map. This flag is not copied when cloning a map. See {@link PropertyListener} */ - public static final int IS_LISTENER_ADDED = 0b0001_0000; - /** Is this process wide "shared" map?. This flag is not copied when cloning a map */ - public static final int IS_SHARED = 0b0010_0000; /** Map status flags. */ private int flags; @@ -77,16 +69,16 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { private int spillLength; /** {@link SwitchPoint}s for gets on inherited properties. */ - private Map<String, SwitchPoint> protoGetSwitches; + private HashMap<String, SwitchPoint> protoGetSwitches; /** History of maps, used to limit map duplication. */ - private HashMap<Property, PropertyMap> history; + private WeakHashMap<Property, SoftReference<PropertyMap>> history; /** History of prototypes, used to limit map duplication. */ - private WeakHashMap<ScriptObject, WeakReference<PropertyMap>> protoHistory; + private WeakHashMap<PropertyMap, SoftReference<PropertyMap>> protoHistory; - /** Cache for hashCode */ - private int hashCode; + /** property listeners */ + private PropertyListeners listeners; /** * Constructor. @@ -119,10 +111,12 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { */ private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties) { this.properties = properties; - this.flags = propertyMap.getClonedFlags(); + this.flags = propertyMap.flags; this.spillLength = propertyMap.spillLength; this.fieldCount = propertyMap.fieldCount; this.fieldMaximum = propertyMap.fieldMaximum; + // We inherit the parent property listeners instance. It will be cloned when a new listener is added. + this.listeners = propertyMap.listeners; if (Context.DEBUG) { count++; @@ -203,35 +197,92 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { } /** - * Return a SwitchPoint used to track changes of a property in a prototype. + * Get the listeners of this map, or null if none exists * - * @param proto Object prototype. - * @param key {@link Property} key. + * @return the listeners + */ + public PropertyListeners getListeners() { + return listeners; + } + + /** + * Add {@code listenerMap} as a listener to this property map for the given {@code key}. * - * @return A shared {@link SwitchPoint} for the property. + * @param key the property name + * @param listenerMap the listener map */ - public SwitchPoint getProtoGetSwitchPoint(final ScriptObject proto, final String key) { - assert !isShared() : "proto SwitchPoint from a shared PropertyMap"; + 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); + } + } - if (proto == null) { - return null; + /** + * A new property is being added. + * + * @param property The new Property added. + */ + public void propertyAdded(final Property property) { + invalidateProtoGetSwitchPoint(property); + if (listeners != null) { + listeners.propertyAdded(property); } + } + /** + * An existing property is being deleted. + * + * @param property The property being deleted. + */ + public void propertyDeleted(final Property property) { + invalidateProtoGetSwitchPoint(property); + if (listeners != null) { + listeners.propertyDeleted(property); + } + } + + /** + * An existing property is being redefined. + * + * @param oldProperty The old property + * @param newProperty The new property + */ + public void propertyModified(final Property oldProperty, final Property newProperty) { + invalidateProtoGetSwitchPoint(oldProperty); + if (listeners != null) { + listeners.propertyModified(oldProperty, newProperty); + } + } + + /** + * The prototype of an object associated with this {@link PropertyMap} is changed. + */ + public void protoChanged() { + invalidateAllProtoGetSwitchPoints(); + if (listeners != null) { + listeners.protoChanged(); + } + } + + /** + * 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 (protoGetSwitches == null) { protoGetSwitches = new HashMap<>(); - if (! isListenerAdded()) { - proto.addPropertyListener(this); - setIsListenerAdded(); - } } - if (protoGetSwitches.containsKey(key)) { - return protoGetSwitches.get(key); + SwitchPoint switchPoint = protoGetSwitches.get(key); + if (switchPoint == null) { + switchPoint = new SwitchPoint(); + protoGetSwitches.put(key, switchPoint); } - final SwitchPoint switchPoint = new SwitchPoint(); - protoGetSwitches.put(key, switchPoint); - return switchPoint; } @@ -240,14 +291,13 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { * * @param property {@link Property} to invalidate. */ - private void invalidateProtoGetSwitchPoint(final Property property) { - assert !isShared() : "proto invalidation on a shared PropertyMap"; - + synchronized void invalidateProtoGetSwitchPoint(final Property property) { if (protoGetSwitches != null) { + final String key = property.getKey(); final SwitchPoint sp = protoGetSwitches.get(key); if (sp != null) { - protoGetSwitches.put(key, new SwitchPoint()); + protoGetSwitches.remove(key); if (Context.DEBUG) { protoInvalidations++; } @@ -257,14 +307,15 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { } /** - * Indicate that proto itself has changed in hierachy somewhere. + * Indicate that proto itself has changed in hierarchy somewhere. */ - private void invalidateAllProtoGetSwitchPoints() { - assert !isShared() : "proto invalidation on a shared PropertyMap"; - - if (protoGetSwitches != null) { - final Collection<SwitchPoint> sws = protoGetSwitches.values(); - SwitchPoint.invalidateAll(sws.toArray(new SwitchPoint[sws.size()])); + synchronized void invalidateAllProtoGetSwitchPoints() { + if (protoGetSwitches != null && !protoGetSwitches.isEmpty()) { + if (Context.DEBUG) { + protoInvalidations += protoGetSwitches.size(); + } + SwitchPoint.invalidateAll(protoGetSwitches.values().toArray(new SwitchPoint[protoGetSwitches.values().size()])); + protoGetSwitches.clear(); } } @@ -279,7 +330,33 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { * @return New {@link PropertyMap} with {@link Property} added. */ PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) { - return addProperty(new AccessorProperty(property, bindTo)); + // No need to store bound property in the history as bound properties can't be reused. + return addPropertyNoHistory(new AccessorProperty(property, bindTo)); + } + + /** + * 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 PropertyMap addPropertyNoHistory(final Property property) { + if (listeners != null) { + listeners.propertyAdded(property); + } + final PropertyHashMap newProperties = properties.immutableAdd(property); + final PropertyMap newMap = new PropertyMap(this, newProperties); + + if(!property.isSpill()) { + newMap.fieldCount = Math.max(newMap.fieldCount, property.getSlot() + 1); + } + if (isValidArrayIndex(getArrayIndex(property.getKey()))) { + newMap.setContainsArrayKeys(); + } + + newMap.spillLength += property.getSpillCount(); + return newMap; } /** @@ -290,6 +367,9 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { * @return New {@link PropertyMap} with {@link Property} added. */ public PropertyMap addProperty(final Property property) { + if (listeners != null) { + listeners.propertyAdded(property); + } PropertyMap newMap = checkHistory(property); if (newMap == null) { @@ -318,6 +398,9 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found. */ public PropertyMap deleteProperty(final Property property) { + if (listeners != null) { + listeners.propertyDeleted(property); + } PropertyMap newMap = checkHistory(property); final String key = property.getKey(); @@ -339,6 +422,9 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { * @return New {@link PropertyMap} with {@link Property} replaced. */ PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) { + if (listeners != null) { + listeners.propertyModified(oldProperty, newProperty); + } // Add replaces existing property. final PropertyHashMap newProperties = properties.immutableAdd(newProperty); final PropertyMap newMap = new PropertyMap(this, newProperties); @@ -363,7 +449,7 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { (oldProperty instanceof AccessorProperty && newProperty instanceof UserAccessorProperty) : "arbitrary replaceProperty attempted"; - newMap.flags = getClonedFlags(); + newMap.flags = flags; /* * spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need @@ -491,28 +577,6 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { } /** - * Make this property map 'shared' one. Shared property map instances are - * process wide singleton objects. A shaped map should never be added as a listener - * to a proto object. Nor it should have history or proto history. A shared map - * is just a template that is meant to be duplicated before use. All nasgen initialized - * property maps are shared. - * - * @return this map after making it as shared - */ - public PropertyMap setIsShared() { - assert !isListenerAdded() : "making PropertyMap shared after listener added"; - assert protoHistory == null : "making PropertyMap shared after associating a proto with it"; - if (Context.DEBUG) { - sharedCount++; - } - - flags |= IS_SHARED; - // clear any history on this PropertyMap, won't be used. - history = null; - return this; - } - - /** * Check for any configurable properties. * * @return {@code true} if any configurable. @@ -551,14 +615,14 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { /** * Check prototype history for an existing property map with specified prototype. * - * @param newProto New prototype object. + * @param parentMap New prototype object. * * @return Existing {@link PropertyMap} or {@code null} if not found. */ - private PropertyMap checkProtoHistory(final ScriptObject newProto) { + private PropertyMap checkProtoHistory(final PropertyMap parentMap) { final PropertyMap cachedMap; if (protoHistory != null) { - final WeakReference<PropertyMap> weakMap = protoHistory.get(newProto); + final SoftReference<PropertyMap> weakMap = protoHistory.get(parentMap); cachedMap = (weakMap != null ? weakMap.get() : null); } else { cachedMap = null; @@ -574,17 +638,15 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { /** * Add a map to the prototype history. * - * @param newProto Prototype to add (key.) + * @param parentMap Prototype to add (key.) * @param newMap {@link PropertyMap} associated with prototype. */ - private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { - assert !isShared() : "proto history modified on a shared PropertyMap"; - + private void addToProtoHistory(final PropertyMap parentMap, final PropertyMap newMap) { if (protoHistory == null) { protoHistory = new WeakHashMap<>(); } - protoHistory.put(newProto, new WeakReference<>(newMap)); + protoHistory.put(parentMap, new SoftReference<>(newMap)); } /** @@ -594,14 +656,12 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { * @param newMap Modified {@link PropertyMap}. */ private void addToHistory(final Property property, final PropertyMap newMap) { - assert !isShared() : "history modified on a shared PropertyMap"; - if (!properties.isEmpty()) { if (history == null) { - history = new LinkedHashMap<>(); + history = new WeakHashMap<>(); } - history.put(property, newMap); + history.put(property, new SoftReference<>(newMap)); } } @@ -613,8 +673,10 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { * @return Existing map or {@code null} if not found. */ private PropertyMap checkHistory(final Property property) { + if (history != null) { - PropertyMap historicMap = history.get(property); + SoftReference<PropertyMap> ref = history.get(property); + final PropertyMap historicMap = ref == null ? null : ref.get(); if (historicMap != null) { if (Context.DEBUG) { @@ -628,54 +690,6 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { return null; } - /** - * Calculate the hash code for the map. - * - * @return Computed hash code. - */ - private int computeHashCode() { - int hash = 0; - - for (final Property property : getProperties()) { - hash = hash << 7 ^ hash >> 7; - hash ^= property.hashCode(); - } - - return hash; - } - - @Override - public int hashCode() { - if (hashCode == 0 && !properties.isEmpty()) { - hashCode = computeHashCode(); - } - return hashCode; - } - - @Override - public boolean equals(final Object other) { - if (!(other instanceof PropertyMap)) { - return false; - } - - final PropertyMap otherMap = (PropertyMap)other; - - 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().equals(otherIter.next())) { - return false; - } - } - - return true; - } - @Override public String toString() { final StringBuilder sb = new StringBuilder(); @@ -728,24 +742,6 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { } /** - * Check whether a {@link PropertyListener} has been added to this map. - * - * @return {@code true} if {@link PropertyListener} exists - */ - public boolean isListenerAdded() { - return (flags & IS_LISTENER_ADDED) != 0; - } - - /** - * Check if this map shared or not. - * - * @return true if this map is shared. - */ - public boolean isShared() { - return (flags & IS_SHARED) != 0; - } - - /** * Test to see if {@link PropertyMap} is extensible. * * @return {@code true} if {@link PropertyMap} can be added to. @@ -800,50 +796,29 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { } /** - * Change the prototype of objects associated with this {@link PropertyMap}. + * Return a property map with the same layout that is associated with the new prototype object. * - * @param oldProto Current prototype object. * @param newProto New prototype object to replace oldProto. - * * @return New {@link PropertyMap} with prototype changed. */ - PropertyMap changeProto(final ScriptObject oldProto, final ScriptObject newProto) { - assert !isShared() : "proto associated with a shared PropertyMap"; + public PropertyMap changeProto(final ScriptObject newProto) { - if (oldProto == newProto) { - return this; - } - - final PropertyMap nextMap = checkProtoHistory(newProto); + final PropertyMap parentMap = newProto == null ? null : newProto.getMap(); + final PropertyMap nextMap = checkProtoHistory(parentMap); if (nextMap != null) { return nextMap; } if (Context.DEBUG) { - incrementSetProtoNewMapCount(); + setProtoNewMapCount++; } final PropertyMap newMap = new PropertyMap(this); - addToProtoHistory(newProto, newMap); + addToProtoHistory(parentMap, newMap); return newMap; } - /** - * Indicate that the map has listeners. - */ - private void setIsListenerAdded() { - flags |= IS_LISTENER_ADDED; - } - - /** - * Return only the flags that should be copied during cloning. - * - * @return Subset of flags that should be copied. - */ - private int getClonedFlags() { - return flags & CLONEABLE_FLAGS_MASK; - } /** * {@link PropertyMap} iterator. @@ -900,41 +875,12 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { } /* - * PropertyListener implementation. - */ - - @Override - public void propertyAdded(final ScriptObject object, final Property prop) { - invalidateProtoGetSwitchPoint(prop); - } - - @Override - public void propertyDeleted(final ScriptObject object, final Property prop) { - invalidateProtoGetSwitchPoint(prop); - } - - @Override - public void propertyModified(final ScriptObject object, final Property oldProp, final Property newProp) { - invalidateProtoGetSwitchPoint(oldProp); - } - - @Override - public void protoChanged(final ScriptObject object, final ScriptObject oldProto, final ScriptObject newProto) { - // We may walk and invalidate SwitchPoints for properties inherited - // from 'object' or it's old proto chain. But, it may not be worth it. - // For example, a new proto may have a user defined getter/setter for - // a data property down the chain. So, invalidating all is better. - invalidateAllProtoGetSwitchPoints(); - } - - /* * Debugging and statistics. */ // counters updated only in debug mode private static int count; private static int clonedCount; - private static int sharedCount; private static int duplicatedCount; private static int historyHit; private static int protoInvalidations; @@ -956,13 +902,6 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { } /** - * @return The number of maps that are shared. - */ - public static int getSharedCount() { - return sharedCount; - } - - /** * @return The number of maps that are duplicated. */ public static int getDuplicatedCount() { @@ -997,10 +936,4 @@ public final class PropertyMap implements Iterable<Object>, PropertyListener { return setProtoNewMapCount; } - /** - * Increment the prototype set count. - */ - private static void incrementSetProtoNewMapCount() { - setProtoNewMapCount++; - } } diff --git a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java index e5ca3c9a..2b18cbc8 100644 --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java @@ -160,10 +160,10 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData { } @Override - ScriptObject allocate() { + ScriptObject allocate(final PropertyMap map) { try { ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try - return allocator == null ? null : (ScriptObject)allocator.invokeExact(allocatorMap); + return allocator == null ? null : (ScriptObject)allocator.invokeExact(map); } catch (final RuntimeException | Error e) { throw e; } catch (final Throwable t) { @@ -178,6 +178,11 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData { } @Override + PropertyMap getAllocatorMap() { + return allocatorMap; + } + + @Override protected synchronized void ensureCodeGenerated() { if (!code.isEmpty()) { return; // nothing to do, we have code, at least some. diff --git a/src/jdk/nashorn/internal/runtime/ScriptFunction.java b/src/jdk/nashorn/internal/runtime/ScriptFunction.java index d0669d70..0f5174ea 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptFunction.java +++ b/src/jdk/nashorn/internal/runtime/ScriptFunction.java @@ -80,6 +80,9 @@ public abstract class ScriptFunction extends ScriptObject { private final ScriptFunctionData data; + /** The property map used for newly allocated object when function is used as constructor. */ + protected PropertyMap allocatorMap; + /** * Constructor * @@ -125,6 +128,7 @@ public abstract class ScriptFunction extends ScriptObject { this.data = data; this.scope = scope; + this.allocatorMap = data.getAllocatorMap(); } @Override @@ -229,16 +233,16 @@ public abstract class ScriptFunction extends ScriptObject { } assert !isBoundFunction(); // allocate never invoked on bound functions - final ScriptObject object = data.allocate(); + final ScriptObject object = data.allocate(allocatorMap); if (object != null) { Object prototype = getPrototype(); if (prototype instanceof ScriptObject) { - object.setProto((ScriptObject)prototype); + object.setInitialProto((ScriptObject)prototype); } if (object.getProto() == null) { - object.setProto(getObjectPrototype()); + object.setInitialProto(getObjectPrototype()); } } diff --git a/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java b/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java index 4dbbfba9..f0569937 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java +++ b/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java @@ -229,9 +229,20 @@ public abstract class ScriptFunctionData { /** * Allocates an object using this function's allocator. + * + * @param map the property map for the allocated object. * @return the object allocated using this function's allocator, or null if the function doesn't have an allocator. */ - ScriptObject allocate() { + ScriptObject allocate(final PropertyMap map) { + return null; + } + + /** + * Get the property map to use for objects allocated by this function. + * + * @return the property map for allocated objects. + */ + PropertyMap getAllocatorMap() { return null; } diff --git a/src/jdk/nashorn/internal/runtime/ScriptObject.java b/src/jdk/nashorn/internal/runtime/ScriptObject.java index 4b3bb939..b7f9c4c1 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptObject.java +++ b/src/jdk/nashorn/internal/runtime/ScriptObject.java @@ -43,6 +43,7 @@ import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.invoke.SwitchPoint; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; @@ -88,7 +89,7 @@ import jdk.nashorn.internal.runtime.linker.NashornGuards; * </ul> */ -public abstract class ScriptObject extends PropertyListenerManager implements PropertyAccess { +public abstract class ScriptObject implements PropertyAccess { /** __proto__ special property name */ public static final String PROTO_PROPERTY_NAME = "__proto__"; @@ -107,9 +108,6 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr /** Per ScriptObject flag - is this an arguments object? */ public static final int IS_ARGUMENTS = 0b0000_0100; - /** Is this a prototype PropertyMap? */ - public static final int IS_PROTOTYPE = 0b0000_1000; - /** Is length property not-writable? */ public static final int IS_LENGTH_NOT_WRITABLE = 0b0001_0000; @@ -155,7 +153,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr public static final Call GET_PROTO = virtualCallNoLookup(ScriptObject.class, "getProto", ScriptObject.class); /** Method handle for setting the proto of a ScriptObject */ - public static final Call SET_PROTO = virtualCallNoLookup(ScriptObject.class, "setProto", void.class, ScriptObject.class); + public static final Call SET_PROTO = virtualCallNoLookup(ScriptObject.class, "setInitialProto", void.class, ScriptObject.class); /** Method handle for setting the proto of a ScriptObject after checking argument */ public static final Call SET_PROTO_CHECK = virtualCallNoLookup(ScriptObject.class, "setProtoCheck", void.class, Object.class); @@ -201,10 +199,6 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr this.arrayData = ArrayData.EMPTY_ARRAY; this.setMap(map == null ? PropertyMap.newMap() : map); this.proto = proto; - - if (proto != null) { - proto.setIsPrototype(); - } } /** @@ -232,7 +226,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr if (oldProp == null) { if (property instanceof UserAccessorProperty) { final UserAccessorProperty prop = this.newUserAccessors(key, property.getFlags(), property.getGetterFunction(source), property.getSetterFunction(source)); - newMap = newMap.addProperty(prop); + newMap = newMap.addPropertyNoHistory(prop); } else { newMap = newMap.addPropertyBind((AccessorProperty)property, source); } @@ -875,8 +869,6 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr newProperty = newUserAccessors(oldProperty.getKey(), propertyFlags, getter, setter); } - notifyPropertyModified(this, oldProperty, newProperty); - return modifyOwnProperty(oldProperty, newProperty); } @@ -1120,29 +1112,33 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr */ public synchronized final void setProto(final ScriptObject newProto) { final ScriptObject oldProto = proto; - map = map.changeProto(oldProto, newProto); - if (newProto != null) { - newProto.setIsPrototype(); - } - - proto = newProto; - - if (isPrototype()) { - // tell listeners that my __proto__ has been changed - notifyProtoChanged(this, oldProto, newProto); - - if (oldProto != null) { - oldProto.removePropertyListener(this); - } + if (oldProto != newProto) { + proto = newProto; - if (newProto != null) { - newProto.addPropertyListener(this); + // Let current listeners know that the protototype has changed and set our map + final PropertyListeners listeners = getMap().getListeners(); + if (listeners != null) { + listeners.protoChanged(); } + // Replace our current allocator map with one that is associated with the new prototype. + setMap(getMap().changeProto(newProto)); } } /** + * Set the initial __proto__ of this object. This should be used instead of + * {@link #setProto} if it is known that the current property map will not be + * used on a new object with any other parent property map, so we can pass over + * property map invalidation/evolution. + * + * @param initialProto the initial __proto__ to set. + */ + public void setInitialProto(final ScriptObject initialProto) { + this.proto = initialProto; + } + + /** * Set the __proto__ of an object with checks. * @param newProto Prototype to set. */ @@ -1332,25 +1328,6 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr } /** - * Check if this object is a prototype - * - * @return {@code true} if is prototype - */ - public final boolean isPrototype() { - return (flags & IS_PROTOTYPE) != 0; - } - - /** - * Flag this object as having a prototype. - */ - public final void setIsPrototype() { - if (proto != null && !isPrototype()) { - proto.addPropertyListener(this); - } - flags |= IS_PROTOTYPE; - } - - /** * Check if this object has non-writable length property * * @return {@code true} if 'length' property is non-writable @@ -1791,6 +1768,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr final boolean noGuard = ObjectClassGenerator.OBJECT_FIELDS_ONLY && NashornCallSiteDescriptor.isFastScope(desc) && !property.canChangeType(); // getMap() is fine as we have the prototype switchpoint depending on where the property was found final MethodHandle guard = noGuard ? null : NashornGuards.getMapGuard(getMap()); + final ScriptObject owner = find.getOwner(); if (methodHandle != null) { assert methodHandle.type().returnType().equals(returnType); @@ -1798,18 +1776,18 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr return new GuardedInvocation(methodHandle, guard); } - if (!property.hasGetterFunction(find.getOwner())) { + if (!property.hasGetterFunction(owner)) { // If not a scope bind to actual prototype as changing prototype will change the property map. // For scopes we install a filter that replaces the self object with the prototype owning the property. methodHandle = isScope() ? addProtoFilter(methodHandle, find.getProtoChainLength()) : - bindTo(methodHandle, find.getOwner()); + bindTo(methodHandle, owner); } - return new GuardedInvocation(methodHandle, noGuard ? null : getMap().getProtoGetSwitchPoint(proto, name), guard); + return new GuardedInvocation(methodHandle, noGuard ? null : getProtoSwitchPoint(name, owner), guard); } assert !NashornCallSiteDescriptor.isFastScope(desc); - return new GuardedInvocation(Lookup.emptyGetter(returnType), getMap().getProtoGetSwitchPoint(proto, name), guard); + return new GuardedInvocation(Lookup.emptyGetter(returnType), getProtoSwitchPoint(name, owner), guard); } private static GuardedInvocation findMegaMorphicGetMethod(final CallSiteDescriptor desc, final String name, final boolean isMethod) { @@ -1866,6 +1844,28 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr } /** + * Get a switch point for a property with the given {@code name} that will be invalidated when + * the property definition is changed in this object's prototype chain. Returns {@code null} if + * the property is defined in this object itself. + * + * @param name the property name + * @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) { + if (owner == this || getProto() == null) { + return null; + } + + for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) { + ScriptObject parent = obj.getProto(); + parent.getMap().addListener(name, obj.getMap()); + } + + return getMap().getSwitchPoint(name); + } + + /** * Find the appropriate SET method for an invoke dynamic call. * * @param desc the call site descriptor @@ -1921,8 +1921,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr throw typeError(strictErrorMessage, name, ScriptRuntime.safeToString((this))); } assert canBeFastScope || !NashornCallSiteDescriptor.isFastScope(desc); - final PropertyMap myMap = getMap(); - return new GuardedInvocation(Lookup.EMPTY_SETTER, myMap.getProtoGetSwitchPoint(proto, name), NashornGuards.getMapGuard(myMap)); + return new GuardedInvocation(Lookup.EMPTY_SETTER, getProtoSwitchPoint(name, null), NashornGuards.getMapGuard(getMap())); } @SuppressWarnings("unused") @@ -2082,7 +2081,7 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr methodHandle = bindTo(methodHandle, UNDEFINED); } return new GuardedInvocation(methodHandle, - find.isInherited()? getMap().getProtoGetSwitchPoint(proto, NO_SUCH_PROPERTY_NAME) : null, + getProtoSwitchPoint(NO_SUCH_PROPERTY_NAME, find.getOwner()), getKnownFunctionPropertyGuard(getMap(), find.getGetter(Object.class), find.getOwner(), func)); } } @@ -2134,7 +2133,8 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr } private GuardedInvocation createEmptyGetter(final CallSiteDescriptor desc, final String name) { - return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), getMap().getProtoGetSwitchPoint(proto, name), NashornGuards.getMapGuard(getMap())); + return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), + getProtoSwitchPoint(name, null), NashornGuards.getMapGuard(getMap())); } private abstract static class ScriptObjectIterator <T extends Object> implements Iterator<T> { @@ -2215,12 +2215,10 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr if (fieldCount < fieldMaximum) { property = new AccessorProperty(key, propertyFlags & ~Property.IS_SPILL, getClass(), fieldCount); - notifyPropertyAdded(this, property); property = addOwnProperty(property); } else { int i = getMap().getSpillLength(); property = new AccessorProperty(key, propertyFlags | Property.IS_SPILL, i); - notifyPropertyAdded(this, property); property = addOwnProperty(property); i = property.getSlot(); @@ -3274,7 +3272,6 @@ public abstract class ScriptObject extends PropertyListenerManager implements Pr } final Property prop = find.getProperty(); - notifyPropertyDeleted(this, prop); deleteOwnProperty(prop); return true; diff --git a/src/jdk/nashorn/internal/runtime/SetMethodCreator.java b/src/jdk/nashorn/internal/runtime/SetMethodCreator.java index 7390aadd..bdbc8ec8 100644 --- a/src/jdk/nashorn/internal/runtime/SetMethodCreator.java +++ b/src/jdk/nashorn/internal/runtime/SetMethodCreator.java @@ -80,7 +80,7 @@ final class SetMethodCreator { } /** - * This class encapsulates the results of looking up a setter method; it's basically a triple of a method hanle, + * This class encapsulates the results of looking up a setter method; it's basically a triple of a method handle, * a Property object, and flags for invocation. * */ @@ -170,7 +170,10 @@ final class SetMethodCreator { private SetMethod createNewPropertySetter() { final SetMethod sm = map.getFieldCount() < map.getFieldMaximum() ? createNewFieldSetter() : createNewSpillPropertySetter(); - sobj.notifyPropertyAdded(sobj, sm.property); + final PropertyListeners listeners = map.getListeners(); + if (listeners != null) { + listeners.propertyAdded(sm.property); + } return sm; } diff --git a/src/jdk/nashorn/internal/runtime/WithObject.java b/src/jdk/nashorn/internal/runtime/WithObject.java index 6e03dc60..d4a28cd7 100644 --- a/src/jdk/nashorn/internal/runtime/WithObject.java +++ b/src/jdk/nashorn/internal/runtime/WithObject.java @@ -36,6 +36,7 @@ import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; import jdk.internal.dynalink.support.CallSiteDescriptorFactory; import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; +import jdk.nashorn.internal.runtime.linker.NashornGuards; /** * This class supports the handling of scope in a with body. @@ -123,7 +124,7 @@ public final class WithObject extends ScriptObject implements Scope { } if (find != null) { - return fixScopeCallSite(scope.lookup(desc, request), name); + return fixScopeCallSite(scope.lookup(desc, request), name, find.getOwner()); } // the property is not found - now check for @@ -175,7 +176,7 @@ public final class WithObject extends ScriptObject implements Scope { link = scope.lookup(desc, request); if (link != null) { - return fixScopeCallSite(link, name); + return fixScopeCallSite(link, name, null); } return null; @@ -252,13 +253,10 @@ public final class WithObject extends ScriptObject implements Scope { filterGuard(link, WITHEXPRESSIONFILTER)); } - private GuardedInvocation fixScopeCallSite(final GuardedInvocation link, final String name) { + private GuardedInvocation fixScopeCallSite(final GuardedInvocation link, final String name, final ScriptObject owner) { final GuardedInvocation newLink = fixReceiverType(link, WITHSCOPEFILTER); return link.replaceMethods(filter(newLink.getInvocation(), WITHSCOPEFILTER), - MH.guardWithTest( - expressionGuard(name), - filterGuard(newLink, WITHSCOPEFILTER), - MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class))); + NashornGuards.combineGuards(expressionGuard(name, owner), filterGuard(newLink, WITHSCOPEFILTER))); } private static MethodHandle filterGuard(final GuardedInvocation link, final MethodHandle filter) { @@ -288,9 +286,9 @@ public final class WithObject extends ScriptObject implements Scope { return fn.makeBoundFunction(withFilterExpression(receiver), new Object[0]); } - private MethodHandle expressionGuard(final String name) { + private MethodHandle expressionGuard(final String name, final ScriptObject owner) { final PropertyMap map = expression.getMap(); - final SwitchPoint sp = map.getProtoGetSwitchPoint(expression.getProto(), name); + final SwitchPoint sp = expression.getProtoSwitchPoint(name, owner); return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp); } diff --git a/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java b/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java index a8e79184..0b720be2 100644 --- a/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java +++ b/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java @@ -37,10 +37,10 @@ import jdk.nashorn.internal.runtime.ScriptObject; * Constructor of method handles used to guard call sites. */ public final class NashornGuards { - private static final MethodHandle IS_SCRIPTOBJECT = findOwnMH("isScriptObject", boolean.class, Object.class); - private static final MethodHandle IS_SCRIPTFUNCTION = findOwnMH("isScriptFunction", boolean.class, Object.class); - private static final MethodHandle IS_MAP = findOwnMH("isMap", boolean.class, Object.class, PropertyMap.class); - private static final MethodHandle IS_INSTANCEOF_2 = findOwnMH("isInstanceOf2", boolean.class, Object.class, Class.class, Class.class); + private static final MethodHandle IS_SCRIPTOBJECT = findOwnMH("isScriptObject", boolean.class, Object.class); + private static final MethodHandle IS_SCRIPTFUNCTION = findOwnMH("isScriptFunction", boolean.class, Object.class); + private static final MethodHandle IS_MAP = findOwnMH("isMap", boolean.class, Object.class, PropertyMap.class); + private static final MethodHandle IS_INSTANCEOF_2 = findOwnMH("isInstanceOf2", boolean.class, Object.class, Class.class, Class.class); // don't create me! private NashornGuards() { @@ -85,6 +85,17 @@ public final class NashornGuards { return MH.insertArguments(IS_INSTANCEOF_2, 1, class1, class2); } + /** + * Combine two method handles of type {@code (Object)boolean} using logical AND. + * + * @param guard1 the first guard + * @param guard2 the second guard, only invoked if guard1 returns true + * @return true if both guard1 and guard2 returned true + */ + public static MethodHandle combineGuards(final MethodHandle guard1, final MethodHandle guard2) { + return MH.guardWithTest(guard1, guard2, MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class)); + } + @SuppressWarnings("unused") private static boolean isScriptObject(final Object self) { return self instanceof ScriptObject; |