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() + "]";
|
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.Collections;
44 import java.util.Iterator;
45 import java.util.NoSuchElementException;
46 import java.util.Set;
47 import java.util.WeakHashMap;
48 import java.util.concurrent.atomic.LongAdder;
49 import jdk.nashorn.internal.runtime.options.Options;
50 import jdk.nashorn.internal.scripts.JO;
51
52 /**
53 * Map of object properties. The PropertyMap is the "template" for JavaScript object
54 * layouts. It contains a map with prototype names as keys and {@link Property} instances
55 * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor
56 * to form the seed map for the ScriptObject.
57 * <p>
58 * All property maps are immutable. If a property is added, modified or removed, the mutator
59 * will return a new map.
60 */
61 public class PropertyMap implements Iterable<Object>, Serializable {
62 private static final int INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT =
63 Math.max(0, Options.getIntProperty("nashorn.propertyMap.softReferenceDerivationLimit", 32));
64
65 /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */
66 private static final int NOT_EXTENSIBLE = 0b0000_0001;
79 /** Number of fields available. */
80 private final int fieldMaximum;
81
82 /** Length of spill in use. */
83 private final int spillLength;
84
85 /** Structure class name */
86 private final String className;
87
88 /**
89 * Countdown of number of times this property map has been derived from another property map. When it
90 * reaches zero, the property map will start using weak references instead of soft references to hold on
91 * to its history elements.
92 */
93 private final int softReferenceDerivationLimit;
94
95 /** A reference to the expected shared prototype property map. If this is set this
96 * property map should only be used if it the same as the actual prototype map. */
97 private transient SharedPropertyMap sharedProtoMap;
98
99 /** History of maps, used to limit map duplication. */
100 private transient WeakHashMap<Property, Reference<PropertyMap>> history;
101
102 /** History of prototypes, used to limit map duplication. */
103 private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory;
104
105 /** SwitchPoints for properties inherited form this map */
106 private transient PropertySwitchPoints propertySwitchPoints;
107
108 private transient BitSet freeSlots;
109
110 private static final long serialVersionUID = -7041836752008732533L;
111
112 /**
113 * Constructs a new property map.
114 *
115 * @param properties A {@link PropertyHashMap} with initial contents.
116 * @param fieldCount Number of fields in use.
117 * @param fieldMaximum Number of fields available.
118 * @param spillLength Number of spill slots used.
119 */
120 private PropertyMap(final PropertyHashMap properties, final int flags, final String className,
121 final int fieldCount, final int fieldMaximum, final int spillLength) {
122 this.properties = properties;
123 this.className = className;
124 this.fieldCount = fieldCount;
125 this.fieldMaximum = fieldMaximum;
126 this.spillLength = spillLength;
128 this.softReferenceDerivationLimit = INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT;
129
130 if (Context.DEBUG) {
131 count.increment();
132 }
133 }
134
135 /**
136 * Constructs a clone of {@code propertyMap} with changed properties, flags, or boundaries.
137 *
138 * @param propertyMap Existing property map.
139 * @param properties A {@link PropertyHashMap} with a new set of properties.
140 */
141 private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength, final int softReferenceDerivationLimit) {
142 this.properties = properties;
143 this.flags = flags;
144 this.spillLength = spillLength;
145 this.fieldCount = fieldCount;
146 this.fieldMaximum = propertyMap.fieldMaximum;
147 this.className = propertyMap.className;
148 // We inherit the parent property propertySwitchPoints instance. It will be cloned when a new listener is added.
149 this.propertySwitchPoints = propertyMap.propertySwitchPoints;
150 this.freeSlots = propertyMap.freeSlots;
151 this.sharedProtoMap = propertyMap.sharedProtoMap;
152 this.softReferenceDerivationLimit = softReferenceDerivationLimit;
153
154 if (Context.DEBUG) {
155 count.increment();
156 clonedCount.increment();
157 }
158 }
159
160 /**
161 * Constructs an exact clone of {@code propertyMap}.
162 *
163 * @param propertyMap Existing property map.
164 */
165 protected PropertyMap(final PropertyMap propertyMap) {
166 this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength, propertyMap.softReferenceDerivationLimit);
167 }
168
169 private void writeObject(final ObjectOutputStream out) throws IOException {
226
227 /**
228 * Return a sharable empty map.
229 *
230 * @return New empty {@link PropertyMap}.
231 */
232 public static PropertyMap newMap() {
233 return newMap(JO.class);
234 }
235
236 /**
237 * Return number of properties in the map.
238 *
239 * @return Number of properties.
240 */
241 public int size() {
242 return properties.size();
243 }
244
245 /**
246 * Get the number of property SwitchPoints of this map
247 *
248 * @return the number of property SwitchPoints
249 */
250 public int getSwitchPointCount() {
251 return propertySwitchPoints == null ? 0 : propertySwitchPoints.getSwitchPointCount();
252 }
253
254 /**
255 * Add a property switchpoint to this property map for the given {@code key}.
256 *
257 * @param key the property name
258 * @param switchPoint the switchpoint
259 */
260 public void addSwitchPoint(final String key, final SwitchPoint switchPoint) {
261 // We need to clone listener instance when adding a new listener since we share
262 // the propertySwitchPoints instance with our parent maps that don't need to see the new listener.
263 propertySwitchPoints = PropertySwitchPoints.addSwitchPoint(propertySwitchPoints, key, switchPoint);
264 }
265
266 /**
267 * A property is about to change - invalidate all prototype switchpoints for the given property.
268 *
269 * @param property The modified property.
270 */
271 public void invalidateProperty(final Property property) {
272 if (propertySwitchPoints != null) {
273 propertySwitchPoints.invalidateProperty(property);
274 }
275 }
276
277 /**
278 * The prototype of an object associated with this {@link PropertyMap} is changed.
279 */
280 void protoChanged() {
281 if (sharedProtoMap != null) {
282 sharedProtoMap.invalidateSwitchPoint();
283 }
284 if (propertySwitchPoints != null) {
285 propertySwitchPoints.invalidateAll();
286 }
287 }
288
289 /**
290 * Return a SwitchPoint for tracking a property in this map, if one exists.
291 *
292 * @param key Property key.
293 * @return A {@link SwitchPoint} for the property, or null.
294 */
295 public synchronized SwitchPoint getSwitchPoint(final String key) {
296 if (propertySwitchPoints != null) {
297 final Set<SwitchPoint> existingSwitchPoints = propertySwitchPoints.getSwitchPoints(key);
298 for (final SwitchPoint switchPoint : existingSwitchPoints) {
299 if (switchPoint != null && !switchPoint.hasBeenInvalidated()) {
300 return switchPoint;
301 }
302 }
303 }
304
305 return null;
306 }
307
308 /**
309 * Add a property to the map, re-binding its getters and setters,
310 * if available, to a given receiver. This is typically the global scope. See
311 * {@link ScriptObject#addBoundProperties(ScriptObject)}
312 *
313 * @param property {@link Property} being added.
314 * @param bindTo Object to bind to.
315 *
316 * @return New {@link PropertyMap} with {@link Property} added.
317 */
318 PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) {
319 // We must not store bound property in the history as bound properties can't be reused.
320 return addPropertyNoHistory(new AccessorProperty(property, bindTo));
321 }
322
323 // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum.
324 private int logicalSlotIndex(final Property property) {
325 final int slot = property.getSlot();
357 }
358 }
359 if (freeSlots != null && newProperty != null) {
360 final int slotIndex = logicalSlotIndex(newProperty);
361 if (slotIndex > -1 && freeSlots.get(slotIndex)) {
362 final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone());
363 newFreeSlots.clear(slotIndex);
364 freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots;
365 }
366 }
367 }
368
369 /**
370 * Add a property to the map without adding it to the history. This should be used for properties that
371 * can't be shared such as bound properties, or properties that are expected to be added only once.
372 *
373 * @param property {@link Property} being added.
374 * @return New {@link PropertyMap} with {@link Property} added.
375 */
376 public final PropertyMap addPropertyNoHistory(final Property property) {
377 invalidateProperty(property);
378 return addPropertyInternal(property);
379 }
380
381 /**
382 * Add a property to the map. Cloning or using an existing map if available.
383 *
384 * @param property {@link Property} being added.
385 *
386 * @return New {@link PropertyMap} with {@link Property} added.
387 */
388 public final synchronized PropertyMap addProperty(final Property property) {
389 invalidateProperty(property);
390 PropertyMap newMap = checkHistory(property);
391
392 if (newMap == null) {
393 newMap = addPropertyInternal(property);
394 addToHistory(property, newMap);
395 }
396
397 return newMap;
398 }
399
400 private PropertyMap deriveMap(final PropertyHashMap newProperties, final int newFlags, final int newFieldCount, final int newSpillLength) {
401 return new PropertyMap(this, newProperties, newFlags, newFieldCount, newSpillLength, softReferenceDerivationLimit == 0 ? 0 : softReferenceDerivationLimit - 1);
402 }
403
404 private PropertyMap addPropertyInternal(final Property property) {
405 final PropertyHashMap newProperties = properties.immutableAdd(property);
406 final PropertyMap newMap = deriveMap(newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
407 newMap.updateFreeSlots(null, property);
408 return newMap;
409 }
410
411 /**
412 * Remove a property from a map. Cloning or using an existing map if available.
413 *
414 * @param property {@link Property} being removed.
415 *
416 * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found.
417 */
418 public final synchronized PropertyMap deleteProperty(final Property property) {
419 invalidateProperty(property);
420 PropertyMap newMap = checkHistory(property);
421 final Object key = property.getKey();
422
423 if (newMap == null && properties.containsKey(key)) {
424 final PropertyHashMap newProperties = properties.immutableRemove(key);
425 final boolean isSpill = property.isSpill();
426 final int slot = property.getSlot();
427 // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count.
428 // Otherwise mark it as free in free slots bitset.
429 if (isSpill && slot >= 0 && slot == spillLength - 1) {
430 newMap = deriveMap(newProperties, flags, fieldCount, spillLength - 1);
431 newMap.freeSlots = freeSlots;
432 } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) {
433 newMap = deriveMap(newProperties, flags, fieldCount - 1, spillLength);
434 newMap.freeSlots = freeSlots;
435 } else {
436 newMap = deriveMap(newProperties, flags, fieldCount, spillLength);
437 newMap.updateFreeSlots(property, null);
438 }
439 addToHistory(property, newMap);
440 }
441
442 return newMap;
443 }
444
445 /**
446 * Replace an existing property with a new one.
447 *
448 * @param oldProperty Property to replace.
449 * @param newProperty New {@link Property}.
450 *
451 * @return New {@link PropertyMap} with {@link Property} replaced.
452 */
453 public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
454 invalidateProperty(oldProperty);
455 /*
456 * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods.
457 *
458 * This replaceProperty method is called only for the following three cases:
459 *
460 * 1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots.
461 * 2. To change one UserAccessor property with another - user getter or setter changed via
462 * Object.defineProperty function. Again, same spill slots are re-used.
463 * 3. Via ScriptObject.setUserAccessors method to set user getter and setter functions
464 * replacing the dummy AccessorProperty with null method handles (added during map init).
465 *
466 * In case (1) and case(2), the property type of old and new property is same. For case (3),
467 * the old property is an AccessorProperty and the new one is a UserAccessorProperty property.
468 */
469
470 final boolean sameType = oldProperty.getClass() == newProperty.getClass();
471 assert sameType ||
472 oldProperty instanceof AccessorProperty &&
473 newProperty instanceof UserAccessorProperty :
474 "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]";
|