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