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