diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/vm/NativeObject.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/NativeObject.cpp')
-rw-r--r-- | js/src/vm/NativeObject.cpp | 2881 |
1 files changed, 2881 insertions, 0 deletions
diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp new file mode 100644 index 0000000000..9e4aef3b96 --- /dev/null +++ b/js/src/vm/NativeObject.cpp @@ -0,0 +1,2881 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/NativeObject-inl.h" + +#include "mozilla/Casting.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" + +#include <algorithm> +#include <iterator> + +#include "debugger/DebugAPI.h" +#include "gc/Marking.h" +#include "gc/MaybeRooted.h" +#include "jit/BaselineIC.h" +#include "js/CharacterEncoding.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/friend/StackLimits.h" // js::CheckRecursionLimit{,DontReport} +#include "js/Result.h" +#include "js/Value.h" +#include "util/Memory.h" +#include "vm/EqualityOperations.h" // js::SameValue +#include "vm/PlainObject.h" // js::PlainObject +#include "vm/TypedArrayObject.h" + +#include "gc/Nursery-inl.h" +#include "vm/ArrayObject-inl.h" +#include "vm/BytecodeLocation-inl.h" +#include "vm/EnvironmentObject-inl.h" +#include "vm/JSObject-inl.h" +#include "vm/JSScript-inl.h" +#include "vm/Shape-inl.h" + +using namespace js; + +using JS::AutoCheckCannotGC; +using mozilla::CheckedInt; +using mozilla::DebugOnly; +using mozilla::PodCopy; +using mozilla::RoundUpPow2; + +struct EmptyObjectElements { + const ObjectElements emptyElementsHeader; + + // Add an extra (unused) Value to make sure an out-of-bounds index when + // masked (resulting in index 0) accesses valid memory. + const Value val; + + public: + constexpr EmptyObjectElements() + : emptyElementsHeader(0, 0), val(UndefinedValue()) {} + explicit constexpr EmptyObjectElements(ObjectElements::SharedMemory shmem) + : emptyElementsHeader(0, 0, shmem), val(UndefinedValue()) {} +}; + +static constexpr EmptyObjectElements emptyElementsHeader; + +/* Objects with no elements share one empty set of elements. */ +HeapSlot* const js::emptyObjectElements = reinterpret_cast<HeapSlot*>( + uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements)); + +static constexpr EmptyObjectElements emptyElementsHeaderShared( + ObjectElements::SharedMemory::IsShared); + +/* Objects with no elements share one empty set of elements. */ +HeapSlot* const js::emptyObjectElementsShared = reinterpret_cast<HeapSlot*>( + uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements)); + +struct EmptyObjectSlots : public ObjectSlots { + explicit constexpr EmptyObjectSlots(size_t dictionarySlotSpan) + : ObjectSlots(0, dictionarySlotSpan) {} +}; + +static constexpr EmptyObjectSlots emptyObjectSlotsHeaders[17] = { + EmptyObjectSlots(0), EmptyObjectSlots(1), EmptyObjectSlots(2), + EmptyObjectSlots(3), EmptyObjectSlots(4), EmptyObjectSlots(5), + EmptyObjectSlots(6), EmptyObjectSlots(7), EmptyObjectSlots(8), + EmptyObjectSlots(9), EmptyObjectSlots(10), EmptyObjectSlots(11), + EmptyObjectSlots(12), EmptyObjectSlots(13), EmptyObjectSlots(14), + EmptyObjectSlots(15), EmptyObjectSlots(16)}; + +static_assert(std::size(emptyObjectSlotsHeaders) == + NativeObject::MAX_FIXED_SLOTS + 1); + +HeapSlot* const js::emptyObjectSlotsForDictionaryObject[17] = { + emptyObjectSlotsHeaders[0].slots(), emptyObjectSlotsHeaders[1].slots(), + emptyObjectSlotsHeaders[2].slots(), emptyObjectSlotsHeaders[3].slots(), + emptyObjectSlotsHeaders[4].slots(), emptyObjectSlotsHeaders[5].slots(), + emptyObjectSlotsHeaders[6].slots(), emptyObjectSlotsHeaders[7].slots(), + emptyObjectSlotsHeaders[8].slots(), emptyObjectSlotsHeaders[9].slots(), + emptyObjectSlotsHeaders[10].slots(), emptyObjectSlotsHeaders[11].slots(), + emptyObjectSlotsHeaders[12].slots(), emptyObjectSlotsHeaders[13].slots(), + emptyObjectSlotsHeaders[14].slots(), emptyObjectSlotsHeaders[15].slots(), + emptyObjectSlotsHeaders[16].slots()}; + +static_assert(std::size(emptyObjectSlotsForDictionaryObject) == + NativeObject::MAX_FIXED_SLOTS + 1); + +HeapSlot* const js::emptyObjectSlots = emptyObjectSlotsForDictionaryObject[0]; + +#ifdef DEBUG + +bool NativeObject::canHaveNonEmptyElements() { + return !this->is<TypedArrayObject>(); +} + +#endif // DEBUG + +/* static */ +void ObjectElements::PrepareForPreventExtensions(JSContext* cx, + NativeObject* obj) { + if (!obj->hasEmptyElements()) { + obj->shrinkCapacityToInitializedLength(cx); + } + + // shrinkCapacityToInitializedLength ensures there are no shifted elements. + MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0); +} + +/* static */ +void ObjectElements::PreventExtensions(NativeObject* obj) { + MOZ_ASSERT(!obj->isExtensible()); + MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0); + MOZ_ASSERT(obj->getDenseInitializedLength() == obj->getDenseCapacity()); + + if (!obj->hasEmptyElements()) { + obj->getElementsHeader()->setNotExtensible(); + } +} + +/* static */ +bool ObjectElements::FreezeOrSeal(JSContext* cx, HandleNativeObject obj, + IntegrityLevel level) { + MOZ_ASSERT_IF(level == IntegrityLevel::Frozen && obj->is<ArrayObject>(), + !obj->as<ArrayObject>().lengthIsWritable()); + MOZ_ASSERT(!obj->isExtensible()); + MOZ_ASSERT(obj->getElementsHeader()->numShiftedElements() == 0); + + if (obj->hasEmptyElements() || obj->denseElementsAreFrozen()) { + return true; + } + + if (level == IntegrityLevel::Frozen) { + if (!JSObject::setFlags(cx, obj, BaseShape::FROZEN_ELEMENTS, + JSObject::GENERATE_SHAPE)) { + return false; + } + } + + if (!obj->denseElementsAreSealed()) { + obj->getElementsHeader()->seal(); + } + + if (level == IntegrityLevel::Frozen) { + obj->getElementsHeader()->freeze(); + } + + return true; +} + +#ifdef DEBUG +static mozilla::Atomic<bool, mozilla::Relaxed> gShapeConsistencyChecksEnabled( + false); + +/* static */ +void js::NativeObject::enableShapeConsistencyChecks() { + gShapeConsistencyChecksEnabled = true; +} + +void js::NativeObject::checkShapeConsistency() { + if (!gShapeConsistencyChecksEnabled) { + return; + } + + MOZ_ASSERT(isNative()); + + Shape* shape = lastProperty(); + Shape* prev = nullptr; + + AutoCheckCannotGC nogc; + if (inDictionaryMode()) { + if (ShapeTable* table = shape->maybeTable(nogc)) { + for (uint32_t fslot = table->freeList(); fslot != SHAPE_INVALID_SLOT; + fslot = getSlot(fslot).toPrivateUint32()) { + MOZ_ASSERT(fslot < slotSpan()); + } + + while (shape->parent) { + MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable()); + + ShapeTable::Entry& entry = + table->search<MaybeAdding::NotAdding>(shape->propid(), nogc); + MOZ_ASSERT(entry.shape() == shape); + shape = shape->parent; + } + } + + shape = lastProperty(); + while (shape) { + MOZ_ASSERT_IF(!shape->isEmptyShape() && shape->isDataProperty(), + shape->slot() < slotSpan()); + if (!prev) { + MOZ_ASSERT(lastProperty() == shape); + MOZ_ASSERT(shape->dictNext.toObject() == this); + } else { + MOZ_ASSERT(shape->dictNext.toShape() == prev); + } + prev = shape; + shape = shape->parent; + } + } else { + while (shape->parent) { + if (ShapeTable* table = shape->maybeTable(nogc)) { + MOZ_ASSERT(shape->parent); + for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) { + ShapeTable::Entry& entry = + table->search<MaybeAdding::NotAdding>(r.front().propid(), nogc); + MOZ_ASSERT(entry.shape() == &r.front()); + } + } + if (prev) { + MOZ_ASSERT_IF(shape->isDataProperty(), + prev->maybeSlot() >= shape->maybeSlot()); + shape->children.checkHasChild(prev); + } + prev = shape; + shape = shape->parent; + } + } +} +#endif + +void js::NativeObject::initializeSlotRange(uint32_t start, uint32_t end) { + /* + * No bounds check, as this is used when the object's shape does not + * reflect its allocated slots (updateSlotsForSpan). + */ + HeapSlot* fixedStart; + HeapSlot* fixedEnd; + HeapSlot* slotsStart; + HeapSlot* slotsEnd; + getSlotRangeUnchecked(start, end, &fixedStart, &fixedEnd, &slotsStart, + &slotsEnd); + + uint32_t offset = start; + for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++) { + sp->init(this, HeapSlot::Slot, offset++, UndefinedValue()); + } + for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++) { + sp->init(this, HeapSlot::Slot, offset++, UndefinedValue()); + } +} + +void js::NativeObject::initSlots(const Value* vector, uint32_t length) { + HeapSlot* fixedStart; + HeapSlot* fixedEnd; + HeapSlot* slotsStart; + HeapSlot* slotsEnd; + getSlotRange(0, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); + + uint32_t offset = 0; + for (HeapSlot* sp = fixedStart; sp < fixedEnd; sp++) { + sp->init(this, HeapSlot::Slot, offset++, *vector++); + } + for (HeapSlot* sp = slotsStart; sp < slotsEnd; sp++) { + sp->init(this, HeapSlot::Slot, offset++, *vector++); + } +} + +#ifdef DEBUG + +bool js::NativeObject::slotInRange(uint32_t slot, + SentinelAllowed sentinel) const { + MOZ_ASSERT(!gc::IsForwarded(lastProperty())); + uint32_t capacity = numFixedSlots() + numDynamicSlots(); + if (sentinel == SENTINEL_ALLOWED) { + return slot <= capacity; + } + return slot < capacity; +} + +bool js::NativeObject::slotIsFixed(uint32_t slot) const { + // We call numFixedSlotsMaybeForwarded() to allow reading slots of + // associated objects in trace hooks that may be called during a moving GC. + return slot < numFixedSlotsMaybeForwarded(); +} + +bool js::NativeObject::isNumFixedSlots(uint32_t nfixed) const { + // We call numFixedSlotsMaybeForwarded() to allow reading slots of + // associated objects in trace hooks that may be called during a moving GC. + return nfixed == numFixedSlotsMaybeForwarded(); +} + +#endif /* DEBUG */ + +Shape* js::NativeObject::lookup(JSContext* cx, jsid id) { + MOZ_ASSERT(isNative()); + return Shape::search(cx, lastProperty(), id); +} + +Shape* js::NativeObject::lookupPure(jsid id) { + MOZ_ASSERT(isNative()); + return Shape::searchNoHashify(lastProperty(), id); +} + +bool NativeObject::ensureSlotsForDictionaryObject(JSContext* cx, + uint32_t span) { + MOZ_ASSERT(inDictionaryMode()); + + size_t oldSpan = dictionaryModeSlotSpan(); + if (oldSpan == span) { + return true; + } + + if (!updateSlotsForSpan(cx, oldSpan, span)) { + return false; + } + + setDictionaryModeSlotSpan(span); + return true; +} + +bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity, + uint32_t newCapacity) { + MOZ_ASSERT(newCapacity > oldCapacity); + MOZ_ASSERT_IF(!is<ArrayObject>(), newCapacity >= SLOT_CAPACITY_MIN); + + /* + * Slot capacities are determined by the span of allocated objects. Due to + * the limited number of bits to store shape slots, object growth is + * throttled well before the slot capacity can overflow. + */ + NativeObject::slotsSizeMustNotOverflow(); + MOZ_ASSERT(newCapacity <= MAX_SLOTS_COUNT); + + if (!hasDynamicSlots()) { + return allocateSlots(cx, newCapacity); + } + + uint32_t newAllocated = ObjectSlots::allocCount(newCapacity); + + uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan(); + + uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity); + + ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_); + MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity); + + HeapSlot* allocation = ReallocateObjectBuffer<HeapSlot>( + cx, this, reinterpret_cast<HeapSlot*>(oldHeaderSlots), oldAllocated, + newAllocated); + if (!allocation) { + return false; /* Leave slots at its old size. */ + } + + auto* newHeaderSlots = + new (allocation) ObjectSlots(newCapacity, dictionarySpan); + slots_ = newHeaderSlots->slots(); + + Debug_SetSlotRangeToCrashOnTouch(slots_ + oldCapacity, + newCapacity - oldCapacity); + + RemoveCellMemory(this, ObjectSlots::allocSize(oldCapacity), + MemoryUse::ObjectSlots); + AddCellMemory(this, ObjectSlots::allocSize(newCapacity), + MemoryUse::ObjectSlots); + + MOZ_ASSERT(hasDynamicSlots()); + return true; +} + +bool NativeObject::allocateSlots(JSContext* cx, uint32_t newCapacity) { + MOZ_ASSERT(!hasDynamicSlots()); + + uint32_t newAllocated = ObjectSlots::allocCount(newCapacity); + + uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan(); + + HeapSlot* allocation = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated); + if (!allocation) { + return false; + } + + auto* newHeaderSlots = + new (allocation) ObjectSlots(newCapacity, dictionarySpan); + slots_ = newHeaderSlots->slots(); + + Debug_SetSlotRangeToCrashOnTouch(slots_, newCapacity); + + AddCellMemory(this, ObjectSlots::allocSize(newCapacity), + MemoryUse::ObjectSlots); + + MOZ_ASSERT(hasDynamicSlots()); + return true; +} + +/* static */ +bool NativeObject::growSlotsPure(JSContext* cx, NativeObject* obj, + uint32_t newCapacity) { + // IC code calls this directly. + AutoUnsafeCallWithABI unsafe; + + if (!obj->growSlots(cx, obj->numDynamicSlots(), newCapacity)) { + cx->recoverFromOutOfMemory(); + return false; + } + + return true; +} + +/* static */ +bool NativeObject::addDenseElementPure(JSContext* cx, NativeObject* obj) { + // IC code calls this directly. + AutoUnsafeCallWithABI unsafe; + + MOZ_ASSERT(obj->getDenseInitializedLength() == obj->getDenseCapacity()); + MOZ_ASSERT(obj->isExtensible()); + MOZ_ASSERT(!obj->isIndexed()); + MOZ_ASSERT(!obj->is<TypedArrayObject>()); + MOZ_ASSERT_IF(obj->is<ArrayObject>(), + obj->as<ArrayObject>().lengthIsWritable()); + + // growElements will report OOM also if the number of dense elements will + // exceed MAX_DENSE_ELEMENTS_COUNT. See goodElementsAllocationAmount. + uint32_t oldCapacity = obj->getDenseCapacity(); + if (MOZ_UNLIKELY(!obj->growElements(cx, oldCapacity + 1))) { + cx->recoverFromOutOfMemory(); + return false; + } + + MOZ_ASSERT(obj->getDenseCapacity() > oldCapacity); + MOZ_ASSERT(obj->getDenseCapacity() <= MAX_DENSE_ELEMENTS_COUNT); + return true; +} + +static inline void FreeSlots(JSContext* cx, NativeObject* obj, + ObjectSlots* slots, size_t nbytes) { + if (cx->isHelperThreadContext()) { + js_free(slots); + } else if (obj->isTenured()) { + MOZ_ASSERT(!cx->nursery().isInside(slots)); + js_free(slots); + } else { + cx->nursery().freeBuffer(slots, nbytes); + } +} + +void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity, + uint32_t newCapacity) { + MOZ_ASSERT(newCapacity < oldCapacity); + MOZ_ASSERT(oldCapacity == getSlotsHeader()->capacity()); + + uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan(); + + ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_); + MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity); + + uint32_t oldAllocated = ObjectSlots::allocCount(oldCapacity); + + if (newCapacity == 0) { + size_t nbytes = ObjectSlots::allocSize(oldCapacity); + RemoveCellMemory(this, nbytes, MemoryUse::ObjectSlots); + FreeSlots(cx, this, oldHeaderSlots, nbytes); + setEmptyDynamicSlots(dictionarySpan); + return; + } + + MOZ_ASSERT_IF(!is<ArrayObject>(), newCapacity >= SLOT_CAPACITY_MIN); + + uint32_t newAllocated = ObjectSlots::allocCount(newCapacity); + + HeapSlot* allocation = ReallocateObjectBuffer<HeapSlot>( + cx, this, reinterpret_cast<HeapSlot*>(oldHeaderSlots), oldAllocated, + newAllocated); + if (!allocation) { + // It's possible for realloc to fail when shrinking an allocation. In this + // case we continue using the original allocation but still update the + // capacity to the new requested capacity, which is smaller than the actual + // capacity. + cx->recoverFromOutOfMemory(); + allocation = reinterpret_cast<HeapSlot*>(getSlotsHeader()); + } + + RemoveCellMemory(this, ObjectSlots::allocSize(oldCapacity), + MemoryUse::ObjectSlots); + AddCellMemory(this, ObjectSlots::allocSize(newCapacity), + MemoryUse::ObjectSlots); + + auto* newHeaderSlots = + new (allocation) ObjectSlots(newCapacity, dictionarySpan); + slots_ = newHeaderSlots->slots(); +} + +bool NativeObject::willBeSparseElements(uint32_t requiredCapacity, + uint32_t newElementsHint) { + MOZ_ASSERT(isNative()); + MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX); + + uint32_t cap = getDenseCapacity(); + MOZ_ASSERT(requiredCapacity >= cap); + + if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT) { + return true; + } + + uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO; + if (newElementsHint >= minimalDenseCount) { + return false; + } + minimalDenseCount -= newElementsHint; + + if (minimalDenseCount > cap) { + return true; + } + + uint32_t len = getDenseInitializedLength(); + const Value* elems = getDenseElements(); + for (uint32_t i = 0; i < len; i++) { + if (!elems[i].isMagic(JS_ELEMENTS_HOLE) && !--minimalDenseCount) { + return false; + } + } + return true; +} + +/* static */ +DenseElementResult NativeObject::maybeDensifySparseElements( + JSContext* cx, HandleNativeObject obj) { + /* + * Wait until after the object goes into dictionary mode, which must happen + * when sparsely packing any array with more than MIN_SPARSE_INDEX elements + * (see PropertyTree::MAX_HEIGHT). + */ + if (!obj->inDictionaryMode()) { + return DenseElementResult::Incomplete; + } + + /* + * Only measure the number of indexed properties every log(n) times when + * populating the object. + */ + uint32_t slotSpan = obj->slotSpan(); + if (slotSpan != RoundUpPow2(slotSpan)) { + return DenseElementResult::Incomplete; + } + + /* Watch for conditions under which an object's elements cannot be dense. */ + if (!obj->isExtensible()) { + return DenseElementResult::Incomplete; + } + + /* + * The indexes in the object need to be sufficiently dense before they can + * be converted to dense mode. + */ + uint32_t numDenseElements = 0; + uint32_t newInitializedLength = 0; + + RootedShape shape(cx, obj->lastProperty()); + while (!shape->isEmptyShape()) { + uint32_t index; + if (IdIsIndex(shape->propid(), &index)) { + if (shape->attributes() == JSPROP_ENUMERATE && + shape->hasDefaultGetter() && shape->hasDefaultSetter()) { + numDenseElements++; + newInitializedLength = std::max(newInitializedLength, index + 1); + } else { + /* + * For simplicity, only densify the object if all indexed + * properties can be converted to dense elements. + */ + return DenseElementResult::Incomplete; + } + } + shape = shape->previous(); + } + + if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength) { + return DenseElementResult::Incomplete; + } + + if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT) { + return DenseElementResult::Incomplete; + } + + /* + * This object meets all necessary restrictions, convert all indexed + * properties into dense elements. + */ + + if (newInitializedLength > obj->getDenseCapacity()) { + if (!obj->growElements(cx, newInitializedLength)) { + return DenseElementResult::Failure; + } + } + + obj->ensureDenseInitializedLength(newInitializedLength, 0); + + if (ObjectRealm::get(obj).objectMaybeInIteration(obj)) { + // Mark the densified elements as maybe-in-iteration. See also the comment + // in GetIterator. + obj->markDenseElementsMaybeInIteration(); + } + + RootedValue value(cx); + + shape = obj->lastProperty(); + while (!shape->isEmptyShape()) { + jsid id = shape->propid(); + uint32_t index; + if (IdIsIndex(id, &index)) { + value = obj->getSlot(shape->slot()); + + /* + * When removing a property from a dictionary, the specified + * property will be removed from the dictionary list and the + * last property will then be changed due to reshaping the object. + * Compute the next shape in the traverse, watching for such + * removals from the list. + */ + if (shape != obj->lastProperty()) { + shape = shape->previous(); + if (!NativeObject::removeProperty(cx, obj, id)) { + return DenseElementResult::Failure; + } + } else { + if (!NativeObject::removeProperty(cx, obj, id)) { + return DenseElementResult::Failure; + } + shape = obj->lastProperty(); + } + + obj->setDenseElement(index, value); + } else { + shape = shape->previous(); + } + } + + /* + * All indexed properties on the object are now dense, clear the indexed + * flag so that we will not start using sparse indexes again if we need + * to grow the object. + */ + if (!NativeObject::clearFlag(cx, obj, BaseShape::INDEXED)) { + return DenseElementResult::Failure; + } + + return DenseElementResult::Success; +} + +void NativeObject::moveShiftedElements() { + MOZ_ASSERT(isExtensible()); + + ObjectElements* header = getElementsHeader(); + uint32_t numShifted = header->numShiftedElements(); + MOZ_ASSERT(numShifted > 0); + + uint32_t initLength = header->initializedLength; + + ObjectElements* newHeader = + static_cast<ObjectElements*>(getUnshiftedElementsHeader()); + memmove(newHeader, header, sizeof(ObjectElements)); + + newHeader->clearShiftedElements(); + newHeader->capacity += numShifted; + elements_ = newHeader->elements(); + + // To move the elements, temporarily update initializedLength to include + // the shifted elements. + newHeader->initializedLength += numShifted; + + // Move the elements. Initialize to |undefined| to ensure pre-barriers + // don't see garbage. + for (size_t i = 0; i < numShifted; i++) { + initDenseElement(i, UndefinedValue()); + } + moveDenseElements(0, numShifted, initLength); + + // Restore the initialized length. We use setDenseInitializedLength to + // make sure prepareElementRangeForOverwrite is called on the shifted + // elements. + setDenseInitializedLength(initLength); +} + +void NativeObject::maybeMoveShiftedElements() { + MOZ_ASSERT(isExtensible()); + + ObjectElements* header = getElementsHeader(); + MOZ_ASSERT(header->numShiftedElements() > 0); + + // Move the elements if less than a third of the allocated space is in use. + if (header->capacity < header->numAllocatedElements() / 3) { + moveShiftedElements(); + } +} + +bool NativeObject::tryUnshiftDenseElements(uint32_t count) { + MOZ_ASSERT(isExtensible()); + MOZ_ASSERT(count > 0); + + ObjectElements* header = getElementsHeader(); + uint32_t numShifted = header->numShiftedElements(); + + if (count > numShifted) { + // We need more elements than are easily available. Try to make space + // for more elements than we need (and shift the remaining ones) so + // that unshifting more elements later will be fast. + + // Don't bother reserving elements if the number of elements is small. + // Note that there's no technical reason for using this particular + // limit. + if (header->initializedLength <= 10 || + header->hasNonwritableArrayLength() || + MOZ_UNLIKELY(count > ObjectElements::MaxShiftedElements)) { + return false; + } + + MOZ_ASSERT(header->capacity >= header->initializedLength); + uint32_t unusedCapacity = header->capacity - header->initializedLength; + + // Determine toShift, the number of extra elements we want to make + // available. + uint32_t toShift = count - numShifted; + MOZ_ASSERT(toShift <= ObjectElements::MaxShiftedElements, + "count <= MaxShiftedElements so toShift <= MaxShiftedElements"); + + // Give up if we need to allocate more elements. + if (toShift > unusedCapacity) { + return false; + } + + // Move more elements than we need, so that other unshift calls will be + // fast. We just have to make sure we don't exceed unusedCapacity. + toShift = std::min(toShift + unusedCapacity / 2, unusedCapacity); + + // Ensure |numShifted + toShift| does not exceed MaxShiftedElements. + if (numShifted + toShift > ObjectElements::MaxShiftedElements) { + toShift = ObjectElements::MaxShiftedElements - numShifted; + } + + MOZ_ASSERT(count <= numShifted + toShift); + MOZ_ASSERT(numShifted + toShift <= ObjectElements::MaxShiftedElements); + MOZ_ASSERT(toShift <= unusedCapacity); + + // Now move/unshift the elements. + uint32_t initLen = header->initializedLength; + setDenseInitializedLength(initLen + toShift); + for (uint32_t i = 0; i < toShift; i++) { + initDenseElement(initLen + i, UndefinedValue()); + } + moveDenseElements(toShift, 0, initLen); + + // Shift the elements we just prepended. + shiftDenseElementsUnchecked(toShift); + + // We can now fall-through to the fast path below. + header = getElementsHeader(); + MOZ_ASSERT(header->numShiftedElements() == numShifted + toShift); + + numShifted = header->numShiftedElements(); + MOZ_ASSERT(count <= numShifted); + } + + elements_ -= count; + ObjectElements* newHeader = getElementsHeader(); + memmove(newHeader, header, sizeof(ObjectElements)); + + newHeader->unshiftShiftedElements(count); + + // Initialize to |undefined| to ensure pre-barriers don't see garbage. + for (uint32_t i = 0; i < count; i++) { + initDenseElement(i, UndefinedValue()); + } + + return true; +} + +// Given a requested capacity (in elements) and (potentially) the length of an +// array for which elements are being allocated, compute an actual allocation +// amount (in elements). (Allocation amounts include space for an +// ObjectElements instance, so a return value of |N| implies +// |N - ObjectElements::VALUES_PER_HEADER| usable elements.) +// +// The requested/actual allocation distinction is meant to: +// +// * preserve amortized O(N) time to add N elements; +// * minimize the number of unused elements beyond an array's length, and +// * provide at least SLOT_CAPACITY_MIN elements no matter what (so adding +// the first several elements to small arrays only needs one allocation). +// +// Note: the structure and behavior of this method follow along with +// UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy +// in one should generally be matched by the other. +/* static */ +bool NativeObject::goodElementsAllocationAmount(JSContext* cx, + uint32_t reqCapacity, + uint32_t length, + uint32_t* goodAmount) { + if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) { + ReportOutOfMemory(cx); + return false; + } + + uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER; + + // Handle "small" requests primarily by doubling. + const uint32_t Mebi = 1 << 20; + if (reqAllocated < Mebi) { + uint32_t amount = + mozilla::AssertedCast<uint32_t>(RoundUpPow2(reqAllocated)); + + // If |amount| would be 2/3 or more of the array's length, adjust + // it (up or down) to be equal to the array's length. This avoids + // allocating excess elements that aren't likely to be needed, either + // in this resizing or a subsequent one. The 2/3 factor is chosen so + // that exceptional resizings will at most triple the capacity, as + // opposed to the usual doubling. + uint32_t goodCapacity = amount - ObjectElements::VALUES_PER_HEADER; + if (length >= reqCapacity && goodCapacity > (length / 3) * 2) { + amount = length + ObjectElements::VALUES_PER_HEADER; + } + + if (amount < SLOT_CAPACITY_MIN) { + amount = SLOT_CAPACITY_MIN; + } + + *goodAmount = amount; + + return true; + } + + // The almost-doubling above wastes a lot of space for larger bucket sizes. + // For large amounts, switch to bucket sizes that obey this formula: + // + // count(n+1) = Math.ceil(count(n) * 1.125) + // + // where |count(n)| is the size of the nth bucket, measured in 2**20 slots. + // These bucket sizes still preserve amortized O(N) time to add N elements, + // just with a larger constant factor. + // + // The bucket size table below was generated with this JavaScript (and + // manual reformatting): + // + // for (let n = 1, i = 0; i < 34; i++) { + // print('0x' + (n * (1 << 20)).toString(16) + ', '); + // n = Math.ceil(n * 1.125); + // } + static constexpr uint32_t BigBuckets[] = { + 0x100000, 0x200000, 0x300000, 0x400000, 0x500000, 0x600000, + 0x700000, 0x800000, 0x900000, 0xb00000, 0xd00000, 0xf00000, + 0x1100000, 0x1400000, 0x1700000, 0x1a00000, 0x1e00000, 0x2200000, + 0x2700000, 0x2c00000, 0x3200000, 0x3900000, 0x4100000, 0x4a00000, + 0x5400000, 0x5f00000, 0x6b00000, 0x7900000, 0x8900000, 0x9b00000, + 0xaf00000, 0xc500000, 0xde00000, 0xfa00000}; + static_assert(BigBuckets[std::size(BigBuckets) - 1] <= + MAX_DENSE_ELEMENTS_ALLOCATION); + + // Pick the first bucket that'll fit |reqAllocated|. + for (uint32_t b : BigBuckets) { + if (b >= reqAllocated) { + *goodAmount = b; + return true; + } + } + + // Otherwise, return the maximum bucket size. + *goodAmount = MAX_DENSE_ELEMENTS_ALLOCATION; + return true; +} + +bool NativeObject::growElements(JSContext* cx, uint32_t reqCapacity) { + MOZ_ASSERT(isExtensible()); + MOZ_ASSERT(canHaveNonEmptyElements()); + + // If there are shifted elements, consider moving them first. If we don't + // move them here, the code below will include the shifted elements in the + // resize. + uint32_t numShifted = getElementsHeader()->numShiftedElements(); + if (numShifted > 0) { + // If the number of elements is small, it's cheaper to just move them as + // it may avoid a malloc/realloc. Note that there's no technical reason + // for using this particular value, but it works well in real-world use + // cases. + static const size_t MaxElementsToMoveEagerly = 20; + + if (getElementsHeader()->initializedLength <= MaxElementsToMoveEagerly) { + moveShiftedElements(); + } else { + maybeMoveShiftedElements(); + } + if (getDenseCapacity() >= reqCapacity) { + return true; + } + numShifted = getElementsHeader()->numShiftedElements(); + + // If |reqCapacity + numShifted| overflows, we just move all shifted + // elements to avoid the problem. + CheckedInt<uint32_t> checkedReqCapacity(reqCapacity); + checkedReqCapacity += numShifted; + if (MOZ_UNLIKELY(!checkedReqCapacity.isValid())) { + moveShiftedElements(); + numShifted = 0; + } + } + + uint32_t oldCapacity = getDenseCapacity(); + MOZ_ASSERT(oldCapacity < reqCapacity); + + uint32_t newAllocated = 0; + if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) { + MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length()); + MOZ_ASSERT(reqCapacity <= MAX_DENSE_ELEMENTS_COUNT); + // Preserve the |capacity <= length| invariant for arrays with + // non-writable length. See also js::ArraySetLength which initially + // enforces this requirement. + newAllocated = reqCapacity + numShifted + ObjectElements::VALUES_PER_HEADER; + } else { + if (!goodElementsAllocationAmount(cx, reqCapacity + numShifted, + getElementsHeader()->length, + &newAllocated)) { + return false; + } + } + + uint32_t newCapacity = + newAllocated - ObjectElements::VALUES_PER_HEADER - numShifted; + MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity); + + // If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become + // sparse. + MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT); + + uint32_t initlen = getDenseInitializedLength(); + + HeapSlot* oldHeaderSlots = + reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader()); + HeapSlot* newHeaderSlots; + uint32_t oldAllocated = 0; + if (hasDynamicElements()) { + MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT); + oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted; + + newHeaderSlots = ReallocateObjectBuffer<HeapSlot>( + cx, this, oldHeaderSlots, oldAllocated, newAllocated); + if (!newHeaderSlots) { + return false; // Leave elements at its old size. + } + } else { + newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated); + if (!newHeaderSlots) { + return false; // Leave elements at its old size. + } + PodCopy(newHeaderSlots, oldHeaderSlots, + ObjectElements::VALUES_PER_HEADER + initlen + numShifted); + } + + if (oldAllocated) { + RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot), + MemoryUse::ObjectElements); + } + + ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots); + elements_ = newheader->elements() + numShifted; + getElementsHeader()->capacity = newCapacity; + + Debug_SetSlotRangeToCrashOnTouch(elements_ + initlen, newCapacity - initlen); + + AddCellMemory(this, newAllocated * sizeof(HeapSlot), + MemoryUse::ObjectElements); + + return true; +} + +void NativeObject::shrinkElements(JSContext* cx, uint32_t reqCapacity) { + MOZ_ASSERT(canHaveNonEmptyElements()); + MOZ_ASSERT(reqCapacity >= getDenseInitializedLength()); + + if (!hasDynamicElements()) { + return; + } + + // If we have shifted elements, consider moving them. + uint32_t numShifted = getElementsHeader()->numShiftedElements(); + if (numShifted > 0) { + maybeMoveShiftedElements(); + numShifted = getElementsHeader()->numShiftedElements(); + } + + uint32_t oldCapacity = getDenseCapacity(); + MOZ_ASSERT(reqCapacity < oldCapacity); + + uint32_t newAllocated = 0; + MOZ_ALWAYS_TRUE(goodElementsAllocationAmount(cx, reqCapacity + numShifted, 0, + &newAllocated)); + MOZ_ASSERT(oldCapacity <= MAX_DENSE_ELEMENTS_COUNT); + + uint32_t oldAllocated = + oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted; + if (newAllocated == oldAllocated) { + return; // Leave elements at its old size. + } + + MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER); + uint32_t newCapacity = + newAllocated - ObjectElements::VALUES_PER_HEADER - numShifted; + MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT); + + HeapSlot* oldHeaderSlots = + reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader()); + HeapSlot* newHeaderSlots = ReallocateObjectBuffer<HeapSlot>( + cx, this, oldHeaderSlots, oldAllocated, newAllocated); + if (!newHeaderSlots) { + cx->recoverFromOutOfMemory(); + return; // Leave elements at its old size. + } + + RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot), + MemoryUse::ObjectElements); + + ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots); + elements_ = newheader->elements() + numShifted; + getElementsHeader()->capacity = newCapacity; + + AddCellMemory(this, newAllocated * sizeof(HeapSlot), + MemoryUse::ObjectElements); +} + +void NativeObject::shrinkCapacityToInitializedLength(JSContext* cx) { + // When an array's length becomes non-writable, writes to indexes greater + // greater than or equal to the length don't change the array. We handle this + // with a check for non-writable length in most places. But in JIT code every + // check counts -- so we piggyback the check on the already-required range + // check for |index < capacity| by making capacity of arrays with non-writable + // length never exceed the length. This mechanism is also used when an object + // becomes non-extensible. + + if (getElementsHeader()->numShiftedElements() > 0) { + moveShiftedElements(); + } + + ObjectElements* header = getElementsHeader(); + uint32_t len = header->initializedLength; + MOZ_ASSERT(header->capacity >= len); + if (header->capacity == len) { + return; + } + + shrinkElements(cx, len); + + header = getElementsHeader(); + uint32_t oldAllocated = header->numAllocatedElements(); + header->capacity = len; + + // The size of the memory allocation hasn't changed but we lose the actual + // capacity information. Make the associated size match the updated capacity. + if (!hasFixedElements()) { + uint32_t newAllocated = header->numAllocatedElements(); + RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot), + MemoryUse::ObjectElements); + AddCellMemory(this, newAllocated * sizeof(HeapSlot), + MemoryUse::ObjectElements); + } +} + +/* static */ +bool NativeObject::allocDictionarySlot(JSContext* cx, HandleNativeObject obj, + uint32_t* slotp) { + MOZ_ASSERT(obj->inDictionaryMode()); + + uint32_t slot = obj->slotSpan(); + MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass())); + + // Try to pull a free slot from the shape table's slot-number free list. + // Shapes without a ShapeTable have an empty free list, because we only + // purge ShapeTables with an empty free list. + { + AutoCheckCannotGC nogc; + if (ShapeTable* table = obj->lastProperty()->maybeTable(nogc)) { + uint32_t last = table->freeList(); + if (last != SHAPE_INVALID_SLOT) { +#ifdef DEBUG + MOZ_ASSERT(last < slot); + uint32_t next = obj->getSlot(last).toPrivateUint32(); + MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot); +#endif + + *slotp = last; + + const Value& vref = obj->getSlot(last); + table->setFreeList(vref.toPrivateUint32()); + obj->setSlot(last, UndefinedValue()); + return true; + } + } + } + + if (slot >= SHAPE_MAXIMUM_SLOT) { + ReportOutOfMemory(cx); + return false; + } + + *slotp = slot; + + return obj->ensureSlotsForDictionaryObject(cx, slot + 1); +} + +void NativeObject::freeSlot(JSContext* cx, uint32_t slot) { + MOZ_ASSERT(slot < slotSpan()); + + if (inDictionaryMode()) { + // Ensure we have a ShapeTable as it stores the object's free list (the + // list of available slots in dictionary objects). + AutoCheckCannotGC nogc; + if (ShapeTable* table = + lastProperty()->ensureTableForDictionary(cx, nogc)) { + uint32_t last = table->freeList(); + + // Can't afford to check the whole free list, but let's check the head. + MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, + last < slotSpan() && last != slot); + + // Place all freed slots other than reserved slots (bug 595230) on the + // dictionary's free list. + if (JSSLOT_FREE(getClass()) <= slot) { + MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan()); + setSlot(slot, PrivateUint32Value(last)); + table->setFreeList(slot); + return; + } + } else { + // OOM while creating the ShapeTable holding the free list. We can + // recover from it - it just means we won't be able to reuse this + // slot later. + cx->recoverFromOutOfMemory(); + } + } + setSlot(slot, UndefinedValue()); +} + +/* static */ +Shape* NativeObject::addDataProperty(JSContext* cx, HandleNativeObject obj, + HandlePropertyName name, uint32_t slot, + unsigned attrs) { + MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); + RootedId id(cx, NameToId(name)); + return addDataProperty(cx, obj, id, slot, attrs); +} + +template <AllowGC allowGC> +bool js::NativeLookupOwnProperty( + JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<jsid, allowGC>::HandleType id, + typename MaybeRooted<PropertyResult, allowGC>::MutableHandleType propp) { + bool done; + return LookupOwnPropertyInline<allowGC>(cx, obj, id, propp, &done); +} + +template bool js::NativeLookupOwnProperty<CanGC>( + JSContext* cx, HandleNativeObject obj, HandleId id, + MutableHandle<PropertyResult> propp); + +template bool js::NativeLookupOwnProperty<NoGC>( + JSContext* cx, NativeObject* const& obj, const jsid& id, + FakeMutableHandle<PropertyResult> propp); + +/*** [[DefineOwnProperty]] **************************************************/ + +static MOZ_ALWAYS_INLINE bool CallAddPropertyHook(JSContext* cx, + HandleNativeObject obj, + HandleId id, + HandleValue value) { + JSAddPropertyOp addProperty = obj->getClass()->getAddProperty(); + if (MOZ_UNLIKELY(addProperty)) { + MOZ_ASSERT(!cx->isHelperThreadContext()); + + if (!CallJSAddPropertyOp(cx, addProperty, obj, id, value)) { + NativeObject::removeProperty(cx, obj, id); + return false; + } + } + return true; +} + +static MOZ_ALWAYS_INLINE bool CallAddPropertyHookDense(JSContext* cx, + HandleNativeObject obj, + uint32_t index, + HandleValue value) { + // Inline addProperty for array objects. + if (obj->is<ArrayObject>()) { + ArrayObject* arr = &obj->as<ArrayObject>(); + uint32_t length = arr->length(); + if (index >= length) { + arr->setLength(index + 1); + } + return true; + } + + JSAddPropertyOp addProperty = obj->getClass()->getAddProperty(); + if (MOZ_UNLIKELY(addProperty)) { + MOZ_ASSERT(!cx->isHelperThreadContext()); + + RootedId id(cx, INT_TO_JSID(index)); + if (!CallJSAddPropertyOp(cx, addProperty, obj, id, value)) { + obj->setDenseElementHole(index); + return false; + } + } + return true; +} + +/** + * Determines whether a write to the given element on |arr| should fail + * because |arr| has a non-writable length, and writing that element would + * increase the length of the array. + */ +static bool WouldDefinePastNonwritableLength(ArrayObject* arr, uint32_t index) { + return !arr->lengthIsWritable() && index >= arr->length(); +} + +static bool ReshapeForShadowedPropSlow(JSContext* cx, HandleNativeObject obj, + HandleId id) { + MOZ_ASSERT(obj->isDelegate()); + + // Lookups on integer ids cannot be cached through prototypes. + if (JSID_IS_INT(id)) { + return true; + } + + RootedObject proto(cx, obj->staticPrototype()); + while (proto) { + // Lookups will not be cached through non-native protos. + if (!proto->isNative()) { + break; + } + + if (proto->as<NativeObject>().contains(cx, id)) { + return NativeObject::reshapeForShadowedProp(cx, proto.as<NativeObject>()); + } + + proto = proto->staticPrototype(); + } + + return true; +} + +static MOZ_ALWAYS_INLINE bool ReshapeForShadowedProp(JSContext* cx, + HandleObject obj, + HandleId id) { + // If |obj| is a prototype of another object, check if we're shadowing a + // property on its proto chain. In this case we need to reshape that object + // for shape teleporting to work correctly. + // + // See also the 'Shape Teleporting Optimization' comment in jit/CacheIR.cpp. + + // Inlined fast path for non-prototype/non-native objects. + if (!obj->isDelegate() || !obj->isNative()) { + return true; + } + + return ReshapeForShadowedPropSlow(cx, obj.as<NativeObject>(), id); +} + +/* static */ +bool NativeObject::reshapeForShadowedProp(JSContext* cx, + HandleNativeObject obj) { + return generateOwnShape(cx, obj); +} + +enum class IsAddOrChange { Add, AddOrChange }; + +template <IsAddOrChange AddOrChange> +static MOZ_ALWAYS_INLINE bool AddOrChangeProperty( + JSContext* cx, HandleNativeObject obj, HandleId id, + Handle<PropertyDescriptor> desc) { + desc.assertComplete(); + + if (!ReshapeForShadowedProp(cx, obj, id)) { + return false; + } + + // Use dense storage for new indexed properties where possible. + if (JSID_IS_INT(id) && !desc.getter() && !desc.setter() && + desc.attributes() == JSPROP_ENUMERATE && + (!obj->isIndexed() || !obj->containsPure(id)) && + !obj->is<TypedArrayObject>()) { + uint32_t index = JSID_TO_INT(id); + DenseElementResult edResult = obj->ensureDenseElements(cx, index, 1); + if (edResult == DenseElementResult::Failure) { + return false; + } + if (edResult == DenseElementResult::Success) { + obj->setDenseElement(index, desc.value()); + if (!CallAddPropertyHookDense(cx, obj, index, desc.value())) { + return false; + } + return true; + } + } + + // If we know this is a new property we can call addProperty instead of + // the slower putProperty. + if constexpr (AddOrChange == IsAddOrChange::Add) { + if (Shape::isDataProperty(desc.attributes(), desc.getter(), + desc.setter())) { + Shape* shape = NativeObject::addDataProperty( + cx, obj, id, SHAPE_INVALID_SLOT, desc.attributes()); + if (!shape) { + return false; + } + obj->initSlot(shape->slot(), desc.value()); + } else { + if (!NativeObject::addAccessorProperty( + cx, obj, id, desc.getter(), desc.setter(), desc.attributes())) { + return false; + } + } + } else { + if (Shape::isDataProperty(desc.attributes(), desc.getter(), + desc.setter())) { + Shape* shape = + NativeObject::putDataProperty(cx, obj, id, desc.attributes()); + if (!shape) { + return false; + } + obj->setSlot(shape->slot(), desc.value()); + } else { + if (!NativeObject::putAccessorProperty( + cx, obj, id, desc.getter(), desc.setter(), desc.attributes())) { + return false; + } + } + } + + // Clear any existing dense index after adding a sparse indexed property, + // and investigate converting the object to dense indexes. + if (JSID_IS_INT(id)) { + uint32_t index = JSID_TO_INT(id); + obj->removeDenseElementForSparseIndex(index); + DenseElementResult edResult = + NativeObject::maybeDensifySparseElements(cx, obj); + if (edResult == DenseElementResult::Failure) { + return false; + } + if (edResult == DenseElementResult::Success) { + MOZ_ASSERT(!desc.setter()); + return CallAddPropertyHookDense(cx, obj, index, desc.value()); + } + } + + return CallAddPropertyHook(cx, obj, id, desc.value()); +} + +// Versions of AddOrChangeProperty optimized for adding a plain data property. +// These function doesn't handle integer ids as we may have to store them in +// dense elements. +static MOZ_ALWAYS_INLINE bool AddDataProperty(JSContext* cx, + HandleNativeObject obj, + HandleId id, HandleValue v) { + MOZ_ASSERT(!JSID_IS_INT(id)); + + if (!ReshapeForShadowedProp(cx, obj, id)) { + return false; + } + + Shape* shape = NativeObject::addEnumerableDataProperty(cx, obj, id); + if (!shape) { + return false; + } + + obj->setSlot(shape->slot(), v); + + return CallAddPropertyHook(cx, obj, id, v); +} + +static MOZ_ALWAYS_INLINE bool AddDataPropertyNonDelegate(JSContext* cx, + HandlePlainObject obj, + HandleId id, + HandleValue v) { + MOZ_ASSERT(!JSID_IS_INT(id)); + MOZ_ASSERT(!obj->isDelegate()); + + // If we know this is a new property we can call addProperty instead of + // the slower putProperty. + Shape* shape = NativeObject::addEnumerableDataProperty(cx, obj, id); + if (!shape) { + return false; + } + + obj->setSlot(shape->slot(), v); + + MOZ_ASSERT(!obj->getClass()->getAddProperty()); + return true; +} + +static bool IsConfigurable(unsigned attrs) { + return (attrs & JSPROP_PERMANENT) == 0; +} +static bool IsEnumerable(unsigned attrs) { + return (attrs & JSPROP_ENUMERATE) != 0; +} +static bool IsWritable(unsigned attrs) { + return (attrs & JSPROP_READONLY) == 0; +} + +static bool IsAccessorDescriptor(unsigned attrs) { + return (attrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0; +} + +static bool IsDataDescriptor(unsigned attrs) { + MOZ_ASSERT((attrs & (JSPROP_IGNORE_VALUE | JSPROP_IGNORE_READONLY)) == 0); + return !IsAccessorDescriptor(attrs); +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool GetExistingProperty( + JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType receiver, + typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<Shape*, allowGC>::HandleType shape, + typename MaybeRooted<Value, allowGC>::MutableHandleType vp); + +static bool GetExistingPropertyValue(JSContext* cx, HandleNativeObject obj, + HandleId id, Handle<PropertyResult> prop, + MutableHandleValue vp) { + if (prop.isDenseElement()) { + vp.set(obj->getDenseElement(prop.denseElementIndex())); + return true; + } + if (prop.isTypedArrayElement()) { + size_t idx = prop.typedArrayElementIndex(); + return obj->as<TypedArrayObject>().getElement<CanGC>(cx, idx, vp); + } + + MOZ_ASSERT(!cx->isHelperThreadContext()); + + MOZ_ASSERT(prop.shape()->propid() == id); + MOZ_ASSERT(obj->contains(cx, prop.shape())); + + RootedValue receiver(cx, ObjectValue(*obj)); + RootedShape shape(cx, prop.shape()); + return GetExistingProperty<CanGC>(cx, receiver, obj, shape, vp); +} + +/* + * If desc is redundant with an existing own property obj[id], then set + * |*redundant = true| and return true. + */ +static bool DefinePropertyIsRedundant(JSContext* cx, HandleNativeObject obj, + HandleId id, Handle<PropertyResult> prop, + unsigned shapeAttrs, + Handle<PropertyDescriptor> desc, + bool* redundant) { + *redundant = false; + + if (desc.hasConfigurable() && + desc.configurable() != IsConfigurable(shapeAttrs)) { + return true; + } + if (desc.hasEnumerable() && desc.enumerable() != IsEnumerable(shapeAttrs)) { + return true; + } + if (desc.isDataDescriptor()) { + if (IsAccessorDescriptor(shapeAttrs)) { + return true; + } + if (desc.hasWritable() && desc.writable() != IsWritable(shapeAttrs)) { + return true; + } + if (desc.hasValue()) { + // Get the current value of the existing property. + RootedValue currentValue(cx); + if (prop.isNativeProperty() && prop.shape()->isDataProperty()) { + // Inline GetExistingPropertyValue in order to omit a type + // correctness assertion that's too strict for this particular + // call site. For details, see bug 1125624 comments 13-16. + currentValue.set(obj->getSlot(prop.shape()->slot())); + } else { + if (!GetExistingPropertyValue(cx, obj, id, prop, ¤tValue)) { + return false; + } + } + + // Don't call SameValue here to ensure we properly update distinct + // NaN values. + if (desc.value() != currentValue) { + return true; + } + } + + GetterOp existingGetterOp = + prop.isNativeProperty() ? prop.shape()->getter() : nullptr; + if (desc.getter() != existingGetterOp) { + return true; + } + + SetterOp existingSetterOp = + prop.isNativeProperty() ? prop.shape()->setter() : nullptr; + if (desc.setter() != existingSetterOp) { + return true; + } + } else { + if (desc.hasGetterObject() && + (!(shapeAttrs & JSPROP_GETTER) || + desc.getterObject() != prop.shape()->getterObject())) { + return true; + } + if (desc.hasSetterObject() && + (!(shapeAttrs & JSPROP_SETTER) || + desc.setterObject() != prop.shape()->setterObject())) { + return true; + } + } + + *redundant = true; + return true; +} + +bool js::NativeDefineProperty(JSContext* cx, HandleNativeObject obj, + HandleId id, Handle<PropertyDescriptor> desc_, + ObjectOpResult& result) { + desc_.assertValid(); + + // Section numbers and step numbers below refer to ES2018, draft rev + // 540b827fccf6122a984be99ab9af7be20e3b5562. + // + // This function aims to implement 9.1.6 [[DefineOwnProperty]] as well as + // the [[DefineOwnProperty]] methods described in 9.4.2.1 (arrays), 9.4.4.2 + // (arguments), and 9.4.5.3 (typed array views). + + // Dispense with custom behavior of exotic native objects first. + if (obj->is<ArrayObject>()) { + // 9.4.2.1 step 2. Redefining an array's length is very special. + Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>()); + if (id == NameToId(cx->names().length)) { + // 9.1.6.3 ValidateAndApplyPropertyDescriptor, step 7.a. + if (desc_.isAccessorDescriptor()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + + MOZ_ASSERT(!cx->isHelperThreadContext()); + return ArraySetLength(cx, arr, id, desc_.attributes(), desc_.value(), + result); + } + + // 9.4.2.1 step 3. Don't extend a fixed-length array. + uint32_t index; + if (IdIsIndex(id, &index)) { + if (WouldDefinePastNonwritableLength(arr, index)) { + return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH); + } + } + } else if (obj->is<TypedArrayObject>()) { + // 9.4.5.3 step 3. Indexed properties of typed arrays are special. + Rooted<TypedArrayObject*> tobj(cx, &obj->as<TypedArrayObject>()); + mozilla::Maybe<uint64_t> index; + JS_TRY_VAR_OR_RETURN_FALSE(cx, index, IsTypedArrayIndex(cx, id)); + + if (index) { + MOZ_ASSERT(!cx->isHelperThreadContext()); + return DefineTypedArrayElement(cx, tobj, index.value(), desc_, result); + } + } else if (obj->is<ArgumentsObject>()) { + Rooted<ArgumentsObject*> argsobj(cx, &obj->as<ArgumentsObject>()); + if (id == NameToId(cx->names().length)) { + // Either we are resolving the .length property on this object, + // or redefining it. In the latter case only, we must reify the + // property. To distinguish the two cases, we note that when + // resolving, the JSPROP_RESOLVING mask is set; whereas the first + // time it is redefined, it isn't set. + if ((desc_.attributes() & JSPROP_RESOLVING) == 0) { + if (!ArgumentsObject::reifyLength(cx, argsobj)) { + return false; + } + } + } else if (JSID_IS_SYMBOL(id) && + JSID_TO_SYMBOL(id) == cx->wellKnownSymbols().iterator) { + // Do same thing as .length for [@@iterator]. + if ((desc_.attributes() & JSPROP_RESOLVING) == 0) { + if (!ArgumentsObject::reifyIterator(cx, argsobj)) { + return false; + } + } + } else if (JSID_IS_INT(id)) { + if ((desc_.attributes() & JSPROP_RESOLVING) == 0) { + argsobj->markElementOverridden(); + } + } + } + + // 9.1.6.1 OrdinaryDefineOwnProperty step 1. + Rooted<PropertyResult> prop(cx); + if (desc_.attributes() & JSPROP_RESOLVING) { + // We are being called from a resolve or enumerate hook to reify a + // lazily-resolved property. To avoid reentering the resolve hook and + // recursing forever, skip the resolve hook when doing this lookup. + if (!NativeLookupOwnPropertyNoResolve(cx, obj, id, &prop)) { + return false; + } + } else { + if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) { + return false; + } + } + + // From this point, the step numbers refer to + // 9.1.6.3, ValidateAndApplyPropertyDescriptor. + // Step 1 is a redundant assertion. + + // Filling in desc: Here we make a copy of the desc_ argument. We will turn + // it into a complete descriptor before updating obj. The spec algorithm + // does not explicitly do this, but the end result is the same. Search for + // "fill in" below for places where the filling-in actually occurs. + Rooted<PropertyDescriptor> desc(cx, desc_); + + // Step 2. + if (!prop) { + // Note: We are sharing the property definition machinery with private + // fields. Private fields may be added to non-extensible objects. + if (!obj->isExtensible() && !id.isPrivateName()) { + return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE); + } + + // Fill in missing desc fields with defaults. + CompletePropertyDescriptor(&desc); + + if (!AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc)) { + return false; + } + return result.succeed(); + } + + // Step 3 and 7.a.i.3, 8.a.iii, 10 (partially). We use shapeAttrs as a + // stand-in for shape in many places below, since shape might not be a + // pointer to a real Shape (see IsImplicitDenseOrTypedArrayElement). + unsigned shapeAttrs = GetPropertyAttributes(obj, prop); + bool redundant; + if (!DefinePropertyIsRedundant(cx, obj, id, prop, shapeAttrs, desc, + &redundant)) { + return false; + } + if (redundant) { + return result.succeed(); + } + + // Step 4. + if (!IsConfigurable(shapeAttrs)) { + if (desc.hasConfigurable() && desc.configurable()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + if (desc.hasEnumerable() && desc.enumerable() != IsEnumerable(shapeAttrs)) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + } + + // Fill in desc.[[Configurable]] and desc.[[Enumerable]] if missing. + if (!desc.hasConfigurable()) { + desc.setConfigurable(IsConfigurable(shapeAttrs)); + } + if (!desc.hasEnumerable()) { + desc.setEnumerable(IsEnumerable(shapeAttrs)); + } + + // Steps 5-8. + if (desc.isGenericDescriptor()) { + // Step 5. No further validation is required. + + // Fill in desc. A generic descriptor has none of these fields, so copy + // everything from shape. + MOZ_ASSERT(!desc.hasValue()); + MOZ_ASSERT(!desc.hasWritable()); + MOZ_ASSERT(!desc.hasGetterObject()); + MOZ_ASSERT(!desc.hasSetterObject()); + if (IsDataDescriptor(shapeAttrs)) { + RootedValue currentValue(cx); + if (!GetExistingPropertyValue(cx, obj, id, prop, ¤tValue)) { + return false; + } + desc.setValue(currentValue); + desc.setWritable(IsWritable(shapeAttrs)); + } else { + desc.setGetterObject(prop.shape()->getterObject()); + desc.setSetterObject(prop.shape()->setterObject()); + } + } else if (desc.isDataDescriptor() != IsDataDescriptor(shapeAttrs)) { + // Step 6. + if (!IsConfigurable(shapeAttrs)) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + + // Fill in desc fields with default values (steps 6.b.i and 6.c.i). + CompletePropertyDescriptor(&desc); + } else if (desc.isDataDescriptor()) { + // Step 7. + bool frozen = !IsConfigurable(shapeAttrs) && !IsWritable(shapeAttrs); + + // Step 7.a.i.1. + if (frozen && desc.hasWritable() && desc.writable()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + + if (frozen || !desc.hasValue()) { + RootedValue currentValue(cx); + if (!GetExistingPropertyValue(cx, obj, id, prop, ¤tValue)) { + return false; + } + + if (!desc.hasValue()) { + // Fill in desc.[[Value]]. + desc.setValue(currentValue); + } else { + // Step 7.a.i.2. + bool same; + MOZ_ASSERT(!cx->isHelperThreadContext()); + if (!SameValue(cx, desc.value(), currentValue, &same)) { + return false; + } + if (!same) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + } + } + + // Step 7.a.i.3. + if (frozen) { + return result.succeed(); + } + + // Fill in desc.[[Writable]]. + if (!desc.hasWritable()) { + desc.setWritable(IsWritable(shapeAttrs)); + } + } else { + // Step 8. + MOZ_ASSERT(prop.shape()->isAccessorDescriptor()); + MOZ_ASSERT(desc.isAccessorDescriptor()); + + // The spec says to use SameValue, but since the values in + // question are objects, we can just compare pointers. + if (desc.hasSetterObject()) { + // Step 8.a.i. + if (!IsConfigurable(shapeAttrs) && + desc.setterObject() != prop.shape()->setterObject()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + } else { + // Fill in desc.[[Set]] from shape. + desc.setSetterObject(prop.shape()->setterObject()); + } + if (desc.hasGetterObject()) { + // Step 8.a.ii. + if (!IsConfigurable(shapeAttrs) && + desc.getterObject() != prop.shape()->getterObject()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + } else { + // Fill in desc.[[Get]] from shape. + desc.setGetterObject(prop.shape()->getterObject()); + } + + // Step 8.a.iii (Omitted). + } + + // Step 9. + if (!AddOrChangeProperty<IsAddOrChange::AddOrChange>(cx, obj, id, desc)) { + return false; + } + + // Step 10. + return result.succeed(); +} + +bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj, + HandleId id, HandleValue value, + unsigned attrs, ObjectOpResult& result) { + Rooted<PropertyDescriptor> desc(cx); + desc.initFields(nullptr, value, attrs, nullptr, nullptr); + return NativeDefineProperty(cx, obj, id, desc, result); +} + +bool js::NativeDefineAccessorProperty(JSContext* cx, HandleNativeObject obj, + HandleId id, GetterOp getter, + SetterOp setter, unsigned attrs) { + Rooted<PropertyDescriptor> desc(cx); + desc.initFields(nullptr, UndefinedHandleValue, attrs, getter, setter); + + ObjectOpResult result; + if (!NativeDefineProperty(cx, obj, id, desc, result)) { + return false; + } + + if (!result) { + // Off-thread callers should not get here: they must call this + // function only with known-valid arguments. Populating a new + // PlainObject with configurable properties is fine. + MOZ_ASSERT(!cx->isHelperThreadContext()); + result.reportError(cx, obj, id); + return false; + } + + return true; +} + +bool js::NativeDefineAccessorProperty(JSContext* cx, HandleNativeObject obj, + HandleId id, HandleObject getter, + HandleObject setter, unsigned attrs) { + Rooted<PropertyDescriptor> desc(cx); + { + GetterOp getterOp = JS_DATA_TO_FUNC_PTR(GetterOp, getter.get()); + SetterOp setterOp = JS_DATA_TO_FUNC_PTR(SetterOp, setter.get()); + desc.initFields(nullptr, UndefinedHandleValue, attrs, getterOp, setterOp); + } + + ObjectOpResult result; + if (!NativeDefineProperty(cx, obj, id, desc, result)) { + return false; + } + + if (!result) { + // Off-thread callers should not get here: they must call this + // function only with known-valid arguments. Populating a new + // PlainObject with configurable properties is fine. + MOZ_ASSERT(!cx->isHelperThreadContext()); + result.reportError(cx, obj, id); + return false; + } + + return true; +} + +bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj, + HandleId id, HandleValue value, + unsigned attrs) { + ObjectOpResult result; + if (!NativeDefineDataProperty(cx, obj, id, value, attrs, result)) { + return false; + } + if (!result) { + // Off-thread callers should not get here: they must call this + // function only with known-valid arguments. Populating a new + // PlainObject with configurable properties is fine. + MOZ_ASSERT(!cx->isHelperThreadContext()); + result.reportError(cx, obj, id); + return false; + } + return true; +} + +bool js::NativeDefineDataProperty(JSContext* cx, HandleNativeObject obj, + PropertyName* name, HandleValue value, + unsigned attrs) { + RootedId id(cx, NameToId(name)); + return NativeDefineDataProperty(cx, obj, id, value, attrs); +} + +static bool DefineNonexistentProperty(JSContext* cx, HandleNativeObject obj, + HandleId id, HandleValue v, + ObjectOpResult& result) { + // Optimized NativeDefineProperty() version for known absent properties. + + // Dispense with custom behavior of exotic native objects first. + if (obj->is<ArrayObject>()) { + // Array's length property is non-configurable, so we shouldn't + // encounter it in this function. + MOZ_ASSERT(id != NameToId(cx->names().length)); + + // 9.4.2.1 step 3. Don't extend a fixed-length array. + uint32_t index; + if (IdIsIndex(id, &index)) { + if (WouldDefinePastNonwritableLength(&obj->as<ArrayObject>(), index)) { + return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH); + } + } + } else if (obj->is<TypedArrayObject>()) { + // 9.4.5.5 step 2. Indexed properties of typed arrays are special. + mozilla::Maybe<uint64_t> index; + JS_TRY_VAR_OR_RETURN_FALSE(cx, index, IsTypedArrayIndex(cx, id)); + + if (index) { + // This method is only called for non-existent properties, which + // means any absent indexed property must be out of range. + MOZ_ASSERT(index.value() >= obj->as<TypedArrayObject>().length().get()); + + // The following steps refer to 9.4.5.11 IntegerIndexedElementSet. + + // Step 1 is enforced by the caller. + + // Steps 2-3. + // We still need to call ToNumber or ToBigInt, because of its + // possible side effects. + if (!obj->as<TypedArrayObject>().convertForSideEffect(cx, v)) { + return false; + } + + // Step 4 (nothing to do, the index is out of range). + + // Step 5. + return result.succeed(); + } + } else if (obj->is<ArgumentsObject>()) { + // If this method is called with either |length| or |@@iterator|, the + // property was previously deleted and hence should already be marked + // as overridden. + MOZ_ASSERT_IF(id == NameToId(cx->names().length), + obj->as<ArgumentsObject>().hasOverriddenLength()); + MOZ_ASSERT_IF(JSID_IS_SYMBOL(id) && + JSID_TO_SYMBOL(id) == cx->wellKnownSymbols().iterator, + obj->as<ArgumentsObject>().hasOverriddenIterator()); + + // We still need to mark any element properties as overridden. + if (JSID_IS_INT(id)) { + obj->as<ArgumentsObject>().markElementOverridden(); + } + } + +#ifdef DEBUG + Rooted<PropertyResult> prop(cx); + if (!NativeLookupOwnPropertyNoResolve(cx, obj, id, &prop)) { + return false; + } + MOZ_ASSERT(!prop, "didn't expect to find an existing property"); +#endif + + // 9.1.6.3, ValidateAndApplyPropertyDescriptor. + // Step 1 is a redundant assertion, step 3 and later don't apply here. + + // Step 2. + if (!obj->isExtensible()) { + return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE); + } + + if (JSID_IS_INT(id)) { + // This might be a dense element. Use AddOrChangeProperty as it knows + // how to deal with that. + + Rooted<PropertyDescriptor> desc(cx); + desc.setDataDescriptor(v, JSPROP_ENUMERATE); + + if (!AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc)) { + return false; + } + } else { + if (!AddDataProperty(cx, obj, id, v)) { + return false; + } + } + + return result.succeed(); +} + +bool js::AddOrUpdateSparseElementHelper(JSContext* cx, HandleArrayObject obj, + int32_t int_id, HandleValue v, + bool strict) { + MOZ_ASSERT(INT_FITS_IN_JSID(int_id)); + RootedId id(cx, INT_TO_JSID(int_id)); + + // This helper doesn't handle the case where the index may be in the dense + // elements + MOZ_ASSERT(int_id >= 0); + MOZ_ASSERT(uint32_t(int_id) >= obj->getDenseInitializedLength()); + + // First decide if this is an add or an update. Because the IC guards have + // already ensured this exists exterior to the dense array range, and the + // prototype checks have ensured there are no indexes on the prototype, we + // can use the shape lineage to find the element if it exists: + RootedShape shape(cx, obj->lastProperty()->search(cx, id)); + + // If we didn't find the shape, we're on the add path: delegate to + // AddSparseElement: + if (shape == nullptr) { + Rooted<PropertyDescriptor> desc(cx); + desc.setDataDescriptor(v, JSPROP_ENUMERATE); + desc.assertComplete(); + + return AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc); + } + + // At this point we're updating a property: See SetExistingProperty + if (shape->writable() && shape->isDataProperty()) { + obj->setSlot(shape->slot(), v); + return true; + } + + // We don't know exactly what this object looks like, hit the slowpath. + RootedValue receiver(cx, ObjectValue(*obj)); + JS::ObjectOpResult result; + return SetProperty(cx, obj, id, v, receiver, result) && + result.checkStrictModeError(cx, obj, id, strict); +} + +/*** [[HasProperty]] ********************************************************/ + +// ES6 draft rev31 9.1.7.1 OrdinaryHasProperty +bool js::NativeHasProperty(JSContext* cx, HandleNativeObject obj, HandleId id, + bool* foundp) { + RootedNativeObject pobj(cx, obj); + Rooted<PropertyResult> prop(cx); + + // This loop isn't explicit in the spec algorithm. See the comment on step + // 7.a. below. + for (;;) { + // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.) + bool done; + if (!LookupOwnPropertyInline<CanGC>(cx, pobj, id, &prop, &done)) { + return false; + } + + // Step 4. + if (prop) { + *foundp = true; + return true; + } + + // Step 5-6. The check for 'done' on this next line is tricky. + // done can be true in exactly these unlikely-sounding cases: + // - We're looking up an element, and pobj is a TypedArray that + // doesn't have that many elements. + // - We're being called from a resolve hook to assign to the property + // being resolved. + // What they all have in common is we do not want to keep walking + // the prototype chain, and always claim that the property + // doesn't exist. + JSObject* proto = done ? nullptr : pobj->staticPrototype(); + + // Step 8. + if (!proto) { + *foundp = false; + return true; + } + + // Step 7.a. If the prototype is also native, this step is a + // recursive tail call, and we don't need to go through all the + // plumbing of HasProperty; the top of the loop is where + // we're going to end up anyway. But if pobj is non-native, + // that optimization would be incorrect. + if (!proto->isNative()) { + RootedObject protoRoot(cx, proto); + return HasProperty(cx, protoRoot, id, foundp); + } + + pobj = &proto->as<NativeObject>(); + } +} + +/*** [[GetOwnPropertyDescriptor]] *******************************************/ + +bool js::NativeGetOwnPropertyDescriptor( + JSContext* cx, HandleNativeObject obj, HandleId id, + MutableHandle<PropertyDescriptor> desc) { + Rooted<PropertyResult> prop(cx); + if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) { + return false; + } + if (!prop) { + desc.object().set(nullptr); + return true; + } + + desc.setAttributes(GetPropertyAttributes(obj, prop)); + if (desc.isAccessorDescriptor()) { + // The result of GetOwnPropertyDescriptor() must be either undefined or + // a complete property descriptor (per ES6 draft rev 32 (2015 Feb 2) + // 6.1.7.3, Invariants of the Essential Internal Methods). + // + // It is an unfortunate fact that in SM, properties can exist that have + // JSPROP_GETTER or JSPROP_SETTER but not both. In these cases, rather + // than return true with desc incomplete, we fill out the missing + // getter or setter with a null, following CompletePropertyDescriptor. + if (desc.hasGetterObject()) { + desc.setGetterObject(prop.shape()->getterObject()); + } else { + desc.setGetterObject(nullptr); + desc.attributesRef() |= JSPROP_GETTER; + } + if (desc.hasSetterObject()) { + desc.setSetterObject(prop.shape()->setterObject()); + } else { + desc.setSetterObject(nullptr); + desc.attributesRef() |= JSPROP_SETTER; + } + + desc.value().setUndefined(); + } else { + // This is either a straight-up data property or (rarely) a + // property with a JSGetterOp/JSSetterOp. The latter must be + // reported to the caller as a plain data property, so clear + // desc.getter/setter, and mask away the SHARED bit. + desc.setGetter(nullptr); + desc.setSetter(nullptr); + + if (prop.isDenseElement()) { + desc.value().set(obj->getDenseElement(prop.denseElementIndex())); + } else if (prop.isTypedArrayElement()) { + size_t idx = prop.typedArrayElementIndex(); + if (!obj->as<TypedArrayObject>().getElement<CanGC>(cx, idx, + desc.value())) { + return false; + } + } else { + RootedShape shape(cx, prop.shape()); + if (!NativeGetExistingProperty(cx, obj, obj, shape, desc.value())) { + return false; + } + } + } + + desc.object().set(obj); + desc.assertComplete(); + return true; +} + +/*** [[Get]] ****************************************************************/ + +static inline bool CallGetter(JSContext* cx, HandleObject obj, + HandleValue receiver, HandleShape shape, + MutableHandleValue vp) { + MOZ_ASSERT(!shape->hasDefaultGetter()); + + if (shape->hasGetterValue()) { + RootedValue getter(cx, shape->getterValue()); + return js::CallGetter(cx, receiver, getter, vp); + } + + // In contrast to normal getters JSGetterOps always want the holder. + RootedId id(cx, shape->propid()); + return CallJSGetterOp(cx, shape->getterOp(), obj, id, vp); +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool GetExistingProperty( + JSContext* cx, typename MaybeRooted<Value, allowGC>::HandleType receiver, + typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<Shape*, allowGC>::HandleType shape, + typename MaybeRooted<Value, allowGC>::MutableHandleType vp) { + if (shape->isDataProperty()) { + MOZ_ASSERT(shape->hasDefaultGetter()); + + vp.set(obj->getSlot(shape->slot())); + return true; + } + + vp.setUndefined(); + + if (shape->hasDefaultGetter()) { + return true; + } + + if constexpr (!allowGC) { + return false; + } else { + return CallGetter(cx, obj, receiver, shape, vp); + } +} + +bool js::NativeGetExistingProperty(JSContext* cx, HandleObject receiver, + HandleNativeObject obj, HandleShape shape, + MutableHandleValue vp) { + RootedValue receiverValue(cx, ObjectValue(*receiver)); + return GetExistingProperty<CanGC>(cx, receiverValue, obj, shape, vp); +} + +enum IsNameLookup { NotNameLookup = false, NameLookup = true }; + +/* + * Finish getting the property `receiver[id]` after looking at every object on + * the prototype chain and not finding any such property. + * + * Per the spec, this should just set the result to `undefined` and call it a + * day. However this function also runs when we're evaluating an + * expression that's an Identifier (that is, an unqualified name lookup), + * so we need to figure out if that's what's happening and throw + * a ReferenceError if so. + */ +static bool GetNonexistentProperty(JSContext* cx, HandleId id, + IsNameLookup nameLookup, + MutableHandleValue vp) { + vp.setUndefined(); + + // If we are doing a name lookup, this is a ReferenceError. + if (nameLookup) { + ReportIsNotDefined(cx, id); + return false; + } + + // Otherwise, just return |undefined|. + return true; +} + +// The NoGC version of GetNonexistentProperty, present only to make types line +// up. +bool GetNonexistentProperty(JSContext* cx, const jsid& id, + IsNameLookup nameLookup, + FakeMutableHandle<Value> vp) { + return false; +} + +static inline bool GeneralizedGetProperty(JSContext* cx, HandleObject obj, + HandleId id, HandleValue receiver, + IsNameLookup nameLookup, + MutableHandleValue vp) { + if (!CheckRecursionLimit(cx)) { + return false; + } + if (nameLookup) { + // When nameLookup is true, GetProperty implements ES6 rev 34 (2015 Feb + // 20) 8.1.1.2.6 GetBindingValue, with step 3 (the call to HasProperty) + // and step 6 (the call to Get) fused so that only a single lookup is + // needed. + // + // If we get here, we've reached a non-native object. Fall back on the + // algorithm as specified, with two separate lookups. (Note that we + // throw ReferenceErrors regardless of strictness, technically a bug.) + + bool found; + if (!HasProperty(cx, obj, id, &found)) { + return false; + } + if (!found) { + ReportIsNotDefined(cx, id); + return false; + } + } + + return GetProperty(cx, obj, receiver, id, vp); +} + +static inline bool GeneralizedGetProperty(JSContext* cx, JSObject* obj, jsid id, + const Value& receiver, + IsNameLookup nameLookup, + FakeMutableHandle<Value> vp) { + if (!CheckRecursionLimitDontReport(cx)) { + return false; + } + if (nameLookup) { + return false; + } + return GetPropertyNoGC(cx, obj, receiver, id, vp.address()); +} + +bool js::GetSparseElementHelper(JSContext* cx, HandleArrayObject obj, + int32_t int_id, MutableHandleValue result) { + // Callers should have ensured that this object has a static prototype. + MOZ_ASSERT(obj->hasStaticPrototype()); + + // Indexed properties can not exist on the prototype chain. + MOZ_ASSERT_IF(obj->staticPrototype() != nullptr, + !ObjectMayHaveExtraIndexedProperties(obj->staticPrototype())); + + MOZ_ASSERT(INT_FITS_IN_JSID(int_id)); + RootedId id(cx, INT_TO_JSID(int_id)); + + Shape* rawShape = obj->lastProperty()->search(cx, id); + if (!rawShape) { + // Property not found, return directly. + result.setUndefined(); + return true; + } + + RootedValue receiver(cx, ObjectValue(*obj)); + RootedShape shape(cx, rawShape); + return GetExistingProperty<CanGC>(cx, receiver, obj, shape, result); +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool NativeGetPropertyInline( + JSContext* cx, typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<Value, allowGC>::HandleType receiver, + typename MaybeRooted<jsid, allowGC>::HandleType id, IsNameLookup nameLookup, + typename MaybeRooted<Value, allowGC>::MutableHandleType vp) { + typename MaybeRooted<NativeObject*, allowGC>::RootType pobj(cx, obj); + typename MaybeRooted<PropertyResult, allowGC>::RootType prop(cx); + + // This loop isn't explicit in the spec algorithm. See the comment on step + // 4.d below. + for (;;) { + // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.) + bool done; + if (!LookupOwnPropertyInline<allowGC>(cx, pobj, id, &prop, &done)) { + return false; + } + + if (prop) { + // Steps 5-8. Special case for dense elements because + // GetExistingProperty doesn't support those. + if (prop.isDenseElement()) { + vp.set(pobj->getDenseElement(prop.denseElementIndex())); + return true; + } + if (prop.isTypedArrayElement()) { + size_t idx = prop.typedArrayElementIndex(); + auto* tarr = &pobj->template as<TypedArrayObject>(); + return tarr->template getElement<allowGC>(cx, idx, vp); + } + + typename MaybeRooted<Shape*, allowGC>::RootType shape(cx, prop.shape()); + return GetExistingProperty<allowGC>(cx, receiver, pobj, shape, vp); + } + + // Steps 4.a-b. The check for 'done' on this next line is tricky. + // done can be true in exactly these unlikely-sounding cases: + // - We're looking up an element, and pobj is a TypedArray that + // doesn't have that many elements. + // - We're being called from a resolve hook to assign to the property + // being resolved. + // What they all have in common is we do not want to keep walking + // the prototype chain. + JSObject* proto = done ? nullptr : pobj->staticPrototype(); + + // Step 4.c. The spec algorithm simply returns undefined if proto is + // null, but see the comment on GetNonexistentProperty. + if (!proto) { + return GetNonexistentProperty(cx, id, nameLookup, vp); + } + + // Step 4.d. If the prototype is also native, this step is a + // recursive tail call, and we don't need to go through all the + // plumbing of JSObject::getGeneric; the top of the loop is where + // we're going to end up anyway. But if pobj is non-native, + // that optimization would be incorrect. + if (proto->getOpsGetProperty()) { + RootedObject protoRoot(cx, proto); + return GeneralizedGetProperty(cx, protoRoot, id, receiver, nameLookup, + vp); + } + + pobj = &proto->as<NativeObject>(); + } +} + +bool js::NativeGetProperty(JSContext* cx, HandleNativeObject obj, + HandleValue receiver, HandleId id, + MutableHandleValue vp) { + return NativeGetPropertyInline<CanGC>(cx, obj, receiver, id, NotNameLookup, + vp); +} + +bool js::NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj, + const Value& receiver, jsid id, Value* vp) { + AutoAssertNoPendingException noexc(cx); + return NativeGetPropertyInline<NoGC>(cx, obj, receiver, id, NotNameLookup, + vp); +} + +bool js::NativeGetElement(JSContext* cx, HandleNativeObject obj, + HandleValue receiver, int32_t index, + MutableHandleValue vp) { + RootedId id(cx); + + if (MOZ_LIKELY(index >= 0)) { + if (!IndexToId(cx, index, &id)) { + return false; + } + } else { + RootedValue indexVal(cx, Int32Value(index)); + if (!PrimitiveValueToId<CanGC>(cx, indexVal, &id)) { + return false; + } + } + return NativeGetProperty(cx, obj, receiver, id, vp); +} + +bool js::GetNameBoundInEnvironment(JSContext* cx, HandleObject envArg, + HandleId id, MutableHandleValue vp) { + // Manually unwrap 'with' environments to prevent looking up @@unscopables + // twice. + // + // This is unfortunate because internally, the engine does not distinguish + // HasProperty from HasBinding: both are implemented as a HasPropertyOp + // hook on a WithEnvironmentObject. + // + // In the case of attempting to get the value of a binding already looked up + // via JSOp::BindName, calling HasProperty on the WithEnvironmentObject is + // equivalent to calling HasBinding a second time. This results in the + // incorrect behavior of performing the @@unscopables check again. + RootedObject env(cx, MaybeUnwrapWithEnvironment(envArg)); + RootedValue receiver(cx, ObjectValue(*env)); + if (env->getOpsGetProperty()) { + return GeneralizedGetProperty(cx, env, id, receiver, NameLookup, vp); + } + return NativeGetPropertyInline<CanGC>(cx, env.as<NativeObject>(), receiver, + id, NameLookup, vp); +} + +/*** [[Set]] ****************************************************************/ + +static bool MaybeReportUndeclaredVarAssignment(JSContext* cx, HandleId id) { + { + jsbytecode* pc; + JSScript* script = + cx->currentScript(&pc, JSContext::AllowCrossRealm::Allow); + if (!script) { + return true; + } + + if (!IsStrictSetPC(pc)) { + return true; + } + } + + UniqueChars bytes = + IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier); + if (!bytes) { + return false; + } + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNDECLARED_VAR, + bytes.get()); + return false; +} + +/* + * Finish assignment to a shapeful data property of a native object obj. This + * conforms to no standard and there is a lot of legacy baggage here. + */ +static bool NativeSetExistingDataProperty(JSContext* cx, HandleNativeObject obj, + HandleShape shape, HandleValue v, + ObjectOpResult& result) { + MOZ_ASSERT(obj->isNative()); + MOZ_ASSERT(shape->isDataDescriptor()); + + if (shape->hasDefaultSetter()) { + if (shape->isDataProperty()) { + // The common path. Standard data property. + obj->setSlot(shape->slot(), v); + return result.succeed(); + } + + // Bizarre: shared (slotless) property that's writable but has no + // JSSetterOp. JS code can't define such a property, but it can be done + // through the JSAPI. Treat it as non-writable. + return result.fail(JSMSG_GETTER_ONLY); + } + + MOZ_ASSERT(!obj->is<WithEnvironmentObject>()); // See bug 1128681. + + RootedId id(cx, shape->propid()); + return CallJSSetterOp(cx, shape->setterOp(), obj, id, v, result); +} + +/* + * When a [[Set]] operation finds no existing property with the given id + * or finds a writable data property on the prototype chain, we end up here. + * Finish the [[Set]] by defining a new property on receiver. + * + * This implements ES6 draft rev 28, 9.1.9 [[Set]] steps 5.b-f, but it + * is really old code and there are a few barnacles. + */ +bool js::SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v, + HandleValue receiverValue, + ObjectOpResult& result) { + // Step 5.b. + if (!receiverValue.isObject()) { + return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER); + } + RootedObject receiver(cx, &receiverValue.toObject()); + + bool existing; + { + // Steps 5.c-d. + Rooted<PropertyDescriptor> desc(cx); + if (!GetOwnPropertyDescriptor(cx, receiver, id, &desc)) { + return false; + } + + existing = !!desc.object(); + + // Step 5.e. + if (existing) { + // Step 5.e.i. + if (desc.isAccessorDescriptor()) { + return result.fail(JSMSG_OVERWRITING_ACCESSOR); + } + + // Step 5.e.ii. + if (!desc.writable()) { + return result.fail(JSMSG_READ_ONLY); + } + } + } + + // Purge the property cache of now-shadowed id in receiver's environment + // chain. + if (!ReshapeForShadowedProp(cx, receiver, id)) { + return false; + } + + // Steps 5.e.iii-iv. and 5.f.i. Define the new data property. + unsigned attrs = existing ? JSPROP_IGNORE_ENUMERATE | JSPROP_IGNORE_READONLY | + JSPROP_IGNORE_PERMANENT + : JSPROP_ENUMERATE; + return DefineDataProperty(cx, receiver, id, v, attrs, result); +} + +// When setting |id| for |receiver| and |obj| has no property for id, continue +// the search up the prototype chain. +bool js::SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, + HandleValue v, HandleValue receiver, + ObjectOpResult& result) { + MOZ_ASSERT(!obj->is<ProxyObject>()); + + RootedObject proto(cx, obj->staticPrototype()); + if (proto) { + return SetProperty(cx, proto, id, v, receiver, result); + } + + return SetPropertyByDefining(cx, id, v, receiver, result); +} + +/* + * Implement "the rest of" assignment to a property when no property + * receiver[id] was found anywhere on the prototype chain. + * + * FIXME: This should be updated to follow ES6 draft rev 28, section 9.1.9, + * steps 4.d.i and 5. + */ +template <QualifiedBool IsQualified> +static bool SetNonexistentProperty(JSContext* cx, HandleNativeObject obj, + HandleId id, HandleValue v, + HandleValue receiver, + ObjectOpResult& result) { + if (!IsQualified && receiver.isObject() && + receiver.toObject().isUnqualifiedVarObj()) { + if (!MaybeReportUndeclaredVarAssignment(cx, id)) { + return false; + } + } + + // Pure optimization for the common case. There's no point performing the + // lookup in step 5.c again, as our caller just did it for us. + if (IsQualified && receiver.isObject() && obj == &receiver.toObject()) { + // Ensure that a custom GetOwnPropertyOp, if present, doesn't + // introduce additional properties which weren't previously found by + // LookupOwnProperty. +#ifdef DEBUG + if (GetOwnPropertyOp op = obj->getOpsGetOwnPropertyDescriptor()) { + Rooted<PropertyDescriptor> desc(cx); + if (!op(cx, obj, id, &desc)) { + return false; + } + + MOZ_ASSERT(!desc.object()); + } +#endif + + // Step 5.e. Define the new data property. + if (DefinePropertyOp op = obj->getOpsDefineProperty()) { + // Purge the property cache of now-shadowed id in receiver's environment + // chain. + if (!ReshapeForShadowedProp(cx, obj, id)) { + return false; + } + + Rooted<PropertyDescriptor> desc(cx); + desc.initFields(nullptr, v, JSPROP_ENUMERATE, nullptr, nullptr); + + MOZ_ASSERT(!cx->isHelperThreadContext()); + return op(cx, obj, id, desc, result); + } + + return DefineNonexistentProperty(cx, obj, id, v, result); + } + + return SetPropertyByDefining(cx, id, v, receiver, result); +} + +// Set an existing own property obj[index] that's a dense element. +static bool SetDenseElement(JSContext* cx, HandleNativeObject obj, + uint32_t index, HandleValue v, + ObjectOpResult& result) { + MOZ_ASSERT(!obj->is<TypedArrayObject>()); + MOZ_ASSERT(obj->containsDenseElement(index)); + + obj->setDenseElement(index, v); + return result.succeed(); +} + +/* + * Finish the assignment `receiver[id] = v` when an existing property (shape) + * has been found on a native object (pobj). This implements ES6 draft rev 32 + * (2015 Feb 2) 9.1.9 steps 5 and 6. + * + * It is necessary to pass both id and shape because shape could be an implicit + * dense or typed array element (i.e. not actually a pointer to a Shape). + */ +static bool SetExistingProperty(JSContext* cx, HandleId id, HandleValue v, + HandleValue receiver, HandleNativeObject pobj, + Handle<PropertyResult> prop, + ObjectOpResult& result) { + // Step 5 for dense elements. + if (prop.isDenseElement() || prop.isTypedArrayElement()) { + // Step 5.a. + if (pobj->denseElementsAreFrozen()) { + return result.fail(JSMSG_READ_ONLY); + } + + // Pure optimization for the common case: + if (receiver.isObject() && pobj == &receiver.toObject()) { + if (prop.isTypedArrayElement()) { + Rooted<TypedArrayObject*> tobj(cx, &pobj->as<TypedArrayObject>()); + size_t idx = prop.typedArrayElementIndex(); + return SetTypedArrayElement(cx, tobj, idx, v, result); + } + + return SetDenseElement(cx, pobj, prop.denseElementIndex(), v, result); + } + + // Steps 5.b-f. + return SetPropertyByDefining(cx, id, v, receiver, result); + } + + // Step 5 for all other properties. + RootedShape shape(cx, prop.shape()); + if (shape->isDataDescriptor()) { + // Step 5.a. + if (!shape->writable()) { + return result.fail(JSMSG_READ_ONLY); + } + + // steps 5.c-f. + if (receiver.isObject() && pobj == &receiver.toObject()) { + // Pure optimization for the common case. There's no point performing + // the lookup in step 5.c again, as our caller just did it for us. The + // result is |shape|. + + // Steps 5.e.i-ii. + return NativeSetExistingDataProperty(cx, pobj, shape, v, result); + } + + // Shadow pobj[id] by defining a new data property receiver[id]. + // Delegate everything to SetPropertyByDefining. + return SetPropertyByDefining(cx, id, v, receiver, result); + } + + // Steps 6-11. + MOZ_ASSERT(shape->isAccessorDescriptor()); + MOZ_ASSERT_IF(!shape->hasSetterObject(), shape->hasDefaultSetter()); + if (shape->hasDefaultSetter()) { + return result.fail(JSMSG_GETTER_ONLY); + } + + RootedValue setter(cx, ObjectValue(*shape->setterObject())); + if (!js::CallSetter(cx, receiver, setter, v)) { + return false; + } + + return result.succeed(); +} + +template <QualifiedBool IsQualified> +bool js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, + HandleValue v, HandleValue receiver, + ObjectOpResult& result) { + // Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal + // method for ordinary objects. We substitute our own names for these names + // used in the spec: O -> pobj, P -> id, ownDesc -> shape. + Rooted<PropertyResult> prop(cx); + RootedNativeObject pobj(cx, obj); + + // This loop isn't explicit in the spec algorithm. See the comment on step + // 4.c.i below. (There's a very similar loop in the NativeGetProperty + // implementation, but unfortunately not similar enough to common up.) + // + // We're intentionally not spec-compliant for TypedArrays: + // When |pobj| is a TypedArray and |id| is a TypedArray index, we should + // ignore |receiver| and instead always try to set the property on |pobj|. + // Bug 1502889 showed that this behavior isn't web-compatible. This issue is + // also reported at <https://github.com/tc39/ecma262/issues/1541>. + for (;;) { + // Steps 2-3. ('done' is a SpiderMonkey-specific thing, used below.) + bool done; + if (!LookupOwnPropertyInline<CanGC>(cx, pobj, id, &prop, &done)) { + return false; + } + + if (prop) { + // Steps 5-6. + return SetExistingProperty(cx, id, v, receiver, pobj, prop, result); + } + + // Steps 4.a-b. The check for 'done' on this next line is tricky. + // done can be true in exactly these unlikely-sounding cases: + // - We're looking up an element, and pobj is a TypedArray that + // doesn't have that many elements. + // - We're being called from a resolve hook to assign to the property + // being resolved. + // What they all have in common is we do not want to keep walking + // the prototype chain. + JSObject* proto = done ? nullptr : pobj->staticPrototype(); + if (!proto) { + // Step 4.d.i (and step 5). + return SetNonexistentProperty<IsQualified>(cx, obj, id, v, receiver, + result); + } + + // Step 4.c.i. If the prototype is also native, this step is a + // recursive tail call, and we don't need to go through all the + // plumbing of SetProperty; the top of the loop is where we're going to + // end up anyway. But if pobj is non-native, that optimization would be + // incorrect. + if (!proto->isNative()) { + // Unqualified assignments are not specified to go through [[Set]] + // at all, but they do go through this function. So check for + // unqualified assignment to a nonexistent global (a strict error). + RootedObject protoRoot(cx, proto); + if (!IsQualified) { + bool found; + if (!HasProperty(cx, protoRoot, id, &found)) { + return false; + } + if (!found) { + return SetNonexistentProperty<IsQualified>(cx, obj, id, v, receiver, + result); + } + } + + return SetProperty(cx, protoRoot, id, v, receiver, result); + } + pobj = &proto->as<NativeObject>(); + } +} + +template bool js::NativeSetProperty<Qualified>(JSContext* cx, + HandleNativeObject obj, + HandleId id, HandleValue value, + HandleValue receiver, + ObjectOpResult& result); + +template bool js::NativeSetProperty<Unqualified>(JSContext* cx, + HandleNativeObject obj, + HandleId id, HandleValue value, + HandleValue receiver, + ObjectOpResult& result); + +bool js::NativeSetElement(JSContext* cx, HandleNativeObject obj, uint32_t index, + HandleValue v, HandleValue receiver, + ObjectOpResult& result) { + RootedId id(cx); + if (!IndexToId(cx, index, &id)) { + return false; + } + return NativeSetProperty<Qualified>(cx, obj, id, v, receiver, result); +} + +/*** [[Delete]] *************************************************************/ + +// ES6 draft rev31 9.1.10 [[Delete]] +bool js::NativeDeleteProperty(JSContext* cx, HandleNativeObject obj, + HandleId id, ObjectOpResult& result) { + // Steps 2-3. + Rooted<PropertyResult> prop(cx); + if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) { + return false; + } + + // Step 4. + if (!prop) { + // If no property call the class's delProperty hook, passing succeeded + // as the result parameter. This always succeeds when there is no hook. + return CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, + id, result); + } + + // Step 6. Non-configurable property. + if (GetPropertyAttributes(obj, prop) & JSPROP_PERMANENT) { + return result.failCantDelete(); + } + + // Typed array elements are configurable, but can't be deleted. + if (prop.isTypedArrayElement()) { + return result.failCantDelete(); + } + + if (!CallJSDeletePropertyOp(cx, obj->getClass()->getDelProperty(), obj, id, + result)) { + return false; + } + if (!result) { + return true; + } + + // Step 5. + if (prop.isDenseElement()) { + obj->setDenseElementHole(prop.denseElementIndex()); + } else { + if (!NativeObject::removeProperty(cx, obj, id)) { + return false; + } + } + + return SuppressDeletedProperty(cx, obj, id); +} + +bool js::CopyDataPropertiesNative(JSContext* cx, HandlePlainObject target, + HandleNativeObject from, + Handle<PlainObject*> excludedItems, + bool* optimized) { + MOZ_ASSERT( + !target->isDelegate(), + "CopyDataPropertiesNative should only be called during object literal " + "construction" + "which precludes that |target| is the prototype of any other object"); + + *optimized = false; + + // Don't use the fast path if |from| may have extra indexed or lazy + // properties. + if (from->getDenseInitializedLength() > 0 || from->isIndexed() || + from->is<TypedArrayObject>() || from->getClass()->getNewEnumerate() || + from->getClass()->getEnumerate()) { + return true; + } + + // Collect all enumerable data properties. + using ShapeVector = GCVector<Shape*, 8>; + Rooted<ShapeVector> shapes(cx, ShapeVector(cx)); + + RootedShape fromShape(cx, from->lastProperty()); + for (Shape::Range<NoGC> r(fromShape); !r.empty(); r.popFront()) { + Shape* shape = &r.front(); + jsid id = shape->propid(); + MOZ_ASSERT(!JSID_IS_INT(id)); + + if (!shape->enumerable()) { + continue; + } + if (excludedItems && excludedItems->contains(cx, id)) { + continue; + } + + // Don't use the fast path if |from| contains non-data properties. + // + // This enables two optimizations: + // 1. We don't need to handle the case when accessors modify |from|. + // 2. String and symbol properties can be added in one go. + if (!shape->isDataProperty()) { + return true; + } + + if (!shapes.append(shape)) { + return false; + } + } + + *optimized = true; + + // If |target| contains no own properties, we can directly call + // addProperty instead of the slower putProperty. + const bool targetHadNoOwnProperties = target->lastProperty()->isEmptyShape(); + + RootedId key(cx); + RootedValue value(cx); + for (size_t i = shapes.length(); i > 0; i--) { + Shape* shape = shapes[i - 1]; + MOZ_ASSERT(shape->isDataProperty()); + MOZ_ASSERT(shape->enumerable()); + + key = shape->propid(); + MOZ_ASSERT(!JSID_IS_INT(key)); + + MOZ_ASSERT(from->isNative()); + MOZ_ASSERT(from->lastProperty() == fromShape); + + value = from->getSlot(shape->slot()); + if (targetHadNoOwnProperties) { + MOZ_ASSERT(!target->contains(cx, key), + "didn't expect to find an existing property"); + + if (!AddDataPropertyNonDelegate(cx, target, key, value)) { + return false; + } + } else { + if (!NativeDefineDataProperty(cx, target, key, value, JSPROP_ENUMERATE)) { + return false; + } + } + } + + return true; +} |