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