2 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
3 * Copyright (C) 2003, 2007-2009, 2012-2013, 2015-2016 Apple Inc. All rights reserved.
4 * Copyright (C) 2003 Peter Kelly (pmk@post.com)
5 * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 #include "ArrayPrototype.h"
27 #include "ButterflyInlines.h"
28 #include "CodeBlock.h"
30 #include "Executable.h"
31 #include "GetterSetter.h"
32 #include "IndexingHeaderInlines.h"
33 #include "JSArrayInlines.h"
34 #include "JSCInlines.h"
35 #include "PropertyNameArray.h"
37 #include <wtf/Assertions.h>
44 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSArray);
46 const ClassInfo JSArray::s_info = {"Array", &JSNonFinalObject::s_info, 0, CREATE_METHOD_TABLE(JSArray)};
48 Butterfly* createArrayButterflyInDictionaryIndexingMode(
49 VM& vm, JSCell* intendedOwner, unsigned initialLength)
51 Butterfly* butterfly = Butterfly::create(
52 vm, intendedOwner, 0, 0, true, IndexingHeader(), ArrayStorage::sizeFor(0));
53 ArrayStorage* storage = butterfly->arrayStorage();
54 storage->setLength(initialLength);
55 storage->setVectorLength(0);
56 storage->m_indexBias = 0;
57 storage->m_sparseMap.clear();
58 storage->m_numValuesInVector = 0;
62 JSArray* JSArray::tryCreateUninitialized(VM& vm, GCDeferralContext* deferralContext, Structure* structure, unsigned initialLength)
64 if (initialLength > MAX_STORAGE_VECTOR_LENGTH)
67 unsigned outOfLineStorage = structure->outOfLineCapacity();
70 IndexingType indexingType = structure->indexingType();
71 if (LIKELY(!hasAnyArrayStorage(indexingType))) {
73 hasUndecided(indexingType)
74 || hasInt32(indexingType)
75 || hasDouble(indexingType)
76 || hasContiguous(indexingType));
78 unsigned vectorLength = Butterfly::optimalContiguousVectorLength(structure, initialLength);
79 void* temp = vm.heap.tryAllocateAuxiliary(deferralContext, nullptr, Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)));
82 butterfly = Butterfly::fromBase(temp, 0, outOfLineStorage);
83 butterfly->setVectorLength(vectorLength);
84 butterfly->setPublicLength(initialLength);
85 if (hasDouble(indexingType)) {
86 for (unsigned i = initialLength; i < vectorLength; ++i)
87 butterfly->contiguousDouble()[i] = PNaN;
89 for (unsigned i = initialLength; i < vectorLength; ++i)
90 butterfly->contiguous()[i].clear();
93 unsigned vectorLength = ArrayStorage::optimalVectorLength(0, structure, initialLength);
94 void* temp = vm.heap.tryAllocateAuxiliary(nullptr, Butterfly::totalSize(0, outOfLineStorage, true, ArrayStorage::sizeFor(vectorLength)));
97 butterfly = Butterfly::fromBase(temp, 0, outOfLineStorage);
98 *butterfly->indexingHeader() = indexingHeaderForArrayStorage(initialLength, vectorLength);
99 ArrayStorage* storage = butterfly->arrayStorage();
100 storage->m_indexBias = 0;
101 storage->m_sparseMap.clear();
102 storage->m_numValuesInVector = initialLength;
103 for (unsigned i = initialLength; i < vectorLength; ++i)
104 storage->m_vector[i].clear();
107 return createWithButterfly(vm, deferralContext, structure, butterfly);
110 void JSArray::setLengthWritable(ExecState* exec, bool writable)
112 ASSERT(isLengthWritable() || !writable);
113 if (!isLengthWritable() || writable)
116 enterDictionaryIndexingMode(exec->vm());
118 SparseArrayValueMap* map = arrayStorage()->m_sparseMap.get();
120 map->setLengthIsReadOnly();
123 // Defined in ES5.1 15.4.5.1
124 bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
127 auto scope = DECLARE_THROW_SCOPE(vm);
129 JSArray* array = jsCast<JSArray*>(object);
131 // 3. If P is "length", then
132 if (propertyName == vm.propertyNames->length) {
133 // All paths through length definition call the default [[DefineOwnProperty]], hence:
134 // from ES5.1 8.12.9 7.a.
135 if (descriptor.configurablePresent() && descriptor.configurable())
136 return reject(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeConfigurabilityError));
137 // from ES5.1 8.12.9 7.b.
138 if (descriptor.enumerablePresent() && descriptor.enumerable())
139 return reject(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeEnumerabilityError));
141 // a. If the [[Value]] field of Desc is absent, then
142 // a.i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", Desc, and Throw as arguments.
143 if (descriptor.isAccessorDescriptor())
144 return reject(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeAccessMechanismError));
145 // from ES5.1 8.12.9 10.a.
146 if (!array->isLengthWritable() && descriptor.writablePresent() && descriptor.writable())
147 return reject(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeWritabilityError));
148 // This descriptor is either just making length read-only, or changing nothing!
149 if (!descriptor.value()) {
150 if (descriptor.writablePresent())
151 array->setLengthWritable(exec, descriptor.writable());
155 // b. Let newLenDesc be a copy of Desc.
156 // c. Let newLen be ToUint32(Desc.[[Value]]).
157 unsigned newLen = descriptor.value().toUInt32(exec);
158 // d. If newLen is not equal to ToNumber( Desc.[[Value]]), throw a RangeError exception.
159 if (newLen != descriptor.value().toNumber(exec)) {
160 JSC::throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
164 // Based on SameValue check in 8.12.9, this is always okay.
165 // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case.
166 if (newLen == array->length()) {
167 if (descriptor.writablePresent())
168 array->setLengthWritable(exec, descriptor.writable());
172 // e. Set newLenDesc.[[Value] to newLen.
173 // f. If newLen >= oldLen, then
174 // f.i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and Throw as arguments.
175 // g. Reject if oldLenDesc.[[Writable]] is false.
176 if (!array->isLengthWritable())
177 return reject(exec, scope, throwException, ASCIILiteral(ReadonlyPropertyChangeError));
179 // h. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
181 // i.i. Need to defer setting the [[Writable]] attribute to false in case any elements cannot be deleted.
182 // i.ii. Let newWritable be false.
183 // i.iii. Set newLenDesc.[[Writable] to true.
184 // j. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and Throw as arguments.
185 // k. If succeeded is false, return false.
186 // l. While newLen < oldLen repeat,
187 // l.i. Set oldLen to oldLen – 1.
188 // l.ii. Let deleteSucceeded be the result of calling the [[Delete]] internal method of A passing ToString(oldLen) and false as arguments.
189 // l.iii. If deleteSucceeded is false, then
190 if (!array->setLength(exec, newLen, throwException)) {
191 // 1. Set newLenDesc.[[Value] to oldLen+1.
192 // 2. If newWritable is false, set newLenDesc.[[Writable] to false.
193 // 3. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and false as arguments.
195 if (descriptor.writablePresent())
196 array->setLengthWritable(exec, descriptor.writable());
200 // m. If newWritable is false, then
201 // i. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length",
202 // Property Descriptor{[[Writable]]: false}, and false as arguments. This call will always
204 if (descriptor.writablePresent())
205 array->setLengthWritable(exec, descriptor.writable());
210 // 4. Else if P is an array index (15.4), then
211 // a. Let index be ToUint32(P).
212 if (Optional<uint32_t> optionalIndex = parseIndex(propertyName)) {
213 // b. Reject if index >= oldLen and oldLenDesc.[[Writable]] is false.
214 uint32_t index = optionalIndex.value();
215 // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case.
216 if (index >= array->length() && !array->isLengthWritable())
217 return reject(exec, scope, throwException, ASCIILiteral("Attempting to define numeric property on array with non-writable length property."));
218 // 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.
219 // d. Reject if succeeded is false.
220 // e. If index >= oldLen
221 // e.i. Set oldLenDesc.[[Value]] to index + 1.
222 // 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.
224 return array->defineOwnIndexedProperty(exec, index, descriptor, throwException);
227 return array->JSObject::defineOwnNonIndexProperty(exec, propertyName, descriptor, throwException);
230 bool JSArray::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
232 JSArray* thisObject = jsCast<JSArray*>(object);
233 if (propertyName == exec->propertyNames().length) {
234 unsigned attributes = thisObject->isLengthWritable() ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly;
235 slot.setValue(thisObject, attributes, jsNumber(thisObject->length()));
239 return JSObject::getOwnPropertySlot(thisObject, exec, propertyName, slot);
243 bool JSArray::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
246 auto scope = DECLARE_THROW_SCOPE(vm);
248 JSArray* thisObject = jsCast<JSArray*>(cell);
250 if (UNLIKELY(isThisValueAltered(slot, thisObject)))
251 return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
253 if (propertyName == exec->propertyNames().length) {
254 unsigned newLength = value.toUInt32(exec);
255 if (value.toNumber(exec) != static_cast<double>(newLength)) {
256 throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
259 return thisObject->setLength(exec, newLength, slot.isStrictMode());
262 return JSObject::put(thisObject, exec, propertyName, value, slot);
265 bool JSArray::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
267 JSArray* thisObject = jsCast<JSArray*>(cell);
269 if (propertyName == exec->propertyNames().length)
272 return JSObject::deleteProperty(thisObject, exec, propertyName);
275 static int compareKeysForQSort(const void* a, const void* b)
277 unsigned da = *static_cast<const unsigned*>(a);
278 unsigned db = *static_cast<const unsigned*>(b);
279 return (da > db) - (da < db);
282 void JSArray::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
284 JSArray* thisObject = jsCast<JSArray*>(object);
286 if (mode.includeDontEnumProperties())
287 propertyNames.add(exec->propertyNames().length);
289 JSObject::getOwnNonIndexPropertyNames(thisObject, exec, propertyNames, mode);
292 // This method makes room in the vector, but leaves the new space for count slots uncleared.
293 bool JSArray::unshiftCountSlowCase(VM& vm, DeferGC&, bool addToFront, unsigned count)
295 ArrayStorage* storage = ensureArrayStorage(vm);
296 Butterfly* butterfly = storage->butterfly();
297 Structure* structure = this->structure(vm);
298 unsigned propertyCapacity = structure->outOfLineCapacity();
299 unsigned propertySize = structure->outOfLineSize();
301 // If not, we should have handled this on the fast path.
302 ASSERT(!addToFront || count > storage->m_indexBias);
305 // Gather 4 key metrics:
306 // * usedVectorLength - how many entries are currently in the vector (conservative estimate - fewer may be in use in sparse vectors).
307 // * requiredVectorLength - how many entries are will there be in the vector, after allocating space for 'count' more.
308 // * currentCapacity - what is the current size of the vector, including any pre-capacity.
309 // * desiredCapacity - how large should we like to grow the vector to - based on 2x requiredVectorLength.
311 unsigned length = storage->length();
312 unsigned oldVectorLength = storage->vectorLength();
313 unsigned usedVectorLength = min(oldVectorLength, length);
314 ASSERT(usedVectorLength <= MAX_STORAGE_VECTOR_LENGTH);
315 // Check that required vector length is possible, in an overflow-safe fashion.
316 if (count > MAX_STORAGE_VECTOR_LENGTH - usedVectorLength)
318 unsigned requiredVectorLength = usedVectorLength + count;
319 ASSERT(requiredVectorLength <= MAX_STORAGE_VECTOR_LENGTH);
320 // The sum of m_vectorLength and m_indexBias will never exceed MAX_STORAGE_VECTOR_LENGTH.
321 ASSERT(storage->vectorLength() <= MAX_STORAGE_VECTOR_LENGTH && (MAX_STORAGE_VECTOR_LENGTH - storage->vectorLength()) >= storage->m_indexBias);
322 unsigned currentCapacity = storage->vectorLength() + storage->m_indexBias;
323 // The calculation of desiredCapacity won't overflow, due to the range of MAX_STORAGE_VECTOR_LENGTH.
324 // FIXME: This code should be fixed to avoid internal fragmentation. It's not super high
325 // priority since increaseVectorLength() will "fix" any mistakes we make, but it would be cool
326 // to get this right eventually.
327 unsigned desiredCapacity = min(MAX_STORAGE_VECTOR_LENGTH, max(BASE_ARRAY_STORAGE_VECTOR_LEN, requiredVectorLength) << 1);
330 // We're either going to choose to allocate a new ArrayStorage, or we're going to reuse the existing one.
332 void* newAllocBase = 0;
333 unsigned newStorageCapacity;
334 bool allocatedNewStorage;
335 // If the current storage array is sufficiently large (but not too large!) then just keep using it.
336 if (currentCapacity > desiredCapacity && isDenseEnoughForVector(currentCapacity, requiredVectorLength)) {
337 newAllocBase = butterfly->base(structure);
338 newStorageCapacity = currentCapacity;
339 allocatedNewStorage = false;
341 size_t newSize = Butterfly::totalSize(0, propertyCapacity, true, ArrayStorage::sizeFor(desiredCapacity));
342 newAllocBase = vm.heap.tryAllocateAuxiliary(this, newSize);
345 newStorageCapacity = desiredCapacity;
346 allocatedNewStorage = true;
350 // Work out where we're going to move things to.
352 // Determine how much of the vector to use as pre-capacity, and how much as post-capacity.
353 // If we're adding to the end, we'll add all the new space to the end.
354 // If the vector had no free post-capacity (length >= m_vectorLength), don't give it any.
355 // If it did, we calculate the amount that will remain based on an atomic decay - leave the
356 // vector with half the post-capacity it had previously.
357 unsigned postCapacity = 0;
359 postCapacity = max(newStorageCapacity - requiredVectorLength, count);
360 else if (length < storage->vectorLength()) {
361 // Atomic decay, + the post-capacity cannot be greater than what is available.
362 postCapacity = min((storage->vectorLength() - length) >> 1, newStorageCapacity - requiredVectorLength);
363 // If we're moving contents within the same allocation, the post-capacity is being reduced.
364 ASSERT(newAllocBase != butterfly->base(structure) || postCapacity < storage->vectorLength() - length);
367 unsigned newVectorLength = requiredVectorLength + postCapacity;
368 unsigned newIndexBias = newStorageCapacity - newVectorLength;
370 Butterfly* newButterfly = Butterfly::fromBase(newAllocBase, newIndexBias, propertyCapacity);
373 ASSERT(count + usedVectorLength <= newVectorLength);
374 memmove(newButterfly->arrayStorage()->m_vector + count, storage->m_vector, sizeof(JSValue) * usedVectorLength);
375 memmove(newButterfly->propertyStorage() - propertySize, butterfly->propertyStorage() - propertySize, sizeof(JSValue) * propertySize + sizeof(IndexingHeader) + ArrayStorage::sizeFor(0));
377 if (allocatedNewStorage) {
378 // We will set the vectorLength to newVectorLength. We populated requiredVectorLength
379 // (usedVectorLength + count), which is less. Clear the difference.
380 for (unsigned i = requiredVectorLength; i < newVectorLength; ++i)
381 newButterfly->arrayStorage()->m_vector[i].clear();
383 } else if ((newAllocBase != butterfly->base(structure)) || (newIndexBias != storage->m_indexBias)) {
384 memmove(newButterfly->propertyStorage() - propertySize, butterfly->propertyStorage() - propertySize, sizeof(JSValue) * propertySize + sizeof(IndexingHeader) + ArrayStorage::sizeFor(0));
385 memmove(newButterfly->arrayStorage()->m_vector, storage->m_vector, sizeof(JSValue) * usedVectorLength);
387 for (unsigned i = requiredVectorLength; i < newVectorLength; i++)
388 newButterfly->arrayStorage()->m_vector[i].clear();
391 newButterfly->arrayStorage()->setVectorLength(newVectorLength);
392 newButterfly->arrayStorage()->m_indexBias = newIndexBias;
394 setButterflyWithoutChangingStructure(vm, newButterfly);
399 bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, bool throwException, ArrayStorage* storage)
402 auto scope = DECLARE_THROW_SCOPE(vm);
404 unsigned length = storage->length();
406 // If the length is read only then we enter sparse mode, so should enter the following 'if'.
407 ASSERT(isLengthWritable() || storage->m_sparseMap);
409 if (SparseArrayValueMap* map = storage->m_sparseMap.get()) {
410 // Fail if the length is not writable.
411 if (map->lengthIsReadOnly())
412 return reject(exec, scope, throwException, ASCIILiteral(ReadonlyPropertyWriteError));
414 if (newLength < length) {
415 // Copy any keys we might be interested in into a vector.
416 Vector<unsigned, 0, UnsafeVectorOverflow> keys;
417 keys.reserveInitialCapacity(min(map->size(), static_cast<size_t>(length - newLength)));
418 SparseArrayValueMap::const_iterator end = map->end();
419 for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it) {
420 unsigned index = static_cast<unsigned>(it->key);
421 if (index < length && index >= newLength)
425 // Check if the array is in sparse mode. If so there may be non-configurable
426 // properties, so we have to perform deletion with caution, if not we can
427 // delete values in any order.
428 if (map->sparseMode()) {
429 qsort(keys.begin(), keys.size(), sizeof(unsigned), compareKeysForQSort);
430 unsigned i = keys.size();
432 unsigned index = keys[--i];
433 SparseArrayValueMap::iterator it = map->find(index);
434 ASSERT(it != map->notFound());
435 if (it->value.attributes & DontDelete) {
436 storage->setLength(index + 1);
437 return reject(exec, scope, throwException, ASCIILiteral(UnableToDeletePropertyError));
442 for (unsigned i = 0; i < keys.size(); ++i)
443 map->remove(keys[i]);
445 deallocateSparseIndexMap();
450 if (newLength < length) {
451 // Delete properties from the vector.
452 unsigned usedVectorLength = min(length, storage->vectorLength());
453 for (unsigned i = newLength; i < usedVectorLength; ++i) {
454 WriteBarrier<Unknown>& valueSlot = storage->m_vector[i];
455 bool hadValue = !!valueSlot;
457 storage->m_numValuesInVector -= hadValue;
461 storage->setLength(newLength);
466 bool JSArray::appendMemcpy(ExecState* exec, VM& vm, unsigned startIndex, JSC::JSArray* otherArray)
468 auto scope = DECLARE_THROW_SCOPE(vm);
470 if (!canFastCopy(vm, otherArray))
473 IndexingType type = indexingType();
474 IndexingType copyType = mergeIndexingTypeForCopying(otherArray->indexingType());
475 if (type == ArrayWithUndecided && copyType != NonArray) {
476 if (copyType == ArrayWithInt32)
477 convertUndecidedToInt32(vm);
478 else if (copyType == ArrayWithDouble)
479 convertUndecidedToDouble(vm);
480 else if (copyType == ArrayWithContiguous)
481 convertUndecidedToContiguous(vm);
483 ASSERT(copyType == ArrayWithUndecided);
486 } else if (type != copyType)
489 unsigned otherLength = otherArray->length();
490 unsigned newLength = startIndex + otherLength;
491 if (newLength >= MIN_SPARSE_ARRAY_INDEX)
494 if (!ensureLength(vm, newLength)) {
495 throwOutOfMemoryError(exec, scope);
498 ASSERT(copyType == indexingType());
500 if (type == ArrayWithDouble)
501 memcpy(butterfly()->contiguousDouble().data() + startIndex, otherArray->butterfly()->contiguousDouble().data(), sizeof(JSValue) * otherLength);
503 memcpy(butterfly()->contiguous().data() + startIndex, otherArray->butterfly()->contiguous().data(), sizeof(JSValue) * otherLength);
508 bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException)
511 auto scope = DECLARE_THROW_SCOPE(vm);
513 Butterfly* butterfly = m_butterfly.get();
514 switch (indexingType()) {
518 if (newLength >= MIN_SPARSE_ARRAY_INDEX) {
519 return setLengthWithArrayStorage(
520 exec, newLength, throwException,
521 ensureArrayStorage(vm));
523 createInitialUndecided(vm, newLength);
526 case ArrayWithUndecided:
528 case ArrayWithDouble:
529 case ArrayWithContiguous: {
530 if (newLength == butterfly->publicLength())
532 if (newLength >= MAX_ARRAY_INDEX // This case ensures that we can do fast push.
533 || (newLength >= MIN_SPARSE_ARRAY_INDEX
534 && !isDenseEnoughForVector(newLength, countElements()))) {
535 return setLengthWithArrayStorage(
536 exec, newLength, throwException,
537 ensureArrayStorage(vm));
539 if (newLength > butterfly->publicLength()) {
540 if (!ensureLength(vm, newLength)) {
541 throwOutOfMemoryError(exec, scope);
547 unsigned lengthToClear = butterfly->publicLength() - newLength;
548 unsigned costToAllocateNewButterfly = 64; // a heuristic.
549 if (lengthToClear > newLength && lengthToClear > costToAllocateNewButterfly) {
550 reallocateAndShrinkButterfly(vm, newLength);
554 if (indexingType() == ArrayWithDouble) {
555 for (unsigned i = butterfly->publicLength(); i-- > newLength;)
556 butterfly->contiguousDouble()[i] = PNaN;
558 for (unsigned i = butterfly->publicLength(); i-- > newLength;)
559 butterfly->contiguous()[i].clear();
561 butterfly->setPublicLength(newLength);
565 case ArrayWithArrayStorage:
566 case ArrayWithSlowPutArrayStorage:
567 return setLengthWithArrayStorage(exec, newLength, throwException, arrayStorage());
575 JSValue JSArray::pop(ExecState* exec)
578 auto scope = DECLARE_THROW_SCOPE(vm);
580 Butterfly* butterfly = m_butterfly.get();
582 switch (indexingType()) {
584 return jsUndefined();
586 case ArrayWithUndecided:
587 if (!butterfly->publicLength())
588 return jsUndefined();
589 // We have nothing but holes. So, drop down to the slow version.
593 case ArrayWithContiguous: {
594 unsigned length = butterfly->publicLength();
597 return jsUndefined();
599 RELEASE_ASSERT(length < butterfly->vectorLength());
600 JSValue value = butterfly->contiguous()[length].get();
602 butterfly->contiguous()[length].clear();
603 butterfly->setPublicLength(length);
609 case ArrayWithDouble: {
610 unsigned length = butterfly->publicLength();
613 return jsUndefined();
615 RELEASE_ASSERT(length < butterfly->vectorLength());
616 double value = butterfly->contiguousDouble()[length];
617 if (value == value) {
618 butterfly->contiguousDouble()[length] = PNaN;
619 butterfly->setPublicLength(length);
620 return JSValue(JSValue::EncodeAsDouble, value);
625 case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: {
626 ArrayStorage* storage = butterfly->arrayStorage();
628 unsigned length = storage->length();
630 if (!isLengthWritable())
631 throwTypeError(exec, scope, ASCIILiteral(ReadonlyPropertyWriteError));
632 return jsUndefined();
635 unsigned index = length - 1;
636 if (index < storage->vectorLength()) {
637 WriteBarrier<Unknown>& valueSlot = storage->m_vector[index];
639 --storage->m_numValuesInVector;
640 JSValue element = valueSlot.get();
643 RELEASE_ASSERT(isLengthWritable());
644 storage->setLength(index);
656 unsigned index = getArrayLength() - 1;
657 // Let element be the result of calling the [[Get]] internal method of O with argument indx.
658 JSValue element = get(exec, index);
659 RETURN_IF_EXCEPTION(scope, JSValue());
660 // Call the [[Delete]] internal method of O with arguments indx and true.
661 if (!deletePropertyByIndex(this, exec, index)) {
662 throwTypeError(exec, scope, ASCIILiteral(UnableToDeletePropertyError));
663 return jsUndefined();
665 // Call the [[Put]] internal method of O with arguments "length", indx, and true.
666 setLength(exec, index, true);
671 // Push & putIndex are almost identical, with two small differences.
672 // - we always are writing beyond the current array bounds, so it is always necessary to update m_length & m_numValuesInVector.
673 // - pushing to an array of length 2^32-1 stores the property, but throws a range error.
674 void JSArray::push(ExecState* exec, JSValue value)
677 auto scope = DECLARE_THROW_SCOPE(vm);
679 Butterfly* butterfly = m_butterfly.get();
681 switch (indexingType()) {
683 createInitialUndecided(vm, 0);
687 case ArrayWithUndecided: {
688 convertUndecidedForValue(vm, value);
693 case ArrayWithInt32: {
694 if (!value.isInt32()) {
695 convertInt32ForValue(vm, value);
700 unsigned length = butterfly->publicLength();
701 ASSERT(length <= butterfly->vectorLength());
702 if (length < butterfly->vectorLength()) {
703 butterfly->contiguousInt32()[length].setWithoutWriteBarrier(value);
704 butterfly->setPublicLength(length + 1);
708 if (length > MAX_ARRAY_INDEX) {
709 methodTable(vm)->putByIndex(this, exec, length, value, true);
710 if (!scope.exception())
711 throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
715 putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, length, value);
719 case ArrayWithContiguous: {
720 unsigned length = butterfly->publicLength();
721 ASSERT(length <= butterfly->vectorLength());
722 if (length < butterfly->vectorLength()) {
723 butterfly->contiguous()[length].set(vm, this, value);
724 butterfly->setPublicLength(length + 1);
728 if (length > MAX_ARRAY_INDEX) {
729 methodTable(vm)->putByIndex(this, exec, length, value, true);
730 if (!scope.exception())
731 throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
735 putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, length, value);
739 case ArrayWithDouble: {
740 if (!value.isNumber()) {
741 convertDoubleToContiguous(vm);
745 double valueAsDouble = value.asNumber();
746 if (valueAsDouble != valueAsDouble) {
747 convertDoubleToContiguous(vm);
752 unsigned length = butterfly->publicLength();
753 ASSERT(length <= butterfly->vectorLength());
754 if (length < butterfly->vectorLength()) {
755 butterfly->contiguousDouble()[length] = valueAsDouble;
756 butterfly->setPublicLength(length + 1);
760 if (length > MAX_ARRAY_INDEX) {
761 methodTable(vm)->putByIndex(this, exec, length, value, true);
762 if (!scope.exception())
763 throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
767 putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, length, value);
771 case ArrayWithSlowPutArrayStorage: {
772 unsigned oldLength = length();
773 bool putResult = false;
774 if (attemptToInterceptPutByIndexOnHole(exec, oldLength, value, true, putResult)) {
775 if (!scope.exception() && oldLength < 0xFFFFFFFFu)
776 setLength(exec, oldLength + 1, true);
782 case ArrayWithArrayStorage: {
783 ArrayStorage* storage = butterfly->arrayStorage();
785 // Fast case - push within vector, always update m_length & m_numValuesInVector.
786 unsigned length = storage->length();
787 if (length < storage->vectorLength()) {
788 storage->m_vector[length].set(vm, this, value);
789 storage->setLength(length + 1);
790 ++storage->m_numValuesInVector;
794 // Pushing to an array of invalid length (2^31-1) stores the property, but throws a range error.
795 if (storage->length() > MAX_ARRAY_INDEX) {
796 methodTable(vm)->putByIndex(this, exec, storage->length(), value, true);
797 // Per ES5.1 15.4.4.7 step 6 & 15.4.5.1 step 3.d.
798 if (!scope.exception())
799 throwException(exec, scope, createRangeError(exec, ASCIILiteral("Invalid array length")));
803 // Handled the same as putIndex.
804 putByIndexBeyondVectorLengthWithArrayStorage(exec, storage->length(), value, true, storage);
809 RELEASE_ASSERT_NOT_REACHED();
813 JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count)
815 auto arrayType = indexingType();
817 case ArrayWithDouble:
819 case ArrayWithContiguous: {
821 if (count >= MIN_SPARSE_ARRAY_INDEX || structure(vm)->holesMustForwardToPrototype(vm))
824 Structure* resultStructure = exec.lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(arrayType);
825 JSArray* resultArray = JSArray::tryCreateUninitialized(vm, resultStructure, count);
829 auto& resultButterfly = *resultArray->butterfly();
830 if (arrayType == ArrayWithDouble)
831 memcpy(resultButterfly.contiguousDouble().data(), m_butterfly.get()->contiguousDouble().data() + startIndex, sizeof(JSValue) * count);
833 memcpy(resultButterfly.contiguous().data(), m_butterfly.get()->contiguous().data() + startIndex, sizeof(JSValue) * count);
834 resultButterfly.setPublicLength(count);
843 bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned count, ArrayStorage* storage)
845 unsigned oldLength = storage->length();
846 RELEASE_ASSERT(count <= oldLength);
848 // If the array contains holes or is otherwise in an abnormal state,
849 // use the generic algorithm in ArrayPrototype.
850 if ((storage->hasHoles() && this->structure(vm)->holesMustForwardToPrototype(vm))
852 || shouldUseSlowPut(indexingType())) {
859 unsigned length = oldLength - count;
861 storage->m_numValuesInVector -= count;
862 storage->setLength(length);
864 unsigned vectorLength = storage->vectorLength();
868 if (startIndex >= vectorLength)
871 if (startIndex + count > vectorLength)
872 count = vectorLength - startIndex;
874 unsigned usedVectorLength = min(vectorLength, oldLength);
876 unsigned numElementsBeforeShiftRegion = startIndex;
877 unsigned firstIndexAfterShiftRegion = startIndex + count;
878 unsigned numElementsAfterShiftRegion = usedVectorLength - firstIndexAfterShiftRegion;
879 ASSERT(numElementsBeforeShiftRegion + count + numElementsAfterShiftRegion == usedVectorLength);
881 // The point of this comparison seems to be to minimize the amount of elements that have to
882 // be moved during a shift operation.
883 if (numElementsBeforeShiftRegion < numElementsAfterShiftRegion) {
884 // The number of elements before the shift region is less than the number of elements
885 // after the shift region, so we move the elements before to the right.
886 if (numElementsBeforeShiftRegion) {
887 RELEASE_ASSERT(count + startIndex <= vectorLength);
888 if (storage->hasHoles()) {
889 for (unsigned i = startIndex; i-- > 0;) {
890 unsigned destinationIndex = count + i;
891 JSValue source = storage->m_vector[i].get();
892 JSValue dest = storage->m_vector[destinationIndex].get();
893 // Any time we overwrite a hole we know we overcounted the number of values we removed
894 // when we subtracted count from m_numValuesInVector above.
895 if (!dest && destinationIndex >= firstIndexAfterShiftRegion)
896 storage->m_numValuesInVector++;
897 storage->m_vector[count + i].setWithoutWriteBarrier(source);
900 memmove(storage->m_vector + count,
902 sizeof(JSValue) * startIndex);
905 // Adjust the Butterfly and the index bias. We only need to do this here because we're changing
906 // the start of the Butterfly, which needs to point at the first indexed property in the used
907 // portion of the vector.
908 Butterfly* butterfly = m_butterfly.get()->shift(structure(), count);
909 m_butterfly.setWithoutBarrier(butterfly);
910 storage = butterfly->arrayStorage();
911 storage->m_indexBias += count;
913 // Since we're consuming part of the vector by moving its beginning to the left,
914 // we need to modify the vector length appropriately.
915 storage->setVectorLength(vectorLength - count);
917 // The number of elements before the shift region is greater than or equal to the number
918 // of elements after the shift region, so we move the elements after the shift region to the left.
919 if (storage->hasHoles()) {
920 for (unsigned i = 0; i < numElementsAfterShiftRegion; ++i) {
921 unsigned destinationIndex = startIndex + i;
922 JSValue source = storage->m_vector[firstIndexAfterShiftRegion + i].get();
923 JSValue dest = storage->m_vector[destinationIndex].get();
924 // Any time we overwrite a hole we know we overcounted the number of values we removed
925 // when we subtracted count from m_numValuesInVector above.
926 if (!dest && destinationIndex < firstIndexAfterShiftRegion)
927 storage->m_numValuesInVector++;
928 storage->m_vector[startIndex + i].setWithoutWriteBarrier(source);
931 memmove(storage->m_vector + startIndex,
932 storage->m_vector + firstIndexAfterShiftRegion,
933 sizeof(JSValue) * numElementsAfterShiftRegion);
935 // Clear the slots of the elements we just moved.
936 unsigned startOfEmptyVectorTail = usedVectorLength - count;
937 for (unsigned i = startOfEmptyVectorTail; i < usedVectorLength; ++i)
938 storage->m_vector[i].clear();
939 // We don't modify the index bias or the Butterfly pointer in this case because we're not changing
940 // the start of the Butterfly, which needs to point at the first indexed property in the used
941 // portion of the vector. We also don't modify the vector length because we're not actually changing
942 // its length; we're just using less of it.
948 bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned& startIndex, unsigned count)
951 RELEASE_ASSERT(count > 0);
953 Butterfly* butterfly = m_butterfly.get();
955 switch (indexingType()) {
959 case ArrayWithUndecided:
960 // Don't handle this because it's confusing and it shouldn't come up.
964 case ArrayWithContiguous: {
965 unsigned oldLength = butterfly->publicLength();
966 RELEASE_ASSERT(count <= oldLength);
968 // We may have to walk the entire array to do the shift. We're willing to do
969 // so only if it's not horribly slow.
970 if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX)
971 return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm));
973 // Storing to a hole is fine since we're still having a good time. But reading from a hole
974 // is totally not fine, since we might have to read from the proto chain.
975 // We have to check for holes before we start moving things around so that we don't get halfway
976 // through shifting and then realize we should have been in ArrayStorage mode.
977 unsigned end = oldLength - count;
978 if (this->structure(vm)->holesMustForwardToPrototype(vm)) {
979 for (unsigned i = startIndex; i < end; ++i) {
980 JSValue v = butterfly->contiguous()[i + count].get();
983 return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm));
985 butterfly->contiguous()[i].setWithoutWriteBarrier(v);
988 memmove(butterfly->contiguous().data() + startIndex,
989 butterfly->contiguous().data() + startIndex + count,
990 sizeof(JSValue) * (end - startIndex));
993 for (unsigned i = end; i < oldLength; ++i)
994 butterfly->contiguous()[i].clear();
996 butterfly->setPublicLength(oldLength - count);
1000 case ArrayWithDouble: {
1001 unsigned oldLength = butterfly->publicLength();
1002 RELEASE_ASSERT(count <= oldLength);
1004 // We may have to walk the entire array to do the shift. We're willing to do
1005 // so only if it's not horribly slow.
1006 if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX)
1007 return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm));
1009 // Storing to a hole is fine since we're still having a good time. But reading from a hole
1010 // is totally not fine, since we might have to read from the proto chain.
1011 // We have to check for holes before we start moving things around so that we don't get halfway
1012 // through shifting and then realize we should have been in ArrayStorage mode.
1013 unsigned end = oldLength - count;
1014 if (this->structure(vm)->holesMustForwardToPrototype(vm)) {
1015 for (unsigned i = startIndex; i < end; ++i) {
1016 double v = butterfly->contiguousDouble()[i + count];
1017 if (UNLIKELY(v != v)) {
1019 return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm));
1021 butterfly->contiguousDouble()[i] = v;
1024 memmove(butterfly->contiguousDouble().data() + startIndex,
1025 butterfly->contiguousDouble().data() + startIndex + count,
1026 sizeof(JSValue) * (end - startIndex));
1028 for (unsigned i = end; i < oldLength; ++i)
1029 butterfly->contiguousDouble()[i] = PNaN;
1031 butterfly->setPublicLength(oldLength - count);
1035 case ArrayWithArrayStorage:
1036 case ArrayWithSlowPutArrayStorage:
1037 return shiftCountWithArrayStorage(vm, startIndex, count, arrayStorage());
1045 // Returns true if the unshift can be handled, false to fallback.
1046 bool JSArray::unshiftCountWithArrayStorage(ExecState* exec, unsigned startIndex, unsigned count, ArrayStorage* storage)
1048 VM& vm = exec->vm();
1049 auto scope = DECLARE_THROW_SCOPE(vm);
1051 unsigned length = storage->length();
1053 RELEASE_ASSERT(startIndex <= length);
1055 // If the array contains holes or is otherwise in an abnormal state,
1056 // use the generic algorithm in ArrayPrototype.
1057 if (storage->hasHoles() || storage->inSparseMode() || shouldUseSlowPut(indexingType()))
1060 bool moveFront = !startIndex || startIndex < length / 2;
1062 unsigned vectorLength = storage->vectorLength();
1064 // Need to have GC deferred around the unshiftCountSlowCase(), since that leaves the butterfly in
1065 // a weird state: some parts of it will be left uninitialized, which we will fill in here.
1066 DeferGC deferGC(vm.heap);
1068 if (moveFront && storage->m_indexBias >= count) {
1069 Butterfly* newButterfly = storage->butterfly()->unshift(structure(), count);
1070 storage = newButterfly->arrayStorage();
1071 storage->m_indexBias -= count;
1072 storage->setVectorLength(vectorLength + count);
1073 setButterflyWithoutChangingStructure(vm, newButterfly);
1074 } else if (!moveFront && vectorLength - length >= count)
1075 storage = storage->butterfly()->arrayStorage();
1076 else if (unshiftCountSlowCase(vm, deferGC, moveFront, count))
1077 storage = arrayStorage();
1079 throwOutOfMemoryError(exec, scope);
1083 WriteBarrier<Unknown>* vector = storage->m_vector;
1087 memmove(vector, vector + count, startIndex * sizeof(JSValue));
1088 else if (length - startIndex)
1089 memmove(vector + startIndex + count, vector + startIndex, (length - startIndex) * sizeof(JSValue));
1092 for (unsigned i = 0; i < count; i++)
1093 vector[i + startIndex].clear();
1097 bool JSArray::unshiftCountWithAnyIndexingType(ExecState* exec, unsigned startIndex, unsigned count)
1099 VM& vm = exec->vm();
1100 auto scope = DECLARE_THROW_SCOPE(vm);
1102 Butterfly* butterfly = m_butterfly.get();
1104 switch (indexingType()) {
1106 case ArrayWithUndecided:
1107 // We could handle this. But it shouldn't ever come up, so we won't.
1110 case ArrayWithInt32:
1111 case ArrayWithContiguous: {
1112 unsigned oldLength = butterfly->publicLength();
1114 // We may have to walk the entire array to do the unshift. We're willing to do so
1115 // only if it's not horribly slow.
1116 if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX)
1117 return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm));
1119 if (!ensureLength(vm, oldLength + count)) {
1120 throwOutOfMemoryError(exec, scope);
1123 butterfly = m_butterfly.get();
1125 // We have to check for holes before we start moving things around so that we don't get halfway
1126 // through shifting and then realize we should have been in ArrayStorage mode.
1127 for (unsigned i = oldLength; i-- > startIndex;) {
1128 JSValue v = butterfly->contiguous()[i].get();
1130 return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm));
1133 for (unsigned i = oldLength; i-- > startIndex;) {
1134 JSValue v = butterfly->contiguous()[i].get();
1136 butterfly->contiguous()[i + count].setWithoutWriteBarrier(v);
1139 // NOTE: we're leaving being garbage in the part of the array that we shifted out
1140 // of. This is fine because the caller is required to store over that area, and
1141 // in contiguous mode storing into a hole is guaranteed to behave exactly the same
1142 // as storing over an existing element.
1147 case ArrayWithDouble: {
1148 unsigned oldLength = butterfly->publicLength();
1150 // We may have to walk the entire array to do the unshift. We're willing to do so
1151 // only if it's not horribly slow.
1152 if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX)
1153 return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm));
1155 if (!ensureLength(vm, oldLength + count)) {
1156 throwOutOfMemoryError(exec, scope);
1159 butterfly = m_butterfly.get();
1161 // We have to check for holes before we start moving things around so that we don't get halfway
1162 // through shifting and then realize we should have been in ArrayStorage mode.
1163 for (unsigned i = oldLength; i-- > startIndex;) {
1164 double v = butterfly->contiguousDouble()[i];
1165 if (UNLIKELY(v != v))
1166 return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(vm));
1169 for (unsigned i = oldLength; i-- > startIndex;) {
1170 double v = butterfly->contiguousDouble()[i];
1172 butterfly->contiguousDouble()[i + count] = v;
1175 // NOTE: we're leaving being garbage in the part of the array that we shifted out
1176 // of. This is fine because the caller is required to store over that area, and
1177 // in contiguous mode storing into a hole is guaranteed to behave exactly the same
1178 // as storing over an existing element.
1183 case ArrayWithArrayStorage:
1184 case ArrayWithSlowPutArrayStorage:
1185 return unshiftCountWithArrayStorage(exec, startIndex, count, arrayStorage());
1193 void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args)
1197 WriteBarrier<Unknown>* vector;
1199 Butterfly* butterfly = m_butterfly.get();
1201 switch (indexingType()) {
1205 case ArrayWithUndecided: {
1211 case ArrayWithInt32:
1212 case ArrayWithContiguous: {
1213 vectorEnd = butterfly->publicLength();
1214 vector = butterfly->contiguous().data();
1218 case ArrayWithDouble: {
1221 for (; i < butterfly->publicLength(); ++i) {
1222 double v = butterfly->contiguousDouble()[i];
1225 args.append(JSValue(JSValue::EncodeAsDouble, v));
1230 case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: {
1231 ArrayStorage* storage = butterfly->arrayStorage();
1233 vector = storage->m_vector;
1234 vectorEnd = min(storage->length(), storage->vectorLength());
1240 #if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE)
1247 for (; i < vectorEnd; ++i) {
1248 WriteBarrier<Unknown>& v = vector[i];
1251 args.append(v.get());
1254 // FIXME: What prevents this from being called with a RuntimeArray? The length function will always return 0 in that case.
1255 for (; i < length(); ++i)
1256 args.append(get(exec, i));
1259 void JSArray::copyToArguments(ExecState* exec, VirtualRegister firstElementDest, unsigned offset, unsigned length)
1261 VM& vm = exec->vm();
1262 auto scope = DECLARE_THROW_SCOPE(vm);
1264 unsigned i = offset;
1265 WriteBarrier<Unknown>* vector;
1267 length += offset; // We like to think of the length as being our length, rather than the output length.
1269 // FIXME: What prevents this from being called with a RuntimeArray? The length function will always return 0 in that case.
1270 ASSERT(length == this->length());
1272 Butterfly* butterfly = m_butterfly.get();
1273 switch (indexingType()) {
1277 case ArrayWithUndecided: {
1283 case ArrayWithInt32:
1284 case ArrayWithContiguous: {
1285 vector = butterfly->contiguous().data();
1286 vectorEnd = butterfly->publicLength();
1290 case ArrayWithDouble: {
1293 for (; i < butterfly->publicLength(); ++i) {
1294 ASSERT(i < butterfly->vectorLength());
1295 double v = butterfly->contiguousDouble()[i];
1298 exec->r(firstElementDest + i - offset) = JSValue(JSValue::EncodeAsDouble, v);
1303 case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: {
1304 ArrayStorage* storage = butterfly->arrayStorage();
1305 vector = storage->m_vector;
1306 vectorEnd = min(length, storage->vectorLength());
1312 #if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE)
1319 for (; i < vectorEnd; ++i) {
1320 WriteBarrier<Unknown>& v = vector[i];
1323 exec->r(firstElementDest + i - offset) = v.get();
1326 for (; i < length; ++i) {
1327 exec->r(firstElementDest + i - offset) = get(exec, i);
1328 RETURN_IF_EXCEPTION(scope, void());