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 #include "vm/NativeObject-inl.h"
8 
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/Casting.h"
11 
12 #include "jswatchpoint.h"
13 
14 #include "gc/Marking.h"
15 #include "js/Value.h"
16 #include "vm/Debugger.h"
17 #include "vm/TypedArrayCommon.h"
18 
19 #include "jsobjinlines.h"
20 
21 #include "gc/Nursery-inl.h"
22 #include "vm/ArrayObject-inl.h"
23 #include "vm/EnvironmentObject-inl.h"
24 #include "vm/Shape-inl.h"
25 
26 using namespace js;
27 
28 using JS::AutoCheckCannotGC;
29 using JS::GenericNaN;
30 using mozilla::ArrayLength;
31 using mozilla::DebugOnly;
32 using mozilla::PodCopy;
33 using mozilla::RoundUpPow2;
34 
35 static const ObjectElements emptyElementsHeader(0, 0);
36 
37 /* Objects with no elements share one empty set of elements. */
38 HeapSlot* const js::emptyObjectElements =
39     reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
40 
41 static const ObjectElements emptyElementsHeaderShared(0, 0, ObjectElements::SharedMemory::IsShared);
42 
43 /* Objects with no elements share one empty set of elements. */
44 HeapSlot* const js::emptyObjectElementsShared =
45     reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
46 
47 
48 #ifdef DEBUG
49 
50 bool
canHaveNonEmptyElements()51 NativeObject::canHaveNonEmptyElements()
52 {
53     return !this->is<TypedArrayObject>();
54 }
55 
56 #endif // DEBUG
57 
58 /* static */ bool
ConvertElementsToDoubles(JSContext * cx,uintptr_t elementsPtr)59 ObjectElements::ConvertElementsToDoubles(JSContext* cx, uintptr_t elementsPtr)
60 {
61     /*
62      * This function is infallible, but has a fallible interface so that it can
63      * be called directly from Ion code. Only arrays can have their dense
64      * elements converted to doubles, and arrays never have empty elements.
65      */
66     HeapSlot* elementsHeapPtr = (HeapSlot*) elementsPtr;
67     MOZ_ASSERT(elementsHeapPtr != emptyObjectElements &&
68                elementsHeapPtr != emptyObjectElementsShared);
69 
70     ObjectElements* header = ObjectElements::fromElements(elementsHeapPtr);
71     MOZ_ASSERT(!header->shouldConvertDoubleElements());
72 
73     // Note: the elements can be mutated in place even for copy on write
74     // arrays. See comment on ObjectElements.
75     Value* vp = (Value*) elementsPtr;
76     for (size_t i = 0; i < header->initializedLength; i++) {
77         if (vp[i].isInt32())
78             vp[i].setDouble(vp[i].toInt32());
79     }
80 
81     header->setShouldConvertDoubleElements();
82     return true;
83 }
84 
85 /* static */ bool
MakeElementsCopyOnWrite(ExclusiveContext * cx,NativeObject * obj)86 ObjectElements::MakeElementsCopyOnWrite(ExclusiveContext* cx, NativeObject* obj)
87 {
88     static_assert(sizeof(HeapSlot) >= sizeof(GCPtrObject),
89                   "there must be enough room for the owner object pointer at "
90                   "the end of the elements");
91     if (!obj->ensureElements(cx, obj->getDenseInitializedLength() + 1))
92         return false;
93 
94     ObjectElements* header = obj->getElementsHeader();
95 
96     // Note: this method doesn't update type information to indicate that the
97     // elements might be copy on write. Handling this is left to the caller.
98     MOZ_ASSERT(!header->isCopyOnWrite());
99     MOZ_ASSERT(!header->isFrozen());
100     header->flags |= COPY_ON_WRITE;
101 
102     header->ownerObject().init(obj);
103     return true;
104 }
105 
106 /* static */ bool
FreezeElements(ExclusiveContext * cx,HandleNativeObject obj)107 ObjectElements::FreezeElements(ExclusiveContext* cx, HandleNativeObject obj)
108 {
109     if (!obj->maybeCopyElementsForWrite(cx))
110         return false;
111 
112     if (obj->hasEmptyElements())
113         return true;
114 
115     ObjectElements* header = obj->getElementsHeader();
116 
117     // Note: this method doesn't update type information to indicate that the
118     // elements might be frozen. Handling this is left to the caller.
119     header->freeze();
120 
121     return true;
122 }
123 
124 #ifdef DEBUG
125 void
checkShapeConsistency()126 js::NativeObject::checkShapeConsistency()
127 {
128     static int throttle = -1;
129     if (throttle < 0) {
130         if (const char* var = getenv("JS_CHECK_SHAPE_THROTTLE"))
131             throttle = atoi(var);
132         if (throttle < 0)
133             throttle = 0;
134     }
135     if (throttle == 0)
136         return;
137 
138     MOZ_ASSERT(isNative());
139 
140     Shape* shape = lastProperty();
141     Shape* prev = nullptr;
142 
143     AutoCheckCannotGC nogc;
144     if (inDictionaryMode()) {
145         if (ShapeTable* table = shape->maybeTable(nogc)) {
146             for (uint32_t fslot = table->freeList();
147                  fslot != SHAPE_INVALID_SLOT;
148                  fslot = getSlot(fslot).toPrivateUint32())
149             {
150                 MOZ_ASSERT(fslot < slotSpan());
151             }
152 
153             for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
154                 MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable());
155 
156                 ShapeTable::Entry& entry = table->search<MaybeAdding::NotAdding>(shape->propid(),
157                                                                                  nogc);
158                 MOZ_ASSERT(entry.shape() == shape);
159             }
160         }
161 
162         shape = lastProperty();
163         for (int n = throttle; --n >= 0 && shape; shape = shape->parent) {
164             MOZ_ASSERT_IF(shape->slot() != SHAPE_INVALID_SLOT, shape->slot() < slotSpan());
165             if (!prev) {
166                 MOZ_ASSERT(lastProperty() == shape);
167                 MOZ_ASSERT(shape->listp == &shape_);
168             } else {
169                 MOZ_ASSERT(shape->listp == &prev->parent);
170             }
171             prev = shape;
172         }
173     } else {
174         for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
175             if (ShapeTable* table = shape->maybeTable(nogc)) {
176                 MOZ_ASSERT(shape->parent);
177                 for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) {
178                     ShapeTable::Entry& entry =
179                         table->search<MaybeAdding::NotAdding>(r.front().propid(), nogc);
180                     MOZ_ASSERT(entry.shape() == &r.front());
181                 }
182             }
183             if (prev) {
184                 MOZ_ASSERT(prev->maybeSlot() >= shape->maybeSlot());
185                 shape->kids.checkConsistency(prev);
186             }
187             prev = shape;
188         }
189     }
190 }
191 #endif
192 
193 void
initializeSlotRange(uint32_t start,uint32_t length)194 js::NativeObject::initializeSlotRange(uint32_t start, uint32_t length)
195 {
196     /*
197      * No bounds check, as this is used when the object's shape does not
198      * reflect its allocated slots (updateSlotsForSpan).
199      */
200     HeapSlot* fixedStart;
201     HeapSlot* fixedEnd;
202     HeapSlot* slotsStart;
203     HeapSlot* slotsEnd;
204     getSlotRangeUnchecked(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
205 
206     uint32_t offset = start;
207     for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++)
208         sp->init(this, HeapSlot::Slot, offset++, UndefinedValue());
209     for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++)
210         sp->init(this, HeapSlot::Slot, offset++, UndefinedValue());
211 }
212 
213 void
initSlotRange(uint32_t start,const Value * vector,uint32_t length)214 js::NativeObject::initSlotRange(uint32_t start, const Value* vector, uint32_t length)
215 {
216     HeapSlot* fixedStart;
217     HeapSlot* fixedEnd;
218     HeapSlot* slotsStart;
219     HeapSlot* slotsEnd;
220     getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
221     for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++)
222         sp->init(this, HeapSlot::Slot, start++, *vector++);
223     for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++)
224         sp->init(this, HeapSlot::Slot, start++, *vector++);
225 }
226 
227 void
copySlotRange(uint32_t start,const Value * vector,uint32_t length)228 js::NativeObject::copySlotRange(uint32_t start, const Value* vector, uint32_t length)
229 {
230     HeapSlot* fixedStart;
231     HeapSlot* fixedEnd;
232     HeapSlot* slotsStart;
233     HeapSlot* slotsEnd;
234     getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
235     for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++)
236         sp->set(this, HeapSlot::Slot, start++, *vector++);
237     for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++)
238         sp->set(this, HeapSlot::Slot, start++, *vector++);
239 }
240 
241 #ifdef DEBUG
242 bool
slotInRange(uint32_t slot,SentinelAllowed sentinel) const243 js::NativeObject::slotInRange(uint32_t slot, SentinelAllowed sentinel) const
244 {
245     uint32_t capacity = numFixedSlots() + numDynamicSlots();
246     if (sentinel == SENTINEL_ALLOWED)
247         return slot <= capacity;
248     return slot < capacity;
249 }
250 #endif /* DEBUG */
251 
252 Shape*
lookup(ExclusiveContext * cx,jsid id)253 js::NativeObject::lookup(ExclusiveContext* cx, jsid id)
254 {
255     MOZ_ASSERT(isNative());
256     return Shape::search(cx, lastProperty(), id);
257 }
258 
259 Shape*
lookupPure(jsid id)260 js::NativeObject::lookupPure(jsid id)
261 {
262     MOZ_ASSERT(isNative());
263     return Shape::searchNoHashify(lastProperty(), id);
264 }
265 
266 uint32_t
numFixedSlotsForCompilation() const267 js::NativeObject::numFixedSlotsForCompilation() const
268 {
269     // This is an alternative method for getting the number of fixed slots in an
270     // object. It requires more logic and memory accesses than numFixedSlots()
271     // but is safe to be called from the compilation thread, even if the main
272     // thread is actively mutating the VM.
273 
274     // The compiler does not have access to nursery things.
275     MOZ_ASSERT(!IsInsideNursery(this));
276 
277     if (this->is<ArrayObject>())
278         return 0;
279 
280     gc::AllocKind kind = asTenured().getAllocKind();
281     return gc::GetGCKindSlots(kind, getClass());
282 }
283 
284 uint32_t
dynamicSlotsCount(uint32_t nfixed,uint32_t span,const Class * clasp)285 js::NativeObject::dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class* clasp)
286 {
287     if (span <= nfixed)
288         return 0;
289     span -= nfixed;
290 
291     // Increase the slots to SLOT_CAPACITY_MIN to decrease the likelihood
292     // the dynamic slots need to get increased again. ArrayObjects ignore
293     // this because slots are uncommon in that case.
294     if (clasp != &ArrayObject::class_ && span <= SLOT_CAPACITY_MIN)
295         return SLOT_CAPACITY_MIN;
296 
297     uint32_t slots = mozilla::RoundUpPow2(span);
298     MOZ_ASSERT(slots >= span);
299     return slots;
300 }
301 
302 inline bool
updateSlotsForSpan(ExclusiveContext * cx,size_t oldSpan,size_t newSpan)303 NativeObject::updateSlotsForSpan(ExclusiveContext* cx, size_t oldSpan, size_t newSpan)
304 {
305     MOZ_ASSERT(oldSpan != newSpan);
306 
307     size_t oldCount = dynamicSlotsCount(numFixedSlots(), oldSpan, getClass());
308     size_t newCount = dynamicSlotsCount(numFixedSlots(), newSpan, getClass());
309 
310     if (oldSpan < newSpan) {
311         if (oldCount < newCount && !growSlots(cx, oldCount, newCount))
312             return false;
313 
314         if (newSpan == oldSpan + 1)
315             initSlotUnchecked(oldSpan, UndefinedValue());
316         else
317             initializeSlotRange(oldSpan, newSpan - oldSpan);
318     } else {
319         /* Trigger write barriers on the old slots before reallocating. */
320         prepareSlotRangeForOverwrite(newSpan, oldSpan);
321         invalidateSlotRange(newSpan, oldSpan - newSpan);
322 
323         if (oldCount > newCount)
324             shrinkSlots(cx, oldCount, newCount);
325     }
326 
327     return true;
328 }
329 
330 bool
setLastProperty(ExclusiveContext * cx,Shape * shape)331 NativeObject::setLastProperty(ExclusiveContext* cx, Shape* shape)
332 {
333     MOZ_ASSERT(!inDictionaryMode());
334     MOZ_ASSERT(!shape->inDictionary());
335     MOZ_ASSERT(shape->zone() == zone());
336     MOZ_ASSERT(shape->numFixedSlots() == numFixedSlots());
337     MOZ_ASSERT(shape->getObjectClass() == getClass());
338 
339     size_t oldSpan = lastProperty()->slotSpan();
340     size_t newSpan = shape->slotSpan();
341 
342     if (oldSpan == newSpan) {
343         shape_ = shape;
344         return true;
345     }
346 
347     if (!updateSlotsForSpan(cx, oldSpan, newSpan))
348         return false;
349 
350     shape_ = shape;
351     return true;
352 }
353 
354 void
setLastPropertyShrinkFixedSlots(Shape * shape)355 NativeObject::setLastPropertyShrinkFixedSlots(Shape* shape)
356 {
357     MOZ_ASSERT(!inDictionaryMode());
358     MOZ_ASSERT(!shape->inDictionary());
359     MOZ_ASSERT(shape->zone() == zone());
360     MOZ_ASSERT(lastProperty()->slotSpan() == shape->slotSpan());
361     MOZ_ASSERT(shape->getObjectClass() == getClass());
362 
363     DebugOnly<size_t> oldFixed = numFixedSlots();
364     DebugOnly<size_t> newFixed = shape->numFixedSlots();
365     MOZ_ASSERT(newFixed < oldFixed);
366     MOZ_ASSERT(shape->slotSpan() <= oldFixed);
367     MOZ_ASSERT(shape->slotSpan() <= newFixed);
368     MOZ_ASSERT(dynamicSlotsCount(oldFixed, shape->slotSpan(), getClass()) == 0);
369     MOZ_ASSERT(dynamicSlotsCount(newFixed, shape->slotSpan(), getClass()) == 0);
370 
371     shape_ = shape;
372 }
373 
374 void
setLastPropertyMakeNonNative(Shape * shape)375 NativeObject::setLastPropertyMakeNonNative(Shape* shape)
376 {
377     MOZ_ASSERT(!inDictionaryMode());
378     MOZ_ASSERT(!shape->getObjectClass()->isNative());
379     MOZ_ASSERT(shape->zone() == zone());
380     MOZ_ASSERT(shape->slotSpan() == 0);
381     MOZ_ASSERT(shape->numFixedSlots() == 0);
382 
383     if (hasDynamicElements())
384         js_free(getElementsHeader());
385     if (hasDynamicSlots()) {
386         js_free(slots_);
387         slots_ = nullptr;
388     }
389 
390     shape_ = shape;
391 }
392 
393 void
setLastPropertyMakeNative(ExclusiveContext * cx,Shape * shape)394 NativeObject::setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape)
395 {
396     MOZ_ASSERT(getClass()->isNative());
397     MOZ_ASSERT(shape->getObjectClass()->isNative());
398     MOZ_ASSERT(!shape->inDictionary());
399 
400     // This method is used to convert unboxed objects into native objects. In
401     // this case, the shape_ field was previously used to store other data and
402     // this should be treated as an initialization.
403     shape_.init(shape);
404 
405     slots_ = nullptr;
406     elements_ = emptyObjectElements;
407 
408     size_t oldSpan = shape->numFixedSlots();
409     size_t newSpan = shape->slotSpan();
410 
411     initializeSlotRange(0, oldSpan);
412 
413     // A failure at this point will leave the object as a mutant, and we
414     // can't recover.
415     AutoEnterOOMUnsafeRegion oomUnsafe;
416     if (oldSpan != newSpan && !updateSlotsForSpan(cx, oldSpan, newSpan))
417         oomUnsafe.crash("NativeObject::setLastPropertyMakeNative");
418 }
419 
420 bool
setSlotSpan(ExclusiveContext * cx,uint32_t span)421 NativeObject::setSlotSpan(ExclusiveContext* cx, uint32_t span)
422 {
423     MOZ_ASSERT(inDictionaryMode());
424 
425     size_t oldSpan = lastProperty()->base()->slotSpan();
426     if (oldSpan == span)
427         return true;
428 
429     if (!updateSlotsForSpan(cx, oldSpan, span))
430         return false;
431 
432     lastProperty()->base()->setSlotSpan(span);
433     return true;
434 }
435 
436 bool
growSlots(ExclusiveContext * cx,uint32_t oldCount,uint32_t newCount)437 NativeObject::growSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount)
438 {
439     MOZ_ASSERT(newCount > oldCount);
440     MOZ_ASSERT_IF(!is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN);
441 
442     /*
443      * Slot capacities are determined by the span of allocated objects. Due to
444      * the limited number of bits to store shape slots, object growth is
445      * throttled well before the slot capacity can overflow.
446      */
447     NativeObject::slotsSizeMustNotOverflow();
448     MOZ_ASSERT(newCount <= MAX_SLOTS_COUNT);
449 
450     if (!oldCount) {
451         MOZ_ASSERT(!slots_);
452         slots_ = AllocateObjectBuffer<HeapSlot>(cx, this, newCount);
453         if (!slots_)
454             return false;
455         Debug_SetSlotRangeToCrashOnTouch(slots_, newCount);
456         return true;
457     }
458 
459     HeapSlot* newslots = ReallocateObjectBuffer<HeapSlot>(cx, this, slots_, oldCount, newCount);
460     if (!newslots)
461         return false;  /* Leave slots at its old size. */
462 
463     slots_ = newslots;
464 
465     Debug_SetSlotRangeToCrashOnTouch(slots_ + oldCount, newCount - oldCount);
466 
467     return true;
468 }
469 
470 /* static */ bool
growSlotsDontReportOOM(ExclusiveContext * cx,NativeObject * obj,uint32_t newCount)471 NativeObject::growSlotsDontReportOOM(ExclusiveContext* cx, NativeObject* obj, uint32_t newCount)
472 {
473     if (!obj->growSlots(cx, obj->numDynamicSlots(), newCount)) {
474         cx->recoverFromOutOfMemory();
475         return false;
476     }
477     return true;
478 }
479 
480 static void
FreeSlots(ExclusiveContext * cx,HeapSlot * slots)481 FreeSlots(ExclusiveContext* cx, HeapSlot* slots)
482 {
483     // Note: threads without a JSContext do not have access to GGC nursery allocated things.
484     if (cx->isJSContext())
485         return cx->asJSContext()->runtime()->gc.nursery.freeBuffer(slots);
486     js_free(slots);
487 }
488 
489 void
shrinkSlots(ExclusiveContext * cx,uint32_t oldCount,uint32_t newCount)490 NativeObject::shrinkSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount)
491 {
492     MOZ_ASSERT(newCount < oldCount);
493 
494     if (newCount == 0) {
495         FreeSlots(cx, slots_);
496         slots_ = nullptr;
497         return;
498     }
499 
500     MOZ_ASSERT_IF(!is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN);
501 
502     HeapSlot* newslots = ReallocateObjectBuffer<HeapSlot>(cx, this, slots_, oldCount, newCount);
503     if (!newslots) {
504         cx->recoverFromOutOfMemory();
505         return;  /* Leave slots at its old size. */
506     }
507 
508     slots_ = newslots;
509 }
510 
511 /* static */ bool
sparsifyDenseElement(ExclusiveContext * cx,HandleNativeObject obj,uint32_t index)512 NativeObject::sparsifyDenseElement(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index)
513 {
514     if (!obj->maybeCopyElementsForWrite(cx))
515         return false;
516 
517     RootedValue value(cx, obj->getDenseElement(index));
518     MOZ_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE));
519 
520     removeDenseElementForSparseIndex(cx, obj, index);
521 
522     uint32_t slot = obj->slotSpan();
523 
524     RootedId id(cx, INT_TO_JSID(index));
525 
526     AutoKeepShapeTables keep(cx);
527     ShapeTable::Entry* entry = nullptr;
528     if (obj->inDictionaryMode()) {
529         ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
530         if (!table)
531             return false;
532         entry = &table->search<MaybeAdding::Adding>(id, keep);
533     }
534 
535     // NOTE: We don't use addDataProperty because we don't want the
536     // extensibility check if we're, for example, sparsifying frozen objects..
537     if (!addPropertyInternal(cx, obj, id, nullptr, nullptr, slot,
538                              obj->getElementsHeader()->elementAttributes(),
539                              0, entry, true, keep)) {
540         obj->setDenseElementUnchecked(index, value);
541         return false;
542     }
543 
544     MOZ_ASSERT(slot == obj->slotSpan() - 1);
545     obj->initSlot(slot, value);
546 
547     return true;
548 }
549 
550 /* static */ bool
sparsifyDenseElements(js::ExclusiveContext * cx,HandleNativeObject obj)551 NativeObject::sparsifyDenseElements(js::ExclusiveContext* cx, HandleNativeObject obj)
552 {
553     if (!obj->maybeCopyElementsForWrite(cx))
554         return false;
555 
556     uint32_t initialized = obj->getDenseInitializedLength();
557 
558     /* Create new properties with the value of non-hole dense elements. */
559     for (uint32_t i = 0; i < initialized; i++) {
560         if (obj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE))
561             continue;
562 
563         if (!sparsifyDenseElement(cx, obj, i))
564             return false;
565     }
566 
567     if (initialized)
568         obj->setDenseInitializedLengthUnchecked(0);
569 
570     /*
571      * Reduce storage for dense elements which are now holes. Explicitly mark
572      * the elements capacity as zero, so that any attempts to add dense
573      * elements will be caught in ensureDenseElements.
574      */
575     if (obj->getDenseCapacity()) {
576         obj->shrinkElements(cx, 0);
577         obj->getElementsHeader()->capacity = 0;
578     }
579 
580     return true;
581 }
582 
583 bool
willBeSparseElements(uint32_t requiredCapacity,uint32_t newElementsHint)584 NativeObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint)
585 {
586     MOZ_ASSERT(isNative());
587     MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
588 
589     uint32_t cap = getDenseCapacity();
590     MOZ_ASSERT(requiredCapacity >= cap);
591 
592     if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT)
593         return true;
594 
595     uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
596     if (newElementsHint >= minimalDenseCount)
597         return false;
598     minimalDenseCount -= newElementsHint;
599 
600     if (minimalDenseCount > cap)
601         return true;
602 
603     uint32_t len = getDenseInitializedLength();
604     const Value* elems = getDenseElements();
605     for (uint32_t i = 0; i < len; i++) {
606         if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount)
607             return false;
608     }
609     return true;
610 }
611 
612 /* static */ DenseElementResult
maybeDensifySparseElements(js::ExclusiveContext * cx,HandleNativeObject obj)613 NativeObject::maybeDensifySparseElements(js::ExclusiveContext* cx, HandleNativeObject obj)
614 {
615     /*
616      * Wait until after the object goes into dictionary mode, which must happen
617      * when sparsely packing any array with more than MIN_SPARSE_INDEX elements
618      * (see PropertyTree::MAX_HEIGHT).
619      */
620     if (!obj->inDictionaryMode())
621         return DenseElementResult::Incomplete;
622 
623     /*
624      * Only measure the number of indexed properties every log(n) times when
625      * populating the object.
626      */
627     uint32_t slotSpan = obj->slotSpan();
628     if (slotSpan != RoundUpPow2(slotSpan))
629         return DenseElementResult::Incomplete;
630 
631     /* Watch for conditions under which an object's elements cannot be dense. */
632     if (!obj->nonProxyIsExtensible() || obj->watched())
633         return DenseElementResult::Incomplete;
634 
635     /*
636      * The indexes in the object need to be sufficiently dense before they can
637      * be converted to dense mode.
638      */
639     uint32_t numDenseElements = 0;
640     uint32_t newInitializedLength = 0;
641 
642     RootedShape shape(cx, obj->lastProperty());
643     while (!shape->isEmptyShape()) {
644         uint32_t index;
645         if (IdIsIndex(shape->propid(), &index)) {
646             if (shape->attributes() == JSPROP_ENUMERATE &&
647                 shape->hasDefaultGetter() &&
648                 shape->hasDefaultSetter())
649             {
650                 numDenseElements++;
651                 newInitializedLength = Max(newInitializedLength, index + 1);
652             } else {
653                 /*
654                  * For simplicity, only densify the object if all indexed
655                  * properties can be converted to dense elements.
656                  */
657                 return DenseElementResult::Incomplete;
658             }
659         }
660         shape = shape->previous();
661     }
662 
663     if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength)
664         return DenseElementResult::Incomplete;
665 
666     if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT)
667         return DenseElementResult::Incomplete;
668 
669     /*
670      * This object meets all necessary restrictions, convert all indexed
671      * properties into dense elements.
672      */
673 
674     if (!obj->maybeCopyElementsForWrite(cx))
675         return DenseElementResult::Failure;
676 
677     if (newInitializedLength > obj->getDenseCapacity()) {
678         if (!obj->growElements(cx, newInitializedLength))
679             return DenseElementResult::Failure;
680     }
681 
682     obj->ensureDenseInitializedLength(cx, newInitializedLength, 0);
683 
684     RootedValue value(cx);
685 
686     shape = obj->lastProperty();
687     while (!shape->isEmptyShape()) {
688         jsid id = shape->propid();
689         uint32_t index;
690         if (IdIsIndex(id, &index)) {
691             value = obj->getSlot(shape->slot());
692 
693             /*
694              * When removing a property from a dictionary, the specified
695              * property will be removed from the dictionary list and the
696              * last property will then be changed due to reshaping the object.
697              * Compute the next shape in the traverse, watching for such
698              * removals from the list.
699              */
700             if (shape != obj->lastProperty()) {
701                 shape = shape->previous();
702                 if (!obj->removeProperty(cx, id))
703                     return DenseElementResult::Failure;
704             } else {
705                 if (!obj->removeProperty(cx, id))
706                     return DenseElementResult::Failure;
707                 shape = obj->lastProperty();
708             }
709 
710             obj->setDenseElement(index, value);
711         } else {
712             shape = shape->previous();
713         }
714     }
715 
716     /*
717      * All indexed properties on the object are now dense, clear the indexed
718      * flag so that we will not start using sparse indexes again if we need
719      * to grow the object.
720      */
721     if (!obj->clearFlag(cx, BaseShape::INDEXED))
722         return DenseElementResult::Failure;
723 
724     return DenseElementResult::Success;
725 }
726 
727 // Given a requested capacity (in elements) and (potentially) the length of an
728 // array for which elements are being allocated, compute an actual allocation
729 // amount (in elements).  (Allocation amounts include space for an
730 // ObjectElements instance, so a return value of |N| implies
731 // |N - ObjectElements::VALUES_PER_HEADER| usable elements.)
732 //
733 // The requested/actual allocation distinction is meant to:
734 //
735 //   * preserve amortized O(N) time to add N elements;
736 //   * minimize the number of unused elements beyond an array's length, and
737 //   * provide at least SLOT_CAPACITY_MIN elements no matter what (so adding
738 //     the first several elements to small arrays only needs one allocation).
739 //
740 // Note: the structure and behavior of this method follow along with
741 // UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy
742 // in one should generally be matched by the other.
743 /* static */ bool
goodElementsAllocationAmount(ExclusiveContext * cx,uint32_t reqCapacity,uint32_t length,uint32_t * goodAmount)744 NativeObject::goodElementsAllocationAmount(ExclusiveContext* cx, uint32_t reqCapacity,
745                                            uint32_t length, uint32_t* goodAmount)
746 {
747     if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) {
748         ReportOutOfMemory(cx);
749         return false;
750     }
751 
752     uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
753 
754     // Handle "small" requests primarily by doubling.
755     const uint32_t Mebi = 1 << 20;
756     if (reqAllocated < Mebi) {
757         uint32_t amount = mozilla::AssertedCast<uint32_t>(RoundUpPow2(reqAllocated));
758 
759         // If |amount| would be 2/3 or more of the array's length, adjust
760         // it (up or down) to be equal to the array's length.  This avoids
761         // allocating excess elements that aren't likely to be needed, either
762         // in this resizing or a subsequent one.  The 2/3 factor is chosen so
763         // that exceptional resizings will at most triple the capacity, as
764         // opposed to the usual doubling.
765         uint32_t goodCapacity = amount - ObjectElements::VALUES_PER_HEADER;
766         if (length >= reqCapacity && goodCapacity > (length / 3) * 2)
767             amount = length + ObjectElements::VALUES_PER_HEADER;
768 
769         if (amount < SLOT_CAPACITY_MIN)
770             amount = SLOT_CAPACITY_MIN;
771 
772         *goodAmount = amount;
773 
774         return true;
775     }
776 
777     // The almost-doubling above wastes a lot of space for larger bucket sizes.
778     // For large amounts, switch to bucket sizes that obey this formula:
779     //
780     //   count(n+1) = Math.ceil(count(n) * 1.125)
781     //
782     // where |count(n)| is the size of the nth bucket, measured in 2**20 slots.
783     // These bucket sizes still preserve amortized O(N) time to add N elements,
784     // just with a larger constant factor.
785     //
786     // The bucket size table below was generated with this JavaScript (and
787     // manual reformatting):
788     //
789     //   for (let n = 1, i = 0; i < 34; i++) {
790     //     print('0x' + (n * (1 << 20)).toString(16) + ', ');
791     //     n = Math.ceil(n * 1.125);
792     //   }
793     static const uint32_t BigBuckets[] = {
794         0x100000, 0x200000, 0x300000, 0x400000, 0x500000, 0x600000, 0x700000,
795         0x800000, 0x900000, 0xb00000, 0xd00000, 0xf00000, 0x1100000, 0x1400000,
796         0x1700000, 0x1a00000, 0x1e00000, 0x2200000, 0x2700000, 0x2c00000,
797         0x3200000, 0x3900000, 0x4100000, 0x4a00000, 0x5400000, 0x5f00000,
798         0x6b00000, 0x7900000, 0x8900000, 0x9b00000, 0xaf00000, 0xc500000,
799         0xde00000, 0xfa00000
800     };
801     MOZ_ASSERT(BigBuckets[ArrayLength(BigBuckets) - 1] <= MAX_DENSE_ELEMENTS_ALLOCATION);
802 
803     // Pick the first bucket that'll fit |reqAllocated|.
804     for (uint32_t b : BigBuckets) {
805         if (b >= reqAllocated) {
806             *goodAmount = b;
807             return true;
808         }
809     }
810 
811     // Otherwise, return the maximum bucket size.
812     *goodAmount = MAX_DENSE_ELEMENTS_ALLOCATION;
813     return true;
814 }
815 
816 bool
growElements(ExclusiveContext * cx,uint32_t reqCapacity)817 NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity)
818 {
819     MOZ_ASSERT(nonProxyIsExtensible());
820     MOZ_ASSERT(canHaveNonEmptyElements());
821     MOZ_ASSERT(!denseElementsAreFrozen());
822     if (denseElementsAreCopyOnWrite())
823         MOZ_CRASH();
824 
825     uint32_t oldCapacity = getDenseCapacity();
826     MOZ_ASSERT(oldCapacity < reqCapacity);
827 
828     uint32_t newAllocated = 0;
829     if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) {
830         MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length());
831         MOZ_ASSERT(reqCapacity <= MAX_DENSE_ELEMENTS_COUNT);
832         // Preserve the |capacity <= length| invariant for arrays with
833         // non-writable length.  See also js::ArraySetLength which initially
834         // enforces this requirement.
835         newAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
836     } else {
837         if (!goodElementsAllocationAmount(cx, reqCapacity, getElementsHeader()->length, &newAllocated))
838             return false;
839     }
840 
841     uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
842     MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity);
843 
844     // If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become
845     // sparse.
846     MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
847 
848     uint32_t initlen = getDenseInitializedLength();
849 
850     HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
851     HeapSlot* newHeaderSlots;
852     if (hasDynamicElements()) {
853         MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
854         uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
855 
856         newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots, oldAllocated, newAllocated);
857         if (!newHeaderSlots)
858             return false;   // Leave elements at its old size.
859     } else {
860         newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
861         if (!newHeaderSlots)
862             return false;   // Leave elements at its old size.
863         PodCopy(newHeaderSlots, oldHeaderSlots, ObjectElements::VALUES_PER_HEADER + initlen);
864     }
865 
866     ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
867     newheader->capacity = newCapacity;
868     elements_ = newheader->elements();
869 
870     Debug_SetSlotRangeToCrashOnTouch(elements_ + initlen, newCapacity - initlen);
871 
872     return true;
873 }
874 
875 void
shrinkElements(ExclusiveContext * cx,uint32_t reqCapacity)876 NativeObject::shrinkElements(ExclusiveContext* cx, uint32_t reqCapacity)
877 {
878     uint32_t oldCapacity = getDenseCapacity();
879     MOZ_ASSERT(reqCapacity < oldCapacity);
880 
881     MOZ_ASSERT(canHaveNonEmptyElements());
882     if (denseElementsAreCopyOnWrite())
883         MOZ_CRASH();
884 
885     if (!hasDynamicElements())
886         return;
887 
888     uint32_t newAllocated = 0;
889     MOZ_ALWAYS_TRUE(goodElementsAllocationAmount(cx, reqCapacity, 0, &newAllocated));
890     MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
891     uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
892     if (newAllocated == oldAllocated)
893         return;  // Leave elements at its old size.
894 
895     MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER);
896     uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
897     MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
898 
899     HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
900     HeapSlot* newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots,
901                                                                 oldAllocated, newAllocated);
902     if (!newHeaderSlots) {
903         cx->recoverFromOutOfMemory();
904         return;  // Leave elements at its old size.
905     }
906 
907     ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
908     newheader->capacity = newCapacity;
909     elements_ = newheader->elements();
910 }
911 
912 /* static */ bool
CopyElementsForWrite(ExclusiveContext * cx,NativeObject * obj)913 NativeObject::CopyElementsForWrite(ExclusiveContext* cx, NativeObject* obj)
914 {
915     MOZ_ASSERT(obj->denseElementsAreCopyOnWrite());
916     MOZ_ASSERT(!obj->denseElementsAreFrozen());
917 
918     // The original owner of a COW elements array should never be modified.
919     MOZ_ASSERT(obj->getElementsHeader()->ownerObject() != obj);
920 
921     uint32_t initlen = obj->getDenseInitializedLength();
922     uint32_t newAllocated = 0;
923     if (!goodElementsAllocationAmount(cx, initlen, 0, &newAllocated))
924         return false;
925 
926     uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
927 
928     // COPY_ON_WRITE flags is set only if obj is a dense array.
929     MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
930 
931     JSObject::writeBarrierPre(obj->getElementsHeader()->ownerObject());
932 
933     HeapSlot* newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, obj, newAllocated);
934     if (!newHeaderSlots)
935         return false;
936     ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
937     js_memcpy(newheader, obj->getElementsHeader(),
938               (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value));
939 
940     newheader->capacity = newCapacity;
941     newheader->clearCopyOnWrite();
942     obj->elements_ = newheader->elements();
943 
944     Debug_SetSlotRangeToCrashOnTouch(obj->elements_ + initlen, newCapacity - initlen);
945 
946     return true;
947 }
948 
949 /* static */ bool
allocSlot(ExclusiveContext * cx,HandleNativeObject obj,uint32_t * slotp)950 NativeObject::allocSlot(ExclusiveContext* cx, HandleNativeObject obj, uint32_t* slotp)
951 {
952     uint32_t slot = obj->slotSpan();
953     MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
954 
955     // If this object is in dictionary mode, try to pull a free slot from the
956     // shape table's slot-number free list. Shapes without a ShapeTable have an
957     // empty free list, because we only purge ShapeTables with an empty free
958     // list.
959     if (obj->inDictionaryMode()) {
960         AutoCheckCannotGC nogc;
961         if (ShapeTable* table = obj->lastProperty()->maybeTable(nogc)) {
962             uint32_t last = table->freeList();
963             if (last != SHAPE_INVALID_SLOT) {
964 #ifdef DEBUG
965                 MOZ_ASSERT(last < slot);
966                 uint32_t next = obj->getSlot(last).toPrivateUint32();
967                 MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot);
968 #endif
969 
970                 *slotp = last;
971 
972                 const Value& vref = obj->getSlot(last);
973                 table->setFreeList(vref.toPrivateUint32());
974                 obj->setSlot(last, UndefinedValue());
975                 return true;
976             }
977         }
978     }
979 
980     if (slot >= SHAPE_MAXIMUM_SLOT) {
981         ReportOutOfMemory(cx);
982         return false;
983     }
984 
985     *slotp = slot;
986 
987     if (obj->inDictionaryMode() && !obj->setSlotSpan(cx, slot + 1))
988         return false;
989 
990     return true;
991 }
992 
993 void
freeSlot(ExclusiveContext * cx,uint32_t slot)994 NativeObject::freeSlot(ExclusiveContext* cx, uint32_t slot)
995 {
996     MOZ_ASSERT(slot < slotSpan());
997 
998     if (inDictionaryMode()) {
999         // Ensure we have a ShapeTable as it stores the object's free list (the
1000         // list of available slots in dictionary objects).
1001         AutoCheckCannotGC nogc;
1002         if (ShapeTable* table = lastProperty()->ensureTableForDictionary(cx, nogc)) {
1003             uint32_t last = table->freeList();
1004 
1005             // Can't afford to check the whole free list, but let's check the head.
1006             MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot);
1007 
1008             // Place all freed slots other than reserved slots (bug 595230) on the
1009             // dictionary's free list.
1010             if (JSSLOT_FREE(getClass()) <= slot) {
1011                 MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan());
1012                 setSlot(slot, PrivateUint32Value(last));
1013                 table->setFreeList(slot);
1014                 return;
1015             }
1016         } else {
1017             // OOM while creating the ShapeTable holding the free list. We can
1018             // recover from it - it just means we won't be able to reuse this
1019             // slot later.
1020             cx->recoverFromOutOfMemory();
1021         }
1022     }
1023     setSlot(slot, UndefinedValue());
1024 }
1025 
1026 Shape*
addDataProperty(ExclusiveContext * cx,jsid idArg,uint32_t slot,unsigned attrs)1027 NativeObject::addDataProperty(ExclusiveContext* cx, jsid idArg, uint32_t slot, unsigned attrs)
1028 {
1029     MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
1030     RootedNativeObject self(cx, this);
1031     RootedId id(cx, idArg);
1032     return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0);
1033 }
1034 
1035 Shape*
addDataProperty(ExclusiveContext * cx,HandlePropertyName name,uint32_t slot,unsigned attrs)1036 NativeObject::addDataProperty(ExclusiveContext* cx, HandlePropertyName name,
1037                               uint32_t slot, unsigned attrs)
1038 {
1039     MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
1040     RootedNativeObject self(cx, this);
1041     RootedId id(cx, NameToId(name));
1042     return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0);
1043 }
1044 
1045 template <AllowGC allowGC>
1046 bool
NativeLookupOwnProperty(ExclusiveContext * cx,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<jsid,allowGC>::HandleType id,typename MaybeRooted<Shape *,allowGC>::MutableHandleType propp)1047 js::NativeLookupOwnProperty(ExclusiveContext* cx,
1048                             typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
1049                             typename MaybeRooted<jsid, allowGC>::HandleType id,
1050                             typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp)
1051 {
1052     bool done;
1053     return LookupOwnPropertyInline<allowGC>(cx, obj, id, propp, &done);
1054 }
1055 
1056 template bool
1057 js::NativeLookupOwnProperty<CanGC>(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1058                                    MutableHandleShape propp);
1059 
1060 template bool
1061 js::NativeLookupOwnProperty<NoGC>(ExclusiveContext* cx, NativeObject* const& obj, const jsid& id,
1062                                   FakeMutableHandle<Shape*> propp);
1063 
1064 /*** [[DefineOwnProperty]] ***********************************************************************/
1065 
1066 static inline bool
CallAddPropertyHook(ExclusiveContext * cx,HandleNativeObject obj,HandleShape shape,HandleValue value)1067 CallAddPropertyHook(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape,
1068                     HandleValue value)
1069 {
1070     if (JSAddPropertyOp addProperty = obj->getClass()->getAddProperty()) {
1071         if (!cx->shouldBeJSContext())
1072             return false;
1073 
1074         RootedId id(cx, shape->propid());
1075         if (!CallJSAddPropertyOp(cx->asJSContext(), addProperty, obj, id, value)) {
1076             obj->removeProperty(cx, shape->propid());
1077             return false;
1078         }
1079     }
1080     return true;
1081 }
1082 
1083 static inline bool
CallAddPropertyHookDense(ExclusiveContext * cx,HandleNativeObject obj,uint32_t index,HandleValue value)1084 CallAddPropertyHookDense(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index,
1085                          HandleValue value)
1086 {
1087     // Inline addProperty for array objects.
1088     if (obj->is<ArrayObject>()) {
1089         ArrayObject* arr = &obj->as<ArrayObject>();
1090         uint32_t length = arr->length();
1091         if (index >= length)
1092             arr->setLength(cx, index + 1);
1093         return true;
1094     }
1095 
1096     if (JSAddPropertyOp addProperty = obj->getClass()->getAddProperty()) {
1097         if (!cx->shouldBeJSContext())
1098             return false;
1099 
1100         if (!obj->maybeCopyElementsForWrite(cx))
1101             return false;
1102 
1103         RootedId id(cx, INT_TO_JSID(index));
1104         if (!CallJSAddPropertyOp(cx->asJSContext(), addProperty, obj, id, value)) {
1105             obj->setDenseElementHole(cx, index);
1106             return false;
1107         }
1108     }
1109     return true;
1110 }
1111 
1112 static bool
UpdateShapeTypeAndValue(ExclusiveContext * cx,HandleNativeObject obj,HandleShape shape,const Value & value)1113 UpdateShapeTypeAndValue(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape, const Value& value)
1114 {
1115     jsid id = shape->propid();
1116     if (shape->hasSlot()) {
1117         obj->setSlotWithType(cx, shape, value, /* overwriting = */ false);
1118 
1119         // Per the acquired properties analysis, when the shape of a partially
1120         // initialized object is changed to its fully initialized shape, its
1121         // group can be updated as well.
1122         if (TypeNewScript* newScript = obj->groupRaw()->newScript()) {
1123             if (newScript->initializedShape() == shape)
1124                 obj->setGroup(newScript->initializedGroup());
1125         }
1126     }
1127     if (!shape->hasSlot() || !shape->hasDefaultGetter() || !shape->hasDefaultSetter())
1128         MarkTypePropertyNonData(cx, obj, id);
1129     if (!shape->writable())
1130         MarkTypePropertyNonWritable(cx, obj, id);
1131     return true;
1132 }
1133 
1134 static bool
PurgeProtoChain(ExclusiveContext * cx,JSObject * objArg,HandleId id)1135 PurgeProtoChain(ExclusiveContext* cx, JSObject* objArg, HandleId id)
1136 {
1137     /* Root locally so we can re-assign. */
1138     RootedObject obj(cx, objArg);
1139 
1140     RootedShape shape(cx);
1141     while (obj) {
1142         /* Lookups will not be cached through non-native protos. */
1143         if (!obj->isNative())
1144             break;
1145 
1146         shape = obj->as<NativeObject>().lookup(cx, id);
1147         if (shape)
1148             return obj->as<NativeObject>().shadowingShapeChange(cx, *shape);
1149 
1150         obj = obj->staticPrototype();
1151     }
1152 
1153     return true;
1154 }
1155 
1156 static bool
PurgeEnvironmentChainHelper(ExclusiveContext * cx,HandleObject objArg,HandleId id)1157 PurgeEnvironmentChainHelper(ExclusiveContext* cx, HandleObject objArg, HandleId id)
1158 {
1159     /* Re-root locally so we can re-assign. */
1160     RootedObject obj(cx, objArg);
1161 
1162     MOZ_ASSERT(obj->isNative());
1163     MOZ_ASSERT(obj->isDelegate());
1164 
1165     /* Lookups on integer ids cannot be cached through prototypes. */
1166     if (JSID_IS_INT(id))
1167         return true;
1168 
1169     if (!PurgeProtoChain(cx, obj->staticPrototype(), id))
1170         return false;
1171 
1172     /*
1173      * We must purge the environment chain only for Call objects as they are
1174      * the only kind of cacheable non-global object that can gain properties
1175      * after outer properties with the same names have been cached or
1176      * traced. Call objects may gain such properties via eval introducing new
1177      * vars; see bug 490364.
1178      */
1179     if (obj->is<CallObject>()) {
1180         while ((obj = obj->enclosingEnvironment()) != nullptr) {
1181             if (!PurgeProtoChain(cx, obj, id))
1182                 return false;
1183         }
1184     }
1185 
1186     return true;
1187 }
1188 
1189 /*
1190  * PurgeEnvironmentChain does nothing if obj is not itself a prototype or
1191  * parent environment, else it reshapes the scope and prototype chains it
1192  * links. It calls PurgeEnvironmentChainHelper, which asserts that obj is
1193  * flagged as a delegate (i.e., obj has ever been on a prototype or parent
1194  * chain).
1195  */
1196 static inline bool
PurgeEnvironmentChain(ExclusiveContext * cx,HandleObject obj,HandleId id)1197 PurgeEnvironmentChain(ExclusiveContext* cx, HandleObject obj, HandleId id)
1198 {
1199     if (obj->isDelegate() && obj->isNative())
1200         return PurgeEnvironmentChainHelper(cx, obj, id);
1201     return true;
1202 }
1203 
1204 static bool
AddOrChangeProperty(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,Handle<PropertyDescriptor> desc)1205 AddOrChangeProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1206                     Handle<PropertyDescriptor> desc)
1207 {
1208     desc.assertComplete();
1209 
1210     if (!PurgeEnvironmentChain(cx, obj, id))
1211         return false;
1212 
1213     // Use dense storage for new indexed properties where possible.
1214     if (JSID_IS_INT(id) &&
1215         !desc.getter() &&
1216         !desc.setter() &&
1217         desc.attributes() == JSPROP_ENUMERATE &&
1218         (!obj->isIndexed() || !obj->containsPure(id)) &&
1219         !obj->is<TypedArrayObject>())
1220     {
1221         uint32_t index = JSID_TO_INT(id);
1222         DenseElementResult edResult = obj->ensureDenseElements(cx, index, 1);
1223         if (edResult == DenseElementResult::Failure)
1224             return false;
1225         if (edResult == DenseElementResult::Success) {
1226             obj->setDenseElementWithType(cx, index, desc.value());
1227             if (!CallAddPropertyHookDense(cx, obj, index, desc.value()))
1228                 return false;
1229             return true;
1230         }
1231     }
1232 
1233     RootedShape shape(cx, NativeObject::putProperty(cx, obj, id, desc.getter(), desc.setter(),
1234                                                     SHAPE_INVALID_SLOT, desc.attributes(), 0));
1235     if (!shape)
1236         return false;
1237 
1238     if (!UpdateShapeTypeAndValue(cx, obj, shape, desc.value()))
1239         return false;
1240 
1241     // Clear any existing dense index after adding a sparse indexed property,
1242     // and investigate converting the object to dense indexes.
1243     if (JSID_IS_INT(id)) {
1244         if (!obj->maybeCopyElementsForWrite(cx))
1245             return false;
1246 
1247         uint32_t index = JSID_TO_INT(id);
1248         NativeObject::removeDenseElementForSparseIndex(cx, obj, index);
1249         DenseElementResult edResult =
1250             NativeObject::maybeDensifySparseElements(cx, obj);
1251         if (edResult == DenseElementResult::Failure)
1252             return false;
1253         if (edResult == DenseElementResult::Success) {
1254             MOZ_ASSERT(!desc.setter());
1255             return CallAddPropertyHookDense(cx, obj, index, desc.value());
1256         }
1257     }
1258 
1259     return CallAddPropertyHook(cx, obj, shape, desc.value());
1260 }
1261 
IsConfigurable(unsigned attrs)1262 static bool IsConfigurable(unsigned attrs) { return (attrs & JSPROP_PERMANENT) == 0; }
IsEnumerable(unsigned attrs)1263 static bool IsEnumerable(unsigned attrs) { return (attrs & JSPROP_ENUMERATE) != 0; }
IsWritable(unsigned attrs)1264 static bool IsWritable(unsigned attrs) { return (attrs & JSPROP_READONLY) == 0; }
1265 
IsAccessorDescriptor(unsigned attrs)1266 static bool IsAccessorDescriptor(unsigned attrs) {
1267     return (attrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0;
1268 }
1269 
IsDataDescriptor(unsigned attrs)1270 static bool IsDataDescriptor(unsigned attrs) {
1271     MOZ_ASSERT((attrs & (JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY)) == 0);
1272     return !IsAccessorDescriptor(attrs);
1273 }
1274 
1275 template <AllowGC allowGC>
1276 static MOZ_ALWAYS_INLINE bool
1277 GetExistingProperty(JSContext* cx,
1278                     typename MaybeRooted<Value, allowGC>::HandleType receiver,
1279                     typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
1280                     typename MaybeRooted<Shape*, allowGC>::HandleType shape,
1281                     typename MaybeRooted<Value, allowGC>::MutableHandleType vp);
1282 
1283 static bool
GetExistingPropertyValue(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,HandleShape shape,MutableHandleValue vp)1284 GetExistingPropertyValue(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1285                          HandleShape shape, MutableHandleValue vp)
1286 {
1287     if (IsImplicitDenseOrTypedArrayElement(shape)) {
1288         vp.set(obj->getDenseOrTypedArrayElement(JSID_TO_INT(id)));
1289         return true;
1290     }
1291     if (!cx->shouldBeJSContext())
1292         return false;
1293 
1294     MOZ_ASSERT(shape->propid() == id);
1295     MOZ_ASSERT(obj->contains(cx, shape));
1296 
1297     RootedValue receiver(cx, ObjectValue(*obj));
1298     return GetExistingProperty<CanGC>(cx->asJSContext(), receiver, obj, shape, vp);
1299 }
1300 
1301 /*
1302  * If ES6 draft rev 37 9.1.6.3 ValidateAndApplyPropertyDescriptor step 4 would
1303  * return early, because desc is redundant with an existing own property obj[id],
1304  * then set *redundant = true and return true.
1305  */
1306 static bool
DefinePropertyIsRedundant(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,HandleShape shape,unsigned shapeAttrs,Handle<PropertyDescriptor> desc,bool * redundant)1307 DefinePropertyIsRedundant(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1308                           HandleShape shape, unsigned shapeAttrs,
1309                           Handle<PropertyDescriptor> desc, bool *redundant)
1310 {
1311     *redundant = false;
1312 
1313     if (desc.hasConfigurable() && desc.configurable() != ((shapeAttrs & JSPROP_PERMANENT) == 0))
1314         return true;
1315     if (desc.hasEnumerable() && desc.enumerable() != ((shapeAttrs & JSPROP_ENUMERATE) != 0))
1316         return true;
1317     if (desc.isDataDescriptor()) {
1318         if ((shapeAttrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0)
1319             return true;
1320         if (desc.hasWritable() && desc.writable() != ((shapeAttrs & JSPROP_READONLY) == 0))
1321             return true;
1322         if (desc.hasValue()) {
1323             // Get the current value of the existing property.
1324             RootedValue currentValue(cx);
1325             if (!IsImplicitDenseOrTypedArrayElement(shape) &&
1326                 shape->hasSlot() &&
1327                 shape->hasDefaultGetter())
1328             {
1329                 // Inline GetExistingPropertyValue in order to omit a type
1330                 // correctness assertion that's too strict for this particular
1331                 // call site. For details, see bug 1125624 comments 13-16.
1332                 currentValue.set(obj->getSlot(shape->slot()));
1333             } else {
1334                 if (!GetExistingPropertyValue(cx, obj, id, shape, &currentValue))
1335                     return false;
1336             }
1337 
1338             // The specification calls for SameValue here, but it seems to be a
1339             // bug. See <https://bugs.ecmascript.org/show_bug.cgi?id=3508>.
1340             if (desc.value() != currentValue)
1341                 return true;
1342         }
1343 
1344         GetterOp existingGetterOp =
1345             IsImplicitDenseOrTypedArrayElement(shape) ? nullptr : shape->getter();
1346         if (desc.getter() != existingGetterOp)
1347             return true;
1348 
1349         SetterOp existingSetterOp =
1350             IsImplicitDenseOrTypedArrayElement(shape) ? nullptr : shape->setter();
1351         if (desc.setter() != existingSetterOp)
1352             return true;
1353     } else {
1354         if (desc.hasGetterObject()) {
1355             if (!(shapeAttrs & JSPROP_GETTER) || desc.getterObject() != shape->getterObject())
1356                 return true;
1357         }
1358         if (desc.hasSetterObject()) {
1359             if (!(shapeAttrs & JSPROP_SETTER) || desc.setterObject() != shape->setterObject())
1360                 return true;
1361         }
1362     }
1363 
1364     *redundant = true;
1365     return true;
1366 }
1367 
1368 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,Handle<PropertyDescriptor> desc_,ObjectOpResult & result)1369 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1370                          Handle<PropertyDescriptor> desc_,
1371                          ObjectOpResult& result)
1372 {
1373     desc_.assertValid();
1374 
1375     // Section numbers and step numbers below refer to ES6 draft rev 36
1376     // (17 March 2015).
1377     //
1378     // This function aims to implement 9.1.6 [[DefineOwnProperty]] as well as
1379     // the [[DefineOwnProperty]] methods described in 9.4.2.1 (arrays), 9.4.4.2
1380     // (arguments), and 9.4.5.3 (typed array views).
1381 
1382     // Dispense with custom behavior of exotic native objects first.
1383     if (obj->is<ArrayObject>()) {
1384         // 9.4.2.1 step 2. Redefining an array's length is very special.
1385         Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
1386         if (id == NameToId(cx->names().length)) {
1387             if (!cx->shouldBeJSContext())
1388                 return false;
1389             return ArraySetLength(cx->asJSContext(), arr, id, desc_.attributes(), desc_.value(),
1390                                   result);
1391         }
1392 
1393         // 9.4.2.1 step 3. Don't extend a fixed-length array.
1394         uint32_t index;
1395         if (IdIsIndex(id, &index)) {
1396             if (WouldDefinePastNonwritableLength(obj, index))
1397                 return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
1398         }
1399     } else if (obj->is<TypedArrayObject>()) {
1400         // 9.4.5.3 step 3. Indexed properties of typed arrays are special.
1401         uint64_t index;
1402         if (IsTypedArrayIndex(id, &index)) {
1403             if (!cx->shouldBeJSContext())
1404                 return false;
1405             return DefineTypedArrayElement(cx->asJSContext(), obj, index, desc_, result);
1406         }
1407     } else if (obj->is<ArgumentsObject>()) {
1408         if (id == NameToId(cx->names().length)) {
1409             // Either we are resolving the .length property on this object, or
1410             // redefining it. In the latter case only, we must set a bit. To
1411             // distinguish the two cases, we note that when resolving, the
1412             // property won't already exist; whereas the first time it is
1413             // redefined, it will.
1414             if ((desc_.attributes() & JSPROP_RESOLVING) == 0)
1415                 obj->as<ArgumentsObject>().markLengthOverridden();
1416         } else if (JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id) == cx->wellKnownSymbols().iterator) {
1417             // Do same thing as .length for [@@iterator].
1418             if ((desc_.attributes() & JSPROP_RESOLVING) == 0)
1419                 obj->as<ArgumentsObject>().markIteratorOverridden();
1420         } else if (JSID_IS_INT(id)) {
1421             if ((desc_.attributes() & JSPROP_RESOLVING) == 0)
1422                 obj->as<ArgumentsObject>().markElementOverridden();
1423         }
1424     }
1425 
1426     // 9.1.6.1 OrdinaryDefineOwnProperty steps 1-2.
1427     RootedShape shape(cx);
1428     if (desc_.attributes() & JSPROP_RESOLVING) {
1429         // We are being called from a resolve or enumerate hook to reify a
1430         // lazily-resolved property. To avoid reentering the resolve hook and
1431         // recursing forever, skip the resolve hook when doing this lookup.
1432         NativeLookupOwnPropertyNoResolve(cx, obj, id, &shape);
1433     } else {
1434         if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
1435             return false;
1436     }
1437 
1438     // From this point, the step numbers refer to
1439     // 9.1.6.3, ValidateAndApplyPropertyDescriptor.
1440     // Step 1 is a redundant assertion.
1441 
1442     // Filling in desc: Here we make a copy of the desc_ argument. We will turn
1443     // it into a complete descriptor before updating obj. The spec algorithm
1444     // does not explicitly do this, but the end result is the same. Search for
1445     // "fill in" below for places where the filling-in actually occurs.
1446     Rooted<PropertyDescriptor> desc(cx, desc_);
1447 
1448     // Step 2.
1449     if (!shape) {
1450         if (!obj->nonProxyIsExtensible())
1451             return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
1452 
1453         // Fill in missing desc fields with defaults.
1454         CompletePropertyDescriptor(&desc);
1455 
1456         if (!AddOrChangeProperty(cx, obj, id, desc))
1457             return false;
1458         return result.succeed();
1459     }
1460 
1461     MOZ_ASSERT(shape);
1462 
1463     // Steps 3-4. (Step 3 is a special case of step 4.) We use shapeAttrs as a
1464     // stand-in for shape in many places below, since shape might not be a
1465     // pointer to a real Shape (see IsImplicitDenseOrTypedArrayElement).
1466     unsigned shapeAttrs = GetShapeAttributes(obj, shape);
1467     bool redundant;
1468     if (!DefinePropertyIsRedundant(cx, obj, id, shape, shapeAttrs, desc, &redundant))
1469         return false;
1470     if (redundant) {
1471         // In cases involving JSOP_NEWOBJECT and JSOP_INITPROP, obj can have a
1472         // type for this property that doesn't match the value in the slot.
1473         // Update the type here, even though this DefineProperty call is
1474         // otherwise a no-op. (See bug 1125624 comment 13.)
1475         if (!IsImplicitDenseOrTypedArrayElement(shape) && desc.hasValue()) {
1476             if (!UpdateShapeTypeAndValue(cx, obj, shape, desc.value()))
1477                 return false;
1478         }
1479         return result.succeed();
1480     }
1481 
1482     // Non-standard hack: Allow redefining non-configurable properties if
1483     // JSPROP_REDEFINE_NONCONFIGURABLE is set _and_ the object is a non-DOM
1484     // global. The idea is that a DOM object can never have such a thing on
1485     // its proto chain directly on the web, so we should be OK optimizing
1486     // access to accessors found on such an object. Bug 1105518 contemplates
1487     // removing this hack.
1488     bool skipRedefineChecks = (desc.attributes() & JSPROP_REDEFINE_NONCONFIGURABLE) &&
1489                               obj->is<GlobalObject>() &&
1490                               !obj->getClass()->isDOMClass();
1491 
1492     // Step 5.
1493     if (!IsConfigurable(shapeAttrs) && !skipRedefineChecks) {
1494         if (desc.hasConfigurable() && desc.configurable())
1495             return result.fail(JSMSG_CANT_REDEFINE_PROP);
1496         if (desc.hasEnumerable() && desc.enumerable() != IsEnumerable(shapeAttrs))
1497             return result.fail(JSMSG_CANT_REDEFINE_PROP);
1498     }
1499 
1500     // Fill in desc.[[Configurable]] and desc.[[Enumerable]] if missing.
1501     if (!desc.hasConfigurable())
1502         desc.setConfigurable(IsConfigurable(shapeAttrs));
1503     if (!desc.hasEnumerable())
1504         desc.setEnumerable(IsEnumerable(shapeAttrs));
1505 
1506     // Steps 6-9.
1507     if (desc.isGenericDescriptor()) {
1508         // Step 6. No further validation is required.
1509 
1510         // Fill in desc. A generic descriptor has none of these fields, so copy
1511         // everything from shape.
1512         MOZ_ASSERT(!desc.hasValue());
1513         MOZ_ASSERT(!desc.hasWritable());
1514         MOZ_ASSERT(!desc.hasGetterObject());
1515         MOZ_ASSERT(!desc.hasSetterObject());
1516         if (IsDataDescriptor(shapeAttrs)) {
1517             RootedValue currentValue(cx);
1518             if (!GetExistingPropertyValue(cx, obj, id, shape, &currentValue))
1519                 return false;
1520             desc.setValue(currentValue);
1521             desc.setWritable(IsWritable(shapeAttrs));
1522         } else {
1523             desc.setGetterObject(shape->getterObject());
1524             desc.setSetterObject(shape->setterObject());
1525         }
1526     } else if (desc.isDataDescriptor() != IsDataDescriptor(shapeAttrs)) {
1527         // Step 7.
1528         if (!IsConfigurable(shapeAttrs) && !skipRedefineChecks)
1529             return result.fail(JSMSG_CANT_REDEFINE_PROP);
1530 
1531         if (IsImplicitDenseOrTypedArrayElement(shape)) {
1532             MOZ_ASSERT(!obj->is<TypedArrayObject>());
1533             if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id)))
1534                 return false;
1535             shape = obj->lookup(cx, id);
1536         }
1537 
1538         // Fill in desc fields with default values (steps 7.b.i and 7.c.i).
1539         CompletePropertyDescriptor(&desc);
1540     } else if (desc.isDataDescriptor()) {
1541         // Step 8.
1542         bool frozen = !IsConfigurable(shapeAttrs) && !IsWritable(shapeAttrs);
1543         if (frozen && desc.hasWritable() && desc.writable() && !skipRedefineChecks)
1544             return result.fail(JSMSG_CANT_REDEFINE_PROP);
1545 
1546         if (frozen || !desc.hasValue()) {
1547             if (IsImplicitDenseOrTypedArrayElement(shape)) {
1548                 MOZ_ASSERT(!obj->is<TypedArrayObject>());
1549                 if (!NativeObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id)))
1550                     return false;
1551                 shape = obj->lookup(cx, id);
1552             }
1553 
1554             RootedValue currentValue(cx);
1555             if (!GetExistingPropertyValue(cx, obj, id, shape, &currentValue))
1556                 return false;
1557 
1558             if (!desc.hasValue()) {
1559                 // Fill in desc.[[Value]].
1560                 desc.setValue(currentValue);
1561             } else {
1562                 // Step 8.a.ii.1.
1563                 bool same;
1564                 if (!cx->shouldBeJSContext())
1565                     return false;
1566                 if (!SameValue(cx->asJSContext(), desc.value(), currentValue, &same))
1567                     return false;
1568                 if (!same && !skipRedefineChecks)
1569                     return result.fail(JSMSG_CANT_REDEFINE_PROP);
1570             }
1571         }
1572 
1573         if (!desc.hasWritable())
1574             desc.setWritable(IsWritable(shapeAttrs));
1575     } else {
1576         // Step 9.
1577         MOZ_ASSERT(shape->isAccessorDescriptor());
1578         MOZ_ASSERT(desc.isAccessorDescriptor());
1579 
1580         // The spec says to use SameValue, but since the values in
1581         // question are objects, we can just compare pointers.
1582         if (desc.hasSetterObject()) {
1583             if (!IsConfigurable(shapeAttrs) &&
1584                 desc.setterObject() != shape->setterObject() &&
1585                 !skipRedefineChecks)
1586             {
1587                 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1588             }
1589         } else {
1590             // Fill in desc.[[Set]] from shape.
1591             desc.setSetterObject(shape->setterObject());
1592         }
1593         if (desc.hasGetterObject()) {
1594             if (!IsConfigurable(shapeAttrs) &&
1595                 desc.getterObject() != shape->getterObject() &&
1596                 !skipRedefineChecks)
1597             {
1598                 return result.fail(JSMSG_CANT_REDEFINE_PROP);
1599             }
1600         } else {
1601             // Fill in desc.[[Get]] from shape.
1602             desc.setGetterObject(shape->getterObject());
1603         }
1604     }
1605 
1606     // Step 10.
1607     if (!AddOrChangeProperty(cx, obj, id, desc))
1608         return false;
1609     return result.succeed();
1610 }
1611 
1612 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,GetterOp getter,SetterOp setter,unsigned attrs,ObjectOpResult & result)1613 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1614                          HandleValue value, GetterOp getter, SetterOp setter, unsigned attrs,
1615                          ObjectOpResult& result)
1616 {
1617     Rooted<PropertyDescriptor> desc(cx);
1618     desc.initFields(nullptr, value, attrs, getter, setter);
1619     return NativeDefineProperty(cx, obj, id, desc, result);
1620 }
1621 
1622 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,PropertyName * name,HandleValue value,GetterOp getter,SetterOp setter,unsigned attrs,ObjectOpResult & result)1623 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* name,
1624                          HandleValue value, GetterOp getter, SetterOp setter, unsigned attrs,
1625                          ObjectOpResult& result)
1626 {
1627     RootedId id(cx, NameToId(name));
1628     return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs, result);
1629 }
1630 
1631 bool
NativeDefineElement(ExclusiveContext * cx,HandleNativeObject obj,uint32_t index,HandleValue value,GetterOp getter,SetterOp setter,unsigned attrs,ObjectOpResult & result)1632 js::NativeDefineElement(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index,
1633                         HandleValue value, GetterOp getter, SetterOp setter, unsigned attrs,
1634                         ObjectOpResult& result)
1635 {
1636     RootedId id(cx);
1637     if (index <= JSID_INT_MAX) {
1638         id = INT_TO_JSID(index);
1639         return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs, result);
1640     }
1641 
1642     AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
1643 
1644     if (!IndexToId(cx, index, &id))
1645         return false;
1646 
1647     return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs, result);
1648 }
1649 
1650 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,JSGetterOp getter,JSSetterOp setter,unsigned attrs)1651 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
1652                          HandleValue value, JSGetterOp getter, JSSetterOp setter,
1653                          unsigned attrs)
1654 {
1655     ObjectOpResult result;
1656     if (!NativeDefineProperty(cx, obj, id, value, getter, setter, attrs, result))
1657         return false;
1658     if (!result) {
1659         // Off-main-thread callers should not get here: they must call this
1660         // function only with known-valid arguments. Populating a new
1661         // PlainObject with configurable properties is fine.
1662         if (!cx->shouldBeJSContext())
1663             return false;
1664         result.reportError(cx->asJSContext(), obj, id);
1665         return false;
1666     }
1667     return true;
1668 }
1669 
1670 bool
NativeDefineProperty(ExclusiveContext * cx,HandleNativeObject obj,PropertyName * name,HandleValue value,JSGetterOp getter,JSSetterOp setter,unsigned attrs)1671 js::NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* name,
1672                          HandleValue value, JSGetterOp getter, JSSetterOp setter,
1673                          unsigned attrs)
1674 {
1675     RootedId id(cx, NameToId(name));
1676     return NativeDefineProperty(cx, obj, id, value, getter, setter, attrs);
1677 }
1678 
1679 
1680 /*** [[HasProperty]] *****************************************************************************/
1681 
1682 // ES6 draft rev31 9.1.7.1 OrdinaryHasProperty
1683 bool
NativeHasProperty(JSContext * cx,HandleNativeObject obj,HandleId id,bool * foundp)1684 js::NativeHasProperty(JSContext* cx, HandleNativeObject obj, HandleId id, bool* foundp)
1685 {
1686     RootedNativeObject pobj(cx, obj);
1687     RootedShape shape(cx);
1688 
1689     // This loop isn't explicit in the spec algorithm. See the comment on step
1690     // 7.a. below.
1691     for (;;) {
1692         // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.)
1693         bool done;
1694         if (!LookupOwnPropertyInline<CanGC>(cx, pobj, id, &shape, &done))
1695             return false;
1696 
1697         // Step 4.
1698         if (shape) {
1699             *foundp = true;
1700             return true;
1701         }
1702 
1703         // Step 5-6. The check for 'done' on this next line is tricky.
1704         // done can be true in exactly these unlikely-sounding cases:
1705         // - We're looking up an element, and pobj is a TypedArray that
1706         //   doesn't have that many elements.
1707         // - We're being called from a resolve hook to assign to the property
1708         //   being resolved.
1709         // What they all have in common is we do not want to keep walking
1710         // the prototype chain, and always claim that the property
1711         // doesn't exist.
1712         RootedObject proto(cx, done ? nullptr : pobj->staticPrototype());
1713 
1714         // Step 8.
1715         if (!proto) {
1716             *foundp = false;
1717             return true;
1718         }
1719 
1720         // Step 7.a. If the prototype is also native, this step is a
1721         // recursive tail call, and we don't need to go through all the
1722         // plumbing of HasProperty; the top of the loop is where
1723         // we're going to end up anyway. But if pobj is non-native,
1724         // that optimization would be incorrect.
1725         if (!proto->isNative())
1726             return HasProperty(cx, proto, id, foundp);
1727 
1728         pobj = &proto->as<NativeObject>();
1729     }
1730 }
1731 
1732 
1733 /*** [[GetOwnPropertyDescriptor]] ****************************************************************/
1734 
1735 bool
NativeGetOwnPropertyDescriptor(JSContext * cx,HandleNativeObject obj,HandleId id,MutableHandle<PropertyDescriptor> desc)1736 js::NativeGetOwnPropertyDescriptor(JSContext* cx, HandleNativeObject obj, HandleId id,
1737                                    MutableHandle<PropertyDescriptor> desc)
1738 {
1739     RootedShape shape(cx);
1740     if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
1741         return false;
1742     if (!shape) {
1743         desc.object().set(nullptr);
1744         return true;
1745     }
1746 
1747     desc.setAttributes(GetShapeAttributes(obj, shape));
1748     if (desc.isAccessorDescriptor()) {
1749         MOZ_ASSERT(desc.isShared());
1750 
1751         // The result of GetOwnPropertyDescriptor() must be either undefined or
1752         // a complete property descriptor (per ES6 draft rev 32 (2015 Feb 2)
1753         // 6.1.7.3, Invariants of the Essential Internal Methods).
1754         //
1755         // It is an unfortunate fact that in SM, properties can exist that have
1756         // JSPROP_GETTER or JSPROP_SETTER but not both. In these cases, rather
1757         // than return true with desc incomplete, we fill out the missing
1758         // getter or setter with a null, following CompletePropertyDescriptor.
1759         if (desc.hasGetterObject()) {
1760             desc.setGetterObject(shape->getterObject());
1761         } else {
1762             desc.setGetterObject(nullptr);
1763             desc.attributesRef() |= JSPROP_GETTER;
1764         }
1765         if (desc.hasSetterObject()) {
1766             desc.setSetterObject(shape->setterObject());
1767         } else {
1768             desc.setSetterObject(nullptr);
1769             desc.attributesRef() |= JSPROP_SETTER;
1770         }
1771 
1772         desc.value().setUndefined();
1773     } else {
1774         // This is either a straight-up data property or (rarely) a
1775         // property with a JSGetterOp/JSSetterOp. The latter must be
1776         // reported to the caller as a plain data property, so clear
1777         // desc.getter/setter, and mask away the SHARED bit.
1778         desc.setGetter(nullptr);
1779         desc.setSetter(nullptr);
1780         desc.attributesRef() &= ~JSPROP_SHARED;
1781 
1782         if (IsImplicitDenseOrTypedArrayElement(shape)) {
1783             desc.value().set(obj->getDenseOrTypedArrayElement(JSID_TO_INT(id)));
1784         } else {
1785             if (!NativeGetExistingProperty(cx, obj, obj, shape, desc.value()))
1786                 return false;
1787         }
1788     }
1789 
1790     desc.object().set(obj);
1791     desc.assertComplete();
1792     return true;
1793 }
1794 
1795 
1796 /*** [[Get]] *************************************************************************************/
1797 
1798 static inline bool
CallGetter(JSContext * cx,HandleObject obj,HandleValue receiver,HandleShape shape,MutableHandleValue vp)1799 CallGetter(JSContext* cx, HandleObject obj, HandleValue receiver, HandleShape shape,
1800            MutableHandleValue vp)
1801 {
1802     MOZ_ASSERT(!shape->hasDefaultGetter());
1803 
1804     if (shape->hasGetterValue()) {
1805         RootedValue getter(cx, shape->getterValue());
1806         return js::CallGetter(cx, receiver, getter, vp);
1807     }
1808 
1809     // In contrast to normal getters JSGetterOps always want the holder.
1810     RootedId id(cx, shape->propid());
1811     return CallJSGetterOp(cx, shape->getterOp(), obj, id, vp);
1812 }
1813 
1814 template <AllowGC allowGC>
1815 static MOZ_ALWAYS_INLINE bool
GetExistingProperty(JSContext * cx,typename MaybeRooted<Value,allowGC>::HandleType receiver,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<Shape *,allowGC>::HandleType shape,typename MaybeRooted<Value,allowGC>::MutableHandleType vp)1816 GetExistingProperty(JSContext* cx,
1817                     typename MaybeRooted<Value, allowGC>::HandleType receiver,
1818                     typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
1819                     typename MaybeRooted<Shape*, allowGC>::HandleType shape,
1820                     typename MaybeRooted<Value, allowGC>::MutableHandleType vp)
1821 {
1822     if (shape->hasSlot()) {
1823         vp.set(obj->getSlot(shape->slot()));
1824         MOZ_ASSERT_IF(!vp.isMagic(JS_UNINITIALIZED_LEXICAL) &&
1825                       !obj->isSingleton() &&
1826                       !obj->template is<EnvironmentObject>() &&
1827                       shape->hasDefaultGetter(),
1828                       ObjectGroupHasProperty(cx, obj->group(), shape->propid(), vp));
1829     } else {
1830         vp.setUndefined();
1831     }
1832     if (shape->hasDefaultGetter())
1833         return true;
1834 
1835     {
1836         jsbytecode* pc;
1837         JSScript* script = cx->currentScript(&pc);
1838         if (script && script->hasBaselineScript()) {
1839             switch (JSOp(*pc)) {
1840               case JSOP_GETPROP:
1841               case JSOP_CALLPROP:
1842               case JSOP_LENGTH:
1843                 script->baselineScript()->noteAccessedGetter(script->pcToOffset(pc));
1844                 break;
1845               default:
1846                 break;
1847             }
1848         }
1849     }
1850 
1851     if (!allowGC)
1852         return false;
1853 
1854     if (!CallGetter(cx,
1855                     MaybeRooted<JSObject*, allowGC>::toHandle(obj),
1856                     MaybeRooted<Value, allowGC>::toHandle(receiver),
1857                     MaybeRooted<Shape*, allowGC>::toHandle(shape),
1858                     MaybeRooted<Value, allowGC>::toMutableHandle(vp)))
1859     {
1860         return false;
1861     }
1862 
1863     // Ancient nonstandard extension: via the JSAPI it's possible to create a
1864     // data property that has both a slot and a getter. In that case, copy the
1865     // value returned by the getter back into the slot.
1866     if (shape->hasSlot() && obj->contains(cx, shape))
1867         obj->setSlot(shape->slot(), vp);
1868 
1869     return true;
1870 }
1871 
1872 bool
NativeGetExistingProperty(JSContext * cx,HandleObject receiver,HandleNativeObject obj,HandleShape shape,MutableHandleValue vp)1873 js::NativeGetExistingProperty(JSContext* cx, HandleObject receiver, HandleNativeObject obj,
1874                               HandleShape shape, MutableHandleValue vp)
1875 {
1876     RootedValue receiverValue(cx, ObjectValue(*receiver));
1877     return GetExistingProperty<CanGC>(cx, receiverValue, obj, shape, vp);
1878 }
1879 
1880 /*
1881  * Given pc pointing after a property accessing bytecode, return true if the
1882  * access is "property-detecting" -- that is, if we shouldn't warn about it
1883  * even if no such property is found and strict warnings are enabled.
1884  */
1885 static bool
Detecting(JSContext * cx,JSScript * script,jsbytecode * pc)1886 Detecting(JSContext* cx, JSScript* script, jsbytecode* pc)
1887 {
1888     MOZ_ASSERT(script->containsPC(pc));
1889 
1890     // Skip jump target opcodes.
1891     while (pc < script->codeEnd() && BytecodeIsJumpTarget(JSOp(*pc)))
1892         pc = GetNextPc(pc);
1893 
1894     MOZ_ASSERT(script->containsPC(pc));
1895     if (pc >= script->codeEnd())
1896         return false;
1897 
1898     // General case: a branch or equality op follows the access.
1899     JSOp op = JSOp(*pc);
1900     if (CodeSpec[op].format & JOF_DETECTING)
1901         return true;
1902 
1903     jsbytecode* endpc = script->codeEnd();
1904 
1905     if (op == JSOP_NULL) {
1906         // Special case #1: don't warn about (obj.prop == null).
1907         if (++pc < endpc) {
1908             op = JSOp(*pc);
1909             return op == JSOP_EQ || op == JSOP_NE;
1910         }
1911         return false;
1912     }
1913 
1914     if (op == JSOP_GETGNAME || op == JSOP_GETNAME) {
1915         // Special case #2: don't warn about (obj.prop == undefined).
1916         JSAtom* atom = script->getAtom(GET_UINT32_INDEX(pc));
1917         if (atom == cx->names().undefined &&
1918             (pc += CodeSpec[op].length) < endpc) {
1919             op = JSOp(*pc);
1920             return op == JSOP_EQ || op == JSOP_NE || op == JSOP_STRICTEQ || op == JSOP_STRICTNE;
1921         }
1922     }
1923 
1924     return false;
1925 }
1926 
1927 enum IsNameLookup { NotNameLookup = false, NameLookup = true };
1928 
1929 /*
1930  * Finish getting the property `receiver[id]` after looking at every object on
1931  * the prototype chain and not finding any such property.
1932  *
1933  * Per the spec, this should just set the result to `undefined` and call it a
1934  * day. However:
1935  *
1936  * 1.  We add support for the nonstandard JSClass::getProperty hook.
1937  *
1938  * 2.  This function also runs when we're evaluating an expression that's an
1939  *     Identifier (that is, an unqualified name lookup), so we need to figure
1940  *     out if that's what's happening and throw a ReferenceError if so.
1941  *
1942  * 3.  We also emit an optional warning for this. (It's not super useful on the
1943  *     web, as there are too many false positives, but anecdotally useful in
1944  *     Gecko code.)
1945  */
1946 static bool
GetNonexistentProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue receiver,IsNameLookup nameLookup,MutableHandleValue vp)1947 GetNonexistentProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
1948                        HandleValue receiver, IsNameLookup nameLookup, MutableHandleValue vp)
1949 {
1950     vp.setUndefined();
1951 
1952     // Non-standard extension: Call the getProperty hook. If it sets vp to a
1953     // value other than undefined, we're done. If not, fall through to the
1954     // warning/error checks below.
1955     if (JSGetterOp getProperty = obj->getClass()->getGetProperty()) {
1956         if (!CallJSGetterOp(cx, getProperty, obj, id, vp))
1957             return false;
1958 
1959         if (!vp.isUndefined())
1960             return true;
1961     }
1962 
1963     // If we are doing a name lookup, this is a ReferenceError.
1964     if (nameLookup)
1965         return ReportIsNotDefined(cx, id);
1966 
1967     // Give a strict warning if foo.bar is evaluated by a script for an object
1968     // foo with no property named 'bar'.
1969     //
1970     // Don't warn if extra warnings not enabled or for random getprop
1971     // operations.
1972     if (!cx->compartment()->behaviors().extraWarnings(cx))
1973         return true;
1974 
1975     jsbytecode* pc;
1976     RootedScript script(cx, cx->currentScript(&pc));
1977     if (!script)
1978         return true;
1979 
1980     if (*pc != JSOP_GETPROP && *pc != JSOP_GETELEM)
1981         return true;
1982 
1983     // Don't warn repeatedly for the same script.
1984     if (script->warnedAboutUndefinedProp())
1985         return true;
1986 
1987     // Don't warn in self-hosted code (where the further presence of
1988     // JS::RuntimeOptions::werror() would result in impossible-to-avoid
1989     // errors to entirely-innocent client code).
1990     if (script->selfHosted())
1991         return true;
1992 
1993     // We may just be checking if that object has an iterator.
1994     if (JSID_IS_ATOM(id, cx->names().iteratorIntrinsic))
1995         return true;
1996 
1997     // Do not warn about tests like (obj[prop] == undefined).
1998     pc += CodeSpec[*pc].length;
1999     if (Detecting(cx, script, pc))
2000         return true;
2001 
2002     unsigned flags = JSREPORT_WARNING | JSREPORT_STRICT;
2003     script->setWarnedAboutUndefinedProp();
2004 
2005     // Ok, bad undefined property reference: whine about it.
2006     RootedValue val(cx, IdToValue(id));
2007     return ReportValueErrorFlags(cx, flags, JSMSG_UNDEFINED_PROP, JSDVG_IGNORE_STACK, val,
2008                                     nullptr, nullptr, nullptr);
2009 }
2010 
2011 /* The NoGC version of GetNonexistentProperty, present only to make types line up. */
2012 bool
GetNonexistentProperty(JSContext * cx,NativeObject * const & obj,const jsid & id,const Value & receiver,IsNameLookup nameLookup,FakeMutableHandle<Value> vp)2013 GetNonexistentProperty(JSContext* cx, NativeObject* const& obj, const jsid& id, const Value& receiver,
2014                        IsNameLookup nameLookup, FakeMutableHandle<Value> vp)
2015 {
2016     return false;
2017 }
2018 
2019 static inline bool
GeneralizedGetProperty(JSContext * cx,HandleObject obj,HandleId id,HandleValue receiver,IsNameLookup nameLookup,MutableHandleValue vp)2020 GeneralizedGetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue receiver,
2021                        IsNameLookup nameLookup, MutableHandleValue vp)
2022 {
2023     JS_CHECK_RECURSION(cx, return false);
2024     if (nameLookup) {
2025         // When nameLookup is true, GetProperty implements ES6 rev 34 (2015 Feb
2026         // 20) 8.1.1.2.6 GetBindingValue, with step 3 (the call to HasProperty)
2027         // and step 6 (the call to Get) fused so that only a single lookup is
2028         // needed.
2029         //
2030         // If we get here, we've reached a non-native object. Fall back on the
2031         // algorithm as specified, with two separate lookups. (Note that we
2032         // throw ReferenceErrors regardless of strictness, technically a bug.)
2033 
2034         bool found;
2035         if (!HasProperty(cx, obj, id, &found))
2036             return false;
2037         if (!found)
2038             return ReportIsNotDefined(cx, id);
2039     }
2040 
2041     return GetProperty(cx, obj, receiver, id, vp);
2042 }
2043 
2044 static inline bool
GeneralizedGetProperty(JSContext * cx,JSObject * obj,jsid id,const Value & receiver,IsNameLookup nameLookup,FakeMutableHandle<Value> vp)2045 GeneralizedGetProperty(JSContext* cx, JSObject* obj, jsid id, const Value& receiver,
2046                        IsNameLookup nameLookup, FakeMutableHandle<Value> vp)
2047 {
2048     JS_CHECK_RECURSION_DONT_REPORT(cx, return false);
2049     if (nameLookup)
2050         return false;
2051     return GetPropertyNoGC(cx, obj, receiver, id, vp.address());
2052 }
2053 
2054 template <AllowGC allowGC>
2055 static MOZ_ALWAYS_INLINE bool
NativeGetPropertyInline(JSContext * cx,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<Value,allowGC>::HandleType receiver,typename MaybeRooted<jsid,allowGC>::HandleType id,IsNameLookup nameLookup,typename MaybeRooted<Value,allowGC>::MutableHandleType vp)2056 NativeGetPropertyInline(JSContext* cx,
2057                         typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
2058                         typename MaybeRooted<Value, allowGC>::HandleType receiver,
2059                         typename MaybeRooted<jsid, allowGC>::HandleType id,
2060                         IsNameLookup nameLookup,
2061                         typename MaybeRooted<Value, allowGC>::MutableHandleType vp)
2062 {
2063     typename MaybeRooted<NativeObject*, allowGC>::RootType pobj(cx, obj);
2064     typename MaybeRooted<Shape*, allowGC>::RootType shape(cx);
2065 
2066     // This loop isn't explicit in the spec algorithm. See the comment on step
2067     // 4.d below.
2068     for (;;) {
2069         // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.)
2070         bool done;
2071         if (!LookupOwnPropertyInline<allowGC>(cx, pobj, id, &shape, &done))
2072             return false;
2073 
2074         if (shape) {
2075             // Steps 5-8. Special case for dense elements because
2076             // GetExistingProperty doesn't support those.
2077             if (IsImplicitDenseOrTypedArrayElement(shape)) {
2078                 vp.set(pobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)));
2079                 return true;
2080             }
2081             return GetExistingProperty<allowGC>(cx, receiver, pobj, shape, vp);
2082         }
2083 
2084         // Steps 4.a-b. The check for 'done' on this next line is tricky.
2085         // done can be true in exactly these unlikely-sounding cases:
2086         // - We're looking up an element, and pobj is a TypedArray that
2087         //   doesn't have that many elements.
2088         // - We're being called from a resolve hook to assign to the property
2089         //   being resolved.
2090         // What they all have in common is we do not want to keep walking
2091         // the prototype chain.
2092         RootedObject proto(cx, done ? nullptr : pobj->staticPrototype());
2093 
2094         // Step 4.c. The spec algorithm simply returns undefined if proto is
2095         // null, but see the comment on GetNonexistentProperty.
2096         if (!proto)
2097             return GetNonexistentProperty(cx, obj, id, receiver, nameLookup, vp);
2098 
2099         // Step 4.d. If the prototype is also native, this step is a
2100         // recursive tail call, and we don't need to go through all the
2101         // plumbing of JSObject::getGeneric; the top of the loop is where
2102         // we're going to end up anyway. But if pobj is non-native,
2103         // that optimization would be incorrect.
2104         if (proto->getOpsGetProperty())
2105             return GeneralizedGetProperty(cx, proto, id, receiver, nameLookup, vp);
2106 
2107         pobj = &proto->as<NativeObject>();
2108     }
2109 }
2110 
2111 bool
NativeGetProperty(JSContext * cx,HandleNativeObject obj,HandleValue receiver,HandleId id,MutableHandleValue vp)2112 js::NativeGetProperty(JSContext* cx, HandleNativeObject obj, HandleValue receiver, HandleId id,
2113                       MutableHandleValue vp)
2114 {
2115     return NativeGetPropertyInline<CanGC>(cx, obj, receiver, id, NotNameLookup, vp);
2116 }
2117 
2118 bool
NativeGetPropertyNoGC(JSContext * cx,NativeObject * obj,const Value & receiver,jsid id,Value * vp)2119 js::NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj, const Value& receiver, jsid id, Value* vp)
2120 {
2121     AutoAssertNoException noexc(cx);
2122     return NativeGetPropertyInline<NoGC>(cx, obj, receiver, id, NotNameLookup, vp);
2123 }
2124 
2125 bool
GetPropertyForNameLookup(JSContext * cx,HandleObject obj,HandleId id,MutableHandleValue vp)2126 js::GetPropertyForNameLookup(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
2127 {
2128     RootedValue receiver(cx, ObjectValue(*obj));
2129     if (obj->getOpsGetProperty())
2130         return GeneralizedGetProperty(cx, obj, id, receiver, NameLookup, vp);
2131     return NativeGetPropertyInline<CanGC>(cx, obj.as<NativeObject>(), receiver, id, NameLookup, vp);
2132 }
2133 
2134 
2135 /*** [[Set]] *************************************************************************************/
2136 
2137 static bool
MaybeReportUndeclaredVarAssignment(JSContext * cx,HandleString propname)2138 MaybeReportUndeclaredVarAssignment(JSContext* cx, HandleString propname)
2139 {
2140     unsigned flags;
2141     {
2142         jsbytecode* pc;
2143         JSScript* script = cx->currentScript(&pc, JSContext::ALLOW_CROSS_COMPARTMENT);
2144         if (!script)
2145             return true;
2146 
2147         // If the code is not strict and extra warnings aren't enabled, then no
2148         // check is needed.
2149         if (IsStrictSetPC(pc))
2150             flags = JSREPORT_ERROR;
2151         else if (cx->compartment()->behaviors().extraWarnings(cx))
2152             flags = JSREPORT_WARNING | JSREPORT_STRICT;
2153         else
2154             return true;
2155     }
2156 
2157     JSAutoByteString bytes;
2158     if (!bytes.encodeUtf8(cx, propname))
2159         return false;
2160     return JS_ReportErrorFlagsAndNumberUTF8(cx, flags, GetErrorMessage, nullptr,
2161                                             JSMSG_UNDECLARED_VAR, bytes.ptr());
2162 }
2163 
2164 /*
2165  * Finish assignment to a shapeful data property of a native object obj. This
2166  * conforms to no standard and there is a lot of legacy baggage here.
2167  */
2168 static bool
NativeSetExistingDataProperty(JSContext * cx,HandleNativeObject obj,HandleShape shape,HandleValue v,HandleValue receiver,ObjectOpResult & result)2169 NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj, HandleShape shape,
2170                               HandleValue v, HandleValue receiver, ObjectOpResult& result)
2171 {
2172     MOZ_ASSERT(obj->isNative());
2173     MOZ_ASSERT(shape->isDataDescriptor());
2174 
2175     if (shape->hasDefaultSetter()) {
2176         if (shape->hasSlot()) {
2177             // The common path. Standard data property.
2178 
2179             // Global properties declared with 'var' will be initially
2180             // defined with an undefined value, so don't treat the initial
2181             // assignments to such properties as overwrites.
2182             bool overwriting = !obj->is<GlobalObject>() || !obj->getSlot(shape->slot()).isUndefined();
2183             obj->setSlotWithType(cx, shape, v, overwriting);
2184             return result.succeed();
2185         }
2186 
2187         // Bizarre: shared (slotless) property that's writable but has no
2188         // JSSetterOp. JS code can't define such a property, but it can be done
2189         // through the JSAPI. Treat it as non-writable.
2190         return result.fail(JSMSG_GETTER_ONLY);
2191     }
2192 
2193     MOZ_ASSERT(!obj->is<WithEnvironmentObject>());  // See bug 1128681.
2194 
2195     uint32_t sample = cx->runtime()->propertyRemovals;
2196     RootedId id(cx, shape->propid());
2197     RootedValue value(cx, v);
2198     if (!CallJSSetterOp(cx, shape->setterOp(), obj, id, &value, result))
2199         return false;
2200 
2201     // Update any slot for the shape with the value produced by the setter,
2202     // unless the setter deleted the shape.
2203     if (shape->hasSlot() &&
2204         (MOZ_LIKELY(cx->runtime()->propertyRemovals == sample) ||
2205          obj->contains(cx, shape)))
2206     {
2207         obj->setSlot(shape->slot(), value);
2208     }
2209 
2210     return true;  // result is populated by CallJSSetterOp above.
2211 }
2212 
2213 /*
2214  * When a [[Set]] operation finds no existing property with the given id
2215  * or finds a writable data property on the prototype chain, we end up here.
2216  * Finish the [[Set]] by defining a new property on receiver.
2217  *
2218  * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.b-f, but it
2219  * is really old code and there are a few barnacles.
2220  */
2221 bool
SetPropertyByDefining(JSContext * cx,HandleId id,HandleValue v,HandleValue receiverValue,ObjectOpResult & result)2222 js::SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v, HandleValue receiverValue,
2223                           ObjectOpResult& result)
2224 {
2225     // Step 5.b.
2226     if (!receiverValue.isObject())
2227         return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
2228     RootedObject receiver(cx, &receiverValue.toObject());
2229 
2230     bool existing;
2231     {
2232         // Steps 5.c-d.
2233         Rooted<PropertyDescriptor> desc(cx);
2234         if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc))
2235             return false;
2236 
2237         existing = !!desc.object();
2238 
2239         // Step 5.e.
2240         if (existing) {
2241             // Step 5.e.i.
2242             if (desc.isAccessorDescriptor())
2243                 return result.fail(JSMSG_OVERWRITING_ACCESSOR);
2244 
2245             // Step 5.e.ii.
2246             if (!desc.writable())
2247                 return result.fail(JSMSG_READ_ONLY);
2248         }
2249     }
2250 
2251     // Invalidate SpiderMonkey-specific caches or bail.
2252     const Class* clasp = receiver->getClass();
2253 
2254     // Purge the property cache of now-shadowed id in receiver's environment chain.
2255     if (!PurgeEnvironmentChain(cx, receiver, id))
2256         return false;
2257 
2258     // Steps 5.e.iii-iv. and 5.f.i. Define the new data property.
2259     unsigned attrs =
2260         existing
2261         ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | JSPROP_IGNORE_PERMANENT
2262         : JSPROP_ENUMERATE;
2263     JSGetterOp getter = clasp->getGetProperty();
2264     JSSetterOp setter = clasp->getSetProperty();
2265     MOZ_ASSERT(getter != JS_PropertyStub);
2266     MOZ_ASSERT(setter != JS_StrictPropertyStub);
2267     if (!DefineProperty(cx, receiver, id, v, getter, setter, attrs, result))
2268         return false;
2269 
2270     // If the receiver is native, there is one more legacy wrinkle: the class
2271     // JSSetterOp is called after defining the new property.
2272     if (setter && receiver->is<NativeObject>()) {
2273         if (!result)
2274             return true;
2275 
2276         Rooted<NativeObject*> nativeReceiver(cx, &receiver->as<NativeObject>());
2277         if (!cx->shouldBeJSContext())
2278             return false;
2279         RootedValue receiverValue(cx, ObjectValue(*receiver));
2280 
2281         // This lookup is a bit unfortunate, but not nearly the most
2282         // unfortunate thing about Class getters and setters. Since the above
2283         // DefineProperty call succeeded, receiver is native, and the property
2284         // has a setter (and thus can't be a dense element), this lookup is
2285         // guaranteed to succeed.
2286         RootedShape shape(cx, nativeReceiver->lookup(cx, id));
2287         MOZ_ASSERT(shape);
2288         return NativeSetExistingDataProperty(cx->asJSContext(), nativeReceiver, shape, v,
2289                                              receiverValue, result);
2290     }
2291 
2292     return true;
2293 }
2294 
2295 // When setting |id| for |receiver| and |obj| has no property for id, continue
2296 // the search up the prototype chain.
2297 bool
SetPropertyOnProto(JSContext * cx,HandleObject obj,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result)2298 js::SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
2299                        HandleValue receiver, ObjectOpResult& result)
2300 {
2301     MOZ_ASSERT(!obj->is<ProxyObject>());
2302 
2303     RootedObject proto(cx, obj->staticPrototype());
2304     if (proto)
2305         return SetProperty(cx, proto, id, v, receiver, result);
2306 
2307     return SetPropertyByDefining(cx, id, v, receiver, result);
2308 }
2309 
2310 /*
2311  * Implement "the rest of" assignment to a property when no property receiver[id]
2312  * was found anywhere on the prototype chain.
2313  *
2314  * FIXME: This should be updated to follow ES6 draft rev 28, section 9.1.9,
2315  * steps 4.d.i and 5.
2316  */
2317 static bool
SetNonexistentProperty(JSContext * cx,HandleId id,HandleValue v,HandleValue receiver,QualifiedBool qualified,ObjectOpResult & result)2318 SetNonexistentProperty(JSContext* cx, HandleId id, HandleValue v, HandleValue receiver,
2319                        QualifiedBool qualified, ObjectOpResult& result)
2320 {
2321     if (!qualified && receiver.isObject() && receiver.toObject().isUnqualifiedVarObj()) {
2322         RootedString idStr(cx, JSID_TO_STRING(id));
2323         if (!MaybeReportUndeclaredVarAssignment(cx, idStr))
2324             return false;
2325     }
2326 
2327     return SetPropertyByDefining(cx, id, v, receiver, result);
2328 }
2329 
2330 /*
2331  * Set an existing own property obj[index] that's a dense element or typed
2332  * array element.
2333  */
2334 static bool
SetDenseOrTypedArrayElement(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue v,ObjectOpResult & result)2335 SetDenseOrTypedArrayElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v,
2336                             ObjectOpResult& result)
2337 {
2338     if (obj->is<TypedArrayObject>()) {
2339         double d;
2340         if (!ToNumber(cx, v, &d))
2341             return false;
2342 
2343         // Silently do nothing for out-of-bounds sets, for consistency with
2344         // current behavior.  (ES6 currently says to throw for this in
2345         // strict mode code, so we may eventually need to change.)
2346         uint32_t len = obj->as<TypedArrayObject>().length();
2347         if (index < len)
2348             TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d);
2349         return result.succeed();
2350     }
2351 
2352     if (WouldDefinePastNonwritableLength(obj, index))
2353         return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
2354 
2355     if (!obj->maybeCopyElementsForWrite(cx))
2356         return false;
2357 
2358     obj->setDenseElementWithType(cx, index, v);
2359     return result.succeed();
2360 }
2361 
2362 /*
2363  * Finish the assignment `receiver[id] = v` when an existing property (shape)
2364  * has been found on a native object (pobj). This implements ES6 draft rev 32
2365  * (2015 Feb 2) 9.1.9 steps 5 and 6.
2366  *
2367  * It is necessary to pass both id and shape because shape could be an implicit
2368  * dense or typed array element (i.e. not actually a pointer to a Shape).
2369  */
2370 static bool
SetExistingProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v,HandleValue receiver,HandleNativeObject pobj,HandleShape shape,ObjectOpResult & result)2371 SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue v,
2372                     HandleValue receiver, HandleNativeObject pobj, HandleShape shape,
2373                     ObjectOpResult& result)
2374 {
2375     // Step 5 for dense elements.
2376     if (IsImplicitDenseOrTypedArrayElement(shape)) {
2377         // Step 5.a.
2378         if (pobj->getElementsHeader()->isFrozen())
2379             return result.fail(JSMSG_READ_ONLY);
2380 
2381         // Pure optimization for the common case:
2382         if (receiver.isObject() && pobj == &receiver.toObject())
2383             return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), v, result);
2384 
2385         // Steps 5.b-f.
2386         return SetPropertyByDefining(cx, id, v, receiver, result);
2387     }
2388 
2389     // Step 5 for all other properties.
2390     if (shape->isDataDescriptor()) {
2391         // Step 5.a.
2392         if (!shape->writable())
2393             return result.fail(JSMSG_READ_ONLY);
2394 
2395         // steps 5.c-f.
2396         if (receiver.isObject() && pobj == &receiver.toObject()) {
2397             // Pure optimization for the common case. There's no point performing
2398             // the lookup in step 5.c again, as our caller just did it for us. The
2399             // result is |shape|.
2400 
2401             // Steps 5.e.i-ii.
2402             if (pobj->is<ArrayObject>() && id == NameToId(cx->names().length)) {
2403                 Rooted<ArrayObject*> arr(cx, &pobj->as<ArrayObject>());
2404                 return ArraySetLength(cx, arr, id, shape->attributes(), v, result);
2405             }
2406             return NativeSetExistingDataProperty(cx, pobj, shape, v, receiver, result);
2407         }
2408 
2409         // SpiderMonkey special case: assigning to an inherited slotless
2410         // property causes the setter to be called, instead of shadowing,
2411         // unless the existing property is JSPROP_SHADOWABLE (see bug 552432).
2412         if (!shape->hasSlot() && !shape->hasShadowable()) {
2413             // Even weirder sub-special-case: inherited slotless data property
2414             // with default setter. Wut.
2415             if (shape->hasDefaultSetter())
2416                 return result.succeed();
2417 
2418             RootedValue valCopy(cx, v);
2419             return CallJSSetterOp(cx, shape->setterOp(), obj, id, &valCopy, result);
2420         }
2421 
2422         // Shadow pobj[id] by defining a new data property receiver[id].
2423         // Delegate everything to SetPropertyByDefining.
2424         return SetPropertyByDefining(cx, id, v, receiver, result);
2425     }
2426 
2427     // Steps 6-11.
2428     MOZ_ASSERT(shape->isAccessorDescriptor());
2429     MOZ_ASSERT_IF(!shape->hasSetterObject(), shape->hasDefaultSetter());
2430     if (shape->hasDefaultSetter())
2431         return result.fail(JSMSG_GETTER_ONLY);
2432 
2433     RootedValue setter(cx, ObjectValue(*shape->setterObject()));
2434     if (!js::CallSetter(cx, receiver, setter, v))
2435         return false;
2436 
2437     return result.succeed();
2438 }
2439 
2440 bool
NativeSetProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,HandleValue receiver,QualifiedBool qualified,ObjectOpResult & result)2441 js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue value,
2442                       HandleValue receiver, QualifiedBool qualified, ObjectOpResult& result)
2443 {
2444     // Fire watchpoints, if any.
2445     RootedValue v(cx, value);
2446     if (MOZ_UNLIKELY(obj->watched())) {
2447         WatchpointMap* wpmap = cx->compartment()->watchpointMap;
2448         if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &v))
2449             return false;
2450     }
2451 
2452     // Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal
2453     // method for ordinary objects. We substitute our own names for these names
2454     // used in the spec: O -> pobj, P -> id, ownDesc -> shape.
2455     RootedShape shape(cx);
2456     RootedNativeObject pobj(cx, obj);
2457 
2458     // This loop isn't explicit in the spec algorithm. See the comment on step
2459     // 4.c.i below. (There's a very similar loop in the NativeGetProperty
2460     // implementation, but unfortunately not similar enough to common up.)
2461     for (;;) {
2462         // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.)
2463         bool done;
2464         if (!LookupOwnPropertyInline<CanGC>(cx, pobj, id, &shape, &done))
2465             return false;
2466 
2467         if (shape) {
2468             // Steps 5-6.
2469             return SetExistingProperty(cx, obj, id, v, receiver, pobj, shape, result);
2470         }
2471 
2472         // Steps 4.a-b. The check for 'done' on this next line is tricky.
2473         // done can be true in exactly these unlikely-sounding cases:
2474         // - We're looking up an element, and pobj is a TypedArray that
2475         //   doesn't have that many elements.
2476         // - We're being called from a resolve hook to assign to the property
2477         //   being resolved.
2478         // What they all have in common is we do not want to keep walking
2479         // the prototype chain.
2480         RootedObject proto(cx, done ? nullptr : pobj->staticPrototype());
2481         if (!proto) {
2482             // Step 4.d.i (and step 5).
2483             return SetNonexistentProperty(cx, id, v, receiver, qualified, result);
2484         }
2485 
2486         // Step 4.c.i. If the prototype is also native, this step is a
2487         // recursive tail call, and we don't need to go through all the
2488         // plumbing of SetProperty; the top of the loop is where we're going to
2489         // end up anyway. But if pobj is non-native, that optimization would be
2490         // incorrect.
2491         if (!proto->isNative()) {
2492             // Unqualified assignments are not specified to go through [[Set]]
2493             // at all, but they do go through this function. So check for
2494             // unqualified assignment to a nonexistent global (a strict error).
2495             if (!qualified) {
2496                 bool found;
2497                 if (!HasProperty(cx, proto, id, &found))
2498                     return false;
2499                 if (!found)
2500                     return SetNonexistentProperty(cx, id, v, receiver, qualified, result);
2501             }
2502 
2503             return SetProperty(cx, proto, id, v, receiver, result);
2504         }
2505         pobj = &proto->as<NativeObject>();
2506     }
2507 }
2508 
2509 bool
NativeSetElement(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue v,HandleValue receiver,ObjectOpResult & result)2510 js::NativeSetElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v,
2511                      HandleValue receiver, ObjectOpResult& result)
2512 {
2513     RootedId id(cx);
2514     if (!IndexToId(cx, index, &id))
2515         return false;
2516     return NativeSetProperty(cx, obj, id, v, receiver, Qualified, result);
2517 }
2518 
2519 /*** [[Delete]] **********************************************************************************/
2520 
2521 // ES6 draft rev31 9.1.10 [[Delete]]
2522 bool
NativeDeleteProperty(JSContext * cx,HandleNativeObject obj,HandleId id,ObjectOpResult & result)2523 js::NativeDeleteProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
2524                          ObjectOpResult& result)
2525 {
2526     // Steps 2-3.
2527     RootedShape shape(cx);
2528     if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &shape))
2529         return false;
2530 
2531     // Step 4.
2532     if (!shape) {
2533         // If no property call the class's delProperty hook, passing succeeded
2534         // as the result parameter. This always succeeds when there is no hook.
2535         return CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id, result);
2536     }
2537 
2538     cx->runtime()->gc.poke();
2539 
2540     // Step 6. Non-configurable property.
2541     if (GetShapeAttributes(obj, shape) & JSPROP_PERMANENT)
2542         return result.failCantDelete();
2543 
2544     if (!CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id, result))
2545         return false;
2546     if (!result)
2547         return true;
2548 
2549     // Step 5.
2550     if (IsImplicitDenseOrTypedArrayElement(shape)) {
2551         // Typed array elements are non-configurable.
2552         MOZ_ASSERT(!obj->is<TypedArrayObject>());
2553 
2554         if (!obj->maybeCopyElementsForWrite(cx))
2555             return false;
2556 
2557         obj->setDenseElementHole(cx, JSID_TO_INT(id));
2558     } else {
2559         if (!obj->removeProperty(cx, id))
2560             return false;
2561     }
2562 
2563     return SuppressDeletedProperty(cx, obj, id);
2564 }
2565