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