Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / NativeObject-inl.h
blob394664a27e5b880ab015ddb2ae25d04069b89353
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/. */
7 #ifndef vm_NativeObject_inl_h
8 #define vm_NativeObject_inl_h
10 #include "vm/NativeObject.h"
12 #include "mozilla/Maybe.h"
14 #include <type_traits>
16 #include "gc/GCEnum.h"
17 #include "gc/GCProbes.h"
18 #include "gc/MaybeRooted.h"
19 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
20 #include "vm/Compartment.h"
21 #include "vm/Iteration.h"
22 #include "vm/PlainObject.h"
23 #include "vm/PropertyResult.h"
24 #include "vm/StringType.h"
25 #include "vm/TypedArrayObject.h"
27 #include "gc/Heap-inl.h"
28 #include "gc/Marking-inl.h"
29 #include "gc/ObjectKind-inl.h"
30 #include "vm/Compartment-inl.h"
31 #include "vm/JSContext-inl.h"
32 #include "vm/JSObject-inl.h"
33 #include "vm/Realm-inl.h"
34 #include "vm/Shape-inl.h"
36 #ifdef ENABLE_RECORD_TUPLE
37 // Defined in vm/RecordTupleShared.{h,cpp}. We cannot include that file
38 // because it causes circular dependencies.
39 extern bool js::IsExtendedPrimitive(const JSObject& obj);
40 #endif
42 namespace js {
44 constexpr ObjectSlots::ObjectSlots(uint32_t capacity,
45 uint32_t dictionarySlotSpan,
46 uint64_t maybeUniqueId)
47 : capacity_(capacity),
48 dictionarySlotSpan_(dictionarySlotSpan),
49 maybeUniqueId_(maybeUniqueId) {
50 MOZ_ASSERT(this->capacity() == capacity);
51 MOZ_ASSERT(this->dictionarySlotSpan() == dictionarySlotSpan);
54 inline uint32_t NativeObject::numFixedSlotsMaybeForwarded() const {
55 return gc::MaybeForwarded(JSObject::shape())->asNative().numFixedSlots();
58 inline uint8_t* NativeObject::fixedData(size_t nslots) const {
59 MOZ_ASSERT(ClassCanHaveFixedData(gc::MaybeForwardedObjectClass(this)));
60 MOZ_ASSERT(nslots == numFixedSlotsMaybeForwarded());
61 return reinterpret_cast<uint8_t*>(&fixedSlots()[nslots]);
64 inline void NativeObject::initDenseElementHole(uint32_t index) {
65 markDenseElementsNotPacked();
66 initDenseElementUnchecked(index, MagicValue(JS_ELEMENTS_HOLE));
69 inline void NativeObject::setDenseElementHole(uint32_t index) {
70 markDenseElementsNotPacked();
71 setDenseElementUnchecked(index, MagicValue(JS_ELEMENTS_HOLE));
74 inline void NativeObject::removeDenseElementForSparseIndex(uint32_t index) {
75 MOZ_ASSERT(containsPure(PropertyKey::Int(index)));
76 if (containsDenseElement(index)) {
77 setDenseElementHole(index);
81 inline void NativeObject::markDenseElementsNotPacked() {
82 MOZ_ASSERT(is<NativeObject>());
83 getElementsHeader()->markNonPacked();
86 inline void NativeObject::elementsRangePostWriteBarrier(uint32_t start,
87 uint32_t count) {
88 if (!isTenured()) {
89 return;
91 for (size_t i = 0; i < count; i++) {
92 const Value& v = elements_[start + i];
93 if (v.isGCThing()) {
94 if (gc::StoreBuffer* sb = v.toGCThing()->storeBuffer()) {
95 sb->putSlot(this, HeapSlot::Element, unshiftedIndex(start + i),
96 count - i);
97 return;
103 inline void NativeObject::copyDenseElements(uint32_t dstStart, const Value* src,
104 uint32_t count) {
105 MOZ_ASSERT(dstStart + count <= getDenseCapacity());
106 MOZ_ASSERT(isExtensible());
107 MOZ_ASSERT_IF(count > 0, src != nullptr);
108 #ifdef DEBUG
109 for (uint32_t i = 0; i < count; ++i) {
110 checkStoredValue(src[i]);
112 #endif
113 if (count == 0) {
114 return;
116 if (zone()->needsIncrementalBarrier()) {
117 uint32_t numShifted = getElementsHeader()->numShiftedElements();
118 for (uint32_t i = 0; i < count; ++i) {
119 elements_[dstStart + i].set(this, HeapSlot::Element,
120 dstStart + i + numShifted, src[i]);
122 } else {
123 memcpy(reinterpret_cast<Value*>(&elements_[dstStart]), src,
124 count * sizeof(Value));
125 elementsRangePostWriteBarrier(dstStart, count);
129 inline void NativeObject::initDenseElements(NativeObject* src,
130 uint32_t srcStart, uint32_t count) {
131 MOZ_ASSERT(src->getDenseInitializedLength() >= srcStart + count);
133 const Value* vp = src->getDenseElements() + srcStart;
135 if (!src->denseElementsArePacked()) {
136 // Mark non-packed if we're copying holes or if there are too many elements
137 // to check this efficiently.
138 static constexpr uint32_t MaxCountForPackedCheck = 30;
139 if (count > MaxCountForPackedCheck) {
140 markDenseElementsNotPacked();
141 } else {
142 for (uint32_t i = 0; i < count; i++) {
143 if (vp[i].isMagic(JS_ELEMENTS_HOLE)) {
144 markDenseElementsNotPacked();
145 break;
151 initDenseElements(vp, count);
154 inline void NativeObject::initDenseElements(const Value* src, uint32_t count) {
155 MOZ_ASSERT(getDenseInitializedLength() == 0);
156 MOZ_ASSERT(count <= getDenseCapacity());
157 MOZ_ASSERT(src);
158 MOZ_ASSERT(isExtensible());
160 setDenseInitializedLength(count);
162 #ifdef DEBUG
163 for (uint32_t i = 0; i < count; ++i) {
164 checkStoredValue(src[i]);
166 #endif
168 memcpy(reinterpret_cast<Value*>(elements_), src, count * sizeof(Value));
169 elementsRangePostWriteBarrier(0, count);
172 inline void NativeObject::initDenseElements(JSLinearString** src,
173 uint32_t count) {
174 MOZ_ASSERT(getDenseInitializedLength() == 0);
175 MOZ_ASSERT(count <= getDenseCapacity());
176 MOZ_ASSERT(src);
177 MOZ_ASSERT(isExtensible());
179 setDenseInitializedLength(count);
180 Value* elementsBase = reinterpret_cast<Value*>(elements_);
181 for (size_t i = 0; i < count; i++) {
182 elementsBase[i].setString(src[i]);
185 elementsRangePostWriteBarrier(0, count);
188 inline void NativeObject::initDenseElementRange(uint32_t destStart,
189 NativeObject* src,
190 uint32_t count) {
191 MOZ_ASSERT(count <= src->getDenseInitializedLength());
193 // The initialized length must already be set to the correct value.
194 MOZ_ASSERT(destStart + count == getDenseInitializedLength());
196 if (!src->denseElementsArePacked()) {
197 markDenseElementsNotPacked();
200 const Value* vp = src->getDenseElements();
201 #ifdef DEBUG
202 for (uint32_t i = 0; i < count; ++i) {
203 checkStoredValue(vp[i]);
205 #endif
206 memcpy(reinterpret_cast<Value*>(elements_) + destStart, vp,
207 count * sizeof(Value));
208 elementsRangePostWriteBarrier(destStart, count);
211 template <typename Iter>
212 inline bool NativeObject::initDenseElementsFromRange(JSContext* cx, Iter begin,
213 Iter end) {
214 // This method populates the elements of a particular Array that's an
215 // internal implementation detail of GeneratorObject. Failing any of the
216 // following means the Array has escaped and/or been mistreated.
217 MOZ_ASSERT(isExtensible());
218 MOZ_ASSERT(!isIndexed());
219 MOZ_ASSERT(is<ArrayObject>());
220 MOZ_ASSERT(as<ArrayObject>().lengthIsWritable());
221 MOZ_ASSERT(!denseElementsAreFrozen());
222 MOZ_ASSERT(getElementsHeader()->numShiftedElements() == 0);
224 MOZ_ASSERT(getDenseInitializedLength() == 0);
226 auto size = end - begin;
227 uint32_t count = uint32_t(size);
228 MOZ_ASSERT(count <= uint32_t(INT32_MAX));
229 if (count > getDenseCapacity()) {
230 if (!growElements(cx, count)) {
231 return false;
235 HeapSlot* sp = elements_;
236 size_t slot = 0;
237 for (; begin != end; sp++, begin++) {
238 Value v = *begin;
239 #ifdef DEBUG
240 checkStoredValue(v);
241 #endif
242 sp->init(this, HeapSlot::Element, slot++, v);
244 MOZ_ASSERT(slot == count);
246 getElementsHeader()->initializedLength = count;
247 as<ArrayObject>().setLength(count);
248 return true;
251 inline bool NativeObject::tryShiftDenseElements(uint32_t count) {
252 MOZ_ASSERT(isExtensible());
254 ObjectElements* header = getElementsHeader();
255 if (header->initializedLength == count ||
256 count > ObjectElements::MaxShiftedElements ||
257 header->hasNonwritableArrayLength()) {
258 return false;
261 shiftDenseElementsUnchecked(count);
262 return true;
265 inline void NativeObject::shiftDenseElementsUnchecked(uint32_t count) {
266 MOZ_ASSERT(isExtensible());
268 ObjectElements* header = getElementsHeader();
269 MOZ_ASSERT(count > 0);
270 MOZ_ASSERT(count < header->initializedLength);
272 if (MOZ_UNLIKELY(header->numShiftedElements() + count >
273 ObjectElements::MaxShiftedElements)) {
274 moveShiftedElements();
275 header = getElementsHeader();
278 prepareElementRangeForOverwrite(0, count);
279 header->addShiftedElements(count);
281 elements_ += count;
282 ObjectElements* newHeader = getElementsHeader();
283 memmove(newHeader, header, sizeof(ObjectElements));
286 inline void NativeObject::moveDenseElements(uint32_t dstStart,
287 uint32_t srcStart, uint32_t count) {
288 MOZ_ASSERT(dstStart + count <= getDenseCapacity());
289 MOZ_ASSERT(srcStart + count <= getDenseInitializedLength());
290 MOZ_ASSERT(isExtensible());
293 * Using memmove here would skip write barriers. Also, we need to consider
294 * an array containing [A, B, C], in the following situation:
296 * 1. Incremental GC marks slot 0 of array (i.e., A), then returns to JS code.
297 * 2. JS code moves slots 1..2 into slots 0..1, so it contains [B, C, C].
298 * 3. Incremental GC finishes by marking slots 1 and 2 (i.e., C).
300 * Since normal marking never happens on B, it is very important that the
301 * write barrier is invoked here on B, despite the fact that it exists in
302 * the array before and after the move.
304 if (zone()->needsIncrementalBarrier()) {
305 uint32_t numShifted = getElementsHeader()->numShiftedElements();
306 if (dstStart < srcStart) {
307 HeapSlot* dst = elements_ + dstStart;
308 HeapSlot* src = elements_ + srcStart;
309 for (uint32_t i = 0; i < count; i++, dst++, src++) {
310 dst->set(this, HeapSlot::Element, dst - elements_ + numShifted, *src);
312 } else {
313 HeapSlot* dst = elements_ + dstStart + count - 1;
314 HeapSlot* src = elements_ + srcStart + count - 1;
315 for (uint32_t i = 0; i < count; i++, dst--, src--) {
316 dst->set(this, HeapSlot::Element, dst - elements_ + numShifted, *src);
319 } else {
320 memmove(elements_ + dstStart, elements_ + srcStart,
321 count * sizeof(HeapSlot));
322 elementsRangePostWriteBarrier(dstStart, count);
326 inline void NativeObject::reverseDenseElementsNoPreBarrier(uint32_t length) {
327 MOZ_ASSERT(!zone()->needsIncrementalBarrier());
329 MOZ_ASSERT(isExtensible());
331 MOZ_ASSERT(length > 1);
332 MOZ_ASSERT(length <= getDenseInitializedLength());
334 Value* valLo = reinterpret_cast<Value*>(elements_);
335 Value* valHi = valLo + (length - 1);
336 MOZ_ASSERT(valLo < valHi);
338 do {
339 Value origLo = *valLo;
340 *valLo = *valHi;
341 *valHi = origLo;
342 ++valLo;
343 --valHi;
344 } while (valLo < valHi);
346 elementsRangePostWriteBarrier(0, length);
349 inline void NativeObject::ensureDenseInitializedLength(uint32_t index,
350 uint32_t extra) {
351 // Ensure that the array's contents have been initialized up to index, and
352 // mark the elements through 'index + extra' as initialized in preparation
353 // for a write.
355 MOZ_ASSERT(!denseElementsAreFrozen());
356 MOZ_ASSERT(isExtensible() || (containsDenseElement(index) && extra == 1));
357 MOZ_ASSERT(index + extra <= getDenseCapacity());
359 uint32_t initlen = getDenseInitializedLength();
360 if (index + extra <= initlen) {
361 return;
364 MOZ_ASSERT(isExtensible());
366 if (index > initlen) {
367 markDenseElementsNotPacked();
370 uint32_t numShifted = getElementsHeader()->numShiftedElements();
371 size_t offset = initlen;
372 for (HeapSlot* sp = elements_ + initlen; sp != elements_ + (index + extra);
373 sp++, offset++) {
374 sp->init(this, HeapSlot::Element, offset + numShifted,
375 MagicValue(JS_ELEMENTS_HOLE));
378 getElementsHeader()->initializedLength = index + extra;
381 DenseElementResult NativeObject::extendDenseElements(JSContext* cx,
382 uint32_t requiredCapacity,
383 uint32_t extra) {
384 MOZ_ASSERT(isExtensible());
387 * Don't grow elements for objects which already have sparse indexes.
388 * This avoids needing to count non-hole elements in willBeSparseElements
389 * every time a new index is added.
391 if (isIndexed()) {
392 return DenseElementResult::Incomplete;
396 * We use the extra argument also as a hint about number of non-hole
397 * elements to be inserted.
399 if (requiredCapacity > MIN_SPARSE_INDEX &&
400 willBeSparseElements(requiredCapacity, extra)) {
401 return DenseElementResult::Incomplete;
404 if (!growElements(cx, requiredCapacity)) {
405 return DenseElementResult::Failure;
408 return DenseElementResult::Success;
411 inline DenseElementResult NativeObject::ensureDenseElements(JSContext* cx,
412 uint32_t index,
413 uint32_t extra) {
414 MOZ_ASSERT(is<NativeObject>());
415 MOZ_ASSERT(isExtensible() || (containsDenseElement(index) && extra == 1));
417 uint32_t requiredCapacity;
418 if (extra == 1) {
419 /* Optimize for the common case. */
420 if (index < getDenseCapacity()) {
421 ensureDenseInitializedLength(index, 1);
422 return DenseElementResult::Success;
424 requiredCapacity = index + 1;
425 if (requiredCapacity == 0) {
426 /* Overflow. */
427 return DenseElementResult::Incomplete;
429 } else {
430 requiredCapacity = index + extra;
431 if (requiredCapacity < index) {
432 /* Overflow. */
433 return DenseElementResult::Incomplete;
435 if (requiredCapacity <= getDenseCapacity()) {
436 ensureDenseInitializedLength(index, extra);
437 return DenseElementResult::Success;
441 DenseElementResult result = extendDenseElements(cx, requiredCapacity, extra);
442 if (result != DenseElementResult::Success) {
443 return result;
446 ensureDenseInitializedLength(index, extra);
447 return DenseElementResult::Success;
450 inline DenseElementResult NativeObject::setOrExtendDenseElements(
451 JSContext* cx, uint32_t start, const Value* vp, uint32_t count) {
452 if (!isExtensible()) {
453 return DenseElementResult::Incomplete;
456 if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable() &&
457 start + count >= as<ArrayObject>().length()) {
458 return DenseElementResult::Incomplete;
461 DenseElementResult result = ensureDenseElements(cx, start, count);
462 if (result != DenseElementResult::Success) {
463 return result;
466 if (is<ArrayObject>() && start + count >= as<ArrayObject>().length()) {
467 as<ArrayObject>().setLength(start + count);
470 copyDenseElements(start, vp, count);
471 return DenseElementResult::Success;
474 inline bool NativeObject::isInWholeCellBuffer() const {
475 const gc::TenuredCell* cell = &asTenured();
476 gc::ArenaCellSet* cells = cell->arena()->bufferedCells();
477 return cells && cells->hasCell(cell);
480 /* static */
481 inline NativeObject* NativeObject::create(
482 JSContext* cx, js::gc::AllocKind kind, js::gc::Heap heap,
483 js::Handle<SharedShape*> shape, js::gc::AllocSite* site /* = nullptr */) {
484 debugCheckNewObject(shape, kind, heap);
486 const JSClass* clasp = shape->getObjectClass();
487 MOZ_ASSERT(clasp->isNativeObject());
488 MOZ_ASSERT(!clasp->isJSFunction(), "should use JSFunction::create");
489 MOZ_ASSERT(clasp != &ArrayObject::class_, "should use ArrayObject::create");
491 const uint32_t nfixed = shape->numFixedSlots();
492 const uint32_t slotSpan = shape->slotSpan();
493 const size_t nDynamicSlots = calculateDynamicSlots(nfixed, slotSpan, clasp);
495 NativeObject* nobj = cx->newCell<NativeObject>(kind, heap, clasp, site);
496 if (!nobj) {
497 return nullptr;
500 nobj->initShape(shape);
501 nobj->setEmptyElements();
503 if (!nDynamicSlots) {
504 nobj->initEmptyDynamicSlots();
505 } else if (!nobj->allocateInitialSlots(cx, nDynamicSlots)) {
506 return nullptr;
509 if (slotSpan > 0) {
510 nobj->initSlots(nfixed, slotSpan);
513 if (MOZ_UNLIKELY(cx->realm()->hasAllocationMetadataBuilder())) {
514 if (clasp->shouldDelayMetadataBuilder()) {
515 cx->realm()->setObjectPendingMetadata(nobj);
516 } else {
517 nobj = SetNewObjectMetadata(cx, nobj);
521 js::gc::gcprobes::CreateObject(nobj);
523 return nobj;
526 MOZ_ALWAYS_INLINE void NativeObject::initEmptyDynamicSlots() {
527 setEmptyDynamicSlots(0);
530 MOZ_ALWAYS_INLINE void NativeObject::setDictionaryModeSlotSpan(uint32_t span) {
531 MOZ_ASSERT(inDictionaryMode());
533 if (!hasDynamicSlots()) {
534 setEmptyDynamicSlots(span);
535 return;
538 getSlotsHeader()->setDictionarySlotSpan(span);
541 MOZ_ALWAYS_INLINE void NativeObject::setEmptyDynamicSlots(
542 uint32_t dictionarySlotSpan) {
543 MOZ_ASSERT_IF(!inDictionaryMode(), dictionarySlotSpan == 0);
544 MOZ_ASSERT(dictionarySlotSpan <= MAX_FIXED_SLOTS);
546 slots_ = emptyObjectSlotsForDictionaryObject[dictionarySlotSpan];
548 MOZ_ASSERT(getSlotsHeader()->capacity() == 0);
549 MOZ_ASSERT(getSlotsHeader()->dictionarySlotSpan() == dictionarySlotSpan);
550 MOZ_ASSERT(!hasDynamicSlots());
551 MOZ_ASSERT(!hasUniqueId());
554 MOZ_ALWAYS_INLINE bool NativeObject::setShapeAndAddNewSlots(
555 JSContext* cx, SharedShape* newShape, uint32_t oldSpan, uint32_t newSpan) {
556 MOZ_ASSERT(!inDictionaryMode());
557 MOZ_ASSERT(newShape->isShared());
558 MOZ_ASSERT(newShape->zone() == zone());
559 MOZ_ASSERT(newShape->numFixedSlots() == numFixedSlots());
560 MOZ_ASSERT(newShape->getObjectClass() == getClass());
562 MOZ_ASSERT(oldSpan < newSpan);
563 MOZ_ASSERT(sharedShape()->slotSpan() == oldSpan);
564 MOZ_ASSERT(newShape->slotSpan() == newSpan);
566 uint32_t numFixed = newShape->numFixedSlots();
567 if (newSpan > numFixed) {
568 uint32_t oldCapacity = numDynamicSlots();
569 uint32_t newCapacity =
570 calculateDynamicSlots(numFixed, newSpan, newShape->getObjectClass());
571 MOZ_ASSERT(oldCapacity <= newCapacity);
573 if (oldCapacity < newCapacity) {
574 if (MOZ_UNLIKELY(!growSlots(cx, oldCapacity, newCapacity))) {
575 return false;
580 // Initialize slots [oldSpan, newSpan). Use the *Unchecked version because
581 // the shape's slot span does not reflect the allocated slots at this
582 // point.
583 auto initRange = [](HeapSlot* start, HeapSlot* end) {
584 for (HeapSlot* slot = start; slot < end; slot++) {
585 slot->initAsUndefined();
588 forEachSlotRangeUnchecked(oldSpan, newSpan, initRange);
590 setShape(newShape);
591 return true;
594 MOZ_ALWAYS_INLINE bool NativeObject::setShapeAndAddNewSlot(
595 JSContext* cx, SharedShape* newShape, uint32_t slot) {
596 MOZ_ASSERT(!inDictionaryMode());
597 MOZ_ASSERT(newShape->isShared());
598 MOZ_ASSERT(newShape->zone() == zone());
599 MOZ_ASSERT(newShape->numFixedSlots() == numFixedSlots());
601 MOZ_ASSERT(newShape->base() == shape()->base());
602 MOZ_ASSERT(newShape->slotSpan() == sharedShape()->slotSpan() + 1);
603 MOZ_ASSERT(newShape->slotSpan() == slot + 1);
605 uint32_t numFixed = newShape->numFixedSlots();
606 if (slot < numFixed) {
607 initFixedSlot(slot, UndefinedValue());
608 } else {
609 uint32_t dynamicSlotIndex = slot - numFixed;
610 if (dynamicSlotIndex >= numDynamicSlots()) {
611 if (MOZ_UNLIKELY(!growSlotsForNewSlot(cx, numFixed, slot))) {
612 return false;
615 initDynamicSlot(numFixed, slot, UndefinedValue());
618 setShape(newShape);
619 return true;
622 inline js::gc::AllocKind NativeObject::allocKindForTenure() const {
623 using namespace js::gc;
624 AllocKind kind = GetGCObjectFixedSlotsKind(numFixedSlots());
625 MOZ_ASSERT(!IsBackgroundFinalized(kind));
626 if (!CanChangeToBackgroundAllocKind(kind, getClass())) {
627 return kind;
629 return ForegroundToBackgroundAllocKind(kind);
632 inline js::GlobalObject& NativeObject::global() const { return nonCCWGlobal(); }
634 inline bool NativeObject::denseElementsHaveMaybeInIterationFlag() {
635 if (!getElementsHeader()->maybeInIteration()) {
636 AssertDenseElementsNotIterated(this);
637 return false;
639 return true;
642 inline bool NativeObject::denseElementsMaybeInIteration() {
643 if (!denseElementsHaveMaybeInIterationFlag()) {
644 return false;
646 return compartment()->objectMaybeInIteration(this);
650 * Call obj's resolve hook.
652 * cx and id are the parameters initially passed to the ongoing lookup;
653 * propp and recursedp are its out parameters.
655 * There are four possible outcomes:
657 * - On failure, report an error or exception and return false.
659 * - If we are already resolving a property of obj, call setRecursiveResolve on
660 * propp and return true.
662 * - If the resolve hook finds or defines the sought property, set propp
663 * appropriately, and return true.
665 * - Otherwise no property was resolved. Set propp to NotFound and return true.
667 static MOZ_ALWAYS_INLINE bool CallResolveOp(JSContext* cx,
668 Handle<NativeObject*> obj,
669 HandleId id,
670 PropertyResult* propp) {
671 // Avoid recursion on (obj, id) already being resolved on cx.
672 AutoResolving resolving(cx, obj, id);
673 if (resolving.alreadyStarted()) {
674 // Already resolving id in obj, suppress recursion.
675 propp->setRecursiveResolve();
676 return true;
679 bool resolved = false;
680 AutoRealm ar(cx, obj);
681 if (!obj->getClass()->getResolve()(cx, obj, id, &resolved)) {
682 return false;
685 if (!resolved) {
686 propp->setNotFound();
687 return true;
690 // Assert the mayResolve hook, if there is one, returns true for this
691 // property.
692 MOZ_ASSERT_IF(obj->getClass()->getMayResolve(),
693 obj->getClass()->getMayResolve()(cx->names(), id, obj));
695 if (id.isInt()) {
696 uint32_t index = id.toInt();
697 if (obj->containsDenseElement(index)) {
698 propp->setDenseElement(index);
699 return true;
703 MOZ_ASSERT(!obj->is<TypedArrayObject>());
705 mozilla::Maybe<PropertyInfo> prop = obj->lookup(cx, id);
706 if (prop.isSome()) {
707 propp->setNativeProperty(*prop);
708 } else {
709 propp->setNotFound();
712 return true;
715 enum class LookupResolveMode {
716 IgnoreResolve,
717 CheckResolve,
718 CheckMayResolve,
721 template <AllowGC allowGC,
722 LookupResolveMode resolveMode = LookupResolveMode::CheckResolve>
723 static MOZ_ALWAYS_INLINE bool NativeLookupOwnPropertyInline(
724 JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
725 typename MaybeRooted<jsid, allowGC>::HandleType id, PropertyResult* propp) {
726 // Native objects should should avoid `lookupProperty` hooks, and those that
727 // use them should avoid recursively triggering lookup, and those that still
728 // violate this guidance are the ModuleEnvironmentObject.
729 MOZ_ASSERT_IF(obj->getOpsLookupProperty(),
730 obj->template is<ModuleEnvironmentObject>());
731 #ifdef ENABLE_RECORD_TUPLE
732 MOZ_ASSERT(!js::IsExtendedPrimitive(*obj));
733 #endif
735 // Check for a native dense element.
736 if (id.isInt()) {
737 uint32_t index = id.toInt();
738 if (obj->containsDenseElement(index)) {
739 propp->setDenseElement(index);
740 return true;
744 // Check for a typed array element. Integer lookups always finish here
745 // so that integer properties on the prototype are ignored even for out
746 // of bounds accesses.
747 if (obj->template is<TypedArrayObject>()) {
748 if (mozilla::Maybe<uint64_t> index = ToTypedArrayIndex(id)) {
749 uint64_t idx = index.value();
750 if (idx < obj->template as<TypedArrayObject>().length().valueOr(0)) {
751 propp->setTypedArrayElement(idx);
752 } else {
753 propp->setTypedArrayOutOfRange();
755 return true;
759 MOZ_ASSERT(cx->compartment() == obj->compartment());
761 // Check for a native property. Call Shape::lookup directly (instead of
762 // NativeObject::lookup) because it's inlined.
763 uint32_t index;
764 if (PropMap* map = obj->shape()->lookup(cx, id, &index)) {
765 propp->setNativeProperty(map->getPropertyInfo(index));
766 return true;
769 // Some callers explicitily want us to ignore the resolve hook entirely. In
770 // that case, we report the property as NotFound.
771 if constexpr (resolveMode == LookupResolveMode::IgnoreResolve) {
772 propp->setNotFound();
773 return true;
776 // JITs in particular use the `mayResolve` hook to determine a JSClass can
777 // never resolve this property name (for all instances of the class).
778 if constexpr (resolveMode == LookupResolveMode::CheckMayResolve) {
779 static_assert(allowGC == false,
780 "CheckMayResolve can only be used with NoGC");
782 MOZ_ASSERT(propp->isNotFound());
783 return !ClassMayResolveId(cx->names(), obj->getClass(), id, obj);
786 MOZ_ASSERT(resolveMode == LookupResolveMode::CheckResolve);
788 // If there is no resolve hook, the property definitely does not exist.
789 if (obj->getClass()->getResolve()) {
790 if constexpr (!allowGC) {
791 return false;
792 } else {
793 return CallResolveOp(cx, obj, id, propp);
797 propp->setNotFound();
798 return true;
802 * Simplified version of NativeLookupOwnPropertyInline that doesn't call
803 * resolve hooks.
805 [[nodiscard]] static inline bool NativeLookupOwnPropertyNoResolve(
806 JSContext* cx, NativeObject* obj, jsid id, PropertyResult* result) {
807 return NativeLookupOwnPropertyInline<NoGC, LookupResolveMode::IgnoreResolve>(
808 cx, obj, id, result);
811 template <AllowGC allowGC,
812 LookupResolveMode resolveMode = LookupResolveMode::CheckResolve>
813 static MOZ_ALWAYS_INLINE bool NativeLookupPropertyInline(
814 JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
815 typename MaybeRooted<jsid, allowGC>::HandleType id,
816 typename MaybeRooted<
817 std::conditional_t<allowGC == AllowGC::CanGC, JSObject*, NativeObject*>,
818 allowGC>::MutableHandleType objp,
819 PropertyResult* propp) {
820 /* Search scopes starting with obj and following the prototype link. */
821 typename MaybeRooted<NativeObject*, allowGC>::RootType current(cx, obj);
823 while (true) {
824 if (!NativeLookupOwnPropertyInline<allowGC, resolveMode>(cx, current, id,
825 propp)) {
826 return false;
829 if (propp->isFound()) {
830 objp.set(current);
831 return true;
834 if (propp->shouldIgnoreProtoChain()) {
835 break;
838 JSObject* proto = current->staticPrototype();
839 if (!proto) {
840 break;
843 // If a `lookupProperty` hook exists, recurse into LookupProperty, otherwise
844 // we can simply loop within this call frame.
845 if (proto->getOpsLookupProperty()) {
846 if constexpr (allowGC) {
847 RootedObject protoRoot(cx, proto);
848 return LookupProperty(cx, protoRoot, id, objp, propp);
849 } else {
850 return false;
854 current = &proto->as<NativeObject>();
857 MOZ_ASSERT(propp->isNotFound());
858 objp.set(nullptr);
859 return true;
862 inline bool ThrowIfNotConstructing(JSContext* cx, const CallArgs& args,
863 const char* builtinName) {
864 if (args.isConstructing()) {
865 return true;
867 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
868 JSMSG_BUILTIN_CTOR_NO_NEW, builtinName);
869 return false;
872 inline bool IsPackedArray(JSObject* obj) {
873 if (!obj->is<ArrayObject>()) {
874 return false;
877 ArrayObject* arr = &obj->as<ArrayObject>();
878 if (arr->getDenseInitializedLength() != arr->length()) {
879 return false;
882 if (!arr->denseElementsArePacked()) {
883 return false;
886 #ifdef DEBUG
887 // Assert correctness of the NON_PACKED flag by checking the first few
888 // elements don't contain holes.
889 uint32_t numToCheck = std::min<uint32_t>(5, arr->getDenseInitializedLength());
890 for (uint32_t i = 0; i < numToCheck; i++) {
891 MOZ_ASSERT(!arr->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE));
893 #endif
895 return true;
898 // Like AddDataProperty but optimized for plain objects. Plain objects don't
899 // have an addProperty hook.
900 MOZ_ALWAYS_INLINE bool AddDataPropertyToPlainObject(
901 JSContext* cx, Handle<PlainObject*> obj, HandleId id, HandleValue v,
902 uint32_t* resultSlot = nullptr) {
903 MOZ_ASSERT(!id.isInt());
905 uint32_t slot;
906 if (!resultSlot) {
907 resultSlot = &slot;
909 if (!NativeObject::addProperty(
910 cx, obj, id, PropertyFlags::defaultDataPropFlags, resultSlot)) {
911 return false;
914 obj->initSlot(*resultSlot, v);
916 MOZ_ASSERT(!obj->getClass()->getAddProperty());
917 return true;
920 } // namespace js
922 #endif /* vm_NativeObject_inl_h */