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 #ifndef vm_NativeObject_inl_h
8 #define vm_NativeObject_inl_h
9 
10 #include "vm/NativeObject.h"
11 
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/Maybe.h"
14 
15 #include <type_traits>
16 
17 #include "gc/Allocator.h"
18 #include "gc/GCProbes.h"
19 #include "gc/MaybeRooted.h"
20 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
21 #include "js/Result.h"
22 #include "proxy/Proxy.h"
23 #include "vm/JSContext.h"
24 #include "vm/PropertyResult.h"
25 #include "vm/ProxyObject.h"
26 #include "vm/TypedArrayObject.h"
27 
28 #include "gc/Heap-inl.h"
29 #include "gc/Marking-inl.h"
30 #include "gc/ObjectKind-inl.h"
31 #include "vm/JSObject-inl.h"
32 #include "vm/Shape-inl.h"
33 
34 namespace js {
35 
numFixedSlotsMaybeForwarded()36 inline uint32_t NativeObject::numFixedSlotsMaybeForwarded() const {
37   return gc::MaybeForwarded(shape())->numFixedSlots();
38 }
39 
getPrivateMaybeForwarded()40 inline void* NativeObject::getPrivateMaybeForwarded() const {
41   MOZ_ASSERT(MaybeForwardedObjectClass(this)->hasPrivate());
42   uint32_t nfixed = numFixedSlotsMaybeForwarded();
43   HeapSlot* end = &fixedSlots()[nfixed];
44   return *reinterpret_cast<void**>(end);
45 }
46 
fixedData(size_t nslots)47 inline uint8_t* NativeObject::fixedData(size_t nslots) const {
48   mozilla::DebugOnly<const JSClass*> clasp =
49       gc::MaybeForwardedObjectClass(this);
50   MOZ_ASSERT(ClassCanHaveFixedData(clasp));
51   MOZ_ASSERT(nslots ==
52              numFixedSlotsMaybeForwarded() + (clasp->hasPrivate() ? 1 : 0));
53   return reinterpret_cast<uint8_t*>(&fixedSlots()[nslots]);
54 }
55 
initDenseElementHole(uint32_t index)56 inline void NativeObject::initDenseElementHole(uint32_t index) {
57   markDenseElementsNotPacked();
58   initDenseElementUnchecked(index, MagicValue(JS_ELEMENTS_HOLE));
59 }
60 
setDenseElementHole(uint32_t index)61 inline void NativeObject::setDenseElementHole(uint32_t index) {
62   markDenseElementsNotPacked();
63   setDenseElementUnchecked(index, MagicValue(JS_ELEMENTS_HOLE));
64 }
65 
removeDenseElementForSparseIndex(uint32_t index)66 inline void NativeObject::removeDenseElementForSparseIndex(uint32_t index) {
67   MOZ_ASSERT(containsPure(INT_TO_JSID(index)));
68   if (containsDenseElement(index)) {
69     setDenseElementHole(index);
70   }
71 }
72 
markDenseElementsNotPacked()73 inline void NativeObject::markDenseElementsNotPacked() {
74   MOZ_ASSERT(is<NativeObject>());
75   getElementsHeader()->markNonPacked();
76 }
77 
elementsRangePostWriteBarrier(uint32_t start,uint32_t count)78 inline void NativeObject::elementsRangePostWriteBarrier(uint32_t start,
79                                                         uint32_t count) {
80   if (!isTenured()) {
81     return;
82   }
83   for (size_t i = 0; i < count; i++) {
84     const Value& v = elements_[start + i];
85     if (v.isGCThing()) {
86       if (gc::StoreBuffer* sb = v.toGCThing()->storeBuffer()) {
87         sb->putSlot(this, HeapSlot::Element, unshiftedIndex(start + i),
88                     count - i);
89         return;
90       }
91     }
92   }
93 }
94 
copyDenseElements(uint32_t dstStart,const Value * src,uint32_t count)95 inline void NativeObject::copyDenseElements(uint32_t dstStart, const Value* src,
96                                             uint32_t count) {
97   MOZ_ASSERT(dstStart + count <= getDenseCapacity());
98   MOZ_ASSERT(isExtensible());
99   MOZ_ASSERT_IF(count > 0, src != nullptr);
100 #ifdef DEBUG
101   for (uint32_t i = 0; i < count; ++i) {
102     checkStoredValue(src[i]);
103   }
104 #endif
105   if (count == 0) {
106     return;
107   }
108   if (zone()->needsIncrementalBarrier()) {
109     uint32_t numShifted = getElementsHeader()->numShiftedElements();
110     for (uint32_t i = 0; i < count; ++i) {
111       elements_[dstStart + i].set(this, HeapSlot::Element,
112                                   dstStart + i + numShifted, src[i]);
113     }
114   } else {
115     memcpy(reinterpret_cast<Value*>(&elements_[dstStart]), src,
116            count * sizeof(Value));
117     elementsRangePostWriteBarrier(dstStart, count);
118   }
119 }
120 
initDenseElements(NativeObject * src,uint32_t srcStart,uint32_t count)121 inline void NativeObject::initDenseElements(NativeObject* src,
122                                             uint32_t srcStart, uint32_t count) {
123   MOZ_ASSERT(src->getDenseInitializedLength() >= srcStart + count);
124 
125   const Value* vp = src->getDenseElements() + srcStart;
126 
127   if (!src->denseElementsArePacked()) {
128     // Mark non-packed if we're copying holes or if there are too many elements
129     // to check this efficiently.
130     static constexpr uint32_t MaxCountForPackedCheck = 30;
131     if (count > MaxCountForPackedCheck) {
132       markDenseElementsNotPacked();
133     } else {
134       for (uint32_t i = 0; i < count; i++) {
135         if (vp[i].isMagic(JS_ELEMENTS_HOLE)) {
136           markDenseElementsNotPacked();
137           break;
138         }
139       }
140     }
141   }
142 
143   initDenseElements(vp, count);
144 }
145 
initDenseElements(const Value * src,uint32_t count)146 inline void NativeObject::initDenseElements(const Value* src, uint32_t count) {
147   MOZ_ASSERT(getDenseInitializedLength() == 0);
148   MOZ_ASSERT(count <= getDenseCapacity());
149   MOZ_ASSERT(isExtensible());
150 
151   setDenseInitializedLength(count);
152 
153 #ifdef DEBUG
154   for (uint32_t i = 0; i < count; ++i) {
155     checkStoredValue(src[i]);
156   }
157 #endif
158 
159   memcpy(reinterpret_cast<Value*>(elements_), src, count * sizeof(Value));
160   elementsRangePostWriteBarrier(0, count);
161 }
162 
163 template <typename Iter>
initDenseElementsFromRange(JSContext * cx,Iter begin,Iter end)164 inline bool NativeObject::initDenseElementsFromRange(JSContext* cx, Iter begin,
165                                                      Iter end) {
166   // This method populates the elements of a particular Array that's an
167   // internal implementation detail of GeneratorObject. Failing any of the
168   // following means the Array has escaped and/or been mistreated.
169   MOZ_ASSERT(isExtensible());
170   MOZ_ASSERT(!isIndexed());
171   MOZ_ASSERT(is<ArrayObject>());
172   MOZ_ASSERT(as<ArrayObject>().lengthIsWritable());
173   MOZ_ASSERT(!denseElementsAreFrozen());
174   MOZ_ASSERT(getElementsHeader()->numShiftedElements() == 0);
175 
176   MOZ_ASSERT(getDenseInitializedLength() == 0);
177 
178   auto size = end - begin;
179   uint32_t count = uint32_t(size);
180   MOZ_ASSERT(count <= uint32_t(INT32_MAX));
181   if (count > getDenseCapacity()) {
182     if (!growElements(cx, count)) {
183       return false;
184     }
185   }
186 
187   HeapSlot* sp = elements_;
188   size_t slot = 0;
189   for (; begin != end; sp++, begin++) {
190     Value v = *begin;
191 #ifdef DEBUG
192     checkStoredValue(v);
193 #endif
194     sp->init(this, HeapSlot::Element, slot++, v);
195   }
196   MOZ_ASSERT(slot == count);
197 
198   getElementsHeader()->initializedLength = count;
199   as<ArrayObject>().setLength(count);
200   return true;
201 }
202 
tryShiftDenseElements(uint32_t count)203 inline bool NativeObject::tryShiftDenseElements(uint32_t count) {
204   MOZ_ASSERT(isExtensible());
205 
206   ObjectElements* header = getElementsHeader();
207   if (header->initializedLength == count ||
208       count > ObjectElements::MaxShiftedElements ||
209       header->hasNonwritableArrayLength()) {
210     return false;
211   }
212 
213   shiftDenseElementsUnchecked(count);
214   return true;
215 }
216 
shiftDenseElementsUnchecked(uint32_t count)217 inline void NativeObject::shiftDenseElementsUnchecked(uint32_t count) {
218   MOZ_ASSERT(isExtensible());
219 
220   ObjectElements* header = getElementsHeader();
221   MOZ_ASSERT(count > 0);
222   MOZ_ASSERT(count < header->initializedLength);
223 
224   if (MOZ_UNLIKELY(header->numShiftedElements() + count >
225                    ObjectElements::MaxShiftedElements)) {
226     moveShiftedElements();
227     header = getElementsHeader();
228   }
229 
230   prepareElementRangeForOverwrite(0, count);
231   header->addShiftedElements(count);
232 
233   elements_ += count;
234   ObjectElements* newHeader = getElementsHeader();
235   memmove(newHeader, header, sizeof(ObjectElements));
236 }
237 
moveDenseElements(uint32_t dstStart,uint32_t srcStart,uint32_t count)238 inline void NativeObject::moveDenseElements(uint32_t dstStart,
239                                             uint32_t srcStart, uint32_t count) {
240   MOZ_ASSERT(dstStart + count <= getDenseCapacity());
241   MOZ_ASSERT(srcStart + count <= getDenseInitializedLength());
242   MOZ_ASSERT(isExtensible());
243 
244   /*
245    * Using memmove here would skip write barriers. Also, we need to consider
246    * an array containing [A, B, C], in the following situation:
247    *
248    * 1. Incremental GC marks slot 0 of array (i.e., A), then returns to JS code.
249    * 2. JS code moves slots 1..2 into slots 0..1, so it contains [B, C, C].
250    * 3. Incremental GC finishes by marking slots 1 and 2 (i.e., C).
251    *
252    * Since normal marking never happens on B, it is very important that the
253    * write barrier is invoked here on B, despite the fact that it exists in
254    * the array before and after the move.
255    */
256   if (zone()->needsIncrementalBarrier()) {
257     uint32_t numShifted = getElementsHeader()->numShiftedElements();
258     if (dstStart < srcStart) {
259       HeapSlot* dst = elements_ + dstStart;
260       HeapSlot* src = elements_ + srcStart;
261       for (uint32_t i = 0; i < count; i++, dst++, src++) {
262         dst->set(this, HeapSlot::Element, dst - elements_ + numShifted, *src);
263       }
264     } else {
265       HeapSlot* dst = elements_ + dstStart + count - 1;
266       HeapSlot* src = elements_ + srcStart + count - 1;
267       for (uint32_t i = 0; i < count; i++, dst--, src--) {
268         dst->set(this, HeapSlot::Element, dst - elements_ + numShifted, *src);
269       }
270     }
271   } else {
272     memmove(elements_ + dstStart, elements_ + srcStart,
273             count * sizeof(HeapSlot));
274     elementsRangePostWriteBarrier(dstStart, count);
275   }
276 }
277 
reverseDenseElementsNoPreBarrier(uint32_t length)278 inline void NativeObject::reverseDenseElementsNoPreBarrier(uint32_t length) {
279   MOZ_ASSERT(!zone()->needsIncrementalBarrier());
280 
281   MOZ_ASSERT(isExtensible());
282 
283   MOZ_ASSERT(length > 1);
284   MOZ_ASSERT(length <= getDenseInitializedLength());
285 
286   Value* valLo = reinterpret_cast<Value*>(elements_);
287   Value* valHi = valLo + (length - 1);
288   MOZ_ASSERT(valLo < valHi);
289 
290   do {
291     Value origLo = *valLo;
292     *valLo = *valHi;
293     *valHi = origLo;
294     ++valLo;
295     --valHi;
296   } while (valLo < valHi);
297 
298   elementsRangePostWriteBarrier(0, length);
299 }
300 
ensureDenseInitializedLength(uint32_t index,uint32_t extra)301 inline void NativeObject::ensureDenseInitializedLength(uint32_t index,
302                                                        uint32_t extra) {
303   // Ensure that the array's contents have been initialized up to index, and
304   // mark the elements through 'index + extra' as initialized in preparation
305   // for a write.
306 
307   MOZ_ASSERT(!denseElementsAreFrozen());
308   MOZ_ASSERT(isExtensible() || (containsDenseElement(index) && extra == 1));
309   MOZ_ASSERT(index + extra <= getDenseCapacity());
310 
311   uint32_t initlen = getDenseInitializedLength();
312   if (index + extra <= initlen) {
313     return;
314   }
315 
316   MOZ_ASSERT(isExtensible());
317 
318   if (index > initlen) {
319     markDenseElementsNotPacked();
320   }
321 
322   uint32_t numShifted = getElementsHeader()->numShiftedElements();
323   size_t offset = initlen;
324   for (HeapSlot* sp = elements_ + initlen; sp != elements_ + (index + extra);
325        sp++, offset++) {
326     sp->init(this, HeapSlot::Element, offset + numShifted,
327              MagicValue(JS_ELEMENTS_HOLE));
328   }
329 
330   getElementsHeader()->initializedLength = index + extra;
331 }
332 
extendDenseElements(JSContext * cx,uint32_t requiredCapacity,uint32_t extra)333 DenseElementResult NativeObject::extendDenseElements(JSContext* cx,
334                                                      uint32_t requiredCapacity,
335                                                      uint32_t extra) {
336   MOZ_ASSERT(isExtensible());
337 
338   /*
339    * Don't grow elements for objects which already have sparse indexes.
340    * This avoids needing to count non-hole elements in willBeSparseElements
341    * every time a new index is added.
342    */
343   if (isIndexed()) {
344     return DenseElementResult::Incomplete;
345   }
346 
347   /*
348    * We use the extra argument also as a hint about number of non-hole
349    * elements to be inserted.
350    */
351   if (requiredCapacity > MIN_SPARSE_INDEX &&
352       willBeSparseElements(requiredCapacity, extra)) {
353     return DenseElementResult::Incomplete;
354   }
355 
356   if (!growElements(cx, requiredCapacity)) {
357     return DenseElementResult::Failure;
358   }
359 
360   return DenseElementResult::Success;
361 }
362 
ensureDenseElements(JSContext * cx,uint32_t index,uint32_t extra)363 inline DenseElementResult NativeObject::ensureDenseElements(JSContext* cx,
364                                                             uint32_t index,
365                                                             uint32_t extra) {
366   MOZ_ASSERT(is<NativeObject>());
367   MOZ_ASSERT(isExtensible() || (containsDenseElement(index) && extra == 1));
368 
369   uint32_t requiredCapacity;
370   if (extra == 1) {
371     /* Optimize for the common case. */
372     if (index < getDenseCapacity()) {
373       ensureDenseInitializedLength(index, 1);
374       return DenseElementResult::Success;
375     }
376     requiredCapacity = index + 1;
377     if (requiredCapacity == 0) {
378       /* Overflow. */
379       return DenseElementResult::Incomplete;
380     }
381   } else {
382     requiredCapacity = index + extra;
383     if (requiredCapacity < index) {
384       /* Overflow. */
385       return DenseElementResult::Incomplete;
386     }
387     if (requiredCapacity <= getDenseCapacity()) {
388       ensureDenseInitializedLength(index, extra);
389       return DenseElementResult::Success;
390     }
391   }
392 
393   DenseElementResult result = extendDenseElements(cx, requiredCapacity, extra);
394   if (result != DenseElementResult::Success) {
395     return result;
396   }
397 
398   ensureDenseInitializedLength(index, extra);
399   return DenseElementResult::Success;
400 }
401 
setOrExtendDenseElements(JSContext * cx,uint32_t start,const Value * vp,uint32_t count)402 inline DenseElementResult NativeObject::setOrExtendDenseElements(
403     JSContext* cx, uint32_t start, const Value* vp, uint32_t count) {
404   if (!isExtensible()) {
405     return DenseElementResult::Incomplete;
406   }
407 
408   if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable() &&
409       start + count >= as<ArrayObject>().length()) {
410     return DenseElementResult::Incomplete;
411   }
412 
413   DenseElementResult result = ensureDenseElements(cx, start, count);
414   if (result != DenseElementResult::Success) {
415     return result;
416   }
417 
418   if (is<ArrayObject>() && start + count >= as<ArrayObject>().length()) {
419     as<ArrayObject>().setLength(start + count);
420   }
421 
422   copyDenseElements(start, vp, count);
423   return DenseElementResult::Success;
424 }
425 
isInWholeCellBuffer()426 inline bool NativeObject::isInWholeCellBuffer() const {
427   const gc::TenuredCell* cell = &asTenured();
428   gc::ArenaCellSet* cells = cell->arena()->bufferedCells();
429   return cells && cells->hasCell(cell);
430 }
431 
create(JSContext * cx,js::gc::AllocKind kind,js::gc::InitialHeap heap,js::HandleShape shape,js::gc::AllocSite * site)432 /* static */ inline JS::Result<NativeObject*, JS::OOM> NativeObject::create(
433     JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
434     js::HandleShape shape, js::gc::AllocSite* site /* = nullptr */) {
435   debugCheckNewObject(shape, kind, heap);
436 
437   const JSClass* clasp = shape->getObjectClass();
438   MOZ_ASSERT(clasp->isNativeObject());
439   MOZ_ASSERT(!clasp->isJSFunction(), "should use JSFunction::create");
440 
441   size_t nDynamicSlots =
442       calculateDynamicSlots(shape->numFixedSlots(), shape->slotSpan(), clasp);
443 
444   JSObject* obj =
445       js::AllocateObject(cx, kind, nDynamicSlots, heap, clasp, site);
446   if (!obj) {
447     return cx->alreadyReportedOOM();
448   }
449 
450   NativeObject* nobj = static_cast<NativeObject*>(obj);
451   nobj->initShape(shape);
452   // NOTE: Dynamic slots are created internally by Allocate<JSObject>.
453   if (!nDynamicSlots) {
454     nobj->initEmptyDynamicSlots();
455   }
456   nobj->setEmptyElements();
457 
458   if (clasp->hasPrivate()) {
459     nobj->initPrivate(nullptr);
460   }
461 
462   if (size_t span = shape->slotSpan()) {
463     nobj->initializeSlotRange(0, span);
464   }
465 
466   if (clasp->shouldDelayMetadataBuilder()) {
467     cx->realm()->setObjectPendingMetadata(cx, nobj);
468   } else {
469     nobj = SetNewObjectMetadata(cx, nobj);
470   }
471 
472   js::gc::gcprobes::CreateObject(nobj);
473 
474   return nobj;
475 }
476 
updateSlotsForSpan(JSContext * cx,size_t oldSpan,size_t newSpan)477 MOZ_ALWAYS_INLINE bool NativeObject::updateSlotsForSpan(JSContext* cx,
478                                                         size_t oldSpan,
479                                                         size_t newSpan) {
480   MOZ_ASSERT(oldSpan != newSpan);
481 
482   size_t oldCapacity = numDynamicSlots();
483   size_t newCapacity =
484       calculateDynamicSlots(numFixedSlots(), newSpan, getClass());
485 
486   if (oldSpan < newSpan) {
487     if (oldCapacity < newCapacity && !growSlots(cx, oldCapacity, newCapacity)) {
488       return false;
489     }
490 
491     if (newSpan == oldSpan + 1) {
492       initSlotUnchecked(oldSpan, UndefinedValue());
493     } else {
494       initializeSlotRange(oldSpan, newSpan);
495     }
496   } else {
497     /* Trigger write barriers on the old slots before reallocating. */
498     prepareSlotRangeForOverwrite(newSpan, oldSpan);
499     invalidateSlotRange(newSpan, oldSpan);
500 
501     if (oldCapacity > newCapacity) {
502       shrinkSlots(cx, oldCapacity, newCapacity);
503     }
504   }
505 
506   return true;
507 }
508 
initEmptyDynamicSlots()509 MOZ_ALWAYS_INLINE void NativeObject::initEmptyDynamicSlots() {
510   setEmptyDynamicSlots(0);
511 }
512 
setDictionaryModeSlotSpan(uint32_t span)513 MOZ_ALWAYS_INLINE void NativeObject::setDictionaryModeSlotSpan(uint32_t span) {
514   MOZ_ASSERT(inDictionaryMode());
515 
516   if (!hasDynamicSlots()) {
517     setEmptyDynamicSlots(span);
518     return;
519   }
520 
521   getSlotsHeader()->setDictionarySlotSpan(span);
522 }
523 
setEmptyDynamicSlots(uint32_t dictionarySlotSpan)524 MOZ_ALWAYS_INLINE void NativeObject::setEmptyDynamicSlots(
525     uint32_t dictionarySlotSpan) {
526   MOZ_ASSERT_IF(!inDictionaryMode(), dictionarySlotSpan == 0);
527   MOZ_ASSERT(dictionarySlotSpan <= MAX_FIXED_SLOTS);
528   slots_ = emptyObjectSlotsForDictionaryObject[dictionarySlotSpan];
529   MOZ_ASSERT(getSlotsHeader()->capacity() == 0);
530   MOZ_ASSERT(getSlotsHeader()->dictionarySlotSpan() == dictionarySlotSpan);
531 }
532 
setShapeAndUpdateSlots(JSContext * cx,Shape * newShape)533 MOZ_ALWAYS_INLINE bool NativeObject::setShapeAndUpdateSlots(JSContext* cx,
534                                                             Shape* newShape) {
535   MOZ_ASSERT(!inDictionaryMode());
536   MOZ_ASSERT(!newShape->isDictionary());
537   MOZ_ASSERT(newShape->zone() == zone());
538   MOZ_ASSERT(newShape->numFixedSlots() == numFixedSlots());
539   MOZ_ASSERT(newShape->getObjectClass() == getClass());
540 
541   size_t oldSpan = shape()->slotSpan();
542   size_t newSpan = newShape->slotSpan();
543 
544   if (oldSpan == newSpan) {
545     setShape(newShape);
546     return true;
547   }
548 
549   if (MOZ_UNLIKELY(!updateSlotsForSpan(cx, oldSpan, newSpan))) {
550     return false;
551   }
552 
553   setShape(newShape);
554   return true;
555 }
556 
setShapeAndUpdateSlotsForNewSlot(JSContext * cx,Shape * newShape,uint32_t slot)557 MOZ_ALWAYS_INLINE bool NativeObject::setShapeAndUpdateSlotsForNewSlot(
558     JSContext* cx, Shape* newShape, uint32_t slot) {
559   MOZ_ASSERT(!inDictionaryMode());
560   MOZ_ASSERT(!newShape->isDictionary());
561   MOZ_ASSERT(newShape->zone() == zone());
562   MOZ_ASSERT(newShape->numFixedSlots() == numFixedSlots());
563 
564   MOZ_ASSERT(newShape->base() == shape()->base());
565   MOZ_ASSERT(newShape->slotSpan() == shape()->slotSpan() + 1);
566   MOZ_ASSERT(newShape->slotSpan() == slot + 1);
567 
568   if (MOZ_UNLIKELY(!updateSlotsForSpan(cx, slot, slot + 1))) {
569     return false;
570   }
571 
572   setShape(newShape);
573   return true;
574 }
575 
allocKindForTenure()576 inline js::gc::AllocKind NativeObject::allocKindForTenure() const {
577   using namespace js::gc;
578   AllocKind kind = GetGCObjectFixedSlotsKind(numFixedSlots());
579   MOZ_ASSERT(!IsBackgroundFinalized(kind));
580   if (!CanChangeToBackgroundAllocKind(kind, getClass())) {
581     return kind;
582   }
583   return ForegroundToBackgroundAllocKind(kind);
584 }
585 
global()586 inline js::GlobalObject& NativeObject::global() const { return nonCCWGlobal(); }
587 
denseElementsHaveMaybeInIterationFlag()588 inline bool NativeObject::denseElementsHaveMaybeInIterationFlag() {
589   if (!getElementsHeader()->maybeInIteration()) {
590     AssertDenseElementsNotIterated(this);
591     return false;
592   }
593   return true;
594 }
595 
denseElementsMaybeInIteration()596 inline bool NativeObject::denseElementsMaybeInIteration() {
597   if (!denseElementsHaveMaybeInIterationFlag()) {
598     return false;
599   }
600   return ObjectRealm::get(this).objectMaybeInIteration(this);
601 }
602 
603 /*
604  * Call obj's resolve hook.
605  *
606  * cx and id are the parameters initially passed to the ongoing lookup;
607  * propp and recursedp are its out parameters.
608  *
609  * There are four possible outcomes:
610  *
611  *  - On failure, report an error or exception and return false.
612  *
613  *  - If we are already resolving a property of obj, call setRecursiveResolve on
614  *    propp and return true.
615  *
616  *  - If the resolve hook finds or defines the sought property, set propp
617  *    appropriately, and return true.
618  *
619  *  - Otherwise no property was resolved. Set propp to NotFound and return true.
620  */
CallResolveOp(JSContext * cx,HandleNativeObject obj,HandleId id,PropertyResult * propp)621 static MOZ_ALWAYS_INLINE bool CallResolveOp(JSContext* cx,
622                                             HandleNativeObject obj, HandleId id,
623                                             PropertyResult* propp) {
624   MOZ_ASSERT(!cx->isHelperThreadContext());
625 
626   // Avoid recursion on (obj, id) already being resolved on cx.
627   AutoResolving resolving(cx, obj, id);
628   if (resolving.alreadyStarted()) {
629     // Already resolving id in obj, suppress recursion.
630     propp->setRecursiveResolve();
631     return true;
632   }
633 
634   bool resolved = false;
635   AutoRealm ar(cx, obj);
636   if (!obj->getClass()->getResolve()(cx, obj, id, &resolved)) {
637     return false;
638   }
639 
640   if (!resolved) {
641     propp->setNotFound();
642     return true;
643   }
644 
645   // Assert the mayResolve hook, if there is one, returns true for this
646   // property.
647   MOZ_ASSERT_IF(obj->getClass()->getMayResolve(),
648                 obj->getClass()->getMayResolve()(cx->names(), id, obj));
649 
650   if (JSID_IS_INT(id)) {
651     uint32_t index = JSID_TO_INT(id);
652     if (obj->containsDenseElement(index)) {
653       propp->setDenseElement(index);
654       return true;
655     }
656   }
657 
658   MOZ_ASSERT(!obj->is<TypedArrayObject>());
659 
660   mozilla::Maybe<PropertyInfo> prop = obj->lookup(cx, id);
661   if (prop.isSome()) {
662     propp->setNativeProperty(*prop);
663   } else {
664     propp->setNotFound();
665   }
666 
667   return true;
668 }
669 
670 enum class LookupResolveMode {
671   IgnoreResolve,
672   CheckResolve,
673   CheckMayResolve,
674 };
675 
676 template <AllowGC allowGC,
677           LookupResolveMode resolveMode = LookupResolveMode::CheckResolve>
NativeLookupOwnPropertyInline(JSContext * cx,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<jsid,allowGC>::HandleType id,PropertyResult * propp)678 static MOZ_ALWAYS_INLINE bool NativeLookupOwnPropertyInline(
679     JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
680     typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyResult* propp) {
681   // Native objects should should avoid `lookupProperty` hooks, and those that
682   // use them should avoid recursively triggering lookup, and those that still
683   // violate this guidance are the ModuleEnvironmentObject.
684   MOZ_ASSERT_IF(obj->getOpsLookupProperty(),
685                 obj->template is<ModuleEnvironmentObject>());
686 
687   // Check for a native dense element.
688   if (JSID_IS_INT(id)) {
689     uint32_t index = JSID_TO_INT(id);
690     if (obj->containsDenseElement(index)) {
691       propp->setDenseElement(index);
692       return true;
693     }
694   }
695 
696   // Check for a typed array element. Integer lookups always finish here
697   // so that integer properties on the prototype are ignored even for out
698   // of bounds accesses.
699   if (obj->template is<TypedArrayObject>()) {
700     mozilla::Maybe<uint64_t> index;
701     if (!ToTypedArrayIndex(cx, id, &index)) {
702       if (!allowGC) {
703         cx->recoverFromOutOfMemory();
704       }
705       return false;
706     }
707 
708     if (index.isSome()) {
709       uint64_t idx = index.value();
710       if (idx < obj->template as<TypedArrayObject>().length()) {
711         propp->setTypedArrayElement(idx);
712       } else {
713         propp->setTypedArrayOutOfRange();
714       }
715       return true;
716     }
717   }
718 
719   MOZ_ASSERT(cx->compartment() == obj->compartment());
720 
721   // Check for a native property. Call Shape::lookup directly (instead of
722   // NativeObject::lookup) because it's inlined.
723   uint32_t index;
724   if (PropMap* map = obj->shape()->lookup(cx, id, &index)) {
725     propp->setNativeProperty(map->getPropertyInfo(index));
726     return true;
727   }
728 
729   // Some callers explicitily want us to ignore the resolve hook entirely. In
730   // that case, we report the property as NotFound.
731   if constexpr (resolveMode == LookupResolveMode::IgnoreResolve) {
732     propp->setNotFound();
733     return true;
734   }
735 
736   // JITs in particular use the `mayResolve` hook to determine a JSClass can
737   // never resolve this property name (for all instances of the class).
738   if constexpr (resolveMode == LookupResolveMode::CheckMayResolve) {
739     static_assert(allowGC == false,
740                   "CheckMayResolve can only be used with NoGC");
741 
742     MOZ_ASSERT(propp->isNotFound());
743     return !ClassMayResolveId(cx->names(), obj->getClass(), id, obj);
744   }
745 
746   MOZ_ASSERT(resolveMode == LookupResolveMode::CheckResolve);
747 
748   // If there is no resolve hook, the property definitely does not exist.
749   if (obj->getClass()->getResolve()) {
750     if constexpr (!allowGC) {
751       return false;
752     } else {
753       return CallResolveOp(cx, obj, id, propp);
754     }
755   }
756 
757   propp->setNotFound();
758   return true;
759 }
760 
761 /*
762  * Simplified version of NativeLookupOwnPropertyInline that doesn't call
763  * resolve hooks.
764  */
NativeLookupOwnPropertyNoResolve(JSContext * cx,NativeObject * obj,jsid id,PropertyResult * result)765 [[nodiscard]] static inline bool NativeLookupOwnPropertyNoResolve(
766     JSContext* cx, NativeObject* obj, jsid id, PropertyResult* result) {
767   return NativeLookupOwnPropertyInline<NoGC, LookupResolveMode::IgnoreResolve>(
768       cx, obj, id, result);
769 }
770 
771 template <AllowGC allowGC,
772           LookupResolveMode resolveMode = LookupResolveMode::CheckResolve>
NativeLookupPropertyInline(JSContext * cx,typename MaybeRooted<NativeObject *,allowGC>::HandleType obj,typename MaybeRooted<jsid,allowGC>::HandleType id,typename MaybeRooted<std::conditional_t<allowGC==AllowGC::CanGC,JSObject *,NativeObject * >,allowGC>::MutableHandleType objp,PropertyResult * propp)773 static MOZ_ALWAYS_INLINE bool NativeLookupPropertyInline(
774     JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
775     typename MaybeRooted<jsid, allowGC>::HandleType id,
776     typename MaybeRooted<
777         std::conditional_t<allowGC == AllowGC::CanGC, JSObject*, NativeObject*>,
778         allowGC>::MutableHandleType objp,
779     PropertyResult* propp) {
780   /* Search scopes starting with obj and following the prototype link. */
781   typename MaybeRooted<NativeObject*, allowGC>::RootType current(cx, obj);
782 
783   while (true) {
784     if (!NativeLookupOwnPropertyInline<allowGC, resolveMode>(cx, current, id,
785                                                              propp)) {
786       return false;
787     }
788 
789     if (propp->isFound()) {
790       objp.set(current);
791       return true;
792     }
793 
794     if (propp->shouldIgnoreProtoChain()) {
795       break;
796     }
797 
798     JSObject* proto = current->staticPrototype();
799     if (!proto) {
800       break;
801     }
802 
803     // If a `lookupProperty` hook exists, recurse into LookupProperty, otherwise
804     // we can simply loop within this call frame.
805     if (proto->getOpsLookupProperty()) {
806       if constexpr (allowGC) {
807         MOZ_ASSERT(!cx->isHelperThreadContext());
808         RootedObject protoRoot(cx, proto);
809         return LookupProperty(cx, protoRoot, id, objp, propp);
810       } else {
811         return false;
812       }
813     }
814 
815     current = &proto->as<NativeObject>();
816   }
817 
818   MOZ_ASSERT(propp->isNotFound());
819   objp.set(nullptr);
820   return true;
821 }
822 
ThrowIfNotConstructing(JSContext * cx,const CallArgs & args,const char * builtinName)823 inline bool ThrowIfNotConstructing(JSContext* cx, const CallArgs& args,
824                                    const char* builtinName) {
825   if (args.isConstructing()) {
826     return true;
827   }
828   JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
829                             JSMSG_BUILTIN_CTOR_NO_NEW, builtinName);
830   return false;
831 }
832 
IsPackedArray(JSObject * obj)833 inline bool IsPackedArray(JSObject* obj) {
834   if (!obj->is<ArrayObject>()) {
835     return false;
836   }
837 
838   ArrayObject* arr = &obj->as<ArrayObject>();
839   if (arr->getDenseInitializedLength() != arr->length()) {
840     return false;
841   }
842 
843   if (!arr->denseElementsArePacked()) {
844     return false;
845   }
846 
847 #ifdef DEBUG
848   // Assert correctness of the NON_PACKED flag by checking the first few
849   // elements don't contain holes.
850   uint32_t numToCheck = std::min<uint32_t>(5, arr->getDenseInitializedLength());
851   for (uint32_t i = 0; i < numToCheck; i++) {
852     MOZ_ASSERT(!arr->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE));
853   }
854 #endif
855 
856   return true;
857 }
858 
AddDataPropertyNonPrototype(JSContext * cx,HandlePlainObject obj,HandleId id,HandleValue v)859 MOZ_ALWAYS_INLINE bool AddDataPropertyNonPrototype(JSContext* cx,
860                                                    HandlePlainObject obj,
861                                                    HandleId id, HandleValue v) {
862   MOZ_ASSERT(!JSID_IS_INT(id));
863   MOZ_ASSERT(!obj->isUsedAsPrototype());
864 
865   uint32_t slot;
866   if (!NativeObject::addProperty(cx, obj, id,
867                                  PropertyFlags::defaultDataPropFlags, &slot)) {
868     return false;
869   }
870 
871   obj->initSlot(slot, v);
872 
873   MOZ_ASSERT(!obj->getClass()->getAddProperty());
874   return true;
875 }
876 
877 }  // namespace js
878 
879 #endif /* vm_NativeObject_inl_h */
880