| /* |
| * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) |
| * Copyright (C) 2003-2021 Apple Inc. All rights reserved. |
| * Copyright (C) 2003 Peter Kelly (pmk@post.com) |
| * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include "config.h" |
| #include "JSArray.h" |
| |
| #include "ArrayPrototype.h" |
| #include "JSArrayInlines.h" |
| #include "JSCInlines.h" |
| #include "PropertyNameArray.h" |
| #include "TypeError.h" |
| #include <wtf/Assertions.h> |
| |
| namespace JSC { |
| |
| const ASCIILiteral LengthExceededTheMaximumArrayLengthError { "Length exceeded the maximum array length"_s }; |
| |
| STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSArray); |
| |
| const ClassInfo JSArray::s_info = { "Array"_s, &JSNonFinalObject::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSArray) }; |
| |
| JSArray* JSArray::tryCreateUninitializedRestricted(ObjectInitializationScope& scope, GCDeferralContext* deferralContext, Structure* structure, unsigned initialLength) |
| { |
| VM& vm = scope.vm(); |
| |
| if (UNLIKELY(initialLength > MAX_STORAGE_VECTOR_LENGTH)) |
| return nullptr; |
| |
| unsigned outOfLineStorage = structure->outOfLineCapacity(); |
| Butterfly* butterfly; |
| IndexingType indexingType = structure->indexingType(); |
| if (LIKELY(!hasAnyArrayStorage(indexingType))) { |
| ASSERT( |
| hasUndecided(indexingType) |
| || hasInt32(indexingType) |
| || hasDouble(indexingType) |
| || hasContiguous(indexingType)); |
| |
| unsigned vectorLength = Butterfly::optimalContiguousVectorLength(structure, initialLength); |
| void* temp = vm.jsValueGigacageAuxiliarySpace().allocate( |
| vm, |
| Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)), |
| deferralContext, AllocationFailureMode::ReturnNull); |
| if (UNLIKELY(!temp)) |
| return nullptr; |
| butterfly = Butterfly::fromBase(temp, 0, outOfLineStorage); |
| butterfly->setVectorLength(vectorLength); |
| butterfly->setPublicLength(initialLength); |
| if (hasDouble(indexingType)) { |
| for (unsigned i = initialLength; i < vectorLength; ++i) |
| butterfly->contiguousDouble().atUnsafe(i) = PNaN; |
| } else { |
| for (unsigned i = initialLength; i < vectorLength; ++i) |
| butterfly->contiguous().atUnsafe(i).clear(); |
| } |
| } else { |
| ASSERT( |
| indexingType == ArrayWithSlowPutArrayStorage |
| || indexingType == ArrayWithArrayStorage); |
| static constexpr unsigned indexBias = 0; |
| unsigned vectorLength = ArrayStorage::optimalVectorLength(indexBias, structure, initialLength); |
| void* temp = vm.jsValueGigacageAuxiliarySpace().allocate( |
| vm, |
| Butterfly::totalSize(indexBias, outOfLineStorage, true, ArrayStorage::sizeFor(vectorLength)), |
| deferralContext, AllocationFailureMode::ReturnNull); |
| if (UNLIKELY(!temp)) |
| return nullptr; |
| butterfly = Butterfly::fromBase(temp, indexBias, outOfLineStorage); |
| *butterfly->indexingHeader() = indexingHeaderForArrayStorage(initialLength, vectorLength); |
| ArrayStorage* storage = butterfly->arrayStorage(); |
| storage->m_indexBias = indexBias; |
| storage->m_sparseMap.clear(); |
| storage->m_numValuesInVector = initialLength; |
| for (unsigned i = initialLength; i < vectorLength; ++i) |
| storage->m_vector[i].clear(); |
| } |
| |
| JSArray* result = createWithButterfly(vm, deferralContext, structure, butterfly); |
| |
| scope.notifyAllocated(result); |
| return result; |
| } |
| |
| void JSArray::eagerlyInitializeButterfly(ObjectInitializationScope& scope, JSArray* array, unsigned initialLength) |
| { |
| Structure* structure = array->structure(); |
| IndexingType indexingType = structure->indexingType(); |
| Butterfly* butterfly = array->butterfly(); |
| |
| // This function only serves as a companion to tryCreateUninitializedRestricted() |
| // in the event that we really can't defer initialization of the butterfly after all. |
| // tryCreateUninitializedRestricted() already initialized the elements between |
| // initialLength and vector length. We just need to do 0 - initialLength. |
| // ObjectInitializationScope::notifyInitialized() will verify that all elements are |
| // initialized. |
| if (LIKELY(!hasAnyArrayStorage(indexingType))) { |
| if (hasDouble(indexingType)) { |
| for (unsigned i = 0; i < initialLength; ++i) |
| butterfly->contiguousDouble().atUnsafe(i) = PNaN; |
| } else { |
| for (unsigned i = 0; i < initialLength; ++i) |
| butterfly->contiguous().atUnsafe(i).clear(); |
| } |
| } else { |
| ArrayStorage* storage = butterfly->arrayStorage(); |
| for (unsigned i = 0; i < initialLength; ++i) |
| storage->m_vector[i].clear(); |
| } |
| scope.notifyInitialized(array); |
| } |
| |
| void JSArray::setLengthWritable(JSGlobalObject* globalObject, bool writable) |
| { |
| ASSERT(isLengthWritable() || !writable); |
| if (!isLengthWritable() || writable) |
| return; |
| |
| enterDictionaryIndexingMode(globalObject->vm()); |
| |
| SparseArrayValueMap* map = arrayStorage()->m_sparseMap.get(); |
| ASSERT(map); |
| map->setLengthIsReadOnly(); |
| } |
| |
| // https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc |
| bool JSArray::defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSArray* array = jsCast<JSArray*>(object); |
| |
| // 2. If P is "length", then |
| // https://tc39.es/ecma262/#sec-arraysetlength |
| if (propertyName == vm.propertyNames->length) { |
| // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case. |
| unsigned newLength = array->length(); |
| if (descriptor.value()) { |
| newLength = descriptor.value().toUInt32(globalObject); |
| RETURN_IF_EXCEPTION(scope, false); |
| double valueAsNumber = descriptor.value().toNumber(globalObject); |
| RETURN_IF_EXCEPTION(scope, false); |
| if (valueAsNumber != static_cast<double>(newLength)) { |
| throwRangeError(globalObject, scope, "Invalid array length"_s); |
| return false; |
| } |
| } |
| |
| // OrdinaryDefineOwnProperty (https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor) at steps 1.a, 11.a, and 15 is now performed: |
| // 4. If current.[[Configurable]] is false, then |
| // 4.a. If Desc.[[Configurable]] is present and its value is true, return false. |
| if (descriptor.configurablePresent() && descriptor.configurable()) |
| return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeConfigurabilityError); |
| // 4.b. If Desc.[[Enumerable]] is present and SameValue(Desc.[[Enumerable]], current.[[Enumerable]]) is false, return false. |
| if (descriptor.enumerablePresent() && descriptor.enumerable()) |
| return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeEnumerabilityError); |
| // 6. Else if SameValue(IsDataDescriptor(current), IsDataDescriptor(Desc)) is false, then |
| // 6.a. If current.[[Configurable]] is false, return false. |
| if (descriptor.isAccessorDescriptor()) |
| return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeAccessMechanismError); |
| // 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then |
| // 7.a. If current.[[Configurable]] is false and current.[[Writable]] is false, then |
| if (!array->isLengthWritable()) { |
| // 7.a.i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false. |
| // This check is unaffected by steps 13-14 of ArraySetLength as they change non-writable descriptors only. |
| if (descriptor.writablePresent() && descriptor.writable()) |
| return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeWritabilityError); |
| // 7.a.ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false. |
| // This check also covers step 12 of ArraySetLength, which is only reachable if newLen < oldLen. |
| if (newLength != array->length()) |
| return typeError(globalObject, scope, throwException, ReadonlyPropertyChangeError); |
| } |
| |
| // setLength() clears indices >= newLength and sets correct "length" value if [[Delete]] fails (step 17.b.i) |
| bool success = true; |
| if (newLength != array->length()) { |
| success = array->setLength(globalObject, newLength, throwException); |
| EXCEPTION_ASSERT(!scope.exception() || !success); |
| } |
| if (descriptor.writablePresent()) |
| array->setLengthWritable(globalObject, descriptor.writable()); |
| return success; |
| } |
| |
| // 4. Else if P is an array index (15.4), then |
| // a. Let index be ToUint32(P). |
| if (std::optional<uint32_t> optionalIndex = parseIndex(propertyName)) { |
| // b. Reject if index >= oldLen and oldLenDesc.[[Writable]] is false. |
| uint32_t index = optionalIndex.value(); |
| // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case. |
| if (index >= array->length() && !array->isLengthWritable()) |
| return typeError(globalObject, scope, throwException, "Attempting to define numeric property on array with non-writable length property."_s); |
| // c. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing P, Desc, and false as arguments. |
| // d. Reject if succeeded is false. |
| // e. If index >= oldLen |
| // e.i. Set oldLenDesc.[[Value]] to index + 1. |
| // e.ii. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", oldLenDesc, and false as arguments. This call will always return true. |
| // f. Return true. |
| RELEASE_AND_RETURN(scope, array->defineOwnIndexedProperty(globalObject, index, descriptor, throwException)); |
| } |
| |
| RELEASE_AND_RETURN(scope, array->JSObject::defineOwnNonIndexProperty(globalObject, propertyName, descriptor, throwException)); |
| } |
| |
| bool JSArray::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) |
| { |
| VM& vm = globalObject->vm(); |
| JSArray* thisObject = jsCast<JSArray*>(object); |
| if (propertyName == vm.propertyNames->length) { |
| unsigned attributes = thisObject->isLengthWritable() ? PropertyAttribute::DontDelete | PropertyAttribute::DontEnum : PropertyAttribute::DontDelete | PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly; |
| slot.setValue(thisObject, attributes, jsNumber(thisObject->length())); |
| return true; |
| } |
| |
| return JSObject::getOwnPropertySlot(thisObject, globalObject, propertyName, slot); |
| } |
| |
| // https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc |
| bool JSArray::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSArray* thisObject = jsCast<JSArray*>(cell); |
| thisObject->ensureWritable(vm); |
| |
| if (propertyName == vm.propertyNames->length) { |
| if (!thisObject->isLengthWritable()) { |
| if (slot.isStrictMode()) |
| throwTypeError(globalObject, scope, "Array length is not writable"_s); |
| return false; |
| } |
| |
| if (UNLIKELY(slot.thisValue() != thisObject)) |
| RELEASE_AND_RETURN(scope, JSObject::definePropertyOnReceiver(globalObject, propertyName, value, slot)); |
| |
| unsigned newLength = value.toUInt32(globalObject); |
| RETURN_IF_EXCEPTION(scope, false); |
| double valueAsNumber = value.toNumber(globalObject); |
| RETURN_IF_EXCEPTION(scope, false); |
| if (valueAsNumber != static_cast<double>(newLength)) { |
| throwException(globalObject, scope, createRangeError(globalObject, "Invalid array length"_s)); |
| return false; |
| } |
| RELEASE_AND_RETURN(scope, thisObject->setLength(globalObject, newLength, slot.isStrictMode())); |
| } |
| |
| RELEASE_AND_RETURN(scope, JSObject::put(thisObject, globalObject, propertyName, value, slot)); |
| } |
| |
| bool JSArray::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, DeletePropertySlot& slot) |
| { |
| VM& vm = globalObject->vm(); |
| JSArray* thisObject = jsCast<JSArray*>(cell); |
| |
| if (propertyName == vm.propertyNames->length) |
| return false; |
| |
| return JSObject::deleteProperty(thisObject, globalObject, propertyName, slot); |
| } |
| |
| static int compareKeysForQSort(const void* a, const void* b) |
| { |
| unsigned da = *static_cast<const unsigned*>(a); |
| unsigned db = *static_cast<const unsigned*>(b); |
| return (da > db) - (da < db); |
| } |
| |
| void JSArray::getOwnSpecialPropertyNames(JSObject*, JSGlobalObject* globalObject, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode) |
| { |
| VM& vm = globalObject->vm(); |
| if (mode == DontEnumPropertiesMode::Include) |
| propertyNames.add(vm.propertyNames->length); |
| } |
| |
| // This method makes room in the vector, but leaves the new space for count slots uncleared. |
| bool JSArray::unshiftCountSlowCase(const AbstractLocker&, VM& vm, DeferGC&, bool addToFront, unsigned count) |
| { |
| ASSERT(cellLock().isLocked()); |
| |
| ArrayStorage* storage = ensureArrayStorage(vm); |
| Butterfly* butterfly = storage->butterfly(); |
| Structure* structure = this->structure(); |
| unsigned propertyCapacity = structure->outOfLineCapacity(); |
| unsigned propertySize = structure->outOfLineSize(); |
| |
| // If not, we should have handled this on the fast path. |
| ASSERT(!addToFront || count > storage->m_indexBias); |
| |
| // Step 1: |
| // Gather 4 key metrics: |
| // * usedVectorLength - how many entries are currently in the vector (conservative estimate - fewer may be in use in sparse vectors). |
| // * requiredVectorLength - how many entries are will there be in the vector, after allocating space for 'count' more. |
| // * currentCapacity - what is the current size of the vector, including any pre-capacity. |
| // * desiredCapacity - how large should we like to grow the vector to - based on 2x requiredVectorLength. |
| |
| unsigned length = storage->length(); |
| unsigned oldVectorLength = storage->vectorLength(); |
| unsigned usedVectorLength = std::min(oldVectorLength, length); |
| ASSERT(usedVectorLength <= MAX_STORAGE_VECTOR_LENGTH); |
| // Check that required vector length is possible, in an overflow-safe fashion. |
| if (count > MAX_STORAGE_VECTOR_LENGTH - usedVectorLength) |
| return false; |
| unsigned requiredVectorLength = usedVectorLength + count; |
| ASSERT(requiredVectorLength <= MAX_STORAGE_VECTOR_LENGTH); |
| // The sum of m_vectorLength and m_indexBias will never exceed MAX_STORAGE_VECTOR_LENGTH. |
| ASSERT(storage->vectorLength() <= MAX_STORAGE_VECTOR_LENGTH && (MAX_STORAGE_VECTOR_LENGTH - storage->vectorLength()) >= storage->m_indexBias); |
| unsigned currentCapacity = storage->vectorLength() + storage->m_indexBias; |
| // The calculation of desiredCapacity won't overflow, due to the range of MAX_STORAGE_VECTOR_LENGTH. |
| // FIXME: This code should be fixed to avoid internal fragmentation. It's not super high |
| // priority since increaseVectorLength() will "fix" any mistakes we make, but it would be cool |
| // to get this right eventually. |
| unsigned desiredCapacity = std::min(MAX_STORAGE_VECTOR_LENGTH, std::max(BASE_ARRAY_STORAGE_VECTOR_LEN, requiredVectorLength) << 1); |
| |
| // Step 2: |
| // We're either going to choose to allocate a new ArrayStorage, or we're going to reuse the existing one. |
| |
| void* newAllocBase = nullptr; |
| unsigned newStorageCapacity; |
| bool allocatedNewStorage; |
| // If the current storage array is sufficiently large (but not too large!) then just keep using it. |
| if (currentCapacity > desiredCapacity && isDenseEnoughForVector(currentCapacity, requiredVectorLength)) { |
| newAllocBase = butterfly->base(structure); |
| newStorageCapacity = currentCapacity; |
| allocatedNewStorage = false; |
| } else { |
| const unsigned preCapacity = 0; |
| Butterfly* newButterfly = Butterfly::tryCreateUninitialized(vm, this, preCapacity, propertyCapacity, true, ArrayStorage::sizeFor(desiredCapacity)); |
| if (!newButterfly) |
| return false; |
| newAllocBase = newButterfly->base(preCapacity, propertyCapacity); |
| newStorageCapacity = desiredCapacity; |
| allocatedNewStorage = true; |
| } |
| |
| // Step 3: |
| // Work out where we're going to move things to. |
| |
| // Determine how much of the vector to use as pre-capacity, and how much as post-capacity. |
| // If we're adding to the end, we'll add all the new space to the end. |
| // If the vector had no free post-capacity (length >= m_vectorLength), don't give it any. |
| // If it did, we calculate the amount that will remain based on an atomic decay - leave the |
| // vector with half the post-capacity it had previously. |
| unsigned postCapacity = 0; |
| if (!addToFront) |
| postCapacity = newStorageCapacity - requiredVectorLength; |
| else if (length < storage->vectorLength()) { |
| // Atomic decay, + the post-capacity cannot be greater than what is available. |
| postCapacity = std::min((storage->vectorLength() - length) >> 1, newStorageCapacity - requiredVectorLength); |
| // If we're moving contents within the same allocation, the post-capacity is being reduced. |
| ASSERT(newAllocBase != butterfly->base(structure) || postCapacity < storage->vectorLength() - length); |
| } |
| |
| unsigned newVectorLength = requiredVectorLength + postCapacity; |
| RELEASE_ASSERT(newVectorLength <= MAX_STORAGE_VECTOR_LENGTH); |
| unsigned preCapacity = newStorageCapacity - newVectorLength; |
| |
| Butterfly* newButterfly = Butterfly::fromBase(newAllocBase, preCapacity, propertyCapacity); |
| |
| { |
| // When moving Butterfly's head to adjust property-storage, we must take a structure lock. |
| // Otherwise, concurrent JIT compiler accesses to a property storage which is half-baked due to move for shift / unshift. |
| // If the butterfly is newly allocated one, we do not need to take a lock since this is not changing the old butterfly. |
| ConcurrentJSLocker structureLock(allocatedNewStorage ? nullptr : &structure->lock()); |
| if (addToFront) { |
| ASSERT(count + usedVectorLength <= newVectorLength); |
| gcSafeMemmove(newButterfly->arrayStorage()->m_vector + count, storage->m_vector, sizeof(JSValue) * usedVectorLength); |
| gcSafeMemmove(newButterfly->propertyStorage() - propertySize, butterfly->propertyStorage() - propertySize, sizeof(JSValue) * propertySize + sizeof(IndexingHeader) + ArrayStorage::sizeFor(0)); |
| |
| // We don't need to zero the pre-capacity for the concurrent GC because it is not available to use as property storage. |
| gcSafeZeroMemory(static_cast<JSValue*>(newButterfly->base(0, propertyCapacity)), (propertyCapacity - propertySize) * sizeof(JSValue)); |
| |
| if (allocatedNewStorage) { |
| // We will set the vectorLength to newVectorLength. We populated requiredVectorLength |
| // (usedVectorLength + count), which is less. Clear the difference. |
| for (unsigned i = requiredVectorLength; i < newVectorLength; ++i) |
| newButterfly->arrayStorage()->m_vector[i].clear(); |
| } |
| } else if ((newAllocBase != butterfly->base(structure)) || (preCapacity != storage->m_indexBias)) { |
| gcSafeMemmove(newButterfly->propertyStorage() - propertyCapacity, butterfly->propertyStorage() - propertyCapacity, sizeof(JSValue) * propertyCapacity + sizeof(IndexingHeader) + ArrayStorage::sizeFor(0)); |
| gcSafeMemmove(newButterfly->arrayStorage()->m_vector, storage->m_vector, sizeof(JSValue) * usedVectorLength); |
| |
| for (unsigned i = requiredVectorLength; i < newVectorLength; i++) |
| newButterfly->arrayStorage()->m_vector[i].clear(); |
| } |
| |
| newButterfly->arrayStorage()->setVectorLength(newVectorLength); |
| newButterfly->arrayStorage()->m_indexBias = preCapacity; |
| |
| setButterfly(vm, newButterfly); |
| } |
| |
| return true; |
| } |
| |
| bool JSArray::setLengthWithArrayStorage(JSGlobalObject* globalObject, unsigned newLength, bool throwException, ArrayStorage* storage) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| unsigned length = storage->length(); |
| |
| // If the length is read only then we enter sparse mode, so should enter the following 'if'. |
| ASSERT(isLengthWritable() || storage->m_sparseMap); |
| |
| if (SparseArrayValueMap* map = storage->m_sparseMap.get()) { |
| // Fail if the length is not writable. |
| if (map->lengthIsReadOnly()) |
| return typeError(globalObject, scope, throwException, ReadonlyPropertyWriteError); |
| |
| if (newLength < length) { |
| // Copy any keys we might be interested in into a vector. |
| Vector<unsigned, 0, UnsafeVectorOverflow> keys; |
| keys.reserveInitialCapacity(std::min(map->size(), static_cast<size_t>(length - newLength))); |
| SparseArrayValueMap::const_iterator end = map->end(); |
| for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it) { |
| unsigned index = static_cast<unsigned>(it->key); |
| if (index < length && index >= newLength) |
| keys.append(index); |
| } |
| |
| // Check if the array is in sparse mode. If so there may be non-configurable |
| // properties, so we have to perform deletion with caution, if not we can |
| // delete values in any order. |
| if (map->sparseMode()) { |
| qsort(keys.begin(), keys.size(), sizeof(unsigned), compareKeysForQSort); |
| unsigned i = keys.size(); |
| while (i) { |
| unsigned index = keys[--i]; |
| SparseArrayValueMap::iterator it = map->find(index); |
| ASSERT(it != map->notFound()); |
| if (it->value.attributes() & PropertyAttribute::DontDelete) { |
| storage->setLength(index + 1); |
| return typeError(globalObject, scope, throwException, UnableToDeletePropertyError); |
| } |
| map->remove(it); |
| } |
| } else { |
| for (unsigned i = 0; i < keys.size(); ++i) |
| map->remove(keys[i]); |
| if (map->isEmpty()) |
| deallocateSparseIndexMap(); |
| } |
| } |
| } |
| |
| if (newLength < length) { |
| // Delete properties from the vector. |
| unsigned usedVectorLength = std::min(length, storage->vectorLength()); |
| for (unsigned i = newLength; i < usedVectorLength; ++i) { |
| WriteBarrier<Unknown>& valueSlot = storage->m_vector[i]; |
| bool hadValue = !!valueSlot; |
| valueSlot.clear(); |
| storage->m_numValuesInVector -= hadValue; |
| } |
| } |
| |
| storage->setLength(newLength); |
| |
| return true; |
| } |
| |
| bool JSArray::appendMemcpy(JSGlobalObject* globalObject, VM& vm, unsigned startIndex, JSC::JSArray* otherArray) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (!canFastCopy(otherArray)) |
| return false; |
| |
| IndexingType type = indexingType(); |
| IndexingType otherType = otherArray->indexingType(); |
| IndexingType copyType = mergeIndexingTypeForCopying(otherType); |
| if (type == ArrayWithUndecided && copyType != NonArray) { |
| if (copyType == ArrayWithInt32) |
| convertUndecidedToInt32(vm); |
| else if (copyType == ArrayWithDouble) |
| convertUndecidedToDouble(vm); |
| else if (copyType == ArrayWithContiguous) |
| convertUndecidedToContiguous(vm); |
| else { |
| ASSERT(copyType == ArrayWithUndecided); |
| return true; |
| } |
| } else if (type != copyType) |
| return false; |
| |
| unsigned otherLength = otherArray->length(); |
| CheckedUint32 checkedNewLength = startIndex; |
| checkedNewLength += otherLength; |
| |
| if (checkedNewLength.hasOverflowed()) { |
| throwException(globalObject, scope, createRangeError(globalObject, LengthExceededTheMaximumArrayLengthError)); |
| return false; |
| } |
| unsigned newLength = checkedNewLength; |
| |
| if (newLength >= MIN_SPARSE_ARRAY_INDEX) |
| return false; |
| |
| if (!ensureLength(vm, newLength)) { |
| throwOutOfMemoryError(globalObject, scope); |
| return false; |
| } |
| ASSERT(copyType == indexingType()); |
| |
| if (UNLIKELY(otherType == ArrayWithUndecided)) { |
| auto* butterfly = this->butterfly(); |
| if (type == ArrayWithDouble) { |
| for (unsigned i = startIndex; i < newLength; ++i) |
| butterfly->contiguousDouble().at(this, i) = PNaN; |
| } else { |
| for (unsigned i = startIndex; i < newLength; ++i) |
| butterfly->contiguousInt32().at(this, i).setWithoutWriteBarrier(JSValue()); |
| } |
| } else if (type == ArrayWithDouble) |
| gcSafeMemcpy(butterfly()->contiguousDouble().data() + startIndex, otherArray->butterfly()->contiguousDouble().data(), sizeof(JSValue) * otherLength); |
| else { |
| gcSafeMemcpy(butterfly()->contiguous().data() + startIndex, otherArray->butterfly()->contiguous().data(), sizeof(JSValue) * otherLength); |
| vm.writeBarrier(this); |
| } |
| |
| return true; |
| } |
| |
| bool JSArray::setLength(JSGlobalObject* globalObject, unsigned newLength, bool throwException) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| Butterfly* butterfly = this->butterfly(); |
| switch (indexingMode()) { |
| case ArrayClass: |
| if (!newLength) |
| return true; |
| if (newLength >= MIN_SPARSE_ARRAY_INDEX) { |
| RELEASE_AND_RETURN(scope, setLengthWithArrayStorage( |
| globalObject, newLength, throwException, |
| ensureArrayStorage(vm))); |
| } |
| createInitialUndecided(vm, newLength); |
| return true; |
| |
| case CopyOnWriteArrayWithInt32: |
| case CopyOnWriteArrayWithDouble: |
| case CopyOnWriteArrayWithContiguous: |
| if (newLength == butterfly->publicLength()) |
| return true; |
| convertFromCopyOnWrite(vm); |
| butterfly = this->butterfly(); |
| FALLTHROUGH; |
| |
| case ArrayWithUndecided: |
| case ArrayWithInt32: |
| case ArrayWithDouble: |
| case ArrayWithContiguous: { |
| if (newLength == butterfly->publicLength()) |
| return true; |
| if (newLength > MAX_STORAGE_VECTOR_LENGTH // This check ensures that we can do fast push. |
| || (newLength >= MIN_SPARSE_ARRAY_INDEX |
| && !isDenseEnoughForVector(newLength, countElements()))) { |
| RELEASE_AND_RETURN(scope, setLengthWithArrayStorage( |
| globalObject, newLength, throwException, |
| ensureArrayStorage(vm))); |
| } |
| if (newLength > butterfly->publicLength()) { |
| if (!ensureLength(vm, newLength)) { |
| throwOutOfMemoryError(globalObject, scope); |
| return false; |
| } |
| return true; |
| } |
| |
| unsigned lengthToClear = butterfly->publicLength() - newLength; |
| unsigned costToAllocateNewButterfly = 64; // a heuristic. |
| if (lengthToClear > newLength && lengthToClear > costToAllocateNewButterfly) { |
| reallocateAndShrinkButterfly(vm, newLength); |
| return true; |
| } |
| |
| if (indexingType() == ArrayWithDouble) { |
| for (unsigned i = butterfly->publicLength(); i-- > newLength;) |
| butterfly->contiguousDouble().at(this, i) = PNaN; |
| } else { |
| for (unsigned i = butterfly->publicLength(); i-- > newLength;) |
| butterfly->contiguous().at(this, i).clear(); |
| } |
| butterfly->setPublicLength(newLength); |
| return true; |
| } |
| |
| case ArrayWithArrayStorage: |
| case ArrayWithSlowPutArrayStorage: |
| RELEASE_AND_RETURN(scope, setLengthWithArrayStorage(globalObject, newLength, throwException, arrayStorage())); |
| |
| default: |
| CRASH(); |
| return false; |
| } |
| } |
| |
| JSValue JSArray::pop(JSGlobalObject* globalObject) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| ensureWritable(vm); |
| |
| Butterfly* butterfly = this->butterfly(); |
| |
| switch (indexingType()) { |
| case ArrayClass: |
| return jsUndefined(); |
| |
| case ArrayWithUndecided: |
| if (!butterfly->publicLength()) |
| return jsUndefined(); |
| // We have nothing but holes. So, drop down to the slow version. |
| break; |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| unsigned length = butterfly->publicLength(); |
| |
| if (!length--) |
| return jsUndefined(); |
| |
| RELEASE_ASSERT(length < butterfly->vectorLength()); |
| JSValue value = butterfly->contiguous().at(this, length).get(); |
| if (value) { |
| butterfly->contiguous().at(this, length).clear(); |
| butterfly->setPublicLength(length); |
| return value; |
| } |
| break; |
| } |
| |
| case ArrayWithDouble: { |
| unsigned length = butterfly->publicLength(); |
| |
| if (!length--) |
| return jsUndefined(); |
| |
| RELEASE_ASSERT(length < butterfly->vectorLength()); |
| double value = butterfly->contiguousDouble().at(this, length); |
| if (value == value) { |
| butterfly->contiguousDouble().at(this, length) = PNaN; |
| butterfly->setPublicLength(length); |
| return JSValue(JSValue::EncodeAsDouble, value); |
| } |
| break; |
| } |
| |
| case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { |
| ArrayStorage* storage = butterfly->arrayStorage(); |
| |
| unsigned length = storage->length(); |
| if (!length) { |
| if (!isLengthWritable()) |
| throwTypeError(globalObject, scope, ReadonlyPropertyWriteError); |
| return jsUndefined(); |
| } |
| |
| unsigned index = length - 1; |
| if (index < storage->vectorLength()) { |
| WriteBarrier<Unknown>& valueSlot = storage->m_vector[index]; |
| if (valueSlot) { |
| --storage->m_numValuesInVector; |
| JSValue element = valueSlot.get(); |
| valueSlot.clear(); |
| |
| RELEASE_ASSERT(isLengthWritable()); |
| storage->setLength(index); |
| return element; |
| } |
| } |
| break; |
| } |
| |
| default: |
| CRASH(); |
| return JSValue(); |
| } |
| |
| unsigned index = getArrayLength() - 1; |
| // Let element be the result of calling the [[Get]] internal method of O with argument indx. |
| JSValue element = get(globalObject, index); |
| RETURN_IF_EXCEPTION(scope, JSValue()); |
| // Call the [[Delete]] internal method of O with arguments indx and true. |
| bool success = deletePropertyByIndex(this, globalObject, index); |
| RETURN_IF_EXCEPTION(scope, JSValue()); |
| if (!success) { |
| throwTypeError(globalObject, scope, UnableToDeletePropertyError); |
| return jsUndefined(); |
| } |
| // Call the [[Put]] internal method of O with arguments "length", indx, and true. |
| scope.release(); |
| setLength(globalObject, index, true); |
| // Return element. |
| return element; |
| } |
| |
| // Push & putIndex are almost identical, with two small differences. |
| // - we always are writing beyond the current array bounds, so it is always necessary to update m_length & m_numValuesInVector. |
| // - pushing to an array of length 2^32-1 stores the property, but throws a range error. |
| NEVER_INLINE void JSArray::push(JSGlobalObject* globalObject, JSValue value) |
| { |
| pushInline(globalObject, value); |
| } |
| |
| JSArray* JSArray::fastSlice(JSGlobalObject* globalObject, JSObject* source, uint64_t startIndex, uint64_t count) |
| { |
| VM& vm = globalObject->vm(); |
| |
| Structure* sourceStructure = source->structure(); |
| if (sourceStructure->typeInfo().interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero()) |
| return nullptr; |
| |
| auto arrayType = source->indexingType() | IsArray; |
| switch (arrayType) { |
| case ArrayWithDouble: |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| if (count >= MIN_SPARSE_ARRAY_INDEX || sourceStructure->holesMustForwardToPrototype(source)) |
| return nullptr; |
| |
| if (startIndex + count > source->butterfly()->vectorLength()) |
| return nullptr; |
| |
| Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(arrayType); |
| if (UNLIKELY(hasAnyArrayStorage(resultStructure->indexingType()))) |
| return nullptr; |
| |
| ASSERT(!globalObject->isHavingABadTime()); |
| ObjectInitializationScope scope(vm); |
| JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(scope, resultStructure, static_cast<uint32_t>(count)); |
| if (UNLIKELY(!resultArray)) |
| return nullptr; |
| |
| auto& resultButterfly = *resultArray->butterfly(); |
| if (arrayType == ArrayWithDouble) |
| gcSafeMemcpy(resultButterfly.contiguousDouble().data(), source->butterfly()->contiguousDouble().data() + startIndex, sizeof(JSValue) * static_cast<uint32_t>(count)); |
| else |
| gcSafeMemcpy(resultButterfly.contiguous().data(), source->butterfly()->contiguous().data() + startIndex, sizeof(JSValue) * static_cast<uint32_t>(count)); |
| |
| ASSERT(resultButterfly.publicLength() == count); |
| return resultArray; |
| } |
| default: |
| return nullptr; |
| } |
| } |
| |
| bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned count, ArrayStorage* storage) |
| { |
| unsigned oldLength = storage->length(); |
| RELEASE_ASSERT(count <= oldLength); |
| |
| // If the array contains holes or is otherwise in an abnormal state, |
| // use the generic algorithm in ArrayPrototype. |
| if (storage->hasHoles() |
| || hasSparseMap() |
| || shouldUseSlowPut(indexingType())) { |
| return false; |
| } |
| |
| if (!oldLength) |
| return true; |
| |
| unsigned length = oldLength - count; |
| |
| storage->m_numValuesInVector -= count; |
| storage->setLength(length); |
| |
| unsigned vectorLength = storage->vectorLength(); |
| if (!vectorLength) |
| return true; |
| |
| if (startIndex >= vectorLength) |
| return true; |
| |
| DisallowGC disallowGC; |
| Locker locker { cellLock() }; |
| |
| if (startIndex + count > vectorLength) |
| count = vectorLength - startIndex; |
| |
| unsigned usedVectorLength = std::min(vectorLength, oldLength); |
| |
| unsigned numElementsBeforeShiftRegion = startIndex; |
| unsigned firstIndexAfterShiftRegion = startIndex + count; |
| unsigned numElementsAfterShiftRegion = usedVectorLength - firstIndexAfterShiftRegion; |
| ASSERT(numElementsBeforeShiftRegion + count + numElementsAfterShiftRegion == usedVectorLength); |
| |
| // The point of this comparison seems to be to minimize the amount of elements that have to |
| // be moved during a shift operation. |
| if (numElementsBeforeShiftRegion < numElementsAfterShiftRegion) { |
| // The number of elements before the shift region is less than the number of elements |
| // after the shift region, so we move the elements before to the right. |
| if (numElementsBeforeShiftRegion) { |
| RELEASE_ASSERT(count + startIndex <= vectorLength); |
| gcSafeMemmove(storage->m_vector + count, |
| storage->m_vector, |
| sizeof(JSValue) * startIndex); |
| } |
| { |
| // When moving Butterfly's head to adjust property-storage, we must take a structure lock. |
| // Otherwise, concurrent JIT compiler accesses to a property storage which is half-baked due to move for shift / unshift. |
| Structure* structure = this->structure(); |
| ConcurrentJSLocker structureLock(structure->lock()); |
| // Adjust the Butterfly and the index bias. We only need to do this here because we're changing |
| // the start of the Butterfly, which needs to point at the first indexed property in the used |
| // portion of the vector. |
| Butterfly* butterfly = this->butterfly()->shift(structure, count); |
| storage = butterfly->arrayStorage(); |
| storage->m_indexBias += count; |
| |
| // Since we're consuming part of the vector by moving its beginning to the left, |
| // we need to modify the vector length appropriately. |
| storage->setVectorLength(vectorLength - count); |
| setButterfly(vm, butterfly); |
| } |
| } else { |
| // The number of elements before the shift region is greater than or equal to the number |
| // of elements after the shift region, so we move the elements after the shift region to the left. |
| gcSafeMemmove(storage->m_vector + startIndex, |
| storage->m_vector + firstIndexAfterShiftRegion, |
| sizeof(JSValue) * numElementsAfterShiftRegion); |
| |
| // Clear the slots of the elements we just moved. |
| unsigned startOfEmptyVectorTail = usedVectorLength - count; |
| for (unsigned i = startOfEmptyVectorTail; i < usedVectorLength; ++i) |
| storage->m_vector[i].clear(); |
| // We don't modify the index bias or the Butterfly pointer in this case because we're not changing |
| // the start of the Butterfly, which needs to point at the first indexed property in the used |
| // portion of the vector. We also don't modify the vector length because we're not actually changing |
| // its length; we're just using less of it. |
| } |
| |
| return true; |
| } |
| |
| bool JSArray::shiftCountWithAnyIndexingType(JSGlobalObject* globalObject, unsigned& startIndex, unsigned count) |
| { |
| VM& vm = globalObject->vm(); |
| RELEASE_ASSERT(count > 0); |
| |
| ensureWritable(vm); |
| |
| Butterfly* butterfly = this->butterfly(); |
| |
| auto indexingType = this->indexingType(); |
| switch (indexingType) { |
| case ArrayClass: |
| return true; |
| |
| case ArrayWithUndecided: |
| // Don't handle this because it's confusing and it shouldn't come up. |
| return false; |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| unsigned oldLength = butterfly->publicLength(); |
| RELEASE_ASSERT(count <= oldLength); |
| |
| // We may have to walk the entire array to do the shift. We're willing to do |
| // so only if it's not horribly slow. |
| if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX) |
| return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm)); |
| |
| // Storing to a hole is fine since we're still having a good time. But reading from a hole |
| // is totally not fine, since we might have to read from the proto chain. |
| // We have to check for holes before we start moving things around so that we don't get halfway |
| // through shifting and then realize we should have been in ArrayStorage mode. |
| unsigned end = oldLength - count; |
| if (this->structure()->holesMustForwardToPrototype(this)) { |
| for (unsigned i = startIndex; i < end; ++i) { |
| JSValue v = butterfly->contiguous().at(this, i + count).get(); |
| if (UNLIKELY(!v)) { |
| startIndex = i; |
| return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm)); |
| } |
| butterfly->contiguous().at(this, i).setWithoutWriteBarrier(v); |
| } |
| } else { |
| gcSafeMemmove(butterfly->contiguous().data() + startIndex, |
| butterfly->contiguous().data() + startIndex + count, |
| sizeof(JSValue) * (end - startIndex)); |
| } |
| |
| for (unsigned i = end; i < oldLength; ++i) |
| butterfly->contiguous().at(this, i).clear(); |
| |
| butterfly->setPublicLength(oldLength - count); |
| |
| // Our memmoving of values around in the array could have concealed some of them from |
| // the collector. Let's make sure that the collector scans this object again. |
| if (indexingType == ArrayWithContiguous) |
| vm.writeBarrier(this); |
| |
| return true; |
| } |
| |
| case ArrayWithDouble: { |
| unsigned oldLength = butterfly->publicLength(); |
| RELEASE_ASSERT(count <= oldLength); |
| |
| // We may have to walk the entire array to do the shift. We're willing to do |
| // so only if it's not horribly slow. |
| if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX) |
| return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm)); |
| |
| // Storing to a hole is fine since we're still having a good time. But reading from a hole |
| // is totally not fine, since we might have to read from the proto chain. |
| // We have to check for holes before we start moving things around so that we don't get halfway |
| // through shifting and then realize we should have been in ArrayStorage mode. |
| unsigned end = oldLength - count; |
| if (this->structure()->holesMustForwardToPrototype(this)) { |
| for (unsigned i = startIndex; i < end; ++i) { |
| double v = butterfly->contiguousDouble().at(this, i + count); |
| if (UNLIKELY(v != v)) { |
| startIndex = i; |
| return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm)); |
| } |
| butterfly->contiguousDouble().at(this, i) = v; |
| } |
| } else { |
| gcSafeMemmove(butterfly->contiguousDouble().data() + startIndex, |
| butterfly->contiguousDouble().data() + startIndex + count, |
| sizeof(JSValue) * (end - startIndex)); |
| } |
| for (unsigned i = end; i < oldLength; ++i) |
| butterfly->contiguousDouble().at(this, i) = PNaN; |
| |
| butterfly->setPublicLength(oldLength - count); |
| return true; |
| } |
| |
| case ArrayWithArrayStorage: |
| case ArrayWithSlowPutArrayStorage: |
| return shiftCountWithArrayStorage(vm, startIndex, count, arrayStorage()); |
| |
| default: |
| CRASH(); |
| return false; |
| } |
| } |
| |
| // Returns true if the unshift can be handled, false to fallback. |
| bool JSArray::unshiftCountWithArrayStorage(JSGlobalObject* globalObject, unsigned startIndex, unsigned count, ArrayStorage* storage) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| unsigned length = storage->length(); |
| |
| RELEASE_ASSERT(startIndex <= length); |
| |
| // If the array contains holes or is otherwise in an abnormal state, |
| // use the generic algorithm in ArrayPrototype. |
| if (storage->hasHoles() || storage->inSparseMode() || shouldUseSlowPut(indexingType())) |
| return false; |
| |
| bool moveFront = !startIndex || startIndex < length / 2; |
| |
| unsigned vectorLength = storage->vectorLength(); |
| |
| // Need to have GC deferred around the unshiftCountSlowCase(), since that leaves the butterfly in |
| // a weird state: some parts of it will be left uninitialized, which we will fill in here. |
| DeferGC deferGC(vm); |
| Locker locker { cellLock() }; |
| |
| if (moveFront && storage->m_indexBias >= count) { |
| // When moving Butterfly's head to adjust property-storage, we must take a structure lock. |
| // Otherwise, concurrent JIT compiler accesses to a property storage which is half-baked due to move for shift / unshift. |
| Structure* structure = this->structure(); |
| ConcurrentJSLocker structureLock(structure->lock()); |
| Butterfly* newButterfly = storage->butterfly()->unshift(structure, count); |
| storage = newButterfly->arrayStorage(); |
| storage->m_indexBias -= count; |
| storage->setVectorLength(vectorLength + count); |
| setButterfly(vm, newButterfly); |
| } else if (!moveFront && vectorLength - length >= count) |
| storage = storage->butterfly()->arrayStorage(); |
| else if (unshiftCountSlowCase(locker, vm, deferGC, moveFront, count)) |
| storage = arrayStorage(); |
| else { |
| throwOutOfMemoryError(globalObject, scope); |
| return true; |
| } |
| |
| WriteBarrier<Unknown>* vector = storage->m_vector; |
| |
| if (startIndex) { |
| if (moveFront) |
| gcSafeMemmove(vector, vector + count, startIndex * sizeof(JSValue)); |
| else if (length - startIndex) |
| gcSafeMemmove(vector + startIndex + count, vector + startIndex, (length - startIndex) * sizeof(JSValue)); |
| } |
| |
| for (unsigned i = 0; i < count; i++) |
| vector[i + startIndex].clear(); |
| |
| return true; |
| } |
| |
| bool JSArray::unshiftCountWithAnyIndexingType(JSGlobalObject* globalObject, unsigned startIndex, unsigned count) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| ensureWritable(vm); |
| |
| Butterfly* butterfly = this->butterfly(); |
| |
| switch (indexingType()) { |
| case ArrayClass: |
| case ArrayWithUndecided: |
| // We could handle this. But it shouldn't ever come up, so we won't. |
| return false; |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| unsigned oldLength = butterfly->publicLength(); |
| |
| // We may have to walk the entire array to do the unshift. We're willing to do so |
| // only if it's not horribly slow. |
| if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX) |
| RELEASE_AND_RETURN(scope, unshiftCountWithArrayStorage(globalObject, startIndex, count, ensureArrayStorage(vm))); |
| |
| CheckedUint32 checkedLength(oldLength); |
| checkedLength += count; |
| if (checkedLength.hasOverflowed()) { |
| throwOutOfMemoryError(globalObject, scope); |
| return true; |
| } |
| unsigned newLength = checkedLength; |
| if (newLength > MAX_STORAGE_VECTOR_LENGTH) |
| return false; |
| if (!ensureLength(vm, newLength)) { |
| throwOutOfMemoryError(globalObject, scope); |
| return true; |
| } |
| butterfly = this->butterfly(); |
| |
| // We have to check for holes before we start moving things around so that we don't get halfway |
| // through shifting and then realize we should have been in ArrayStorage mode. |
| for (unsigned i = oldLength; i-- > startIndex;) { |
| JSValue v = butterfly->contiguous().at(this, i).get(); |
| if (UNLIKELY(!v)) |
| RELEASE_AND_RETURN(scope, unshiftCountWithArrayStorage(globalObject, startIndex, count, ensureArrayStorage(vm))); |
| } |
| |
| for (unsigned i = oldLength; i-- > startIndex;) { |
| JSValue v = butterfly->contiguous().at(this, i).get(); |
| ASSERT(v); |
| butterfly->contiguous().at(this, i + count).setWithoutWriteBarrier(v); |
| } |
| |
| // Our memmoving of values around in the array could have concealed some of them from |
| // the collector. Let's make sure that the collector scans this object again. |
| vm.writeBarrier(this); |
| |
| // NOTE: we're leaving being garbage in the part of the array that we shifted out |
| // of. This is fine because the caller is required to store over that area, and |
| // in contiguous mode storing into a hole is guaranteed to behave exactly the same |
| // as storing over an existing element. |
| |
| return true; |
| } |
| |
| case ArrayWithDouble: { |
| unsigned oldLength = butterfly->publicLength(); |
| |
| // We may have to walk the entire array to do the unshift. We're willing to do so |
| // only if it's not horribly slow. |
| if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX) |
| RELEASE_AND_RETURN(scope, unshiftCountWithArrayStorage(globalObject, startIndex, count, ensureArrayStorage(vm))); |
| |
| CheckedUint32 checkedLength(oldLength); |
| checkedLength += count; |
| if (checkedLength.hasOverflowed()) { |
| throwOutOfMemoryError(globalObject, scope); |
| return true; |
| } |
| unsigned newLength = checkedLength; |
| if (newLength > MAX_STORAGE_VECTOR_LENGTH) |
| return false; |
| if (!ensureLength(vm, newLength)) { |
| throwOutOfMemoryError(globalObject, scope); |
| return true; |
| } |
| butterfly = this->butterfly(); |
| |
| // We have to check for holes before we start moving things around so that we don't get halfway |
| // through shifting and then realize we should have been in ArrayStorage mode. |
| for (unsigned i = oldLength; i-- > startIndex;) { |
| double v = butterfly->contiguousDouble().at(this, i); |
| if (UNLIKELY(v != v)) |
| RELEASE_AND_RETURN(scope, unshiftCountWithArrayStorage(globalObject, startIndex, count, ensureArrayStorage(vm))); |
| } |
| |
| for (unsigned i = oldLength; i-- > startIndex;) { |
| double v = butterfly->contiguousDouble().at(this, i); |
| ASSERT(v == v); |
| butterfly->contiguousDouble().at(this, i + count) = v; |
| } |
| |
| // NOTE: we're leaving being garbage in the part of the array that we shifted out |
| // of. This is fine because the caller is required to store over that area, and |
| // in contiguous mode storing into a hole is guaranteed to behave exactly the same |
| // as storing over an existing element. |
| |
| return true; |
| } |
| |
| case ArrayWithArrayStorage: |
| case ArrayWithSlowPutArrayStorage: |
| RELEASE_AND_RETURN(scope, unshiftCountWithArrayStorage(globalObject, startIndex, count, arrayStorage())); |
| |
| default: |
| CRASH(); |
| return false; |
| } |
| } |
| |
| void JSArray::fillArgList(JSGlobalObject* globalObject, MarkedArgumentBuffer& args) |
| { |
| unsigned i = 0; |
| unsigned vectorEnd; |
| WriteBarrier<Unknown>* vector; |
| |
| Butterfly* butterfly = this->butterfly(); |
| |
| switch (indexingType()) { |
| case ArrayClass: |
| return; |
| |
| case ArrayWithUndecided: { |
| vector = nullptr; |
| vectorEnd = 0; |
| break; |
| } |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| vectorEnd = butterfly->publicLength(); |
| vector = butterfly->contiguous().data(); |
| break; |
| } |
| |
| case ArrayWithDouble: { |
| vector = nullptr; |
| vectorEnd = 0; |
| for (; i < butterfly->publicLength(); ++i) { |
| double v = butterfly->contiguousDouble().at(this, i); |
| if (v != v) |
| break; |
| args.append(JSValue(JSValue::EncodeAsDouble, v)); |
| } |
| break; |
| } |
| |
| case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { |
| ArrayStorage* storage = butterfly->arrayStorage(); |
| |
| vector = storage->m_vector; |
| vectorEnd = std::min(storage->length(), storage->vectorLength()); |
| break; |
| } |
| |
| default: |
| CRASH(); |
| #if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE) |
| vector = 0; |
| vectorEnd = 0; |
| break; |
| #endif |
| } |
| |
| for (; i < vectorEnd; ++i) { |
| WriteBarrier<Unknown>& v = vector[i]; |
| if (!v) |
| break; |
| args.append(v.get()); |
| } |
| |
| // FIXME: What prevents this from being called with a RuntimeArray? The length function will always return 0 in that case. |
| for (; i < length(); ++i) |
| args.append(get(globalObject, i)); |
| } |
| |
| void JSArray::copyToArguments(JSGlobalObject* globalObject, JSValue* firstElementDest, unsigned offset, unsigned length) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| unsigned i = offset; |
| WriteBarrier<Unknown>* vector; |
| unsigned vectorEnd; |
| length += offset; // We like to think of the length as being our length, rather than the output length. |
| |
| // FIXME: What prevents this from being called with a RuntimeArray? The length function will always return 0 in that case. |
| ASSERT(length == this->length()); |
| |
| Butterfly* butterfly = this->butterfly(); |
| switch (indexingType()) { |
| case ArrayClass: |
| return; |
| |
| case ArrayWithUndecided: { |
| vector = nullptr; |
| vectorEnd = 0; |
| break; |
| } |
| |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| vector = butterfly->contiguous().data(); |
| vectorEnd = butterfly->publicLength(); |
| break; |
| } |
| |
| case ArrayWithDouble: { |
| vector = nullptr; |
| vectorEnd = 0; |
| for (; i < butterfly->publicLength(); ++i) { |
| ASSERT(i < butterfly->vectorLength()); |
| double v = butterfly->contiguousDouble().at(this, i); |
| if (v != v) |
| break; |
| firstElementDest[i - offset] = JSValue(JSValue::EncodeAsDouble, v); |
| } |
| break; |
| } |
| |
| case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: { |
| ArrayStorage* storage = butterfly->arrayStorage(); |
| vector = storage->m_vector; |
| vectorEnd = std::min(length, storage->vectorLength()); |
| break; |
| } |
| |
| default: |
| CRASH(); |
| #if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE) |
| vector = 0; |
| vectorEnd = 0; |
| break; |
| #endif |
| } |
| |
| for (; i < vectorEnd; ++i) { |
| WriteBarrier<Unknown>& v = vector[i]; |
| if (!v) |
| break; |
| firstElementDest[i - offset] = v.get(); |
| } |
| |
| for (; i < length; ++i) { |
| firstElementDest[i - offset] = get(globalObject, i); |
| RETURN_IF_EXCEPTION(scope, void()); |
| } |
| } |
| |
| bool JSArray::isIteratorProtocolFastAndNonObservable() |
| { |
| JSGlobalObject* globalObject = this->globalObject(); |
| if (!globalObject->isArrayPrototypeIteratorProtocolFastAndNonObservable()) |
| return false; |
| |
| VM& vm = globalObject->vm(); |
| Structure* structure = this->structure(); |
| // This is the fast case. Many arrays will be an original array. |
| if (globalObject->isOriginalArrayStructure(structure)) |
| return true; |
| |
| if (structure->mayInterceptIndexedAccesses()) |
| return false; |
| |
| if (getPrototypeDirect() != globalObject->arrayPrototype()) |
| return false; |
| |
| if (getDirectOffset(vm, vm.propertyNames->iteratorSymbol) != invalidOffset) |
| return false; |
| |
| return true; |
| } |
| |
| inline JSArray* constructArray(ObjectInitializationScope& scope, Structure* arrayStructure, unsigned length) |
| { |
| JSArray* array = JSArray::tryCreateUninitializedRestricted(scope, arrayStructure, length); |
| |
| // FIXME: we should probably throw an out of memory error here, but |
| // when making this change we should check that all clients of this |
| // function will correctly handle an exception being thrown from here. |
| // https://bugs.webkit.org/show_bug.cgi?id=169786 |
| RELEASE_ASSERT(array); |
| |
| // FIXME: We only need this for subclasses of Array because we might need to allocate a new structure to change |
| // indexing types while initializing. If this triggered a GC then we might scan our currently uninitialized |
| // array and crash. https://bugs.webkit.org/show_bug.cgi?id=186811 |
| if (!arrayStructure->globalObject()->isOriginalArrayStructure(arrayStructure)) |
| JSArray::eagerlyInitializeButterfly(scope, array, length); |
| |
| return array; |
| } |
| |
| JSArray* constructArray(JSGlobalObject* globalObject, Structure* arrayStructure, const ArgList& values) |
| { |
| VM& vm = globalObject->vm(); |
| unsigned length = values.size(); |
| ObjectInitializationScope scope(vm); |
| |
| JSArray* array = constructArray(scope, arrayStructure, length); |
| for (unsigned i = 0; i < length; ++i) |
| array->initializeIndex(scope, i, values.at(i)); |
| return array; |
| } |
| |
| JSArray* constructArray(JSGlobalObject* globalObject, Structure* arrayStructure, const JSValue* values, unsigned length) |
| { |
| VM& vm = globalObject->vm(); |
| ObjectInitializationScope scope(vm); |
| |
| JSArray* array = constructArray(scope, arrayStructure, length); |
| for (unsigned i = 0; i < length; ++i) |
| array->initializeIndex(scope, i, values[i]); |
| return array; |
| } |
| |
| JSArray* constructArrayNegativeIndexed(JSGlobalObject* globalObject, Structure* arrayStructure, const JSValue* values, unsigned length) |
| { |
| VM& vm = globalObject->vm(); |
| ObjectInitializationScope scope(vm); |
| |
| JSArray* array = constructArray(scope, arrayStructure, length); |
| for (int i = 0; i < static_cast<int>(length); ++i) |
| array->initializeIndex(scope, i, values[-i]); |
| return array; |
| } |
| |
| } // namespace JSC |