| /* |
| * Copyright (C) 1999-2000 Harri Porten ([email protected]) |
| * Copyright (C) 2003-2021 Apple Inc. All rights reserved. |
| * Copyright (C) 2003 Peter Kelly ([email protected]) |
| * Copyright (C) 2006 Alexey Proskuryakov ([email protected]) |
| * |
| * 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 "ArrayPrototypeInlines.h" |
| #include "JSArrayInlines.h" |
| #include "JSCInlines.h" |
| #include "PropertyNameArray.h" |
| #include "ResourceExhaustion.h" |
| #include "TypeError.h" |
| #include <wtf/Assertions.h> |
| |
| WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN |
| |
| 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 (initialLength > MAX_STORAGE_VECTOR_LENGTH) [[unlikely]] |
| return nullptr; |
| |
| unsigned outOfLineStorage = structure->outOfLineCapacity(); |
| Butterfly* butterfly; |
| IndexingType indexingType = structure->indexingType(); |
| if (!hasAnyArrayStorage(indexingType)) [[likely]] { |
| ASSERT( |
| hasUndecided(indexingType) |
| || hasInt32(indexingType) |
| || hasDouble(indexingType) |
| || hasContiguous(indexingType)); |
| |
| unsigned vectorLength = Butterfly::optimalContiguousVectorLength(structure, initialLength); |
| void* temp = vm.auxiliarySpace().allocate( |
| vm, |
| Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)), |
| deferralContext, AllocationFailureMode::ReturnNull); |
| if (!temp) [[unlikely]] |
| 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.auxiliarySpace().allocate( |
| vm, |
| Butterfly::totalSize(indexBias, outOfLineStorage, true, ArrayStorage::sizeFor(vectorLength)), |
| deferralContext, AllocationFailureMode::ReturnNull); |
| if (!temp) [[unlikely]] |
| 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 (!hasAnyArrayStorage(indexingType)) [[likely]] { |
| 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 (slot.thisValue() != thisObject) [[unlikely]] |
| 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, PropertyNameArrayBuilder& 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::fastFill(VM& vm, unsigned startIndex, unsigned endIndex, JSValue value) |
| { |
| if (isCopyOnWrite(indexingMode())) |
| convertFromCopyOnWrite(vm); |
| |
| IndexingType type = indexingType(); |
| if (!(type & IsArray) || hasAnyArrayStorage(type)) |
| return false; |
| IndexingType nextType = leastUpperBoundOfIndexingTypeAndValue(type, value); |
| if (hasArrayStorage(nextType)) |
| return false; |
| convertToIndexingTypeIfNeeded(vm, nextType); |
| |
| ASSERT(nextType == indexingType()); |
| |
| // There is a chance that endIndex is beyond the length. If it is, let's just fail. |
| if (endIndex > this->butterfly()->publicLength()) |
| return false; |
| |
| if (nextType == ArrayWithDouble) { |
| auto* data = butterfly()->contiguousDouble().data(); |
| double pattern = value.asNumber(); |
| #if OS(DARWIN) |
| memset_pattern8(data + startIndex, &pattern, sizeof(double) * (endIndex - startIndex)); |
| #else |
| std::fill(data + startIndex, data + endIndex, pattern); |
| #endif |
| } else if (nextType == ArrayWithInt32) { |
| auto* data = butterfly()->contiguous().data(); |
| auto pattern = std::bit_cast<const WriteBarrier<Unknown>>(JSValue::encode(value)); |
| #if OS(DARWIN) |
| memset_pattern8(data + startIndex, &pattern, sizeof(JSValue) * (endIndex - startIndex)); |
| #else |
| std::fill(data + startIndex, data + endIndex, pattern); |
| #endif |
| vm.writeBarrier(this); |
| } else { |
| // FIXME: https://bugs.webkit.org/show_bug.cgi?id=283786 |
| auto contiguousStorage = butterfly()->contiguous(); |
| for (unsigned i = startIndex; i < endIndex; ++i) |
| contiguousStorage.at(this, i).setWithoutWriteBarrier(value); |
| vm.writeBarrier(this); |
| } |
| |
| return true; |
| } |
| |
| JSArray* JSArray::fastToReversed(JSGlobalObject* globalObject, uint64_t length) |
| { |
| ASSERT(length <= std::numeric_limits<uint32_t>::max()); |
| |
| VM& vm = globalObject->vm(); |
| |
| auto sourceType = indexingType(); |
| switch (sourceType) { |
| case ArrayWithInt32: |
| case ArrayWithContiguous: |
| case ArrayWithDouble: { |
| if (length > this->butterfly()->vectorLength()) [[unlikely]] |
| return nullptr; |
| if (holesMustForwardToPrototype()) [[unlikely]] |
| return nullptr; |
| |
| IndexingType resultType = sourceType; |
| if (sourceType == ArrayWithDouble) { |
| auto* buffer = this->butterfly()->contiguousDouble().data(); |
| if (containsHole(buffer, length)) [[unlikely]] |
| resultType = ArrayWithContiguous; |
| } else if (sourceType == ArrayWithInt32) { |
| auto* buffer = this->butterfly()->contiguousInt32().data(); |
| if (containsHole(buffer, length)) [[unlikely]] |
| resultType = ArrayWithContiguous; |
| } |
| |
| Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(resultType); |
| IndexingType indexingType = resultStructure->indexingType(); |
| if (hasAnyArrayStorage(indexingType)) [[unlikely]] |
| return nullptr; |
| ASSERT(!globalObject->isHavingABadTime()); |
| |
| auto vectorLength = Butterfly::optimalContiguousVectorLength(resultStructure, length); |
| void* memory = vm.auxiliarySpace().allocate( |
| vm, |
| Butterfly::totalSize(0, 0, true, vectorLength * sizeof(EncodedJSValue)), |
| nullptr, AllocationFailureMode::ReturnNull); |
| if (!memory) [[unlikely]] |
| return nullptr; |
| auto* butterfly = Butterfly::fromBase(memory, 0, 0); |
| butterfly->setVectorLength(vectorLength); |
| butterfly->setPublicLength(length); |
| |
| if (hasDouble(indexingType)) { |
| ASSERT(!containsHole(this->butterfly()->contiguousDouble().data(), length)); |
| auto* sourceBuffer = this->butterfly()->contiguousDouble().data(); |
| auto* resultBuffer = butterfly->contiguousDouble().data(); |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| std::reverse(resultBuffer, resultBuffer + length); |
| } else if (hasInt32(indexingType)) { |
| ASSERT(!containsHole(this->butterfly()->contiguous().data(), length)); |
| auto* sourceBuffer = this->butterfly()->contiguous().data(); |
| auto* resultBuffer = butterfly->contiguous().data(); |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| std::reverse(resultBuffer, resultBuffer + length); |
| } else { |
| auto* resultBuffer = butterfly->contiguous().data(); |
| if (sourceType == ArrayWithDouble) { |
| auto* sourceBuffer = this->butterfly()->contiguousDouble().data(); |
| copyArrayElements<ArrayFillMode::Undefined, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| } else { |
| auto* sourceBuffer = this->butterfly()->contiguous().data(); |
| copyArrayElements<ArrayFillMode::Undefined, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| } |
| std::reverse(resultBuffer, resultBuffer + length); |
| } |
| Butterfly::clearRange(resultType, butterfly, length, vectorLength); |
| return createWithButterfly(vm, nullptr, resultStructure, butterfly); |
| } |
| case ArrayWithArrayStorage: { |
| auto& storage = *this->butterfly()->arrayStorage(); |
| if (storage.m_sparseMap.get()) |
| return nullptr; |
| if (length > storage.vectorLength()) |
| return nullptr; |
| if (storage.hasHoles()) |
| return nullptr; |
| |
| Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous); |
| if (hasAnyArrayStorage(resultStructure->indexingType())) [[unlikely]] |
| return nullptr; |
| |
| ASSERT(!globalObject->isHavingABadTime()); |
| ObjectInitializationScope scope(vm); |
| JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(scope, resultStructure, length); |
| if (!resultArray) [[unlikely]] |
| return nullptr; |
| gcSafeMemcpy(resultArray->butterfly()->contiguous().data(), this->butterfly()->arrayStorage()->m_vector, sizeof(JSValue) * static_cast<uint32_t>(length)); |
| ASSERT(resultArray->butterfly()->publicLength() == length); |
| |
| auto data = resultArray->butterfly()->contiguous().data(); |
| std::reverse(data, data + length); |
| vm.writeBarrier(resultArray); |
| |
| return resultArray; |
| } |
| default: |
| return nullptr; |
| } |
| } |
| |
| JSArray* JSArray::fastWith(JSGlobalObject* globalObject, uint32_t index, JSValue value, uint64_t length) |
| { |
| ASSERT(length <= std::numeric_limits<uint32_t>::max()); |
| |
| VM& vm = globalObject->vm(); |
| |
| auto sourceType = indexingType(); |
| switch (sourceType) { |
| case ArrayWithInt32: |
| case ArrayWithContiguous: |
| case ArrayWithDouble: { |
| if (length > this->butterfly()->vectorLength()) [[unlikely]] |
| return nullptr; |
| if (holesMustForwardToPrototype()) [[unlikely]] |
| return nullptr; |
| |
| IndexingType resultType = leastUpperBoundOfIndexingTypeAndValue(sourceType, value); |
| if (sourceType == ArrayWithDouble) { |
| auto* buffer = this->butterfly()->contiguousDouble().data(); |
| if (containsHole(buffer, length)) [[unlikely]] |
| resultType = ArrayWithContiguous; |
| } else if (sourceType == ArrayWithInt32) { |
| auto* buffer = this->butterfly()->contiguousInt32().data(); |
| if (containsHole(buffer, length)) [[unlikely]] |
| resultType = ArrayWithContiguous; |
| } |
| |
| Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(resultType); |
| IndexingType indexingType = resultStructure->indexingType(); |
| if (hasAnyArrayStorage(indexingType)) [[unlikely]] |
| return nullptr; |
| ASSERT(!globalObject->isHavingABadTime()); |
| |
| auto vectorLength = Butterfly::optimalContiguousVectorLength(resultStructure, length); |
| void* memory = vm.auxiliarySpace().allocate( |
| vm, |
| Butterfly::totalSize(0, 0, true, vectorLength * sizeof(EncodedJSValue)), |
| nullptr, AllocationFailureMode::ReturnNull); |
| if (!memory) [[unlikely]] |
| return nullptr; |
| auto* butterfly = Butterfly::fromBase(memory, 0, 0); |
| butterfly->setVectorLength(vectorLength); |
| butterfly->setPublicLength(length); |
| |
| if (hasDouble(indexingType)) { |
| auto* resultBuffer = butterfly->contiguousDouble().data(); |
| if (sourceType == ArrayWithDouble) { |
| auto* sourceBuffer = this->butterfly()->contiguousDouble().data(); |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| } else { |
| ASSERT(sourceType == ArrayWithInt32); |
| auto* sourceBuffer = this->butterfly()->contiguous().data(); |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| } |
| resultBuffer[index] = value.asNumber(); |
| } else if (hasInt32(indexingType)) { |
| ASSERT(sourceType == ArrayWithInt32); |
| auto* sourceBuffer = this->butterfly()->contiguous().data(); |
| auto* resultBuffer = butterfly->contiguous().data(); |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| resultBuffer[index].setWithoutWriteBarrier(value); |
| } else { |
| auto* resultBuffer = butterfly->contiguous().data(); |
| if (sourceType == ArrayWithDouble) { |
| auto* sourceBuffer = this->butterfly()->contiguousDouble().data(); |
| copyArrayElements<ArrayFillMode::Undefined, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| } else { |
| auto* sourceBuffer = this->butterfly()->contiguous().data(); |
| copyArrayElements<ArrayFillMode::Undefined, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, length, sourceType); |
| } |
| resultBuffer[index].setWithoutWriteBarrier(value); |
| } |
| |
| Butterfly::clearRange(indexingType, butterfly, length, vectorLength); |
| return createWithButterfly(vm, nullptr, resultStructure, butterfly); |
| } |
| case ArrayWithArrayStorage: { |
| auto& storage = *this->butterfly()->arrayStorage(); |
| if (storage.m_sparseMap.get()) |
| return nullptr; |
| if (length > storage.vectorLength()) |
| return nullptr; |
| if (storage.hasHoles()) |
| return nullptr; |
| |
| Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous); |
| if (hasAnyArrayStorage(resultStructure->indexingType())) [[unlikely]] |
| return nullptr; |
| |
| ASSERT(!globalObject->isHavingABadTime()); |
| ObjectInitializationScope scope(vm); |
| JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(scope, resultStructure, length); |
| if (!resultArray) [[unlikely]] |
| return nullptr; |
| gcSafeMemcpy(resultArray->butterfly()->contiguous().data(), this->butterfly()->arrayStorage()->m_vector, sizeof(JSValue) * static_cast<uint32_t>(length)); |
| ASSERT(resultArray->butterfly()->publicLength() == length); |
| |
| resultArray->butterfly()->contiguous().at(resultArray, index).setWithoutWriteBarrier(value); |
| vm.writeBarrier(resultArray); |
| |
| return resultArray; |
| } |
| default: |
| return nullptr; |
| } |
| } |
| |
| std::optional<bool> JSArray::fastIncludes(JSGlobalObject* globalObject, JSValue searchElement, uint64_t index64, uint64_t length64) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| bool canDoFastPath = this->canDoFastIndexedAccess() |
| && this->getArrayLength() == length64 // The effects in getting `index` could have changed the length of this array. |
| && static_cast<uint32_t>(index64) == index64; |
| if (!canDoFastPath) |
| return std::nullopt; |
| |
| uint32_t length = static_cast<uint32_t>(length64); |
| uint32_t index = static_cast<uint32_t>(index64); |
| |
| switch (this->indexingType()) { |
| case ArrayWithInt32: { |
| auto& butterfly = *this->butterfly(); |
| auto data = butterfly.contiguous().data(); |
| |
| int32_t int32Value = 0; |
| if (searchElement.isInt32AsAnyInt()) |
| int32Value = searchElement.asInt32AsAnyInt(); |
| else if (searchElement.isUndefined()) [[unlikely]] |
| return containsHole(data, length64); |
| else if (!searchElement.isNumber() || searchElement.asNumber() != 0.0) [[unlikely]] |
| return false; |
| |
| EncodedJSValue encodedSearchElement = JSValue::encode(jsNumber(int32Value)); |
| auto* result = WTF::find64(std::bit_cast<const uint64_t*>(data + index), encodedSearchElement, length - index); |
| return static_cast<bool>(result); |
| } |
| case ArrayWithContiguous: { |
| auto& butterfly = *this->butterfly(); |
| auto data = butterfly.contiguous().data(); |
| |
| if (searchElement.isObject()) { |
| auto* result = std::bit_cast<const WriteBarrier<Unknown>*>(WTF::find64(std::bit_cast<const uint64_t*>(data + index), JSValue::encode(searchElement), length - index)); |
| if (result) |
| return true; |
| return false; |
| } |
| |
| bool searchElementIsUndefined = searchElement.isUndefined(); |
| for (; index < length; ++index) { |
| JSValue value = data[index].get(); |
| if (!value) { |
| if (searchElementIsUndefined) |
| return true; |
| continue; |
| } |
| bool isEqual = sameValueZero(globalObject, searchElement, value); |
| RETURN_IF_EXCEPTION(scope, { }); |
| if (isEqual) |
| return true; |
| } |
| return false; |
| } |
| case ALL_DOUBLE_INDEXING_TYPES: { |
| auto& butterfly = *this->butterfly(); |
| auto data = butterfly.contiguousDouble().data(); |
| |
| if (searchElement.isUndefined()) |
| return containsHole(data, length64); |
| if (!searchElement.isNumber()) |
| return false; |
| |
| double searchNumber = searchElement.asNumber(); |
| for (; index < length; ++index) { |
| if (data[index] == searchNumber) |
| return true; |
| } |
| return false; |
| } |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| bool JSArray::fastCopyWithin(JSGlobalObject* globalObject, uint64_t from64, uint64_t to64, uint64_t count64, uint64_t length64) |
| { |
| VM& vm = globalObject->vm(); |
| |
| uint32_t from = static_cast<uint32_t>(from64); |
| uint32_t to = static_cast<uint32_t>(to64); |
| uint32_t count = static_cast<uint32_t>(count64); |
| uint32_t length = static_cast<uint32_t>(length64); |
| |
| ASSERT(from + count <= length); |
| ASSERT(to + count <= length); |
| |
| bool canDoFastPath = this->canDoFastIndexedAccess() |
| && this->getArrayLength() == length |
| && from64 == from |
| && to64 == to |
| && count64 == count; |
| |
| if (!canDoFastPath) |
| return false; |
| |
| if (isCopyOnWrite(indexingMode())) |
| convertFromCopyOnWrite(vm); |
| |
| auto type = this->indexingType(); |
| switch (type) { |
| case ArrayWithInt32: |
| case ArrayWithContiguous: { |
| auto data = this->butterfly()->contiguous().data(); |
| |
| if (containsHole(data, length)) |
| return false; |
| |
| std::span<WriteBarrier<Unknown>> destination { data + to, count }; |
| std::span<const WriteBarrier<Unknown>> source { data + from, count }; |
| |
| if (type == ArrayWithInt32) |
| memmoveSpan(destination, source); |
| else { |
| ASSERT(type == ArrayWithContiguous); |
| gcSafeMemmove(destination.data(), source.data(), count * sizeof(JSValue)); |
| vm.writeBarrier(this); |
| } |
| return true; |
| } |
| case ArrayWithDouble: { |
| auto data = this->butterfly()->contiguousDouble().data(); |
| |
| if (containsHole(data, length)) |
| return false; |
| |
| std::span<double> destination { data + to, count }; |
| std::span<double> source { data + from, count }; |
| |
| memmoveSpan(destination, source); |
| return true; |
| } |
| case ArrayWithArrayStorage: { |
| auto& storage = *this->butterfly()->arrayStorage(); |
| if (storage.m_sparseMap.get()) |
| return false; |
| if (length > storage.vectorLength()) |
| return false; |
| if (storage.hasHoles()) |
| return false; |
| ASSERT(!globalObject->isHavingABadTime()); |
| |
| auto vector = this->butterfly()->arrayStorage()->m_vector; |
| gcSafeMemmove(vector + to, vector + from, count * sizeof(JSValue)); |
| vm.writeBarrier(this); |
| |
| return true; |
| } |
| default: |
| return false; |
| } |
| } |
| |
| JSArray* JSArray::fastToSpliced(JSGlobalObject* globalObject, CallFrame* callFrame, uint64_t length, uint64_t newLength, uint64_t start, uint64_t deleteCount, uint64_t insertCount) |
| { |
| VM& vm = globalObject->vm(); |
| |
| IndexingType sourceType = indexingType(); |
| switch (sourceType) { |
| case ArrayWithInt32: |
| case ArrayWithContiguous: |
| case ArrayWithDouble: { |
| if (newLength > MAX_STORAGE_VECTOR_LENGTH) [[unlikely]] |
| return nullptr; |
| |
| if (newLength >= MIN_SPARSE_ARRAY_INDEX) [[unlikely]] |
| return nullptr; |
| |
| if (length > this->butterfly()->vectorLength()) [[unlikely]] |
| return nullptr; |
| |
| if (hasDouble(sourceType)) { |
| if (containsHole(this->butterfly()->contiguousDouble().data(), static_cast<uint32_t>(length))) |
| return nullptr; |
| } else if (containsHole(this->butterfly()->contiguous().data(), static_cast<uint32_t>(length))) |
| return nullptr; |
| |
| IndexingType insertedItemsIndexingType = sourceType; |
| for (uint64_t i = 0; i < insertCount; i++) |
| insertedItemsIndexingType = leastUpperBoundOfIndexingTypeAndValue(insertedItemsIndexingType, callFrame->uncheckedArgument(i + 2)); |
| |
| Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(insertedItemsIndexingType); |
| IndexingType resultIndexingType = resultStructure->indexingType(); |
| |
| if (hasAnyArrayStorage(resultIndexingType)) [[unlikely]] |
| return nullptr; |
| ASSERT(!globalObject->isHavingABadTime()); |
| |
| auto vectorLength = Butterfly::optimalContiguousVectorLength(resultStructure, newLength); |
| void* memory = vm.auxiliarySpace().allocate( |
| vm, |
| Butterfly::totalSize(0, 0, true, vectorLength * sizeof(EncodedJSValue)), |
| nullptr, AllocationFailureMode::ReturnNull); |
| if (!memory) [[unlikely]] |
| return nullptr; |
| auto* resultButterfly = Butterfly::fromBase(memory, 0, 0); |
| resultButterfly->setVectorLength(vectorLength); |
| resultButterfly->setPublicLength(newLength); |
| |
| auto copyArrayPrefixElements = [&]<typename T, typename U>(T* resultBuffer, U* sourceBuffer) { |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(resultBuffer, 0, sourceBuffer, 0, start, sourceType); |
| }; |
| auto copyArraySuffixElements = [&]<typename T, typename U>(T* resultBuffer, U* sourceBuffer) { |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(resultBuffer, start + insertCount, sourceBuffer, start + deleteCount, length - start - deleteCount, sourceType); |
| }; |
| |
| auto* sourceButterfly = this->butterfly(); |
| if (hasDouble(resultIndexingType)) { |
| ASSERT(sourceType == ArrayWithInt32 || sourceType == ArrayWithDouble); |
| double* resultBuffer = resultButterfly->contiguousDouble().data(); |
| if (sourceType == ArrayWithDouble) |
| copyArrayPrefixElements(resultBuffer, sourceButterfly->contiguousDouble().data()); |
| else |
| copyArrayPrefixElements(resultBuffer, sourceButterfly->contiguous().data()); |
| for (uint64_t i = 0; i < insertCount; ++i) { |
| JSValue value = callFrame->uncheckedArgument(i + 2); |
| ASSERT(value.isNumber()); |
| resultButterfly->contiguousDouble().atUnsafe(start + i) = value.asNumber(); |
| } |
| if (sourceType == ArrayWithDouble) |
| copyArraySuffixElements(resultBuffer, sourceButterfly->contiguousDouble().data()); |
| else |
| copyArraySuffixElements(resultBuffer, sourceButterfly->contiguous().data()); |
| } else if (hasInt32(resultIndexingType) || hasContiguous(resultIndexingType)) { |
| auto* resultBuffer = resultButterfly->contiguous().data(); |
| if (sourceType == ArrayWithDouble) |
| copyArrayPrefixElements(resultBuffer, sourceButterfly->contiguousDouble().data()); |
| else |
| copyArrayPrefixElements(resultBuffer, sourceButterfly->contiguous().data()); |
| for (uint64_t i = 0; i < insertCount; ++i) { |
| JSValue value = callFrame->uncheckedArgument(i + 2); |
| resultButterfly->contiguous().atUnsafe(start + i).setWithoutWriteBarrier(value); |
| } |
| if (sourceType == ArrayWithDouble) |
| copyArraySuffixElements(resultBuffer, sourceButterfly->contiguousDouble().data()); |
| else |
| copyArraySuffixElements(resultBuffer, sourceButterfly->contiguous().data()); |
| } else |
| RELEASE_ASSERT_NOT_REACHED(); |
| |
| Butterfly::clearRange(resultIndexingType, resultButterfly, newLength, vectorLength); |
| return createWithButterfly(vm, nullptr, resultStructure, resultButterfly); |
| } |
| default: { |
| return nullptr; |
| } |
| } |
| } |
| |
| JSString* JSArray::fastToString(JSGlobalObject* globalObject) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| unsigned length = this->length(); |
| |
| StringRecursionChecker checker(globalObject, this); |
| EXCEPTION_ASSERT(!scope.exception() || checker.earlyReturnValue()); |
| if (JSValue earlyReturnValue = checker.earlyReturnValue()) |
| return jsEmptyString(vm); |
| |
| if (canUseFastArrayJoin(this)) [[likely]] { |
| const Latin1Character comma = ','; |
| |
| bool isCoW = isCopyOnWrite(this->indexingMode()); |
| JSCellButterfly* immutableButterfly = nullptr; |
| if (isCoW) { |
| immutableButterfly = JSCellButterfly::fromButterfly(this->butterfly()); |
| auto iter = vm.heap.immutableButterflyToStringCache.find(immutableButterfly); |
| if (iter != vm.heap.immutableButterflyToStringCache.end()) |
| return iter->value; |
| } |
| |
| bool sawHoles = false; |
| bool genericCase = false; |
| JSString* result = fastArrayJoin(globalObject, this, span(comma), length, sawHoles, genericCase); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| if (!sawHoles && !genericCase && result && isCoW) { |
| ASSERT(JSCellButterfly::fromButterfly(this->butterfly()) == immutableButterfly); |
| vm.heap.immutableButterflyToStringCache.add(immutableButterfly, jsCast<JSString*>(result)); |
| } |
| |
| return result; |
| } |
| |
| JSStringJoiner joiner(","_s); |
| for (unsigned i = 0; i < length; ++i) { |
| JSValue element = this->tryGetIndexQuickly(i); |
| if (!element) { |
| element = this->get(globalObject, i); |
| RETURN_IF_EXCEPTION(scope, { }); |
| } |
| joiner.append(globalObject, element); |
| RETURN_IF_EXCEPTION(scope, { }); |
| } |
| |
| RELEASE_AND_RETURN(scope, joiner.join(globalObject)); |
| } |
| |
| bool JSArray::appendMemcpy(JSGlobalObject* globalObject, VM& vm, unsigned startIndex, IndexingType otherType, std::span<const EncodedJSValue> values) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (isCopyOnWrite(indexingMode())) |
| convertFromCopyOnWrite(vm); |
| |
| IndexingType type = indexingType(); |
| bool allowPromotion = false; |
| IndexingType copyType = mergeIndexingTypeForCopying(otherType, allowPromotion); |
| 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; |
| |
| if (values.size() >= MIN_SPARSE_ARRAY_INDEX) |
| return false; |
| |
| CheckedUint32 checkedNewLength = startIndex; |
| checkedNewLength += values.size(); |
| |
| 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 (otherType == ArrayWithUndecided) [[unlikely]] { |
| 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) { |
| auto data = butterfly()->contiguousDouble().data(); |
| unsigned index = startIndex; |
| for (EncodedJSValue encodedDouble : values) |
| data[index++] = JSValue::decode(encodedDouble).asNumber(); |
| } else if (type == ArrayWithInt32) |
| memcpy(butterfly()->contiguous().data() + startIndex, std::bit_cast<const WriteBarrier<Unknown>*>(values.data()), sizeof(JSValue) * values.size()); |
| else { |
| gcSafeMemcpy(butterfly()->contiguous().data() + startIndex, std::bit_cast<const WriteBarrier<Unknown>*>(values.data()), sizeof(JSValue) * values.size()); |
| vm.writeBarrier(this); |
| } |
| |
| return true; |
| } |
| |
| bool JSArray::appendMemcpy(JSGlobalObject* globalObject, VM& vm, unsigned startIndex, JSC::JSArray* otherArray) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (!canFastAppend(otherArray)) |
| return false; |
| |
| IndexingType type = indexingType(); |
| IndexingType otherType = otherArray->indexingType(); |
| bool allowPromotion = false; |
| IndexingType copyType = mergeIndexingTypeForCopying(otherType, allowPromotion); |
| 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 (otherType == ArrayWithUndecided) [[unlikely]] { |
| 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) { |
| // Double array storage do not need to be safe against GC since they are not scanned. |
| memcpy(butterfly()->contiguousDouble().data() + startIndex, otherArray->butterfly()->contiguousDouble().data(), sizeof(double) * otherLength); |
| } else if (type == ArrayWithInt32) |
| memcpy(butterfly()->contiguous().data() + startIndex, otherArray->butterfly()->contiguous().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()) { |
| // We do not need to have ClonedArgumentsType here since it does not have interceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero. |
| switch (source->type()) { |
| case DirectArgumentsType: |
| return DirectArguments::fastSlice(globalObject, jsCast<DirectArguments*>(source), startIndex, count); |
| default: |
| return nullptr; |
| } |
| 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); |
| IndexingType indexingType = resultStructure->indexingType(); |
| if (hasAnyArrayStorage(indexingType)) [[unlikely]] |
| return nullptr; |
| |
| if (isCopyOnWrite(source->indexingMode())) { |
| if (!startIndex && count == source->butterfly()->publicLength()) |
| return JSArray::createWithButterfly(vm, nullptr, globalObject->originalArrayStructureForIndexingType(source->indexingMode()), source->butterfly()); |
| } |
| |
| ASSERT(!globalObject->isHavingABadTime()); |
| if (count > MAX_STORAGE_VECTOR_LENGTH) [[unlikely]] |
| return nullptr; |
| |
| ASSERT(!resultStructure->outOfLineCapacity()); // JSArray's initial Structure should not have any properties. |
| uint32_t initialLength = static_cast<uint32_t>(count); |
| unsigned vectorLength = Butterfly::optimalContiguousVectorLength(resultStructure, initialLength); |
| void* memory = vm.auxiliarySpace().allocate(vm, Butterfly::totalSize(0, 0, true, vectorLength * sizeof(EncodedJSValue)), nullptr, AllocationFailureMode::ReturnNull); |
| if (!memory) [[unlikely]] |
| return nullptr; |
| |
| auto* butterfly = Butterfly::fromBase(memory, 0, 0); |
| butterfly->setVectorLength(vectorLength); |
| butterfly->setPublicLength(initialLength); |
| // We initialize Butterfly first before setting it to JSArray. In that case, butterfly is not scannoed so that we can safely use memcpy here. |
| memcpy(butterfly->contiguous().data(), source->butterfly()->contiguous().data() + startIndex, sizeof(JSValue) * initialLength); |
| |
| Butterfly::clearRange(indexingType, butterfly, initialLength, vectorLength); |
| return createWithButterfly(vm, nullptr, resultStructure, butterfly); |
| } |
| case ArrayWithArrayStorage: { |
| if (count >= MIN_SPARSE_ARRAY_INDEX || sourceStructure->holesMustForwardToPrototype(source)) |
| return nullptr; |
| |
| if (startIndex + count > source->butterfly()->arrayStorage()->vectorLength()) |
| return nullptr; |
| |
| Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous); |
| if (hasAnyArrayStorage(resultStructure->indexingType())) [[unlikely]] |
| return nullptr; |
| |
| ASSERT(!globalObject->isHavingABadTime()); |
| ObjectInitializationScope scope(vm); |
| JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(scope, resultStructure, static_cast<uint32_t>(count)); |
| if (!resultArray) [[unlikely]] |
| return nullptr; |
| gcSafeMemcpy(resultArray->butterfly()->contiguous().data(), source->butterfly()->arrayStorage()->m_vector + startIndex, sizeof(JSValue) * static_cast<uint32_t>(count)); |
| ASSERT(resultArray->butterfly()->publicLength() == count); |
| return resultArray; |
| } |
| case ArrayWithUndecided: { |
| if (count) |
| return nullptr; |
| return constructEmptyArray(globalObject, nullptr); |
| } |
| 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; |
| |
| AssertNoGC assertNoGC; |
| 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, unsigned shiftThreshold) |
| { |
| 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 || oldLength > shiftThreshold) |
| 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; |
| unsigned moveCount = end - startIndex; |
| if (moveCount) { |
| if (holesMustForwardToPrototype()) [[unlikely]] { |
| for (unsigned i = startIndex; i < end; ++i) { |
| JSValue v = butterfly->contiguous().at(this, i + count).get(); |
| if (!v) [[unlikely]] { |
| 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) * moveCount); |
| } |
| } |
| |
| 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 || oldLength > shiftThreshold) |
| 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; |
| unsigned moveCount = end - startIndex; |
| if (moveCount) { |
| if (holesMustForwardToPrototype()) [[unlikely]] { |
| for (unsigned i = startIndex; i < end; ++i) { |
| double v = butterfly->contiguousDouble().at(this, i + count); |
| if (v != v) [[unlikely]] { |
| 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(double) * moveCount); |
| } |
| } |
| 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. |
| unsigned moveCount = oldLength - startIndex; |
| if (moveCount >= 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; |
| |
| // FIXME: If we create a new butterfly, we should move elements at the same time. |
| 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. |
| if (moveCount) { |
| if (holesMustForwardToPrototype()) [[unlikely]] { |
| auto* buffer = butterfly->contiguous().data() + startIndex; |
| if (containsHole(buffer, moveCount)) [[unlikely]] |
| RELEASE_AND_RETURN(scope, unshiftCountWithArrayStorage(globalObject, startIndex, count, ensureArrayStorage(vm))); |
| } |
| |
| gcSafeMemmove(butterfly->contiguous().data() + startIndex + count, butterfly->contiguous().data() + startIndex, moveCount * sizeof(EncodedJSValue)); |
| } |
| |
| // 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. |
| unsigned moveCount = oldLength - startIndex; |
| if (moveCount >= 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; |
| |
| // FIXME: If we create a new butterfly, we should move elements at the same time. |
| 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. |
| if (moveCount) { |
| if (holesMustForwardToPrototype()) [[unlikely]] { |
| for (unsigned i = oldLength; i-- > startIndex;) { |
| double v = butterfly->contiguousDouble().at(this, i); |
| if (v != v) [[unlikely]] |
| RELEASE_AND_RETURN(scope, unshiftCountWithArrayStorage(globalObject, startIndex, count, ensureArrayStorage(vm))); |
| } |
| } |
| |
| gcSafeMemmove(butterfly->contiguousDouble().data() + startIndex + count, butterfly->contiguousDouble().data() + startIndex, moveCount * sizeof(double)); |
| } |
| |
| // 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; |
| } |
| |
| bool JSArray::isToPrimitiveFastAndNonObservable() |
| { |
| JSGlobalObject* globalObject = this->globalObject(); |
| if (!globalObject->arrayPrototypeChainIsSane()) [[unlikely]] |
| return false; |
| if (!globalObject->arrayToStringWatchpointSet().isStillValid()) [[unlikely]] |
| return false; |
| if (!globalObject->arraySymbolToPrimitiveWatchpointSet().isStillValid()) [[unlikely]] |
| return false; |
| if (!globalObject->arrayJoinWatchpointSet().isStillValid()) [[unlikely]] |
| return false; |
| |
| Structure* structure = this->structure(); |
| return globalObject->isOriginalArrayStructure(structure); |
| } |
| |
| template<AllocationFailureMode failureMode> |
| 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 |
| if constexpr (failureMode == AllocationFailureMode::Assert) |
| RELEASE_ASSERT_RESOURCE_AVAILABLE(array, MemoryExhaustion, "Crash intentionally because memory is exhausted."); |
| else if (!array) |
| return nullptr; |
| |
| // 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<AllocationFailureMode::Assert>(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<AllocationFailureMode::Assert>(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(); |
| auto throwScope = DECLARE_THROW_SCOPE(vm); |
| ObjectInitializationScope scope(vm); |
| |
| JSArray* array = constructArray<AllocationFailureMode::ReturnNull>(scope, arrayStructure, length); |
| if (!array) [[unlikely]] { |
| throwOutOfMemoryError(globalObject, throwScope); |
| return nullptr; |
| } |
| |
| for (int i = 0; i < static_cast<int>(length); ++i) |
| array->initializeIndex(scope, i, values[-i]); |
| return array; |
| } |
| |
| JSArray* constructArrayPair(JSGlobalObject* globalObject, JSValue first, JSValue second) |
| { |
| VM& vm = globalObject->vm(); |
| auto throwScope = DECLARE_THROW_SCOPE(vm); |
| ObjectInitializationScope initializationScope(vm); |
| |
| IndexingType indexingType = ArrayWithUndecided; |
| indexingType = leastUpperBoundOfIndexingTypeAndValue(indexingType, first); |
| indexingType = leastUpperBoundOfIndexingTypeAndValue(indexingType, second); |
| |
| Structure* structure = globalObject->arrayStructureForIndexingTypeDuringAllocation(indexingType); |
| |
| JSArray* array = JSArray::tryCreateUninitializedRestricted(initializationScope, structure, 2); |
| if (!array) [[unlikely]] { |
| throwOutOfMemoryError(globalObject, throwScope); |
| return nullptr; |
| } |
| array->initializeIndex(initializationScope, 0, first); |
| array->initializeIndex(initializationScope, 1, second); |
| |
| return array; |
| } |
| |
| template<> |
| void clearElement(double& element) |
| { |
| element = PNaN; |
| } |
| |
| template<ArrayFillMode fillMode> |
| JSArray* tryCloneArrayFromFast(JSGlobalObject* globalObject, JSValue arrayValue) |
| { |
| ASSERT(isJSArray(arrayValue)); |
| |
| VM& vm = globalObject->vm(); |
| |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| auto* array = jsCast<JSArray*>(arrayValue); |
| if (!array->isIteratorProtocolFastAndNonObservable()) [[unlikely]] |
| return nullptr; |
| |
| IndexingType sourceType = array->indexingType(); |
| if (shouldUseSlowPut(sourceType) || sourceType == ArrayClass) [[unlikely]] |
| return nullptr; |
| |
| Butterfly* butterfly= array->butterfly(); |
| unsigned resultSize = butterfly->publicLength(); |
| if (hasAnyArrayStorage(sourceType) || resultSize >= MIN_SPARSE_ARRAY_INDEX) [[unlikely]] { |
| JSArray* result = constructEmptyArray(globalObject, nullptr, resultSize); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| scope.release(); |
| moveArrayElements<fillMode>(globalObject, vm, result, 0, array, resultSize); |
| return result; |
| } |
| |
| ASSERT(sourceType == ArrayWithDouble || sourceType == ArrayWithInt32 || sourceType == ArrayWithContiguous || sourceType == ArrayWithUndecided); |
| |
| if (!globalObject->isHavingABadTime()) [[likely]] { |
| if constexpr (fillMode == ArrayFillMode::Empty) { |
| if (isCopyOnWrite(array->indexingMode())) |
| return JSArray::createWithButterfly(vm, nullptr, globalObject->originalArrayStructureForIndexingType(array->indexingMode()), array->butterfly()); |
| } |
| |
| if (!resultSize) |
| RELEASE_AND_RETURN(scope, constructEmptyArray(globalObject, nullptr)); |
| } |
| |
| IndexingType resultType = sourceType; |
| if constexpr (fillMode == ArrayFillMode::Undefined) { |
| if (sourceType == ArrayWithDouble) { |
| double* buffer = butterfly->contiguousDouble().data(); |
| if (containsHole(buffer, resultSize)) [[unlikely]] |
| resultType = ArrayWithContiguous; |
| } else if (sourceType == ArrayWithInt32) { |
| auto* buffer = butterfly->contiguous().data(); |
| if (containsHole(buffer, resultSize)) [[unlikely]] |
| resultType = ArrayWithContiguous; |
| } else if (sourceType == ArrayWithUndecided && resultSize) |
| resultType = ArrayWithContiguous; |
| } |
| |
| Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(resultType); |
| if (hasAnyArrayStorage(resultStructure->indexingType())) [[unlikely]] |
| return nullptr; |
| |
| ASSERT(!globalObject->isHavingABadTime()); |
| auto vectorLength = Butterfly::optimalContiguousVectorLength(resultStructure, resultSize); |
| if (vectorLength > MAX_STORAGE_VECTOR_LENGTH) [[unlikely]] |
| return { }; |
| |
| ASSERT(!resultStructure->outOfLineCapacity()); |
| void* memory = vm.auxiliarySpace().allocate(vm, Butterfly::totalSize(0, 0, true, vectorLength * sizeof(EncodedJSValue)), nullptr, AllocationFailureMode::ReturnNull); |
| if (!memory) [[unlikely]] { |
| throwOutOfMemoryError(globalObject, scope); |
| return { }; |
| } |
| auto* resultButterfly = Butterfly::fromBase(memory, 0, 0); |
| resultButterfly->setVectorLength(vectorLength); |
| resultButterfly->setPublicLength(resultSize); |
| |
| switch (resultType) { |
| case ArrayWithUndecided: |
| if constexpr (fillMode == ArrayFillMode::Empty) { |
| auto* buffer = resultButterfly->contiguous().data(); |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(buffer, 0, butterfly->contiguous().data(), 0, resultSize, ArrayWithUndecided); |
| break; |
| } |
| ASSERT(!resultSize); |
| break; |
| case ArrayWithDouble: { |
| ASSERT(sourceType == ArrayWithDouble); |
| double* buffer = resultButterfly->contiguousDouble().data(); |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(buffer, 0, butterfly->contiguousDouble().data(), 0, resultSize, ArrayWithDouble); |
| break; |
| } |
| case ArrayWithInt32: { |
| ASSERT(sourceType == ArrayWithInt32); |
| auto* buffer = resultButterfly->contiguous().data(); |
| copyArrayElements<ArrayFillMode::Empty, NeedsGCSafeOps::No>(buffer, 0, butterfly->contiguous().data(), 0, resultSize, ArrayWithInt32); |
| break; |
| } |
| case ArrayWithContiguous: { |
| auto* buffer = resultButterfly->contiguous().data(); |
| if (sourceType == ArrayWithDouble) |
| copyArrayElements<fillMode, NeedsGCSafeOps::No>(buffer, 0, butterfly->contiguousDouble().data(), 0, resultSize, ArrayWithDouble); |
| else |
| copyArrayElements<fillMode, NeedsGCSafeOps::No>(buffer, 0, butterfly->contiguous().data(), 0, resultSize, sourceType); |
| break; |
| } |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| Butterfly::clearRange(resultType, resultButterfly, resultSize, vectorLength); |
| return JSArray::createWithButterfly(vm, nullptr, resultStructure, resultButterfly); |
| } |
| |
| template JSArray* tryCloneArrayFromFast<ArrayFillMode::Undefined>(JSGlobalObject*, JSValue); |
| template JSArray* tryCloneArrayFromFast<ArrayFillMode::Empty>(JSGlobalObject*, JSValue); |
| |
| } // namespace JSC |
| |
| WTF_ALLOW_UNSAFE_BUFFER_USAGE_END |