Structure.cpp
1 /* 2 * Copyright (C) 2008-2019 Apple Inc. All rights reserved. 3 * Copyright (C) 2020 Alexey Shvayka <shvaikalesh@gmail.com>. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include "config.h" 28 #include "Structure.h" 29 30 #include "BuiltinNames.h" 31 #include "DumpContext.h" 32 #include "JSCInlines.h" 33 #include "PropertyMapHashTable.h" 34 #include "PropertyNameArray.h" 35 #include <wtf/CommaPrinter.h> 36 #include <wtf/NeverDestroyed.h> 37 #include <wtf/RefPtr.h> 38 39 #define DUMP_STRUCTURE_ID_STATISTICS 0 40 41 namespace JSC { 42 43 #if DUMP_STRUCTURE_ID_STATISTICS 44 static HashSet<Structure*>& liveStructureSet = *(new HashSet<Structure*>); 45 #endif 46 47 class SingleSlotTransitionWeakOwner final : public WeakHandleOwner { 48 void finalize(Handle<Unknown>, void* context) final 49 { 50 StructureTransitionTable* table = reinterpret_cast<StructureTransitionTable*>(context); 51 ASSERT(table->isUsingSingleSlot()); 52 WeakSet::deallocate(table->weakImpl()); 53 table->m_data = StructureTransitionTable::UsingSingleSlotFlag; 54 } 55 }; 56 57 static SingleSlotTransitionWeakOwner& singleSlotTransitionWeakOwner() 58 { 59 static NeverDestroyed<SingleSlotTransitionWeakOwner> owner; 60 return owner; 61 } 62 63 inline Structure* StructureTransitionTable::singleTransition() const 64 { 65 ASSERT(isUsingSingleSlot()); 66 if (WeakImpl* impl = this->weakImpl()) { 67 if (impl->state() == WeakImpl::Live) 68 return jsCast<Structure*>(impl->jsValue().asCell()); 69 } 70 return nullptr; 71 } 72 73 inline void StructureTransitionTable::setSingleTransition(Structure* structure) 74 { 75 ASSERT(isUsingSingleSlot()); 76 if (WeakImpl* impl = this->weakImpl()) 77 WeakSet::deallocate(impl); 78 WeakImpl* impl = WeakSet::allocate(structure, &singleSlotTransitionWeakOwner(), this); 79 m_data = bitwise_cast<intptr_t>(impl) | UsingSingleSlotFlag; 80 } 81 82 bool StructureTransitionTable::contains(UniquedStringImpl* rep, unsigned attributes, TransitionKind transitionKind) const 83 { 84 if (isUsingSingleSlot()) { 85 Structure* transition = singleTransition(); 86 return transition && transition->m_transitionPropertyName == rep && transition->transitionPropertyAttributes() == attributes && transition->transitionKind() == transitionKind; 87 } 88 return map()->get(StructureTransitionTable::Hash::Key(rep, attributes, transitionKind)); 89 } 90 91 inline Structure* StructureTransitionTable::get(UniquedStringImpl* rep, unsigned attributes, TransitionKind transitionKind) const 92 { 93 if (isUsingSingleSlot()) { 94 Structure* transition = singleTransition(); 95 return (transition && transition->m_transitionPropertyName == rep && transition->transitionPropertyAttributes() == attributes && transition->transitionKind() == transitionKind) ? transition : nullptr; 96 } 97 return map()->get(StructureTransitionTable::Hash::Key(rep, attributes, transitionKind)); 98 } 99 100 void StructureTransitionTable::add(VM& vm, Structure* structure) 101 { 102 if (isUsingSingleSlot()) { 103 Structure* existingTransition = singleTransition(); 104 105 // This handles the first transition being added. 106 if (!existingTransition) { 107 setSingleTransition(structure); 108 return; 109 } 110 111 // This handles the second transition being added 112 // (or the first transition being despecified!) 113 setMap(new TransitionMap(vm)); 114 add(vm, existingTransition); 115 } 116 117 // Add the structure to the map. 118 map()->set(StructureTransitionTable::Hash::Key(structure->m_transitionPropertyName.get(), structure->transitionPropertyAttributes(), structure->transitionKind()), structure); 119 } 120 121 void Structure::dumpStatistics() 122 { 123 #if DUMP_STRUCTURE_ID_STATISTICS 124 unsigned numberLeaf = 0; 125 unsigned numberUsingSingleSlot = 0; 126 unsigned numberSingletons = 0; 127 unsigned numberWithPropertyMaps = 0; 128 unsigned totalPropertyMapsSize = 0; 129 130 HashSet<Structure*>::const_iterator end = liveStructureSet.end(); 131 for (HashSet<Structure*>::const_iterator it = liveStructureSet.begin(); it != end; ++it) { 132 Structure* structure = *it; 133 134 switch (structure->m_transitionTable.size()) { 135 case 0: 136 ++numberLeaf; 137 if (!structure->previousID()) 138 ++numberSingletons; 139 break; 140 141 case 1: 142 ++numberUsingSingleSlot; 143 break; 144 } 145 146 if (PropertyTable* table = structure->propertyTableOrNull()) { 147 ++numberWithPropertyMaps; 148 totalPropertyMapsSize += table->sizeInMemory(); 149 } 150 } 151 152 dataLogF("Number of live Structures: %d\n", liveStructureSet.size()); 153 dataLogF("Number of Structures using the single item optimization for transition map: %d\n", numberUsingSingleSlot); 154 dataLogF("Number of Structures that are leaf nodes: %d\n", numberLeaf); 155 dataLogF("Number of Structures that singletons: %d\n", numberSingletons); 156 dataLogF("Number of Structures with PropertyMaps: %d\n", numberWithPropertyMaps); 157 158 dataLogF("Size of a single Structures: %d\n", static_cast<unsigned>(sizeof(Structure))); 159 dataLogF("Size of sum of all property maps: %d\n", totalPropertyMapsSize); 160 dataLogF("Size of average of all property maps: %f\n", static_cast<double>(totalPropertyMapsSize) / static_cast<double>(liveStructureSet.size())); 161 #else 162 dataLogF("Dumping Structure statistics is not enabled.\n"); 163 #endif 164 } 165 166 #if ASSERT_ENABLED 167 void Structure::validateFlags() 168 { 169 const MethodTable& methodTable = m_classInfo->methodTable; 170 171 bool overridesGetCallData = methodTable.getCallData != JSCell::getCallData; 172 RELEASE_ASSERT(overridesGetCallData == typeInfo().overridesGetCallData()); 173 174 bool overridesGetOwnPropertySlot = 175 methodTable.getOwnPropertySlot != JSObject::getOwnPropertySlot 176 && methodTable.getOwnPropertySlot != JSCell::getOwnPropertySlot; 177 // We can strengthen this into an equivalence test if there are no classes 178 // that specifies this flag without overriding getOwnPropertySlot. 179 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=212956 180 if (overridesGetOwnPropertySlot) 181 RELEASE_ASSERT(typeInfo().overridesGetOwnPropertySlot()); 182 183 bool overridesGetOwnPropertySlotByIndex = 184 methodTable.getOwnPropertySlotByIndex != JSObject::getOwnPropertySlotByIndex 185 && methodTable.getOwnPropertySlotByIndex != JSCell::getOwnPropertySlotByIndex; 186 // We can strengthen this into an equivalence test if there are no classes 187 // that specifies this flag without overriding getOwnPropertySlotByIndex. 188 // FIXME: https://bugs.webkit.org/show_bug.cgi?id=212958 189 if (overridesGetOwnPropertySlotByIndex) 190 RELEASE_ASSERT(typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero()); 191 192 bool overridesPutPropertySecurityCheck = 193 methodTable.doPutPropertySecurityCheck != JSObject::doPutPropertySecurityCheck 194 && methodTable.doPutPropertySecurityCheck != JSCell::doPutPropertySecurityCheck; 195 RELEASE_ASSERT(overridesPutPropertySecurityCheck == typeInfo().hasPutPropertySecurityCheck()); 196 197 bool overridesGetOwnPropertyNames = 198 methodTable.getOwnPropertyNames != JSObject::getOwnPropertyNames 199 && methodTable.getOwnPropertyNames != JSCell::getOwnPropertyNames; 200 RELEASE_ASSERT(overridesGetOwnPropertyNames == typeInfo().overridesGetOwnPropertyNames()); 201 202 bool overridesGetOwnSpecialPropertyNames = 203 methodTable.getOwnSpecialPropertyNames != JSObject::getOwnSpecialPropertyNames 204 && methodTable.getOwnSpecialPropertyNames != JSCell::getOwnSpecialPropertyNames; 205 RELEASE_ASSERT(overridesGetOwnSpecialPropertyNames == typeInfo().overridesGetOwnSpecialPropertyNames()); 206 207 bool overridesGetPrototype = 208 methodTable.getPrototype != static_cast<MethodTable::GetPrototypeFunctionPtr>(JSObject::getPrototype) 209 && methodTable.getPrototype != JSCell::getPrototype; 210 RELEASE_ASSERT(overridesGetPrototype == typeInfo().overridesGetPrototype()); 211 } 212 #else 213 inline void Structure::validateFlags() { } 214 #endif 215 216 Structure::Structure(VM& vm, JSGlobalObject* globalObject, JSValue prototype, const TypeInfo& typeInfo, const ClassInfo* classInfo, IndexingType indexingType, unsigned inlineCapacity) 217 : JSCell(vm, vm.structureStructure.get()) 218 , m_blob(vm.heap.structureIDTable().allocateID(this), indexingType, typeInfo) 219 , m_outOfLineTypeFlags(typeInfo.outOfLineTypeFlags()) 220 , m_inlineCapacity(inlineCapacity) 221 , m_bitField(0) 222 , m_globalObject(vm, this, globalObject, WriteBarrier<JSGlobalObject>::MayBeNull) 223 , m_prototype(vm, this, prototype) 224 , m_classInfo(classInfo) 225 , m_transitionWatchpointSet(IsWatched) 226 , m_propertyHash(0) 227 { 228 setDictionaryKind(NoneDictionaryKind); 229 setIsPinnedPropertyTable(false); 230 setHasGetterSetterProperties(classInfo->hasStaticSetterOrReadonlyProperties()); 231 setHasCustomGetterSetterProperties(false); 232 setHasReadOnlyOrGetterSetterPropertiesExcludingProto(classInfo->hasStaticSetterOrReadonlyProperties()); 233 setHasUnderscoreProtoPropertyExcludingOriginalProto(false); 234 setIsQuickPropertyAccessAllowedForEnumeration(true); 235 setTransitionPropertyAttributes(0); 236 setTransitionKind(TransitionKind::Unknown); 237 setDidPreventExtensions(false); 238 setDidTransition(false); 239 setStaticPropertiesReified(false); 240 setTransitionWatchpointIsLikelyToBeFired(false); 241 setHasBeenDictionary(false); 242 setProtectPropertyTableWhileTransitioning(false); 243 setTransitionOffset(vm, invalidOffset); 244 setMaxOffset(vm, invalidOffset); 245 246 ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity()); 247 ASSERT(static_cast<PropertyOffset>(inlineCapacity) < firstOutOfLineOffset); 248 ASSERT(!hasRareData()); 249 ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() == m_classInfo->hasStaticSetterOrReadonlyProperties()); 250 ASSERT(hasGetterSetterProperties() == m_classInfo->hasStaticSetterOrReadonlyProperties()); 251 252 validateFlags(); 253 } 254 255 const ClassInfo Structure::s_info = { "Structure", nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(Structure) }; 256 257 Structure::Structure(VM& vm) 258 : JSCell(CreatingEarlyCell) 259 , m_inlineCapacity(0) 260 , m_bitField(0) 261 , m_prototype(vm, this, jsNull()) 262 , m_classInfo(info()) 263 , m_transitionWatchpointSet(IsWatched) 264 , m_propertyHash(0) 265 { 266 setDictionaryKind(NoneDictionaryKind); 267 setIsPinnedPropertyTable(false); 268 setHasGetterSetterProperties(m_classInfo->hasStaticSetterOrReadonlyProperties()); 269 setHasCustomGetterSetterProperties(false); 270 setHasReadOnlyOrGetterSetterPropertiesExcludingProto(m_classInfo->hasStaticSetterOrReadonlyProperties()); 271 setHasUnderscoreProtoPropertyExcludingOriginalProto(false); 272 setIsQuickPropertyAccessAllowedForEnumeration(true); 273 setTransitionPropertyAttributes(0); 274 setTransitionKind(TransitionKind::Unknown); 275 setDidPreventExtensions(false); 276 setDidTransition(false); 277 setStaticPropertiesReified(false); 278 setTransitionWatchpointIsLikelyToBeFired(false); 279 setHasBeenDictionary(false); 280 setProtectPropertyTableWhileTransitioning(false); 281 setTransitionOffset(vm, invalidOffset); 282 setMaxOffset(vm, invalidOffset); 283 284 TypeInfo typeInfo = TypeInfo(CellType, StructureFlags); 285 m_blob = StructureIDBlob(vm.heap.structureIDTable().allocateID(this), 0, typeInfo); 286 m_outOfLineTypeFlags = typeInfo.outOfLineTypeFlags(); 287 288 ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !m_classInfo->hasStaticSetterOrReadonlyProperties()); 289 ASSERT(hasGetterSetterProperties() || !m_classInfo->hasStaticSetterOrReadonlyProperties()); 290 ASSERT(!this->typeInfo().overridesGetCallData() || m_classInfo->methodTable.getCallData != &JSCell::getCallData); 291 } 292 293 Structure::Structure(VM& vm, Structure* previous, DeferredStructureTransitionWatchpointFire* deferred) 294 : JSCell(vm, vm.structureStructure.get()) 295 , m_inlineCapacity(previous->m_inlineCapacity) 296 , m_bitField(0) 297 , m_prototype(vm, this, previous->m_prototype.get()) 298 , m_classInfo(previous->m_classInfo) 299 , m_transitionWatchpointSet(IsWatched) 300 , m_propertyHash(previous->m_propertyHash) 301 , m_seenProperties(previous->m_seenProperties) 302 { 303 setDictionaryKind(previous->dictionaryKind()); 304 setIsPinnedPropertyTable(false); 305 setHasBeenFlattenedBefore(previous->hasBeenFlattenedBefore()); 306 setHasGetterSetterProperties(previous->hasGetterSetterProperties()); 307 setHasCustomGetterSetterProperties(previous->hasCustomGetterSetterProperties()); 308 setHasReadOnlyOrGetterSetterPropertiesExcludingProto(previous->hasReadOnlyOrGetterSetterPropertiesExcludingProto()); 309 setHasUnderscoreProtoPropertyExcludingOriginalProto(previous->hasUnderscoreProtoPropertyExcludingOriginalProto()); 310 setIsQuickPropertyAccessAllowedForEnumeration(previous->isQuickPropertyAccessAllowedForEnumeration()); 311 setTransitionPropertyAttributes(0); 312 setTransitionKind(TransitionKind::Unknown); 313 setDidPreventExtensions(previous->didPreventExtensions()); 314 setDidTransition(true); 315 setStaticPropertiesReified(previous->staticPropertiesReified()); 316 setHasBeenDictionary(previous->hasBeenDictionary()); 317 setProtectPropertyTableWhileTransitioning(false); 318 setTransitionOffset(vm, invalidOffset); 319 setMaxOffset(vm, invalidOffset); 320 321 TypeInfo typeInfo = previous->typeInfo(); 322 m_blob = StructureIDBlob(vm.heap.structureIDTable().allocateID(this), previous->indexingModeIncludingHistory(), typeInfo); 323 m_outOfLineTypeFlags = typeInfo.outOfLineTypeFlags(); 324 325 ASSERT(!previous->typeInfo().structureIsImmortal()); 326 setPreviousID(vm, previous); 327 328 previous->didTransitionFromThisStructure(deferred); 329 330 // Copy this bit now, in case previous was being watched. 331 setTransitionWatchpointIsLikelyToBeFired(previous->transitionWatchpointIsLikelyToBeFired()); 332 333 if (previous->m_globalObject) 334 m_globalObject.set(vm, this, previous->m_globalObject.get()); 335 ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !m_classInfo->hasStaticSetterOrReadonlyProperties()); 336 ASSERT(hasGetterSetterProperties() || !m_classInfo->hasStaticSetterOrReadonlyProperties()); 337 ASSERT(!this->typeInfo().overridesGetCallData() || m_classInfo->methodTable.getCallData != &JSCell::getCallData); 338 } 339 340 Structure::~Structure() 341 { 342 if (typeInfo().structureIsImmortal()) 343 return; 344 Heap::heap(this)->structureIDTable().deallocateID(this, m_blob.structureID()); 345 } 346 347 void Structure::destroy(JSCell* cell) 348 { 349 static_cast<Structure*>(cell)->Structure::~Structure(); 350 } 351 352 Structure* Structure::create(PolyProtoTag, VM& vm, JSGlobalObject* globalObject, JSObject* prototype, const TypeInfo& typeInfo, const ClassInfo* classInfo, IndexingType indexingType, unsigned inlineCapacity) 353 { 354 Structure* result = create(vm, globalObject, prototype, typeInfo, classInfo, indexingType, inlineCapacity); 355 356 unsigned oldOutOfLineCapacity = result->outOfLineCapacity(); 357 result->addPropertyWithoutTransition( 358 vm, vm.propertyNames->builtinNames().polyProtoName(), static_cast<unsigned>(PropertyAttribute::DontEnum), 359 [&] (const GCSafeConcurrentJSLocker&, PropertyOffset offset, PropertyOffset newMaxOffset) { 360 RELEASE_ASSERT(Structure::outOfLineCapacity(newMaxOffset) == oldOutOfLineCapacity); 361 RELEASE_ASSERT(offset == knownPolyProtoOffset); 362 RELEASE_ASSERT(isInlineOffset(knownPolyProtoOffset)); 363 result->m_prototype.setWithoutWriteBarrier(JSValue()); 364 result->setMaxOffset(vm, newMaxOffset); 365 }); 366 367 return result; 368 } 369 370 bool Structure::isValidPrototype(JSValue prototype) 371 { 372 return prototype.isNull() || (prototype.isObject() && prototype.getObject()->mayBePrototype()); 373 } 374 375 void Structure::findStructuresAndMapForMaterialization(Vector<Structure*, 8>& structures, Structure*& structure, PropertyTable*& table) 376 { 377 ASSERT(structures.isEmpty()); 378 table = nullptr; 379 380 for (structure = this; structure; structure = structure->previousID()) { 381 structure->m_lock.lock(); 382 383 table = structure->propertyTableOrNull(); 384 if (table) { 385 // Leave the structure locked, so that the caller can do things to it atomically 386 // before it loses its property table. 387 return; 388 } 389 390 structures.append(structure); 391 structure->m_lock.unlock(); 392 } 393 394 ASSERT(!structure); 395 ASSERT(!table); 396 } 397 398 PropertyTable* Structure::materializePropertyTable(VM& vm, bool setPropertyTable) 399 { 400 ASSERT(structure(vm)->classInfo() == info()); 401 ASSERT(!protectPropertyTableWhileTransitioning()); 402 403 DeferGC deferGC(vm.heap); 404 405 Vector<Structure*, 8> structures; 406 Structure* structure; 407 PropertyTable* table; 408 409 findStructuresAndMapForMaterialization(structures, structure, table); 410 411 unsigned capacity = numberOfSlotsForMaxOffset(maxOffset(), m_inlineCapacity); 412 if (table) { 413 table = table->copy(vm, capacity); 414 structure->m_lock.unlock(); 415 } else 416 table = PropertyTable::create(vm, capacity); 417 418 // Must hold the lock on this structure, since we will be modifying this structure's 419 // property map. We don't want getConcurrently() to see the property map in a half-baked 420 // state. 421 GCSafeConcurrentJSLocker locker(m_lock, vm.heap); 422 if (setPropertyTable) 423 this->setPropertyTable(vm, table); 424 425 for (size_t i = structures.size(); i--;) { 426 structure = structures[i]; 427 if (!structure->m_transitionPropertyName) 428 continue; 429 switch (structure->transitionKind()) { 430 case TransitionKind::PropertyAddition: { 431 PropertyMapEntry entry(structure->m_transitionPropertyName.get(), structure->transitionOffset(), structure->transitionPropertyAttributes()); 432 auto nextOffset = table->nextOffset(structure->inlineCapacity()); 433 ASSERT_UNUSED(nextOffset, nextOffset == structure->transitionOffset()); 434 auto result = table->add(vm, entry); 435 ASSERT_UNUSED(result, result.second); 436 ASSERT_UNUSED(result, result.first.first->offset == nextOffset); 437 break; 438 } 439 case TransitionKind::PropertyDeletion: { 440 auto item = table->find(structure->m_transitionPropertyName.get()); 441 ASSERT(item.first); 442 table->remove(vm, item); 443 table->addDeletedOffset(structure->transitionOffset()); 444 break; 445 } 446 case TransitionKind::PropertyAttributeChange: { 447 PropertyMapEntry* entry = table->get(structure->m_transitionPropertyName.get()); 448 entry->attributes = structure->transitionPropertyAttributes(); 449 ASSERT(entry->offset == structure->transitionOffset()); 450 break; 451 } 452 default: 453 ASSERT_NOT_REACHED(); 454 break; 455 } 456 } 457 458 checkOffsetConsistency( 459 table, 460 [&] () { 461 dataLog("Detected in materializePropertyTable.\n"); 462 dataLog("Found structure = ", RawPointer(structure), "\n"); 463 dataLog("structures = "); 464 CommaPrinter comma; 465 for (Structure* structure : structures) 466 dataLog(comma, RawPointer(structure)); 467 dataLog("\n"); 468 }); 469 470 return table; 471 } 472 473 Structure* Structure::addPropertyTransitionToExistingStructureImpl(Structure* structure, UniquedStringImpl* uid, unsigned attributes, PropertyOffset& offset) 474 { 475 ASSERT(!structure->isDictionary()); 476 ASSERT(structure->isObject()); 477 478 offset = invalidOffset; 479 480 if (structure->hasBeenDictionary()) 481 return nullptr; 482 483 if (Structure* existingTransition = structure->m_transitionTable.get(uid, attributes, TransitionKind::PropertyAddition)) { 484 validateOffset(existingTransition->transitionOffset(), existingTransition->inlineCapacity()); 485 offset = existingTransition->transitionOffset(); 486 return existingTransition; 487 } 488 489 return nullptr; 490 } 491 492 Structure* Structure::addPropertyTransitionToExistingStructure(Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset) 493 { 494 ASSERT(!isCompilationThread()); 495 return addPropertyTransitionToExistingStructureImpl(structure, propertyName.uid(), attributes, offset); 496 } 497 498 Structure* Structure::addPropertyTransitionToExistingStructureConcurrently(Structure* structure, UniquedStringImpl* uid, unsigned attributes, PropertyOffset& offset) 499 { 500 ConcurrentJSLocker locker(structure->m_lock); 501 return addPropertyTransitionToExistingStructureImpl(structure, uid, attributes, offset); 502 } 503 504 bool Structure::holesMustForwardToPrototype(VM& vm, JSObject* base) const 505 { 506 ASSERT(base->structure(vm) == this); 507 508 if (this->mayInterceptIndexedAccesses()) 509 return true; 510 511 JSValue prototype = this->storedPrototype(base); 512 if (!prototype.isObject()) 513 return false; 514 JSObject* object = asObject(prototype); 515 516 while (true) { 517 Structure& structure = *object->structure(vm); 518 if (hasIndexedProperties(object->indexingType()) || structure.mayInterceptIndexedAccesses()) 519 return true; 520 prototype = structure.storedPrototype(object); 521 if (!prototype.isObject()) 522 return false; 523 object = asObject(prototype); 524 } 525 526 RELEASE_ASSERT_NOT_REACHED(); 527 return false; 528 } 529 530 Structure* Structure::addPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset) 531 { 532 Structure* newStructure = addPropertyTransitionToExistingStructure(structure, propertyName, attributes, offset); 533 if (newStructure) 534 return newStructure; 535 536 return addNewPropertyTransition( 537 vm, structure, propertyName, attributes, offset, PutPropertySlot::UnknownContext); 538 } 539 540 Structure* Structure::addNewPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset, PutPropertySlot::Context context, DeferredStructureTransitionWatchpointFire* deferred) 541 { 542 ASSERT(!structure->isDictionary()); 543 ASSERT(structure->isObject()); 544 ASSERT(!Structure::addPropertyTransitionToExistingStructure(structure, propertyName, attributes, offset)); 545 546 int maxTransitionLength; 547 if (context == PutPropertySlot::PutById) 548 maxTransitionLength = s_maxTransitionLengthForNonEvalPutById; 549 else 550 maxTransitionLength = s_maxTransitionLength; 551 if (structure->transitionCountEstimate() > maxTransitionLength) { 552 ASSERT(!isCopyOnWrite(structure->indexingMode())); 553 Structure* transition = toCacheableDictionaryTransition(vm, structure, deferred); 554 ASSERT(structure != transition); 555 offset = transition->add(vm, propertyName, attributes); 556 return transition; 557 } 558 559 Structure* transition = create(vm, structure, deferred); 560 561 transition->m_cachedPrototypeChain.setMayBeNull(vm, transition, structure->m_cachedPrototypeChain.get()); 562 563 // While we are adding the property, rematerializing the property table is super weird: we already 564 // have a m_transitionPropertyName and transitionPropertyAttributes but the m_transitionOffset is still wrong. If the 565 // materialization algorithm runs, it'll build a property table that already has the property but 566 // at a bogus offset. Rather than try to teach the materialization code how to create a table under 567 // those conditions, we just tell the GC not to blow the table away during this period of time. 568 // Holding the lock ensures that we either do this before the GC starts scanning the structure, in 569 // which case the GC will not blow the table away, or we do it after the GC already ran in which 570 // case all is well. If it wasn't for the lock, the GC would have TOCTOU: if could read 571 // protectPropertyTableWhileTransitioning before we set it to true, and then blow the table away after. 572 { 573 ConcurrentJSLocker locker(transition->m_lock); 574 transition->setProtectPropertyTableWhileTransitioning(true); 575 } 576 577 transition->m_blob.setIndexingModeIncludingHistory(structure->indexingModeIncludingHistory() & ~CopyOnWrite); 578 transition->m_transitionPropertyName = propertyName.uid(); 579 transition->setTransitionPropertyAttributes(attributes); 580 transition->setTransitionKind(TransitionKind::PropertyAddition); 581 transition->setPropertyTable(vm, structure->takePropertyTableOrCloneIfPinned(vm)); 582 transition->setMaxOffset(vm, structure->maxOffset()); 583 584 offset = transition->add(vm, propertyName, attributes); 585 transition->setTransitionOffset(vm, offset); 586 587 // Now that everything is fine with the new structure's bookkeeping, the GC is free to blow the 588 // table away if it wants. We can now rebuild it fine. 589 WTF::storeStoreFence(); 590 transition->setProtectPropertyTableWhileTransitioning(false); 591 592 checkOffset(transition->transitionOffset(), transition->inlineCapacity()); 593 if (!structure->hasBeenDictionary()) { 594 GCSafeConcurrentJSLocker locker(structure->m_lock, vm.heap); 595 structure->m_transitionTable.add(vm, transition); 596 } 597 transition->checkOffsetConsistency(); 598 structure->checkOffsetConsistency(); 599 return transition; 600 } 601 602 Structure* Structure::removePropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, PropertyOffset& offset, DeferredStructureTransitionWatchpointFire* deferred) 603 { 604 Structure* newStructure = removePropertyTransitionFromExistingStructure(structure, propertyName, offset); 605 if (newStructure) 606 return newStructure; 607 608 return removeNewPropertyTransition( 609 vm, structure, propertyName, offset, deferred); 610 } 611 612 Structure* Structure::removePropertyTransitionFromExistingStructureImpl(Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset) 613 { 614 ASSERT(!structure->isUncacheableDictionary()); 615 ASSERT(structure->isObject()); 616 617 offset = invalidOffset; 618 619 if (structure->hasBeenDictionary()) 620 return nullptr; 621 622 if (Structure* existingTransition = structure->m_transitionTable.get(propertyName.uid(), attributes, TransitionKind::PropertyDeletion)) { 623 validateOffset(existingTransition->transitionOffset(), existingTransition->inlineCapacity()); 624 offset = existingTransition->transitionOffset(); 625 return existingTransition; 626 } 627 628 return nullptr; 629 } 630 631 Structure* Structure::removePropertyTransitionFromExistingStructure(Structure* structure, PropertyName propertyName, PropertyOffset& offset) 632 { 633 ASSERT(!isCompilationThread()); 634 unsigned attributes = 0; 635 if (structure->getConcurrently(propertyName.uid(), attributes) == invalidOffset) 636 return nullptr; 637 return removePropertyTransitionFromExistingStructureImpl(structure, propertyName, attributes, offset); 638 } 639 640 Structure* Structure::removePropertyTransitionFromExistingStructureConcurrently(Structure* structure, PropertyName propertyName, PropertyOffset& offset) 641 { 642 unsigned attributes = 0; 643 if (structure->getConcurrently(propertyName.uid(), attributes) == invalidOffset) 644 return nullptr; 645 ConcurrentJSLocker locker(structure->m_lock); 646 return removePropertyTransitionFromExistingStructureImpl(structure, propertyName, attributes, offset); 647 } 648 649 Structure* Structure::removeNewPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, PropertyOffset& offset, DeferredStructureTransitionWatchpointFire* deferred) 650 { 651 ASSERT(!isCompilationThread()); 652 ASSERT(!structure->isUncacheableDictionary()); 653 ASSERT(structure->isObject()); 654 ASSERT(!Structure::removePropertyTransitionFromExistingStructure(structure, propertyName, offset)); 655 ASSERT(structure->getConcurrently(propertyName.uid()) != invalidOffset); 656 657 if (structure->transitionCountHasOverflowed()) { 658 ASSERT(!isCopyOnWrite(structure->indexingMode())); 659 Structure* transition = toUncacheableDictionaryTransition(vm, structure, deferred); 660 ASSERT(structure != transition); 661 offset = transition->remove(vm, propertyName); 662 return transition; 663 } 664 665 Structure* transition = create(vm, structure, deferred); 666 transition->m_cachedPrototypeChain.setMayBeNull(vm, transition, structure->m_cachedPrototypeChain.get()); 667 668 // While we are deleting the property, we need to make sure the table is not cleared. 669 { 670 ConcurrentJSLocker locker(transition->m_lock); 671 transition->setProtectPropertyTableWhileTransitioning(true); 672 } 673 674 transition->m_blob.setIndexingModeIncludingHistory(structure->indexingModeIncludingHistory() & ~CopyOnWrite); 675 transition->m_transitionPropertyName = propertyName.uid(); 676 transition->setTransitionKind(TransitionKind::PropertyDeletion); 677 transition->setPropertyTable(vm, structure->takePropertyTableOrCloneIfPinned(vm)); 678 transition->setMaxOffset(vm, structure->maxOffset()); 679 680 offset = transition->remove(vm, propertyName); 681 ASSERT(offset != invalidOffset); 682 transition->setTransitionOffset(vm, offset); 683 684 // Now that everything is fine with the new structure's bookkeeping, the GC is free to blow the 685 // table away if it wants. We can now rebuild it fine. 686 WTF::storeStoreFence(); 687 transition->setProtectPropertyTableWhileTransitioning(false); 688 689 checkOffset(transition->transitionOffset(), transition->inlineCapacity()); 690 if (!structure->hasBeenDictionary()) { 691 GCSafeConcurrentJSLocker locker(structure->m_lock, vm.heap); 692 structure->m_transitionTable.add(vm, transition); 693 } 694 transition->checkOffsetConsistency(); 695 structure->checkOffsetConsistency(); 696 return transition; 697 } 698 699 Structure* Structure::changePrototypeTransition(VM& vm, Structure* structure, JSValue prototype, DeferredStructureTransitionWatchpointFire& deferred) 700 { 701 ASSERT(isValidPrototype(prototype)); 702 703 DeferGC deferGC(vm.heap); 704 Structure* transition = create(vm, structure, &deferred); 705 706 transition->m_prototype.set(vm, transition, prototype); 707 708 PropertyTable* table = structure->copyPropertyTableForPinning(vm); 709 transition->pin(holdLock(transition->m_lock), vm, table); 710 transition->setMaxOffset(vm, structure->maxOffset()); 711 712 transition->checkOffsetConsistency(); 713 return transition; 714 } 715 716 Structure* Structure::attributeChangeTransitionToExistingStructure(Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset) 717 { 718 ASSERT(structure->isObject()); 719 720 offset = invalidOffset; 721 722 if (structure->hasBeenDictionary()) 723 return nullptr; 724 725 if (Structure* existingTransition = structure->m_transitionTable.get(propertyName.uid(), attributes, TransitionKind::PropertyAttributeChange)) { 726 validateOffset(existingTransition->transitionOffset(), existingTransition->inlineCapacity()); 727 offset = existingTransition->transitionOffset(); 728 return existingTransition; 729 } 730 731 return nullptr; 732 } 733 734 Structure* Structure::attributeChangeTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, DeferredStructureTransitionWatchpointFire* deferred) 735 { 736 if (structure->isUncacheableDictionary()) { 737 structure->attributeChangeWithoutTransition(vm, propertyName, attributes, [](const GCSafeConcurrentJSLocker&, PropertyOffset, PropertyOffset) { }); 738 structure->checkOffsetConsistency(); 739 return structure; 740 } 741 742 ASSERT(!structure->isUncacheableDictionary()); 743 PropertyOffset offset = invalidOffset; 744 if (Structure* existingTransition = attributeChangeTransitionToExistingStructure(structure, propertyName, attributes, offset)) { 745 validateOffset(existingTransition->transitionOffset(), existingTransition->inlineCapacity()); 746 existingTransition->checkOffsetConsistency(); 747 return existingTransition; 748 } 749 750 if (structure->transitionCountHasOverflowed()) { 751 ASSERT(!isCopyOnWrite(structure->indexingMode())); 752 Structure* transition = toUncacheableDictionaryTransition(vm, structure, deferred); 753 ASSERT(structure != transition); 754 transition->attributeChange(vm, propertyName, attributes); 755 return transition; 756 } 757 758 // Even if the current structure is dictionary, we should perform transition since this changes attributes of existing properties to keep 759 // structure still cacheable. 760 Structure* transition = create(vm, structure); 761 transition->m_cachedPrototypeChain.setMayBeNull(vm, transition, structure->m_cachedPrototypeChain.get()); 762 763 { 764 ConcurrentJSLocker locker(transition->m_lock); 765 transition->setProtectPropertyTableWhileTransitioning(true); 766 } 767 768 transition->m_blob.setIndexingModeIncludingHistory(structure->indexingModeIncludingHistory() & ~CopyOnWrite); 769 transition->m_transitionPropertyName = propertyName.uid(); 770 transition->setTransitionPropertyAttributes(attributes); 771 transition->setTransitionKind(TransitionKind::PropertyAttributeChange); 772 transition->setPropertyTable(vm, structure->takePropertyTableOrCloneIfPinned(vm)); 773 transition->setMaxOffset(vm, structure->maxOffset()); 774 775 offset = transition->attributeChange(vm, propertyName, attributes); 776 transition->setTransitionOffset(vm, offset); 777 778 // Now that everything is fine with the new structure's bookkeeping, the GC is free to blow the 779 // table away if it wants. We can now rebuild it fine. 780 WTF::storeStoreFence(); 781 transition->setProtectPropertyTableWhileTransitioning(false); 782 783 checkOffset(transition->transitionOffset(), transition->inlineCapacity()); 784 if (!structure->hasBeenDictionary()) { 785 GCSafeConcurrentJSLocker locker(structure->m_lock, vm.heap); 786 structure->m_transitionTable.add(vm, transition); 787 } 788 transition->checkOffsetConsistency(); 789 structure->checkOffsetConsistency(); 790 return transition; 791 } 792 793 Structure* Structure::toDictionaryTransition(VM& vm, Structure* structure, DictionaryKind kind, DeferredStructureTransitionWatchpointFire* deferred) 794 { 795 ASSERT(!structure->isUncacheableDictionary()); 796 DeferGC deferGC(vm.heap); 797 798 Structure* transition = create(vm, structure, deferred); 799 800 PropertyTable* table = structure->copyPropertyTableForPinning(vm); 801 transition->pin(holdLock(transition->m_lock), vm, table); 802 transition->setMaxOffset(vm, structure->maxOffset()); 803 transition->setDictionaryKind(kind); 804 transition->setHasBeenDictionary(true); 805 806 transition->checkOffsetConsistency(); 807 return transition; 808 } 809 810 Structure* Structure::toCacheableDictionaryTransition(VM& vm, Structure* structure, DeferredStructureTransitionWatchpointFire* deferred) 811 { 812 return toDictionaryTransition(vm, structure, CachedDictionaryKind, deferred); 813 } 814 815 Structure* Structure::toUncacheableDictionaryTransition(VM& vm, Structure* structure, DeferredStructureTransitionWatchpointFire* deferred) 816 { 817 return toDictionaryTransition(vm, structure, UncachedDictionaryKind, deferred); 818 } 819 820 Structure* Structure::sealTransition(VM& vm, Structure* structure) 821 { 822 return nonPropertyTransition(vm, structure, TransitionKind::Seal); 823 } 824 825 Structure* Structure::freezeTransition(VM& vm, Structure* structure) 826 { 827 return nonPropertyTransition(vm, structure, TransitionKind::Freeze); 828 } 829 830 Structure* Structure::preventExtensionsTransition(VM& vm, Structure* structure) 831 { 832 return nonPropertyTransition(vm, structure, TransitionKind::PreventExtensions); 833 } 834 835 PropertyTable* Structure::takePropertyTableOrCloneIfPinned(VM& vm) 836 { 837 // This must always return a property table. It can't return null. 838 PropertyTable* result = propertyTableOrNull(); 839 if (result) { 840 if (isPinnedPropertyTable()) 841 return result->copy(vm, result->size() + 1); 842 ConcurrentJSLocker locker(m_lock); 843 setPropertyTable(vm, nullptr); 844 return result; 845 } 846 bool setPropertyTable = false; 847 return materializePropertyTable(vm, setPropertyTable); 848 } 849 850 Structure* Structure::nonPropertyTransitionSlow(VM& vm, Structure* structure, TransitionKind transitionKind) 851 { 852 IndexingType indexingModeIncludingHistory = newIndexingType(structure->indexingModeIncludingHistory(), transitionKind); 853 854 if (!structure->isDictionary()) { 855 if (Structure* existingTransition = structure->m_transitionTable.get(nullptr, 0, transitionKind)) { 856 ASSERT(existingTransition->transitionKind() == transitionKind); 857 ASSERT(existingTransition->indexingModeIncludingHistory() == indexingModeIncludingHistory); 858 return existingTransition; 859 } 860 } 861 862 DeferGC deferGC(vm.heap); 863 864 Structure* transition = create(vm, structure); 865 transition->setTransitionKind(transitionKind); 866 transition->m_blob.setIndexingModeIncludingHistory(indexingModeIncludingHistory); 867 868 if (preventsExtensions(transitionKind)) 869 transition->setDidPreventExtensions(true); 870 871 if (setsDontDeleteOnAllProperties(transitionKind) 872 || setsReadOnlyOnNonAccessorProperties(transitionKind)) { 873 // We pin the property table on transitions that do wholesale editing of the property 874 // table, since our logic for walking the property transition chain to rematerialize the 875 // table doesn't know how to take into account such wholesale edits. 876 877 PropertyTable* table = structure->copyPropertyTableForPinning(vm); 878 transition->pinForCaching(holdLock(transition->m_lock), vm, table); 879 transition->setMaxOffset(vm, structure->maxOffset()); 880 881 table = transition->propertyTableOrNull(); 882 RELEASE_ASSERT(table); 883 for (auto& entry : *table) { 884 if (setsDontDeleteOnAllProperties(transitionKind)) 885 entry.attributes |= static_cast<unsigned>(PropertyAttribute::DontDelete); 886 if (setsReadOnlyOnNonAccessorProperties(transitionKind) && !(entry.attributes & PropertyAttribute::Accessor)) 887 entry.attributes |= static_cast<unsigned>(PropertyAttribute::ReadOnly); 888 } 889 } else { 890 transition->setPropertyTable(vm, structure->takePropertyTableOrCloneIfPinned(vm)); 891 transition->setMaxOffset(vm, structure->maxOffset()); 892 checkOffset(transition->maxOffset(), transition->inlineCapacity()); 893 } 894 895 if (setsReadOnlyOnNonAccessorProperties(transitionKind) 896 && !transition->propertyTableOrNull()->isEmpty()) 897 transition->setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true); 898 899 if (structure->isDictionary()) { 900 PropertyTable* table = transition->ensurePropertyTable(vm); 901 transition->pin(holdLock(transition->m_lock), vm, table); 902 } else { 903 auto locker = holdLock(structure->m_lock); 904 structure->m_transitionTable.add(vm, transition); 905 } 906 907 transition->checkOffsetConsistency(); 908 return transition; 909 } 910 911 // In future we may want to cache this property. 912 bool Structure::isSealed(VM& vm) 913 { 914 if (isStructureExtensible()) 915 return false; 916 917 PropertyTable* table = ensurePropertyTableIfNotEmpty(vm); 918 if (!table) 919 return true; 920 921 PropertyTable::iterator end = table->end(); 922 for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) { 923 if ((iter->attributes & PropertyAttribute::DontDelete) != static_cast<unsigned>(PropertyAttribute::DontDelete)) 924 return false; 925 } 926 return true; 927 } 928 929 // In future we may want to cache this property. 930 bool Structure::isFrozen(VM& vm) 931 { 932 if (isStructureExtensible()) 933 return false; 934 935 PropertyTable* table = ensurePropertyTableIfNotEmpty(vm); 936 if (!table) 937 return true; 938 939 PropertyTable::iterator end = table->end(); 940 for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) { 941 if (!(iter->attributes & PropertyAttribute::DontDelete)) 942 return false; 943 if (!(iter->attributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor))) 944 return false; 945 } 946 return true; 947 } 948 949 Structure* Structure::flattenDictionaryStructure(VM& vm, JSObject* object) 950 { 951 checkOffsetConsistency(); 952 ASSERT(isDictionary()); 953 ASSERT(object->structure(vm) == this); 954 955 GCSafeConcurrentJSLocker locker(m_lock, vm.heap); 956 957 object->setStructureIDDirectly(nuke(id())); 958 WTF::storeStoreFence(); 959 960 size_t beforeOutOfLineCapacity = this->outOfLineCapacity(); 961 if (isUncacheableDictionary()) { 962 PropertyTable* table = propertyTableOrNull(); 963 ASSERT(table); 964 965 size_t propertyCount = table->size(); 966 967 // Holds our values compacted by insertion order. 968 Vector<JSValue> values(propertyCount); 969 970 // Copies out our values from their hashed locations, compacting property table offsets as we go. 971 unsigned i = 0; 972 PropertyTable::iterator end = table->end(); 973 auto offset = invalidOffset; 974 for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter, ++i) { 975 values[i] = object->getDirect(iter->offset); 976 offset = iter->offset = offsetForPropertyNumber(i, m_inlineCapacity); 977 } 978 setMaxOffset(vm, offset); 979 ASSERT(transitionOffset() == invalidOffset); 980 981 // Copies in our values to their compacted locations. 982 for (unsigned i = 0; i < propertyCount; i++) 983 object->putDirect(vm, offsetForPropertyNumber(i, m_inlineCapacity), values[i]); 984 985 table->clearDeletedOffsets(); 986 987 // We need to zero our unused property space; otherwise the GC might see a 988 // stale pointer when we add properties in the future. 989 gcSafeZeroMemory( 990 object->inlineStorageUnsafe() + inlineSize(), 991 (inlineCapacity() - inlineSize()) * sizeof(EncodedJSValue)); 992 993 Butterfly* butterfly = object->butterfly(); 994 size_t preCapacity = butterfly->indexingHeader()->preCapacity(this); 995 void* base = butterfly->base(preCapacity, beforeOutOfLineCapacity); 996 void* startOfPropertyStorageSlots = reinterpret_cast<EncodedJSValue*>(base) + preCapacity; 997 gcSafeZeroMemory(static_cast<JSValue*>(startOfPropertyStorageSlots), (beforeOutOfLineCapacity - outOfLineSize()) * sizeof(EncodedJSValue)); 998 checkOffsetConsistency(); 999 } 1000 1001 setDictionaryKind(NoneDictionaryKind); 1002 setHasBeenFlattenedBefore(true); 1003 1004 size_t afterOutOfLineCapacity = this->outOfLineCapacity(); 1005 1006 if (object->butterfly() && beforeOutOfLineCapacity != afterOutOfLineCapacity) { 1007 ASSERT(beforeOutOfLineCapacity > afterOutOfLineCapacity); 1008 // If the object had a Butterfly but after flattening/compacting we no longer have need of it, 1009 // we need to zero it out because the collector depends on the Structure to know the size for copying. 1010 if (!afterOutOfLineCapacity && !this->hasIndexingHeader(object)) 1011 object->setButterfly(vm, nullptr); 1012 // If the object was down-sized to the point where the base of the Butterfly is no longer within the 1013 // first CopiedBlock::blockSize bytes, we'll get the wrong answer if we try to mask the base back to 1014 // the CopiedBlock header. To prevent this case we need to memmove the Butterfly down. 1015 else 1016 object->shiftButterflyAfterFlattening(locker, vm, this, afterOutOfLineCapacity); 1017 } 1018 1019 WTF::storeStoreFence(); 1020 object->setStructureIDDirectly(id()); 1021 1022 // We need to do a writebarrier here because the GC thread might be scanning the butterfly while 1023 // we are shuffling properties around. See: https://bugs.webkit.org/show_bug.cgi?id=166989 1024 vm.heap.writeBarrier(object); 1025 1026 return this; 1027 } 1028 1029 void Structure::pin(const AbstractLocker&, VM& vm, PropertyTable* table) 1030 { 1031 setIsPinnedPropertyTable(true); 1032 setPropertyTable(vm, table); 1033 clearPreviousID(); 1034 m_transitionPropertyName = nullptr; 1035 } 1036 1037 void Structure::pinForCaching(const AbstractLocker&, VM& vm, PropertyTable* table) 1038 { 1039 setIsPinnedPropertyTable(true); 1040 setPropertyTable(vm, table); 1041 m_transitionPropertyName = nullptr; 1042 } 1043 1044 void Structure::allocateRareData(VM& vm) 1045 { 1046 ASSERT(!hasRareData()); 1047 StructureRareData* rareData = StructureRareData::create(vm, previousID()); 1048 WTF::storeStoreFence(); 1049 m_previousOrRareData.set(vm, this, rareData); 1050 ASSERT(hasRareData()); 1051 } 1052 1053 WatchpointSet* Structure::ensurePropertyReplacementWatchpointSet(VM& vm, PropertyOffset offset) 1054 { 1055 ASSERT(!isUncacheableDictionary()); 1056 1057 // In some places it's convenient to call this with an invalid offset. So, we do the check here. 1058 if (!isValidOffset(offset)) 1059 return nullptr; 1060 1061 if (!hasRareData()) 1062 allocateRareData(vm); 1063 ConcurrentJSLocker locker(m_lock); 1064 StructureRareData* rareData = this->rareData(); 1065 if (!rareData->m_replacementWatchpointSets) { 1066 rareData->m_replacementWatchpointSets = 1067 makeUnique<StructureRareData::PropertyWatchpointMap>(); 1068 WTF::storeStoreFence(); 1069 } 1070 auto result = rareData->m_replacementWatchpointSets->add(offset, nullptr); 1071 if (result.isNewEntry) 1072 result.iterator->value = WatchpointSet::create(IsWatched); 1073 return result.iterator->value.get(); 1074 } 1075 1076 void Structure::startWatchingPropertyForReplacements(VM& vm, PropertyName propertyName) 1077 { 1078 ASSERT(!isUncacheableDictionary()); 1079 1080 startWatchingPropertyForReplacements(vm, get(vm, propertyName)); 1081 } 1082 1083 void Structure::didCachePropertyReplacement(VM& vm, PropertyOffset offset) 1084 { 1085 RELEASE_ASSERT(isValidOffset(offset)); 1086 ensurePropertyReplacementWatchpointSet(vm, offset)->fireAll(vm, "Did cache property replacement"); 1087 } 1088 1089 void Structure::startWatchingInternalProperties(VM& vm) 1090 { 1091 if (!isUncacheableDictionary()) { 1092 startWatchingPropertyForReplacements(vm, vm.propertyNames->toString); 1093 startWatchingPropertyForReplacements(vm, vm.propertyNames->valueOf); 1094 } 1095 setDidWatchInternalProperties(true); 1096 } 1097 1098 #if DUMP_PROPERTYMAP_STATS 1099 1100 PropertyMapHashTableStats* propertyMapHashTableStats = 0; 1101 1102 struct PropertyMapStatisticsExitLogger { 1103 PropertyMapStatisticsExitLogger(); 1104 ~PropertyMapStatisticsExitLogger(); 1105 }; 1106 1107 DEFINE_GLOBAL_FOR_LOGGING(PropertyMapStatisticsExitLogger, logger, ); 1108 1109 PropertyMapStatisticsExitLogger::PropertyMapStatisticsExitLogger() 1110 { 1111 propertyMapHashTableStats = adoptPtr(new PropertyMapHashTableStats()).leakPtr(); 1112 } 1113 1114 PropertyMapStatisticsExitLogger::~PropertyMapStatisticsExitLogger() 1115 { 1116 unsigned finds = propertyMapHashTableStats->numFinds; 1117 unsigned collisions = propertyMapHashTableStats->numCollisions; 1118 dataLogF("\nJSC::PropertyMap statistics for process %d\n\n", getCurrentProcessID()); 1119 dataLogF("%d finds\n", finds); 1120 dataLogF("%d collisions (%.1f%%)\n", collisions, 100.0 * collisions / finds); 1121 dataLogF("%d lookups\n", propertyMapHashTableStats->numLookups.load()); 1122 dataLogF("%d lookup probings\n", propertyMapHashTableStats->numLookupProbing.load()); 1123 dataLogF("%d adds\n", propertyMapHashTableStats->numAdds.load()); 1124 dataLogF("%d removes\n", propertyMapHashTableStats->numRemoves.load()); 1125 dataLogF("%d rehashes\n", propertyMapHashTableStats->numRehashes.load()); 1126 dataLogF("%d reinserts\n", propertyMapHashTableStats->numReinserts.load()); 1127 } 1128 1129 #endif 1130 1131 PropertyTable* Structure::copyPropertyTableForPinning(VM& vm) 1132 { 1133 if (PropertyTable* table = propertyTableOrNull()) 1134 return PropertyTable::clone(vm, *table); 1135 bool setPropertyTable = false; 1136 return materializePropertyTable(vm, setPropertyTable); 1137 } 1138 1139 PropertyOffset Structure::getConcurrently(UniquedStringImpl* uid, unsigned& attributes) 1140 { 1141 PropertyOffset result = invalidOffset; 1142 1143 forEachPropertyConcurrently( 1144 [&] (const PropertyMapEntry& candidate) -> bool { 1145 if (candidate.key != uid) 1146 return true; 1147 1148 result = candidate.offset; 1149 attributes = candidate.attributes; 1150 return false; 1151 }); 1152 1153 return result; 1154 } 1155 1156 Vector<PropertyMapEntry> Structure::getPropertiesConcurrently() 1157 { 1158 Vector<PropertyMapEntry> result; 1159 1160 forEachPropertyConcurrently( 1161 [&] (const PropertyMapEntry& entry) -> bool { 1162 result.append(entry); 1163 return true; 1164 }); 1165 1166 return result; 1167 } 1168 1169 PropertyOffset Structure::add(VM& vm, PropertyName propertyName, unsigned attributes) 1170 { 1171 return add<ShouldPin::No>( 1172 vm, propertyName, attributes, 1173 [this, &vm] (const GCSafeConcurrentJSLocker&, PropertyOffset, PropertyOffset newMaxOffset) { 1174 setMaxOffset(vm, newMaxOffset); 1175 }); 1176 } 1177 1178 PropertyOffset Structure::remove(VM& vm, PropertyName propertyName) 1179 { 1180 return remove<ShouldPin::No>(vm, propertyName, [this, &vm] (const GCSafeConcurrentJSLocker&, PropertyOffset, PropertyOffset newMaxOffset) { 1181 setMaxOffset(vm, newMaxOffset); 1182 }); 1183 } 1184 1185 PropertyOffset Structure::attributeChange(VM& vm, PropertyName propertyName, unsigned attributes) 1186 { 1187 return attributeChange<ShouldPin::No>( 1188 vm, propertyName, attributes, 1189 [this, &vm] (const GCSafeConcurrentJSLocker&, PropertyOffset, PropertyOffset newMaxOffset) { 1190 setMaxOffset(vm, newMaxOffset); 1191 }); 1192 } 1193 1194 void Structure::getPropertyNamesFromStructure(VM& vm, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode) 1195 { 1196 PropertyTable* table = ensurePropertyTableIfNotEmpty(vm); 1197 if (!table) 1198 return; 1199 1200 bool knownUnique = propertyNames.canAddKnownUniqueForStructure(); 1201 bool foundSymbol = false; 1202 1203 auto checkDontEnumAndAdd = [&](PropertyTable::iterator iter) { 1204 if (mode == DontEnumPropertiesMode::Include || !(iter->attributes & PropertyAttribute::DontEnum)) { 1205 if (knownUnique) 1206 propertyNames.addUnchecked(iter->key); 1207 else 1208 propertyNames.add(iter->key); 1209 } 1210 }; 1211 1212 PropertyTable::iterator end = table->end(); 1213 for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) { 1214 ASSERT(!isQuickPropertyAccessAllowedForEnumeration() || !(iter->attributes & PropertyAttribute::DontEnum)); 1215 ASSERT(!isQuickPropertyAccessAllowedForEnumeration() || !iter->key->isSymbol()); 1216 if (iter->key->isSymbol()) { 1217 foundSymbol = true; 1218 if (propertyNames.propertyNameMode() != PropertyNameMode::Symbols) 1219 continue; 1220 } 1221 checkDontEnumAndAdd(iter); 1222 } 1223 1224 if (foundSymbol && propertyNames.propertyNameMode() == PropertyNameMode::StringsAndSymbols) { 1225 // To ensure the order defined in the spec, we append symbols at the last elements of keys. 1226 // https://tc39.es/ecma262/#sec-ordinaryownpropertykeys 1227 for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) { 1228 if (iter->key->isSymbol()) 1229 checkDontEnumAndAdd(iter); 1230 } 1231 } 1232 } 1233 1234 void StructureFireDetail::dump(PrintStream& out) const 1235 { 1236 out.print("Structure transition from ", *m_structure); 1237 } 1238 1239 DeferredStructureTransitionWatchpointFire::DeferredStructureTransitionWatchpointFire(VM& vm, Structure* structure) 1240 : DeferredWatchpointFire(vm) 1241 , m_structure(structure) 1242 { 1243 } 1244 1245 DeferredStructureTransitionWatchpointFire::~DeferredStructureTransitionWatchpointFire() 1246 { 1247 fireAll(); 1248 } 1249 1250 void DeferredStructureTransitionWatchpointFire::dump(PrintStream& out) const 1251 { 1252 out.print("Structure transition from ", *m_structure); 1253 } 1254 1255 void Structure::didTransitionFromThisStructure(DeferredStructureTransitionWatchpointFire* deferred) const 1256 { 1257 // If the structure is being watched, and this is the kind of structure that the DFG would 1258 // like to watch, then make sure to note for all future versions of this structure that it's 1259 // unwise to watch it. 1260 if (m_transitionWatchpointSet.isBeingWatched()) 1261 const_cast<Structure*>(this)->setTransitionWatchpointIsLikelyToBeFired(true); 1262 1263 if (deferred) { 1264 ASSERT(deferred->structure() == this); 1265 m_transitionWatchpointSet.fireAll(vm(), deferred); 1266 } else 1267 m_transitionWatchpointSet.fireAll(vm(), StructureFireDetail(this)); 1268 } 1269 1270 void Structure::visitChildren(JSCell* cell, SlotVisitor& visitor) 1271 { 1272 Structure* thisObject = jsCast<Structure*>(cell); 1273 ASSERT_GC_OBJECT_INHERITS(thisObject, info()); 1274 1275 Base::visitChildren(thisObject, visitor); 1276 1277 ConcurrentJSLocker locker(thisObject->m_lock); 1278 1279 visitor.append(thisObject->m_globalObject); 1280 if (!thisObject->isObject()) 1281 thisObject->m_cachedPrototypeChain.clear(); 1282 else { 1283 visitor.append(thisObject->m_prototype); 1284 visitor.append(thisObject->m_cachedPrototypeChain); 1285 } 1286 visitor.append(thisObject->m_previousOrRareData); 1287 1288 if (thisObject->isPinnedPropertyTable() || thisObject->protectPropertyTableWhileTransitioning()) { 1289 // NOTE: This can interleave in pin(), in which case it may see a null property table. 1290 // That's fine, because then the barrier will fire and we will scan this again. 1291 visitor.append(thisObject->m_propertyTableUnsafe); 1292 } else if (visitor.isAnalyzingHeap()) 1293 visitor.append(thisObject->m_propertyTableUnsafe); 1294 else if (thisObject->m_propertyTableUnsafe) 1295 thisObject->m_propertyTableUnsafe.clear(); 1296 } 1297 1298 bool Structure::isCheapDuringGC(VM& vm) 1299 { 1300 // FIXME: We could make this even safer by returning false if this structure's property table 1301 // has any large property names. 1302 // https://bugs.webkit.org/show_bug.cgi?id=157334 1303 1304 return (!m_globalObject || vm.heap.isMarked(m_globalObject.get())) 1305 && (hasPolyProto() || !storedPrototypeObject() || vm.heap.isMarked(storedPrototypeObject())); 1306 } 1307 1308 bool Structure::markIfCheap(SlotVisitor& visitor) 1309 { 1310 VM& vm = visitor.vm(); 1311 if (!isCheapDuringGC(vm)) 1312 return vm.heap.isMarked(this); 1313 1314 visitor.appendUnbarriered(this); 1315 return true; 1316 } 1317 1318 Ref<StructureShape> Structure::toStructureShape(JSValue value, bool& sawPolyProtoStructure) 1319 { 1320 Ref<StructureShape> baseShape = StructureShape::create(); 1321 RefPtr<StructureShape> curShape = baseShape.ptr(); 1322 Structure* curStructure = this; 1323 JSValue curValue = value; 1324 sawPolyProtoStructure = false; 1325 while (curStructure) { 1326 sawPolyProtoStructure |= curStructure->hasPolyProto(); 1327 curStructure->forEachPropertyConcurrently( 1328 [&] (const PropertyMapEntry& entry) -> bool { 1329 if (!PropertyName(entry.key).isPrivateName()) 1330 curShape->addProperty(*entry.key); 1331 return true; 1332 }); 1333 1334 if (JSObject* curObject = curValue.getObject()) 1335 curShape->setConstructorName(JSObject::calculatedClassName(curObject)); 1336 else 1337 curShape->setConstructorName(curStructure->classInfo()->className); 1338 1339 if (curStructure->isDictionary()) 1340 curShape->enterDictionaryMode(); 1341 1342 curShape->markAsFinal(); 1343 1344 if (!curValue.isObject()) 1345 break; 1346 1347 JSObject* object = asObject(curValue); 1348 JSObject* prototypeObject = object->structure()->storedPrototypeObject(object); 1349 if (!prototypeObject) 1350 break; 1351 1352 auto newShape = StructureShape::create(); 1353 curShape->setProto(newShape.copyRef()); 1354 curShape = WTFMove(newShape); 1355 curValue = prototypeObject; 1356 curStructure = prototypeObject->structure(); 1357 } 1358 1359 return baseShape; 1360 } 1361 1362 void Structure::dump(PrintStream& out) const 1363 { 1364 out.print(RawPointer(this), ":[", RawPointer(reinterpret_cast<void*>(id())), ", ", classInfo()->className, ", {"); 1365 1366 CommaPrinter comma; 1367 1368 const_cast<Structure*>(this)->forEachPropertyConcurrently( 1369 [&] (const PropertyMapEntry& entry) -> bool { 1370 out.print(comma, entry.key, ":", static_cast<int>(entry.offset)); 1371 return true; 1372 }); 1373 1374 out.print("}, ", IndexingTypeDump(indexingMode())); 1375 1376 if (hasPolyProto()) 1377 out.print(", PolyProto offset:", knownPolyProtoOffset); 1378 else if (m_prototype.get().isCell()) 1379 out.print(", Proto:", RawPointer(m_prototype.get().asCell())); 1380 1381 switch (dictionaryKind()) { 1382 case NoneDictionaryKind: 1383 if (hasBeenDictionary()) 1384 out.print(", Has been dictionary"); 1385 break; 1386 case CachedDictionaryKind: 1387 out.print(", Dictionary"); 1388 break; 1389 case UncachedDictionaryKind: 1390 out.print(", UncacheableDictionary"); 1391 break; 1392 } 1393 1394 if (transitionWatchpointSetIsStillValid()) 1395 out.print(", Leaf"); 1396 else if (transitionWatchpointIsLikelyToBeFired()) 1397 out.print(", Shady leaf"); 1398 1399 if (transitionWatchpointSet().isBeingWatched()) 1400 out.print(" (Watched)"); 1401 1402 out.print("]"); 1403 } 1404 1405 void Structure::dumpInContext(PrintStream& out, DumpContext* context) const 1406 { 1407 if (context) 1408 context->structures.dumpBrief(this, out); 1409 else 1410 dump(out); 1411 } 1412 1413 void Structure::dumpBrief(PrintStream& out, const CString& string) const 1414 { 1415 out.print("%", string, ":", classInfo()->className); 1416 if (indexingType() & IndexingShapeMask) 1417 out.print(",", IndexingTypeDump(indexingType())); 1418 } 1419 1420 void Structure::dumpContextHeader(PrintStream& out) 1421 { 1422 out.print("Structures:"); 1423 } 1424 1425 bool ClassInfo::hasStaticSetterOrReadonlyProperties() const 1426 { 1427 for (const ClassInfo* ci = this; ci; ci = ci->parentClass) { 1428 if (const HashTable* table = ci->staticPropHashTable) { 1429 if (table->hasSetterOrReadonlyProperties) 1430 return true; 1431 } 1432 } 1433 return false; 1434 } 1435 1436 void Structure::setCachedPropertyNameEnumerator(VM& vm, JSPropertyNameEnumerator* enumerator) 1437 { 1438 ASSERT(!isDictionary()); 1439 if (!hasRareData()) 1440 allocateRareData(vm); 1441 rareData()->setCachedPropertyNameEnumerator(vm, enumerator); 1442 } 1443 1444 JSPropertyNameEnumerator* Structure::cachedPropertyNameEnumerator() const 1445 { 1446 if (!hasRareData()) 1447 return nullptr; 1448 return rareData()->cachedPropertyNameEnumerator(); 1449 } 1450 1451 bool Structure::canCachePropertyNameEnumerator(VM& vm) const 1452 { 1453 if (!this->canCacheOwnPropertyNames()) 1454 return false; 1455 1456 StructureChain* structureChain = m_cachedPrototypeChain.get(); 1457 ASSERT(structureChain); 1458 StructureID* currentStructureID = structureChain->head(); 1459 while (true) { 1460 StructureID structureID = *currentStructureID; 1461 if (!structureID) 1462 return true; 1463 Structure* structure = vm.getStructure(structureID); 1464 if (!structure->canCacheOwnPropertyNames()) 1465 return false; 1466 currentStructureID++; 1467 } 1468 1469 ASSERT_NOT_REACHED(); 1470 return true; 1471 } 1472 1473 bool Structure::canAccessPropertiesQuicklyForEnumeration() const 1474 { 1475 if (!isQuickPropertyAccessAllowedForEnumeration()) 1476 return false; 1477 if (hasGetterSetterProperties()) 1478 return false; 1479 if (hasCustomGetterSetterProperties()) 1480 return false; 1481 if (isUncacheableDictionary()) 1482 return false; 1483 if (typeInfo().overridesGetOwnPropertyNames()) 1484 return false; 1485 return true; 1486 } 1487 1488 auto Structure::findPropertyHashEntry(PropertyName propertyName) const -> Optional<PropertyHashEntry> 1489 { 1490 for (const ClassInfo* info = classInfo(); info; info = info->parentClass) { 1491 if (const HashTable* propHashTable = info->staticPropHashTable) { 1492 if (const HashTableValue* entry = propHashTable->entry(propertyName)) 1493 return PropertyHashEntry { propHashTable, entry }; 1494 } 1495 } 1496 return WTF::nullopt; 1497 } 1498 1499 } // namespace JSC