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