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; 66 /** Does this map contain valid array keys? */ 67 private static final int CONTAINS_ARRAY_KEYS = 0b0000_0010; 68 69 /** Map status flags. */ 70 private final int flags; 71 72 /** Map of properties. */ 73 private transient PropertyHashMap properties; 74 75 /** Number of fields in use. */ 76 private final int fieldCount; 77 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 */ PropertyMap(final PropertyHashMap properties, final int flags, final String className, final int fieldCount, final int fieldMaximum, final int spillLength)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; 126 this.flags = flags; 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 */ PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength, final int softReferenceDerivationLimit)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 */ PropertyMap(final PropertyMap propertyMap)164 protected PropertyMap(final PropertyMap propertyMap) { 165 this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength, propertyMap.softReferenceDerivationLimit); 166 } 167 writeObject(final ObjectOutputStream out)168 private void writeObject(final ObjectOutputStream out) throws IOException { 169 out.defaultWriteObject(); 170 out.writeObject(properties.getProperties()); 171 } 172 readObject(final ObjectInputStream in)173 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 174 in.defaultReadObject(); 175 176 final Property[] props = (Property[]) in.readObject(); 177 this.properties = EMPTY_HASHMAP.immutableAdd(props); 178 179 assert className != null; 180 final Class<?> structure = Context.forStructureClass(className); 181 for (final Property prop : props) { 182 prop.initMethodHandles(structure); 183 } 184 } 185 186 /** 187 * Public property map allocator. 188 * 189 * <p>It is the caller's responsibility to make sure that {@code properties} does not contain 190 * properties with keys that are valid array indices.</p> 191 * 192 * @param properties Collection of initial properties. 193 * @param className class name 194 * @param fieldCount Number of fields in use. 195 * @param fieldMaximum Number of fields available. 196 * @param spillLength Number of used spill slots. 197 * @return New {@link PropertyMap}. 198 */ newMap(final Collection<Property> properties, final String className, final int fieldCount, final int fieldMaximum, final int spillLength)199 public static PropertyMap newMap(final Collection<Property> properties, final String className, final int fieldCount, final int fieldMaximum, final int spillLength) { 200 final PropertyHashMap newProperties = EMPTY_HASHMAP.immutableAdd(properties); 201 return new PropertyMap(newProperties, 0, className, fieldCount, fieldMaximum, spillLength); 202 } 203 204 /** 205 * Public property map allocator. Used by nasgen generated code. 206 * 207 * <p>It is the caller's responsibility to make sure that {@code properties} does not contain 208 * properties with keys that are valid array indices.</p> 209 * 210 * @param properties Collection of initial properties. 211 * @return New {@link PropertyMap}. 212 */ newMap(final Collection<Property> properties)213 public static PropertyMap newMap(final Collection<Property> properties) { 214 return properties == null || properties.isEmpty()? newMap() : newMap(properties, JO.class.getName(), 0, 0, 0); 215 } 216 217 /** 218 * Return a sharable empty map for the given object class. 219 * @param clazz the base object class 220 * @return New empty {@link PropertyMap}. 221 */ newMap(final Class<? extends ScriptObject> clazz)222 public static PropertyMap newMap(final Class<? extends ScriptObject> clazz) { 223 return new PropertyMap(EMPTY_HASHMAP, 0, clazz.getName(), 0, 0, 0); 224 } 225 226 /** 227 * Return a sharable empty map. 228 * 229 * @return New empty {@link PropertyMap}. 230 */ newMap()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 */ size()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 */ getSwitchPointCount()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 */ addSwitchPoint(final String key, final SwitchPoint switchPoint)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 */ propertyChanged(final Property property)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 */ protoChanged()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 */ getSwitchPoint(final String key)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 */ addPropertyBind(final AccessorProperty property, final Object bindTo)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. logicalSlotIndex(final Property property)327 private int logicalSlotIndex(final Property property) { 328 final int slot = property.getSlot(); 329 if (slot < 0) { 330 return -1; 331 } 332 return property.isSpill() ? slot + fieldMaximum : slot; 333 } 334 newSpillLength(final Property newProperty)335 private int newSpillLength(final Property newProperty) { 336 return newProperty.isSpill() ? Math.max(spillLength, newProperty.getSlot() + 1) : spillLength; 337 } 338 newFieldCount(final Property newProperty)339 private int newFieldCount(final Property newProperty) { 340 return !newProperty.isSpill() ? Math.max(fieldCount, newProperty.getSlot() + 1) : fieldCount; 341 } 342 newFlags(final Property newProperty)343 private int newFlags(final Property newProperty) { 344 return isValidArrayIndex(getArrayIndex(newProperty.getKey())) ? flags | CONTAINS_ARRAY_KEYS : flags; 345 } 346 347 // Update the free slots bitmap for a property that has been deleted and/or added. This method is not synchronized 348 // as it is always invoked on a newly created instance. updateFreeSlots(final Property oldProperty, final Property newProperty)349 private void updateFreeSlots(final Property oldProperty, final Property newProperty) { 350 // Free slots bitset is possibly shared with parent map, so we must clone it before making modifications. 351 boolean freeSlotsCloned = false; 352 if (oldProperty != null) { 353 final int slotIndex = logicalSlotIndex(oldProperty); 354 if (slotIndex >= 0) { 355 final BitSet newFreeSlots = freeSlots == null ? new BitSet() : (BitSet)freeSlots.clone(); 356 assert !newFreeSlots.get(slotIndex); 357 newFreeSlots.set(slotIndex); 358 freeSlots = newFreeSlots; 359 freeSlotsCloned = true; 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 */ addPropertyNoHistory(final Property property)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 */ addProperty(final Property property)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 deriveMap(final PropertyHashMap newProperties, final int newFlags, final int newFieldCount, final int newSpillLength)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 addPropertyInternal(final Property property)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 */ deleteProperty(final Property property)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 */ replaceProperty(final Property oldProperty, final Property newProperty)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() + "]"; 478 479 /* 480 * spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need 481 * to add spill count of the newly added UserAccessorProperty property. 482 */ 483 final int newSpillLength = sameType ? spillLength : Math.max(spillLength, newProperty.getSlot() + 1); 484 485 // Add replaces existing property. 486 final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty); 487 final PropertyMap newMap = deriveMap(newProperties, flags, fieldCount, newSpillLength); 488 489 if (!sameType) { 490 newMap.updateFreeSlots(oldProperty, newProperty); 491 } 492 return newMap; 493 } 494 495 /** 496 * Make a new UserAccessorProperty property. getter and setter functions are stored in 497 * this ScriptObject and slot values are used in property object. Note that slots 498 * are assigned speculatively and should be added to map before adding other 499 * properties. 500 * 501 * @param key the property name 502 * @param propertyFlags attribute flags of the property 503 * @return the newly created UserAccessorProperty 504 */ newUserAccessors(final Object key, final int propertyFlags)505 public final UserAccessorProperty newUserAccessors(final Object key, final int propertyFlags) { 506 return new UserAccessorProperty(key, propertyFlags, getFreeSpillSlot()); 507 } 508 509 /** 510 * Find a property in the map. 511 * 512 * @param key Key to search for. 513 * 514 * @return {@link Property} matching key. 515 */ findProperty(final Object key)516 public final Property findProperty(final Object key) { 517 return properties.find(key); 518 } 519 520 /** 521 * Adds all map properties from another map. 522 * 523 * @param other The source of properties. 524 * 525 * @return New {@link PropertyMap} with added properties. 526 */ addAll(final PropertyMap other)527 public final PropertyMap addAll(final PropertyMap other) { 528 assert this != other : "adding property map to itself"; 529 final Property[] otherProperties = other.properties.getProperties(); 530 final PropertyHashMap newProperties = properties.immutableAdd(otherProperties); 531 532 final PropertyMap newMap = deriveMap(newProperties, flags, fieldCount, spillLength); 533 for (final Property property : otherProperties) { 534 // This method is only safe to use with non-slotted, native getter/setter properties 535 assert property.getSlot() == -1; 536 assert !(isValidArrayIndex(getArrayIndex(property.getKey()))); 537 } 538 539 return newMap; 540 } 541 542 /** 543 * Return an array of all properties. 544 * 545 * @return Properties as an array. 546 */ getProperties()547 public final Property[] getProperties() { 548 return properties.getProperties(); 549 } 550 551 /** 552 * Return the name of the class of objects using this property map. 553 * 554 * @return class name of owner objects. 555 */ getClassName()556 public final String getClassName() { 557 return className; 558 } 559 560 /** 561 * Prevents the map from having additional properties. 562 * 563 * @return New map with {@link #NOT_EXTENSIBLE} flag set. 564 */ preventExtensions()565 PropertyMap preventExtensions() { 566 return deriveMap(properties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); 567 } 568 569 /** 570 * Prevents properties in map from being modified. 571 * 572 * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with 573 * {@link Property#NOT_CONFIGURABLE} set. 574 */ seal()575 PropertyMap seal() { 576 PropertyHashMap newProperties = EMPTY_HASHMAP; 577 578 for (final Property oldProperty : properties.getProperties()) { 579 newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE)); 580 } 581 582 return deriveMap(newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); 583 } 584 585 /** 586 * Prevents properties in map from being modified or written to. 587 * 588 * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with 589 * {@link Property#NOT_CONFIGURABLE} and {@link Property#NOT_WRITABLE} set. 590 */ freeze()591 PropertyMap freeze() { 592 PropertyHashMap newProperties = EMPTY_HASHMAP; 593 594 for (final Property oldProperty : properties.getProperties()) { 595 int propertyFlags = Property.NOT_CONFIGURABLE; 596 597 if (!(oldProperty instanceof UserAccessorProperty)) { 598 propertyFlags |= Property.NOT_WRITABLE; 599 } 600 601 newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags)); 602 } 603 604 return deriveMap(newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); 605 } 606 607 /** 608 * Check for any configurable properties. 609 * 610 * @return {@code true} if any configurable. 611 */ anyConfigurable()612 private boolean anyConfigurable() { 613 for (final Property property : properties.getProperties()) { 614 if (property.isConfigurable()) { 615 return true; 616 } 617 } 618 619 return false; 620 } 621 622 /** 623 * Check if all properties are frozen. 624 * 625 * @return {@code true} if all are frozen. 626 */ allFrozen()627 private boolean allFrozen() { 628 for (final Property property : properties.getProperties()) { 629 // check if it is a data descriptor 630 if (!property.isAccessorProperty() && property.isWritable()) { 631 return false; 632 } 633 if (property.isConfigurable()) { 634 return false; 635 } 636 } 637 638 return true; 639 } 640 641 /** 642 * Check prototype history for an existing property map with specified prototype. 643 * 644 * @param proto New prototype object. 645 * 646 * @return Existing {@link PropertyMap} or {@code null} if not found. 647 */ checkProtoHistory(final ScriptObject proto)648 private PropertyMap checkProtoHistory(final ScriptObject proto) { 649 final PropertyMap cachedMap; 650 if (protoHistory != null) { 651 final SoftReference<PropertyMap> weakMap = protoHistory.get(proto); 652 cachedMap = (weakMap != null ? weakMap.get() : null); 653 } else { 654 cachedMap = null; 655 } 656 657 if (Context.DEBUG && cachedMap != null) { 658 protoHistoryHit.increment(); 659 } 660 661 return cachedMap; 662 } 663 664 /** 665 * Add a map to the prototype history. 666 * 667 * @param newProto Prototype to add (key.) 668 * @param newMap {@link PropertyMap} associated with prototype. 669 */ addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap)670 private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { 671 if (protoHistory == null) { 672 protoHistory = new WeakHashMap<>(); 673 } 674 675 protoHistory.put(newProto, new SoftReference<>(newMap)); 676 } 677 678 /** 679 * Track the modification of the map. 680 * 681 * @param property Mapping property. 682 * @param newMap Modified {@link PropertyMap}. 683 */ addToHistory(final Property property, final PropertyMap newMap)684 private void addToHistory(final Property property, final PropertyMap newMap) { 685 if (history == null) { 686 history = new WeakHashMap<>(); 687 } 688 689 history.put(property, softReferenceDerivationLimit == 0 ? new WeakReference<>(newMap) : new SoftReference<>(newMap)); 690 } 691 692 /** 693 * Check the history for a map that already has the given property added. 694 * 695 * @param property {@link Property} to add. 696 * 697 * @return Existing map or {@code null} if not found. 698 */ checkHistory(final Property property)699 private PropertyMap checkHistory(final Property property) { 700 701 if (history != null) { 702 final Reference<PropertyMap> ref = history.get(property); 703 final PropertyMap historicMap = ref == null ? null : ref.get(); 704 705 if (historicMap != null) { 706 if (Context.DEBUG) { 707 historyHit.increment(); 708 } 709 710 return historicMap; 711 } 712 } 713 714 return null; 715 } 716 717 /** 718 * Returns true if the two maps have identical properties in the same order, but allows the properties to differ in 719 * their types. This method is mostly useful for tests. 720 * @param otherMap the other map 721 * @return true if this map has identical properties in the same order as the other map, allowing the properties to 722 * differ in type. 723 */ equalsWithoutType(final PropertyMap otherMap)724 public boolean equalsWithoutType(final PropertyMap otherMap) { 725 if (properties.size() != otherMap.properties.size()) { 726 return false; 727 } 728 729 final Iterator<Property> iter = properties.values().iterator(); 730 final Iterator<Property> otherIter = otherMap.properties.values().iterator(); 731 732 while (iter.hasNext() && otherIter.hasNext()) { 733 if (!iter.next().equalsWithoutType(otherIter.next())) { 734 return false; 735 } 736 } 737 738 return true; 739 } 740 741 @Override toString()742 public String toString() { 743 final StringBuilder sb = new StringBuilder(); 744 745 sb.append(Debug.id(this)); 746 sb.append(" = {\n"); 747 748 for (final Property property : getProperties()) { 749 sb.append('\t'); 750 sb.append(property); 751 sb.append('\n'); 752 } 753 754 sb.append('}'); 755 756 return sb.toString(); 757 } 758 759 @Override iterator()760 public Iterator<Object> iterator() { 761 return new PropertyMapIterator(this); 762 } 763 764 /** 765 * Check if this map contains properties with valid array keys 766 * 767 * @return {@code true} if this map contains properties with valid array keys 768 */ containsArrayKeys()769 public final boolean containsArrayKeys() { 770 return (flags & CONTAINS_ARRAY_KEYS) != 0; 771 } 772 773 /** 774 * Test to see if {@link PropertyMap} is extensible. 775 * 776 * @return {@code true} if {@link PropertyMap} can be added to. 777 */ isExtensible()778 boolean isExtensible() { 779 return (flags & NOT_EXTENSIBLE) == 0; 780 } 781 782 /** 783 * Test to see if {@link PropertyMap} is not extensible or any properties 784 * can not be modified. 785 * 786 * @return {@code true} if {@link PropertyMap} is sealed. 787 */ isSealed()788 boolean isSealed() { 789 return !isExtensible() && !anyConfigurable(); 790 } 791 792 /** 793 * Test to see if {@link PropertyMap} is not extensible or all properties 794 * can not be modified. 795 * 796 * @return {@code true} if {@link PropertyMap} is frozen. 797 */ isFrozen()798 boolean isFrozen() { 799 return !isExtensible() && allFrozen(); 800 } 801 802 /** 803 * Return a free field slot for this map, or {@code -1} if none is available. 804 * 805 * @return free field slot or -1 806 */ getFreeFieldSlot()807 int getFreeFieldSlot() { 808 if (freeSlots != null) { 809 final int freeSlot = freeSlots.nextSetBit(0); 810 if (freeSlot > -1 && freeSlot < fieldMaximum) { 811 return freeSlot; 812 } 813 } 814 if (fieldCount < fieldMaximum) { 815 return fieldCount; 816 } 817 return -1; 818 } 819 820 /** 821 * Get a free spill slot for this map. 822 * 823 * @return free spill slot 824 */ getFreeSpillSlot()825 int getFreeSpillSlot() { 826 if (freeSlots != null) { 827 final int freeSlot = freeSlots.nextSetBit(fieldMaximum); 828 if (freeSlot > -1) { 829 return freeSlot - fieldMaximum; 830 } 831 } 832 return spillLength; 833 } 834 835 /** 836 * Return a property map with the same layout that is associated with the new prototype object. 837 * 838 * @param newProto New prototype object to replace oldProto. 839 * @return New {@link PropertyMap} with prototype changed. 840 */ changeProto(final ScriptObject newProto)841 public synchronized PropertyMap changeProto(final ScriptObject newProto) { 842 final PropertyMap nextMap = checkProtoHistory(newProto); 843 if (nextMap != null) { 844 return nextMap; 845 } 846 847 if (Context.DEBUG) { 848 setProtoNewMapCount.increment(); 849 } 850 851 final PropertyMap newMap = makeUnsharedCopy(); 852 addToProtoHistory(newProto, newMap); 853 854 return newMap; 855 } 856 857 /** 858 * Make a copy of this property map with the shared prototype field set to null. Note that this is 859 * only necessary for shared maps of top-level objects. Shared prototype maps represented by 860 * {@link SharedPropertyMap} are automatically converted to plain property maps when they evolve. 861 * 862 * @return a copy with the shared proto map unset 863 */ makeUnsharedCopy()864 PropertyMap makeUnsharedCopy() { 865 final PropertyMap newMap = new PropertyMap(this); 866 newMap.sharedProtoMap = null; 867 return newMap; 868 } 869 870 /** 871 * Set a reference to the expected parent prototype map. This is used for class-like 872 * structures where we only want to use a top-level property map if all of the 873 * prototype property maps have not been modified. 874 * 875 * @param protoMap weak reference to the prototype property map 876 */ setSharedProtoMap(final SharedPropertyMap protoMap)877 void setSharedProtoMap(final SharedPropertyMap protoMap) { 878 sharedProtoMap = protoMap; 879 } 880 881 /** 882 * Get the expected prototype property map if it is known, or null. 883 * 884 * @return parent map or null 885 */ getSharedProtoMap()886 public PropertyMap getSharedProtoMap() { 887 return sharedProtoMap; 888 } 889 890 /** 891 * Returns {@code true} if this map has been used as a shared prototype map (i.e. as a prototype 892 * for a JavaScript constructor function) and has not had properties added, deleted or replaced since then. 893 * @return true if this is a valid shared prototype map 894 */ isValidSharedProtoMap()895 boolean isValidSharedProtoMap() { 896 return false; 897 } 898 899 /** 900 * Returns the shared prototype switch point, or null if this is not a shared prototype map. 901 * @return the shared prototype switch point, or null 902 */ getSharedProtoSwitchPoint()903 SwitchPoint getSharedProtoSwitchPoint() { 904 return null; 905 } 906 907 /** 908 * Return true if this map has a shared prototype map which has either been invalidated or does 909 * not match the map of {@code proto}. 910 * @param prototype the prototype object 911 * @return true if this is an invalid shared map for {@code prototype} 912 */ isInvalidSharedMapFor(final ScriptObject prototype)913 boolean isInvalidSharedMapFor(final ScriptObject prototype) { 914 return sharedProtoMap != null 915 && (!sharedProtoMap.isValidSharedProtoMap() || prototype == null || sharedProtoMap != prototype.getMap()); 916 } 917 918 /** 919 * {@link PropertyMap} iterator. 920 */ 921 private static class PropertyMapIterator implements Iterator<Object> { 922 /** Property iterator. */ 923 final Iterator<Property> iter; 924 925 /** Current Property. */ 926 Property property; 927 928 /** 929 * Constructor. 930 * 931 * @param propertyMap {@link PropertyMap} to iterate over. 932 */ PropertyMapIterator(final PropertyMap propertyMap)933 PropertyMapIterator(final PropertyMap propertyMap) { 934 iter = Arrays.asList(propertyMap.properties.getProperties()).iterator(); 935 property = iter.hasNext() ? iter.next() : null; 936 skipNotEnumerable(); 937 } 938 939 /** 940 * Ignore properties that are not enumerable. 941 */ skipNotEnumerable()942 private void skipNotEnumerable() { 943 while (property != null && !property.isEnumerable()) { 944 property = iter.hasNext() ? iter.next() : null; 945 } 946 } 947 948 @Override hasNext()949 public boolean hasNext() { 950 return property != null; 951 } 952 953 @Override next()954 public Object next() { 955 if (property == null) { 956 throw new NoSuchElementException(); 957 } 958 959 final Object key = property.getKey(); 960 property = iter.hasNext() ? iter.next() : null; 961 skipNotEnumerable(); 962 963 return key; 964 } 965 966 @Override remove()967 public void remove() { 968 throw new UnsupportedOperationException("remove"); 969 } 970 } 971 972 /* 973 * Debugging and statistics. 974 */ 975 976 /** 977 * Debug helper function that returns the diff of two property maps, only 978 * displaying the information that is different and in which map it exists 979 * compared to the other map. Can be used to e.g. debug map guards and 980 * investigate why they fail, causing relink 981 * 982 * @param map0 the first property map 983 * @param map1 the second property map 984 * 985 * @return property map diff as string 986 */ diff(final PropertyMap map0, final PropertyMap map1)987 public static String diff(final PropertyMap map0, final PropertyMap map1) { 988 final StringBuilder sb = new StringBuilder(); 989 990 if (map0 != map1) { 991 sb.append(">>> START: Map diff"); 992 boolean found = false; 993 994 for (final Property p : map0.getProperties()) { 995 final Property p2 = map1.findProperty(p.getKey()); 996 if (p2 == null) { 997 sb.append("FIRST ONLY : [").append(p).append("]"); 998 found = true; 999 } else if (p2 != p) { 1000 sb.append("DIFFERENT : [").append(p).append("] != [").append(p2).append("]"); 1001 found = true; 1002 } 1003 } 1004 1005 for (final Property p2 : map1.getProperties()) { 1006 final Property p1 = map0.findProperty(p2.getKey()); 1007 if (p1 == null) { 1008 sb.append("SECOND ONLY: [").append(p2).append("]"); 1009 found = true; 1010 } 1011 } 1012 1013 //assert found; 1014 1015 if (!found) { 1016 sb.append(map0). 1017 append("!="). 1018 append(map1); 1019 } 1020 1021 sb.append("<<< END: Map diff\n"); 1022 } 1023 1024 return sb.toString(); 1025 } 1026 1027 // counters updated only in debug mode 1028 private static LongAdder count; 1029 private static LongAdder clonedCount; 1030 private static LongAdder historyHit; 1031 private static LongAdder protoInvalidations; 1032 private static LongAdder protoHistoryHit; 1033 private static LongAdder setProtoNewMapCount; 1034 static { 1035 if (Context.DEBUG) { 1036 count = new LongAdder(); 1037 clonedCount = new LongAdder(); 1038 historyHit = new LongAdder(); 1039 protoInvalidations = new LongAdder(); 1040 protoHistoryHit = new LongAdder(); 1041 setProtoNewMapCount = new LongAdder(); 1042 } 1043 } 1044 1045 /** 1046 * @return Total number of maps. 1047 */ getCount()1048 public static long getCount() { 1049 return count.longValue(); 1050 } 1051 1052 /** 1053 * @return The number of maps that were cloned. 1054 */ getClonedCount()1055 public static long getClonedCount() { 1056 return clonedCount.longValue(); 1057 } 1058 1059 /** 1060 * @return The number of times history was successfully used. 1061 */ getHistoryHit()1062 public static long getHistoryHit() { 1063 return historyHit.longValue(); 1064 } 1065 1066 /** 1067 * @return The number of times prototype changes caused invalidation. 1068 */ getProtoInvalidations()1069 public static long getProtoInvalidations() { 1070 return protoInvalidations.longValue(); 1071 } 1072 1073 /** 1074 * @return The number of times proto history was successfully used. 1075 */ getProtoHistoryHit()1076 public static long getProtoHistoryHit() { 1077 return protoHistoryHit.longValue(); 1078 } 1079 1080 /** 1081 * @return The number of times prototypes were modified. 1082 */ getSetProtoNewMapCount()1083 public static long getSetProtoNewMapCount() { 1084 return setProtoNewMapCount.longValue(); 1085 } 1086 } 1087