1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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 #include "vm/Shape-inl.h"
8
9 #include "mozilla/MathAlgorithms.h"
10 #include "mozilla/PodOperations.h"
11
12 #include "gc/FreeOp.h"
13 #include "gc/HashUtil.h"
14 #include "gc/Policy.h"
15 #include "gc/PublicIterators.h"
16 #include "js/friend/WindowProxy.h" // js::IsWindow
17 #include "js/HashTable.h"
18 #include "js/UniquePtr.h"
19 #include "util/Text.h"
20 #include "vm/JSAtom.h"
21 #include "vm/JSContext.h"
22 #include "vm/JSObject.h"
23 #include "vm/ShapeZone.h"
24
25 #include "vm/Caches-inl.h"
26 #include "vm/JSContext-inl.h"
27 #include "vm/JSObject-inl.h"
28 #include "vm/NativeObject-inl.h"
29 #include "vm/ObjectFlags-inl.h"
30 #include "vm/Realm-inl.h"
31
32 using namespace js;
33
34 using mozilla::CeilingLog2Size;
35 using mozilla::PodZero;
36
37 using JS::AutoCheckCannotGC;
38
39 /* static */
replaceShape(JSContext * cx,HandleObject obj,ObjectFlags objectFlags,TaggedProto proto,uint32_t nfixed)40 bool Shape::replaceShape(JSContext* cx, HandleObject obj,
41 ObjectFlags objectFlags, TaggedProto proto,
42 uint32_t nfixed) {
43 MOZ_ASSERT(!obj->shape()->isDictionary());
44
45 Shape* newShape;
46 if (obj->shape()->propMap()) {
47 Rooted<BaseShape*> base(cx, obj->shape()->base());
48 if (proto != base->proto()) {
49 Rooted<TaggedProto> protoRoot(cx, proto);
50 base = BaseShape::get(cx, base->clasp(), base->realm(), protoRoot);
51 if (!base) {
52 return false;
53 }
54 }
55 Rooted<SharedPropMap*> map(cx, obj->shape()->sharedPropMap());
56 uint32_t mapLength = obj->shape()->propMapLength();
57 newShape = SharedShape::getPropMapShape(cx, base, nfixed, map, mapLength,
58 objectFlags);
59 } else {
60 newShape = SharedShape::getInitialShape(cx, obj->shape()->getObjectClass(),
61 obj->shape()->realm(), proto,
62 nfixed, objectFlags);
63 }
64 if (!newShape) {
65 return false;
66 }
67
68 obj->setShape(newShape);
69 return true;
70 }
71
72 /* static */
toDictionaryMode(JSContext * cx,HandleNativeObject obj)73 bool js::NativeObject::toDictionaryMode(JSContext* cx, HandleNativeObject obj) {
74 MOZ_ASSERT(!obj->inDictionaryMode());
75 MOZ_ASSERT(cx->isInsideCurrentCompartment(obj));
76
77 RootedShape shape(cx, obj->shape());
78 uint32_t span = obj->slotSpan();
79
80 uint32_t mapLength = shape->propMapLength();
81 MOZ_ASSERT(mapLength > 0, "shouldn't convert empty object to dictionary");
82
83 // Clone the shared property map to an unshared dictionary map.
84 Rooted<SharedPropMap*> map(cx, shape->propMap()->asShared());
85 Rooted<DictionaryPropMap*> dictMap(
86 cx, SharedPropMap::toDictionaryMap(cx, map, mapLength));
87 if (!dictMap) {
88 return false;
89 }
90
91 // Allocate and use a new dictionary shape.
92 Rooted<BaseShape*> base(cx, shape->base());
93 shape = DictionaryShape::new_(cx, base, shape->objectFlags(),
94 shape->numFixedSlots(), dictMap, mapLength);
95 if (!shape) {
96 return false;
97 }
98 obj->setShape(shape);
99
100 MOZ_ASSERT(obj->inDictionaryMode());
101 obj->setDictionaryModeSlotSpan(span);
102
103 return true;
104 }
105
106 namespace js {
107
108 class MOZ_RAII AutoCheckShapeConsistency {
109 #ifdef DEBUG
110 HandleNativeObject obj_;
111 #endif
112
113 public:
AutoCheckShapeConsistency(HandleNativeObject obj)114 explicit AutoCheckShapeConsistency(HandleNativeObject obj)
115 #ifdef DEBUG
116 : obj_(obj)
117 #endif
118 {
119 }
120
121 #ifdef DEBUG
~AutoCheckShapeConsistency()122 ~AutoCheckShapeConsistency() { obj_->checkShapeConsistency(); }
123 #endif
124 };
125
126 } // namespace js
127
128 /* static */ MOZ_ALWAYS_INLINE bool
maybeConvertToDictionaryForAdd(JSContext * cx,HandleNativeObject obj)129 NativeObject::maybeConvertToDictionaryForAdd(JSContext* cx,
130 HandleNativeObject obj) {
131 if (obj->inDictionaryMode()) {
132 return true;
133 }
134 SharedPropMap* map = obj->shape()->sharedPropMap();
135 if (!map) {
136 return true;
137 }
138 if (MOZ_LIKELY(!map->shouldConvertToDictionaryForAdd())) {
139 return true;
140 }
141 return toDictionaryMode(cx, obj);
142 }
143
AssertValidCustomDataProp(NativeObject * obj,PropertyFlags flags)144 static void AssertValidCustomDataProp(NativeObject* obj, PropertyFlags flags) {
145 // We only support custom data properties on ArrayObject and ArgumentsObject.
146 // The mechanism is deprecated so we don't want to add new uses.
147 MOZ_ASSERT(flags.isCustomDataProperty());
148 MOZ_ASSERT(!flags.isAccessorProperty());
149 MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<ArgumentsObject>());
150 }
151
152 /* static */
addCustomDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,PropertyFlags flags)153 bool NativeObject::addCustomDataProperty(JSContext* cx, HandleNativeObject obj,
154 HandleId id, PropertyFlags flags) {
155 MOZ_ASSERT(!JSID_IS_VOID(id));
156 MOZ_ASSERT(!id.isPrivateName());
157 MOZ_ASSERT(!obj->containsPure(id));
158
159 AutoCheckShapeConsistency check(obj);
160 AssertValidCustomDataProp(obj, flags);
161
162 if (!maybeConvertToDictionaryForAdd(cx, obj)) {
163 return false;
164 }
165
166 ObjectFlags objectFlags = obj->shape()->objectFlags();
167 const JSClass* clasp = obj->shape()->getObjectClass();
168
169 if (obj->inDictionaryMode()) {
170 // First generate a new dictionary shape so that the map can be mutated
171 // without having to worry about OOM conditions.
172 if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
173 return false;
174 }
175
176 Rooted<DictionaryPropMap*> map(cx, obj->shape()->dictionaryPropMap());
177 uint32_t mapLength = obj->shape()->propMapLength();
178 if (!DictionaryPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags,
179 SHAPE_INVALID_SLOT, &objectFlags)) {
180 return false;
181 }
182
183 obj->shape()->updateNewDictionaryShape(objectFlags, map, mapLength);
184 return true;
185 }
186
187 Rooted<SharedPropMap*> map(cx, obj->shape()->sharedPropMap());
188 uint32_t mapLength = obj->shape()->propMapLength();
189 if (!SharedPropMap::addCustomDataProperty(cx, clasp, &map, &mapLength, id,
190 flags, &objectFlags)) {
191 return false;
192 }
193
194 Shape* shape = SharedShape::getPropMapShape(cx, obj->shape()->base(),
195 obj->shape()->numFixedSlots(),
196 map, mapLength, objectFlags);
197 if (!shape) {
198 return false;
199 }
200
201 obj->setShape(shape);
202 return true;
203 }
204
MakeShapeSetForAdd(Shape * shape1,Shape * shape2)205 static ShapeSetForAdd* MakeShapeSetForAdd(Shape* shape1, Shape* shape2) {
206 MOZ_ASSERT(shape1 != shape2);
207 MOZ_ASSERT(shape1->propMapLength() == shape2->propMapLength());
208
209 auto hash = MakeUnique<ShapeSetForAdd>();
210 if (!hash || !hash->reserve(2)) {
211 return nullptr;
212 }
213
214 PropertyInfoWithKey prop = shape1->lastProperty();
215 hash->putNewInfallible(ShapeForAddHasher::Lookup(prop.key(), prop.flags()),
216 shape1);
217
218 prop = shape2->lastProperty();
219 hash->putNewInfallible(ShapeForAddHasher::Lookup(prop.key(), prop.flags()),
220 shape2);
221
222 return hash.release();
223 }
224
LookupShapeForAdd(Shape * shape,PropertyKey key,PropertyFlags flags,uint32_t * slot)225 static MOZ_ALWAYS_INLINE Shape* LookupShapeForAdd(Shape* shape, PropertyKey key,
226 PropertyFlags flags,
227 uint32_t* slot) {
228 ShapeCachePtr cache = shape->cache();
229
230 if (cache.isSingleShapeForAdd()) {
231 Shape* newShape = cache.toSingleShapeForAdd();
232 if (newShape->lastPropertyMatchesForAdd(key, flags, slot)) {
233 return newShape;
234 }
235 return nullptr;
236 }
237
238 if (cache.isShapeSetForAdd()) {
239 ShapeSetForAdd* set = cache.toShapeSetForAdd();
240 ShapeForAddHasher::Lookup lookup(key, flags);
241 if (auto p = set->lookup(lookup)) {
242 Shape* newShape = *p;
243 *slot = newShape->lastProperty().slot();
244 return newShape;
245 }
246 return nullptr;
247 }
248
249 MOZ_ASSERT(cache.isNone() || cache.isShapeWithProto());
250 return nullptr;
251 }
252
253 // Add shapes with a non-None ShapeCachePtr to the shapesWithCache list so that
254 // these caches can be discarded on GC.
RegisterShapeCache(JSContext * cx,Shape * shape)255 static bool RegisterShapeCache(JSContext* cx, Shape* shape) {
256 ShapeCachePtr cache = shape->cache();
257 if (!cache.isNone()) {
258 // Already registered this shape.
259 return true;
260 }
261 return cx->zone()->shapeZone().shapesWithCache.append(shape);
262 }
263
264 /* static */
addProperty(JSContext * cx,HandleNativeObject obj,HandleId id,PropertyFlags flags,uint32_t * slot)265 bool NativeObject::addProperty(JSContext* cx, HandleNativeObject obj,
266 HandleId id, PropertyFlags flags,
267 uint32_t* slot) {
268 AutoCheckShapeConsistency check(obj);
269 MOZ_ASSERT(!flags.isCustomDataProperty(),
270 "Use addCustomDataProperty for custom data properties");
271
272 // The object must not contain a property named |id|. The object must be
273 // extensible, but allow private fields and sparsifying dense elements.
274 MOZ_ASSERT(!JSID_IS_VOID(id));
275 MOZ_ASSERT(!obj->containsPure(id));
276 MOZ_ASSERT_IF(
277 !id.isPrivateName(),
278 obj->isExtensible() ||
279 (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))));
280
281 if (!maybeConvertToDictionaryForAdd(cx, obj)) {
282 return false;
283 }
284
285 if (Shape* shape = LookupShapeForAdd(obj->shape(), id, flags, slot)) {
286 return obj->setShapeAndUpdateSlotsForNewSlot(cx, shape, *slot);
287 }
288
289 if (obj->inDictionaryMode()) {
290 // First generate a new dictionary shape so that the map and shape can be
291 // mutated without having to worry about OOM conditions.
292 if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
293 return false;
294 }
295 if (!allocDictionarySlot(cx, obj, slot)) {
296 return false;
297 }
298
299 ObjectFlags objectFlags = obj->shape()->objectFlags();
300 const JSClass* clasp = obj->shape()->getObjectClass();
301
302 Rooted<DictionaryPropMap*> map(cx, obj->shape()->propMap()->asDictionary());
303 uint32_t mapLength = obj->shape()->propMapLength();
304 if (!DictionaryPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags,
305 *slot, &objectFlags)) {
306 return false;
307 }
308
309 obj->shape()->updateNewDictionaryShape(objectFlags, map, mapLength);
310 return true;
311 }
312
313 ObjectFlags objectFlags = obj->shape()->objectFlags();
314 const JSClass* clasp = obj->shape()->getObjectClass();
315
316 Rooted<SharedPropMap*> map(cx, obj->shape()->sharedPropMap());
317 uint32_t mapLength = obj->shape()->propMapLength();
318
319 if (!SharedPropMap::addProperty(cx, clasp, &map, &mapLength, id, flags,
320 &objectFlags, slot)) {
321 return false;
322 }
323
324 bool allocatedNewShape;
325 Shape* newShape = SharedShape::getPropMapShape(
326 cx, obj->shape()->base(), obj->shape()->numFixedSlots(), map, mapLength,
327 objectFlags, &allocatedNewShape);
328 if (!newShape) {
329 return false;
330 }
331
332 Shape* oldShape = obj->shape();
333 if (!obj->setShapeAndUpdateSlotsForNewSlot(cx, newShape, *slot)) {
334 return false;
335 }
336
337 // Add the new shape to the old shape's shape cache, to optimize this shape
338 // transition. Don't do this if we just allocated a new shape, because that
339 // suggests this may not be a hot transition that would benefit from the
340 // cache.
341
342 if (allocatedNewShape) {
343 return true;
344 }
345
346 if (!RegisterShapeCache(cx, oldShape)) {
347 // Ignore OOM, the cache is just an optimization.
348 return true;
349 }
350
351 ShapeCachePtr& cache = oldShape->cacheRef();
352 if (cache.isNone() || cache.isShapeWithProto()) {
353 cache.setSingleShapeForAdd(newShape);
354 } else if (cache.isSingleShapeForAdd()) {
355 Shape* prevShape = cache.toSingleShapeForAdd();
356 if (ShapeSetForAdd* set = MakeShapeSetForAdd(prevShape, newShape)) {
357 cache.setShapeSetForAdd(set);
358 AddCellMemory(oldShape, sizeof(ShapeSetForAdd),
359 MemoryUse::ShapeSetForAdd);
360 }
361 } else {
362 ShapeForAddHasher::Lookup lookup(id, flags);
363 (void)cache.toShapeSetForAdd()->putNew(lookup, newShape);
364 }
365
366 return true;
367 }
368
369 /* static */
addPropertyInReservedSlot(JSContext * cx,HandleNativeObject obj,HandleId id,uint32_t slot,PropertyFlags flags)370 bool NativeObject::addPropertyInReservedSlot(JSContext* cx,
371 HandleNativeObject obj,
372 HandleId id, uint32_t slot,
373 PropertyFlags flags) {
374 AutoCheckShapeConsistency check(obj);
375 MOZ_ASSERT(!flags.isCustomDataProperty(),
376 "Use addCustomDataProperty for custom data properties");
377
378 // The slot must be a reserved slot.
379 MOZ_ASSERT(slot < JSCLASS_RESERVED_SLOTS(obj->getClass()));
380
381 // The object must not contain a property named |id| and must be extensible.
382 MOZ_ASSERT(!JSID_IS_VOID(id));
383 MOZ_ASSERT(!obj->containsPure(id));
384 MOZ_ASSERT(!id.isPrivateName());
385 MOZ_ASSERT(obj->isExtensible());
386
387 // The object must not be in dictionary mode. This simplifies the code below.
388 MOZ_ASSERT(!obj->inDictionaryMode());
389
390 ObjectFlags objectFlags = obj->shape()->objectFlags();
391 const JSClass* clasp = obj->shape()->getObjectClass();
392
393 Rooted<SharedPropMap*> map(cx, obj->shape()->sharedPropMap());
394 uint32_t mapLength = obj->shape()->propMapLength();
395 if (!SharedPropMap::addPropertyInReservedSlot(cx, clasp, &map, &mapLength, id,
396 flags, slot, &objectFlags)) {
397 return false;
398 }
399
400 Shape* shape = SharedShape::getPropMapShape(cx, obj->shape()->base(),
401 obj->shape()->numFixedSlots(),
402 map, mapLength, objectFlags);
403 if (!shape) {
404 return false;
405 }
406 obj->setShape(shape);
407
408 MOZ_ASSERT(obj->getLastProperty().slot() == slot);
409 return true;
410 }
411
412 /*
413 * Assert some invariants that should hold when changing properties. It's the
414 * responsibility of the callers to ensure these hold.
415 */
AssertCanChangeFlags(PropertyInfo prop,PropertyFlags flags)416 static void AssertCanChangeFlags(PropertyInfo prop, PropertyFlags flags) {
417 #ifdef DEBUG
418 if (prop.configurable()) {
419 return;
420 }
421
422 // A non-configurable property must stay non-configurable.
423 MOZ_ASSERT(!flags.configurable());
424
425 // Reject attempts to turn a non-configurable data property into an accessor
426 // or custom data property.
427 MOZ_ASSERT_IF(prop.isDataProperty(), flags.isDataProperty());
428
429 // Reject attempts to turn a non-configurable accessor property into a data
430 // property or custom data property.
431 MOZ_ASSERT_IF(prop.isAccessorProperty(), flags.isAccessorProperty());
432 #endif
433 }
434
AssertValidArrayIndex(NativeObject * obj,jsid id)435 static void AssertValidArrayIndex(NativeObject* obj, jsid id) {
436 #ifdef DEBUG
437 if (obj->is<ArrayObject>()) {
438 ArrayObject* arr = &obj->as<ArrayObject>();
439 uint32_t index;
440 if (IdIsIndex(id, &index)) {
441 MOZ_ASSERT(index < arr->length() || arr->lengthIsWritable());
442 }
443 }
444 #endif
445 }
446
447 /* static */
changeProperty(JSContext * cx,HandleNativeObject obj,HandleId id,PropertyFlags flags,uint32_t * slotOut)448 bool NativeObject::changeProperty(JSContext* cx, HandleNativeObject obj,
449 HandleId id, PropertyFlags flags,
450 uint32_t* slotOut) {
451 MOZ_ASSERT(!JSID_IS_VOID(id));
452
453 AutoCheckShapeConsistency check(obj);
454 AssertValidArrayIndex(obj, id);
455 MOZ_ASSERT(!flags.isCustomDataProperty(),
456 "Use changeCustomDataPropAttributes for custom data properties");
457
458 Rooted<PropMap*> map(cx, obj->shape()->propMap());
459 uint32_t mapLength = obj->shape()->propMapLength();
460
461 uint32_t propIndex;
462 Rooted<PropMap*> propMap(cx, map->lookup(cx, mapLength, id, &propIndex));
463 MOZ_ASSERT(propMap);
464
465 ObjectFlags objectFlags = obj->shape()->objectFlags();
466
467 PropertyInfo oldProp = propMap->getPropertyInfo(propIndex);
468 AssertCanChangeFlags(oldProp, flags);
469
470 if (oldProp.isAccessorProperty()) {
471 objectFlags.setFlag(ObjectFlag::HadGetterSetterChange);
472 }
473
474 // If the property flags are not changing, the only thing we have to do is
475 // update the object flags. This prevents a dictionary mode conversion below.
476 if (oldProp.flags() == flags) {
477 if (objectFlags == obj->shape()->objectFlags()) {
478 *slotOut = oldProp.slot();
479 return true;
480 }
481 if (map->isShared()) {
482 if (!Shape::replaceShape(cx, obj, objectFlags, obj->shape()->proto(),
483 obj->shape()->numFixedSlots())) {
484 return false;
485 }
486 *slotOut = oldProp.slot();
487 return true;
488 }
489 }
490
491 const JSClass* clasp = obj->shape()->getObjectClass();
492
493 if (map->isShared()) {
494 // Fast path for changing the last property in a SharedPropMap. Call
495 // getPrevious to "remove" the last property and then call addProperty
496 // to re-add the last property with the new flags.
497 if (propMap == map && propIndex == mapLength - 1) {
498 MOZ_ASSERT(obj->getLastProperty().key() == id);
499
500 Rooted<SharedPropMap*> sharedMap(cx, map->asShared());
501 SharedPropMap::getPrevious(&sharedMap, &mapLength);
502
503 if (oldProp.hasSlot()) {
504 *slotOut = oldProp.slot();
505 if (!SharedPropMap::addPropertyWithKnownSlot(cx, clasp, &sharedMap,
506 &mapLength, id, flags,
507 *slotOut, &objectFlags)) {
508 return false;
509 }
510 } else {
511 if (!SharedPropMap::addProperty(cx, clasp, &sharedMap, &mapLength, id,
512 flags, &objectFlags, slotOut)) {
513 return false;
514 }
515 }
516
517 Shape* newShape = SharedShape::getPropMapShape(
518 cx, obj->shape()->base(), obj->shape()->numFixedSlots(), sharedMap,
519 mapLength, objectFlags);
520 if (!newShape) {
521 return false;
522 }
523 return obj->setShapeAndUpdateSlots(cx, newShape);
524 }
525
526 // Changing a non-last property. Switch to dictionary mode and relookup
527 // pointers for the new dictionary map.
528 if (!NativeObject::toDictionaryMode(cx, obj)) {
529 return false;
530 }
531 map = obj->shape()->propMap();
532 propMap = map->lookup(cx, mapLength, id, &propIndex);
533 MOZ_ASSERT(propMap);
534 } else {
535 if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
536 return false;
537 }
538 }
539
540 // The object has a new dictionary shape (see toDictionaryMode and
541 // generateNewDictionaryShape calls above), so we can mutate the map and shape
542 // in place.
543
544 MOZ_ASSERT(map->isDictionary());
545 MOZ_ASSERT(propMap->isDictionary());
546
547 uint32_t slot = oldProp.hasSlot() ? oldProp.slot() : SHAPE_INVALID_SLOT;
548 if (slot == SHAPE_INVALID_SLOT) {
549 if (!allocDictionarySlot(cx, obj, &slot)) {
550 return false;
551 }
552 }
553
554 propMap->asDictionary()->changeProperty(cx, clasp, propIndex, flags, slot,
555 &objectFlags);
556 obj->shape()->setObjectFlags(objectFlags);
557
558 *slotOut = slot;
559 return true;
560 }
561
562 /* static */
changeCustomDataPropAttributes(JSContext * cx,HandleNativeObject obj,HandleId id,PropertyFlags flags)563 bool NativeObject::changeCustomDataPropAttributes(JSContext* cx,
564 HandleNativeObject obj,
565 HandleId id,
566 PropertyFlags flags) {
567 MOZ_ASSERT(!JSID_IS_VOID(id));
568
569 AutoCheckShapeConsistency check(obj);
570 AssertValidArrayIndex(obj, id);
571 AssertValidCustomDataProp(obj, flags);
572
573 Rooted<PropMap*> map(cx, obj->shape()->propMap());
574 uint32_t mapLength = obj->shape()->propMapLength();
575
576 uint32_t propIndex;
577 Rooted<PropMap*> propMap(cx, map->lookup(cx, mapLength, id, &propIndex));
578 MOZ_ASSERT(propMap);
579
580 PropertyInfo oldProp = propMap->getPropertyInfo(propIndex);
581 MOZ_ASSERT(oldProp.isCustomDataProperty());
582 AssertCanChangeFlags(oldProp, flags);
583
584 // If the property flags are not changing, we're done.
585 if (oldProp.flags() == flags) {
586 return true;
587 }
588
589 const JSClass* clasp = obj->shape()->getObjectClass();
590 ObjectFlags objectFlags = obj->shape()->objectFlags();
591
592 if (map->isShared()) {
593 // Fast path for changing the last property in a SharedPropMap. Call
594 // getPrevious to "remove" the last property and then call
595 // addCustomDataProperty to re-add the last property with the new flags.
596 if (propMap == map && propIndex == mapLength - 1) {
597 MOZ_ASSERT(obj->getLastProperty().key() == id);
598
599 Rooted<SharedPropMap*> sharedMap(cx, map->asShared());
600 SharedPropMap::getPrevious(&sharedMap, &mapLength);
601
602 if (!SharedPropMap::addCustomDataProperty(
603 cx, clasp, &sharedMap, &mapLength, id, flags, &objectFlags)) {
604 return false;
605 }
606
607 Shape* newShape = SharedShape::getPropMapShape(
608 cx, obj->shape()->base(), obj->shape()->numFixedSlots(), sharedMap,
609 mapLength, objectFlags);
610 if (!newShape) {
611 return false;
612 }
613 obj->setShape(newShape);
614 return true;
615 }
616
617 // Changing a non-last property. Switch to dictionary mode and relookup
618 // pointers for the new dictionary map.
619 if (!NativeObject::toDictionaryMode(cx, obj)) {
620 return false;
621 }
622 map = obj->shape()->propMap();
623 propMap = map->lookup(cx, mapLength, id, &propIndex);
624 MOZ_ASSERT(propMap);
625 } else {
626 if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
627 return false;
628 }
629 }
630
631 // The object has a new dictionary shape (see toDictionaryMode and
632 // generateNewDictionaryShape calls above), so we can mutate the map and shape
633 // in place.
634
635 MOZ_ASSERT(map->isDictionary());
636 MOZ_ASSERT(propMap->isDictionary());
637
638 propMap->asDictionary()->changePropertyFlags(cx, clasp, propIndex, flags,
639 &objectFlags);
640 obj->shape()->setObjectFlags(objectFlags);
641 return true;
642 }
643
644 /* static */
removeProperty(JSContext * cx,HandleNativeObject obj,HandleId id)645 bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
646 HandleId id) {
647 AutoCheckShapeConsistency check(obj);
648
649 Rooted<PropMap*> map(cx, obj->shape()->propMap());
650 uint32_t mapLength = obj->shape()->propMapLength();
651
652 AutoKeepPropMapTables keep(cx);
653 PropMapTable* table;
654 PropMapTable::Ptr ptr;
655 Rooted<PropMap*> propMap(cx);
656 uint32_t propIndex;
657 if (!PropMap::lookupForRemove(cx, map, mapLength, id, keep, propMap.address(),
658 &propIndex, &table, &ptr)) {
659 return false;
660 }
661
662 if (!propMap) {
663 return true;
664 }
665
666 PropertyInfo prop = propMap->getPropertyInfo(propIndex);
667
668 // If we're removing an accessor property, ensure the HadGetterSetterChange
669 // object flag is set. This is necessary because the slot holding the
670 // GetterSetter can be changed indirectly by removing the property and then
671 // adding it back with a different GetterSetter value but the same shape.
672 if (prop.isAccessorProperty() && !obj->hadGetterSetterChange()) {
673 if (!NativeObject::setHadGetterSetterChange(cx, obj)) {
674 return false;
675 }
676 }
677
678 if (map->isShared()) {
679 // Fast path for removing the last property from a SharedPropMap. In this
680 // case we can just call getPrevious and then look up a shape for the
681 // resulting map/mapLength.
682 if (propMap == map && propIndex == mapLength - 1) {
683 MOZ_ASSERT(obj->getLastProperty().key() == id);
684
685 Rooted<SharedPropMap*> sharedMap(cx, map->asShared());
686 SharedPropMap::getPrevious(&sharedMap, &mapLength);
687
688 Shape* shape = obj->shape();
689 Shape* newShape;
690 if (sharedMap) {
691 newShape = SharedShape::getPropMapShape(
692 cx, shape->base(), shape->numFixedSlots(), sharedMap, mapLength,
693 shape->objectFlags());
694 } else {
695 newShape = SharedShape::getInitialShape(
696 cx, shape->getObjectClass(), shape->realm(), shape->proto(),
697 shape->numFixedSlots(), shape->objectFlags());
698 }
699 if (!newShape) {
700 return false;
701 }
702
703 if (prop.hasSlot()) {
704 obj->setSlot(prop.slot(), UndefinedValue());
705 }
706 MOZ_ALWAYS_TRUE(obj->setShapeAndUpdateSlots(cx, newShape));
707 return true;
708 }
709
710 // Removing a non-last property. Switch to dictionary mode and relookup
711 // pointers for the new dictionary map.
712 if (!NativeObject::toDictionaryMode(cx, obj)) {
713 return false;
714 }
715 map = obj->shape()->propMap();
716 if (!PropMap::lookupForRemove(cx, map, mapLength, id, keep,
717 propMap.address(), &propIndex, &table,
718 &ptr)) {
719 return false;
720 }
721 } else {
722 if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
723 return false;
724 }
725 }
726
727 // The object has a new dictionary shape (see toDictionaryMode and
728 // generateNewDictionaryShape calls above), so we can mutate the map and shape
729 // in place.
730
731 MOZ_ASSERT(map->isDictionary());
732 MOZ_ASSERT(table);
733 MOZ_ASSERT(prop == ptr->propertyInfo());
734
735 Rooted<DictionaryPropMap*> dictMap(cx, map->asDictionary());
736
737 // If the property has a slot, free its slot number.
738 if (prop.hasSlot()) {
739 obj->freeDictionarySlot(prop.slot());
740 }
741
742 DictionaryPropMap::removeProperty(cx, &dictMap, &mapLength, table, ptr);
743
744 obj->shape()->updateNewDictionaryShape(obj->shape()->objectFlags(), dictMap,
745 mapLength);
746 return true;
747 }
748
749 /* static */
densifySparseElements(JSContext * cx,HandleNativeObject obj)750 bool NativeObject::densifySparseElements(JSContext* cx,
751 HandleNativeObject obj) {
752 AutoCheckShapeConsistency check(obj);
753 MOZ_ASSERT(obj->inDictionaryMode());
754
755 // First generate a new dictionary shape so that the shape and map can then
756 // be updated infallibly.
757 if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
758 return false;
759 }
760
761 Rooted<DictionaryPropMap*> map(cx, obj->shape()->propMap()->asDictionary());
762 uint32_t mapLength = obj->shape()->propMapLength();
763
764 DictionaryPropMap::densifyElements(cx, &map, &mapLength, obj);
765
766 // All indexed properties on the object are now dense. Clear the indexed
767 // flag so that we will not start using sparse indexes again if we need
768 // to grow the object.
769 ObjectFlags objectFlags = obj->shape()->objectFlags();
770 objectFlags.clearFlag(ObjectFlag::Indexed);
771
772 obj->shape()->updateNewDictionaryShape(objectFlags, map, mapLength);
773 return true;
774 }
775
776 // static
freezeOrSealProperties(JSContext * cx,HandleNativeObject obj,IntegrityLevel level)777 bool NativeObject::freezeOrSealProperties(JSContext* cx, HandleNativeObject obj,
778 IntegrityLevel level) {
779 uint32_t mapLength = obj->shape()->propMapLength();
780 MOZ_ASSERT(mapLength > 0);
781
782 const JSClass* clasp = obj->shape()->getObjectClass();
783 ObjectFlags objectFlags = obj->shape()->objectFlags();
784
785 if (obj->inDictionaryMode()) {
786 // First generate a new dictionary shape so that the map and shape can be
787 // updated infallibly.
788 if (!generateNewDictionaryShape(cx, obj)) {
789 return false;
790 }
791 DictionaryPropMap* map = obj->shape()->dictionaryPropMap();
792 map->freezeOrSealProperties(cx, level, clasp, mapLength, &objectFlags);
793 obj->shape()->updateNewDictionaryShape(objectFlags, map, mapLength);
794 return true;
795 }
796
797 Rooted<SharedPropMap*> map(cx, obj->shape()->sharedPropMap());
798 if (!SharedPropMap::freezeOrSealProperties(cx, level, clasp, &map, mapLength,
799 &objectFlags)) {
800 return false;
801 }
802
803 Shape* newShape = SharedShape::getPropMapShape(cx, obj->shape()->base(),
804 obj->numFixedSlots(), map,
805 mapLength, objectFlags);
806 if (!newShape) {
807 return false;
808 }
809 MOZ_ASSERT(obj->shape()->slotSpan() == newShape->slotSpan());
810
811 obj->setShape(newShape);
812 return true;
813 }
814
815 /* static */
generateNewDictionaryShape(JSContext * cx,HandleNativeObject obj)816 bool NativeObject::generateNewDictionaryShape(JSContext* cx,
817 HandleNativeObject obj) {
818 // Clone the current dictionary shape to a new shape. This ensures ICs and
819 // other shape guards are properly invalidated before we start mutating the
820 // map or new shape.
821
822 MOZ_ASSERT(obj->inDictionaryMode());
823
824 Rooted<BaseShape*> base(cx, obj->shape()->base());
825 Rooted<DictionaryPropMap*> map(cx, obj->shape()->dictionaryPropMap());
826 uint32_t mapLength = obj->shape()->propMapLength();
827
828 Shape* shape =
829 DictionaryShape::new_(cx, base, obj->shape()->objectFlags(),
830 obj->shape()->numFixedSlots(), map, mapLength);
831 if (!shape) {
832 return false;
833 }
834
835 obj->setShape(shape);
836 return true;
837 }
838
839 /* static */
setFlag(JSContext * cx,HandleObject obj,ObjectFlag flag)840 bool JSObject::setFlag(JSContext* cx, HandleObject obj, ObjectFlag flag) {
841 MOZ_ASSERT(cx->compartment() == obj->compartment());
842
843 if (obj->hasFlag(flag)) {
844 return true;
845 }
846
847 ObjectFlags objectFlags = obj->shape()->objectFlags();
848 objectFlags.setFlag(flag);
849
850 if (obj->is<NativeObject>() && obj->as<NativeObject>().inDictionaryMode()) {
851 if (!NativeObject::generateNewDictionaryShape(cx, obj.as<NativeObject>())) {
852 return false;
853 }
854 obj->shape()->setObjectFlags(objectFlags);
855 return true;
856 }
857
858 return Shape::replaceShape(cx, obj, objectFlags, obj->shape()->proto(),
859 obj->shape()->numFixedSlots());
860 }
861
862 /* static */
setProtoUnchecked(JSContext * cx,HandleObject obj,Handle<TaggedProto> proto)863 bool JSObject::setProtoUnchecked(JSContext* cx, HandleObject obj,
864 Handle<TaggedProto> proto) {
865 MOZ_ASSERT(cx->compartment() == obj->compartment());
866 MOZ_ASSERT_IF(proto.isObject(), proto.toObject()->isUsedAsPrototype());
867
868 if (obj->shape()->proto() == proto) {
869 return true;
870 }
871
872 if (obj->is<NativeObject>() && obj->as<NativeObject>().inDictionaryMode()) {
873 HandleNativeObject nobj = obj.as<NativeObject>();
874 Rooted<BaseShape*> nbase(
875 cx, BaseShape::get(cx, nobj->getClass(), nobj->realm(), proto));
876 if (!nbase) {
877 return false;
878 }
879
880 if (!NativeObject::generateNewDictionaryShape(cx, nobj)) {
881 return false;
882 }
883
884 nobj->shape()->setBase(nbase);
885 return true;
886 }
887
888 return Shape::replaceShape(cx, obj, obj->shape()->objectFlags(), proto,
889 obj->shape()->numFixedSlots());
890 }
891
892 /* static */
changeNumFixedSlotsAfterSwap(JSContext * cx,HandleNativeObject obj,uint32_t nfixed)893 bool NativeObject::changeNumFixedSlotsAfterSwap(JSContext* cx,
894 HandleNativeObject obj,
895 uint32_t nfixed) {
896 MOZ_ASSERT(nfixed != obj->shape()->numFixedSlots());
897
898 if (obj->inDictionaryMode()) {
899 if (!NativeObject::generateNewDictionaryShape(cx, obj)) {
900 return false;
901 }
902 obj->shape()->setNumFixedSlots(nfixed);
903 return true;
904 }
905
906 return Shape::replaceShape(cx, obj, obj->shape()->objectFlags(),
907 obj->shape()->proto(), nfixed);
908 }
909
BaseShape(const JSClass * clasp,JS::Realm * realm,TaggedProto proto)910 BaseShape::BaseShape(const JSClass* clasp, JS::Realm* realm, TaggedProto proto)
911 : TenuredCellWithNonGCPointer(clasp), realm_(realm), proto_(proto) {
912 MOZ_ASSERT(JS::StringIsASCII(clasp->name));
913
914 MOZ_ASSERT_IF(proto.isObject(),
915 compartment() == proto.toObject()->compartment());
916 MOZ_ASSERT_IF(proto.isObject(), proto.toObject()->isUsedAsPrototype());
917
918 // Windows may not appear on prototype chains.
919 MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
920
921 #ifdef DEBUG
922 if (GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal()) {
923 AssertTargetIsNotGray(global);
924 }
925 #endif
926 }
927
928 /* static */
get(JSContext * cx,const JSClass * clasp,JS::Realm * realm,Handle<TaggedProto> proto)929 BaseShape* BaseShape::get(JSContext* cx, const JSClass* clasp, JS::Realm* realm,
930 Handle<TaggedProto> proto) {
931 auto& table = cx->zone()->shapeZone().baseShapes;
932
933 using Lookup = BaseShapeHasher::Lookup;
934
935 auto p = MakeDependentAddPtr(cx, table, Lookup(clasp, realm, proto));
936 if (p) {
937 return *p;
938 }
939
940 BaseShape* nbase = Allocate<BaseShape>(cx);
941 if (!nbase) {
942 return nullptr;
943 }
944 new (nbase) BaseShape(clasp, realm, proto);
945
946 if (!p.add(cx, table, Lookup(clasp, realm, proto), nbase)) {
947 return nullptr;
948 }
949
950 return nbase;
951 }
952
new_(JSContext * cx,Handle<BaseShape * > base,ObjectFlags objectFlags,uint32_t nfixed,Handle<SharedPropMap * > map,uint32_t mapLength)953 Shape* SharedShape::new_(JSContext* cx, Handle<BaseShape*> base,
954 ObjectFlags objectFlags, uint32_t nfixed,
955 Handle<SharedPropMap*> map, uint32_t mapLength) {
956 Shape* shape = Allocate<Shape>(cx);
957 if (!shape) {
958 ReportOutOfMemory(cx);
959 return nullptr;
960 }
961
962 new (shape) SharedShape(base, objectFlags, nfixed, map, mapLength);
963 return shape;
964 }
965
new_(JSContext * cx,Handle<BaseShape * > base,ObjectFlags objectFlags,uint32_t nfixed,Handle<DictionaryPropMap * > map,uint32_t mapLength)966 Shape* DictionaryShape::new_(JSContext* cx, Handle<BaseShape*> base,
967 ObjectFlags objectFlags, uint32_t nfixed,
968 Handle<DictionaryPropMap*> map,
969 uint32_t mapLength) {
970 Shape* shape = Allocate<Shape>(cx);
971 if (!shape) {
972 ReportOutOfMemory(cx);
973 return nullptr;
974 }
975
976 new (shape) DictionaryShape(base, objectFlags, nfixed, map, mapLength);
977 return shape;
978 }
979
hash(const Lookup & l)980 MOZ_ALWAYS_INLINE HashNumber ShapeForAddHasher::hash(const Lookup& l) {
981 HashNumber hash = HashPropertyKey(l.key);
982 return mozilla::AddToHash(hash, l.flags.toRaw());
983 }
984
match(Shape * shape,const Lookup & l)985 MOZ_ALWAYS_INLINE bool ShapeForAddHasher::match(Shape* shape, const Lookup& l) {
986 uint32_t slot;
987 return shape->lastPropertyMatchesForAdd(l.key, l.flags, &slot);
988 }
989
990 #ifdef DEBUG
dump(js::GenericPrinter & out) const991 void Shape::dump(js::GenericPrinter& out) const {
992 out.printf("shape @ 0x%p\n", this);
993 out.printf("base: 0x%p\n", base());
994 out.printf("mapLength: %u\n", propMapLength());
995 out.printf("dictionary: %s\n", isDictionary() ? "yes" : "no");
996 if (propMap_) {
997 out.printf("map:\n");
998 propMap_->dump(out);
999 } else {
1000 out.printf("map: (none)\n");
1001 }
1002 }
1003
dump() const1004 void Shape::dump() const {
1005 Fprinter out(stderr);
1006 dump(out);
1007 }
1008 #endif // DEBUG
1009
1010 /* static */
getInitialShape(JSContext * cx,const JSClass * clasp,JS::Realm * realm,TaggedProto proto,size_t nfixed,ObjectFlags objectFlags)1011 Shape* SharedShape::getInitialShape(JSContext* cx, const JSClass* clasp,
1012 JS::Realm* realm, TaggedProto proto,
1013 size_t nfixed, ObjectFlags objectFlags) {
1014 MOZ_ASSERT(cx->compartment() == realm->compartment());
1015 MOZ_ASSERT_IF(proto.isObject(),
1016 cx->isInsideCurrentCompartment(proto.toObject()));
1017
1018 if (proto.isObject()) {
1019 if (proto.toObject()->isUsedAsPrototype()) {
1020 // Use the cache on the prototype's shape to get to the initial shape.
1021 // This cache has a hit rate of 80-90% on typical workloads and is faster
1022 // than the HashSet lookup below.
1023 JSObject* protoObj = proto.toObject();
1024 Shape* protoObjShape = protoObj->shape();
1025 if (protoObjShape->cache().isShapeWithProto()) {
1026 Shape* shape = protoObjShape->cache().toShapeWithProto();
1027 if (shape->numFixedSlots() == nfixed &&
1028 shape->objectFlags() == objectFlags &&
1029 shape->getObjectClass() == clasp && shape->realm() == realm &&
1030 shape->proto() == proto) {
1031 #ifdef DEBUG
1032 // Verify the table lookup below would have resulted in the same
1033 // shape.
1034 using Lookup = InitialShapeHasher::Lookup;
1035 Lookup lookup(clasp, realm, proto, nfixed, objectFlags);
1036 auto p = realm->zone()->shapeZone().initialShapes.lookup(lookup);
1037 MOZ_ASSERT(*p == shape);
1038 #endif
1039 return shape;
1040 }
1041 }
1042 } else {
1043 RootedObject protoObj(cx, proto.toObject());
1044 if (!JSObject::setIsUsedAsPrototype(cx, protoObj)) {
1045 return nullptr;
1046 }
1047 // Ensure the proto object has a unique id to prevent OOM crashes below.
1048 uint64_t unused;
1049 if (!cx->zone()->getOrCreateUniqueId(protoObj, &unused)) {
1050 ReportOutOfMemory(cx);
1051 return nullptr;
1052 }
1053 proto = TaggedProto(protoObj);
1054 }
1055 }
1056
1057 auto& table = realm->zone()->shapeZone().initialShapes;
1058
1059 using Lookup = InitialShapeHasher::Lookup;
1060 auto ptr = MakeDependentAddPtr(
1061 cx, table, Lookup(clasp, realm, proto, nfixed, objectFlags));
1062 if (ptr) {
1063 // Cache the result of this lookup on the prototype's shape.
1064 if (proto.isObject()) {
1065 JSObject* protoObj = proto.toObject();
1066 Shape* protoShape = protoObj->shape();
1067 if ((protoShape->cache().isShapeWithProto() ||
1068 protoShape->cache().isNone()) &&
1069 RegisterShapeCache(cx, protoShape)) {
1070 protoShape->cacheRef().setShapeWithProto(*ptr);
1071 }
1072 }
1073 return *ptr;
1074 }
1075
1076 Rooted<TaggedProto> protoRoot(cx, proto);
1077 Rooted<BaseShape*> nbase(cx, BaseShape::get(cx, clasp, realm, protoRoot));
1078 if (!nbase) {
1079 return nullptr;
1080 }
1081
1082 RootedShape shape(
1083 cx, SharedShape::new_(cx, nbase, objectFlags, nfixed, nullptr, 0));
1084 if (!shape) {
1085 return nullptr;
1086 }
1087
1088 Lookup lookup(clasp, realm, protoRoot, nfixed, objectFlags);
1089 if (!ptr.add(cx, table, lookup, shape)) {
1090 return nullptr;
1091 }
1092
1093 return shape;
1094 }
1095
1096 /* static */
getInitialShape(JSContext * cx,const JSClass * clasp,JS::Realm * realm,TaggedProto proto,gc::AllocKind kind,ObjectFlags objectFlags)1097 Shape* SharedShape::getInitialShape(JSContext* cx, const JSClass* clasp,
1098 JS::Realm* realm, TaggedProto proto,
1099 gc::AllocKind kind,
1100 ObjectFlags objectFlags) {
1101 return getInitialShape(cx, clasp, realm, proto, GetGCKindSlots(kind, clasp),
1102 objectFlags);
1103 }
1104
1105 /* static */
getPropMapShape(JSContext * cx,BaseShape * base,size_t nfixed,Handle<SharedPropMap * > map,uint32_t mapLength,ObjectFlags objectFlags,bool * allocatedNewShape)1106 Shape* SharedShape::getPropMapShape(JSContext* cx, BaseShape* base,
1107 size_t nfixed, Handle<SharedPropMap*> map,
1108 uint32_t mapLength, ObjectFlags objectFlags,
1109 bool* allocatedNewShape) {
1110 MOZ_ASSERT(cx->compartment() == base->compartment());
1111 MOZ_ASSERT_IF(base->proto().isObject(),
1112 cx->isInsideCurrentCompartment(base->proto().toObject()));
1113 MOZ_ASSERT_IF(base->proto().isObject(),
1114 base->proto().toObject()->isUsedAsPrototype());
1115 MOZ_ASSERT(map);
1116 MOZ_ASSERT(mapLength > 0);
1117
1118 auto& table = cx->zone()->shapeZone().propMapShapes;
1119
1120 using Lookup = PropMapShapeHasher::Lookup;
1121 auto ptr = MakeDependentAddPtr(
1122 cx, table, Lookup(base, nfixed, map, mapLength, objectFlags));
1123 if (ptr) {
1124 if (allocatedNewShape) {
1125 *allocatedNewShape = false;
1126 }
1127 return *ptr;
1128 }
1129
1130 Rooted<BaseShape*> baseRoot(cx, base);
1131 RootedShape shape(
1132 cx, SharedShape::new_(cx, baseRoot, objectFlags, nfixed, map, mapLength));
1133 if (!shape) {
1134 return nullptr;
1135 }
1136
1137 Lookup lookup(baseRoot, nfixed, map, mapLength, objectFlags);
1138 if (!ptr.add(cx, table, lookup, shape)) {
1139 return nullptr;
1140 }
1141
1142 if (allocatedNewShape) {
1143 *allocatedNewShape = true;
1144 }
1145
1146 return shape;
1147 }
1148
1149 /* static */
getInitialOrPropMapShape(JSContext * cx,const JSClass * clasp,JS::Realm * realm,TaggedProto proto,size_t nfixed,Handle<SharedPropMap * > map,uint32_t mapLength,ObjectFlags objectFlags)1150 Shape* SharedShape::getInitialOrPropMapShape(
1151 JSContext* cx, const JSClass* clasp, JS::Realm* realm, TaggedProto proto,
1152 size_t nfixed, Handle<SharedPropMap*> map, uint32_t mapLength,
1153 ObjectFlags objectFlags) {
1154 if (!map) {
1155 MOZ_ASSERT(mapLength == 0);
1156 return getInitialShape(cx, clasp, realm, proto, nfixed, objectFlags);
1157 }
1158
1159 Rooted<TaggedProto> protoRoot(cx, proto);
1160 BaseShape* nbase = BaseShape::get(cx, clasp, realm, protoRoot);
1161 if (!nbase) {
1162 return nullptr;
1163 }
1164
1165 return getPropMapShape(cx, nbase, nfixed, map, mapLength, objectFlags);
1166 }
1167
invalidateEntriesForShape(Shape * shape)1168 void NewObjectCache::invalidateEntriesForShape(Shape* shape) {
1169 const JSClass* clasp = shape->getObjectClass();
1170
1171 gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
1172 if (CanChangeToBackgroundAllocKind(kind, clasp)) {
1173 kind = ForegroundToBackgroundAllocKind(kind);
1174 }
1175
1176 EntryIndex entry;
1177 for (RealmsInZoneIter realm(shape->zone()); !realm.done(); realm.next()) {
1178 if (GlobalObject* global = realm->unsafeUnbarrieredMaybeGlobal()) {
1179 if (lookupGlobal(clasp, global, kind, &entry)) {
1180 PodZero(&entries[entry]);
1181 }
1182 }
1183 }
1184
1185 JSObject* proto = shape->proto().toObject();
1186 if (!proto->is<GlobalObject>() && lookupProto(clasp, proto, kind, &entry)) {
1187 PodZero(&entries[entry]);
1188 }
1189 }
1190
1191 /* static */
insertInitialShape(JSContext * cx,HandleShape shape)1192 void SharedShape::insertInitialShape(JSContext* cx, HandleShape shape) {
1193 using Lookup = InitialShapeHasher::Lookup;
1194 Lookup lookup(shape->getObjectClass(), shape->realm(), shape->proto(),
1195 shape->numFixedSlots(), shape->objectFlags());
1196
1197 auto& table = cx->zone()->shapeZone().initialShapes;
1198 InitialShapeSet::Ptr p = table.lookup(lookup);
1199 MOZ_ASSERT(p);
1200
1201 // The metadata callback can end up causing redundant changes of the initial
1202 // shape.
1203 Shape* initialShape = *p;
1204 if (initialShape == shape) {
1205 return;
1206 }
1207
1208 MOZ_ASSERT(initialShape->numFixedSlots() == shape->numFixedSlots());
1209 MOZ_ASSERT(initialShape->base() == shape->base());
1210 MOZ_ASSERT(initialShape->objectFlags() == shape->objectFlags());
1211
1212 table.replaceKey(p, lookup, shape.get());
1213
1214 // Purge the prototype's shape cache entry.
1215 if (shape->proto().isObject()) {
1216 JSObject* protoObj = shape->proto().toObject();
1217 if (protoObj->shape()->cache().isShapeWithProto()) {
1218 protoObj->shape()->cacheRef().setNone();
1219 }
1220 }
1221
1222 /*
1223 * This affects the shape that will be produced by the various NewObject
1224 * methods, so clear any cache entry referring to the old shape. This is
1225 * not required for correctness: the NewObject must always check for a
1226 * nativeEmpty() result and generate the appropriate properties if found.
1227 * Clearing the cache entry avoids this duplicate regeneration.
1228 *
1229 * Clearing is not necessary when this context is running off
1230 * thread, as it will not use the new object cache for allocations.
1231 */
1232 if (!cx->isHelperThreadContext()) {
1233 cx->caches().newObjectCache.invalidateEntriesForShape(shape);
1234 }
1235 }
1236
size(mozilla::MallocSizeOf mallocSizeOf) const1237 JS::ubi::Node::Size JS::ubi::Concrete<js::Shape>::size(
1238 mozilla::MallocSizeOf mallocSizeOf) const {
1239 Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
1240
1241 if (get().cache().isShapeSetForAdd()) {
1242 ShapeSetForAdd* set = get().cache().toShapeSetForAdd();
1243 size += set->shallowSizeOfIncludingThis(mallocSizeOf);
1244 }
1245
1246 return size;
1247 }
1248
size(mozilla::MallocSizeOf mallocSizeOf) const1249 JS::ubi::Node::Size JS::ubi::Concrete<js::BaseShape>::size(
1250 mozilla::MallocSizeOf mallocSizeOf) const {
1251 return js::gc::Arena::thingSize(get().asTenured().getAllocKind());
1252 }
1253