1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* JS symbol tables. */
8
9 #include "vm/Shape-inl.h"
10
11 #include "mozilla/MathAlgorithms.h"
12 #include "mozilla/PodOperations.h"
13
14 #include "gc/FreeOp.h"
15 #include "gc/HashUtil.h"
16 #include "gc/Policy.h"
17 #include "gc/PublicIterators.h"
18 #include "js/HashTable.h"
19 #include "util/Text.h"
20 #include "vm/JSAtom.h"
21 #include "vm/JSContext.h"
22 #include "vm/JSObject.h"
23
24 #include "vm/Caches-inl.h"
25 #include "vm/JSCompartment-inl.h"
26 #include "vm/JSContext-inl.h"
27 #include "vm/JSObject-inl.h"
28 #include "vm/NativeObject-inl.h"
29
30 using namespace js;
31 using namespace js::gc;
32
33 using mozilla::CeilingLog2Size;
34 using mozilla::PodZero;
35
36 using JS::AutoCheckCannotGC;
37
38 Shape* const ShapeTable::Entry::SHAPE_REMOVED =
39 (Shape*)ShapeTable::Entry::SHAPE_COLLISION;
40
init(JSContext * cx,Shape * lastProp)41 bool ShapeTable::init(JSContext* cx, Shape* lastProp) {
42 uint32_t sizeLog2 = CeilingLog2Size(entryCount_);
43 uint32_t size = JS_BIT(sizeLog2);
44 if (entryCount_ >= size - (size >> 2)) sizeLog2++;
45 if (sizeLog2 < MIN_SIZE_LOG2) sizeLog2 = MIN_SIZE_LOG2;
46
47 size = JS_BIT(sizeLog2);
48 entries_ = cx->pod_calloc<Entry>(size);
49 if (!entries_) return false;
50
51 MOZ_ASSERT(sizeLog2 <= HASH_BITS);
52 hashShift_ = HASH_BITS - sizeLog2;
53
54 for (Shape::Range<NoGC> r(lastProp); !r.empty(); r.popFront()) {
55 Shape& shape = r.front();
56 Entry& entry = searchUnchecked<MaybeAdding::Adding>(shape.propid());
57
58 /*
59 * Beware duplicate args and arg vs. var conflicts: the youngest shape
60 * (nearest to lastProp) must win. See bug 600067.
61 */
62 if (!entry.shape()) entry.setPreservingCollision(&shape);
63 }
64
65 MOZ_ASSERT(capacity() == size);
66 MOZ_ASSERT(size >= MIN_SIZE);
67 MOZ_ASSERT(!needsToGrow());
68 return true;
69 }
70
removeFromDictionary(NativeObject * obj)71 void Shape::removeFromDictionary(NativeObject* obj) {
72 MOZ_ASSERT(inDictionary());
73 MOZ_ASSERT(obj->inDictionaryMode());
74 MOZ_ASSERT(listp);
75
76 MOZ_ASSERT(obj->shape()->inDictionary());
77 MOZ_ASSERT(obj->shape()->listp == obj->shapePtr());
78
79 if (parent) parent->listp = listp;
80 *listp = parent;
81 listp = nullptr;
82
83 obj->shape()->clearCachedBigEnoughForShapeTable();
84 }
85
insertIntoDictionary(GCPtrShape * dictp)86 void Shape::insertIntoDictionary(GCPtrShape* dictp) {
87 // Don't assert inDictionaryMode() here because we may be called from
88 // NativeObject::toDictionaryMode via Shape::initDictionaryShape.
89 MOZ_ASSERT(inDictionary());
90 MOZ_ASSERT(!listp);
91
92 MOZ_ASSERT_IF(*dictp, (*dictp)->inDictionary());
93 MOZ_ASSERT_IF(*dictp, (*dictp)->listp == dictp);
94 MOZ_ASSERT_IF(*dictp, zone() == (*dictp)->zone());
95
96 setParent(dictp->get());
97 if (parent) parent->listp = &parent;
98 listp = (GCPtrShape*)dictp;
99 *dictp = this;
100 }
101
makeOwnBaseShape(JSContext * cx)102 bool Shape::makeOwnBaseShape(JSContext* cx) {
103 MOZ_ASSERT(!base()->isOwned());
104 MOZ_ASSERT(cx->zone() == zone());
105
106 BaseShape* nbase = Allocate<BaseShape, NoGC>(cx);
107 if (!nbase) return false;
108
109 new (nbase) BaseShape(StackBaseShape(this));
110 nbase->setOwned(base()->toUnowned());
111
112 this->base_ = nbase;
113
114 return true;
115 }
116
handoffTableTo(Shape * shape)117 void Shape::handoffTableTo(Shape* shape) {
118 MOZ_ASSERT(inDictionary() && shape->inDictionary());
119
120 if (this == shape) return;
121
122 MOZ_ASSERT(base()->isOwned() && !shape->base()->isOwned());
123
124 BaseShape* nbase = base();
125
126 MOZ_ASSERT_IF(!shape->isEmptyShape() && shape->isDataProperty(),
127 nbase->slotSpan() > shape->slot());
128
129 this->base_ = nbase->baseUnowned();
130 nbase->adoptUnowned(shape->base()->toUnowned());
131
132 shape->base_ = nbase;
133 }
134
hashify(JSContext * cx,Shape * shape)135 /* static */ bool Shape::hashify(JSContext* cx, Shape* shape) {
136 MOZ_ASSERT(!shape->hasTable());
137
138 if (!shape->ensureOwnBaseShape(cx)) return false;
139
140 ShapeTable* table = cx->new_<ShapeTable>(shape->entryCount());
141 if (!table) return false;
142
143 if (!table->init(cx, shape)) {
144 js_free(table);
145 return false;
146 }
147
148 shape->base()->setTable(table);
149 return true;
150 }
151
change(JSContext * cx,int log2Delta)152 bool ShapeTable::change(JSContext* cx, int log2Delta) {
153 MOZ_ASSERT(entries_);
154 MOZ_ASSERT(-1 <= log2Delta && log2Delta <= 1);
155
156 /*
157 * Grow, shrink, or compress by changing this->entries_.
158 */
159 uint32_t oldLog2 = HASH_BITS - hashShift_;
160 uint32_t newLog2 = oldLog2 + log2Delta;
161 uint32_t oldSize = JS_BIT(oldLog2);
162 uint32_t newSize = JS_BIT(newLog2);
163 Entry* newTable = cx->maybe_pod_calloc<Entry>(newSize);
164 if (!newTable) return false;
165
166 /* Now that we have newTable allocated, update members. */
167 MOZ_ASSERT(newLog2 <= HASH_BITS);
168 hashShift_ = HASH_BITS - newLog2;
169 removedCount_ = 0;
170 Entry* oldTable = entries_;
171 entries_ = newTable;
172
173 /* Copy only live entries, leaving removed and free ones behind. */
174 AutoCheckCannotGC nogc;
175 for (Entry* oldEntry = oldTable; oldSize != 0; oldEntry++) {
176 if (Shape* shape = oldEntry->shape()) {
177 Entry& entry = search<MaybeAdding::Adding>(shape->propid(), nogc);
178 MOZ_ASSERT(entry.isFree());
179 entry.setShape(shape);
180 }
181 oldSize--;
182 }
183
184 MOZ_ASSERT(capacity() == newSize);
185
186 /* Finally, free the old entries storage. */
187 js_free(oldTable);
188 return true;
189 }
190
grow(JSContext * cx)191 bool ShapeTable::grow(JSContext* cx) {
192 MOZ_ASSERT(needsToGrow());
193
194 uint32_t size = capacity();
195 int delta = removedCount_ < (size >> 2);
196
197 MOZ_ASSERT(entryCount_ + removedCount_ <= size - 1);
198
199 if (!change(cx, delta)) {
200 if (entryCount_ + removedCount_ == size - 1) {
201 ReportOutOfMemory(cx);
202 return false;
203 }
204 }
205
206 return true;
207 }
208
trace(JSTracer * trc)209 void ShapeTable::trace(JSTracer* trc) {
210 for (size_t i = 0; i < capacity(); i++) {
211 Entry& entry = getEntry(i);
212 Shape* shape = entry.shape();
213 if (shape) {
214 TraceManuallyBarrieredEdge(trc, &shape, "ShapeTable shape");
215 if (shape != entry.shape()) entry.setPreservingCollision(shape);
216 }
217 }
218 }
219
220 #ifdef JSGC_HASH_TABLE_CHECKS
221
checkAfterMovingGC()222 void ShapeTable::checkAfterMovingGC() {
223 for (size_t i = 0; i < capacity(); i++) {
224 Entry& entry = getEntry(i);
225 Shape* shape = entry.shape();
226 if (shape) CheckGCThingAfterMovingGC(shape);
227 }
228 }
229
230 #endif
231
replaceLastProperty(JSContext * cx,StackBaseShape & base,TaggedProto proto,HandleShape shape)232 /* static */ Shape* Shape::replaceLastProperty(JSContext* cx,
233 StackBaseShape& base,
234 TaggedProto proto,
235 HandleShape shape) {
236 MOZ_ASSERT(!shape->inDictionary());
237
238 if (!shape->parent) {
239 /* Treat as resetting the initial property of the shape hierarchy. */
240 AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
241 return EmptyShape::getInitialShape(
242 cx, base.clasp, proto, kind, base.flags & BaseShape::OBJECT_FLAG_MASK);
243 }
244
245 UnownedBaseShape* nbase = BaseShape::getUnowned(cx, base);
246 if (!nbase) return nullptr;
247
248 Rooted<StackShape> child(cx, StackShape(shape));
249 child.setBase(nbase);
250
251 return cx->zone()->propertyTree().getChild(cx, shape->parent, child);
252 }
253
254 /*
255 * Get or create a property-tree or dictionary child property of |parent|,
256 * which must be lastProperty() if inDictionaryMode(), else parent must be
257 * one of lastProperty() or lastProperty()->parent.
258 */
getChildDataProperty(JSContext * cx,HandleNativeObject obj,HandleShape parent,MutableHandle<StackShape> child)259 /* static */ MOZ_ALWAYS_INLINE Shape* NativeObject::getChildDataProperty(
260 JSContext* cx, HandleNativeObject obj, HandleShape parent,
261 MutableHandle<StackShape> child) {
262 MOZ_ASSERT(child.isDataProperty());
263
264 if (child.hasMissingSlot()) {
265 uint32_t slot;
266 if (obj->inDictionaryMode()) {
267 if (!allocDictionarySlot(cx, obj, &slot)) return nullptr;
268 } else {
269 slot = obj->slotSpan();
270 MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
271 // Objects with many properties are converted to dictionary
272 // mode, so we can't overflow SHAPE_MAXIMUM_SLOT here.
273 MOZ_ASSERT(slot <
274 JSSLOT_FREE(obj->getClass()) + PropertyTree::MAX_HEIGHT);
275 MOZ_ASSERT(slot < SHAPE_MAXIMUM_SLOT);
276 }
277 child.setSlot(slot);
278 } else {
279 /*
280 * Slots can only be allocated out of order on objects in
281 * dictionary mode. Otherwise the child's slot must be after the
282 * parent's slot (if it has one), because slot number determines
283 * slot span for objects with that shape. Usually child slot
284 * *immediately* follows parent slot, but there may be a slot gap
285 * when the object uses some -- but not all -- of its reserved
286 * slots to store properties.
287 */
288 MOZ_ASSERT(obj->inDictionaryMode() || parent->hasMissingSlot() ||
289 child.slot() == parent->maybeSlot() + 1 ||
290 (parent->maybeSlot() + 1 < JSSLOT_FREE(obj->getClass()) &&
291 child.slot() == JSSLOT_FREE(obj->getClass())));
292 }
293
294 if (obj->inDictionaryMode()) {
295 MOZ_ASSERT(parent == obj->lastProperty());
296 Shape* shape = Allocate<Shape>(cx);
297 if (!shape) return nullptr;
298 if (child.slot() >= obj->lastProperty()->base()->slotSpan()) {
299 if (!obj->setSlotSpan(cx, child.slot() + 1)) {
300 new (shape) Shape(obj->lastProperty()->base()->unowned(), 0);
301 return nullptr;
302 }
303 }
304 shape->initDictionaryShape(child, obj->numFixedSlots(), obj->shapePtr());
305 return shape;
306 }
307
308 Shape* shape = cx->zone()->propertyTree().inlinedGetChild(cx, parent, child);
309 if (!shape) return nullptr;
310
311 MOZ_ASSERT(shape->parent == parent);
312 MOZ_ASSERT_IF(parent != obj->lastProperty(),
313 parent == obj->lastProperty()->parent);
314
315 if (!obj->setLastProperty(cx, shape)) return nullptr;
316 return shape;
317 }
318
getChildAccessorProperty(JSContext * cx,HandleNativeObject obj,HandleShape parent,MutableHandle<StackShape> child)319 /* static */ MOZ_ALWAYS_INLINE Shape* NativeObject::getChildAccessorProperty(
320 JSContext* cx, HandleNativeObject obj, HandleShape parent,
321 MutableHandle<StackShape> child) {
322 MOZ_ASSERT(!child.isDataProperty());
323
324 // Accessor properties have no slot, but slot_ will reflect that of parent.
325 child.setSlot(parent->maybeSlot());
326
327 if (obj->inDictionaryMode()) {
328 MOZ_ASSERT(parent == obj->lastProperty());
329 Shape* shape = Allocate<AccessorShape>(cx);
330 if (!shape) return nullptr;
331 shape->initDictionaryShape(child, obj->numFixedSlots(), obj->shapePtr());
332 return shape;
333 }
334
335 Shape* shape = cx->zone()->propertyTree().inlinedGetChild(cx, parent, child);
336 if (!shape) return nullptr;
337
338 MOZ_ASSERT(shape->parent == parent);
339 MOZ_ASSERT_IF(parent != obj->lastProperty(),
340 parent == obj->lastProperty()->parent);
341
342 if (!obj->setLastProperty(cx, shape)) return nullptr;
343 return shape;
344 }
345
toDictionaryMode(JSContext * cx,HandleNativeObject obj)346 /* static */ bool js::NativeObject::toDictionaryMode(JSContext* cx,
347 HandleNativeObject obj) {
348 MOZ_ASSERT(!obj->inDictionaryMode());
349 MOZ_ASSERT(cx->isInsideCurrentCompartment(obj));
350
351 uint32_t span = obj->slotSpan();
352
353 // Clone the shapes into a new dictionary list. Don't update the last
354 // property of this object until done, otherwise a GC triggered while
355 // creating the dictionary will get the wrong slot span for this object.
356 RootedShape root(cx);
357 RootedShape dictionaryShape(cx);
358
359 RootedShape shape(cx, obj->lastProperty());
360 while (shape) {
361 MOZ_ASSERT(!shape->inDictionary());
362
363 Shape* dprop = shape->isAccessorShape() ? Allocate<AccessorShape>(cx)
364 : Allocate<Shape>(cx);
365 if (!dprop) {
366 ReportOutOfMemory(cx);
367 return false;
368 }
369
370 GCPtrShape* listp = dictionaryShape ? &dictionaryShape->parent : nullptr;
371 StackShape child(shape);
372 dprop->initDictionaryShape(child, obj->numFixedSlots(), listp);
373
374 if (!dictionaryShape) root = dprop;
375
376 MOZ_ASSERT(!dprop->hasTable());
377 dictionaryShape = dprop;
378 shape = shape->previous();
379 }
380
381 if (!Shape::hashify(cx, root)) {
382 ReportOutOfMemory(cx);
383 return false;
384 }
385
386 if (IsInsideNursery(obj) &&
387 !cx->nursery().queueDictionaryModeObjectToSweep(obj)) {
388 ReportOutOfMemory(cx);
389 return false;
390 }
391
392 MOZ_ASSERT(root->listp == nullptr);
393 root->listp = obj->shapePtr();
394 obj->setShape(root);
395
396 MOZ_ASSERT(obj->inDictionaryMode());
397 root->base()->setSlotSpan(span);
398
399 return true;
400 }
401
ShouldConvertToDictionary(NativeObject * obj)402 static bool ShouldConvertToDictionary(NativeObject* obj) {
403 /*
404 * Use a lower limit if this object is likely a hashmap (SETELEM was used
405 * to set properties).
406 */
407 if (obj->hadElementsAccess())
408 return obj->lastProperty()->entryCount() >=
409 PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS;
410 return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT;
411 }
412
GetBaseShapeForNewShape(JSContext * cx,HandleShape last,HandleId id)413 static MOZ_ALWAYS_INLINE UnownedBaseShape* GetBaseShapeForNewShape(
414 JSContext* cx, HandleShape last, HandleId id) {
415 uint32_t index;
416 bool indexed = IdIsIndex(id, &index);
417 bool interestingSymbol =
418 JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id)->isInterestingSymbol();
419
420 if (MOZ_LIKELY(!indexed && !interestingSymbol))
421 return last->base()->unowned();
422
423 StackBaseShape base(last->base());
424 if (indexed)
425 base.flags |= BaseShape::INDEXED;
426 else if (interestingSymbol)
427 base.flags |= BaseShape::HAS_INTERESTING_SYMBOL;
428 return BaseShape::getUnowned(cx, base);
429 }
430
431 namespace js {
432
433 class MOZ_RAII AutoCheckShapeConsistency {
434 #ifdef DEBUG
435 HandleNativeObject obj_;
436 #endif
437
438 public:
AutoCheckShapeConsistency(HandleNativeObject obj)439 explicit AutoCheckShapeConsistency(HandleNativeObject obj)
440 #ifdef DEBUG
441 : obj_(obj)
442 #endif
443 {
444 }
445
446 #ifdef DEBUG
~AutoCheckShapeConsistency()447 ~AutoCheckShapeConsistency() { obj_->checkShapeConsistency(); }
448 #endif
449 };
450
451 } // namespace js
452
453 /* static */ MOZ_ALWAYS_INLINE bool
maybeConvertToOrGrowDictionaryForAdd(JSContext * cx,HandleNativeObject obj,HandleId id,ShapeTable ** table,ShapeTable::Entry ** entry,const AutoKeepShapeTables & keep)454 NativeObject::maybeConvertToOrGrowDictionaryForAdd(
455 JSContext* cx, HandleNativeObject obj, HandleId id, ShapeTable** table,
456 ShapeTable::Entry** entry, const AutoKeepShapeTables& keep) {
457 MOZ_ASSERT(!!*table == !!*entry);
458
459 // The code below deals with either converting obj to dictionary mode or
460 // growing an object that's already in dictionary mode.
461 if (!obj->inDictionaryMode()) {
462 if (!ShouldConvertToDictionary(obj)) return true;
463 if (!toDictionaryMode(cx, obj)) return false;
464 *table = obj->lastProperty()->maybeTable(keep);
465 } else {
466 if (!(*table)->needsToGrow()) return true;
467 if (!(*table)->grow(cx)) return false;
468 }
469
470 *entry = &(*table)->search<MaybeAdding::Adding>(id, keep);
471 MOZ_ASSERT(!(*entry)->shape());
472 return true;
473 }
474
updateDictionaryTable(ShapeTable * table,ShapeTable::Entry * entry,const AutoKeepShapeTables & keep)475 MOZ_ALWAYS_INLINE void Shape::updateDictionaryTable(
476 ShapeTable* table, ShapeTable::Entry* entry,
477 const AutoKeepShapeTables& keep) {
478 MOZ_ASSERT(table);
479 MOZ_ASSERT(entry);
480 MOZ_ASSERT(inDictionary());
481
482 // Store this Shape in the table entry.
483 entry->setPreservingCollision(this);
484 table->incEntryCount();
485
486 // Pass the table along to the new last property, namely *this.
487 MOZ_ASSERT(parent->maybeTable(keep) == table);
488 parent->handoffTableTo(this);
489 }
490
addAccessorPropertyInternal(JSContext * cx,HandleNativeObject obj,HandleId id,GetterOp getter,SetterOp setter,unsigned attrs,ShapeTable * table,ShapeTable::Entry * entry,const AutoKeepShapeTables & keep)491 /* static */ Shape* NativeObject::addAccessorPropertyInternal(
492 JSContext* cx, HandleNativeObject obj, HandleId id, GetterOp getter,
493 SetterOp setter, unsigned attrs, ShapeTable* table,
494 ShapeTable::Entry* entry, const AutoKeepShapeTables& keep) {
495 AutoCheckShapeConsistency check(obj);
496 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
497
498 if (!maybeConvertToOrGrowDictionaryForAdd(cx, obj, id, &table, &entry, keep))
499 return nullptr;
500
501 // Find or create a property tree node labeled by our arguments.
502 RootedShape shape(cx);
503 {
504 RootedShape last(cx, obj->lastProperty());
505 Rooted<UnownedBaseShape*> nbase(cx, GetBaseShapeForNewShape(cx, last, id));
506 if (!nbase) return nullptr;
507
508 Rooted<StackShape> child(cx,
509 StackShape(nbase, id, SHAPE_INVALID_SLOT, attrs));
510 child.updateGetterSetter(getter, setter);
511 shape = getChildAccessorProperty(cx, obj, last, &child);
512 if (!shape) return nullptr;
513 }
514
515 MOZ_ASSERT(shape == obj->lastProperty());
516
517 if (table) shape->updateDictionaryTable(table, entry, keep);
518
519 return shape;
520 }
521
addDataPropertyInternal(JSContext * cx,HandleNativeObject obj,HandleId id,uint32_t slot,unsigned attrs,ShapeTable * table,ShapeTable::Entry * entry,const AutoKeepShapeTables & keep)522 /* static */ Shape* NativeObject::addDataPropertyInternal(
523 JSContext* cx, HandleNativeObject obj, HandleId id, uint32_t slot,
524 unsigned attrs, ShapeTable* table, ShapeTable::Entry* entry,
525 const AutoKeepShapeTables& keep) {
526 AutoCheckShapeConsistency check(obj);
527
528 // The slot, if any, must be a reserved slot.
529 MOZ_ASSERT(slot == SHAPE_INVALID_SLOT ||
530 slot < JSCLASS_RESERVED_SLOTS(obj->getClass()));
531
532 if (!maybeConvertToOrGrowDictionaryForAdd(cx, obj, id, &table, &entry, keep))
533 return nullptr;
534
535 // Find or create a property tree node labeled by our arguments.
536 RootedShape shape(cx);
537 {
538 RootedShape last(cx, obj->lastProperty());
539 Rooted<UnownedBaseShape*> nbase(cx, GetBaseShapeForNewShape(cx, last, id));
540 if (!nbase) return nullptr;
541
542 Rooted<StackShape> child(cx, StackShape(nbase, id, slot, attrs));
543 shape = getChildDataProperty(cx, obj, last, &child);
544 if (!shape) return nullptr;
545 }
546
547 MOZ_ASSERT(shape == obj->lastProperty());
548
549 if (table) shape->updateDictionaryTable(table, entry, keep);
550
551 return shape;
552 }
553
PropertyTreeReadBarrier(Shape * parent,Shape * shape)554 static MOZ_ALWAYS_INLINE Shape* PropertyTreeReadBarrier(Shape* parent,
555 Shape* shape) {
556 JS::Zone* zone = shape->zone();
557 if (zone->needsIncrementalBarrier()) {
558 // We need a read barrier for the shape tree, since these are weak
559 // pointers.
560 Shape* tmp = shape;
561 TraceManuallyBarrieredEdge(zone->barrierTracer(), &tmp, "read barrier");
562 MOZ_ASSERT(tmp == shape);
563 return shape;
564 }
565
566 if (MOZ_LIKELY(!zone->isGCSweepingOrCompacting() ||
567 !IsAboutToBeFinalizedUnbarriered(&shape))) {
568 if (shape->isMarkedGray()) UnmarkGrayShapeRecursively(shape);
569 return shape;
570 }
571
572 // The shape we've found is unreachable and due to be finalized, so
573 // remove our weak reference to it and don't use it.
574 MOZ_ASSERT(parent->isMarkedAny());
575 parent->removeChild(shape);
576
577 return nullptr;
578 }
579
addEnumerableDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id)580 /* static */ Shape* NativeObject::addEnumerableDataProperty(
581 JSContext* cx, HandleNativeObject obj, HandleId id) {
582 // Like addProperty(Internal), but optimized for the common case of adding a
583 // new enumerable data property.
584
585 AutoCheckShapeConsistency check(obj);
586
587 // Fast path for non-dictionary shapes with a single kid.
588 do {
589 AutoCheckCannotGC nogc;
590
591 Shape* lastProperty = obj->lastProperty();
592 if (lastProperty->inDictionary()) break;
593
594 KidsPointer* kidp = &lastProperty->kids;
595 if (!kidp->isShape()) break;
596
597 Shape* kid = kidp->toShape();
598 MOZ_ASSERT(!kid->inDictionary());
599
600 if (kid->propidRaw() != id || kid->isAccessorShape() ||
601 kid->attributes() != JSPROP_ENUMERATE ||
602 kid->base()->unowned() != lastProperty->base()->unowned()) {
603 break;
604 }
605
606 MOZ_ASSERT(kid->isDataProperty());
607
608 kid = PropertyTreeReadBarrier(lastProperty, kid);
609 if (!kid) break;
610
611 if (!obj->setLastProperty(cx, kid)) return nullptr;
612 return kid;
613 } while (0);
614
615 AutoKeepShapeTables keep(cx);
616 ShapeTable* table = nullptr;
617 ShapeTable::Entry* entry = nullptr;
618
619 if (!obj->inDictionaryMode()) {
620 if (MOZ_UNLIKELY(ShouldConvertToDictionary(obj))) {
621 if (!toDictionaryMode(cx, obj)) return nullptr;
622 table = obj->lastProperty()->maybeTable(keep);
623 entry = &table->search<MaybeAdding::Adding>(id, keep);
624 }
625 } else {
626 table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
627 if (!table) return nullptr;
628 if (table->needsToGrow()) {
629 if (!table->grow(cx)) return nullptr;
630 }
631 entry = &table->search<MaybeAdding::Adding>(id, keep);
632 MOZ_ASSERT(!entry->shape());
633 }
634
635 MOZ_ASSERT(!!table == !!entry);
636
637 /* Find or create a property tree node labeled by our arguments. */
638 RootedShape last(cx, obj->lastProperty());
639 UnownedBaseShape* nbase = GetBaseShapeForNewShape(cx, last, id);
640 if (!nbase) return nullptr;
641
642 Shape* shape;
643 if (obj->inDictionaryMode()) {
644 uint32_t slot;
645 if (!allocDictionarySlot(cx, obj, &slot)) return nullptr;
646
647 Rooted<StackShape> child(cx, StackShape(nbase, id, slot, JSPROP_ENUMERATE));
648
649 MOZ_ASSERT(last == obj->lastProperty());
650 shape = Allocate<Shape>(cx);
651 if (!shape) return nullptr;
652 if (slot >= obj->lastProperty()->base()->slotSpan()) {
653 if (MOZ_UNLIKELY(!obj->setSlotSpan(cx, slot + 1))) {
654 new (shape) Shape(obj->lastProperty()->base()->unowned(), 0);
655 return nullptr;
656 }
657 }
658 shape->initDictionaryShape(child, obj->numFixedSlots(), obj->shapePtr());
659 } else {
660 uint32_t slot = obj->slotSpan();
661 MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
662 // Objects with many properties are converted to dictionary
663 // mode, so we can't overflow SHAPE_MAXIMUM_SLOT here.
664 MOZ_ASSERT(slot < JSSLOT_FREE(obj->getClass()) + PropertyTree::MAX_HEIGHT);
665 MOZ_ASSERT(slot < SHAPE_MAXIMUM_SLOT);
666
667 Rooted<StackShape> child(cx, StackShape(nbase, id, slot, JSPROP_ENUMERATE));
668 shape = cx->zone()->propertyTree().inlinedGetChild(cx, last, child);
669 if (!shape) return nullptr;
670 if (!obj->setLastProperty(cx, shape)) return nullptr;
671 }
672
673 MOZ_ASSERT(shape == obj->lastProperty());
674
675 if (table) shape->updateDictionaryTable(table, entry, keep);
676
677 return shape;
678 }
679
ReshapeForAllocKind(JSContext * cx,Shape * shape,TaggedProto proto,gc::AllocKind allocKind)680 Shape* js::ReshapeForAllocKind(JSContext* cx, Shape* shape, TaggedProto proto,
681 gc::AllocKind allocKind) {
682 // Compute the number of fixed slots with the new allocation kind.
683 size_t nfixed = gc::GetGCKindSlots(allocKind, shape->getObjectClass());
684
685 // Get all the ids in the shape, in order.
686 js::AutoIdVector ids(cx);
687 {
688 for (unsigned i = 0; i < shape->slotSpan(); i++) {
689 if (!ids.append(JSID_VOID)) return nullptr;
690 }
691 Shape* nshape = shape;
692 while (!nshape->isEmptyShape()) {
693 ids[nshape->slot()].set(nshape->propid());
694 nshape = nshape->previous();
695 }
696 }
697
698 // Construct the new shape, without updating type information.
699 RootedId id(cx);
700 RootedShape newShape(
701 cx, EmptyShape::getInitialShape(cx, shape->getObjectClass(), proto,
702 nfixed, shape->getObjectFlags()));
703 if (!newShape) return nullptr;
704
705 for (unsigned i = 0; i < ids.length(); i++) {
706 id = ids[i];
707
708 UnownedBaseShape* nbase = GetBaseShapeForNewShape(cx, newShape, id);
709 if (!nbase) return nullptr;
710
711 Rooted<StackShape> child(cx, StackShape(nbase, id, i, JSPROP_ENUMERATE));
712 newShape = cx->zone()->propertyTree().getChild(cx, newShape, child);
713 if (!newShape) return nullptr;
714 }
715
716 return newShape;
717 }
718
719 /*
720 * Assert some invariants that should hold when changing properties. It's the
721 * responsibility of the callers to ensure these hold.
722 */
AssertCanChangeAttrs(Shape * shape,unsigned attrs)723 static void AssertCanChangeAttrs(Shape* shape, unsigned attrs) {
724 #ifdef DEBUG
725 if (shape->configurable()) return;
726
727 /* A permanent property must stay permanent. */
728 MOZ_ASSERT(attrs & JSPROP_PERMANENT);
729
730 /* Reject attempts to remove a slot from the permanent data property. */
731 MOZ_ASSERT_IF(shape->isDataProperty(),
732 !(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
733 #endif
734 }
735
AssertValidArrayIndex(NativeObject * obj,jsid id)736 static void AssertValidArrayIndex(NativeObject* obj, jsid id) {
737 #ifdef DEBUG
738 if (obj->is<ArrayObject>()) {
739 ArrayObject* arr = &obj->as<ArrayObject>();
740 uint32_t index;
741 if (IdIsIndex(id, &index))
742 MOZ_ASSERT(index < arr->length() || arr->lengthIsWritable());
743 }
744 #endif
745 }
746
maybeToDictionaryModeForPut(JSContext * cx,HandleNativeObject obj,MutableHandleShape shape)747 /* static */ bool NativeObject::maybeToDictionaryModeForPut(
748 JSContext* cx, HandleNativeObject obj, MutableHandleShape shape) {
749 // Overwriting a non-last property requires switching to dictionary mode.
750 // The shape tree is shared immutable, and we can't removeProperty and then
751 // addAccessorPropertyInternal because a failure under add would lose data.
752
753 if (shape == obj->lastProperty() || obj->inDictionaryMode()) return true;
754
755 if (!toDictionaryMode(cx, obj)) return false;
756
757 AutoCheckCannotGC nogc;
758 ShapeTable* table = obj->lastProperty()->maybeTable(nogc);
759 MOZ_ASSERT(table);
760 shape.set(
761 table->search<MaybeAdding::NotAdding>(shape->propid(), nogc).shape());
762 return true;
763 }
764
putDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,unsigned attrs)765 /* static */ Shape* NativeObject::putDataProperty(JSContext* cx,
766 HandleNativeObject obj,
767 HandleId id, unsigned attrs) {
768 MOZ_ASSERT(!JSID_IS_VOID(id));
769
770 AutoCheckShapeConsistency check(obj);
771 AssertValidArrayIndex(obj, id);
772
773 // Search for id in order to claim its entry if table has been allocated.
774 AutoKeepShapeTables keep(cx);
775 RootedShape shape(cx);
776 {
777 ShapeTable* table;
778 ShapeTable::Entry* entry;
779 if (!Shape::search<MaybeAdding::Adding>(cx, obj->lastProperty(), id, keep,
780 shape.address(), &table, &entry)) {
781 return nullptr;
782 }
783
784 if (!shape) {
785 MOZ_ASSERT(obj->nonProxyIsExtensible(),
786 "Can't add new property to non-extensible object");
787 return addDataPropertyInternal(cx, obj, id, SHAPE_INVALID_SLOT, attrs,
788 table, entry, keep);
789 }
790
791 // Property exists: search must have returned a valid entry.
792 MOZ_ASSERT_IF(entry, !entry->isRemoved());
793 }
794
795 AssertCanChangeAttrs(shape, attrs);
796
797 // If the caller wants to allocate a slot, but doesn't care which slot,
798 // copy the existing shape's slot into slot so we can match shape, if all
799 // other members match.
800 bool hadSlot = shape->isDataProperty();
801 uint32_t oldSlot = shape->maybeSlot();
802 uint32_t slot = hadSlot ? oldSlot : SHAPE_INVALID_SLOT;
803
804 Rooted<UnownedBaseShape*> nbase(cx);
805 {
806 RootedShape shape(cx, obj->lastProperty());
807 nbase = GetBaseShapeForNewShape(cx, shape, id);
808 if (!nbase) return nullptr;
809 }
810
811 // Now that we've possibly preserved slot, check whether all members match.
812 // If so, this is a redundant "put" and we can return without more work.
813 if (shape->matchesParamsAfterId(nbase, slot, attrs, nullptr, nullptr))
814 return shape;
815
816 if (!maybeToDictionaryModeForPut(cx, obj, &shape)) return nullptr;
817
818 MOZ_ASSERT_IF(shape->isDataProperty(), shape->slot() == slot);
819
820 if (obj->inDictionaryMode()) {
821 // Updating some property in a dictionary-mode object. Create a new
822 // shape for the existing property, and also generate a new shape for
823 // the last property of the dictionary (unless the modified property
824 // is also the last property).
825 bool updateLast = (shape == obj->lastProperty());
826 shape = NativeObject::replaceWithNewEquivalentShape(
827 cx, obj, shape, nullptr,
828 /* accessorShape = */ false);
829 if (!shape) return nullptr;
830 if (!updateLast && !NativeObject::generateOwnShape(cx, obj)) return nullptr;
831
832 if (slot == SHAPE_INVALID_SLOT) {
833 if (!allocDictionarySlot(cx, obj, &slot)) return nullptr;
834 }
835
836 if (updateLast)
837 shape->base()->adoptUnowned(nbase);
838 else
839 shape->base_ = nbase;
840
841 shape->setSlot(slot);
842 shape->attrs = uint8_t(attrs);
843 shape->flags = Shape::IN_DICTIONARY;
844 } else {
845 // Updating the last property in a non-dictionary-mode object. Find an
846 // alternate shared child of the last property's previous shape.
847
848 MOZ_ASSERT(shape == obj->lastProperty());
849
850 // Find or create a property tree node labeled by our arguments.
851 Rooted<StackShape> child(cx, StackShape(nbase, id, slot, attrs));
852 RootedShape parent(cx, shape->parent);
853 shape = getChildDataProperty(cx, obj, parent, &child);
854 if (!shape) return nullptr;
855 }
856
857 MOZ_ASSERT(shape->isDataProperty());
858 return shape;
859 }
860
putAccessorProperty(JSContext * cx,HandleNativeObject obj,HandleId id,GetterOp getter,SetterOp setter,unsigned attrs)861 /* static */ Shape* NativeObject::putAccessorProperty(
862 JSContext* cx, HandleNativeObject obj, HandleId id, GetterOp getter,
863 SetterOp setter, unsigned attrs) {
864 MOZ_ASSERT(!JSID_IS_VOID(id));
865
866 AutoCheckShapeConsistency check(obj);
867 AssertValidArrayIndex(obj, id);
868
869 AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
870
871 // Search for id in order to claim its entry if table has been allocated.
872 AutoKeepShapeTables keep(cx);
873 RootedShape shape(cx);
874 {
875 ShapeTable* table;
876 ShapeTable::Entry* entry;
877 if (!Shape::search<MaybeAdding::Adding>(cx, obj->lastProperty(), id, keep,
878 shape.address(), &table, &entry)) {
879 return nullptr;
880 }
881
882 if (!shape) {
883 MOZ_ASSERT(obj->nonProxyIsExtensible(),
884 "Can't add new property to non-extensible object");
885 return addAccessorPropertyInternal(cx, obj, id, getter, setter, attrs,
886 table, entry, keep);
887 }
888
889 // Property exists: search must have returned a valid entry.
890 MOZ_ASSERT_IF(entry, !entry->isRemoved());
891 }
892
893 AssertCanChangeAttrs(shape, attrs);
894
895 bool hadSlot = shape->isDataProperty();
896 uint32_t oldSlot = shape->maybeSlot();
897
898 Rooted<UnownedBaseShape*> nbase(cx);
899 {
900 RootedShape shape(cx, obj->lastProperty());
901 nbase = GetBaseShapeForNewShape(cx, shape, id);
902 if (!nbase) return nullptr;
903 }
904
905 // Check whether all members match. If so, this is a redundant "put" and we
906 // can return without more work.
907 if (shape->matchesParamsAfterId(nbase, SHAPE_INVALID_SLOT, attrs, getter,
908 setter))
909 return shape;
910
911 if (!maybeToDictionaryModeForPut(cx, obj, &shape)) return nullptr;
912
913 if (obj->inDictionaryMode()) {
914 // Updating some property in a dictionary-mode object. Create a new
915 // shape for the existing property, and also generate a new shape for
916 // the last property of the dictionary (unless the modified property
917 // is also the last property).
918 bool updateLast = (shape == obj->lastProperty());
919 shape =
920 NativeObject::replaceWithNewEquivalentShape(cx, obj, shape, nullptr,
921 /* accessorShape = */ true);
922 if (!shape) return nullptr;
923 if (!updateLast && !NativeObject::generateOwnShape(cx, obj)) return nullptr;
924
925 if (updateLast)
926 shape->base()->adoptUnowned(nbase);
927 else
928 shape->base_ = nbase;
929
930 shape->setSlot(SHAPE_INVALID_SLOT);
931 shape->attrs = uint8_t(attrs);
932 shape->flags = Shape::IN_DICTIONARY | Shape::ACCESSOR_SHAPE;
933
934 AccessorShape& accShape = shape->asAccessorShape();
935 accShape.rawGetter = getter;
936 accShape.rawSetter = setter;
937 GetterSetterWriteBarrierPost(&accShape);
938 } else {
939 // Updating the last property in a non-dictionary-mode object. Find an
940 // alternate shared child of the last property's previous shape.
941
942 MOZ_ASSERT(shape == obj->lastProperty());
943
944 // Find or create a property tree node labeled by our arguments.
945 Rooted<StackShape> child(cx,
946 StackShape(nbase, id, SHAPE_INVALID_SLOT, attrs));
947 child.updateGetterSetter(getter, setter);
948 RootedShape parent(cx, shape->parent);
949 shape = getChildAccessorProperty(cx, obj, parent, &child);
950 if (!shape) return nullptr;
951 }
952
953 // Can't fail now, so free the previous incarnation's slot. But we do not
954 // need to free oldSlot (and must not, as trying to will botch an assertion
955 // in NativeObject::freeSlot) if the new last property (shape here) has a
956 // slotSpan that does not cover it.
957 if (hadSlot && oldSlot < obj->slotSpan()) obj->freeSlot(cx, oldSlot);
958
959 MOZ_ASSERT(!shape->isDataProperty());
960 return shape;
961 }
962
changeProperty(JSContext * cx,HandleNativeObject obj,HandleShape shape,unsigned attrs,GetterOp getter,SetterOp setter)963 /* static */ Shape* NativeObject::changeProperty(
964 JSContext* cx, HandleNativeObject obj, HandleShape shape, unsigned attrs,
965 GetterOp getter, SetterOp setter) {
966 MOZ_ASSERT(obj->containsPure(shape));
967
968 AutoCheckShapeConsistency check(obj);
969
970 /* Allow only shared (slotless) => unshared (slotful) transition. */
971 #ifdef DEBUG
972 bool needSlot = Shape::isDataProperty(attrs, getter, setter);
973 MOZ_ASSERT_IF(shape->isDataProperty() != needSlot, needSlot);
974 #endif
975
976 MarkTypePropertyNonData(cx, obj, shape->propid());
977
978 AssertCanChangeAttrs(shape, attrs);
979
980 if (shape->attrs == attrs && shape->getter() == getter &&
981 shape->setter() == setter)
982 return shape;
983
984 RootedId propid(cx, shape->propid());
985 return putAccessorProperty(cx, obj, propid, getter, setter, attrs);
986 }
987
removeProperty(JSContext * cx,HandleNativeObject obj,jsid id_)988 /* static */ bool NativeObject::removeProperty(JSContext* cx,
989 HandleNativeObject obj,
990 jsid id_) {
991 RootedId id(cx, id_);
992
993 AutoKeepShapeTables keep(cx);
994 ShapeTable* table;
995 ShapeTable::Entry* entry;
996 RootedShape shape(cx);
997 if (!Shape::search(cx, obj->lastProperty(), id, keep, shape.address(), &table,
998 &entry))
999 return false;
1000
1001 if (!shape) return true;
1002
1003 /*
1004 * If shape is not the last property added, or the last property cannot
1005 * be removed, switch to dictionary mode.
1006 */
1007 if (!obj->inDictionaryMode() &&
1008 (shape != obj->lastProperty() || !obj->canRemoveLastProperty())) {
1009 if (!toDictionaryMode(cx, obj)) return false;
1010 table = obj->lastProperty()->maybeTable(keep);
1011 MOZ_ASSERT(table);
1012 entry = &table->search<MaybeAdding::NotAdding>(shape->propid(), keep);
1013 shape = entry->shape();
1014 }
1015
1016 /*
1017 * If in dictionary mode, get a new shape for the last property after the
1018 * removal. We need a fresh shape for all dictionary deletions, even of
1019 * the last property. Otherwise, a shape could replay and caches might
1020 * return deleted DictionaryShapes! See bug 595365. Do this before changing
1021 * the object or table, so the remaining removal is infallible.
1022 */
1023 RootedShape spare(cx);
1024 if (obj->inDictionaryMode()) {
1025 /* For simplicity, always allocate an accessor shape for now. */
1026 spare = Allocate<AccessorShape>(cx);
1027 if (!spare) return false;
1028 new (spare) Shape(shape->base()->unowned(), 0);
1029 if (shape == obj->lastProperty()) {
1030 /*
1031 * Get an up to date unowned base shape for the new last property
1032 * when removing the dictionary's last property. Information in
1033 * base shapes for non-last properties may be out of sync with the
1034 * object's state.
1035 */
1036 RootedShape previous(cx, obj->lastProperty()->parent);
1037 StackBaseShape base(obj->lastProperty()->base());
1038 BaseShape* nbase = BaseShape::getUnowned(cx, base);
1039 if (!nbase) return false;
1040 previous->base_ = nbase;
1041 }
1042 }
1043
1044 /* If shape has a slot, free its slot number. */
1045 if (shape->isDataProperty()) obj->freeSlot(cx, shape->slot());
1046
1047 /*
1048 * A dictionary-mode object owns mutable, unique shapes on a non-circular
1049 * doubly linked list, hashed by lastProperty()->table. So we can edit the
1050 * list and hash in place.
1051 */
1052 if (obj->inDictionaryMode()) {
1053 MOZ_ASSERT(obj->lastProperty()->maybeTable(keep) == table);
1054
1055 if (entry->hadCollision()) {
1056 entry->setRemoved();
1057 table->decEntryCount();
1058 table->incRemovedCount();
1059 } else {
1060 entry->setFree();
1061 table->decEntryCount();
1062
1063 #ifdef DEBUG
1064 /*
1065 * Check the consistency of the table but limit the number of
1066 * checks not to alter significantly the complexity of the
1067 * delete in debug builds, see bug 534493.
1068 */
1069 Shape* aprop = obj->lastProperty();
1070 for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent)
1071 MOZ_ASSERT_IF(aprop != shape, obj->contains(cx, aprop));
1072 #endif
1073 }
1074
1075 {
1076 /* Remove shape from its non-circular doubly linked list. */
1077 Shape* oldLastProp = obj->lastProperty();
1078 shape->removeFromDictionary(obj);
1079
1080 /* Hand off table from the old to new last property. */
1081 oldLastProp->handoffTableTo(obj->lastProperty());
1082 }
1083
1084 /* Generate a new shape for the object, infallibly. */
1085 JS_ALWAYS_TRUE(NativeObject::generateOwnShape(cx, obj, spare));
1086
1087 /* Consider shrinking table if its load factor is <= .25. */
1088 uint32_t size = table->capacity();
1089 if (size > ShapeTable::MIN_SIZE && table->entryCount() <= size >> 2)
1090 (void)table->change(cx, -1);
1091 } else {
1092 /*
1093 * Non-dictionary-mode shape tables are shared immutables, so all we
1094 * need do is retract the last property and we'll either get or else
1095 * lazily make via a later hashify the exact table for the new property
1096 * lineage.
1097 */
1098 MOZ_ASSERT(shape == obj->lastProperty());
1099 obj->removeLastProperty(cx);
1100 }
1101
1102 obj->checkShapeConsistency();
1103 return true;
1104 }
1105
clear(JSContext * cx,HandleNativeObject obj)1106 /* static */ void NativeObject::clear(JSContext* cx, HandleNativeObject obj) {
1107 Shape* shape = obj->lastProperty();
1108 MOZ_ASSERT(obj->inDictionaryMode() == shape->inDictionary());
1109
1110 while (shape->parent) {
1111 shape = shape->parent;
1112 MOZ_ASSERT(obj->inDictionaryMode() == shape->inDictionary());
1113 }
1114 MOZ_ASSERT(shape->isEmptyShape());
1115
1116 if (obj->inDictionaryMode()) shape->listp = obj->shapePtr();
1117
1118 JS_ALWAYS_TRUE(obj->setLastProperty(cx, shape));
1119
1120 obj->checkShapeConsistency();
1121 }
1122
rollbackProperties(JSContext * cx,HandleNativeObject obj,uint32_t slotSpan)1123 /* static */ bool NativeObject::rollbackProperties(JSContext* cx,
1124 HandleNativeObject obj,
1125 uint32_t slotSpan) {
1126 /*
1127 * Remove properties from this object until it has a matching slot span.
1128 * The object cannot have escaped in a way which would prevent safe
1129 * removal of the last properties.
1130 */
1131 MOZ_ASSERT(!obj->inDictionaryMode() && slotSpan <= obj->slotSpan());
1132 while (true) {
1133 if (obj->lastProperty()->isEmptyShape()) {
1134 MOZ_ASSERT(slotSpan == 0);
1135 break;
1136 } else {
1137 uint32_t slot = obj->lastProperty()->slot();
1138 if (slot < slotSpan) break;
1139 }
1140 if (!NativeObject::removeProperty(cx, obj, obj->lastProperty()->propid()))
1141 return false;
1142 }
1143
1144 return true;
1145 }
1146
replaceWithNewEquivalentShape(JSContext * cx,HandleNativeObject obj,Shape * oldShape,Shape * newShape,bool accessorShape)1147 /* static */ Shape* NativeObject::replaceWithNewEquivalentShape(
1148 JSContext* cx, HandleNativeObject obj, Shape* oldShape, Shape* newShape,
1149 bool accessorShape) {
1150 MOZ_ASSERT(cx->isInsideCurrentZone(oldShape));
1151 MOZ_ASSERT_IF(oldShape != obj->lastProperty(),
1152 obj->inDictionaryMode() &&
1153 obj->lookup(cx, oldShape->propidRef()) == oldShape);
1154
1155 if (!obj->inDictionaryMode()) {
1156 RootedShape newRoot(cx, newShape);
1157 if (!toDictionaryMode(cx, obj)) return nullptr;
1158 oldShape = obj->lastProperty();
1159 newShape = newRoot;
1160 }
1161
1162 if (!newShape) {
1163 RootedShape oldRoot(cx, oldShape);
1164 newShape = (oldShape->isAccessorShape() || accessorShape)
1165 ? Allocate<AccessorShape>(cx)
1166 : Allocate<Shape>(cx);
1167 if (!newShape) return nullptr;
1168 new (newShape) Shape(oldRoot->base()->unowned(), 0);
1169 oldShape = oldRoot;
1170 }
1171
1172 AutoCheckCannotGC nogc;
1173 ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, nogc);
1174 if (!table) return nullptr;
1175
1176 ShapeTable::Entry* entry =
1177 oldShape->isEmptyShape()
1178 ? nullptr
1179 : &table->search<MaybeAdding::NotAdding>(oldShape->propidRef(), nogc);
1180
1181 /*
1182 * Splice the new shape into the same position as the old shape, preserving
1183 * enumeration order (see bug 601399).
1184 */
1185 StackShape nshape(oldShape);
1186 newShape->initDictionaryShape(nshape, obj->numFixedSlots(), oldShape->listp);
1187
1188 MOZ_ASSERT(newShape->parent == oldShape);
1189 oldShape->removeFromDictionary(obj);
1190
1191 if (newShape == obj->lastProperty()) oldShape->handoffTableTo(newShape);
1192
1193 if (entry) entry->setPreservingCollision(newShape);
1194 return newShape;
1195 }
1196
setFlags(JSContext * cx,HandleObject obj,BaseShape::Flag flags,GenerateShape generateShape)1197 /* static */ bool JSObject::setFlags(JSContext* cx, HandleObject obj,
1198 BaseShape::Flag flags,
1199 GenerateShape generateShape) {
1200 if (obj->hasAllFlags(flags)) return true;
1201
1202 Shape* existingShape = obj->ensureShape(cx);
1203 if (!existingShape) return false;
1204
1205 if (obj->isNative() && obj->as<NativeObject>().inDictionaryMode()) {
1206 if (generateShape == GENERATE_SHAPE) {
1207 if (!NativeObject::generateOwnShape(cx, obj.as<NativeObject>()))
1208 return false;
1209 }
1210 StackBaseShape base(obj->as<NativeObject>().lastProperty());
1211 base.flags |= flags;
1212 UnownedBaseShape* nbase = BaseShape::getUnowned(cx, base);
1213 if (!nbase) return false;
1214
1215 obj->as<NativeObject>().lastProperty()->base()->adoptUnowned(nbase);
1216 return true;
1217 }
1218
1219 Shape* newShape =
1220 Shape::setObjectFlags(cx, flags, obj->taggedProto(), existingShape);
1221 if (!newShape) return false;
1222
1223 // The success of the |JSObject::ensureShape| call above means that |obj|
1224 // can be assumed to have a shape.
1225 obj->as<ShapedObject>().setShape(newShape);
1226
1227 return true;
1228 }
1229
clearFlag(JSContext * cx,HandleNativeObject obj,BaseShape::Flag flag)1230 /* static */ bool NativeObject::clearFlag(JSContext* cx, HandleNativeObject obj,
1231 BaseShape::Flag flag) {
1232 MOZ_ASSERT(obj->lastProperty()->getObjectFlags() & flag);
1233
1234 if (!obj->inDictionaryMode()) {
1235 if (!toDictionaryMode(cx, obj)) return false;
1236 }
1237
1238 StackBaseShape base(obj->lastProperty());
1239 base.flags &= ~flag;
1240 UnownedBaseShape* nbase = BaseShape::getUnowned(cx, base);
1241 if (!nbase) return false;
1242
1243 obj->lastProperty()->base()->adoptUnowned(nbase);
1244 return true;
1245 }
1246
setObjectFlags(JSContext * cx,BaseShape::Flag flags,TaggedProto proto,Shape * last)1247 /* static */ Shape* Shape::setObjectFlags(JSContext* cx, BaseShape::Flag flags,
1248 TaggedProto proto, Shape* last) {
1249 if ((last->getObjectFlags() & flags) == flags) return last;
1250
1251 StackBaseShape base(last);
1252 base.flags |= flags;
1253
1254 RootedShape lastRoot(cx, last);
1255 return replaceLastProperty(cx, base, proto, lastRoot);
1256 }
1257
BaseShape(const StackBaseShape & base)1258 inline BaseShape::BaseShape(const StackBaseShape& base)
1259 : clasp_(base.clasp),
1260 flags(base.flags),
1261 slotSpan_(0),
1262 unowned_(nullptr),
1263 table_(nullptr) {}
1264
copyFromUnowned(BaseShape & dest,UnownedBaseShape & src)1265 /* static */ void BaseShape::copyFromUnowned(BaseShape& dest,
1266 UnownedBaseShape& src) {
1267 dest.clasp_ = src.clasp_;
1268 dest.slotSpan_ = src.slotSpan_;
1269 dest.unowned_ = &src;
1270 dest.flags = src.flags | OWNED_SHAPE;
1271 }
1272
adoptUnowned(UnownedBaseShape * other)1273 inline void BaseShape::adoptUnowned(UnownedBaseShape* other) {
1274 // This is a base shape owned by a dictionary object, update it to reflect the
1275 // unowned base shape of a new last property.
1276 MOZ_ASSERT(isOwned());
1277
1278 uint32_t span = slotSpan();
1279
1280 BaseShape::copyFromUnowned(*this, *other);
1281 setSlotSpan(span);
1282
1283 assertConsistency();
1284 }
1285
getUnowned(JSContext * cx,StackBaseShape & base)1286 /* static */ UnownedBaseShape* BaseShape::getUnowned(JSContext* cx,
1287 StackBaseShape& base) {
1288 auto& table = cx->zone()->baseShapes();
1289
1290 if (!table.initialized() && !table.init()) {
1291 ReportOutOfMemory(cx);
1292 return nullptr;
1293 }
1294
1295 auto p = MakeDependentAddPtr(cx, table, base);
1296 if (p) return *p;
1297
1298 BaseShape* nbase_ = Allocate<BaseShape>(cx);
1299 if (!nbase_) return nullptr;
1300
1301 new (nbase_) BaseShape(base);
1302
1303 UnownedBaseShape* nbase = static_cast<UnownedBaseShape*>(nbase_);
1304
1305 if (!p.add(cx, table, base, nbase)) return nullptr;
1306
1307 return nbase;
1308 }
1309
assertConsistency()1310 void BaseShape::assertConsistency() {
1311 #ifdef DEBUG
1312 if (isOwned()) {
1313 UnownedBaseShape* unowned = baseUnowned();
1314 MOZ_ASSERT(getObjectFlags() == unowned->getObjectFlags());
1315 }
1316 #endif
1317 }
1318
traceChildren(JSTracer * trc)1319 void BaseShape::traceChildren(JSTracer* trc) {
1320 traceChildrenSkipShapeTable(trc);
1321 traceShapeTable(trc);
1322 }
1323
traceChildrenSkipShapeTable(JSTracer * trc)1324 void BaseShape::traceChildrenSkipShapeTable(JSTracer* trc) {
1325 if (isOwned()) TraceEdge(trc, &unowned_, "base");
1326
1327 assertConsistency();
1328 }
1329
traceShapeTable(JSTracer * trc)1330 void BaseShape::traceShapeTable(JSTracer* trc) {
1331 AutoCheckCannotGC nogc;
1332 if (ShapeTable* table = maybeTable(nogc)) table->trace(trc);
1333 }
1334
1335 #ifdef DEBUG
canSkipMarkingShapeTable(Shape * lastShape)1336 bool BaseShape::canSkipMarkingShapeTable(Shape* lastShape) {
1337 // Check that every shape in the shape table will be marked by marking
1338 // |lastShape|.
1339
1340 AutoCheckCannotGC nogc;
1341 ShapeTable* table = maybeTable(nogc);
1342 if (!table) return true;
1343
1344 uint32_t count = 0;
1345 for (Shape::Range<NoGC> r(lastShape); !r.empty(); r.popFront()) {
1346 Shape* shape = &r.front();
1347 ShapeTable::Entry& entry =
1348 table->search<MaybeAdding::NotAdding>(shape->propid(), nogc);
1349 if (entry.isLive()) count++;
1350 }
1351
1352 return count == table->entryCount();
1353 }
1354 #endif
1355
1356 #ifdef JSGC_HASH_TABLE_CHECKS
1357
checkBaseShapeTableAfterMovingGC()1358 void Zone::checkBaseShapeTableAfterMovingGC() {
1359 if (!baseShapes().initialized()) return;
1360
1361 for (auto r = baseShapes().all(); !r.empty(); r.popFront()) {
1362 UnownedBaseShape* base = r.front().unbarrieredGet();
1363 CheckGCThingAfterMovingGC(base);
1364
1365 BaseShapeSet::Ptr ptr = baseShapes().lookup(base);
1366 MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
1367 }
1368 }
1369
1370 #endif // JSGC_HASH_TABLE_CHECKS
1371
finalize(FreeOp * fop)1372 void BaseShape::finalize(FreeOp* fop) {
1373 if (table_) {
1374 fop->delete_(table_);
1375 table_ = nullptr;
1376 }
1377 }
1378
InitialShapeEntry()1379 inline InitialShapeEntry::InitialShapeEntry() : shape(nullptr), proto() {}
1380
InitialShapeEntry(Shape * shape,const Lookup::ShapeProto & proto)1381 inline InitialShapeEntry::InitialShapeEntry(Shape* shape,
1382 const Lookup::ShapeProto& proto)
1383 : shape(shape), proto(proto) {}
1384
1385 #ifdef JSGC_HASH_TABLE_CHECKS
1386
checkInitialShapesTableAfterMovingGC()1387 void Zone::checkInitialShapesTableAfterMovingGC() {
1388 if (!initialShapes().initialized()) return;
1389
1390 /*
1391 * Assert that the postbarriers have worked and that nothing is left in
1392 * initialShapes that points into the nursery, and that the hash table
1393 * entries are discoverable.
1394 */
1395 for (auto r = initialShapes().all(); !r.empty(); r.popFront()) {
1396 InitialShapeEntry entry = r.front();
1397 JSProtoKey protoKey = entry.proto.key();
1398 TaggedProto proto = entry.proto.proto().unbarrieredGet();
1399 Shape* shape = entry.shape.unbarrieredGet();
1400
1401 CheckGCThingAfterMovingGC(shape);
1402 if (proto.isObject()) CheckGCThingAfterMovingGC(proto.toObject());
1403
1404 using Lookup = InitialShapeEntry::Lookup;
1405 Lookup lookup(shape->getObjectClass(), Lookup::ShapeProto(protoKey, proto),
1406 shape->numFixedSlots(), shape->getObjectFlags());
1407 InitialShapeSet::Ptr ptr = initialShapes().lookup(lookup);
1408 MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
1409 }
1410 }
1411
1412 #endif // JSGC_HASH_TABLE_CHECKS
1413
new_(JSContext * cx,Handle<UnownedBaseShape * > base,uint32_t nfixed)1414 Shape* EmptyShape::new_(JSContext* cx, Handle<UnownedBaseShape*> base,
1415 uint32_t nfixed) {
1416 Shape* shape = Allocate<Shape>(cx);
1417 if (!shape) {
1418 ReportOutOfMemory(cx);
1419 return nullptr;
1420 }
1421
1422 new (shape) EmptyShape(base, nfixed);
1423 return shape;
1424 }
1425
hash(const Lookup & l)1426 MOZ_ALWAYS_INLINE HashNumber ShapeHasher::hash(const Lookup& l) {
1427 return l.hash();
1428 }
1429
match(const Key k,const Lookup & l)1430 MOZ_ALWAYS_INLINE bool ShapeHasher::match(const Key k, const Lookup& l) {
1431 return k->matches(l);
1432 }
1433
HashChildren(Shape * kid1,Shape * kid2)1434 static KidsHash* HashChildren(Shape* kid1, Shape* kid2) {
1435 KidsHash* hash = js_new<KidsHash>();
1436 if (!hash || !hash->init(2)) {
1437 js_delete(hash);
1438 return nullptr;
1439 }
1440
1441 hash->putNewInfallible(StackShape(kid1), kid1);
1442 hash->putNewInfallible(StackShape(kid2), kid2);
1443 return hash;
1444 }
1445
insertChild(JSContext * cx,Shape * parent,Shape * child)1446 bool PropertyTree::insertChild(JSContext* cx, Shape* parent, Shape* child) {
1447 MOZ_ASSERT(!parent->inDictionary());
1448 MOZ_ASSERT(!child->parent);
1449 MOZ_ASSERT(!child->inDictionary());
1450 MOZ_ASSERT(child->zone() == parent->zone());
1451 MOZ_ASSERT(cx->zone() == zone_);
1452
1453 KidsPointer* kidp = &parent->kids;
1454
1455 if (kidp->isNull()) {
1456 child->setParent(parent);
1457 kidp->setShape(child);
1458 return true;
1459 }
1460
1461 if (kidp->isShape()) {
1462 Shape* shape = kidp->toShape();
1463 MOZ_ASSERT(shape != child);
1464 MOZ_ASSERT(!shape->matches(child));
1465
1466 KidsHash* hash = HashChildren(shape, child);
1467 if (!hash) {
1468 ReportOutOfMemory(cx);
1469 return false;
1470 }
1471 kidp->setHash(hash);
1472 child->setParent(parent);
1473 return true;
1474 }
1475
1476 if (!kidp->toHash()->putNew(StackShape(child), child)) {
1477 ReportOutOfMemory(cx);
1478 return false;
1479 }
1480
1481 child->setParent(parent);
1482 return true;
1483 }
1484
removeChild(Shape * child)1485 void Shape::removeChild(Shape* child) {
1486 MOZ_ASSERT(!child->inDictionary());
1487 MOZ_ASSERT(child->parent == this);
1488
1489 KidsPointer* kidp = &kids;
1490
1491 if (kidp->isShape()) {
1492 MOZ_ASSERT(kidp->toShape() == child);
1493 kidp->setNull();
1494 child->parent = nullptr;
1495 return;
1496 }
1497
1498 KidsHash* hash = kidp->toHash();
1499 MOZ_ASSERT(hash->count() >= 2); /* otherwise kidp->isShape() should be true */
1500
1501 #ifdef DEBUG
1502 size_t oldCount = hash->count();
1503 #endif
1504
1505 hash->remove(StackShape(child));
1506 child->parent = nullptr;
1507
1508 MOZ_ASSERT(hash->count() == oldCount - 1);
1509
1510 if (hash->count() == 1) {
1511 /* Convert from HASH form back to SHAPE form. */
1512 KidsHash::Range r = hash->all();
1513 Shape* otherChild = r.front();
1514 MOZ_ASSERT((r.popFront(), r.empty())); /* No more elements! */
1515 kidp->setShape(otherChild);
1516 js_delete(hash);
1517 }
1518 }
1519
inlinedGetChild(JSContext * cx,Shape * parent,Handle<StackShape> child)1520 MOZ_ALWAYS_INLINE Shape* PropertyTree::inlinedGetChild(
1521 JSContext* cx, Shape* parent, Handle<StackShape> child) {
1522 MOZ_ASSERT(parent);
1523
1524 Shape* existingShape = nullptr;
1525
1526 /*
1527 * The property tree has extremely low fan-out below its root in
1528 * popular embeddings with real-world workloads. Patterns such as
1529 * defining closures that capture a constructor's environment as
1530 * getters or setters on the new object that is passed in as
1531 * |this| can significantly increase fan-out below the property
1532 * tree root -- see bug 335700 for details.
1533 */
1534 KidsPointer* kidp = &parent->kids;
1535 if (kidp->isShape()) {
1536 Shape* kid = kidp->toShape();
1537 if (kid->matches(child)) existingShape = kid;
1538 } else if (kidp->isHash()) {
1539 if (KidsHash::Ptr p = kidp->toHash()->lookup(child)) existingShape = *p;
1540 } else {
1541 /* If kidp->isNull(), we always insert. */
1542 }
1543
1544 if (existingShape) {
1545 existingShape = PropertyTreeReadBarrier(parent, existingShape);
1546 if (existingShape) return existingShape;
1547 }
1548
1549 RootedShape parentRoot(cx, parent);
1550 Shape* shape = Shape::new_(cx, child, parentRoot->numFixedSlots());
1551 if (!shape) return nullptr;
1552
1553 if (!insertChild(cx, parentRoot, shape)) return nullptr;
1554
1555 return shape;
1556 }
1557
getChild(JSContext * cx,Shape * parent,Handle<StackShape> child)1558 Shape* PropertyTree::getChild(JSContext* cx, Shape* parent,
1559 Handle<StackShape> child) {
1560 return inlinedGetChild(cx, parent, child);
1561 }
1562
sweep()1563 void Shape::sweep() {
1564 /*
1565 * We detach the child from the parent if the parent is reachable.
1566 *
1567 * This test depends on shape arenas not being freed until after we finish
1568 * incrementally sweeping them. If that were not the case the parent pointer
1569 * could point to a marked cell that had been deallocated and then
1570 * reallocated, since allocating a cell in a zone that is being marked will
1571 * set the mark bit for that cell.
1572 */
1573 if (parent && parent->isMarkedAny()) {
1574 if (inDictionary()) {
1575 if (parent->listp == &parent) parent->listp = nullptr;
1576 } else {
1577 parent->removeChild(this);
1578 }
1579 }
1580 }
1581
finalize(FreeOp * fop)1582 void Shape::finalize(FreeOp* fop) {
1583 if (!inDictionary() && kids.isHash()) fop->delete_(kids.toHash());
1584 }
1585
fixupDictionaryShapeAfterMovingGC()1586 void Shape::fixupDictionaryShapeAfterMovingGC() {
1587 if (!listp) return;
1588
1589 // The listp field either points to the parent field of the next shape in
1590 // the list if there is one. Otherwise if this shape is the last in the
1591 // list then it points to the shape_ field of the object the list is for.
1592 // We can tell which it is because the base shape is owned if this is the
1593 // last property and not otherwise.
1594 bool listpPointsIntoShape = !MaybeForwarded(base())->isOwned();
1595
1596 #ifdef DEBUG
1597 // Check that we got this right by interrogating the arena.
1598 // We use a fake cell pointer for this: it might not point to the beginning
1599 // of a cell, but will point into the right arena and will have the right
1600 // alignment.
1601 Cell* cell = reinterpret_cast<Cell*>(uintptr_t(listp) & ~CellAlignMask);
1602 AllocKind kind = TenuredCell::fromPointer(cell)->getAllocKind();
1603 MOZ_ASSERT_IF(listpPointsIntoShape, IsShapeAllocKind(kind));
1604 MOZ_ASSERT_IF(!listpPointsIntoShape, IsObjectAllocKind(kind));
1605 #endif
1606
1607 if (listpPointsIntoShape) {
1608 // listp points to the parent field of the next shape.
1609 Shape* next = Shape::fromParentFieldPointer(uintptr_t(listp));
1610 if (gc::IsForwarded(next)) listp = &gc::Forwarded(next)->parent;
1611 } else {
1612 // listp points to the shape_ field of an object.
1613 JSObject* last = ShapedObject::fromShapeFieldPointer(uintptr_t(listp));
1614 if (gc::IsForwarded(last))
1615 listp = gc::Forwarded(last)->as<NativeObject>().shapePtr();
1616 }
1617 }
1618
fixupShapeTreeAfterMovingGC()1619 void Shape::fixupShapeTreeAfterMovingGC() {
1620 if (kids.isNull()) return;
1621
1622 if (kids.isShape()) {
1623 if (gc::IsForwarded(kids.toShape()))
1624 kids.setShape(gc::Forwarded(kids.toShape()));
1625 return;
1626 }
1627
1628 MOZ_ASSERT(kids.isHash());
1629 KidsHash* kh = kids.toHash();
1630 for (KidsHash::Enum e(*kh); !e.empty(); e.popFront()) {
1631 Shape* key = e.front();
1632 if (IsForwarded(key)) key = Forwarded(key);
1633
1634 BaseShape* base = key->base();
1635 if (IsForwarded(base)) base = Forwarded(base);
1636 UnownedBaseShape* unowned = base->unowned();
1637 if (IsForwarded(unowned)) unowned = Forwarded(unowned);
1638
1639 GetterOp getter = key->getter();
1640 if (key->hasGetterObject())
1641 getter = GetterOp(MaybeForwarded(key->getterObject()));
1642
1643 SetterOp setter = key->setter();
1644 if (key->hasSetterObject())
1645 setter = SetterOp(MaybeForwarded(key->setterObject()));
1646
1647 StackShape lookup(unowned, const_cast<Shape*>(key)->propidRef(),
1648 key->slotInfo & Shape::SLOT_MASK, key->attrs);
1649 lookup.updateGetterSetter(getter, setter);
1650 e.rekeyFront(lookup, key);
1651 }
1652 }
1653
fixupAfterMovingGC()1654 void Shape::fixupAfterMovingGC() {
1655 if (inDictionary())
1656 fixupDictionaryShapeAfterMovingGC();
1657 else
1658 fixupShapeTreeAfterMovingGC();
1659 }
1660
trace(JSTracer * trc)1661 void NurseryShapesRef::trace(JSTracer* trc) {
1662 auto& shapes = zone_->nurseryShapes();
1663 for (auto shape : shapes) shape->fixupGetterSetterForBarrier(trc);
1664 shapes.clearAndFree();
1665 }
1666
fixupGetterSetterForBarrier(JSTracer * trc)1667 void Shape::fixupGetterSetterForBarrier(JSTracer* trc) {
1668 if (!hasGetterValue() && !hasSetterValue()) return;
1669
1670 JSObject* priorGetter = asAccessorShape().getterObj;
1671 JSObject* priorSetter = asAccessorShape().setterObj;
1672 if (!priorGetter && !priorSetter) return;
1673
1674 JSObject* postGetter = priorGetter;
1675 JSObject* postSetter = priorSetter;
1676 if (priorGetter) TraceManuallyBarrieredEdge(trc, &postGetter, "getterObj");
1677 if (priorSetter) TraceManuallyBarrieredEdge(trc, &postSetter, "setterObj");
1678 if (priorGetter == postGetter && priorSetter == postSetter) return;
1679
1680 if (parent && !parent->inDictionary() && parent->kids.isHash()) {
1681 // Relocating the getterObj or setterObj will have changed our location
1682 // in our parent's KidsHash, so take care to update it. We must do this
1683 // before we update the shape itself, since the shape is used to match
1684 // the original entry in the hash set.
1685
1686 StackShape original(this);
1687 StackShape updated(this);
1688 updated.rawGetter = reinterpret_cast<GetterOp>(postGetter);
1689 updated.rawSetter = reinterpret_cast<SetterOp>(postSetter);
1690
1691 KidsHash* kh = parent->kids.toHash();
1692 MOZ_ALWAYS_TRUE(kh->rekeyAs(original, updated, this));
1693 }
1694
1695 asAccessorShape().getterObj = postGetter;
1696 asAccessorShape().setterObj = postSetter;
1697
1698 MOZ_ASSERT_IF(parent && !parent->inDictionary() && parent->kids.isHash(),
1699 parent->kids.toHash()->has(StackShape(this)));
1700 }
1701
1702 #ifdef DEBUG
1703
checkConsistency(Shape * aKid) const1704 void KidsPointer::checkConsistency(Shape* aKid) const {
1705 if (isShape()) {
1706 MOZ_ASSERT(toShape() == aKid);
1707 } else {
1708 MOZ_ASSERT(isHash());
1709 KidsHash* hash = toHash();
1710 KidsHash::Ptr ptr = hash->lookup(StackShape(aKid));
1711 MOZ_ASSERT(*ptr == aKid);
1712 }
1713 }
1714
dump(js::GenericPrinter & out) const1715 void Shape::dump(js::GenericPrinter& out) const {
1716 jsid propid = this->propid();
1717
1718 MOZ_ASSERT(!JSID_IS_VOID(propid));
1719
1720 if (JSID_IS_INT(propid)) {
1721 out.printf("[%ld]", (long)JSID_TO_INT(propid));
1722 } else if (JSID_IS_ATOM(propid)) {
1723 if (JSLinearString* str = JSID_TO_ATOM(propid))
1724 EscapedStringPrinter(out, str, '"');
1725 else
1726 out.put("<error>");
1727 } else {
1728 MOZ_ASSERT(JSID_IS_SYMBOL(propid));
1729 JSID_TO_SYMBOL(propid)->dump(out);
1730 }
1731
1732 out.printf(" g/s %p/%p slot %d attrs %x ",
1733 JS_FUNC_TO_DATA_PTR(void*, getter()),
1734 JS_FUNC_TO_DATA_PTR(void*, setter()),
1735 isDataProperty() ? slot() : -1, attrs);
1736
1737 if (attrs) {
1738 int first = 1;
1739 out.putChar('(');
1740 #define DUMP_ATTR(name, display) \
1741 if (attrs & JSPROP_##name) out.put(&(" " #display)[first]), first = 0
1742 DUMP_ATTR(ENUMERATE, enumerate);
1743 DUMP_ATTR(READONLY, readonly);
1744 DUMP_ATTR(PERMANENT, permanent);
1745 DUMP_ATTR(GETTER, getter);
1746 DUMP_ATTR(SETTER, setter);
1747 #undef DUMP_ATTR
1748 out.putChar(')');
1749 }
1750
1751 out.printf("flags %x ", flags);
1752 if (flags) {
1753 int first = 1;
1754 out.putChar('(');
1755 #define DUMP_FLAG(name, display) \
1756 if (flags & name) out.put(&(" " #display)[first]), first = 0
1757 DUMP_FLAG(IN_DICTIONARY, in_dictionary);
1758 #undef DUMP_FLAG
1759 out.putChar(')');
1760 }
1761 }
1762
dump() const1763 void Shape::dump() const {
1764 Fprinter out(stderr);
1765 dump(out);
1766 }
1767
dumpSubtree(int level,js::GenericPrinter & out) const1768 void Shape::dumpSubtree(int level, js::GenericPrinter& out) const {
1769 if (!parent) {
1770 MOZ_ASSERT(level == 0);
1771 MOZ_ASSERT(JSID_IS_EMPTY(propid_));
1772 out.printf("class %s emptyShape\n", getObjectClass()->name);
1773 } else {
1774 out.printf("%*sid ", level, "");
1775 dump(out);
1776 }
1777
1778 if (!kids.isNull()) {
1779 ++level;
1780 if (kids.isShape()) {
1781 Shape* kid = kids.toShape();
1782 MOZ_ASSERT(kid->parent == this);
1783 kid->dumpSubtree(level, out);
1784 } else {
1785 const KidsHash& hash = *kids.toHash();
1786 for (KidsHash::Range range = hash.all(); !range.empty();
1787 range.popFront()) {
1788 Shape* kid = range.front();
1789
1790 MOZ_ASSERT(kid->parent == this);
1791 kid->dumpSubtree(level, out);
1792 }
1793 }
1794 }
1795 }
1796
1797 #endif
1798
IsOriginalProto(GlobalObject * global,JSProtoKey key,JSObject & proto)1799 static bool IsOriginalProto(GlobalObject* global, JSProtoKey key,
1800 JSObject& proto) {
1801 if (global->getPrototype(key) != ObjectValue(proto)) return false;
1802
1803 if (key == JSProto_Object) {
1804 MOZ_ASSERT(proto.staticPrototypeIsImmutable(),
1805 "proto should be Object.prototype, whose prototype is "
1806 "immutable");
1807 MOZ_ASSERT(proto.staticPrototype() == nullptr,
1808 "Object.prototype must have null prototype");
1809 return true;
1810 }
1811
1812 // Check that other prototypes still have Object.prototype as proto.
1813 JSObject* protoProto = proto.staticPrototype();
1814 if (!protoProto ||
1815 global->getPrototype(JSProto_Object) != ObjectValue(*protoProto))
1816 return false;
1817
1818 MOZ_ASSERT(protoProto->staticPrototypeIsImmutable(),
1819 "protoProto should be Object.prototype, whose prototype is "
1820 "immutable");
1821 MOZ_ASSERT(protoProto->staticPrototype() == nullptr,
1822 "Object.prototype must have null prototype");
1823 return true;
1824 }
1825
GetInitialShapeProtoKey(TaggedProto proto,JSContext * cx)1826 static JSProtoKey GetInitialShapeProtoKey(TaggedProto proto, JSContext* cx) {
1827 if (proto.isObject() && proto.toObject()->hasStaticPrototype()) {
1828 GlobalObject* global = cx->global();
1829 JSObject& obj = *proto.toObject();
1830 MOZ_ASSERT(global == &obj.global());
1831
1832 if (IsOriginalProto(global, JSProto_Object, obj)) return JSProto_Object;
1833 if (IsOriginalProto(global, JSProto_Function, obj)) return JSProto_Function;
1834 if (IsOriginalProto(global, JSProto_Array, obj)) return JSProto_Array;
1835 if (IsOriginalProto(global, JSProto_RegExp, obj)) return JSProto_RegExp;
1836 }
1837 return JSProto_LIMIT;
1838 }
1839
getInitialShape(JSContext * cx,const Class * clasp,TaggedProto proto,size_t nfixed,uint32_t objectFlags)1840 /* static */ Shape* EmptyShape::getInitialShape(JSContext* cx,
1841 const Class* clasp,
1842 TaggedProto proto,
1843 size_t nfixed,
1844 uint32_t objectFlags) {
1845 MOZ_ASSERT_IF(proto.isObject(),
1846 cx->isInsideCurrentCompartment(proto.toObject()));
1847
1848 auto& table = cx->zone()->initialShapes();
1849
1850 if (!table.initialized() && !table.init()) {
1851 ReportOutOfMemory(cx);
1852 return nullptr;
1853 }
1854
1855 using Lookup = InitialShapeEntry::Lookup;
1856 auto protoPointer = MakeDependentAddPtr(
1857 cx, table, Lookup(clasp, Lookup::ShapeProto(proto), nfixed, objectFlags));
1858 if (protoPointer) return protoPointer->shape;
1859
1860 // No entry for this proto. If the proto is one of a few common builtin
1861 // prototypes, try to do a lookup based on the JSProtoKey, so we can share
1862 // shapes across globals.
1863 Rooted<TaggedProto> protoRoot(cx, proto);
1864 Shape* shape = nullptr;
1865 bool insertKey = false;
1866 mozilla::Maybe<DependentAddPtr<InitialShapeSet>> keyPointer;
1867
1868 JSProtoKey key = GetInitialShapeProtoKey(protoRoot, cx);
1869 if (key != JSProto_LIMIT) {
1870 keyPointer.emplace(MakeDependentAddPtr(
1871 cx, table,
1872 Lookup(clasp, Lookup::ShapeProto(key), nfixed, objectFlags)));
1873 if (keyPointer.ref()) {
1874 shape = keyPointer.ref()->shape;
1875 MOZ_ASSERT(shape);
1876 } else {
1877 insertKey = true;
1878 }
1879 }
1880
1881 if (!shape) {
1882 StackBaseShape base(clasp, objectFlags);
1883 Rooted<UnownedBaseShape*> nbase(cx, BaseShape::getUnowned(cx, base));
1884 if (!nbase) return nullptr;
1885
1886 shape = EmptyShape::new_(cx, nbase, nfixed);
1887 if (!shape) return nullptr;
1888 }
1889
1890 Lookup::ShapeProto shapeProto(protoRoot);
1891 Lookup lookup(clasp, shapeProto, nfixed, objectFlags);
1892 if (!protoPointer.add(cx, table, lookup,
1893 InitialShapeEntry(shape, shapeProto)))
1894 return nullptr;
1895
1896 // Also add an entry based on the JSProtoKey, if needed.
1897 if (insertKey) {
1898 Lookup::ShapeProto shapeProto(key);
1899 Lookup lookup(clasp, shapeProto, nfixed, objectFlags);
1900 if (!keyPointer->add(cx, table, lookup,
1901 InitialShapeEntry(shape, shapeProto)))
1902 return nullptr;
1903 }
1904
1905 return shape;
1906 }
1907
getInitialShape(JSContext * cx,const Class * clasp,TaggedProto proto,AllocKind kind,uint32_t objectFlags)1908 /* static */ Shape* EmptyShape::getInitialShape(JSContext* cx,
1909 const Class* clasp,
1910 TaggedProto proto,
1911 AllocKind kind,
1912 uint32_t objectFlags) {
1913 return getInitialShape(cx, clasp, proto, GetGCKindSlots(kind, clasp),
1914 objectFlags);
1915 }
1916
invalidateEntriesForShape(JSContext * cx,HandleShape shape,HandleObject proto)1917 void NewObjectCache::invalidateEntriesForShape(JSContext* cx, HandleShape shape,
1918 HandleObject proto) {
1919 const Class* clasp = shape->getObjectClass();
1920
1921 gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
1922 if (CanBeFinalizedInBackground(kind, clasp))
1923 kind = GetBackgroundAllocKind(kind);
1924
1925 RootedObjectGroup group(
1926 cx, ObjectGroup::defaultNewGroup(cx, clasp, TaggedProto(proto)));
1927 if (!group) {
1928 purge();
1929 cx->recoverFromOutOfMemory();
1930 return;
1931 }
1932
1933 EntryIndex entry;
1934 for (CompartmentsInZoneIter comp(shape->zone()); !comp.done(); comp.next()) {
1935 if (GlobalObject* global = comp->unsafeUnbarrieredMaybeGlobal()) {
1936 if (lookupGlobal(clasp, global, kind, &entry)) PodZero(&entries[entry]);
1937 }
1938 }
1939 if (!proto->is<GlobalObject>() && lookupProto(clasp, proto, kind, &entry))
1940 PodZero(&entries[entry]);
1941 if (lookupGroup(group, kind, &entry)) PodZero(&entries[entry]);
1942 }
1943
insertInitialShape(JSContext * cx,HandleShape shape,HandleObject proto)1944 /* static */ void EmptyShape::insertInitialShape(JSContext* cx,
1945 HandleShape shape,
1946 HandleObject proto) {
1947 using Lookup = InitialShapeEntry::Lookup;
1948 Lookup lookup(shape->getObjectClass(), Lookup::ShapeProto(TaggedProto(proto)),
1949 shape->numFixedSlots(), shape->getObjectFlags());
1950
1951 InitialShapeSet::Ptr p = cx->zone()->initialShapes().lookup(lookup);
1952 MOZ_ASSERT(p);
1953
1954 InitialShapeEntry& entry = const_cast<InitialShapeEntry&>(*p);
1955
1956 // The metadata callback can end up causing redundant changes of the initial
1957 // shape.
1958 if (entry.shape == shape) return;
1959
1960 // The new shape had better be rooted at the old one.
1961 #ifdef DEBUG
1962 Shape* nshape = shape;
1963 while (!nshape->isEmptyShape()) nshape = nshape->previous();
1964 MOZ_ASSERT(nshape == entry.shape);
1965 #endif
1966
1967 entry.shape = ReadBarrieredShape(shape);
1968
1969 // For certain prototypes -- namely, those of various builtin classes,
1970 // keyed by JSProtoKey |key| -- there are two entries: one for a lookup
1971 // via |proto|, and one for a lookup via |key|. If this is such a
1972 // prototype, also update the alternate |key|-keyed shape.
1973 JSProtoKey key = GetInitialShapeProtoKey(TaggedProto(proto), cx);
1974 if (key != JSProto_LIMIT) {
1975 Lookup lookup(shape->getObjectClass(), Lookup::ShapeProto(key),
1976 shape->numFixedSlots(), shape->getObjectFlags());
1977 if (InitialShapeSet::Ptr p = cx->zone()->initialShapes().lookup(lookup)) {
1978 InitialShapeEntry& entry = const_cast<InitialShapeEntry&>(*p);
1979 if (entry.shape != shape) entry.shape = ReadBarrieredShape(shape);
1980 }
1981 }
1982
1983 /*
1984 * This affects the shape that will be produced by the various NewObject
1985 * methods, so clear any cache entry referring to the old shape. This is
1986 * not required for correctness: the NewObject must always check for a
1987 * nativeEmpty() result and generate the appropriate properties if found.
1988 * Clearing the cache entry avoids this duplicate regeneration.
1989 *
1990 * Clearing is not necessary when this context is running off
1991 * thread, as it will not use the new object cache for allocations.
1992 */
1993 if (!cx->helperThread())
1994 cx->caches().newObjectCache.invalidateEntriesForShape(cx, shape, proto);
1995 }
1996
fixupInitialShapeTable()1997 void Zone::fixupInitialShapeTable() {
1998 if (!initialShapes().initialized()) return;
1999
2000 for (InitialShapeSet::Enum e(initialShapes()); !e.empty(); e.popFront()) {
2001 // The shape may have been moved, but we can update that in place.
2002 Shape* shape = e.front().shape.unbarrieredGet();
2003 if (IsForwarded(shape)) {
2004 shape = Forwarded(shape);
2005 e.mutableFront().shape.set(shape);
2006 }
2007 shape->updateBaseShapeAfterMovingGC();
2008
2009 // If the prototype has moved we have to rekey the entry.
2010 InitialShapeEntry entry = e.front();
2011 if (entry.proto.proto().isObject() &&
2012 IsForwarded(entry.proto.proto().toObject())) {
2013 entry.proto.setProto(
2014 TaggedProto(Forwarded(entry.proto.proto().toObject())));
2015 using Lookup = InitialShapeEntry::Lookup;
2016 Lookup relookup(shape->getObjectClass(), Lookup::ShapeProto(entry.proto),
2017 shape->numFixedSlots(), shape->getObjectFlags());
2018 e.rekeyFront(relookup, entry);
2019 }
2020 }
2021 }
2022
trace(JSTracer * trc)2023 void AutoRooterGetterSetter::Inner::trace(JSTracer* trc) {
2024 if ((attrs & JSPROP_GETTER) && *pgetter)
2025 TraceRoot(trc, (JSObject**)pgetter, "AutoRooterGetterSetter getter");
2026 if ((attrs & JSPROP_SETTER) && *psetter)
2027 TraceRoot(trc, (JSObject**)psetter, "AutoRooterGetterSetter setter");
2028 }
2029
size(mozilla::MallocSizeOf mallocSizeOf) const2030 JS::ubi::Node::Size JS::ubi::Concrete<js::Shape>::size(
2031 mozilla::MallocSizeOf mallocSizeOf) const {
2032 Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
2033
2034 AutoCheckCannotGC nogc;
2035 if (ShapeTable* table = get().maybeTable(nogc))
2036 size += table->sizeOfIncludingThis(mallocSizeOf);
2037
2038 if (!get().inDictionary() && get().kids.isHash())
2039 size += get().kids.toHash()->sizeOfIncludingThis(mallocSizeOf);
2040
2041 return size;
2042 }
2043
size(mozilla::MallocSizeOf mallocSizeOf) const2044 JS::ubi::Node::Size JS::ubi::Concrete<js::BaseShape>::size(
2045 mozilla::MallocSizeOf mallocSizeOf) const {
2046 return js::gc::Arena::thingSize(get().asTenured().getAllocKind());
2047 }
2048
trace(JSTracer * trc)2049 void PropertyResult::trace(JSTracer* trc) {
2050 if (isNativeProperty()) TraceRoot(trc, &shape_, "PropertyResult::shape_");
2051 }
2052