1 /* 2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.runtime; 27 28 import static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP; 29 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; 30 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; 31 32 import java.io.IOException; 33 import java.io.ObjectInputStream; 34 import java.io.ObjectOutputStream; 35 import java.io.Serializable; 36 import java.lang.invoke.SwitchPoint; 37 import java.lang.ref.Reference; 38 import java.lang.ref.SoftReference; 39 import java.lang.ref.WeakReference; 40 import java.util.Arrays; 41 import java.util.BitSet; 42 import java.util.Collection; 43 import java.util.HashMap; 44 import java.util.Iterator; 45 import java.util.NoSuchElementException; 46 import java.util.WeakHashMap; 47 import java.util.concurrent.atomic.LongAdder; 48 import jdk.nashorn.internal.runtime.options.Options; 49 import jdk.nashorn.internal.scripts.JO; 50 51 /** 52 * Map of object properties. The PropertyMap is the "template" for JavaScript object 53 * layouts. It contains a map with prototype names as keys and {@link Property} instances 54 * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor 55 * to form the seed map for the ScriptObject. 56 * <p> 57 * All property maps are immutable. If a property is added, modified or removed, the mutator 58 * will return a new map. 59 */ 60 public class PropertyMap implements Iterable<Object>, Serializable { 61 private static final int INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT = 62 Math.max(0, Options.getIntProperty("nashorn.propertyMap.softReferenceDerivationLimit", 32)); 63 64 /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */ 65 private static final int NOT_EXTENSIBLE = 0b0000_0001; 78 /** Number of fields available. */ 79 private final int fieldMaximum; 80 81 /** Length of spill in use. */ 82 private final int spillLength; 83 84 /** Structure class name */ 85 private final String className; 86 87 /** 88 * Countdown of number of times this property map has been derived from another property map. When it 89 * reaches zero, the property map will start using weak references instead of soft references to hold on 90 * to its history elements. 91 */ 92 private final int softReferenceDerivationLimit; 93 94 /** A reference to the expected shared prototype property map. If this is set this 95 * property map should only be used if it the same as the actual prototype map. */ 96 private transient SharedPropertyMap sharedProtoMap; 97 98 /** {@link SwitchPoint}s for gets on inherited properties. */ 99 private transient HashMap<Object, SwitchPoint> protoSwitches; 100 101 /** History of maps, used to limit map duplication. */ 102 private transient WeakHashMap<Property, Reference<PropertyMap>> history; 103 104 /** History of prototypes, used to limit map duplication. */ 105 private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory; 106 107 /** property listeners */ 108 private transient PropertyListeners listeners; 109 110 private transient BitSet freeSlots; 111 112 private static final long serialVersionUID = -7041836752008732533L; 113 114 /** 115 * Constructs a new property map. 116 * 117 * @param properties A {@link PropertyHashMap} with initial contents. 118 * @param fieldCount Number of fields in use. 119 * @param fieldMaximum Number of fields available. 120 * @param spillLength Number of spill slots used. 121 */ 122 private PropertyMap(final PropertyHashMap properties, final int flags, final String className, 123 final int fieldCount, final int fieldMaximum, final int spillLength) { 124 this.properties = properties; 125 this.className = className; 126 this.fieldCount = fieldCount; 127 this.fieldMaximum = fieldMaximum; 128 this.spillLength = spillLength; 130 this.softReferenceDerivationLimit = INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT; 131 132 if (Context.DEBUG) { 133 count.increment(); 134 } 135 } 136 137 /** 138 * Constructs a clone of {@code propertyMap} with changed properties, flags, or boundaries. 139 * 140 * @param propertyMap Existing property map. 141 * @param properties A {@link PropertyHashMap} with a new set of properties. 142 */ 143 private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength, final int softReferenceDerivationLimit) { 144 this.properties = properties; 145 this.flags = flags; 146 this.spillLength = spillLength; 147 this.fieldCount = fieldCount; 148 this.fieldMaximum = propertyMap.fieldMaximum; 149 this.className = propertyMap.className; 150 // We inherit the parent property listeners instance. It will be cloned when a new listener is added. 151 this.listeners = propertyMap.listeners; 152 this.freeSlots = propertyMap.freeSlots; 153 this.sharedProtoMap = propertyMap.sharedProtoMap; 154 this.softReferenceDerivationLimit = softReferenceDerivationLimit; 155 156 if (Context.DEBUG) { 157 count.increment(); 158 clonedCount.increment(); 159 } 160 } 161 162 /** 163 * Constructs an exact clone of {@code propertyMap}. 164 * 165 * @param propertyMap Existing property map. 166 */ 167 protected PropertyMap(final PropertyMap propertyMap) { 168 this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength, propertyMap.softReferenceDerivationLimit); 169 } 170 171 private void writeObject(final ObjectOutputStream out) throws IOException { 228 229 /** 230 * Return a sharable empty map. 231 * 232 * @return New empty {@link PropertyMap}. 233 */ 234 public static PropertyMap newMap() { 235 return newMap(JO.class); 236 } 237 238 /** 239 * Return number of properties in the map. 240 * 241 * @return Number of properties. 242 */ 243 public int size() { 244 return properties.size(); 245 } 246 247 /** 248 * Get the number of listeners of this map 249 * 250 * @return the number of listeners 251 */ 252 public int getListenerCount() { 253 return listeners == null ? 0 : listeners.getListenerCount(); 254 } 255 256 /** 257 * Add {@code listenerMap} as a listener to this property map for the given {@code key}. 258 * 259 * @param key the property name 260 * @param listenerMap the listener map 261 */ 262 public void addListener(final String key, final PropertyMap listenerMap) { 263 if (listenerMap != this) { 264 // We need to clone listener instance when adding a new listener since we share 265 // the listeners instance with our parent maps that don't need to see the new listener. 266 listeners = PropertyListeners.addListener(listeners, key, listenerMap); 267 } 268 } 269 270 /** 271 * A new property is being added. 272 * 273 * @param property The new Property added. 274 * @param isSelf was the property added to this map? 275 */ 276 public void propertyAdded(final Property property, final boolean isSelf) { 277 if (!isSelf) { 278 invalidateProtoSwitchPoint(property.getKey()); 279 } 280 if (listeners != null) { 281 listeners.propertyAdded(property); 282 } 283 } 284 285 /** 286 * An existing property is being deleted. 287 * 288 * @param property The property being deleted. 289 * @param isSelf was the property deleted from this map? 290 */ 291 public void propertyDeleted(final Property property, final boolean isSelf) { 292 if (!isSelf) { 293 invalidateProtoSwitchPoint(property.getKey()); 294 } 295 if (listeners != null) { 296 listeners.propertyDeleted(property); 297 } 298 } 299 300 /** 301 * An existing property is being redefined. 302 * 303 * @param oldProperty The old property 304 * @param newProperty The new property 305 * @param isSelf was the property modified on this map? 306 */ 307 public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) { 308 if (!isSelf) { 309 invalidateProtoSwitchPoint(oldProperty.getKey()); 310 } 311 if (listeners != null) { 312 listeners.propertyModified(oldProperty, newProperty); 313 } 314 } 315 316 /** 317 * The prototype of an object associated with this {@link PropertyMap} is changed. 318 * 319 * @param isSelf was the prototype changed on the object using this map? 320 */ 321 public void protoChanged(final boolean isSelf) { 322 if (!isSelf) { 323 invalidateAllProtoSwitchPoints(); 324 } else if (sharedProtoMap != null) { 325 sharedProtoMap.invalidateSwitchPoint(); 326 } 327 if (listeners != null) { 328 listeners.protoChanged(); 329 } 330 } 331 332 /** 333 * Return a SwitchPoint used to track changes of a property in a prototype. 334 * 335 * @param key Property key. 336 * @return A shared {@link SwitchPoint} for the property. 337 */ 338 public synchronized SwitchPoint getSwitchPoint(final String key) { 339 if (protoSwitches == null) { 340 protoSwitches = new HashMap<>(); 341 } 342 343 SwitchPoint switchPoint = protoSwitches.get(key); 344 if (switchPoint == null) { 345 switchPoint = new SwitchPoint(); 346 protoSwitches.put(key, switchPoint); 347 } 348 349 return switchPoint; 350 } 351 352 /** 353 * Indicate that a prototype property has changed. 354 * 355 * @param key {@link Property} key to invalidate. 356 */ 357 synchronized void invalidateProtoSwitchPoint(final Object key) { 358 if (protoSwitches != null) { 359 final SwitchPoint sp = protoSwitches.get(key); 360 if (sp != null) { 361 protoSwitches.remove(key); 362 if (Context.DEBUG) { 363 protoInvalidations.increment(); 364 } 365 SwitchPoint.invalidateAll(new SwitchPoint[]{sp}); 366 } 367 } 368 } 369 370 /** 371 * Indicate that proto itself has changed in hierarchy somewhere. 372 */ 373 synchronized void invalidateAllProtoSwitchPoints() { 374 if (protoSwitches != null) { 375 final int size = protoSwitches.size(); 376 if (size > 0) { 377 if (Context.DEBUG) { 378 protoInvalidations.add(size); 379 } 380 SwitchPoint.invalidateAll(protoSwitches.values().toArray(new SwitchPoint[0])); 381 protoSwitches.clear(); 382 } 383 } 384 } 385 386 /** 387 * Add a property to the map, re-binding its getters and setters, 388 * if available, to a given receiver. This is typically the global scope. See 389 * {@link ScriptObject#addBoundProperties(ScriptObject)} 390 * 391 * @param property {@link Property} being added. 392 * @param bindTo Object to bind to. 393 * 394 * @return New {@link PropertyMap} with {@link Property} added. 395 */ 396 PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) { 397 // We must not store bound property in the history as bound properties can't be reused. 398 return addPropertyNoHistory(new AccessorProperty(property, bindTo)); 399 } 400 401 // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum. 402 private int logicalSlotIndex(final Property property) { 403 final int slot = property.getSlot(); 435 } 436 } 437 if (freeSlots != null && newProperty != null) { 438 final int slotIndex = logicalSlotIndex(newProperty); 439 if (slotIndex > -1 && freeSlots.get(slotIndex)) { 440 final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone()); 441 newFreeSlots.clear(slotIndex); 442 freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots; 443 } 444 } 445 } 446 447 /** 448 * Add a property to the map without adding it to the history. This should be used for properties that 449 * can't be shared such as bound properties, or properties that are expected to be added only once. 450 * 451 * @param property {@link Property} being added. 452 * @return New {@link PropertyMap} with {@link Property} added. 453 */ 454 public final PropertyMap addPropertyNoHistory(final Property property) { 455 propertyAdded(property, true); 456 return addPropertyInternal(property); 457 } 458 459 /** 460 * Add a property to the map. Cloning or using an existing map if available. 461 * 462 * @param property {@link Property} being added. 463 * 464 * @return New {@link PropertyMap} with {@link Property} added. 465 */ 466 public final synchronized PropertyMap addProperty(final Property property) { 467 propertyAdded(property, true); 468 PropertyMap newMap = checkHistory(property); 469 470 if (newMap == null) { 471 newMap = addPropertyInternal(property); 472 addToHistory(property, newMap); 473 } 474 475 return newMap; 476 } 477 478 private PropertyMap deriveMap(final PropertyHashMap newProperties, final int newFlags, final int newFieldCount, final int newSpillLength) { 479 return new PropertyMap(this, newProperties, newFlags, newFieldCount, newSpillLength, softReferenceDerivationLimit == 0 ? 0 : softReferenceDerivationLimit - 1); 480 } 481 482 private PropertyMap addPropertyInternal(final Property property) { 483 final PropertyHashMap newProperties = properties.immutableAdd(property); 484 final PropertyMap newMap = deriveMap(newProperties, newFlags(property), newFieldCount(property), newSpillLength(property)); 485 newMap.updateFreeSlots(null, property); 486 return newMap; 487 } 488 489 /** 490 * Remove a property from a map. Cloning or using an existing map if available. 491 * 492 * @param property {@link Property} being removed. 493 * 494 * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found. 495 */ 496 public final synchronized PropertyMap deleteProperty(final Property property) { 497 propertyDeleted(property, true); 498 PropertyMap newMap = checkHistory(property); 499 final Object key = property.getKey(); 500 501 if (newMap == null && properties.containsKey(key)) { 502 final PropertyHashMap newProperties = properties.immutableRemove(key); 503 final boolean isSpill = property.isSpill(); 504 final int slot = property.getSlot(); 505 // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count. 506 // Otherwise mark it as free in free slots bitset. 507 if (isSpill && slot >= 0 && slot == spillLength - 1) { 508 newMap = deriveMap(newProperties, flags, fieldCount, spillLength - 1); 509 newMap.freeSlots = freeSlots; 510 } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) { 511 newMap = deriveMap(newProperties, flags, fieldCount - 1, spillLength); 512 newMap.freeSlots = freeSlots; 513 } else { 514 newMap = deriveMap(newProperties, flags, fieldCount, spillLength); 515 newMap.updateFreeSlots(property, null); 516 } 517 addToHistory(property, newMap); 518 } 519 520 return newMap; 521 } 522 523 /** 524 * Replace an existing property with a new one. 525 * 526 * @param oldProperty Property to replace. 527 * @param newProperty New {@link Property}. 528 * 529 * @return New {@link PropertyMap} with {@link Property} replaced. 530 */ 531 public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) { 532 propertyModified(oldProperty, newProperty, true); 533 /* 534 * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods. 535 * 536 * This replaceProperty method is called only for the following three cases: 537 * 538 * 1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots. 539 * 2. To change one UserAccessor property with another - user getter or setter changed via 540 * Object.defineProperty function. Again, same spill slots are re-used. 541 * 3. Via ScriptObject.setUserAccessors method to set user getter and setter functions 542 * replacing the dummy AccessorProperty with null method handles (added during map init). 543 * 544 * In case (1) and case(2), the property type of old and new property is same. For case (3), 545 * the old property is an AccessorProperty and the new one is a UserAccessorProperty property. 546 */ 547 548 final boolean sameType = oldProperty.getClass() == newProperty.getClass(); 549 assert sameType || 550 oldProperty instanceof AccessorProperty && 551 newProperty instanceof UserAccessorProperty : 552 "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]"; | 1 /* 2 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.runtime; 27 28 import static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP; 29 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; 30 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; 31 32 import java.io.IOException; 33 import java.io.ObjectInputStream; 34 import java.io.ObjectOutputStream; 35 import java.io.Serializable; 36 import java.lang.invoke.SwitchPoint; 37 import java.lang.ref.Reference; 38 import java.lang.ref.SoftReference; 39 import java.lang.ref.WeakReference; 40 import java.util.Arrays; 41 import java.util.BitSet; 42 import java.util.Collection; 43 import java.util.Iterator; 44 import java.util.NoSuchElementException; 45 import java.util.Set; 46 import java.util.WeakHashMap; 47 import java.util.concurrent.atomic.LongAdder; 48 import jdk.nashorn.internal.runtime.options.Options; 49 import jdk.nashorn.internal.scripts.JO; 50 51 /** 52 * Map of object properties. The PropertyMap is the "template" for JavaScript object 53 * layouts. It contains a map with prototype names as keys and {@link Property} instances 54 * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor 55 * to form the seed map for the ScriptObject. 56 * <p> 57 * All property maps are immutable. If a property is added, modified or removed, the mutator 58 * will return a new map. 59 */ 60 public class PropertyMap implements Iterable<Object>, Serializable { 61 private static final int INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT = 62 Math.max(0, Options.getIntProperty("nashorn.propertyMap.softReferenceDerivationLimit", 32)); 63 64 /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */ 65 private static final int NOT_EXTENSIBLE = 0b0000_0001; 78 /** Number of fields available. */ 79 private final int fieldMaximum; 80 81 /** Length of spill in use. */ 82 private final int spillLength; 83 84 /** Structure class name */ 85 private final String className; 86 87 /** 88 * Countdown of number of times this property map has been derived from another property map. When it 89 * reaches zero, the property map will start using weak references instead of soft references to hold on 90 * to its history elements. 91 */ 92 private final int softReferenceDerivationLimit; 93 94 /** A reference to the expected shared prototype property map. If this is set this 95 * property map should only be used if it the same as the actual prototype map. */ 96 private transient SharedPropertyMap sharedProtoMap; 97 98 /** History of maps, used to limit map duplication. */ 99 private transient WeakHashMap<Property, Reference<PropertyMap>> history; 100 101 /** History of prototypes, used to limit map duplication. */ 102 private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory; 103 104 /** SwitchPoints for properties inherited form this map */ 105 private transient PropertySwitchPoints propertySwitchPoints; 106 107 private transient BitSet freeSlots; 108 109 private static final long serialVersionUID = -7041836752008732533L; 110 111 /** 112 * Constructs a new property map. 113 * 114 * @param properties A {@link PropertyHashMap} with initial contents. 115 * @param fieldCount Number of fields in use. 116 * @param fieldMaximum Number of fields available. 117 * @param spillLength Number of spill slots used. 118 */ 119 private PropertyMap(final PropertyHashMap properties, final int flags, final String className, 120 final int fieldCount, final int fieldMaximum, final int spillLength) { 121 this.properties = properties; 122 this.className = className; 123 this.fieldCount = fieldCount; 124 this.fieldMaximum = fieldMaximum; 125 this.spillLength = spillLength; 127 this.softReferenceDerivationLimit = INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT; 128 129 if (Context.DEBUG) { 130 count.increment(); 131 } 132 } 133 134 /** 135 * Constructs a clone of {@code propertyMap} with changed properties, flags, or boundaries. 136 * 137 * @param propertyMap Existing property map. 138 * @param properties A {@link PropertyHashMap} with a new set of properties. 139 */ 140 private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength, final int softReferenceDerivationLimit) { 141 this.properties = properties; 142 this.flags = flags; 143 this.spillLength = spillLength; 144 this.fieldCount = fieldCount; 145 this.fieldMaximum = propertyMap.fieldMaximum; 146 this.className = propertyMap.className; 147 // We inherit the parent property propertySwitchPoints instance. It will be cloned when a new listener is added. 148 this.propertySwitchPoints = propertyMap.propertySwitchPoints; 149 this.freeSlots = propertyMap.freeSlots; 150 this.sharedProtoMap = propertyMap.sharedProtoMap; 151 this.softReferenceDerivationLimit = softReferenceDerivationLimit; 152 153 if (Context.DEBUG) { 154 count.increment(); 155 clonedCount.increment(); 156 } 157 } 158 159 /** 160 * Constructs an exact clone of {@code propertyMap}. 161 * 162 * @param propertyMap Existing property map. 163 */ 164 protected PropertyMap(final PropertyMap propertyMap) { 165 this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength, propertyMap.softReferenceDerivationLimit); 166 } 167 168 private void writeObject(final ObjectOutputStream out) throws IOException { 225 226 /** 227 * Return a sharable empty map. 228 * 229 * @return New empty {@link PropertyMap}. 230 */ 231 public static PropertyMap newMap() { 232 return newMap(JO.class); 233 } 234 235 /** 236 * Return number of properties in the map. 237 * 238 * @return Number of properties. 239 */ 240 public int size() { 241 return properties.size(); 242 } 243 244 /** 245 * Get the number of property SwitchPoints of this map 246 * 247 * @return the number of property SwitchPoints 248 */ 249 public int getSwitchPointCount() { 250 return propertySwitchPoints == null ? 0 : propertySwitchPoints.getSwitchPointCount(); 251 } 252 253 /** 254 * Add a property switchpoint to this property map for the given {@code key}. 255 * 256 * @param key the property name 257 * @param switchPoint the switchpoint 258 */ 259 public void addSwitchPoint(final String key, final SwitchPoint switchPoint) { 260 // We need to clone listener instance when adding a new listener since we share 261 // the propertySwitchPoints instance with our parent maps that don't need to see the new listener. 262 propertySwitchPoints = PropertySwitchPoints.addSwitchPoint(propertySwitchPoints, key, switchPoint); 263 } 264 265 /** 266 * Method called when a property of an object using this property map is being created, 267 * modified, or deleted. If a switchpoint for the property exists it will be invalidated. 268 * 269 * @param property The changed property. 270 */ 271 public void propertyChanged(final Property property) { 272 if (propertySwitchPoints != null) { 273 propertySwitchPoints.invalidateProperty(property); 274 } 275 } 276 277 /** 278 * Method called when the prototype of an object using this property map is changed. 279 */ 280 void protoChanged() { 281 if (sharedProtoMap != null) { 282 sharedProtoMap.invalidateSwitchPoint(); 283 } 284 if (propertySwitchPoints != null) { 285 propertySwitchPoints.invalidateInheritedProperties(this); 286 } 287 } 288 289 /** 290 * Returns a SwitchPoint for use with a property inherited from this or a parent map. 291 * If such a switchpoint exists, it will be invalidated when the property is modified 292 * in an object using this map. This method returns {@code null} if no swichpoint exists 293 * for the property. 294 * 295 * @param key Property key. 296 * @return A {@link SwitchPoint} for the property, or null. 297 */ 298 public synchronized SwitchPoint getSwitchPoint(final String key) { 299 if (propertySwitchPoints != null) { 300 final Set<SwitchPoint> existingSwitchPoints = propertySwitchPoints.getSwitchPoints(key); 301 for (final SwitchPoint switchPoint : existingSwitchPoints) { 302 if (switchPoint != null && !switchPoint.hasBeenInvalidated()) { 303 return switchPoint; 304 } 305 } 306 } 307 308 return null; 309 } 310 311 /** 312 * Add a property to the map, re-binding its getters and setters, 313 * if available, to a given receiver. This is typically the global scope. See 314 * {@link ScriptObject#addBoundProperties(ScriptObject)} 315 * 316 * @param property {@link Property} being added. 317 * @param bindTo Object to bind to. 318 * 319 * @return New {@link PropertyMap} with {@link Property} added. 320 */ 321 PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) { 322 // We must not store bound property in the history as bound properties can't be reused. 323 return addPropertyNoHistory(new AccessorProperty(property, bindTo)); 324 } 325 326 // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum. 327 private int logicalSlotIndex(final Property property) { 328 final int slot = property.getSlot(); 360 } 361 } 362 if (freeSlots != null && newProperty != null) { 363 final int slotIndex = logicalSlotIndex(newProperty); 364 if (slotIndex > -1 && freeSlots.get(slotIndex)) { 365 final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone()); 366 newFreeSlots.clear(slotIndex); 367 freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots; 368 } 369 } 370 } 371 372 /** 373 * Add a property to the map without adding it to the history. This should be used for properties that 374 * can't be shared such as bound properties, or properties that are expected to be added only once. 375 * 376 * @param property {@link Property} being added. 377 * @return New {@link PropertyMap} with {@link Property} added. 378 */ 379 public final PropertyMap addPropertyNoHistory(final Property property) { 380 propertyChanged(property); 381 return addPropertyInternal(property); 382 } 383 384 /** 385 * Add a property to the map. Cloning or using an existing map if available. 386 * 387 * @param property {@link Property} being added. 388 * 389 * @return New {@link PropertyMap} with {@link Property} added. 390 */ 391 public final synchronized PropertyMap addProperty(final Property property) { 392 propertyChanged(property); 393 PropertyMap newMap = checkHistory(property); 394 395 if (newMap == null) { 396 newMap = addPropertyInternal(property); 397 addToHistory(property, newMap); 398 } 399 400 return newMap; 401 } 402 403 private PropertyMap deriveMap(final PropertyHashMap newProperties, final int newFlags, final int newFieldCount, final int newSpillLength) { 404 return new PropertyMap(this, newProperties, newFlags, newFieldCount, newSpillLength, softReferenceDerivationLimit == 0 ? 0 : softReferenceDerivationLimit - 1); 405 } 406 407 private PropertyMap addPropertyInternal(final Property property) { 408 final PropertyHashMap newProperties = properties.immutableAdd(property); 409 final PropertyMap newMap = deriveMap(newProperties, newFlags(property), newFieldCount(property), newSpillLength(property)); 410 newMap.updateFreeSlots(null, property); 411 return newMap; 412 } 413 414 /** 415 * Remove a property from a map. Cloning or using an existing map if available. 416 * 417 * @param property {@link Property} being removed. 418 * 419 * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found. 420 */ 421 public final synchronized PropertyMap deleteProperty(final Property property) { 422 propertyChanged(property); 423 PropertyMap newMap = checkHistory(property); 424 final Object key = property.getKey(); 425 426 if (newMap == null && properties.containsKey(key)) { 427 final PropertyHashMap newProperties = properties.immutableRemove(key); 428 final boolean isSpill = property.isSpill(); 429 final int slot = property.getSlot(); 430 // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count. 431 // Otherwise mark it as free in free slots bitset. 432 if (isSpill && slot >= 0 && slot == spillLength - 1) { 433 newMap = deriveMap(newProperties, flags, fieldCount, spillLength - 1); 434 newMap.freeSlots = freeSlots; 435 } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) { 436 newMap = deriveMap(newProperties, flags, fieldCount - 1, spillLength); 437 newMap.freeSlots = freeSlots; 438 } else { 439 newMap = deriveMap(newProperties, flags, fieldCount, spillLength); 440 newMap.updateFreeSlots(property, null); 441 } 442 addToHistory(property, newMap); 443 } 444 445 return newMap; 446 } 447 448 /** 449 * Replace an existing property with a new one. 450 * 451 * @param oldProperty Property to replace. 452 * @param newProperty New {@link Property}. 453 * 454 * @return New {@link PropertyMap} with {@link Property} replaced. 455 */ 456 public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) { 457 propertyChanged(oldProperty); 458 /* 459 * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods. 460 * 461 * This replaceProperty method is called only for the following three cases: 462 * 463 * 1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots. 464 * 2. To change one UserAccessor property with another - user getter or setter changed via 465 * Object.defineProperty function. Again, same spill slots are re-used. 466 * 3. Via ScriptObject.setUserAccessors method to set user getter and setter functions 467 * replacing the dummy AccessorProperty with null method handles (added during map init). 468 * 469 * In case (1) and case(2), the property type of old and new property is same. For case (3), 470 * the old property is an AccessorProperty and the new one is a UserAccessorProperty property. 471 */ 472 473 final boolean sameType = oldProperty.getClass() == newProperty.getClass(); 474 assert sameType || 475 oldProperty instanceof AccessorProperty && 476 newProperty instanceof UserAccessorProperty : 477 "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]"; |