1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "vm/NativeObject-inl.h"
8 
9 #include "mozilla/Casting.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/Maybe.h"
13 
14 #include <algorithm>
15 #include <iterator>
16 
17 #include "debugger/DebugAPI.h"
18 #include "gc/Marking.h"
19 #include "gc/MaybeRooted.h"
20 #include "jit/BaselineIC.h"
21 #include "js/CharacterEncoding.h"
22 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
23 #include "js/friend/StackLimits.h"    // js::AutoCheckRecursionLimit
24 #include "js/Result.h"
25 #include "js/Value.h"
26 #include "util/Memory.h"
27 #include "vm/EqualityOperations.h"  // js::SameValue
28 #include "vm/GetterSetter.h"        // js::GetterSetter
29 #include "vm/PlainObject.h"         // js::PlainObject
30 #include "vm/TypedArrayObject.h"
31 
32 #include "gc/Nursery-inl.h"
33 #include "vm/ArrayObject-inl.h"
34 #include "vm/BytecodeLocation-inl.h"
35 #include "vm/EnvironmentObject-inl.h"
36 #include "vm/JSObject-inl.h"
37 #include "vm/JSScript-inl.h"
38 #include "vm/Shape-inl.h"
39 
40 using namespace js;
41 
42 using JS::AutoCheckCannotGC;
43 using mozilla::CheckedInt;
44 using mozilla::DebugOnly;
45 using mozilla::PodCopy;
46 using mozilla::RoundUpPow2;
47 
48 struct EmptyObjectElements {
49   const ObjectElements emptyElementsHeader;
50 
51   // Add an extra (unused) Value to make sure an out-of-bounds index when
52   // masked (resulting in index 0) accesses valid memory.
53   const Value val;
54 
55  public:
EmptyObjectElementsEmptyObjectElements56   constexpr EmptyObjectElements()
57       : emptyElementsHeader(0, 0), val(UndefinedValue()) {}
EmptyObjectElementsEmptyObjectElements58   explicit constexpr EmptyObjectElements(ObjectElements::SharedMemory shmem)
59       : emptyElementsHeader(0, 0, shmem), val(UndefinedValue()) {}
60 };
61 
62 static constexpr EmptyObjectElements emptyElementsHeader;
63 
64 /* Objects with no elements share one empty set of elements. */
65 HeapSlot* const js::emptyObjectElements = reinterpret_cast<HeapSlot*>(
66     uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
67 
68 static constexpr EmptyObjectElements emptyElementsHeaderShared(
69     ObjectElements::SharedMemory::IsShared);
70 
71 /* Objects with no elements share one empty set of elements. */
72 HeapSlot* const js::emptyObjectElementsShared = reinterpret_cast<HeapSlot*>(
73     uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
74 
75 struct EmptyObjectSlots : public ObjectSlots {
EmptyObjectSlotsEmptyObjectSlots76   explicit constexpr EmptyObjectSlots(size_t dictionarySlotSpan)
77       : ObjectSlots(0, dictionarySlotSpan) {}
78 };
79 
80 static constexpr EmptyObjectSlots emptyObjectSlotsHeaders[17] = {
81     EmptyObjectSlots(0),  EmptyObjectSlots(1),  EmptyObjectSlots(2),
82     EmptyObjectSlots(3),  EmptyObjectSlots(4),  EmptyObjectSlots(5),
83     EmptyObjectSlots(6),  EmptyObjectSlots(7),  EmptyObjectSlots(8),
84     EmptyObjectSlots(9),  EmptyObjectSlots(10), EmptyObjectSlots(11),
85     EmptyObjectSlots(12), EmptyObjectSlots(13), EmptyObjectSlots(14),
86     EmptyObjectSlots(15), EmptyObjectSlots(16)};
87 
88 static_assert(std::size(emptyObjectSlotsHeaders) ==
89               NativeObject::MAX_FIXED_SLOTS + 1);
90 
91 HeapSlot* const js::emptyObjectSlotsForDictionaryObject[17] = {
92     emptyObjectSlotsHeaders[0].slots(),  emptyObjectSlotsHeaders[1].slots(),
93     emptyObjectSlotsHeaders[2].slots(),  emptyObjectSlotsHeaders[3].slots(),
94     emptyObjectSlotsHeaders[4].slots(),  emptyObjectSlotsHeaders[5].slots(),
95     emptyObjectSlotsHeaders[6].slots(),  emptyObjectSlotsHeaders[7].slots(),
96     emptyObjectSlotsHeaders[8].slots(),  emptyObjectSlotsHeaders[9].slots(),
97     emptyObjectSlotsHeaders[10].slots(), emptyObjectSlotsHeaders[11].slots(),
98     emptyObjectSlotsHeaders[12].slots(), emptyObjectSlotsHeaders[13].slots(),
99     emptyObjectSlotsHeaders[14].slots(), emptyObjectSlotsHeaders[15].slots(),
100     emptyObjectSlotsHeaders[16].slots()};
101 
102 static_assert(std::size(emptyObjectSlotsForDictionaryObject) ==
103               NativeObject::MAX_FIXED_SLOTS + 1);
104 
105 HeapSlot* const js::emptyObjectSlots = emptyObjectSlotsForDictionaryObject[0];
106 
107 #ifdef DEBUG
108 
canHaveNonEmptyElements()109 bool NativeObject::canHaveNonEmptyElements() {
110   return !this->is<TypedArrayObject>();
111 }
112 
113 #endif  // DEBUG
114 
115 /* static */
PrepareForPreventExtensions(JSContext * cx,NativeObject * obj)116 void ObjectElements::PrepareForPreventExtensions(JSContext* cx,
117                                                  NativeObject* obj) {
118   if (!obj->hasEmptyElements()) {
119     obj->shrinkCapacityToInitializedLength(cx);
120   }
121 
122   // shrinkCapacityToInitializedLength ensures there are no shifted elements.
123   MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
124 }
125 
126 /* static */
PreventExtensions(NativeObject * obj)127 void ObjectElements::PreventExtensions(NativeObject* obj) {
128   MOZ_ASSERT(!obj->isExtensible());
129   MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
130   MOZ_ASSERT(obj->getDenseInitializedLength() == obj->getDenseCapacity());
131 
132   if (!obj->hasEmptyElements()) {
133     obj->getElementsHeader()->setNotExtensible();
134   }
135 }
136 
137 /* static */
FreezeOrSeal(JSContext * cx,HandleNativeObject obj,IntegrityLevel level)138 bool ObjectElements::FreezeOrSeal(JSContext* cx, HandleNativeObject obj,
139                                   IntegrityLevel level) {
140   MOZ_ASSERT_IF(level == IntegrityLevel::Frozen && obj->is<ArrayObject>(),
141                 !obj->as<ArrayObject>().lengthIsWritable());
142   MOZ_ASSERT(!obj->isExtensible());
143   MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0);
144 
145   if (obj->hasEmptyElements() || obj->denseElementsAreFrozen()) {
146     return true;
147   }
148 
149   if (level == IntegrityLevel::Frozen) {
150     if (!JSObject::setFlag(cx, obj, ObjectFlag::FrozenElements)) {
151       return false;
152     }
153   }
154 
155   if (!obj->denseElementsAreSealed()) {
156     obj->getElementsHeader()->seal();
157   }
158 
159   if (level == IntegrityLevel::Frozen) {
160     obj->getElementsHeader()->freeze();
161   }
162 
163   return true;
164 }
165 
166 #ifdef DEBUG
167 static mozilla::Atomic<bool, mozilla::Relaxed> gShapeConsistencyChecksEnabled(
168     false);
169 
170 /* static */
enableShapeConsistencyChecks()171 void js::NativeObject::enableShapeConsistencyChecks() {
172   gShapeConsistencyChecksEnabled = true;
173 }
174 
checkShapeConsistency()175 void js::NativeObject::checkShapeConsistency() {
176   if (!gShapeConsistencyChecksEnabled) {
177     return;
178   }
179 
180   MOZ_ASSERT(is<NativeObject>());
181 
182   if (PropMap* map = shape()->propMap()) {
183     map->checkConsistency(this);
184   } else {
185     MOZ_ASSERT(shape()->propMapLength() == 0);
186   }
187 }
188 #endif
189 
initializeSlotRange(uint32_t start,uint32_t end)190 void js::NativeObject::initializeSlotRange(uint32_t start, uint32_t end) {
191   /*
192    * No bounds check, as this is used when the object's shape does not
193    * reflect its allocated slots (updateSlotsForSpan).
194    */
195   HeapSlot* fixedStart;
196   HeapSlot* fixedEnd;
197   HeapSlot* slotsStart;
198   HeapSlot* slotsEnd;
199   getSlotRangeUnchecked(start, end, &fixedStart, &fixedEnd, &slotsStart,
200                         &slotsEnd);
201 
202   uint32_t offset = start;
203   for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++) {
204     sp->init(this, HeapSlot::Slot, offset++, UndefinedValue());
205   }
206   for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++) {
207     sp->init(this, HeapSlot::Slot, offset++, UndefinedValue());
208   }
209 }
210 
initSlots(const Value * vector,uint32_t length)211 void js::NativeObject::initSlots(const Value* vector, uint32_t length) {
212   HeapSlot* fixedStart;
213   HeapSlot* fixedEnd;
214   HeapSlot* slotsStart;
215   HeapSlot* slotsEnd;
216   getSlotRange(0, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
217 
218   uint32_t offset = 0;
219   for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++) {
220     sp->init(this, HeapSlot::Slot, offset++, *vector++);
221   }
222   for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++) {
223     sp->init(this, HeapSlot::Slot, offset++, *vector++);
224   }
225 }
226 
227 #ifdef DEBUG
228 
slotInRange(uint32_t slot,SentinelAllowed sentinel) const229 bool js::NativeObject::slotInRange(uint32_t slot,
230                                    SentinelAllowed sentinel) const {
231   MOZ_ASSERT(!gc::IsForwarded(shape()));
232   uint32_t capacity = numFixedSlots() + numDynamicSlots();
233   if (sentinel == SENTINEL_ALLOWED) {
234     return slot <= capacity;
235   }
236   return slot < capacity;
237 }
238 
slotIsFixed(uint32_t slot) const239 bool js::NativeObject::slotIsFixed(uint32_t slot) const {
240   // We call numFixedSlotsMaybeForwarded() to allow reading slots of
241   // associated objects in trace hooks that may be called during a moving GC.
242   return slot < numFixedSlotsMaybeForwarded();
243 }
244 
isNumFixedSlots(uint32_t nfixed) const245 bool js::NativeObject::isNumFixedSlots(uint32_t nfixed) const {
246   // We call numFixedSlotsMaybeForwarded() to allow reading slots of
247   // associated objects in trace hooks that may be called during a moving GC.
248   return nfixed == numFixedSlotsMaybeForwarded();
249 }
250 
251 #endif /* DEBUG */
252 
lookup(JSContext * cx,jsid id)253 mozilla::Maybe<PropertyInfo> js::NativeObject::lookup(JSContext* cx, jsid id) {
254   MOZ_ASSERT(is<NativeObject>());
255   uint32_t index;
256   if (PropMap* map = shape()->lookup(cx, id, &index)) {
257     return mozilla::Some(map->getPropertyInfo(index));
258   }
259   return mozilla::Nothing();
260 }
261 
lookupPure(jsid id)262 mozilla::Maybe<PropertyInfo> js::NativeObject::lookupPure(jsid id) {
263   MOZ_ASSERT(is<NativeObject>());
264   uint32_t index;
265   if (PropMap* map = shape()->lookupPure(id, &index)) {
266     return mozilla::Some(map->getPropertyInfo(index));
267   }
268   return mozilla::Nothing();
269 }
270 
ensureSlotsForDictionaryObject(JSContext * cx,uint32_t span)271 bool NativeObject::ensureSlotsForDictionaryObject(JSContext* cx,
272                                                   uint32_t span) {
273   MOZ_ASSERT(inDictionaryMode());
274 
275   size_t oldSpan = dictionaryModeSlotSpan();
276   if (oldSpan == span) {
277     return true;
278   }
279 
280   if (!updateSlotsForSpan(cx, oldSpan, span)) {
281     return false;
282   }
283 
284   setDictionaryModeSlotSpan(span);
285   return true;
286 }
287 
growSlots(JSContext * cx,uint32_t oldCapacity,uint32_t newCapacity)288 bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity,
289                              uint32_t newCapacity) {
290   MOZ_ASSERT(newCapacity > oldCapacity);
291   MOZ_ASSERT_IF(!is<ArrayObject>(), newCapacity >= SLOT_CAPACITY_MIN);
292 
293   /*
294    * Slot capacities are determined by the span of allocated objects. Due to
295    * the limited number of bits to store shape slots, object growth is
296    * throttled well before the slot capacity can overflow.
297    */
298   NativeObject::slotsSizeMustNotOverflow();
299   MOZ_ASSERT(newCapacity <= MAX_SLOTS_COUNT);
300 
301   if (!hasDynamicSlots()) {
302     return allocateSlots(cx, newCapacity);
303   }
304 
305   uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
306 
307   uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
308 
309   uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity);
310 
311   ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_);
312   MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity);
313 
314   HeapSlot* allocation = ReallocateObjectBuffer<HeapSlot>(
315       cx, this, reinterpret_cast<HeapSlot*>(oldHeaderSlots), oldAllocated,
316       newAllocated);
317   if (!allocation) {
318     return false; /* Leave slots at its old size. */
319   }
320 
321   auto* newHeaderSlots =
322       new (allocation) ObjectSlots(newCapacity, dictionarySpan);
323   slots_ = newHeaderSlots->slots();
324 
325   Debug_SetSlotRangeToCrashOnTouch(slots_ + oldCapacity,
326                                    newCapacity - oldCapacity);
327 
328   RemoveCellMemory(this, ObjectSlots::allocSize(oldCapacity),
329                    MemoryUse::ObjectSlots);
330   AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
331                 MemoryUse::ObjectSlots);
332 
333   MOZ_ASSERT(hasDynamicSlots());
334   return true;
335 }
336 
allocateSlots(JSContext * cx,uint32_t newCapacity)337 bool NativeObject::allocateSlots(JSContext* cx, uint32_t newCapacity) {
338   MOZ_ASSERT(!hasDynamicSlots());
339 
340   uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
341 
342   uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
343 
344   HeapSlot* allocation = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
345   if (!allocation) {
346     return false;
347   }
348 
349   auto* newHeaderSlots =
350       new (allocation) ObjectSlots(newCapacity, dictionarySpan);
351   slots_ = newHeaderSlots->slots();
352 
353   Debug_SetSlotRangeToCrashOnTouch(slots_, newCapacity);
354 
355   AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
356                 MemoryUse::ObjectSlots);
357 
358   MOZ_ASSERT(hasDynamicSlots());
359   return true;
360 }
361 
362 /* static */
growSlotsPure(JSContext * cx,NativeObject * obj,uint32_t newCapacity)363 bool NativeObject::growSlotsPure(JSContext* cx, NativeObject* obj,
364                                  uint32_t newCapacity) {
365   // IC code calls this directly.
366   AutoUnsafeCallWithABI unsafe;
367 
368   if (!obj->growSlots(cx, obj->numDynamicSlots(), newCapacity)) {
369     cx->recoverFromOutOfMemory();
370     return false;
371   }
372 
373   return true;
374 }
375 
376 /* static */
addDenseElementPure(JSContext * cx,NativeObject * obj)377 bool NativeObject::addDenseElementPure(JSContext* cx, NativeObject* obj) {
378   // IC code calls this directly.
379   AutoUnsafeCallWithABI unsafe;
380 
381   MOZ_ASSERT(obj->getDenseInitializedLength() == obj->getDenseCapacity());
382   MOZ_ASSERT(obj->isExtensible());
383   MOZ_ASSERT(!obj->isIndexed());
384   MOZ_ASSERT(!obj->is<TypedArrayObject>());
385   MOZ_ASSERT_IF(obj->is<ArrayObject>(),
386                 obj->as<ArrayObject>().lengthIsWritable());
387 
388   // growElements will report OOM also if the number of dense elements will
389   // exceed MAX_DENSE_ELEMENTS_COUNT. See goodElementsAllocationAmount.
390   uint32_t oldCapacity = obj->getDenseCapacity();
391   if (MOZ_UNLIKELY(!obj->growElements(cx, oldCapacity + 1))) {
392     cx->recoverFromOutOfMemory();
393     return false;
394   }
395 
396   MOZ_ASSERT(obj->getDenseCapacity() > oldCapacity);
397   MOZ_ASSERT(obj->getDenseCapacity() <= MAX_DENSE_ELEMENTS_COUNT);
398   return true;
399 }
400 
FreeSlots(JSContext * cx,NativeObject * obj,ObjectSlots * slots,size_t nbytes)401 static inline void FreeSlots(JSContext* cx, NativeObject* obj,
402                              ObjectSlots* slots, size_t nbytes) {
403   if (cx->isHelperThreadContext()) {
404     js_free(slots);
405   } else if (obj->isTenured()) {
406     MOZ_ASSERT(!cx->nursery().isInside(slots));
407     js_free(slots);
408   } else {
409     cx->nursery().freeBuffer(slots, nbytes);
410   }
411 }
412 
shrinkSlots(JSContext * cx,uint32_t oldCapacity,uint32_t newCapacity)413 void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity,
414                                uint32_t newCapacity) {
415   MOZ_ASSERT(newCapacity < oldCapacity);
416   MOZ_ASSERT(oldCapacity == getSlotsHeader()->capacity());
417 
418   uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
419 
420   ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_);
421   MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity);
422 
423   uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity);
424 
425   if (newCapacity == 0) {
426     size_t nbytes = ObjectSlots::allocSize(oldCapacity);
427     RemoveCellMemory(this, nbytes, MemoryUse::ObjectSlots);
428     FreeSlots(cx, this, oldHeaderSlots, nbytes);
429     setEmptyDynamicSlots(dictionarySpan);
430     return;
431   }
432 
433   MOZ_ASSERT_IF(!is<ArrayObject>(), newCapacity >= SLOT_CAPACITY_MIN);
434 
435   uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
436 
437   HeapSlot* allocation = ReallocateObjectBuffer<HeapSlot>(
438       cx, this, reinterpret_cast<HeapSlot*>(oldHeaderSlots), oldAllocated,
439       newAllocated);
440   if (!allocation) {
441     // It's possible for realloc to fail when shrinking an allocation. In this
442     // case we continue using the original allocation but still update the
443     // capacity to the new requested capacity, which is smaller than the actual
444     // capacity.
445     cx->recoverFromOutOfMemory();
446     allocation = reinterpret_cast<HeapSlot*>(getSlotsHeader());
447   }
448 
449   RemoveCellMemory(this, ObjectSlots::allocSize(oldCapacity),
450                    MemoryUse::ObjectSlots);
451   AddCellMemory(this, ObjectSlots::allocSize(newCapacity),
452                 MemoryUse::ObjectSlots);
453 
454   auto* newHeaderSlots =
455       new (allocation) ObjectSlots(newCapacity, dictionarySpan);
456   slots_ = newHeaderSlots->slots();
457 }
458 
willBeSparseElements(uint32_t requiredCapacity,uint32_t newElementsHint)459 bool NativeObject::willBeSparseElements(uint32_t requiredCapacity,
460                                         uint32_t newElementsHint) {
461   MOZ_ASSERT(is<NativeObject>());
462   MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
463 
464   uint32_t cap = getDenseCapacity();
465   MOZ_ASSERT(requiredCapacity >= cap);
466 
467   if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT) {
468     return true;
469   }
470 
471   uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
472   if (newElementsHint >= minimalDenseCount) {
473     return false;
474   }
475   minimalDenseCount -= newElementsHint;
476 
477   if (minimalDenseCount > cap) {
478     return true;
479   }
480 
481   uint32_t len = getDenseInitializedLength();
482   const Value* elems = getDenseElements();
483   for (uint32_t i = 0; i < len; i++) {
484     if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) {
485       return false;
486     }
487   }
488   return true;
489 }
490 
491 /* static */
maybeDensifySparseElements(JSContext * cx,HandleNativeObject obj)492 DenseElementResult NativeObject::maybeDensifySparseElements(
493     JSContext* cx, HandleNativeObject obj) {
494   /*
495    * Wait until after the object goes into dictionary mode, which must happen
496    * when sparsely packing any array with more than MIN_SPARSE_INDEX elements
497    * (see PropertyTree::MAX_HEIGHT).
498    */
499   if (!obj->inDictionaryMode()) {
500     return DenseElementResult::Incomplete;
501   }
502 
503   /*
504    * Only measure the number of indexed properties every log(n) times when
505    * populating the object.
506    */
507   uint32_t slotSpan = obj->slotSpan();
508   if (slotSpan != RoundUpPow2(slotSpan)) {
509     return DenseElementResult::Incomplete;
510   }
511 
512   /* Watch for conditions under which an object's elements cannot be dense. */
513   if (!obj->isExtensible()) {
514     return DenseElementResult::Incomplete;
515   }
516 
517   /*
518    * The indexes in the object need to be sufficiently dense before they can
519    * be converted to dense mode.
520    */
521   uint32_t numDenseElements = 0;
522   uint32_t newInitializedLength = 0;
523 
524   for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
525     uint32_t index;
526     if (!IdIsIndex(iter->key(), &index)) {
527       continue;
528     }
529     if (iter->flags() != PropertyFlags::defaultDataPropFlags) {
530       // For simplicity, only densify the object if all indexed properties can
531       // be converted to dense elements.
532       return DenseElementResult::Incomplete;
533     }
534     MOZ_ASSERT(iter->isDataProperty());
535     numDenseElements++;
536     newInitializedLength = std::max(newInitializedLength, index + 1);
537   }
538 
539   if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) {
540     return DenseElementResult::Incomplete;
541   }
542 
543   if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT) {
544     return DenseElementResult::Incomplete;
545   }
546 
547   /*
548    * This object meets all necessary restrictions, convert all indexed
549    * properties into dense elements.
550    */
551 
552   if (newInitializedLength > obj->getDenseCapacity()) {
553     if (!obj->growElements(cx, newInitializedLength)) {
554       return DenseElementResult::Failure;
555     }
556   }
557 
558   obj->ensureDenseInitializedLength(newInitializedLength, 0);
559 
560   if (ObjectRealm::get(obj).objectMaybeInIteration(obj)) {
561     // Mark the densified elements as maybe-in-iteration. See also the comment
562     // in GetIterator.
563     obj->markDenseElementsMaybeInIteration();
564   }
565 
566   if (!NativeObject::densifySparseElements(cx, obj)) {
567     return DenseElementResult::Failure;
568   }
569 
570   return DenseElementResult::Success;
571 }
572 
moveShiftedElements()573 void NativeObject::moveShiftedElements() {
574   MOZ_ASSERT(isExtensible());
575 
576   ObjectElements* header = getElementsHeader();
577   uint32_t numShifted = header->numShiftedElements();
578   MOZ_ASSERT(numShifted > 0);
579 
580   uint32_t initLength = header->initializedLength;
581 
582   ObjectElements* newHeader =
583       static_cast<ObjectElements*>(getUnshiftedElementsHeader());
584   memmove(newHeader, header, sizeof(ObjectElements));
585 
586   newHeader->clearShiftedElements();
587   newHeader->capacity += numShifted;
588   elements_ = newHeader->elements();
589 
590   // To move the elements, temporarily update initializedLength to include
591   // the shifted elements.
592   newHeader->initializedLength += numShifted;
593 
594   // Move the elements. Initialize to |undefined| to ensure pre-barriers
595   // don't see garbage.
596   for (size_t i = 0; i < numShifted; i++) {
597     initDenseElement(i, UndefinedValue());
598   }
599   moveDenseElements(0, numShifted, initLength);
600 
601   // Restore the initialized length. We use setDenseInitializedLength to
602   // make sure prepareElementRangeForOverwrite is called on the shifted
603   // elements.
604   setDenseInitializedLength(initLength);
605 }
606 
maybeMoveShiftedElements()607 void NativeObject::maybeMoveShiftedElements() {
608   MOZ_ASSERT(isExtensible());
609 
610   ObjectElements* header = getElementsHeader();
611   MOZ_ASSERT(header->numShiftedElements() > 0);
612 
613   // Move the elements if less than a third of the allocated space is in use.
614   if (header->capacity < header->numAllocatedElements() / 3) {
615     moveShiftedElements();
616   }
617 }
618 
tryUnshiftDenseElements(uint32_t count)619 bool NativeObject::tryUnshiftDenseElements(uint32_t count) {
620   MOZ_ASSERT(isExtensible());
621   MOZ_ASSERT(count > 0);
622 
623   ObjectElements* header = getElementsHeader();
624   uint32_t numShifted = header->numShiftedElements();
625 
626   if (count > numShifted) {
627     // We need more elements than are easily available. Try to make space
628     // for more elements than we need (and shift the remaining ones) so
629     // that unshifting more elements later will be fast.
630 
631     // Don't bother reserving elements if the number of elements is small.
632     // Note that there's no technical reason for using this particular
633     // limit.
634     if (header->initializedLength <= 10 ||
635         header->hasNonwritableArrayLength() ||
636         MOZ_UNLIKELY(count > ObjectElements::MaxShiftedElements)) {
637       return false;
638     }
639 
640     MOZ_ASSERT(header->capacity >= header->initializedLength);
641     uint32_t unusedCapacity = header->capacity - header->initializedLength;
642 
643     // Determine toShift, the number of extra elements we want to make
644     // available.
645     uint32_t toShift = count - numShifted;
646     MOZ_ASSERT(toShift <= ObjectElements::MaxShiftedElements,
647                "count <= MaxShiftedElements so toShift <= MaxShiftedElements");
648 
649     // Give up if we need to allocate more elements.
650     if (toShift > unusedCapacity) {
651       return false;
652     }
653 
654     // Move more elements than we need, so that other unshift calls will be
655     // fast. We just have to make sure we don't exceed unusedCapacity.
656     toShift = std::min(toShift + unusedCapacity / 2, unusedCapacity);
657 
658     // Ensure |numShifted + toShift| does not exceed MaxShiftedElements.
659     if (numShifted + toShift > ObjectElements::MaxShiftedElements) {
660       toShift = ObjectElements::MaxShiftedElements - numShifted;
661     }
662 
663     MOZ_ASSERT(count <= numShifted + toShift);
664     MOZ_ASSERT(numShifted + toShift <= ObjectElements::MaxShiftedElements);
665     MOZ_ASSERT(toShift <= unusedCapacity);
666 
667     // Now move/unshift the elements.
668     uint32_t initLen = header->initializedLength;
669     setDenseInitializedLength(initLen + toShift);
670     for (uint32_t i = 0; i < toShift; i++) {
671       initDenseElement(initLen + i, UndefinedValue());
672     }
673     moveDenseElements(toShift, 0, initLen);
674 
675     // Shift the elements we just prepended.
676     shiftDenseElementsUnchecked(toShift);
677 
678     // We can now fall-through to the fast path below.
679     header = getElementsHeader();
680     MOZ_ASSERT(header->numShiftedElements() == numShifted + toShift);
681 
682     numShifted = header->numShiftedElements();
683     MOZ_ASSERT(count <= numShifted);
684   }
685 
686   elements_ -= count;
687   ObjectElements* newHeader = getElementsHeader();
688   memmove(newHeader, header, sizeof(ObjectElements));
689 
690   newHeader->unshiftShiftedElements(count);
691 
692   // Initialize to |undefined| to ensure pre-barriers don't see garbage.
693   for (uint32_t i = 0; i < count; i++) {
694     initDenseElement(i, UndefinedValue());
695   }
696 
697   return true;
698 }
699 
700 // Given a requested capacity (in elements) and (potentially) the length of an
701 // array for which elements are being allocated, compute an actual allocation
702 // amount (in elements).  (Allocation amounts include space for an
703 // ObjectElements instance, so a return value of |N| implies
704 // |N - ObjectElements::VALUES_PER_HEADER| usable elements.)
705 //
706 // The requested/actual allocation distinction is meant to:
707 //
708 //   * preserve amortized O(N) time to add N elements;
709 //   * minimize the number of unused elements beyond an array's length, and
710 //   * provide at least SLOT_CAPACITY_MIN elements no matter what (so adding
711 //     the first several elements to small arrays only needs one allocation).
712 //
713 // Note: the structure and behavior of this method follow along with
714 // UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy
715 // in one should generally be matched by the other.
716 /* static */
goodElementsAllocationAmount(JSContext * cx,uint32_t reqCapacity,uint32_t length,uint32_t * goodAmount)717 bool NativeObject::goodElementsAllocationAmount(JSContext* cx,
718                                                 uint32_t reqCapacity,
719                                                 uint32_t length,
720                                                 uint32_t* goodAmount) {
721   if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) {
722     ReportOutOfMemory(cx);
723     return false;
724   }
725 
726   uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
727 
728   // Handle "small" requests primarily by doubling.
729   const uint32_t Mebi = 1 << 20;
730   if (reqAllocated < Mebi) {
731     uint32_t amount =
732         mozilla::AssertedCast<uint32_t>(RoundUpPow2(reqAllocated));
733 
734     // If |amount| would be 2/3 or more of the array's length, adjust
735     // it (up or down) to be equal to the array's length.  This avoids
736     // allocating excess elements that aren't likely to be needed, either
737     // in this resizing or a subsequent one.  The 2/3 factor is chosen so
738     // that exceptional resizings will at most triple the capacity, as
739     // opposed to the usual doubling.
740     uint32_t goodCapacity = amount - ObjectElements::VALUES_PER_HEADER;
741     if (length >= reqCapacity && goodCapacity > (length / 3) * 2) {
742       amount = length + ObjectElements::VALUES_PER_HEADER;
743     }
744 
745     if (amount < SLOT_CAPACITY_MIN) {
746       amount = SLOT_CAPACITY_MIN;
747     }
748 
749     *goodAmount = amount;
750 
751     return true;
752   }
753 
754   // The almost-doubling above wastes a lot of space for larger bucket sizes.
755   // For large amounts, switch to bucket sizes that obey this formula:
756   //
757   //   count(n+1) = Math.ceil(count(n) * 1.125)
758   //
759   // where |count(n)| is the size of the nth bucket, measured in 2**20 slots.
760   // These bucket sizes still preserve amortized O(N) time to add N elements,
761   // just with a larger constant factor.
762   //
763   // The bucket size table below was generated with this JavaScript (and
764   // manual reformatting):
765   //
766   //   for (let n = 1, i = 0; i < 34; i++) {
767   //     print('0x' + (n * (1 << 20)).toString(16) + ', ');
768   //     n = Math.ceil(n * 1.125);
769   //   }
770   static constexpr uint32_t BigBuckets[] = {
771       0x100000,  0x200000,  0x300000,  0x400000,  0x500000,  0x600000,
772       0x700000,  0x800000,  0x900000,  0xb00000,  0xd00000,  0xf00000,
773       0x1100000, 0x1400000, 0x1700000, 0x1a00000, 0x1e00000, 0x2200000,
774       0x2700000, 0x2c00000, 0x3200000, 0x3900000, 0x4100000, 0x4a00000,
775       0x5400000, 0x5f00000, 0x6b00000, 0x7900000, 0x8900000, 0x9b00000,
776       0xaf00000, 0xc500000, 0xde00000, 0xfa00000};
777   static_assert(BigBuckets[std::size(BigBuckets) - 1] <=
778                 MAX_DENSE_ELEMENTS_ALLOCATION);
779 
780   // Pick the first bucket that'll fit |reqAllocated|.
781   for (uint32_t b : BigBuckets) {
782     if (b >= reqAllocated) {
783       *goodAmount = b;
784       return true;
785     }
786   }
787 
788   // Otherwise, return the maximum bucket size.
789   *goodAmount = MAX_DENSE_ELEMENTS_ALLOCATION;
790   return true;
791 }
792 
growElements(JSContext * cx,uint32_t reqCapacity)793 bool NativeObject::growElements(JSContext* cx, uint32_t reqCapacity) {
794   MOZ_ASSERT(isExtensible());
795   MOZ_ASSERT(canHaveNonEmptyElements());
796 
797   // If there are shifted elements, consider moving them first. If we don't
798   // move them here, the code below will include the shifted elements in the
799   // resize.
800   uint32_t numShifted = getElementsHeader()->numShiftedElements();
801   if (numShifted > 0) {
802     // If the number of elements is small, it's cheaper to just move them as
803     // it may avoid a malloc/realloc. Note that there's no technical reason
804     // for using this particular value, but it works well in real-world use
805     // cases.
806     static const size_t MaxElementsToMoveEagerly = 20;
807 
808     if (getElementsHeader()->initializedLength <= MaxElementsToMoveEagerly) {
809       moveShiftedElements();
810     } else {
811       maybeMoveShiftedElements();
812     }
813     if (getDenseCapacity() >= reqCapacity) {
814       return true;
815     }
816     numShifted = getElementsHeader()->numShiftedElements();
817 
818     // If |reqCapacity + numShifted| overflows, we just move all shifted
819     // elements to avoid the problem.
820     CheckedInt<uint32_t> checkedReqCapacity(reqCapacity);
821     checkedReqCapacity += numShifted;
822     if (MOZ_UNLIKELY(!checkedReqCapacity.isValid())) {
823       moveShiftedElements();
824       numShifted = 0;
825     }
826   }
827 
828   uint32_t oldCapacity = getDenseCapacity();
829   MOZ_ASSERT(oldCapacity < reqCapacity);
830 
831   uint32_t newAllocated = 0;
832   if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) {
833     MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length());
834     MOZ_ASSERT(reqCapacity <= MAX_DENSE_ELEMENTS_COUNT);
835     // Preserve the |capacity <= length| invariant for arrays with
836     // non-writable length.  See also js::ArraySetLength which initially
837     // enforces this requirement.
838     newAllocated = reqCapacity + numShifted + ObjectElements::VALUES_PER_HEADER;
839   } else {
840     if (!goodElementsAllocationAmount(cx, reqCapacity + numShifted,
841                                       getElementsHeader()->length,
842                                       &newAllocated)) {
843       return false;
844     }
845   }
846 
847   uint32_t newCapacity =
848       newAllocated - ObjectElements::VALUES_PER_HEADER - numShifted;
849   MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity);
850 
851   // If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become
852   // sparse.
853   MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
854 
855   uint32_t initlen = getDenseInitializedLength();
856 
857   HeapSlot* oldHeaderSlots =
858       reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader());
859   HeapSlot* newHeaderSlots;
860   uint32_t oldAllocated = 0;
861   if (hasDynamicElements()) {
862     MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
863     oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted;
864 
865     newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(
866         cx, this, oldHeaderSlots, oldAllocated, newAllocated);
867     if (!newHeaderSlots) {
868       return false;  // Leave elements at its old size.
869     }
870   } else {
871     newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
872     if (!newHeaderSlots) {
873       return false;  // Leave elements at its old size.
874     }
875     PodCopy(newHeaderSlots, oldHeaderSlots,
876             ObjectElements::VALUES_PER_HEADER + initlen + numShifted);
877   }
878 
879   if (oldAllocated) {
880     RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
881                      MemoryUse::ObjectElements);
882   }
883 
884   ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
885   elements_ = newheader->elements() + numShifted;
886   getElementsHeader()->capacity = newCapacity;
887 
888   Debug_SetSlotRangeToCrashOnTouch(elements_ + initlen, newCapacity - initlen);
889 
890   AddCellMemory(this, newAllocated * sizeof(HeapSlot),
891                 MemoryUse::ObjectElements);
892 
893   return true;
894 }
895 
shrinkElements(JSContext * cx,uint32_t reqCapacity)896 void NativeObject::shrinkElements(JSContext* cx, uint32_t reqCapacity) {
897   MOZ_ASSERT(canHaveNonEmptyElements());
898   MOZ_ASSERT(reqCapacity >= getDenseInitializedLength());
899 
900   if (!hasDynamicElements()) {
901     return;
902   }
903 
904   // If we have shifted elements, consider moving them.
905   uint32_t numShifted = getElementsHeader()->numShiftedElements();
906   if (numShifted > 0) {
907     maybeMoveShiftedElements();
908     numShifted = getElementsHeader()->numShiftedElements();
909   }
910 
911   uint32_t oldCapacity = getDenseCapacity();
912   MOZ_ASSERT(reqCapacity < oldCapacity);
913 
914   uint32_t newAllocated = 0;
915   MOZ_ALWAYS_TRUE(goodElementsAllocationAmount(cx, reqCapacity + numShifted, 0,
916                                                &newAllocated));
917   MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT);
918 
919   uint32_t oldAllocated =
920       oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted;
921   if (newAllocated == oldAllocated) {
922     return;  // Leave elements at its old size.
923   }
924 
925   MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER);
926   uint32_t newCapacity =
927       newAllocated - ObjectElements::VALUES_PER_HEADER - numShifted;
928   MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
929 
930   HeapSlot* oldHeaderSlots =
931       reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader());
932   HeapSlot* newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(
933       cx, this, oldHeaderSlots, oldAllocated, newAllocated);
934   if (!newHeaderSlots) {
935     cx->recoverFromOutOfMemory();
936     return;  // Leave elements at its old size.
937   }
938 
939   RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
940                    MemoryUse::ObjectElements);
941 
942   ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
943   elements_ = newheader->elements() + numShifted;
944   getElementsHeader()->capacity = newCapacity;
945 
946   AddCellMemory(this, newAllocated * sizeof(HeapSlot),
947                 MemoryUse::ObjectElements);
948 }
949 
shrinkCapacityToInitializedLength(JSContext * cx)950 void NativeObject::shrinkCapacityToInitializedLength(JSContext* cx) {
951   // When an array's length becomes non-writable, writes to indexes greater
952   // greater than or equal to the length don't change the array.  We handle this
953   // with a check for non-writable length in most places. But in JIT code every
954   // check counts -- so we piggyback the check on the already-required range
955   // check for |index < capacity| by making capacity of arrays with non-writable
956   // length never exceed the length. This mechanism is also used when an object
957   // becomes non-extensible.
958 
959   if (getElementsHeader()->numShiftedElements() > 0) {
960     moveShiftedElements();
961   }
962 
963   ObjectElements* header = getElementsHeader();
964   uint32_t len = header->initializedLength;
965   MOZ_ASSERT(header->capacity >= len);
966   if (header->capacity == len) {
967     return;
968   }
969 
970   shrinkElements(cx, len);
971 
972   header = getElementsHeader();
973   uint32_t oldAllocated = header->numAllocatedElements();
974   header->capacity = len;
975 
976   // The size of the memory allocation hasn't changed but we lose the actual
977   // capacity information. Make the associated size match the updated capacity.
978   if (!hasFixedElements()) {
979     uint32_t newAllocated = header->numAllocatedElements();
980     RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
981                      MemoryUse::ObjectElements);
982     AddCellMemory(this, newAllocated * sizeof(HeapSlot),
983                   MemoryUse::ObjectElements);
984   }
985 }
986 
987 /* static */
allocDictionarySlot(JSContext * cx,HandleNativeObject obj,uint32_t * slotp)988 bool NativeObject::allocDictionarySlot(JSContext* cx, HandleNativeObject obj,
989                                        uint32_t* slotp) {
990   MOZ_ASSERT(obj->inDictionaryMode());
991 
992   uint32_t slotSpan = obj->slotSpan();
993   MOZ_ASSERT(slotSpan >= JSSLOT_FREE(obj->getClass()));
994 
995   // Try to pull a free slot from the slot-number free list.
996   DictionaryPropMap* map = obj->shape()->dictionaryPropMap();
997   uint32_t last = map->freeList();
998   if (last != SHAPE_INVALID_SLOT) {
999 #ifdef DEBUG
1000     MOZ_ASSERT(last < slotSpan);
1001     uint32_t next = obj->getSlot(last).toPrivateUint32();
1002     MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slotSpan);
1003 #endif
1004     *slotp = last;
1005     const Value& vref = obj->getSlot(last);
1006     map->setFreeList(vref.toPrivateUint32());
1007     obj->setSlot(last, UndefinedValue());
1008     return true;
1009   }
1010 
1011   if (MOZ_UNLIKELY(slotSpan >= SHAPE_MAXIMUM_SLOT)) {
1012     ReportOutOfMemory(cx);
1013     return false;
1014   }
1015 
1016   *slotp = slotSpan;
1017 
1018   return obj->ensureSlotsForDictionaryObject(cx, slotSpan + 1);
1019 }
1020 
freeDictionarySlot(uint32_t slot)1021 void NativeObject::freeDictionarySlot(uint32_t slot) {
1022   MOZ_ASSERT(inDictionaryMode());
1023   MOZ_ASSERT(slot < slotSpan());
1024 
1025   DictionaryPropMap* map = shape()->dictionaryPropMap();
1026   uint32_t last = map->freeList();
1027 
1028   // Can't afford to check the whole free list, but let's check the head.
1029   MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot);
1030 
1031   // Place all freed slots other than reserved slots (bug 595230) on the
1032   // dictionary's free list.
1033   if (JSSLOT_FREE(getClass()) <= slot) {
1034     MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan());
1035     setSlot(slot, PrivateUint32Value(last));
1036     map->setFreeList(slot);
1037   } else {
1038     setSlot(slot, UndefinedValue());
1039   }
1040 }
1041 
1042 template <AllowGC allowGC>
NativeLookupOwnProperty(JSContext * cx,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<jsid,allowGC>::HandleType id,PropertyResult * propp)1043 bool js::NativeLookupOwnProperty(
1044     JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
1045     typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyResult* propp) {
1046   return NativeLookupOwnPropertyInline<allowGC>(cx, obj, id, propp);
1047 }
1048 
1049 template bool js::NativeLookupOwnProperty<CanGC>(JSContext* cx,
1050                                                  HandleNativeObject obj,
1051                                                  HandleId id,
1052                                                  PropertyResult* propp);
1053 
1054 template bool js::NativeLookupOwnProperty<NoGC>(JSContext* cx,
1055                                                 NativeObject* const& obj,
1056                                                 const jsid& id,
1057                                                 PropertyResult* propp);
1058 
1059 /*** [[DefineOwnProperty]] **************************************************/
1060 
CallJSAddPropertyOp(JSContext * cx,JSAddPropertyOp op,HandleObject obj,HandleId id,HandleValue v)1061 static bool CallJSAddPropertyOp(JSContext* cx, JSAddPropertyOp op,
1062                                 HandleObject obj, HandleId id, HandleValue v) {
1063   AutoCheckRecursionLimit recursion(cx);
1064   if (!recursion.check(cx)) {
1065     return false;
1066   }
1067 
1068   cx->check(obj, id, v);
1069   return op(cx, obj, id, v);
1070 }
1071 
CallAddPropertyHook(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue value)1072 static MOZ_ALWAYS_INLINE bool CallAddPropertyHook(JSContext* cx,
1073                                                   HandleNativeObject obj,
1074                                                   HandleId id,
1075                                                   HandleValue value) {
1076   JSAddPropertyOp addProperty = obj->getClass()->getAddProperty();
1077   if (MOZ_UNLIKELY(addProperty)) {
1078     MOZ_ASSERT(!cx->isHelperThreadContext());
1079 
1080     if (!CallJSAddPropertyOp(cx, addProperty, obj, id, value)) {
1081       NativeObject::removeProperty(cx, obj, id);
1082       return false;
1083     }
1084   }
1085   return true;
1086 }
1087 
CallAddPropertyHookDense(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue value)1088 static MOZ_ALWAYS_INLINE bool CallAddPropertyHookDense(JSContext* cx,
1089                                                        HandleNativeObject obj,
1090                                                        uint32_t index,
1091                                                        HandleValue value) {
1092   // Inline addProperty for array objects.
1093   if (obj->is<ArrayObject>()) {
1094     ArrayObject* arr = &obj->as<ArrayObject>();
1095     uint32_t length = arr->length();
1096     if (index >= length) {
1097       arr->setLength(index + 1);
1098     }
1099     return true;
1100   }
1101 
1102   JSAddPropertyOp addProperty = obj->getClass()->getAddProperty();
1103   if (MOZ_UNLIKELY(addProperty)) {
1104     MOZ_ASSERT(!cx->isHelperThreadContext());
1105 
1106     RootedId id(cx, INT_TO_JSID(index));
1107     if (!CallJSAddPropertyOp(cx, addProperty, obj, id, value)) {
1108       obj->setDenseElementHole(index);
1109       return false;
1110     }
1111   }
1112   return true;
1113 }
1114 
1115 /**
1116  * Determines whether a write to the given element on |arr| should fail
1117  * because |arr| has a non-writable length, and writing that element would
1118  * increase the length of the array.
1119  */
WouldDefinePastNonwritableLength(ArrayObject * arr,uint32_t index)1120 static bool WouldDefinePastNonwritableLength(ArrayObject* arr, uint32_t index) {
1121   return !arr->lengthIsWritable() && index >= arr->length();
1122 }
1123 
ReshapeForShadowedPropSlow(JSContext * cx,HandleNativeObject obj,HandleId id)1124 static bool ReshapeForShadowedPropSlow(JSContext* cx, HandleNativeObject obj,
1125                                        HandleId id) {
1126   MOZ_ASSERT(obj->isUsedAsPrototype());
1127 
1128   // Lookups on integer ids cannot be cached through prototypes.
1129   if (JSID_IS_INT(id)) {
1130     return true;
1131   }
1132 
1133   RootedObject proto(cx, obj->staticPrototype());
1134   while (proto) {
1135     // Lookups will not be cached through non-native protos.
1136     if (!proto->is<NativeObject>()) {
1137       break;
1138     }
1139 
1140     if (proto->as<NativeObject>().contains(cx, id)) {
1141       return NativeObject::reshapeForShadowedProp(cx, proto.as<NativeObject>());
1142     }
1143 
1144     proto = proto->staticPrototype();
1145   }
1146 
1147   return true;
1148 }
1149 
ReshapeForShadowedProp(JSContext * cx,HandleObject obj,HandleId id)1150 static MOZ_ALWAYS_INLINE bool ReshapeForShadowedProp(JSContext* cx,
1151                                                      HandleObject obj,
1152                                                      HandleId id) {
1153   // If |obj| is a prototype of another object, check if we're shadowing a
1154   // property on its proto chain. In this case we need to reshape that object
1155   // for shape teleporting to work correctly.
1156   //
1157   // See also the 'Shape Teleporting Optimization' comment in jit/CacheIR.cpp.
1158 
1159   // Inlined fast path for non-prototype/non-native objects.
1160   if (!obj->isUsedAsPrototype() || !obj->is<NativeObject>()) {
1161     return true;
1162   }
1163 
1164   return ReshapeForShadowedPropSlow(cx, obj.as<NativeObject>(), id);
1165 }
1166 
1167 /* static */
reshapeForShadowedProp(JSContext * cx,HandleNativeObject obj)1168 bool NativeObject::reshapeForShadowedProp(JSContext* cx,
1169                                           HandleNativeObject obj) {
1170   if (!obj->inDictionaryMode()) {
1171     return toDictionaryMode(cx, obj);
1172   }
1173   return generateNewDictionaryShape(cx, obj);
1174 }
1175 
ChangeProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleObject getter,HandleObject setter,PropertyFlags flags,PropertyResult * existing)1176 static bool ChangeProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
1177                            HandleObject getter, HandleObject setter,
1178                            PropertyFlags flags, PropertyResult* existing) {
1179   MOZ_ASSERT(existing);
1180 
1181   Rooted<GetterSetter*> gs(cx);
1182 
1183   // If we're redefining a getter/setter property but the getter and setter
1184   // objects are still the same, use the existing GetterSetter.
1185   if (existing->isNativeProperty()) {
1186     PropertyInfo prop = existing->propertyInfo();
1187     if (prop.isAccessorProperty()) {
1188       GetterSetter* current = obj->getGetterSetter(prop);
1189       if (current->getter() == getter && current->setter() == setter) {
1190         gs = current;
1191       }
1192     }
1193   }
1194 
1195   if (!gs) {
1196     gs = GetterSetter::create(cx, getter, setter);
1197     if (!gs) {
1198       return false;
1199     }
1200   }
1201 
1202   uint32_t slot;
1203   if (existing->isNativeProperty()) {
1204     if (!NativeObject::changeProperty(cx, obj, id, flags, &slot)) {
1205       return false;
1206     }
1207   } else {
1208     if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
1209       return false;
1210     }
1211   }
1212 
1213   obj->setSlot(slot, PrivateGCThingValue(gs));
1214   return true;
1215 }
1216 
ComputePropertyFlags(const PropertyDescriptor & desc)1217 static PropertyFlags ComputePropertyFlags(const PropertyDescriptor& desc) {
1218   desc.assertComplete();
1219 
1220   PropertyFlags flags;
1221   flags.setFlag(PropertyFlag::Configurable, desc.configurable());
1222   flags.setFlag(PropertyFlag::Enumerable, desc.enumerable());
1223 
1224   if (desc.isDataDescriptor()) {
1225     flags.setFlag(PropertyFlag::Writable, desc.writable());
1226   } else {
1227     MOZ_ASSERT(desc.isAccessorDescriptor());
1228     flags.setFlag(PropertyFlag::AccessorProperty);
1229   }
1230 
1231   return flags;
1232 }
1233 
1234 // Whether we're adding a new property or changing an existing property (this
1235 // can be either a property stored in the shape tree or a dense element).
1236 enum class IsAddOrChange { Add, Change };
1237 
1238 template <IsAddOrChange AddOrChange>
AddOrChangeProperty(JSContext * cx,HandleNativeObject obj,HandleId id,Handle<PropertyDescriptor> desc,PropertyResult * existing=nullptr)1239 static MOZ_ALWAYS_INLINE bool AddOrChangeProperty(
1240     JSContext* cx, HandleNativeObject obj, HandleId id,
1241     Handle<PropertyDescriptor> desc, PropertyResult* existing = nullptr) {
1242   desc.assertComplete();
1243 
1244 #ifdef DEBUG
1245   if constexpr (AddOrChange == IsAddOrChange::Add) {
1246     MOZ_ASSERT(existing == nullptr);
1247     MOZ_ASSERT(!obj->containsPure(id));
1248   } else {
1249     static_assert(AddOrChange == IsAddOrChange::Change);
1250     MOZ_ASSERT(existing);
1251     MOZ_ASSERT(existing->isNativeProperty() || existing->isDenseElement());
1252   }
1253 #endif
1254 
1255   if (!ReshapeForShadowedProp(cx, obj, id)) {
1256     return false;
1257   }
1258 
1259   // Use dense storage for indexed properties where possible: when we have an
1260   // integer key with default property attributes and are either adding a new
1261   // property or changing a dense element.
1262   PropertyFlags flags = ComputePropertyFlags(desc);
1263   if (id.isInt() && flags == PropertyFlags::defaultDataPropFlags &&
1264       (AddOrChange == IsAddOrChange::Add || existing->isDenseElement())) {
1265     MOZ_ASSERT(!desc.isAccessorDescriptor());
1266     MOZ_ASSERT(!obj->is<TypedArrayObject>());
1267     uint32_t index = JSID_TO_INT(id);
1268     DenseElementResult edResult = obj->ensureDenseElements(cx, index, 1);
1269     if (edResult == DenseElementResult::Failure) {
1270       return false;
1271     }
1272     if (edResult == DenseElementResult::Success) {
1273       obj->setDenseElement(index, desc.value());
1274       if (!CallAddPropertyHookDense(cx, obj, index, desc.value())) {
1275         return false;
1276       }
1277       return true;
1278     }
1279   }
1280 
1281   if constexpr (AddOrChange == IsAddOrChange::Add) {
1282     if (desc.isAccessorDescriptor()) {
1283       Rooted<GetterSetter*> gs(
1284           cx, GetterSetter::create(cx, desc.getter(), desc.setter()));
1285       if (!gs) {
1286         return false;
1287       }
1288       uint32_t slot;
1289       if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
1290         return false;
1291       }
1292       obj->initSlot(slot, PrivateGCThingValue(gs));
1293     } else {
1294       uint32_t slot;
1295       if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
1296         return false;
1297       }
1298       obj->initSlot(slot, desc.value());
1299     }
1300   } else {
1301     if (desc.isAccessorDescriptor()) {
1302       if (!ChangeProperty(cx, obj, id, desc.getter(), desc.setter(), flags,
1303                           existing)) {
1304         return false;
1305       }
1306     } else {
1307       uint32_t slot;
1308       if (existing->isNativeProperty()) {
1309         if (!NativeObject::changeProperty(cx, obj, id, flags, &slot)) {
1310           return false;
1311         }
1312       } else {
1313         if (!NativeObject::addProperty(cx, obj, id, flags, &slot)) {
1314           return false;
1315         }
1316       }
1317       obj->setSlot(slot, desc.value());
1318     }
1319   }
1320 
1321   // Clear any existing dense index after adding a sparse indexed property,
1322   // and investigate converting the object to dense indexes.
1323   if (JSID_IS_INT(id)) {
1324     uint32_t index = JSID_TO_INT(id);
1325     if constexpr (AddOrChange == IsAddOrChange::Add) {
1326       MOZ_ASSERT(!obj->containsDenseElement(index));
1327     } else {
1328       obj->removeDenseElementForSparseIndex(index);
1329     }
1330     DenseElementResult edResult =
1331         NativeObject::maybeDensifySparseElements(cx, obj);
1332     if (edResult == DenseElementResult::Failure) {
1333       return false;
1334     }
1335     if (edResult == DenseElementResult::Success) {
1336       MOZ_ASSERT(!desc.isAccessorDescriptor());
1337       return CallAddPropertyHookDense(cx, obj, index, desc.value());
1338     }
1339   }
1340 
1341   if (desc.isDataDescriptor()) {
1342     return CallAddPropertyHook(cx, obj, id, desc.value());
1343   }
1344 
1345   return CallAddPropertyHook(cx, obj, id, UndefinedHandleValue);
1346 }
1347 
1348 // Versions of AddOrChangeProperty optimized for adding a plain data property.
1349 // These function doesn't handle integer ids as we may have to store them in
1350 // dense elements.
AddDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v)1351 static MOZ_ALWAYS_INLINE bool AddDataProperty(JSContext* cx,
1352                                               HandleNativeObject obj,
1353                                               HandleId id, HandleValue v) {
1354   MOZ_ASSERT(!JSID_IS_INT(id));
1355 
1356   if (!ReshapeForShadowedProp(cx, obj, id)) {
1357     return false;
1358   }
1359 
1360   uint32_t slot;
1361   if (!NativeObject::addProperty(cx, obj, id,
1362                                  PropertyFlags::defaultDataPropFlags, &slot)) {
1363     return false;
1364   }
1365 
1366   obj->initSlot(slot, v);
1367 
1368   return CallAddPropertyHook(cx, obj, id, v);
1369 }
1370 
IsAccessorDescriptor(const PropertyResult & prop)1371 static bool IsAccessorDescriptor(const PropertyResult& prop) {
1372   if (prop.isNativeProperty()) {
1373     return prop.propertyInfo().isAccessorProperty();
1374   }
1375 
1376   MOZ_ASSERT(prop.isDenseElement() || prop.isTypedArrayElement());
1377   return false;
1378 }
1379 
IsDataDescriptor(const PropertyResult & prop)1380 static bool IsDataDescriptor(const PropertyResult& prop) {
1381   return !IsAccessorDescriptor(prop);
1382 }
1383 
1384 static bool GetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
1385                                   MutableHandleValue vp);
1386 
GetExistingDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,const PropertyResult & prop,MutableHandleValue vp)1387 static bool GetExistingDataProperty(JSContext* cx, HandleNativeObject obj,
1388                                     HandleId id, const PropertyResult& prop,
1389                                     MutableHandleValue vp) {
1390   if (prop.isDenseElement()) {
1391     vp.set(obj->getDenseElement(prop.denseElementIndex()));
1392     return true;
1393   }
1394   if (prop.isTypedArrayElement()) {
1395     size_t idx = prop.typedArrayElementIndex();
1396     return obj->as<TypedArrayObject>().getElement<CanGC>(cx, idx, vp);
1397   }
1398 
1399   PropertyInfo propInfo = prop.propertyInfo();
1400   if (propInfo.isDataProperty()) {
1401     vp.set(obj->getSlot(propInfo.slot()));
1402     return true;
1403   }
1404 
1405   MOZ_ASSERT(!cx->isHelperThreadContext());
1406   MOZ_RELEASE_ASSERT(propInfo.isCustomDataProperty());
1407   return GetCustomDataProperty(cx, obj, id, vp);
1408 }
1409 
1410 /*
1411  * If desc is redundant with an existing own property obj[id], then set
1412  * |*redundant = true| and return true.
1413  */
DefinePropertyIsRedundant(JSContext * cx,HandleNativeObject obj,HandleId id,const PropertyResult & prop,JS::PropertyAttributes attrs,Handle<PropertyDescriptor> desc,bool * redundant)1414 static bool DefinePropertyIsRedundant(JSContext* cx, HandleNativeObject obj,
1415                                       HandleId id, const PropertyResult& prop,
1416                                       JS::PropertyAttributes attrs,
1417                                       Handle<PropertyDescriptor> desc,
1418                                       bool* redundant) {
1419   *redundant = false;
1420 
1421   if (desc.hasConfigurable() && desc.configurable() != attrs.configurable()) {
1422     return true;
1423   }
1424   if (desc.hasEnumerable() && desc.enumerable() != attrs.enumerable()) {
1425     return true;
1426   }
1427   if (desc.isDataDescriptor()) {
1428     if (IsAccessorDescriptor(prop)) {
1429       return true;
1430     }
1431     if (desc.hasWritable() && desc.writable() != attrs.writable()) {
1432       return true;
1433     }
1434     if (desc.hasValue()) {
1435       // Get the current value of the existing property.
1436       RootedValue currentValue(cx);
1437       if (!GetExistingDataProperty(cx, obj, id, prop, &currentValue)) {
1438         return false;
1439       }
1440 
1441       // Don't call SameValue here to ensure we properly update distinct
1442       // NaN values.
1443       if (desc.value() != currentValue) {
1444         return true;
1445       }
1446     }
1447 
1448     // Check for custom data properties for ArrayObject/ArgumentsObject.
1449     // PropertyDescriptor can't represent these properties so they're never
1450     // redundant.
1451     if (prop.isNativeProperty() && prop.propertyInfo().isCustomDataProperty()) {
1452       return true;
1453     }
1454   } else if (desc.isAccessorDescriptor()) {
1455     if (!prop.isNativeProperty()) {
1456       return true;
1457     }
1458     PropertyInfo propInfo = prop.propertyInfo();
1459     if (desc.hasGetter() && (!propInfo.isAccessorProperty() ||
1460                              desc.getter() != obj->getGetter(propInfo))) {
1461       return true;
1462     }
1463     if (desc.hasSetter() && (!propInfo.isAccessorProperty() ||
1464                              desc.setter() != obj->getSetter(propInfo))) {
1465       return true;
1466     }
1467   }
1468 
1469   *redundant = true;
1470   return true;
1471 }
1472 
NativeDefineProperty(JSContext * cx,HandleNativeObject obj,HandleId id,Handle<PropertyDescriptor> desc_,ObjectOpResult & result)1473 bool js::NativeDefineProperty(JSContext* cx, HandleNativeObject obj,
1474                               HandleId id, Handle<PropertyDescriptor> desc_,
1475                               ObjectOpResult& result) {
1476   desc_.assertValid();
1477 
1478   // Section numbers and step numbers below refer to ES2018, draft rev
1479   // 540b827fccf6122a984be99ab9af7be20e3b5562.
1480   //
1481   // This function aims to implement 9.1.6 [[DefineOwnProperty]] as well as
1482   // the [[DefineOwnProperty]] methods described in 9.4.2.1 (arrays), 9.4.4.2
1483   // (arguments), and 9.4.5.3 (typed array views).
1484 
1485   // Dispense with custom behavior of exotic native objects first.
1486   if (obj->is<ArrayObject>()) {
1487     // 9.4.2.1 step 2. Redefining an array's length is very special.
1488     Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
1489     if (id == NameToId(cx->names().length)) {
1490       // 9.1.6.3 ValidateAndApplyPropertyDescriptor, step 7.a.
1491       if (desc_.isAccessorDescriptor()) {
1492         return result.fail(JSMSG_CANT_REDEFINE_PROP);
1493       }
1494 
1495       MOZ_ASSERT(!cx->isHelperThreadContext());
1496       return ArraySetLength(cx, arr, id, desc_, result);
1497     }
1498 
1499     // 9.4.2.1 step 3. Don't extend a fixed-length array.
1500     uint32_t index;
1501     if (IdIsIndex(id, &index)) {
1502       if (WouldDefinePastNonwritableLength(arr, index)) {
1503         return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
1504       }
1505     }
1506   } else if (obj->is<TypedArrayObject>()) {
1507     // 9.4.5.3 step 3. Indexed properties of typed arrays are special.
1508     Rooted<TypedArrayObject*> tobj(cx, &obj->as<TypedArrayObject>());
1509     mozilla::Maybe<uint64_t> index;
1510     if (!ToTypedArrayIndex(cx, id, &index)) {
1511       return false;
1512     }
1513 
1514     if (index) {
1515       MOZ_ASSERT(!cx->isHelperThreadContext());
1516       return DefineTypedArrayElement(cx, tobj, index.value(), desc_, result);
1517     }
1518   } else if (obj->is<ArgumentsObject>()) {
1519     Rooted<ArgumentsObject*> argsobj(cx, &obj->as<ArgumentsObject>());
1520     if (id.isAtom(cx->names().length)) {
1521       // Either we are resolving the .length property on this object,
1522       // or redefining it. In the latter case only, we must reify the
1523       // property.
1524       if (!desc_.resolving()) {
1525         if (!ArgumentsObject::reifyLength(cx, argsobj)) {
1526           return false;
1527         }
1528       }
1529     } else if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
1530       // Do same thing as .length for [@@iterator].
1531       if (!desc_.resolving()) {
1532         if (!ArgumentsObject::reifyIterator(cx, argsobj)) {
1533           return false;
1534         }
1535       }
1536     } else if (id.isInt()) {
1537       if (!desc_.resolving()) {
1538         argsobj->markElementOverridden();
1539       }
1540     }
1541   }
1542 
1543   // 9.1.6.1 OrdinaryDefineOwnProperty step 1.
1544   PropertyResult prop;
1545   if (desc_.resolving()) {
1546     // We are being called from a resolve or enumerate hook to reify a
1547     // lazily-resolved property. To avoid reentering the resolve hook and
1548     // recursing forever, skip the resolve hook when doing this lookup.
1549     if (!NativeLookupOwnPropertyNoResolve(cx, obj, id, &prop)) {
1550       return false;
1551     }
1552   } else {
1553     if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
1554       return false;
1555     }
1556   }
1557 
1558   // From this point, the step numbers refer to
1559   // 9.1.6.3, ValidateAndApplyPropertyDescriptor.
1560   // Step 1 is a redundant assertion.
1561 
1562   // Filling in desc: Here we make a copy of the desc_ argument. We will turn
1563   // it into a complete descriptor before updating obj. The spec algorithm
1564   // does not explicitly do this, but the end result is the same. Search for
1565   // "fill in" below for places where the filling-in actually occurs.
1566   Rooted<PropertyDescriptor> desc(cx, desc_);
1567 
1568   // Step 2.
1569   if (prop.isNotFound()) {
1570     // Note: We are sharing the property definition machinery with private
1571     //       fields. Private fields may be added to non-extensible objects.
1572     if (!obj->isExtensible() && !id.isPrivateName()) {
1573       return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
1574     }
1575 
1576     // Fill in missing desc fields with defaults.
1577     CompletePropertyDescriptor(&desc);
1578 
1579     if (!AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc)) {
1580       return false;
1581     }
1582     return result.succeed();
1583   }
1584 
1585   // Step 3 and 7.a.i.3, 8.a.iii, 10 (partially). Prop might not actually
1586   // have a real shape, e.g. in the case of typed array elements,
1587   // GetPropertyAttributes is used to paper-over that difference.
1588   JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop);
1589   bool redundant;
1590   if (!DefinePropertyIsRedundant(cx, obj, id, prop, attrs, desc, &redundant)) {
1591     return false;
1592   }
1593   if (redundant) {
1594     return result.succeed();
1595   }
1596 
1597   // Step 4.
1598   if (!attrs.configurable()) {
1599     if (desc.hasConfigurable() && desc.configurable()) {
1600       return result.fail(JSMSG_CANT_REDEFINE_PROP);
1601     }
1602     if (desc.hasEnumerable() && desc.enumerable() != attrs.enumerable()) {
1603       return result.fail(JSMSG_CANT_REDEFINE_PROP);
1604     }
1605   }
1606 
1607   // Fill in desc.[[Configurable]] and desc.[[Enumerable]] if missing.
1608   if (!desc.hasConfigurable()) {
1609     desc.setConfigurable(attrs.configurable());
1610   }
1611   if (!desc.hasEnumerable()) {
1612     desc.setEnumerable(attrs.enumerable());
1613   }
1614 
1615   // Steps 5-8.
1616   if (desc.isGenericDescriptor()) {
1617     // Step 5. No further validation is required.
1618 
1619     // Fill in desc. A generic descriptor has none of these fields, so copy
1620     // everything from shape.
1621     MOZ_ASSERT(!desc.hasValue());
1622     MOZ_ASSERT(!desc.hasWritable());
1623     MOZ_ASSERT(!desc.hasGetter());
1624     MOZ_ASSERT(!desc.hasSetter());
1625     if (IsDataDescriptor(prop)) {
1626       RootedValue currentValue(cx);
1627       if (!GetExistingDataProperty(cx, obj, id, prop, &currentValue)) {
1628         return false;
1629       }
1630       desc.setValue(currentValue);
1631       desc.setWritable(attrs.writable());
1632     } else {
1633       PropertyInfo propInfo = prop.propertyInfo();
1634       desc.setGetter(obj->getGetter(propInfo));
1635       desc.setSetter(obj->getSetter(propInfo));
1636     }
1637   } else if (desc.isDataDescriptor() != IsDataDescriptor(prop)) {
1638     // Step 6.
1639     if (!attrs.configurable()) {
1640       return result.fail(JSMSG_CANT_REDEFINE_PROP);
1641     }
1642 
1643     // Fill in desc fields with default values (steps 6.b.i and 6.c.i).
1644     CompletePropertyDescriptor(&desc);
1645   } else if (desc.isDataDescriptor()) {
1646     // Step 7.
1647     bool frozen = !attrs.configurable() && !attrs.writable();
1648 
1649     // Step 7.a.i.1.
1650     if (frozen && desc.hasWritable() && desc.writable()) {
1651       return result.fail(JSMSG_CANT_REDEFINE_PROP);
1652     }
1653 
1654     if (frozen || !desc.hasValue()) {
1655       RootedValue currentValue(cx);
1656       if (!GetExistingDataProperty(cx, obj, id, prop, &currentValue)) {
1657         return false;
1658       }
1659 
1660       if (!desc.hasValue()) {
1661         // Fill in desc.[[Value]].
1662         desc.setValue(currentValue);
1663       } else {
1664         // Step 7.a.i.2.
1665         bool same;
1666         MOZ_ASSERT(!cx->isHelperThreadContext());
1667         if (!SameValue(cx, desc.value(), currentValue, &same)) {
1668           return false;
1669         }
1670         if (!same) {
1671           return result.fail(JSMSG_CANT_REDEFINE_PROP);
1672         }
1673       }
1674     }
1675 
1676     // Step 7.a.i.3.
1677     if (frozen) {
1678       return result.succeed();
1679     }
1680 
1681     // Fill in desc.[[Writable]].
1682     if (!desc.hasWritable()) {
1683       desc.setWritable(attrs.writable());
1684     }
1685   } else {
1686     // Step 8.
1687     PropertyInfo propInfo = prop.propertyInfo();
1688     MOZ_ASSERT(propInfo.isAccessorProperty());
1689     MOZ_ASSERT(desc.isAccessorDescriptor());
1690 
1691     // The spec says to use SameValue, but since the values in
1692     // question are objects, we can just compare pointers.
1693     if (desc.hasSetter()) {
1694       // Step 8.a.i.
1695       if (!attrs.configurable() && desc.setter() != obj->getSetter(propInfo)) {
1696         return result.fail(JSMSG_CANT_REDEFINE_PROP);
1697       }
1698     } else {
1699       // Fill in desc.[[Set]] from shape.
1700       desc.setSetter(obj->getSetter(propInfo));
1701     }
1702     if (desc.hasGetter()) {
1703       // Step 8.a.ii.
1704       if (!attrs.configurable() && desc.getter() != obj->getGetter(propInfo)) {
1705         return result.fail(JSMSG_CANT_REDEFINE_PROP);
1706       }
1707     } else {
1708       // Fill in desc.[[Get]] from shape.
1709       desc.setGetter(obj->getGetter(propInfo));
1710     }
1711 
1712     // Step 8.a.iii (Omitted).
1713   }
1714 
1715   // Step 9.
1716   if (!AddOrChangeProperty<IsAddOrChange::Change>(cx, obj, id, desc, &prop)) {
1717     return false;
1718   }
1719 
1720   // Step 10.
1721   return result.succeed();
1722 }
1723 
NativeDefineDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,unsigned attrs,ObjectOpResult & result)1724 bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
1725                                   HandleId id, HandleValue value,
1726                                   unsigned attrs, ObjectOpResult& result) {
1727   Rooted<PropertyDescriptor> desc(cx, PropertyDescriptor::Data(value, attrs));
1728   return NativeDefineProperty(cx, obj, id, desc, result);
1729 }
1730 
NativeDefineAccessorProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleObject getter,HandleObject setter,unsigned attrs)1731 bool js::NativeDefineAccessorProperty(JSContext* cx, HandleNativeObject obj,
1732                                       HandleId id, HandleObject getter,
1733                                       HandleObject setter, unsigned attrs) {
1734   Rooted<PropertyDescriptor> desc(
1735       cx, PropertyDescriptor::Accessor(
1736               getter ? mozilla::Some(getter) : mozilla::Nothing(),
1737               setter ? mozilla::Some(setter) : mozilla::Nothing(), attrs));
1738 
1739   ObjectOpResult result;
1740   if (!NativeDefineProperty(cx, obj, id, desc, result)) {
1741     return false;
1742   }
1743 
1744   if (!result) {
1745     // Off-thread callers should not get here: they must call this
1746     // function only with known-valid arguments. Populating a new
1747     // PlainObject with configurable properties is fine.
1748     MOZ_ASSERT(!cx->isHelperThreadContext());
1749     result.reportError(cx, obj, id);
1750     return false;
1751   }
1752 
1753   return true;
1754 }
1755 
NativeDefineDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue value,unsigned attrs)1756 bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
1757                                   HandleId id, HandleValue value,
1758                                   unsigned attrs) {
1759   ObjectOpResult result;
1760   if (!NativeDefineDataProperty(cx, obj, id, value, attrs, result)) {
1761     return false;
1762   }
1763   if (!result) {
1764     // Off-thread callers should not get here: they must call this
1765     // function only with known-valid arguments. Populating a new
1766     // PlainObject with configurable properties is fine.
1767     MOZ_ASSERT(!cx->isHelperThreadContext());
1768     result.reportError(cx, obj, id);
1769     return false;
1770   }
1771   return true;
1772 }
1773 
NativeDefineDataProperty(JSContext * cx,HandleNativeObject obj,PropertyName * name,HandleValue value,unsigned attrs)1774 bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj,
1775                                   PropertyName* name, HandleValue value,
1776                                   unsigned attrs) {
1777   RootedId id(cx, NameToId(name));
1778   return NativeDefineDataProperty(cx, obj, id, value, attrs);
1779 }
1780 
DefineNonexistentProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v,ObjectOpResult & result)1781 static bool DefineNonexistentProperty(JSContext* cx, HandleNativeObject obj,
1782                                       HandleId id, HandleValue v,
1783                                       ObjectOpResult& result) {
1784   // Optimized NativeDefineProperty() version for known absent properties.
1785 
1786   // Dispense with custom behavior of exotic native objects first.
1787   if (obj->is<ArrayObject>()) {
1788     // Array's length property is non-configurable, so we shouldn't
1789     // encounter it in this function.
1790     MOZ_ASSERT(id != NameToId(cx->names().length));
1791 
1792     // 9.4.2.1 step 3. Don't extend a fixed-length array.
1793     uint32_t index;
1794     if (IdIsIndex(id, &index)) {
1795       if (WouldDefinePastNonwritableLength(&obj->as<ArrayObject>(), index)) {
1796         return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH);
1797       }
1798     }
1799   } else if (obj->is<TypedArrayObject>()) {
1800     // 9.4.5.5 step 2. Indexed properties of typed arrays are special.
1801     mozilla::Maybe<uint64_t> index;
1802     if (!ToTypedArrayIndex(cx, id, &index)) {
1803       return false;
1804     }
1805 
1806     if (index) {
1807       // This method is only called for non-existent properties, which
1808       // means any absent indexed property must be out of range.
1809       MOZ_ASSERT(index.value() >= obj->as<TypedArrayObject>().length());
1810 
1811       // The following steps refer to 9.4.5.11 IntegerIndexedElementSet.
1812 
1813       // Step 1 is enforced by the caller.
1814 
1815       // Steps 2-3.
1816       // We still need to call ToNumber or ToBigInt, because of its
1817       // possible side effects.
1818       if (!obj->as<TypedArrayObject>().convertForSideEffect(cx, v)) {
1819         return false;
1820       }
1821 
1822       // Step 4 (nothing to do, the index is out of range).
1823 
1824       // Step 5.
1825       return result.succeed();
1826     }
1827   } else if (obj->is<ArgumentsObject>()) {
1828     // If this method is called with either |length| or |@@iterator|, the
1829     // property was previously deleted and hence should already be marked
1830     // as overridden.
1831     MOZ_ASSERT_IF(id.isAtom(cx->names().length),
1832                   obj->as<ArgumentsObject>().hasOverriddenLength());
1833     MOZ_ASSERT_IF(id.isWellKnownSymbol(JS::SymbolCode::iterator),
1834                   obj->as<ArgumentsObject>().hasOverriddenIterator());
1835 
1836     // We still need to mark any element properties as overridden.
1837     if (JSID_IS_INT(id)) {
1838       obj->as<ArgumentsObject>().markElementOverridden();
1839     }
1840   }
1841 
1842 #ifdef DEBUG
1843   PropertyResult prop;
1844   if (!NativeLookupOwnPropertyNoResolve(cx, obj, id, &prop)) {
1845     return false;
1846   }
1847   MOZ_ASSERT(prop.isNotFound(), "didn't expect to find an existing property");
1848 #endif
1849 
1850   // 9.1.6.3, ValidateAndApplyPropertyDescriptor.
1851   // Step 1 is a redundant assertion, step 3 and later don't apply here.
1852 
1853   // Step 2.
1854   if (!obj->isExtensible()) {
1855     return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
1856   }
1857 
1858   if (id.isInt()) {
1859     // This might be a dense element. Use AddOrChangeProperty as it knows
1860     // how to deal with that.
1861     Rooted<PropertyDescriptor> desc(
1862         cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
1863                                          JS::PropertyAttribute::Enumerable,
1864                                          JS::PropertyAttribute::Writable}));
1865     if (!AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc)) {
1866       return false;
1867     }
1868   } else {
1869     if (!AddDataProperty(cx, obj, id, v)) {
1870       return false;
1871     }
1872   }
1873 
1874   return result.succeed();
1875 }
1876 
AddOrUpdateSparseElementHelper(JSContext * cx,HandleArrayObject obj,int32_t int_id,HandleValue v,bool strict)1877 bool js::AddOrUpdateSparseElementHelper(JSContext* cx, HandleArrayObject obj,
1878                                         int32_t int_id, HandleValue v,
1879                                         bool strict) {
1880   MOZ_ASSERT(INT_FITS_IN_JSID(int_id));
1881   RootedId id(cx, INT_TO_JSID(int_id));
1882 
1883   // This helper doesn't handle the case where the index may be in the dense
1884   // elements
1885   MOZ_ASSERT(int_id >= 0);
1886   MOZ_ASSERT(uint32_t(int_id) >= obj->getDenseInitializedLength());
1887 
1888   // First decide if this is an add or an update. Because the IC guards have
1889   // already ensured this exists exterior to the dense array range, and the
1890   // prototype checks have ensured there are no indexes on the prototype, we
1891   // can use the shape lineage to find the element if it exists:
1892   uint32_t index;
1893   PropMap* map = obj->shape()->lookup(cx, id, &index);
1894 
1895   // If we didn't find the property, we're on the add path: delegate to
1896   // AddOrChangeProperty.
1897   if (map == nullptr) {
1898     Rooted<PropertyDescriptor> desc(
1899         cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
1900                                          JS::PropertyAttribute::Enumerable,
1901                                          JS::PropertyAttribute::Writable}));
1902     return AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc);
1903   }
1904 
1905   // At this point we're updating a property: See SetExistingProperty.
1906   PropertyInfo prop = map->getPropertyInfo(index);
1907   if (prop.isDataProperty() && prop.writable()) {
1908     obj->setSlot(prop.slot(), v);
1909     return true;
1910   }
1911 
1912   // We don't know exactly what this object looks like, hit the slowpath.
1913   RootedValue receiver(cx, ObjectValue(*obj));
1914   JS::ObjectOpResult result;
1915   return SetProperty(cx, obj, id, v, receiver, result) &&
1916          result.checkStrictModeError(cx, obj, id, strict);
1917 }
1918 
1919 /*** [[HasProperty]] ********************************************************/
1920 
1921 // ES6 draft rev31 9.1.7.1 OrdinaryHasProperty
NativeHasProperty(JSContext * cx,HandleNativeObject obj,HandleId id,bool * foundp)1922 bool js::NativeHasProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
1923                            bool* foundp) {
1924   RootedNativeObject pobj(cx, obj);
1925   PropertyResult prop;
1926 
1927   // This loop isn't explicit in the spec algorithm. See the comment on step
1928   // 7.a. below.
1929   for (;;) {
1930     // Steps 2-3.
1931     if (!NativeLookupOwnPropertyInline<CanGC>(cx, pobj, id, &prop)) {
1932       return false;
1933     }
1934 
1935     // Step 4.
1936     if (prop.isFound()) {
1937       *foundp = true;
1938       return true;
1939     }
1940 
1941     // Step 5-6.
1942     JSObject* proto = pobj->staticPrototype();
1943 
1944     // Step 8.
1945     // As a side-effect of NativeLookupOwnPropertyInline, we may determine that
1946     // a property is not found and the proto chain should not be searched. This
1947     // can occur for:
1948     //  - Out-of-range numeric properties of a TypedArrayObject
1949     //  - Recursive resolve hooks (which is expected when they try to set the
1950     //    property being resolved).
1951     if (!proto || prop.shouldIgnoreProtoChain()) {
1952       *foundp = false;
1953       return true;
1954     }
1955 
1956     // Step 7.a. If the prototype is also native, this step is a
1957     // recursive tail call, and we don't need to go through all the
1958     // plumbing of HasProperty; the top of the loop is where
1959     // we're going to end up anyway. But if pobj is non-native,
1960     // that optimization would be incorrect.
1961     if (!proto->is<NativeObject>()) {
1962       RootedObject protoRoot(cx, proto);
1963       return HasProperty(cx, protoRoot, id, foundp);
1964     }
1965 
1966     pobj = &proto->as<NativeObject>();
1967   }
1968 }
1969 
1970 /*** [[GetOwnPropertyDescriptor]] *******************************************/
1971 
NativeGetOwnPropertyDescriptor(JSContext * cx,HandleNativeObject obj,HandleId id,MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc)1972 bool js::NativeGetOwnPropertyDescriptor(
1973     JSContext* cx, HandleNativeObject obj, HandleId id,
1974     MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
1975   PropertyResult prop;
1976   if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
1977     return false;
1978   }
1979   if (prop.isNotFound()) {
1980     desc.reset();
1981     return true;
1982   }
1983 
1984   if (prop.isNativeProperty() && prop.propertyInfo().isAccessorProperty()) {
1985     PropertyInfo propInfo = prop.propertyInfo();
1986     desc.set(mozilla::Some(PropertyDescriptor::Accessor(
1987         obj->getGetter(propInfo), obj->getSetter(propInfo),
1988         propInfo.propAttributes())));
1989     return true;
1990   }
1991 
1992   RootedValue value(cx);
1993   if (!GetExistingDataProperty(cx, obj, id, prop, &value)) {
1994     return false;
1995   }
1996 
1997   JS::PropertyAttributes attrs = GetPropertyAttributes(obj, prop);
1998   desc.set(mozilla::Some(PropertyDescriptor::Data(value, attrs)));
1999   return true;
2000 }
2001 
2002 /*** [[Get]] ****************************************************************/
2003 
GetCustomDataProperty(JSContext * cx,HandleObject obj,HandleId id,MutableHandleValue vp)2004 static bool GetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
2005                                   MutableHandleValue vp) {
2006   AutoCheckRecursionLimit recursion(cx);
2007   if (!recursion.check(cx)) {
2008     return false;
2009   }
2010 
2011   cx->check(obj, id, vp);
2012 
2013   const JSClass* clasp = obj->getClass();
2014   if (clasp == &ArrayObject::class_) {
2015     if (!ArrayLengthGetter(cx, obj, id, vp)) {
2016       return false;
2017     }
2018   } else if (clasp == &MappedArgumentsObject::class_) {
2019     if (!MappedArgGetter(cx, obj, id, vp)) {
2020       return false;
2021     }
2022   } else {
2023     MOZ_RELEASE_ASSERT(clasp == &UnmappedArgumentsObject::class_);
2024     if (!UnmappedArgGetter(cx, obj, id, vp)) {
2025       return false;
2026     }
2027   }
2028 
2029   cx->check(vp);
2030   return true;
2031 }
2032 
CallGetter(JSContext * cx,HandleNativeObject obj,HandleValue receiver,HandleId id,PropertyInfo prop,MutableHandleValue vp)2033 static inline bool CallGetter(JSContext* cx, HandleNativeObject obj,
2034                               HandleValue receiver, HandleId id,
2035                               PropertyInfo prop, MutableHandleValue vp) {
2036   MOZ_ASSERT(!prop.isDataProperty());
2037 
2038   if (prop.isAccessorProperty()) {
2039     RootedValue getter(cx, obj->getGetterValue(prop));
2040     return js::CallGetter(cx, receiver, getter, vp);
2041   }
2042 
2043   MOZ_ASSERT(prop.isCustomDataProperty());
2044 
2045   return GetCustomDataProperty(cx, obj, id, vp);
2046 }
2047 
2048 template <AllowGC allowGC>
GetExistingProperty(JSContext * cx,typename MaybeRooted<Value,allowGC>::HandleType receiver,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<jsid,allowGC>::HandleType id,PropertyInfo prop,typename MaybeRooted<Value,allowGC>::MutableHandleType vp)2049 static MOZ_ALWAYS_INLINE bool GetExistingProperty(
2050     JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType receiver,
2051     typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
2052     typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyInfo prop,
2053     typename MaybeRooted<Value, allowGC>::MutableHandleType vp) {
2054   if (prop.isDataProperty()) {
2055     vp.set(obj->getSlot(prop.slot()));
2056     return true;
2057   }
2058 
2059   vp.setUndefined();
2060 
2061   if (!prop.isCustomDataProperty() && !obj->hasGetter(prop)) {
2062     return true;
2063   }
2064 
2065   if constexpr (!allowGC) {
2066     return false;
2067   } else {
2068     return CallGetter(cx, obj, receiver, id, prop, vp);
2069   }
2070 }
2071 
NativeGetExistingProperty(JSContext * cx,HandleObject receiver,HandleNativeObject obj,HandleId id,PropertyInfo prop,MutableHandleValue vp)2072 bool js::NativeGetExistingProperty(JSContext* cx, HandleObject receiver,
2073                                    HandleNativeObject obj, HandleId id,
2074                                    PropertyInfo prop, MutableHandleValue vp) {
2075   RootedValue receiverValue(cx, ObjectValue(*receiver));
2076   return GetExistingProperty<CanGC>(cx, receiverValue, obj, id, prop, vp);
2077 }
2078 
2079 enum IsNameLookup { NotNameLookup = false, NameLookup = true };
2080 
2081 /*
2082  * Finish getting the property `receiver[id]` after looking at every object on
2083  * the prototype chain and not finding any such property.
2084  *
2085  * Per the spec, this should just set the result to `undefined` and call it a
2086  * day. However this function also runs when we're evaluating an
2087  * expression that's an Identifier (that is, an unqualified name lookup),
2088  * so we need to figure out if that's what's happening and throw
2089  * a ReferenceError if so.
2090  */
GetNonexistentProperty(JSContext * cx,HandleId id,IsNameLookup nameLookup,MutableHandleValue vp)2091 static bool GetNonexistentProperty(JSContext* cx, HandleId id,
2092                                    IsNameLookup nameLookup,
2093                                    MutableHandleValue vp) {
2094   vp.setUndefined();
2095 
2096   // If we are doing a name lookup, this is a ReferenceError.
2097   if (nameLookup) {
2098     ReportIsNotDefined(cx, id);
2099     return false;
2100   }
2101 
2102   // Otherwise, just return |undefined|.
2103   return true;
2104 }
2105 
2106 // The NoGC version of GetNonexistentProperty, present only to make types line
2107 // up.
GetNonexistentProperty(JSContext * cx,const jsid & id,IsNameLookup nameLookup,FakeMutableHandle<Value> vp)2108 bool GetNonexistentProperty(JSContext* cx, const jsid& id,
2109                             IsNameLookup nameLookup,
2110                             FakeMutableHandle<Value> vp) {
2111   return false;
2112 }
2113 
GeneralizedGetProperty(JSContext * cx,HandleObject obj,HandleId id,HandleValue receiver,IsNameLookup nameLookup,MutableHandleValue vp)2114 static inline bool GeneralizedGetProperty(JSContext* cx, HandleObject obj,
2115                                           HandleId id, HandleValue receiver,
2116                                           IsNameLookup nameLookup,
2117                                           MutableHandleValue vp) {
2118   AutoCheckRecursionLimit recursion(cx);
2119   if (!recursion.check(cx)) {
2120     return false;
2121   }
2122   if (nameLookup) {
2123     // When nameLookup is true, GetProperty implements ES6 rev 34 (2015 Feb
2124     // 20) 8.1.1.2.6 GetBindingValue, with step 3 (the call to HasProperty)
2125     // and step 6 (the call to Get) fused so that only a single lookup is
2126     // needed.
2127     //
2128     // If we get here, we've reached a non-native object. Fall back on the
2129     // algorithm as specified, with two separate lookups. (Note that we
2130     // throw ReferenceErrors regardless of strictness, technically a bug.)
2131 
2132     bool found;
2133     if (!HasProperty(cx, obj, id, &found)) {
2134       return false;
2135     }
2136     if (!found) {
2137       ReportIsNotDefined(cx, id);
2138       return false;
2139     }
2140   }
2141 
2142   return GetProperty(cx, obj, receiver, id, vp);
2143 }
2144 
GeneralizedGetProperty(JSContext * cx,JSObject * obj,jsid id,const Value & receiver,IsNameLookup nameLookup,FakeMutableHandle<Value> vp)2145 static inline bool GeneralizedGetProperty(JSContext* cx, JSObject* obj, jsid id,
2146                                           const Value& receiver,
2147                                           IsNameLookup nameLookup,
2148                                           FakeMutableHandle<Value> vp) {
2149   AutoCheckRecursionLimit recursion(cx);
2150   if (!recursion.checkDontReport(cx)) {
2151     return false;
2152   }
2153   if (nameLookup) {
2154     return false;
2155   }
2156   return GetPropertyNoGC(cx, obj, receiver, id, vp.address());
2157 }
2158 
GetSparseElementHelper(JSContext * cx,HandleArrayObject obj,int32_t int_id,MutableHandleValue result)2159 bool js::GetSparseElementHelper(JSContext* cx, HandleArrayObject obj,
2160                                 int32_t int_id, MutableHandleValue result) {
2161   // Callers should have ensured that this object has a static prototype.
2162   MOZ_ASSERT(obj->hasStaticPrototype());
2163 
2164   // Indexed properties can not exist on the prototype chain.
2165   MOZ_ASSERT_IF(obj->staticPrototype() != nullptr,
2166                 !ObjectMayHaveExtraIndexedProperties(obj->staticPrototype()));
2167 
2168   MOZ_ASSERT(INT_FITS_IN_JSID(int_id));
2169   RootedId id(cx, INT_TO_JSID(int_id));
2170 
2171   uint32_t index;
2172   PropMap* map = obj->shape()->lookup(cx, id, &index);
2173   if (!map) {
2174     // Property not found, return directly.
2175     result.setUndefined();
2176     return true;
2177   }
2178 
2179   PropertyInfo prop = map->getPropertyInfo(index);
2180   RootedValue receiver(cx, ObjectValue(*obj));
2181   return GetExistingProperty<CanGC>(cx, receiver, obj, id, prop, result);
2182 }
2183 
2184 template <AllowGC allowGC>
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)2185 static MOZ_ALWAYS_INLINE bool NativeGetPropertyInline(
2186     JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
2187     typename MaybeRooted<Value, allowGC>::HandleType receiver,
2188     typename MaybeRooted<jsid, allowGC>::HandleType id, IsNameLookup nameLookup,
2189     typename MaybeRooted<Value, allowGC>::MutableHandleType vp) {
2190   typename MaybeRooted<NativeObject*, allowGC>::RootType pobj(cx, obj);
2191   PropertyResult prop;
2192 
2193   // This loop isn't explicit in the spec algorithm. See the comment on step
2194   // 4.d below.
2195   for (;;) {
2196     // Steps 2-3.
2197     if (!NativeLookupOwnPropertyInline<allowGC>(cx, pobj, id, &prop)) {
2198       return false;
2199     }
2200 
2201     if (prop.isFound()) {
2202       // Steps 5-8. Special case for dense elements because
2203       // GetExistingProperty doesn't support those.
2204       if (prop.isDenseElement()) {
2205         vp.set(pobj->getDenseElement(prop.denseElementIndex()));
2206         return true;
2207       }
2208       if (prop.isTypedArrayElement()) {
2209         size_t idx = prop.typedArrayElementIndex();
2210         auto* tarr = &pobj->template as<TypedArrayObject>();
2211         return tarr->template getElement<allowGC>(cx, idx, vp);
2212       }
2213 
2214       return GetExistingProperty<allowGC>(cx, receiver, pobj, id,
2215                                           prop.propertyInfo(), vp);
2216     }
2217 
2218     // Steps 4.a-b.
2219     JSObject* proto = pobj->staticPrototype();
2220 
2221     // Step 4.c. The spec algorithm simply returns undefined if proto is
2222     // null, but see the comment on GetNonexistentProperty.
2223     if (!proto || prop.shouldIgnoreProtoChain()) {
2224       return GetNonexistentProperty(cx, id, nameLookup, vp);
2225     }
2226 
2227     // Step 4.d. If the prototype is also native, this step is a
2228     // recursive tail call, and we don't need to go through all the
2229     // plumbing of JSObject::getGeneric; the top of the loop is where
2230     // we're going to end up anyway. But if pobj is non-native,
2231     // that optimization would be incorrect.
2232     if (proto->getOpsGetProperty()) {
2233       RootedObject protoRoot(cx, proto);
2234       return GeneralizedGetProperty(cx, protoRoot, id, receiver, nameLookup,
2235                                     vp);
2236     }
2237 
2238     pobj = &proto->as<NativeObject>();
2239   }
2240 }
2241 
NativeGetProperty(JSContext * cx,HandleNativeObject obj,HandleValue receiver,HandleId id,MutableHandleValue vp)2242 bool js::NativeGetProperty(JSContext* cx, HandleNativeObject obj,
2243                            HandleValue receiver, HandleId id,
2244                            MutableHandleValue vp) {
2245   return NativeGetPropertyInline<CanGC>(cx, obj, receiver, id, NotNameLookup,
2246                                         vp);
2247 }
2248 
NativeGetPropertyNoGC(JSContext * cx,NativeObject * obj,const Value & receiver,jsid id,Value * vp)2249 bool js::NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj,
2250                                const Value& receiver, jsid id, Value* vp) {
2251   AutoAssertNoPendingException noexc(cx);
2252   return NativeGetPropertyInline<NoGC>(cx, obj, receiver, id, NotNameLookup,
2253                                        vp);
2254 }
2255 
NativeGetElement(JSContext * cx,HandleNativeObject obj,HandleValue receiver,int32_t index,MutableHandleValue vp)2256 bool js::NativeGetElement(JSContext* cx, HandleNativeObject obj,
2257                           HandleValue receiver, int32_t index,
2258                           MutableHandleValue vp) {
2259   RootedId id(cx);
2260 
2261   if (MOZ_LIKELY(index >= 0)) {
2262     if (!IndexToId(cx, index, &id)) {
2263       return false;
2264     }
2265   } else {
2266     RootedValue indexVal(cx, Int32Value(index));
2267     if (!PrimitiveValueToId<CanGC>(cx, indexVal, &id)) {
2268       return false;
2269     }
2270   }
2271   return NativeGetProperty(cx, obj, receiver, id, vp);
2272 }
2273 
GetNameBoundInEnvironment(JSContext * cx,HandleObject envArg,HandleId id,MutableHandleValue vp)2274 bool js::GetNameBoundInEnvironment(JSContext* cx, HandleObject envArg,
2275                                    HandleId id, MutableHandleValue vp) {
2276   // Manually unwrap 'with' environments to prevent looking up @@unscopables
2277   // twice.
2278   //
2279   // This is unfortunate because internally, the engine does not distinguish
2280   // HasProperty from HasBinding: both are implemented as a HasPropertyOp
2281   // hook on a WithEnvironmentObject.
2282   //
2283   // In the case of attempting to get the value of a binding already looked up
2284   // via JSOp::BindName, calling HasProperty on the WithEnvironmentObject is
2285   // equivalent to calling HasBinding a second time. This results in the
2286   // incorrect behavior of performing the @@unscopables check again.
2287   RootedObject env(cx, MaybeUnwrapWithEnvironment(envArg));
2288   RootedValue receiver(cx, ObjectValue(*env));
2289   if (env->getOpsGetProperty()) {
2290     return GeneralizedGetProperty(cx, env, id, receiver, NameLookup, vp);
2291   }
2292   return NativeGetPropertyInline<CanGC>(cx, env.as<NativeObject>(), receiver,
2293                                         id, NameLookup, vp);
2294 }
2295 
2296 /*** [[Set]] ****************************************************************/
2297 
SetCustomDataProperty(JSContext * cx,HandleObject obj,HandleId id,HandleValue v,ObjectOpResult & result)2298 static bool SetCustomDataProperty(JSContext* cx, HandleObject obj, HandleId id,
2299                                   HandleValue v, ObjectOpResult& result) {
2300   AutoCheckRecursionLimit recursion(cx);
2301   if (!recursion.check(cx)) {
2302     return false;
2303   }
2304 
2305   cx->check(obj, id, v);
2306 
2307   const JSClass* clasp = obj->getClass();
2308   if (clasp == &ArrayObject::class_) {
2309     return ArrayLengthSetter(cx, obj, id, v, result);
2310   }
2311   if (clasp == &MappedArgumentsObject::class_) {
2312     return MappedArgSetter(cx, obj, id, v, result);
2313   }
2314   MOZ_RELEASE_ASSERT(clasp == &UnmappedArgumentsObject::class_);
2315   return UnmappedArgSetter(cx, obj, id, v, result);
2316 }
2317 
MaybeReportUndeclaredVarAssignment(JSContext * cx,HandleId id)2318 static bool MaybeReportUndeclaredVarAssignment(JSContext* cx, HandleId id) {
2319   {
2320     jsbytecode* pc;
2321     JSScript* script =
2322         cx->currentScript(&pc, JSContext::AllowCrossRealm::Allow);
2323     if (!script) {
2324       return true;
2325     }
2326 
2327     if (!IsStrictSetPC(pc)) {
2328       return true;
2329     }
2330   }
2331 
2332   UniqueChars bytes =
2333       IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier);
2334   if (!bytes) {
2335     return false;
2336   }
2337   JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNDECLARED_VAR,
2338                            bytes.get());
2339   return false;
2340 }
2341 
2342 /*
2343  * Finish assignment to a shapeful data property of a native object obj. This
2344  * conforms to no standard and there is a lot of legacy baggage here.
2345  */
NativeSetExistingDataProperty(JSContext * cx,HandleNativeObject obj,HandleId id,PropertyInfo prop,HandleValue v,ObjectOpResult & result)2346 static bool NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj,
2347                                           HandleId id, PropertyInfo prop,
2348                                           HandleValue v,
2349                                           ObjectOpResult& result) {
2350   MOZ_ASSERT(obj->is<NativeObject>());
2351   MOZ_ASSERT(prop.isDataDescriptor());
2352 
2353   if (prop.isDataProperty()) {
2354     // The common path. Standard data property.
2355     obj->setSlot(prop.slot(), v);
2356     return result.succeed();
2357   }
2358 
2359   MOZ_ASSERT(prop.isCustomDataProperty());
2360   MOZ_ASSERT(!obj->is<WithEnvironmentObject>());  // See bug 1128681.
2361 
2362   return SetCustomDataProperty(cx, obj, id, v, result);
2363 }
2364 
2365 /*
2366  * When a [[Set]] operation finds no existing property with the given id
2367  * or finds a writable data property on the prototype chain, we end up here.
2368  * Finish the [[Set]] by defining a new property on receiver.
2369  *
2370  * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.b-f, but it
2371  * is really old code and there are a few barnacles.
2372  */
SetPropertyByDefining(JSContext * cx,HandleId id,HandleValue v,HandleValue receiverValue,ObjectOpResult & result)2373 bool js::SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v,
2374                                HandleValue receiverValue,
2375                                ObjectOpResult& result) {
2376   // Step 5.b.
2377   if (!receiverValue.isObject()) {
2378     return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
2379   }
2380   RootedObject receiver(cx, &receiverValue.toObject());
2381 
2382   bool existing;
2383   {
2384     // Steps 5.c-d.
2385     Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
2386     if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc)) {
2387       return false;
2388     }
2389 
2390     existing = desc.isSome();
2391 
2392     // Step 5.e.
2393     if (existing) {
2394       // Step 5.e.i.
2395       if (desc->isAccessorDescriptor()) {
2396         return result.fail(JSMSG_OVERWRITING_ACCESSOR);
2397       }
2398 
2399       // Step 5.e.ii.
2400       if (!desc->writable()) {
2401         return result.fail(JSMSG_READ_ONLY);
2402       }
2403     }
2404   }
2405 
2406   // Purge the property cache of now-shadowed id in receiver's environment
2407   // chain.
2408   if (!ReshapeForShadowedProp(cx, receiver, id)) {
2409     return false;
2410   }
2411 
2412   // Steps 5.e.iii-iv. and 5.f.i. Define the new data property.
2413   Rooted<PropertyDescriptor> desc(cx);
2414   if (existing) {
2415     desc = PropertyDescriptor::Empty();
2416     desc.setValue(v);
2417   } else {
2418     desc = PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
2419                                         JS::PropertyAttribute::Enumerable,
2420                                         JS::PropertyAttribute::Writable});
2421   }
2422   return DefineProperty(cx, receiver, id, desc, result);
2423 }
2424 
2425 // When setting |id| for |receiver| and |obj| has no property for id, continue
2426 // the search up the prototype chain.
SetPropertyOnProto(JSContext * cx,HandleObject obj,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result)2427 bool js::SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id,
2428                             HandleValue v, HandleValue receiver,
2429                             ObjectOpResult& result) {
2430   MOZ_ASSERT(!obj->is<ProxyObject>());
2431 
2432   RootedObject proto(cx, obj->staticPrototype());
2433   if (proto) {
2434     return SetProperty(cx, proto, id, v, receiver, result);
2435   }
2436 
2437   return SetPropertyByDefining(cx, id, v, receiver, result);
2438 }
2439 
2440 /*
2441  * Implement "the rest of" assignment to a property when no property
2442  * receiver[id] was found anywhere on the prototype chain.
2443  *
2444  * FIXME: This should be updated to follow ES6 draft rev 28, section 9.1.9,
2445  * steps 4.d.i and 5.
2446  */
2447 template <QualifiedBool IsQualified>
SetNonexistentProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result)2448 static bool SetNonexistentProperty(JSContext* cx, HandleNativeObject obj,
2449                                    HandleId id, HandleValue v,
2450                                    HandleValue receiver,
2451                                    ObjectOpResult& result) {
2452   if (!IsQualified && receiver.isObject() &&
2453       receiver.toObject().isUnqualifiedVarObj()) {
2454     if (!MaybeReportUndeclaredVarAssignment(cx, id)) {
2455       return false;
2456     }
2457   }
2458 
2459   // Pure optimization for the common case. There's no point performing the
2460   // lookup in step 5.c again, as our caller just did it for us.
2461   if (IsQualified && receiver.isObject() && obj == &receiver.toObject()) {
2462     // Ensure that a custom GetOwnPropertyOp, if present, doesn't
2463     // introduce additional properties which weren't previously found by
2464     // LookupOwnProperty.
2465 #ifdef DEBUG
2466     if (GetOwnPropertyOp op = obj->getOpsGetOwnPropertyDescriptor()) {
2467       Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
2468       if (!op(cx, obj, id, &desc)) {
2469         return false;
2470       }
2471       MOZ_ASSERT(desc.isNothing());
2472     }
2473 #endif
2474 
2475     // Step 5.e. Define the new data property.
2476     if (DefinePropertyOp op = obj->getOpsDefineProperty()) {
2477       // Purge the property cache of now-shadowed id in receiver's environment
2478       // chain.
2479       if (!ReshapeForShadowedProp(cx, obj, id)) {
2480         return false;
2481       }
2482 
2483       MOZ_ASSERT(!cx->isHelperThreadContext());
2484 
2485       Rooted<PropertyDescriptor> desc(
2486           cx, PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
2487                                            JS::PropertyAttribute::Enumerable,
2488                                            JS::PropertyAttribute::Writable}));
2489       return op(cx, obj, id, desc, result);
2490     }
2491 
2492     return DefineNonexistentProperty(cx, obj, id, v, result);
2493   }
2494 
2495   return SetPropertyByDefining(cx, id, v, receiver, result);
2496 }
2497 
2498 // Set an existing own property obj[index] that's a dense element.
SetDenseElement(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue v,ObjectOpResult & result)2499 static bool SetDenseElement(JSContext* cx, HandleNativeObject obj,
2500                             uint32_t index, HandleValue v,
2501                             ObjectOpResult& result) {
2502   MOZ_ASSERT(!obj->is<TypedArrayObject>());
2503   MOZ_ASSERT(obj->containsDenseElement(index));
2504 
2505   obj->setDenseElement(index, v);
2506   return result.succeed();
2507 }
2508 
2509 /*
2510  * Finish the assignment `receiver[id] = v` when an existing property (shape)
2511  * has been found on a native object (pobj). This implements ES6 draft rev 32
2512  * (2015 Feb 2) 9.1.9 steps 5 and 6.
2513  *
2514  * It is necessary to pass both id and shape because shape could be an implicit
2515  * dense or typed array element (i.e. not actually a pointer to a Shape).
2516  */
SetExistingProperty(JSContext * cx,HandleId id,HandleValue v,HandleValue receiver,HandleNativeObject pobj,const PropertyResult & prop,ObjectOpResult & result)2517 static bool SetExistingProperty(JSContext* cx, HandleId id, HandleValue v,
2518                                 HandleValue receiver, HandleNativeObject pobj,
2519                                 const PropertyResult& prop,
2520                                 ObjectOpResult& result) {
2521   // Step 5 for dense elements.
2522   if (prop.isDenseElement() || prop.isTypedArrayElement()) {
2523     // Step 5.a.
2524     if (pobj->denseElementsAreFrozen()) {
2525       return result.fail(JSMSG_READ_ONLY);
2526     }
2527 
2528     // Pure optimization for the common case:
2529     if (receiver.isObject() && pobj == &receiver.toObject()) {
2530       if (prop.isTypedArrayElement()) {
2531         Rooted<TypedArrayObject*> tobj(cx, &pobj->as<TypedArrayObject>());
2532         size_t idx = prop.typedArrayElementIndex();
2533         return SetTypedArrayElement(cx, tobj, idx, v, result);
2534       }
2535 
2536       return SetDenseElement(cx, pobj, prop.denseElementIndex(), v, result);
2537     }
2538 
2539     // Steps 5.b-f.
2540     return SetPropertyByDefining(cx, id, v, receiver, result);
2541   }
2542 
2543   // Step 5 for all other properties.
2544   PropertyInfo propInfo = prop.propertyInfo();
2545   if (propInfo.isDataDescriptor()) {
2546     // Step 5.a.
2547     if (!propInfo.writable()) {
2548       return result.fail(JSMSG_READ_ONLY);
2549     }
2550 
2551     // steps 5.c-f.
2552     if (receiver.isObject() && pobj == &receiver.toObject()) {
2553       // Pure optimization for the common case. There's no point performing
2554       // the lookup in step 5.c again, as our caller just did it for us. The
2555       // result is |shapeProp|.
2556 
2557       // Steps 5.e.i-ii.
2558       return NativeSetExistingDataProperty(cx, pobj, id, propInfo, v, result);
2559     }
2560 
2561     // Shadow pobj[id] by defining a new data property receiver[id].
2562     // Delegate everything to SetPropertyByDefining.
2563     return SetPropertyByDefining(cx, id, v, receiver, result);
2564   }
2565 
2566   // Steps 6-11.
2567   MOZ_ASSERT(propInfo.isAccessorProperty());
2568 
2569   JSObject* setterObject = pobj->getSetter(propInfo);
2570   if (!setterObject) {
2571     return result.fail(JSMSG_GETTER_ONLY);
2572   }
2573 
2574   RootedValue setter(cx, ObjectValue(*setterObject));
2575   if (!js::CallSetter(cx, receiver, setter, v)) {
2576     return false;
2577   }
2578 
2579   return result.succeed();
2580 }
2581 
2582 template <QualifiedBool IsQualified>
NativeSetProperty(JSContext * cx,HandleNativeObject obj,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result)2583 bool js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
2584                            HandleValue v, HandleValue receiver,
2585                            ObjectOpResult& result) {
2586   // Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal
2587   // method for ordinary objects. We substitute our own names for these names
2588   // used in the spec: O -> pobj, P -> id, ownDesc -> shape.
2589   PropertyResult prop;
2590   RootedNativeObject pobj(cx, obj);
2591 
2592   // This loop isn't explicit in the spec algorithm. See the comment on step
2593   // 4.c.i below. (There's a very similar loop in the NativeGetProperty
2594   // implementation, but unfortunately not similar enough to common up.)
2595   //
2596   // We're intentionally not spec-compliant for TypedArrays:
2597   // When |pobj| is a TypedArray and |id| is a TypedArray index, we should
2598   // ignore |receiver| and instead always try to set the property on |pobj|.
2599   // Bug 1502889 showed that this behavior isn't web-compatible. This issue is
2600   // also reported at <https://github.com/tc39/ecma262/issues/1541>.
2601   for (;;) {
2602     // Steps 2-3.
2603     if (!NativeLookupOwnPropertyInline<CanGC>(cx, pobj, id, &prop)) {
2604       return false;
2605     }
2606 
2607     if (prop.isFound()) {
2608       // Steps 5-6.
2609       return SetExistingProperty(cx, id, v, receiver, pobj, prop, result);
2610     }
2611 
2612     // Steps 4.a-b.
2613     // As a side-effect of NativeLookupOwnPropertyInline, we may determine that
2614     // a property is not found and the proto chain should not be searched. This
2615     // can occur for:
2616     //  - Out-of-range numeric properties of a TypedArrayObject
2617     //  - Recursive resolve hooks (which is expected when they try to set the
2618     //    property being resolved).
2619     JSObject* proto = pobj->staticPrototype();
2620     if (!proto || prop.shouldIgnoreProtoChain()) {
2621       // Step 4.d.i (and step 5).
2622       return SetNonexistentProperty<IsQualified>(cx, obj, id, v, receiver,
2623                                                  result);
2624     }
2625 
2626     // Step 4.c.i. If the prototype is also native, this step is a
2627     // recursive tail call, and we don't need to go through all the
2628     // plumbing of SetProperty; the top of the loop is where we're going to
2629     // end up anyway. But if pobj is non-native, that optimization would be
2630     // incorrect.
2631     if (!proto->is<NativeObject>()) {
2632       // Unqualified assignments are not specified to go through [[Set]]
2633       // at all, but they do go through this function. So check for
2634       // unqualified assignment to a nonexistent global (a strict error).
2635       RootedObject protoRoot(cx, proto);
2636       if (!IsQualified) {
2637         bool found;
2638         if (!HasProperty(cx, protoRoot, id, &found)) {
2639           return false;
2640         }
2641         if (!found) {
2642           return SetNonexistentProperty<IsQualified>(cx, obj, id, v, receiver,
2643                                                      result);
2644         }
2645       }
2646 
2647       return SetProperty(cx, protoRoot, id, v, receiver, result);
2648     }
2649     pobj = &proto->as<NativeObject>();
2650   }
2651 }
2652 
2653 template bool js::NativeSetProperty<Qualified>(JSContext* cx,
2654                                                HandleNativeObject obj,
2655                                                HandleId id, HandleValue value,
2656                                                HandleValue receiver,
2657                                                ObjectOpResult& result);
2658 
2659 template bool js::NativeSetProperty<Unqualified>(JSContext* cx,
2660                                                  HandleNativeObject obj,
2661                                                  HandleId id, HandleValue value,
2662                                                  HandleValue receiver,
2663                                                  ObjectOpResult& result);
2664 
NativeSetElement(JSContext * cx,HandleNativeObject obj,uint32_t index,HandleValue v,HandleValue receiver,ObjectOpResult & result)2665 bool js::NativeSetElement(JSContext* cx, HandleNativeObject obj, uint32_t index,
2666                           HandleValue v, HandleValue receiver,
2667                           ObjectOpResult& result) {
2668   RootedId id(cx);
2669   if (!IndexToId(cx, index, &id)) {
2670     return false;
2671   }
2672   return NativeSetProperty<Qualified>(cx, obj, id, v, receiver, result);
2673 }
2674 
2675 /*** [[Delete]] *************************************************************/
2676 
CallJSDeletePropertyOp(JSContext * cx,JSDeletePropertyOp op,HandleObject receiver,HandleId id,ObjectOpResult & result)2677 static bool CallJSDeletePropertyOp(JSContext* cx, JSDeletePropertyOp op,
2678                                    HandleObject receiver, HandleId id,
2679                                    ObjectOpResult& result) {
2680   AutoCheckRecursionLimit recursion(cx);
2681   if (!recursion.check(cx)) {
2682     return false;
2683   }
2684 
2685   cx->check(receiver, id);
2686   if (op) {
2687     return op(cx, receiver, id, result);
2688   }
2689   return result.succeed();
2690 }
2691 
2692 // ES6 draft rev31 9.1.10 [[Delete]]
NativeDeleteProperty(JSContext * cx,HandleNativeObject obj,HandleId id,ObjectOpResult & result)2693 bool js::NativeDeleteProperty(JSContext* cx, HandleNativeObject obj,
2694                               HandleId id, ObjectOpResult& result) {
2695   // Steps 2-3.
2696   PropertyResult prop;
2697   if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
2698     return false;
2699   }
2700 
2701   // Step 4.
2702   if (prop.isNotFound()) {
2703     // If no property call the class's delProperty hook, passing succeeded
2704     // as the result parameter. This always succeeds when there is no hook.
2705     return CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj,
2706                                   id, result);
2707   }
2708 
2709   // Step 6. Non-configurable property.
2710   if (!GetPropertyAttributes(obj, prop).configurable()) {
2711     return result.failCantDelete();
2712   }
2713 
2714   // Typed array elements are configurable, but can't be deleted.
2715   if (prop.isTypedArrayElement()) {
2716     return result.failCantDelete();
2717   }
2718 
2719   if (!CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id,
2720                               result)) {
2721     return false;
2722   }
2723   if (!result) {
2724     return true;
2725   }
2726 
2727   // Step 5.
2728   if (prop.isDenseElement()) {
2729     obj->setDenseElementHole(prop.denseElementIndex());
2730   } else {
2731     if (!NativeObject::removeProperty(cx, obj, id)) {
2732       return false;
2733     }
2734   }
2735 
2736   return SuppressDeletedProperty(cx, obj, id);
2737 }
2738 
CopyDataPropertiesNative(JSContext * cx,HandlePlainObject target,HandleNativeObject from,Handle<PlainObject * > excludedItems,bool * optimized)2739 bool js::CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target,
2740                                   HandleNativeObject from,
2741                                   Handle<PlainObject*> excludedItems,
2742                                   bool* optimized) {
2743   MOZ_ASSERT(
2744       !target->isUsedAsPrototype(),
2745       "CopyDataPropertiesNative should only be called during object literal "
2746       "construction"
2747       "which precludes that |target| is the prototype of any other object");
2748 
2749   *optimized = false;
2750 
2751   // Don't use the fast path if |from| may have extra indexed or lazy
2752   // properties.
2753   if (from->getDenseInitializedLength() > 0 || from->isIndexed() ||
2754       from->is<TypedArrayObject>() || from->getClass()->getNewEnumerate() ||
2755       from->getClass()->getEnumerate()) {
2756     return true;
2757   }
2758 
2759   // Collect all enumerable data properties.
2760   Rooted<PropertyInfoWithKeyVector> props(cx, PropertyInfoWithKeyVector(cx));
2761 
2762   RootedShape fromShape(cx, from->shape());
2763   for (ShapePropertyIter<NoGC> iter(fromShape); !iter.done(); iter++) {
2764     jsid id = iter->key();
2765     MOZ_ASSERT(!JSID_IS_INT(id));
2766 
2767     if (!iter->enumerable()) {
2768       continue;
2769     }
2770     if (excludedItems && excludedItems->contains(cx, id)) {
2771       continue;
2772     }
2773 
2774     // Don't use the fast path if |from| contains non-data properties.
2775     //
2776     // This enables two optimizations:
2777     // 1. We don't need to handle the case when accessors modify |from|.
2778     // 2. String and symbol properties can be added in one go.
2779     if (!iter->isDataProperty()) {
2780       return true;
2781     }
2782 
2783     if (!props.append(*iter)) {
2784       return false;
2785     }
2786   }
2787 
2788   *optimized = true;
2789 
2790   // If |target| contains no own properties, we can directly call
2791   // AddDataPropertyNonPrototype.
2792   const bool targetHadNoOwnProperties = target->empty();
2793 
2794   RootedId key(cx);
2795   RootedValue value(cx);
2796   for (size_t i = props.length(); i > 0; i--) {
2797     PropertyInfoWithKey prop = props[i - 1];
2798     MOZ_ASSERT(prop.isDataProperty());
2799     MOZ_ASSERT(prop.enumerable());
2800 
2801     key = prop.key();
2802     MOZ_ASSERT(!JSID_IS_INT(key));
2803 
2804     MOZ_ASSERT(from->is<NativeObject>());
2805     MOZ_ASSERT(from->shape() == fromShape);
2806 
2807     value = from->getSlot(prop.slot());
2808     if (targetHadNoOwnProperties) {
2809       MOZ_ASSERT(!target->containsPure(key),
2810                  "didn't expect to find an existing property");
2811 
2812       if (!AddDataPropertyNonPrototype(cx, target, key, value)) {
2813         return false;
2814       }
2815     } else {
2816       if (!NativeDefineDataProperty(cx, target, key, value, JSPROP_ENUMERATE)) {
2817         return false;
2818       }
2819     }
2820   }
2821 
2822   return true;
2823 }
2824