1/*
2 * Copyright (C) 2013-2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#pragma once
27
28#include "JSArrayBufferView.h"
29#include "JSCJSValueInlines.h"
30#include "JSGlobalObject.h"
31#include "PropertyMapHashTable.h"
32#include "Structure.h"
33#include "StructureChain.h"
34#include "StructureRareDataInlines.h"
35#include <wtf/Threading.h>
36
37namespace JSC {
38
39inline Structure* Structure::create(VM& vm, JSGlobalObject* globalObject, JSValue prototype, const TypeInfo& typeInfo, const ClassInfo* classInfo, IndexingType indexingType, unsigned inlineCapacity)
40{
41 ASSERT(vm.structureStructure);
42 ASSERT(classInfo);
43 if (auto* object = prototype.getObject()) {
44 ASSERT(!object->anyObjectInChainMayInterceptIndexedAccesses(vm) || hasSlowPutArrayStorage(indexingType) || !hasIndexedProperties(indexingType));
45 object->didBecomePrototype();
46 }
47
48 Structure* structure = new (NotNull, allocateCell<Structure>(vm.heap)) Structure(vm, globalObject, prototype, typeInfo, classInfo, indexingType, inlineCapacity);
49 structure->finishCreation(vm);
50 return structure;
51}
52
53inline Structure* Structure::createStructure(VM& vm)
54{
55 ASSERT(!vm.structureStructure);
56 Structure* structure = new (NotNull, allocateCell<Structure>(vm.heap)) Structure(vm);
57 structure->finishCreation(vm, CreatingEarlyCell);
58 return structure;
59}
60
61inline Structure* Structure::create(VM& vm, Structure* previous, DeferredStructureTransitionWatchpointFire* deferred)
62{
63 ASSERT(vm.structureStructure);
64 Structure* newStructure = new (NotNull, allocateCell<Structure>(vm.heap)) Structure(vm, previous, deferred);
65 newStructure->finishCreation(vm, previous);
66 return newStructure;
67}
68
69inline bool Structure::mayInterceptIndexedAccesses() const
70{
71 if (indexingModeIncludingHistory() & MayHaveIndexedAccessors)
72 return true;
73
74 // Consider a scenario where object O (of global G1)'s prototype is set to A
75 // (of global G2), and G2 is already having a bad time. If an object B with
76 // indexed accessors is then set as the prototype of A:
77 // O -> A -> B
78 // Then, O should be converted to SlowPutArrayStorage (because it now has an
79 // object with indexed accessors in its prototype chain). But it won't be
80 // converted because this conversion is done by JSGlobalObject::haveAbadTime(),
81 // but G2 is already having a bad time. We solve this by conservatively
82 // treating A as potentially having indexed accessors if its global is already
83 // having a bad time. Hence, when A is set as O's prototype, O will be
84 // converted to SlowPutArrayStorage.
85
86 JSGlobalObject* globalObject = this->globalObject();
87 if (!globalObject)
88 return false;
89 return globalObject->isHavingABadTime();
90}
91
92inline JSObject* Structure::storedPrototypeObject() const
93{
94 ASSERT(hasMonoProto());
95 JSValue value = m_prototype.get();
96 if (value.isNull())
97 return nullptr;
98 return asObject(value);
99}
100
101inline Structure* Structure::storedPrototypeStructure() const
102{
103 ASSERT(hasMonoProto());
104 JSObject* object = storedPrototypeObject();
105 if (!object)
106 return nullptr;
107 return object->structure();
108}
109
110ALWAYS_INLINE JSValue Structure::storedPrototype(const JSObject* object) const
111{
112 ASSERT(isCompilationThread() || Thread::mayBeGCThread() || object->structure() == this);
113 if (hasMonoProto())
114 return storedPrototype();
115 return object->getDirect(knownPolyProtoOffset);
116}
117
118ALWAYS_INLINE JSObject* Structure::storedPrototypeObject(const JSObject* object) const
119{
120 ASSERT(isCompilationThread() || Thread::mayBeGCThread() || object->structure() == this);
121 if (hasMonoProto())
122 return storedPrototypeObject();
123 JSValue proto = object->getDirect(knownPolyProtoOffset);
124 if (proto.isNull())
125 return nullptr;
126 return asObject(proto);
127}
128
129ALWAYS_INLINE Structure* Structure::storedPrototypeStructure(const JSObject* object) const
130{
131 if (JSObject* proto = storedPrototypeObject(object))
132 return proto->structure();
133 return nullptr;
134}
135
136ALWAYS_INLINE PropertyOffset Structure::get(VM& vm, PropertyName propertyName)
137{
138 unsigned attributes;
139 return get(vm, propertyName, attributes);
140}
141
142ALWAYS_INLINE PropertyOffset Structure::get(VM& vm, PropertyName propertyName, unsigned& attributes)
143{
144 ASSERT(!isCompilationThread());
145 ASSERT(structure(vm)->classInfo() == info());
146
147 PropertyTable* propertyTable = ensurePropertyTableIfNotEmpty(vm);
148 if (!propertyTable)
149 return invalidOffset;
150
151 PropertyMapEntry* entry = propertyTable->get(propertyName.uid());
152 if (!entry)
153 return invalidOffset;
154
155 attributes = entry->attributes;
156 return entry->offset;
157}
158
159template<typename Functor>
160void Structure::forEachPropertyConcurrently(const Functor& functor)
161{
162 Vector<Structure*, 8> structures;
163 Structure* structure;
164 PropertyTable* table;
165
166 findStructuresAndMapForMaterialization(structures, structure, table);
167
168 if (table) {
169 for (auto& entry : *table) {
170 if (!functor(entry)) {
171 structure->m_lock.unlock();
172 return;
173 }
174 }
175 structure->m_lock.unlock();
176 }
177
178 for (unsigned i = structures.size(); i--;) {
179 structure = structures[i];
180 if (!structure->m_nameInPrevious)
181 continue;
182
183 if (!functor(PropertyMapEntry(structure->m_nameInPrevious.get(), structure->m_offset, structure->attributesInPrevious())))
184 return;
185 }
186}
187
188template<typename Functor>
189void Structure::forEachProperty(VM& vm, const Functor& functor)
190{
191 if (PropertyTable* table = ensurePropertyTableIfNotEmpty(vm)) {
192 for (auto& entry : *table) {
193 if (!functor(entry))
194 return;
195 }
196 }
197}
198
199inline PropertyOffset Structure::getConcurrently(UniquedStringImpl* uid)
200{
201 unsigned attributesIgnored;
202 return getConcurrently(uid, attributesIgnored);
203}
204
205inline bool Structure::hasIndexingHeader(const JSCell* cell) const
206{
207 if (hasIndexedProperties(indexingType()))
208 return true;
209
210 if (!isTypedView(typedArrayTypeForType(m_blob.type())))
211 return false;
212
213 return jsCast<const JSArrayBufferView*>(cell)->mode() == WastefulTypedArray;
214}
215
216inline bool Structure::masqueradesAsUndefined(JSGlobalObject* lexicalGlobalObject)
217{
218 return typeInfo().masqueradesAsUndefined() && globalObject() == lexicalGlobalObject;
219}
220
221inline bool Structure::transitivelyTransitionedFrom(Structure* structureToFind)
222{
223 for (Structure* current = this; current; current = current->previousID()) {
224 if (current == structureToFind)
225 return true;
226 }
227 return false;
228}
229
230inline void Structure::setCachedOwnKeys(VM& vm, JSImmutableButterfly* ownKeys)
231{
232 ensureRareData(vm)->setCachedOwnKeys(vm, ownKeys);
233}
234
235inline JSImmutableButterfly* Structure::cachedOwnKeys() const
236{
237 if (!hasRareData())
238 return nullptr;
239 return rareData()->cachedOwnKeys();
240}
241
242inline JSImmutableButterfly* Structure::cachedOwnKeysIgnoringSentinel() const
243{
244 if (!hasRareData())
245 return nullptr;
246 return rareData()->cachedOwnKeysIgnoringSentinel();
247}
248
249inline bool Structure::canCacheOwnKeys() const
250{
251 if (isDictionary())
252 return false;
253 if (hasIndexedProperties(indexingType()))
254 return false;
255 if (typeInfo().overridesGetPropertyNames())
256 return false;
257 return true;
258}
259
260ALWAYS_INLINE JSValue prototypeForLookupPrimitiveImpl(JSGlobalObject* globalObject, const Structure* structure)
261{
262 ASSERT(!structure->isObject());
263
264 if (structure->typeInfo().type() == StringType)
265 return globalObject->stringPrototype();
266
267 if (structure->typeInfo().type() == BigIntType)
268 return globalObject->bigIntPrototype();
269
270 ASSERT(structure->typeInfo().type() == SymbolType);
271 return globalObject->symbolPrototype();
272}
273
274inline JSValue Structure::prototypeForLookup(JSGlobalObject* globalObject) const
275{
276 ASSERT(hasMonoProto());
277 if (isObject())
278 return storedPrototype();
279 return prototypeForLookupPrimitiveImpl(globalObject, this);
280}
281
282inline JSValue Structure::prototypeForLookup(JSGlobalObject* globalObject, JSCell* base) const
283{
284 ASSERT(base->structure() == this);
285 if (isObject())
286 return storedPrototype(asObject(base));
287 return prototypeForLookupPrimitiveImpl(globalObject, this);
288}
289
290inline StructureChain* Structure::prototypeChain(VM& vm, JSGlobalObject* globalObject, JSObject* base) const
291{
292 ASSERT(base->structure(vm) == this);
293 // We cache our prototype chain so our clients can share it.
294 if (!isValid(globalObject, m_cachedPrototypeChain.get(), base)) {
295 JSValue prototype = prototypeForLookup(globalObject, base);
296 m_cachedPrototypeChain.set(vm, this, StructureChain::create(vm, prototype.isNull() ? nullptr : asObject(prototype)));
297 }
298 return m_cachedPrototypeChain.get();
299}
300
301inline StructureChain* Structure::prototypeChain(JSGlobalObject* globalObject, JSObject* base) const
302{
303 return prototypeChain(globalObject->vm(), globalObject, base);
304}
305
306inline bool Structure::isValid(JSGlobalObject* globalObject, StructureChain* cachedPrototypeChain, JSObject* base) const
307{
308 if (!cachedPrototypeChain)
309 return false;
310
311 VM& vm = globalObject->vm();
312 JSValue prototype = prototypeForLookup(globalObject, base);
313 WriteBarrier<Structure>* cachedStructure = cachedPrototypeChain->head();
314 while (*cachedStructure && !prototype.isNull()) {
315 if (asObject(prototype)->structure(vm) != cachedStructure->get())
316 return false;
317 ++cachedStructure;
318 prototype = asObject(prototype)->getPrototypeDirect(vm);
319 }
320 return prototype.isNull() && !*cachedStructure;
321}
322
323inline void Structure::didReplaceProperty(PropertyOffset offset)
324{
325 if (LIKELY(!hasRareData()))
326 return;
327 StructureRareData::PropertyWatchpointMap* map = rareData()->m_replacementWatchpointSets.get();
328 if (LIKELY(!map))
329 return;
330 WatchpointSet* set = map->get(offset);
331 if (LIKELY(!set))
332 return;
333 set->fireAll(vm(), "Property did get replaced");
334}
335
336inline WatchpointSet* Structure::propertyReplacementWatchpointSet(PropertyOffset offset)
337{
338 ConcurrentJSLocker locker(m_lock);
339 if (!hasRareData())
340 return nullptr;
341 WTF::loadLoadFence();
342 StructureRareData::PropertyWatchpointMap* map = rareData()->m_replacementWatchpointSets.get();
343 if (!map)
344 return nullptr;
345 return map->get(offset);
346}
347
348template<typename DetailsFunc>
349ALWAYS_INLINE bool Structure::checkOffsetConsistency(PropertyTable* propertyTable, const DetailsFunc& detailsFunc) const
350{
351 // We cannot reliably assert things about the property table in the concurrent
352 // compilation thread. It is possible for the table to be stolen and then have
353 // things added to it, which leads to the offsets being all messed up. We could
354 // get around this by grabbing a lock here, but I think that would be overkill.
355 if (isCompilationThread())
356 return true;
357
358 unsigned totalSize = propertyTable->propertyStorageSize();
359 unsigned inlineOverflowAccordingToTotalSize = totalSize < m_inlineCapacity ? 0 : totalSize - m_inlineCapacity;
360
361 auto fail = [&] (const char* description) {
362 dataLog("Detected offset inconsistency: ", description, "!\n");
363 dataLog("this = ", RawPointer(this), "\n");
364 dataLog("m_offset = ", m_offset, "\n");
365 dataLog("m_inlineCapacity = ", m_inlineCapacity, "\n");
366 dataLog("propertyTable = ", RawPointer(propertyTable), "\n");
367 dataLog("numberOfSlotsForLastOffset = ", numberOfSlotsForLastOffset(m_offset, m_inlineCapacity), "\n");
368 dataLog("totalSize = ", totalSize, "\n");
369 dataLog("inlineOverflowAccordingToTotalSize = ", inlineOverflowAccordingToTotalSize, "\n");
370 dataLog("numberOfOutOfLineSlotsForLastOffset = ", numberOfOutOfLineSlotsForLastOffset(m_offset), "\n");
371 detailsFunc();
372 UNREACHABLE_FOR_PLATFORM();
373 };
374
375 if (numberOfSlotsForLastOffset(m_offset, m_inlineCapacity) != totalSize)
376 fail("numberOfSlotsForLastOffset doesn't match totalSize");
377 if (inlineOverflowAccordingToTotalSize != numberOfOutOfLineSlotsForLastOffset(m_offset))
378 fail("inlineOverflowAccordingToTotalSize doesn't match numberOfOutOfLineSlotsForLastOffset");
379
380 return true;
381}
382
383ALWAYS_INLINE bool Structure::checkOffsetConsistency() const
384{
385 PropertyTable* propertyTable = propertyTableOrNull();
386
387 if (!propertyTable) {
388 ASSERT(!isPinnedPropertyTable());
389 return true;
390 }
391
392 // We cannot reliably assert things about the property table in the concurrent
393 // compilation thread. It is possible for the table to be stolen and then have
394 // things added to it, which leads to the offsets being all messed up. We could
395 // get around this by grabbing a lock here, but I think that would be overkill.
396 if (isCompilationThread())
397 return true;
398
399 return checkOffsetConsistency(propertyTable, [] () { });
400}
401
402inline void Structure::checkConsistency()
403{
404 checkOffsetConsistency();
405}
406
407inline size_t nextOutOfLineStorageCapacity(size_t currentCapacity)
408{
409 if (!currentCapacity)
410 return initialOutOfLineCapacity;
411 return currentCapacity * outOfLineGrowthFactor;
412}
413
414inline void Structure::setObjectToStringValue(JSGlobalObject* globalObject, VM& vm, JSString* value, PropertySlot toStringTagSymbolSlot)
415{
416 if (!hasRareData())
417 allocateRareData(vm);
418 rareData()->setObjectToStringValue(globalObject, vm, this, value, toStringTagSymbolSlot);
419}
420
421template<Structure::ShouldPin shouldPin, typename Func>
422inline PropertyOffset Structure::add(VM& vm, PropertyName propertyName, unsigned attributes, const Func& func)
423{
424 PropertyTable* table = ensurePropertyTable(vm);
425
426 GCSafeConcurrentJSLocker locker(m_lock, vm.heap);
427
428 switch (shouldPin) {
429 case ShouldPin::Yes:
430 pin(locker, vm, table);
431 break;
432 case ShouldPin::No:
433 setPropertyTable(vm, table);
434 break;
435 }
436
437 ASSERT(!JSC::isValidOffset(get(vm, propertyName)));
438
439 checkConsistency();
440 if (attributes & PropertyAttribute::DontEnum || propertyName.isSymbol())
441 setIsQuickPropertyAccessAllowedForEnumeration(false);
442 if (propertyName == vm.propertyNames->underscoreProto)
443 setHasUnderscoreProtoPropertyExcludingOriginalProto(true);
444
445 auto rep = propertyName.uid();
446
447 PropertyOffset newOffset = table->nextOffset(m_inlineCapacity);
448
449 m_propertyHash = m_propertyHash ^ rep->existingSymbolAwareHash();
450
451 PropertyOffset newLastOffset = m_offset;
452 table->add(PropertyMapEntry(rep, newOffset, attributes), newLastOffset, PropertyTable::PropertyOffsetMayChange);
453
454 func(locker, newOffset, newLastOffset);
455
456 ASSERT(m_offset == newLastOffset);
457
458 checkConsistency();
459 return newOffset;
460}
461
462template<typename Func>
463inline PropertyOffset Structure::remove(PropertyName propertyName, const Func& func)
464{
465 ConcurrentJSLocker locker(m_lock);
466
467 checkConsistency();
468
469 auto rep = propertyName.uid();
470
471 // We ONLY remove from uncacheable dictionaries, which will have a pinned property table.
472 // The only way for them not to have a table is if they are empty.
473 PropertyTable* table = propertyTableOrNull();
474
475 if (!table)
476 return invalidOffset;
477
478 PropertyTable::find_iterator position = table->find(rep);
479 if (!position.first)
480 return invalidOffset;
481
482 PropertyOffset offset = position.first->offset;
483
484 table->remove(position);
485 table->addDeletedOffset(offset);
486
487 checkConsistency();
488
489 func(locker, offset);
490 return offset;
491}
492
493template<typename Func>
494inline PropertyOffset Structure::addPropertyWithoutTransition(VM& vm, PropertyName propertyName, unsigned attributes, const Func& func)
495{
496 return add<ShouldPin::Yes>(vm, propertyName, attributes, func);
497}
498
499template<typename Func>
500inline PropertyOffset Structure::removePropertyWithoutTransition(VM&, PropertyName propertyName, const Func& func)
501{
502 ASSERT(isUncacheableDictionary());
503 ASSERT(isPinnedPropertyTable());
504 ASSERT(propertyTableOrNull());
505
506 return remove(propertyName, func);
507}
508
509ALWAYS_INLINE void Structure::setPrototypeWithoutTransition(VM& vm, JSValue prototype)
510{
511 ASSERT(isValidPrototype(prototype));
512 m_prototype.set(vm, this, prototype);
513}
514
515ALWAYS_INLINE void Structure::setGlobalObject(VM& vm, JSGlobalObject* globalObject)
516{
517 m_globalObject.set(vm, this, globalObject);
518}
519
520ALWAYS_INLINE void Structure::setPropertyTable(VM& vm, PropertyTable* table)
521{
522 m_propertyTableUnsafe.setMayBeNull(vm, this, table);
523}
524
525ALWAYS_INLINE void Structure::setPreviousID(VM& vm, Structure* structure)
526{
527 if (hasRareData())
528 rareData()->setPreviousID(vm, structure);
529 else
530 m_previousOrRareData.set(vm, this, structure);
531}
532
533ALWAYS_INLINE bool Structure::shouldConvertToPolyProto(const Structure* a, const Structure* b)
534{
535 if (!a || !b)
536 return false;
537
538 if (a == b)
539 return false;
540
541 if (a->propertyHash() != b->propertyHash())
542 return false;
543
544 // We only care about objects created via a constructor's to_this. These
545 // all have Structures with rare data and a sharedPolyProtoWatchpoint.
546 if (!a->hasRareData() || !b->hasRareData())
547 return false;
548
549 // We only care about Structure's generated from functions that share
550 // the same executable.
551 const Box<InlineWatchpointSet>& aInlineWatchpointSet = a->rareData()->sharedPolyProtoWatchpoint();
552 const Box<InlineWatchpointSet>& bInlineWatchpointSet = b->rareData()->sharedPolyProtoWatchpoint();
553 if (aInlineWatchpointSet.get() != bInlineWatchpointSet.get() || !aInlineWatchpointSet)
554 return false;
555 ASSERT(aInlineWatchpointSet && bInlineWatchpointSet && aInlineWatchpointSet.get() == bInlineWatchpointSet.get());
556
557 if (a->hasPolyProto() || b->hasPolyProto())
558 return false;
559
560 if (a->storedPrototype() == b->storedPrototype())
561 return false;
562
563 VM& vm = a->vm();
564 JSObject* aObj = a->storedPrototypeObject();
565 JSObject* bObj = b->storedPrototypeObject();
566 while (aObj && bObj) {
567 a = aObj->structure(vm);
568 b = bObj->structure(vm);
569
570 if (a->propertyHash() != b->propertyHash())
571 return false;
572
573 aObj = a->storedPrototypeObject(aObj);
574 bObj = b->storedPrototypeObject(bObj);
575 }
576
577 return !aObj && !bObj;
578}
579
580inline Structure* Structure::nonPropertyTransition(VM& vm, Structure* structure, NonPropertyTransition transitionKind)
581{
582 IndexingType indexingModeIncludingHistory = newIndexingType(structure->indexingModeIncludingHistory(), transitionKind);
583
584 if (changesIndexingType(transitionKind)) {
585 if (JSGlobalObject* globalObject = structure->m_globalObject.get()) {
586 if (globalObject->isOriginalArrayStructure(structure)) {
587 Structure* result = globalObject->originalArrayStructureForIndexingType(indexingModeIncludingHistory);
588 if (result->indexingModeIncludingHistory() == indexingModeIncludingHistory) {
589 structure->didTransitionFromThisStructure();
590 return result;
591 }
592 }
593 }
594 }
595
596 return nonPropertyTransitionSlow(vm, structure, transitionKind);
597}
598
599} // namespace JSC
600