PropertyMap.java revision 1429:b4eb53200105
1/* 2 * Copyright (c) 2010, 2013, 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 26package jdk.nashorn.internal.runtime; 27 28import static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP; 29import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; 30import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; 31 32import java.io.IOException; 33import java.io.ObjectInputStream; 34import java.io.ObjectOutputStream; 35import java.io.Serializable; 36import java.lang.invoke.SwitchPoint; 37import java.lang.ref.SoftReference; 38import java.util.Arrays; 39import java.util.BitSet; 40import java.util.Collection; 41import java.util.HashMap; 42import java.util.Iterator; 43import java.util.NoSuchElementException; 44import java.util.WeakHashMap; 45import java.util.concurrent.atomic.LongAdder; 46import jdk.nashorn.internal.scripts.JO; 47 48/** 49 * Map of object properties. The PropertyMap is the "template" for JavaScript object 50 * layouts. It contains a map with prototype names as keys and {@link Property} instances 51 * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor 52 * to form the seed map for the ScriptObject. 53 * <p> 54 * All property maps are immutable. If a property is added, modified or removed, the mutator 55 * will return a new map. 56 */ 57public class PropertyMap implements Iterable<Object>, Serializable { 58 /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */ 59 private static final int NOT_EXTENSIBLE = 0b0000_0001; 60 /** Does this map contain valid array keys? */ 61 private static final int CONTAINS_ARRAY_KEYS = 0b0000_0010; 62 63 /** Map status flags. */ 64 private final int flags; 65 66 /** Map of properties. */ 67 private transient PropertyHashMap properties; 68 69 /** Number of fields in use. */ 70 private final int fieldCount; 71 72 /** Number of fields available. */ 73 private final int fieldMaximum; 74 75 /** Length of spill in use. */ 76 private final int spillLength; 77 78 /** Structure class name */ 79 private final String className; 80 81 /** A reference to the expected shared prototype property map. If this is set this 82 * property map should only be used if it the same as the actual prototype map. */ 83 private transient SharedPropertyMap sharedProtoMap; 84 85 /** {@link SwitchPoint}s for gets on inherited properties. */ 86 private transient HashMap<String, SwitchPoint> protoSwitches; 87 88 /** History of maps, used to limit map duplication. */ 89 private transient WeakHashMap<Property, SoftReference<PropertyMap>> history; 90 91 /** History of prototypes, used to limit map duplication. */ 92 private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory; 93 94 /** property listeners */ 95 private transient PropertyListeners listeners; 96 97 private transient BitSet freeSlots; 98 99 private static final long serialVersionUID = -7041836752008732533L; 100 101 /** 102 * Constructs a new property map. 103 * 104 * @param properties A {@link PropertyHashMap} with initial contents. 105 * @param fieldCount Number of fields in use. 106 * @param fieldMaximum Number of fields available. 107 * @param spillLength Number of spill slots used. 108 */ 109 private PropertyMap(final PropertyHashMap properties, final int flags, final String className, 110 final int fieldCount, final int fieldMaximum, final int spillLength) { 111 this.properties = properties; 112 this.className = className; 113 this.fieldCount = fieldCount; 114 this.fieldMaximum = fieldMaximum; 115 this.spillLength = spillLength; 116 this.flags = flags; 117 118 if (Context.DEBUG) { 119 count.increment(); 120 } 121 } 122 123 /** 124 * Constructs a clone of {@code propertyMap} with changed properties, flags, or boundaries. 125 * 126 * @param propertyMap Existing property map. 127 * @param properties A {@link PropertyHashMap} with a new set of properties. 128 */ 129 private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength) { 130 this.properties = properties; 131 this.flags = flags; 132 this.spillLength = spillLength; 133 this.fieldCount = fieldCount; 134 this.fieldMaximum = propertyMap.fieldMaximum; 135 this.className = propertyMap.className; 136 // We inherit the parent property listeners instance. It will be cloned when a new listener is added. 137 this.listeners = propertyMap.listeners; 138 this.freeSlots = propertyMap.freeSlots; 139 this.sharedProtoMap = propertyMap.sharedProtoMap; 140 141 if (Context.DEBUG) { 142 count.increment(); 143 clonedCount.increment(); 144 } 145 } 146 147 /** 148 * Constructs an exact clone of {@code propertyMap}. 149 * 150 * @param propertyMap Existing property map. 151 */ 152 protected PropertyMap(final PropertyMap propertyMap) { 153 this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength); 154 } 155 156 private void writeObject(final ObjectOutputStream out) throws IOException { 157 out.defaultWriteObject(); 158 out.writeObject(properties.getProperties()); 159 } 160 161 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 162 in.defaultReadObject(); 163 164 final Property[] props = (Property[]) in.readObject(); 165 this.properties = EMPTY_HASHMAP.immutableAdd(props); 166 167 assert className != null; 168 final Class<?> structure = Context.forStructureClass(className); 169 for (final Property prop : props) { 170 prop.initMethodHandles(structure); 171 } 172 } 173 174 /** 175 * Public property map allocator. 176 * 177 * <p>It is the caller's responsibility to make sure that {@code properties} does not contain 178 * properties with keys that are valid array indices.</p> 179 * 180 * @param properties Collection of initial properties. 181 * @param className class name 182 * @param fieldCount Number of fields in use. 183 * @param fieldMaximum Number of fields available. 184 * @param spillLength Number of used spill slots. 185 * @return New {@link PropertyMap}. 186 */ 187 public static PropertyMap newMap(final Collection<Property> properties, final String className, final int fieldCount, final int fieldMaximum, final int spillLength) { 188 final PropertyHashMap newProperties = EMPTY_HASHMAP.immutableAdd(properties); 189 return new PropertyMap(newProperties, 0, className, fieldCount, fieldMaximum, spillLength); 190 } 191 192 /** 193 * Public property map allocator. Used by nasgen generated code. 194 * 195 * <p>It is the caller's responsibility to make sure that {@code properties} does not contain 196 * properties with keys that are valid array indices.</p> 197 * 198 * @param properties Collection of initial properties. 199 * @return New {@link PropertyMap}. 200 */ 201 public static PropertyMap newMap(final Collection<Property> properties) { 202 return properties == null || properties.isEmpty()? newMap() : newMap(properties, JO.class.getName(), 0, 0, 0); 203 } 204 205 /** 206 * Return a sharable empty map for the given object class. 207 * @param clazz the base object class 208 * @return New empty {@link PropertyMap}. 209 */ 210 public static PropertyMap newMap(final Class<? extends ScriptObject> clazz) { 211 return new PropertyMap(EMPTY_HASHMAP, 0, clazz.getName(), 0, 0, 0); 212 } 213 214 /** 215 * Return a sharable empty map. 216 * 217 * @return New empty {@link PropertyMap}. 218 */ 219 public static PropertyMap newMap() { 220 return newMap(JO.class); 221 } 222 223 /** 224 * Return number of properties in the map. 225 * 226 * @return Number of properties. 227 */ 228 public int size() { 229 return properties.size(); 230 } 231 232 /** 233 * Get the number of listeners of this map 234 * 235 * @return the number of listeners 236 */ 237 public int getListenerCount() { 238 return listeners == null ? 0 : listeners.getListenerCount(); 239 } 240 241 /** 242 * Add {@code listenerMap} as a listener to this property map for the given {@code key}. 243 * 244 * @param key the property name 245 * @param listenerMap the listener map 246 */ 247 public void addListener(final String key, final PropertyMap listenerMap) { 248 if (listenerMap != this) { 249 // We need to clone listener instance when adding a new listener since we share 250 // the listeners instance with our parent maps that don't need to see the new listener. 251 listeners = PropertyListeners.addListener(listeners, key, listenerMap); 252 } 253 } 254 255 /** 256 * A new property is being added. 257 * 258 * @param property The new Property added. 259 * @param isSelf was the property added to this map? 260 */ 261 public void propertyAdded(final Property property, final boolean isSelf) { 262 if (!isSelf) { 263 invalidateProtoSwitchPoint(property.getKey()); 264 } 265 if (listeners != null) { 266 listeners.propertyAdded(property); 267 } 268 } 269 270 /** 271 * An existing property is being deleted. 272 * 273 * @param property The property being deleted. 274 * @param isSelf was the property deleted from this map? 275 */ 276 public void propertyDeleted(final Property property, final boolean isSelf) { 277 if (!isSelf) { 278 invalidateProtoSwitchPoint(property.getKey()); 279 } 280 if (listeners != null) { 281 listeners.propertyDeleted(property); 282 } 283 } 284 285 /** 286 * An existing property is being redefined. 287 * 288 * @param oldProperty The old property 289 * @param newProperty The new property 290 * @param isSelf was the property modified on this map? 291 */ 292 public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) { 293 if (!isSelf) { 294 invalidateProtoSwitchPoint(oldProperty.getKey()); 295 } 296 if (listeners != null) { 297 listeners.propertyModified(oldProperty, newProperty); 298 } 299 } 300 301 /** 302 * The prototype of an object associated with this {@link PropertyMap} is changed. 303 * 304 * @param isSelf was the prototype changed on the object using this map? 305 */ 306 public void protoChanged(final boolean isSelf) { 307 if (!isSelf) { 308 invalidateAllProtoSwitchPoints(); 309 } else if (sharedProtoMap != null) { 310 sharedProtoMap.invalidateSwitchPoint(); 311 } 312 if (listeners != null) { 313 listeners.protoChanged(); 314 } 315 } 316 317 /** 318 * Return a SwitchPoint used to track changes of a property in a prototype. 319 * 320 * @param key Property key. 321 * @return A shared {@link SwitchPoint} for the property. 322 */ 323 public synchronized SwitchPoint getSwitchPoint(final String key) { 324 if (protoSwitches == null) { 325 protoSwitches = new HashMap<>(); 326 } 327 328 SwitchPoint switchPoint = protoSwitches.get(key); 329 if (switchPoint == null) { 330 switchPoint = new SwitchPoint(); 331 protoSwitches.put(key, switchPoint); 332 } 333 334 return switchPoint; 335 } 336 337 /** 338 * Indicate that a prototype property has changed. 339 * 340 * @param key {@link Property} key to invalidate. 341 */ 342 synchronized void invalidateProtoSwitchPoint(final String key) { 343 if (protoSwitches != null) { 344 final SwitchPoint sp = protoSwitches.get(key); 345 if (sp != null) { 346 protoSwitches.remove(key); 347 if (Context.DEBUG) { 348 protoInvalidations.increment(); 349 } 350 SwitchPoint.invalidateAll(new SwitchPoint[]{sp}); 351 } 352 } 353 } 354 355 /** 356 * Indicate that proto itself has changed in hierarchy somewhere. 357 */ 358 synchronized void invalidateAllProtoSwitchPoints() { 359 if (protoSwitches != null) { 360 final int size = protoSwitches.size(); 361 if (size > 0) { 362 if (Context.DEBUG) { 363 protoInvalidations.add(size); 364 } 365 SwitchPoint.invalidateAll(protoSwitches.values().toArray(new SwitchPoint[size])); 366 protoSwitches.clear(); 367 } 368 } 369 } 370 371 /** 372 * Add a property to the map, re-binding its getters and setters, 373 * if available, to a given receiver. This is typically the global scope. See 374 * {@link ScriptObject#addBoundProperties(ScriptObject)} 375 * 376 * @param property {@link Property} being added. 377 * @param bindTo Object to bind to. 378 * 379 * @return New {@link PropertyMap} with {@link Property} added. 380 */ 381 PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) { 382 // We must not store bound property in the history as bound properties can't be reused. 383 return addPropertyNoHistory(new AccessorProperty(property, bindTo)); 384 } 385 386 // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum. 387 private int logicalSlotIndex(final Property property) { 388 final int slot = property.getSlot(); 389 if (slot < 0) { 390 return -1; 391 } 392 return property.isSpill() ? slot + fieldMaximum : slot; 393 } 394 395 private int newSpillLength(final Property newProperty) { 396 return newProperty.isSpill() ? Math.max(spillLength, newProperty.getSlot() + 1) : spillLength; 397 } 398 399 private int newFieldCount(final Property newProperty) { 400 return !newProperty.isSpill() ? Math.max(fieldCount, newProperty.getSlot() + 1) : fieldCount; 401 } 402 403 private int newFlags(final Property newProperty) { 404 return isValidArrayIndex(getArrayIndex(newProperty.getKey())) ? flags | CONTAINS_ARRAY_KEYS : flags; 405 } 406 407 // Update the free slots bitmap for a property that has been deleted and/or added. This method is not synchronized 408 // as it is always invoked on a newly created instance. 409 private void updateFreeSlots(final Property oldProperty, final Property newProperty) { 410 // Free slots bitset is possibly shared with parent map, so we must clone it before making modifications. 411 boolean freeSlotsCloned = false; 412 if (oldProperty != null) { 413 final int slotIndex = logicalSlotIndex(oldProperty); 414 if (slotIndex >= 0) { 415 final BitSet newFreeSlots = freeSlots == null ? new BitSet() : (BitSet)freeSlots.clone(); 416 assert !newFreeSlots.get(slotIndex); 417 newFreeSlots.set(slotIndex); 418 freeSlots = newFreeSlots; 419 freeSlotsCloned = true; 420 } 421 } 422 if (freeSlots != null && newProperty != null) { 423 final int slotIndex = logicalSlotIndex(newProperty); 424 if (slotIndex > -1 && freeSlots.get(slotIndex)) { 425 final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone()); 426 newFreeSlots.clear(slotIndex); 427 freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots; 428 } 429 } 430 } 431 432 /** 433 * Add a property to the map without adding it to the history. This should be used for properties that 434 * can't be shared such as bound properties, or properties that are expected to be added only once. 435 * 436 * @param property {@link Property} being added. 437 * @return New {@link PropertyMap} with {@link Property} added. 438 */ 439 public final PropertyMap addPropertyNoHistory(final Property property) { 440 propertyAdded(property, true); 441 final PropertyHashMap newProperties = properties.immutableAdd(property); 442 final PropertyMap newMap = new PropertyMap(this, newProperties, newFlags(property), newFieldCount(property), newSpillLength(property)); 443 newMap.updateFreeSlots(null, property); 444 445 return newMap; 446 } 447 448 /** 449 * Add a property to the map. Cloning or using an existing map if available. 450 * 451 * @param property {@link Property} being added. 452 * 453 * @return New {@link PropertyMap} with {@link Property} added. 454 */ 455 public final synchronized PropertyMap addProperty(final Property property) { 456 propertyAdded(property, true); 457 PropertyMap newMap = checkHistory(property); 458 459 if (newMap == null) { 460 final PropertyHashMap newProperties = properties.immutableAdd(property); 461 newMap = new PropertyMap(this, newProperties, newFlags(property), newFieldCount(property), newSpillLength(property)); 462 newMap.updateFreeSlots(null, property); 463 addToHistory(property, newMap); 464 } 465 466 return newMap; 467 } 468 469 /** 470 * Remove a property from a map. Cloning or using an existing map if available. 471 * 472 * @param property {@link Property} being removed. 473 * 474 * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found. 475 */ 476 public final synchronized PropertyMap deleteProperty(final Property property) { 477 propertyDeleted(property, true); 478 PropertyMap newMap = checkHistory(property); 479 final String key = property.getKey(); 480 481 if (newMap == null && properties.containsKey(key)) { 482 final PropertyHashMap newProperties = properties.immutableRemove(key); 483 final boolean isSpill = property.isSpill(); 484 final int slot = property.getSlot(); 485 // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count. 486 // Otherwise mark it as free in free slots bitset. 487 if (isSpill && slot >= 0 && slot == spillLength - 1) { 488 newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength - 1); 489 newMap.freeSlots = freeSlots; 490 } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) { 491 newMap = new PropertyMap(this, newProperties, flags, fieldCount - 1, spillLength); 492 newMap.freeSlots = freeSlots; 493 } else { 494 newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength); 495 newMap.updateFreeSlots(property, null); 496 } 497 addToHistory(property, newMap); 498 } 499 500 return newMap; 501 } 502 503 /** 504 * Replace an existing property with a new one. 505 * 506 * @param oldProperty Property to replace. 507 * @param newProperty New {@link Property}. 508 * 509 * @return New {@link PropertyMap} with {@link Property} replaced. 510 */ 511 public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) { 512 propertyModified(oldProperty, newProperty, true); 513 /* 514 * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods. 515 * 516 * This replaceProperty method is called only for the following three cases: 517 * 518 * 1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots. 519 * 2. To change one UserAccessor property with another - user getter or setter changed via 520 * Object.defineProperty function. Again, same spill slots are re-used. 521 * 3. Via ScriptObject.setUserAccessors method to set user getter and setter functions 522 * replacing the dummy AccessorProperty with null method handles (added during map init). 523 * 524 * In case (1) and case(2), the property type of old and new property is same. For case (3), 525 * the old property is an AccessorProperty and the new one is a UserAccessorProperty property. 526 */ 527 528 final boolean sameType = oldProperty.getClass() == newProperty.getClass(); 529 assert sameType || 530 oldProperty instanceof AccessorProperty && 531 newProperty instanceof UserAccessorProperty : 532 "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]"; 533 534 /* 535 * spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need 536 * to add spill count of the newly added UserAccessorProperty property. 537 */ 538 final int newSpillLength = sameType ? spillLength : Math.max(spillLength, newProperty.getSlot() + 1); 539 540 // Add replaces existing property. 541 final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty); 542 final PropertyMap newMap = new PropertyMap(this, newProperties, flags, fieldCount, newSpillLength); 543 544 if (!sameType) { 545 newMap.updateFreeSlots(oldProperty, newProperty); 546 } 547 return newMap; 548 } 549 550 /** 551 * Make a new UserAccessorProperty property. getter and setter functions are stored in 552 * this ScriptObject and slot values are used in property object. Note that slots 553 * are assigned speculatively and should be added to map before adding other 554 * properties. 555 * 556 * @param key the property name 557 * @param propertyFlags attribute flags of the property 558 * @return the newly created UserAccessorProperty 559 */ 560 public final UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) { 561 return new UserAccessorProperty(key, propertyFlags, getFreeSpillSlot()); 562 } 563 564 /** 565 * Find a property in the map. 566 * 567 * @param key Key to search for. 568 * 569 * @return {@link Property} matching key. 570 */ 571 public final Property findProperty(final String key) { 572 return properties.find(key); 573 } 574 575 /** 576 * Adds all map properties from another map. 577 * 578 * @param other The source of properties. 579 * 580 * @return New {@link PropertyMap} with added properties. 581 */ 582 public final PropertyMap addAll(final PropertyMap other) { 583 assert this != other : "adding property map to itself"; 584 final Property[] otherProperties = other.properties.getProperties(); 585 final PropertyHashMap newProperties = properties.immutableAdd(otherProperties); 586 587 final PropertyMap newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength); 588 for (final Property property : otherProperties) { 589 // This method is only safe to use with non-slotted, native getter/setter properties 590 assert property.getSlot() == -1; 591 assert !(isValidArrayIndex(getArrayIndex(property.getKey()))); 592 } 593 594 return newMap; 595 } 596 597 /** 598 * Return an array of all properties. 599 * 600 * @return Properties as an array. 601 */ 602 public final Property[] getProperties() { 603 return properties.getProperties(); 604 } 605 606 /** 607 * Return the name of the class of objects using this property map. 608 * 609 * @return class name of owner objects. 610 */ 611 public final String getClassName() { 612 return className; 613 } 614 615 /** 616 * Prevents the map from having additional properties. 617 * 618 * @return New map with {@link #NOT_EXTENSIBLE} flag set. 619 */ 620 PropertyMap preventExtensions() { 621 return new PropertyMap(this, properties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); 622 } 623 624 /** 625 * Prevents properties in map from being modified. 626 * 627 * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with 628 * {@link Property#NOT_CONFIGURABLE} set. 629 */ 630 PropertyMap seal() { 631 PropertyHashMap newProperties = EMPTY_HASHMAP; 632 633 for (final Property oldProperty : properties.getProperties()) { 634 newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE)); 635 } 636 637 return new PropertyMap(this, newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); 638 } 639 640 /** 641 * Prevents properties in map from being modified or written to. 642 * 643 * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with 644 * {@link Property#NOT_CONFIGURABLE} and {@link Property#NOT_WRITABLE} set. 645 */ 646 PropertyMap freeze() { 647 PropertyHashMap newProperties = EMPTY_HASHMAP; 648 649 for (final Property oldProperty : properties.getProperties()) { 650 int propertyFlags = Property.NOT_CONFIGURABLE; 651 652 if (!(oldProperty instanceof UserAccessorProperty)) { 653 propertyFlags |= Property.NOT_WRITABLE; 654 } 655 656 newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags)); 657 } 658 659 return new PropertyMap(this, newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength); 660 } 661 662 /** 663 * Check for any configurable properties. 664 * 665 * @return {@code true} if any configurable. 666 */ 667 private boolean anyConfigurable() { 668 for (final Property property : properties.getProperties()) { 669 if (property.isConfigurable()) { 670 return true; 671 } 672 } 673 674 return false; 675 } 676 677 /** 678 * Check if all properties are frozen. 679 * 680 * @return {@code true} if all are frozen. 681 */ 682 private boolean allFrozen() { 683 for (final Property property : properties.getProperties()) { 684 // check if it is a data descriptor 685 if (!(property instanceof UserAccessorProperty)) { 686 if (property.isWritable()) { 687 return false; 688 } 689 } 690 if (property.isConfigurable()) { 691 return false; 692 } 693 } 694 695 return true; 696 } 697 698 /** 699 * Check prototype history for an existing property map with specified prototype. 700 * 701 * @param proto New prototype object. 702 * 703 * @return Existing {@link PropertyMap} or {@code null} if not found. 704 */ 705 private PropertyMap checkProtoHistory(final ScriptObject proto) { 706 final PropertyMap cachedMap; 707 if (protoHistory != null) { 708 final SoftReference<PropertyMap> weakMap = protoHistory.get(proto); 709 cachedMap = (weakMap != null ? weakMap.get() : null); 710 } else { 711 cachedMap = null; 712 } 713 714 if (Context.DEBUG && cachedMap != null) { 715 protoHistoryHit.increment(); 716 } 717 718 return cachedMap; 719 } 720 721 /** 722 * Add a map to the prototype history. 723 * 724 * @param newProto Prototype to add (key.) 725 * @param newMap {@link PropertyMap} associated with prototype. 726 */ 727 private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { 728 if (protoHistory == null) { 729 protoHistory = new WeakHashMap<>(); 730 } 731 732 protoHistory.put(newProto, new SoftReference<>(newMap)); 733 } 734 735 /** 736 * Track the modification of the map. 737 * 738 * @param property Mapping property. 739 * @param newMap Modified {@link PropertyMap}. 740 */ 741 private void addToHistory(final Property property, final PropertyMap newMap) { 742 if (history == null) { 743 history = new WeakHashMap<>(); 744 } 745 746 history.put(property, new SoftReference<>(newMap)); 747 } 748 749 /** 750 * Check the history for a map that already has the given property added. 751 * 752 * @param property {@link Property} to add. 753 * 754 * @return Existing map or {@code null} if not found. 755 */ 756 private PropertyMap checkHistory(final Property property) { 757 758 if (history != null) { 759 final SoftReference<PropertyMap> ref = history.get(property); 760 final PropertyMap historicMap = ref == null ? null : ref.get(); 761 762 if (historicMap != null) { 763 if (Context.DEBUG) { 764 historyHit.increment(); 765 } 766 767 return historicMap; 768 } 769 } 770 771 return null; 772 } 773 774 /** 775 * Returns true if the two maps have identical properties in the same order, but allows the properties to differ in 776 * their types. This method is mostly useful for tests. 777 * @param otherMap the other map 778 * @return true if this map has identical properties in the same order as the other map, allowing the properties to 779 * differ in type. 780 */ 781 public boolean equalsWithoutType(final PropertyMap otherMap) { 782 if (properties.size() != otherMap.properties.size()) { 783 return false; 784 } 785 786 final Iterator<Property> iter = properties.values().iterator(); 787 final Iterator<Property> otherIter = otherMap.properties.values().iterator(); 788 789 while (iter.hasNext() && otherIter.hasNext()) { 790 if (!iter.next().equalsWithoutType(otherIter.next())) { 791 return false; 792 } 793 } 794 795 return true; 796 } 797 798 @Override 799 public String toString() { 800 final StringBuilder sb = new StringBuilder(); 801 802 sb.append(Debug.id(this)); 803 sb.append(" = {\n"); 804 805 for (final Property property : getProperties()) { 806 sb.append('\t'); 807 sb.append(property); 808 sb.append('\n'); 809 } 810 811 sb.append('}'); 812 813 return sb.toString(); 814 } 815 816 @Override 817 public Iterator<Object> iterator() { 818 return new PropertyMapIterator(this); 819 } 820 821 /** 822 * Check if this map contains properties with valid array keys 823 * 824 * @return {@code true} if this map contains properties with valid array keys 825 */ 826 public final boolean containsArrayKeys() { 827 return (flags & CONTAINS_ARRAY_KEYS) != 0; 828 } 829 830 /** 831 * Test to see if {@link PropertyMap} is extensible. 832 * 833 * @return {@code true} if {@link PropertyMap} can be added to. 834 */ 835 boolean isExtensible() { 836 return (flags & NOT_EXTENSIBLE) == 0; 837 } 838 839 /** 840 * Test to see if {@link PropertyMap} is not extensible or any properties 841 * can not be modified. 842 * 843 * @return {@code true} if {@link PropertyMap} is sealed. 844 */ 845 boolean isSealed() { 846 return !isExtensible() && !anyConfigurable(); 847 } 848 849 /** 850 * Test to see if {@link PropertyMap} is not extensible or all properties 851 * can not be modified. 852 * 853 * @return {@code true} if {@link PropertyMap} is frozen. 854 */ 855 boolean isFrozen() { 856 return !isExtensible() && allFrozen(); 857 } 858 859 /** 860 * Return a free field slot for this map, or {@code -1} if none is available. 861 * 862 * @return free field slot or -1 863 */ 864 int getFreeFieldSlot() { 865 if (freeSlots != null) { 866 final int freeSlot = freeSlots.nextSetBit(0); 867 if (freeSlot > -1 && freeSlot < fieldMaximum) { 868 return freeSlot; 869 } 870 } 871 if (fieldCount < fieldMaximum) { 872 return fieldCount; 873 } 874 return -1; 875 } 876 877 /** 878 * Get a free spill slot for this map. 879 * 880 * @return free spill slot 881 */ 882 int getFreeSpillSlot() { 883 if (freeSlots != null) { 884 final int freeSlot = freeSlots.nextSetBit(fieldMaximum); 885 if (freeSlot > -1) { 886 return freeSlot - fieldMaximum; 887 } 888 } 889 return spillLength; 890 } 891 892 /** 893 * Return a property map with the same layout that is associated with the new prototype object. 894 * 895 * @param newProto New prototype object to replace oldProto. 896 * @return New {@link PropertyMap} with prototype changed. 897 */ 898 public synchronized PropertyMap changeProto(final ScriptObject newProto) { 899 final PropertyMap nextMap = checkProtoHistory(newProto); 900 if (nextMap != null) { 901 return nextMap; 902 } 903 904 if (Context.DEBUG) { 905 setProtoNewMapCount.increment(); 906 } 907 908 final PropertyMap newMap = makeUnsharedCopy(); 909 addToProtoHistory(newProto, newMap); 910 911 return newMap; 912 } 913 914 /** 915 * Make a copy of this property map with the shared prototype field set to null. Note that this is 916 * only necessary for shared maps of top-level objects. Shared prototype maps represented by 917 * {@link SharedPropertyMap} are automatically converted to plain property maps when they evolve. 918 * 919 * @return a copy with the shared proto map unset 920 */ 921 PropertyMap makeUnsharedCopy() { 922 final PropertyMap newMap = new PropertyMap(this); 923 newMap.sharedProtoMap = null; 924 return newMap; 925 } 926 927 /** 928 * Set a reference to the expected parent prototype map. This is used for class-like 929 * structures where we only want to use a top-level property map if all of the 930 * prototype property maps have not been modified. 931 * 932 * @param protoMap weak reference to the prototype property map 933 */ 934 void setSharedProtoMap(final SharedPropertyMap protoMap) { 935 sharedProtoMap = protoMap; 936 } 937 938 /** 939 * Get the expected prototype property map if it is known, or null. 940 * 941 * @return parent map or null 942 */ 943 public PropertyMap getSharedProtoMap() { 944 return sharedProtoMap; 945 } 946 947 /** 948 * Returns {@code true} if this map has been used as a shared prototype map (i.e. as a prototype 949 * for a JavaScript constructor function) and has not had properties added, deleted or replaced since then. 950 * @return true if this is a valid shared prototype map 951 */ 952 boolean isValidSharedProtoMap() { 953 return false; 954 } 955 956 /** 957 * Returns the shared prototype switch point, or null if this is not a shared prototype map. 958 * @return the shared prototype switch point, or null 959 */ 960 SwitchPoint getSharedProtoSwitchPoint() { 961 return null; 962 } 963 964 /** 965 * Return true if this map has a shared prototype map which has either been invalidated or does 966 * not match the map of {@code proto}. 967 * @param prototype the prototype object 968 * @return true if this is an invalid shared map for {@code prototype} 969 */ 970 boolean isInvalidSharedMapFor(final ScriptObject prototype) { 971 return sharedProtoMap != null 972 && (!sharedProtoMap.isValidSharedProtoMap() || prototype == null || sharedProtoMap != prototype.getMap()); 973 } 974 975 /** 976 * {@link PropertyMap} iterator. 977 */ 978 private static class PropertyMapIterator implements Iterator<Object> { 979 /** Property iterator. */ 980 final Iterator<Property> iter; 981 982 /** Current Property. */ 983 Property property; 984 985 /** 986 * Constructor. 987 * 988 * @param propertyMap {@link PropertyMap} to iterate over. 989 */ 990 PropertyMapIterator(final PropertyMap propertyMap) { 991 iter = Arrays.asList(propertyMap.properties.getProperties()).iterator(); 992 property = iter.hasNext() ? iter.next() : null; 993 skipNotEnumerable(); 994 } 995 996 /** 997 * Ignore properties that are not enumerable. 998 */ 999 private void skipNotEnumerable() { 1000 while (property != null && !property.isEnumerable()) { 1001 property = iter.hasNext() ? iter.next() : null; 1002 } 1003 } 1004 1005 @Override 1006 public boolean hasNext() { 1007 return property != null; 1008 } 1009 1010 @Override 1011 public Object next() { 1012 if (property == null) { 1013 throw new NoSuchElementException(); 1014 } 1015 1016 final Object key = property.getKey(); 1017 property = iter.next(); 1018 skipNotEnumerable(); 1019 1020 return key; 1021 } 1022 1023 @Override 1024 public void remove() { 1025 throw new UnsupportedOperationException("remove"); 1026 } 1027 } 1028 1029 /* 1030 * Debugging and statistics. 1031 */ 1032 1033 /** 1034 * Debug helper function that returns the diff of two property maps, only 1035 * displaying the information that is different and in which map it exists 1036 * compared to the other map. Can be used to e.g. debug map guards and 1037 * investigate why they fail, causing relink 1038 * 1039 * @param map0 the first property map 1040 * @param map1 the second property map 1041 * 1042 * @return property map diff as string 1043 */ 1044 public static String diff(final PropertyMap map0, final PropertyMap map1) { 1045 final StringBuilder sb = new StringBuilder(); 1046 1047 if (map0 != map1) { 1048 sb.append(">>> START: Map diff"); 1049 boolean found = false; 1050 1051 for (final Property p : map0.getProperties()) { 1052 final Property p2 = map1.findProperty(p.getKey()); 1053 if (p2 == null) { 1054 sb.append("FIRST ONLY : [").append(p).append("]"); 1055 found = true; 1056 } else if (p2 != p) { 1057 sb.append("DIFFERENT : [").append(p).append("] != [").append(p2).append("]"); 1058 found = true; 1059 } 1060 } 1061 1062 for (final Property p2 : map1.getProperties()) { 1063 final Property p1 = map0.findProperty(p2.getKey()); 1064 if (p1 == null) { 1065 sb.append("SECOND ONLY: [").append(p2).append("]"); 1066 found = true; 1067 } 1068 } 1069 1070 //assert found; 1071 1072 if (!found) { 1073 sb.append(map0). 1074 append("!="). 1075 append(map1); 1076 } 1077 1078 sb.append("<<< END: Map diff\n"); 1079 } 1080 1081 return sb.toString(); 1082 } 1083 1084 // counters updated only in debug mode 1085 private static LongAdder count; 1086 private static LongAdder clonedCount; 1087 private static LongAdder historyHit; 1088 private static LongAdder protoInvalidations; 1089 private static LongAdder protoHistoryHit; 1090 private static LongAdder setProtoNewMapCount; 1091 static { 1092 if (Context.DEBUG) { 1093 count = new LongAdder(); 1094 clonedCount = new LongAdder(); 1095 historyHit = new LongAdder(); 1096 protoInvalidations = new LongAdder(); 1097 protoHistoryHit = new LongAdder(); 1098 setProtoNewMapCount = new LongAdder(); 1099 } 1100 } 1101 1102 /** 1103 * @return Total number of maps. 1104 */ 1105 public static long getCount() { 1106 return count.longValue(); 1107 } 1108 1109 /** 1110 * @return The number of maps that were cloned. 1111 */ 1112 public static long getClonedCount() { 1113 return clonedCount.longValue(); 1114 } 1115 1116 /** 1117 * @return The number of times history was successfully used. 1118 */ 1119 public static long getHistoryHit() { 1120 return historyHit.longValue(); 1121 } 1122 1123 /** 1124 * @return The number of times prototype changes caused invalidation. 1125 */ 1126 public static long getProtoInvalidations() { 1127 return protoInvalidations.longValue(); 1128 } 1129 1130 /** 1131 * @return The number of times proto history was successfully used. 1132 */ 1133 public static long getProtoHistoryHit() { 1134 return protoHistoryHit.longValue(); 1135 } 1136 1137 /** 1138 * @return The number of times prototypes were modified. 1139 */ 1140 public static long getSetProtoNewMapCount() { 1141 return setProtoNewMapCount.longValue(); 1142 } 1143} 1144