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