2 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
3 * Copyright (C) 2003, 2007, 2008, 2009, 2012, 2013, 2015 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 "CachedCall.h"
29 #include "CopiedSpace.h"
31 #include "Executable.h"
32 #include "GetterSetter.h"
33 #include "IndexingHeaderInlines.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 void JSArray::setLengthWritable(ExecState
* exec
, bool writable
)
64 ASSERT(isLengthWritable() || !writable
);
65 if (!isLengthWritable() || writable
)
68 enterDictionaryIndexingMode(exec
->vm());
70 SparseArrayValueMap
* map
= arrayStorage()->m_sparseMap
.get();
72 map
->setLengthIsReadOnly();
75 // Defined in ES5.1 15.4.5.1
76 bool JSArray::defineOwnProperty(JSObject
* object
, ExecState
* exec
, PropertyName propertyName
, const PropertyDescriptor
& descriptor
, bool throwException
)
78 JSArray
* array
= jsCast
<JSArray
*>(object
);
80 // 3. If P is "length", then
81 if (propertyName
== exec
->propertyNames().length
) {
82 // All paths through length definition call the default [[DefineOwnProperty]], hence:
83 // from ES5.1 8.12.9 7.a.
84 if (descriptor
.configurablePresent() && descriptor
.configurable())
85 return reject(exec
, throwException
, "Attempting to change configurable attribute of unconfigurable property.");
86 // from ES5.1 8.12.9 7.b.
87 if (descriptor
.enumerablePresent() && descriptor
.enumerable())
88 return reject(exec
, throwException
, "Attempting to change enumerable attribute of unconfigurable property.");
90 // a. If the [[Value]] field of Desc is absent, then
91 // a.i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", Desc, and Throw as arguments.
92 if (descriptor
.isAccessorDescriptor())
93 return reject(exec
, throwException
, "Attempting to change access mechanism for an unconfigurable property.");
94 // from ES5.1 8.12.9 10.a.
95 if (!array
->isLengthWritable() && descriptor
.writablePresent() && descriptor
.writable())
96 return reject(exec
, throwException
, "Attempting to change writable attribute of unconfigurable property.");
97 // This descriptor is either just making length read-only, or changing nothing!
98 if (!descriptor
.value()) {
99 if (descriptor
.writablePresent())
100 array
->setLengthWritable(exec
, descriptor
.writable());
104 // b. Let newLenDesc be a copy of Desc.
105 // c. Let newLen be ToUint32(Desc.[[Value]]).
106 unsigned newLen
= descriptor
.value().toUInt32(exec
);
107 // d. If newLen is not equal to ToNumber( Desc.[[Value]]), throw a RangeError exception.
108 if (newLen
!= descriptor
.value().toNumber(exec
)) {
109 exec
->vm().throwException(exec
, createRangeError(exec
, ASCIILiteral("Invalid array length")));
113 // Based on SameValue check in 8.12.9, this is always okay.
114 // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case.
115 if (newLen
== array
->length()) {
116 if (descriptor
.writablePresent())
117 array
->setLengthWritable(exec
, descriptor
.writable());
121 // e. Set newLenDesc.[[Value] to newLen.
122 // f. If newLen >= oldLen, then
123 // f.i. Return the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and Throw as arguments.
124 // g. Reject if oldLenDesc.[[Writable]] is false.
125 if (!array
->isLengthWritable())
126 return reject(exec
, throwException
, "Attempting to change value of a readonly property.");
128 // h. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
130 // i.i. Need to defer setting the [[Writable]] attribute to false in case any elements cannot be deleted.
131 // i.ii. Let newWritable be false.
132 // i.iii. Set newLenDesc.[[Writable] to true.
133 // 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.
134 // k. If succeeded is false, return false.
135 // l. While newLen < oldLen repeat,
136 // l.i. Set oldLen to oldLen – 1.
137 // l.ii. Let deleteSucceeded be the result of calling the [[Delete]] internal method of A passing ToString(oldLen) and false as arguments.
138 // l.iii. If deleteSucceeded is false, then
139 if (!array
->setLength(exec
, newLen
, throwException
)) {
140 // 1. Set newLenDesc.[[Value] to oldLen+1.
141 // 2. If newWritable is false, set newLenDesc.[[Writable] to false.
142 // 3. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", newLenDesc, and false as arguments.
144 if (descriptor
.writablePresent())
145 array
->setLengthWritable(exec
, descriptor
.writable());
149 // m. If newWritable is false, then
150 // i. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length",
151 // Property Descriptor{[[Writable]]: false}, and false as arguments. This call will always
153 if (descriptor
.writablePresent())
154 array
->setLengthWritable(exec
, descriptor
.writable());
159 // 4. Else if P is an array index (15.4), then
160 // a. Let index be ToUint32(P).
161 if (Optional
<uint32_t> optionalIndex
= parseIndex(propertyName
)) {
162 // b. Reject if index >= oldLen and oldLenDesc.[[Writable]] is false.
163 uint32_t index
= optionalIndex
.value();
164 // FIXME: Nothing prevents this from being called on a RuntimeArray, and the length function will always return 0 in that case.
165 if (index
>= array
->length() && !array
->isLengthWritable())
166 return reject(exec
, throwException
, "Attempting to define numeric property on array with non-writable length property.");
167 // 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.
168 // d. Reject if succeeded is false.
169 // e. If index >= oldLen
170 // e.i. Set oldLenDesc.[[Value]] to index + 1.
171 // 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.
173 return array
->defineOwnIndexedProperty(exec
, index
, descriptor
, throwException
);
176 return array
->JSObject::defineOwnNonIndexProperty(exec
, propertyName
, descriptor
, throwException
);
179 bool JSArray::getOwnPropertySlot(JSObject
* object
, ExecState
* exec
, PropertyName propertyName
, PropertySlot
& slot
)
181 JSArray
* thisObject
= jsCast
<JSArray
*>(object
);
182 if (propertyName
== exec
->propertyNames().length
) {
183 unsigned attributes
= thisObject
->isLengthWritable() ? DontDelete
| DontEnum
: DontDelete
| DontEnum
| ReadOnly
;
184 slot
.setValue(thisObject
, attributes
, jsNumber(thisObject
->length()));
188 return JSObject::getOwnPropertySlot(thisObject
, exec
, propertyName
, slot
);
192 void JSArray::put(JSCell
* cell
, ExecState
* exec
, PropertyName propertyName
, JSValue value
, PutPropertySlot
& slot
)
194 JSArray
* thisObject
= jsCast
<JSArray
*>(cell
);
196 if (propertyName
== exec
->propertyNames().length
) {
197 unsigned newLength
= value
.toUInt32(exec
);
198 if (value
.toNumber(exec
) != static_cast<double>(newLength
)) {
199 exec
->vm().throwException(exec
, createRangeError(exec
, ASCIILiteral("Invalid array length")));
202 thisObject
->setLength(exec
, newLength
, slot
.isStrictMode());
206 JSObject::put(thisObject
, exec
, propertyName
, value
, slot
);
209 bool JSArray::deleteProperty(JSCell
* cell
, ExecState
* exec
, PropertyName propertyName
)
211 JSArray
* thisObject
= jsCast
<JSArray
*>(cell
);
213 if (propertyName
== exec
->propertyNames().length
)
216 return JSObject::deleteProperty(thisObject
, exec
, propertyName
);
219 static int compareKeysForQSort(const void* a
, const void* b
)
221 unsigned da
= *static_cast<const unsigned*>(a
);
222 unsigned db
= *static_cast<const unsigned*>(b
);
223 return (da
> db
) - (da
< db
);
226 void JSArray::getOwnNonIndexPropertyNames(JSObject
* object
, ExecState
* exec
, PropertyNameArray
& propertyNames
, EnumerationMode mode
)
228 JSArray
* thisObject
= jsCast
<JSArray
*>(object
);
230 if (mode
.includeDontEnumProperties())
231 propertyNames
.add(exec
->propertyNames().length
);
233 JSObject::getOwnNonIndexPropertyNames(thisObject
, exec
, propertyNames
, mode
);
236 // This method makes room in the vector, but leaves the new space for count slots uncleared.
237 bool JSArray::unshiftCountSlowCase(VM
& vm
, bool addToFront
, unsigned count
)
239 ArrayStorage
* storage
= ensureArrayStorage(vm
);
240 Butterfly
* butterfly
= storage
->butterfly();
241 unsigned propertyCapacity
= structure(vm
)->outOfLineCapacity();
242 unsigned propertySize
= structure(vm
)->outOfLineSize();
244 // If not, we should have handled this on the fast path.
245 ASSERT(!addToFront
|| count
> storage
->m_indexBias
);
248 // Gather 4 key metrics:
249 // * usedVectorLength - how many entries are currently in the vector (conservative estimate - fewer may be in use in sparse vectors).
250 // * requiredVectorLength - how many entries are will there be in the vector, after allocating space for 'count' more.
251 // * currentCapacity - what is the current size of the vector, including any pre-capacity.
252 // * desiredCapacity - how large should we like to grow the vector to - based on 2x requiredVectorLength.
254 unsigned length
= storage
->length();
255 unsigned usedVectorLength
= min(storage
->vectorLength(), length
);
256 ASSERT(usedVectorLength
<= MAX_STORAGE_VECTOR_LENGTH
);
257 // Check that required vector length is possible, in an overflow-safe fashion.
258 if (count
> MAX_STORAGE_VECTOR_LENGTH
- usedVectorLength
)
260 unsigned requiredVectorLength
= usedVectorLength
+ count
;
261 ASSERT(requiredVectorLength
<= MAX_STORAGE_VECTOR_LENGTH
);
262 // The sum of m_vectorLength and m_indexBias will never exceed MAX_STORAGE_VECTOR_LENGTH.
263 ASSERT(storage
->vectorLength() <= MAX_STORAGE_VECTOR_LENGTH
&& (MAX_STORAGE_VECTOR_LENGTH
- storage
->vectorLength()) >= storage
->m_indexBias
);
264 unsigned currentCapacity
= storage
->vectorLength() + storage
->m_indexBias
;
265 // The calculation of desiredCapacity won't overflow, due to the range of MAX_STORAGE_VECTOR_LENGTH.
266 unsigned desiredCapacity
= min(MAX_STORAGE_VECTOR_LENGTH
, max(BASE_VECTOR_LEN
, requiredVectorLength
) << 1);
269 // We're either going to choose to allocate a new ArrayStorage, or we're going to reuse the existing one.
271 DeferGC
deferGC(vm
.heap
);
272 void* newAllocBase
= 0;
273 unsigned newStorageCapacity
;
274 // If the current storage array is sufficiently large (but not too large!) then just keep using it.
275 if (currentCapacity
> desiredCapacity
&& isDenseEnoughForVector(currentCapacity
, requiredVectorLength
)) {
276 newAllocBase
= butterfly
->base(structure(vm
));
277 newStorageCapacity
= currentCapacity
;
279 size_t newSize
= Butterfly::totalSize(0, propertyCapacity
, true, ArrayStorage::sizeFor(desiredCapacity
));
280 if (!vm
.heap
.tryAllocateStorage(this, newSize
, &newAllocBase
))
282 newStorageCapacity
= desiredCapacity
;
286 // Work out where we're going to move things to.
288 // Determine how much of the vector to use as pre-capacity, and how much as post-capacity.
289 // If we're adding to the end, we'll add all the new space to the end.
290 // If the vector had no free post-capacity (length >= m_vectorLength), don't give it any.
291 // If it did, we calculate the amount that will remain based on an atomic decay - leave the
292 // vector with half the post-capacity it had previously.
293 unsigned postCapacity
= 0;
295 postCapacity
= max(newStorageCapacity
- requiredVectorLength
, count
);
296 else if (length
< storage
->vectorLength()) {
297 // Atomic decay, + the post-capacity cannot be greater than what is available.
298 postCapacity
= min((storage
->vectorLength() - length
) >> 1, newStorageCapacity
- requiredVectorLength
);
299 // If we're moving contents within the same allocation, the post-capacity is being reduced.
300 ASSERT(newAllocBase
!= butterfly
->base(structure(vm
)) || postCapacity
< storage
->vectorLength() - length
);
303 unsigned newVectorLength
= requiredVectorLength
+ postCapacity
;
304 unsigned newIndexBias
= newStorageCapacity
- newVectorLength
;
306 Butterfly
* newButterfly
= Butterfly::fromBase(newAllocBase
, newIndexBias
, propertyCapacity
);
309 ASSERT(count
+ usedVectorLength
<= newVectorLength
);
310 memmove(newButterfly
->arrayStorage()->m_vector
+ count
, storage
->m_vector
, sizeof(JSValue
) * usedVectorLength
);
311 memmove(newButterfly
->propertyStorage() - propertySize
, butterfly
->propertyStorage() - propertySize
, sizeof(JSValue
) * propertySize
+ sizeof(IndexingHeader
) + ArrayStorage::sizeFor(0));
312 } else if ((newAllocBase
!= butterfly
->base(structure(vm
))) || (newIndexBias
!= storage
->m_indexBias
)) {
313 memmove(newButterfly
->propertyStorage() - propertySize
, butterfly
->propertyStorage() - propertySize
, sizeof(JSValue
) * propertySize
+ sizeof(IndexingHeader
) + ArrayStorage::sizeFor(0));
314 memmove(newButterfly
->arrayStorage()->m_vector
, storage
->m_vector
, sizeof(JSValue
) * usedVectorLength
);
316 WriteBarrier
<Unknown
>* newVector
= newButterfly
->arrayStorage()->m_vector
;
317 for (unsigned i
= requiredVectorLength
; i
< newVectorLength
; i
++)
318 newVector
[i
].clear();
321 newButterfly
->arrayStorage()->setVectorLength(newVectorLength
);
322 newButterfly
->arrayStorage()->m_indexBias
= newIndexBias
;
323 setButterflyWithoutChangingStructure(vm
, newButterfly
);
328 bool JSArray::setLengthWithArrayStorage(ExecState
* exec
, unsigned newLength
, bool throwException
, ArrayStorage
* storage
)
330 unsigned length
= storage
->length();
332 // If the length is read only then we enter sparse mode, so should enter the following 'if'.
333 ASSERT(isLengthWritable() || storage
->m_sparseMap
);
335 if (SparseArrayValueMap
* map
= storage
->m_sparseMap
.get()) {
336 // Fail if the length is not writable.
337 if (map
->lengthIsReadOnly())
338 return reject(exec
, throwException
, StrictModeReadonlyPropertyWriteError
);
340 if (newLength
< length
) {
341 // Copy any keys we might be interested in into a vector.
342 Vector
<unsigned, 0, UnsafeVectorOverflow
> keys
;
343 keys
.reserveInitialCapacity(min(map
->size(), static_cast<size_t>(length
- newLength
)));
344 SparseArrayValueMap::const_iterator end
= map
->end();
345 for (SparseArrayValueMap::const_iterator it
= map
->begin(); it
!= end
; ++it
) {
346 unsigned index
= static_cast<unsigned>(it
->key
);
347 if (index
< length
&& index
>= newLength
)
351 // Check if the array is in sparse mode. If so there may be non-configurable
352 // properties, so we have to perform deletion with caution, if not we can
353 // delete values in any order.
354 if (map
->sparseMode()) {
355 qsort(keys
.begin(), keys
.size(), sizeof(unsigned), compareKeysForQSort
);
356 unsigned i
= keys
.size();
358 unsigned index
= keys
[--i
];
359 SparseArrayValueMap::iterator it
= map
->find(index
);
360 ASSERT(it
!= map
->notFound());
361 if (it
->value
.attributes
& DontDelete
) {
362 storage
->setLength(index
+ 1);
363 return reject(exec
, throwException
, "Unable to delete property.");
368 for (unsigned i
= 0; i
< keys
.size(); ++i
)
369 map
->remove(keys
[i
]);
371 deallocateSparseIndexMap();
376 if (newLength
< length
) {
377 // Delete properties from the vector.
378 unsigned usedVectorLength
= min(length
, storage
->vectorLength());
379 for (unsigned i
= newLength
; i
< usedVectorLength
; ++i
) {
380 WriteBarrier
<Unknown
>& valueSlot
= storage
->m_vector
[i
];
381 bool hadValue
= !!valueSlot
;
383 storage
->m_numValuesInVector
-= hadValue
;
387 storage
->setLength(newLength
);
392 bool JSArray::setLength(ExecState
* exec
, unsigned newLength
, bool throwException
)
394 switch (indexingType()) {
398 if (newLength
>= MIN_SPARSE_ARRAY_INDEX
) {
399 return setLengthWithArrayStorage(
400 exec
, newLength
, throwException
,
401 convertContiguousToArrayStorage(exec
->vm()));
403 createInitialUndecided(exec
->vm(), newLength
);
406 case ArrayWithUndecided
:
408 case ArrayWithDouble
:
409 case ArrayWithContiguous
: {
410 if (newLength
== m_butterfly
->publicLength())
412 if (newLength
>= MAX_ARRAY_INDEX
// This case ensures that we can do fast push.
413 || (newLength
>= MIN_SPARSE_ARRAY_INDEX
414 && !isDenseEnoughForVector(newLength
, countElements()))) {
415 return setLengthWithArrayStorage(
416 exec
, newLength
, throwException
,
417 ensureArrayStorage(exec
->vm()));
419 if (newLength
> m_butterfly
->publicLength()) {
420 ensureLength(exec
->vm(), newLength
);
424 unsigned lengthToClear
= m_butterfly
->publicLength() - newLength
;
425 unsigned costToAllocateNewButterfly
= 64; // a heuristic.
426 if (lengthToClear
> newLength
&& lengthToClear
> costToAllocateNewButterfly
) {
427 reallocateAndShrinkButterfly(exec
->vm(), newLength
);
431 if (indexingType() == ArrayWithDouble
) {
432 for (unsigned i
= m_butterfly
->publicLength(); i
-- > newLength
;)
433 m_butterfly
->contiguousDouble()[i
] = PNaN
;
435 for (unsigned i
= m_butterfly
->publicLength(); i
-- > newLength
;)
436 m_butterfly
->contiguous()[i
].clear();
438 m_butterfly
->setPublicLength(newLength
);
442 case ArrayWithArrayStorage
:
443 case ArrayWithSlowPutArrayStorage
:
444 return setLengthWithArrayStorage(exec
, newLength
, throwException
, arrayStorage());
452 JSValue
JSArray::pop(ExecState
* exec
)
454 switch (indexingType()) {
456 return jsUndefined();
458 case ArrayWithUndecided
:
459 if (!m_butterfly
->publicLength())
460 return jsUndefined();
461 // We have nothing but holes. So, drop down to the slow version.
465 case ArrayWithContiguous
: {
466 unsigned length
= m_butterfly
->publicLength();
469 return jsUndefined();
471 RELEASE_ASSERT(length
< m_butterfly
->vectorLength());
472 JSValue value
= m_butterfly
->contiguous()[length
].get();
474 m_butterfly
->contiguous()[length
].clear();
475 m_butterfly
->setPublicLength(length
);
481 case ArrayWithDouble
: {
482 unsigned length
= m_butterfly
->publicLength();
485 return jsUndefined();
487 RELEASE_ASSERT(length
< m_butterfly
->vectorLength());
488 double value
= m_butterfly
->contiguousDouble()[length
];
489 if (value
== value
) {
490 m_butterfly
->contiguousDouble()[length
] = PNaN
;
491 m_butterfly
->setPublicLength(length
);
492 return JSValue(JSValue::EncodeAsDouble
, value
);
497 case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES
: {
498 ArrayStorage
* storage
= m_butterfly
->arrayStorage();
500 unsigned length
= storage
->length();
502 if (!isLengthWritable())
503 throwTypeError(exec
, StrictModeReadonlyPropertyWriteError
);
504 return jsUndefined();
507 unsigned index
= length
- 1;
508 if (index
< storage
->vectorLength()) {
509 WriteBarrier
<Unknown
>& valueSlot
= storage
->m_vector
[index
];
511 --storage
->m_numValuesInVector
;
512 JSValue element
= valueSlot
.get();
515 RELEASE_ASSERT(isLengthWritable());
516 storage
->setLength(index
);
528 unsigned index
= getArrayLength() - 1;
529 // Let element be the result of calling the [[Get]] internal method of O with argument indx.
530 JSValue element
= get(exec
, index
);
531 if (exec
->hadException())
532 return jsUndefined();
533 // Call the [[Delete]] internal method of O with arguments indx and true.
534 if (!deletePropertyByIndex(this, exec
, index
)) {
535 throwTypeError(exec
, ASCIILiteral("Unable to delete property."));
536 return jsUndefined();
538 // Call the [[Put]] internal method of O with arguments "length", indx, and true.
539 setLength(exec
, index
, true);
544 // Push & putIndex are almost identical, with two small differences.
545 // - we always are writing beyond the current array bounds, so it is always necessary to update m_length & m_numValuesInVector.
546 // - pushing to an array of length 2^32-1 stores the property, but throws a range error.
547 void JSArray::push(ExecState
* exec
, JSValue value
)
549 switch (indexingType()) {
551 createInitialUndecided(exec
->vm(), 0);
555 case ArrayWithUndecided
: {
556 convertUndecidedForValue(exec
->vm(), value
);
561 case ArrayWithInt32
: {
562 if (!value
.isInt32()) {
563 convertInt32ForValue(exec
->vm(), value
);
568 unsigned length
= m_butterfly
->publicLength();
569 ASSERT(length
<= m_butterfly
->vectorLength());
570 if (length
< m_butterfly
->vectorLength()) {
571 m_butterfly
->contiguousInt32()[length
].setWithoutWriteBarrier(value
);
572 m_butterfly
->setPublicLength(length
+ 1);
576 if (length
> MAX_ARRAY_INDEX
) {
577 methodTable(exec
->vm())->putByIndex(this, exec
, length
, value
, true);
578 if (!exec
->hadException())
579 exec
->vm().throwException(exec
, createRangeError(exec
, ASCIILiteral("Invalid array length")));
583 putByIndexBeyondVectorLengthWithoutAttributes
<Int32Shape
>(exec
, length
, value
);
587 case ArrayWithContiguous
: {
588 unsigned length
= m_butterfly
->publicLength();
589 ASSERT(length
<= m_butterfly
->vectorLength());
590 if (length
< m_butterfly
->vectorLength()) {
591 m_butterfly
->contiguous()[length
].set(exec
->vm(), this, value
);
592 m_butterfly
->setPublicLength(length
+ 1);
596 if (length
> MAX_ARRAY_INDEX
) {
597 methodTable(exec
->vm())->putByIndex(this, exec
, length
, value
, true);
598 if (!exec
->hadException())
599 exec
->vm().throwException(exec
, createRangeError(exec
, ASCIILiteral("Invalid array length")));
603 putByIndexBeyondVectorLengthWithoutAttributes
<ContiguousShape
>(exec
, length
, value
);
607 case ArrayWithDouble
: {
608 if (!value
.isNumber()) {
609 convertDoubleToContiguous(exec
->vm());
613 double valueAsDouble
= value
.asNumber();
614 if (valueAsDouble
!= valueAsDouble
) {
615 convertDoubleToContiguous(exec
->vm());
620 unsigned length
= m_butterfly
->publicLength();
621 ASSERT(length
<= m_butterfly
->vectorLength());
622 if (length
< m_butterfly
->vectorLength()) {
623 m_butterfly
->contiguousDouble()[length
] = valueAsDouble
;
624 m_butterfly
->setPublicLength(length
+ 1);
628 if (length
> MAX_ARRAY_INDEX
) {
629 methodTable(exec
->vm())->putByIndex(this, exec
, length
, value
, true);
630 if (!exec
->hadException())
631 exec
->vm().throwException(exec
, createRangeError(exec
, ASCIILiteral("Invalid array length")));
635 putByIndexBeyondVectorLengthWithoutAttributes
<DoubleShape
>(exec
, length
, value
);
639 case ArrayWithSlowPutArrayStorage
: {
640 unsigned oldLength
= length();
641 if (attemptToInterceptPutByIndexOnHole(exec
, oldLength
, value
, true)) {
642 if (!exec
->hadException() && oldLength
< 0xFFFFFFFFu
)
643 setLength(exec
, oldLength
+ 1, true);
649 case ArrayWithArrayStorage
: {
650 ArrayStorage
* storage
= m_butterfly
->arrayStorage();
652 // Fast case - push within vector, always update m_length & m_numValuesInVector.
653 unsigned length
= storage
->length();
654 if (length
< storage
->vectorLength()) {
655 storage
->m_vector
[length
].set(exec
->vm(), this, value
);
656 storage
->setLength(length
+ 1);
657 ++storage
->m_numValuesInVector
;
661 // Pushing to an array of invalid length (2^31-1) stores the property, but throws a range error.
662 if (storage
->length() > MAX_ARRAY_INDEX
) {
663 methodTable(exec
->vm())->putByIndex(this, exec
, storage
->length(), value
, true);
664 // Per ES5.1 15.4.4.7 step 6 & 15.4.5.1 step 3.d.
665 if (!exec
->hadException())
666 exec
->vm().throwException(exec
, createRangeError(exec
, ASCIILiteral("Invalid array length")));
670 // Handled the same as putIndex.
671 putByIndexBeyondVectorLengthWithArrayStorage(exec
, storage
->length(), value
, true, storage
);
676 RELEASE_ASSERT_NOT_REACHED();
680 JSArray
* JSArray::fastSlice(ExecState
& exec
, unsigned startIndex
, unsigned count
)
682 auto arrayType
= indexingType();
684 case ArrayWithDouble
:
686 case ArrayWithContiguous
: {
688 if (count
>= MIN_SPARSE_ARRAY_INDEX
|| structure(vm
)->holesMustForwardToPrototype(vm
))
691 Structure
* resultStructure
= exec
.lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(arrayType
);
692 JSArray
* resultArray
= JSArray::tryCreateUninitialized(vm
, resultStructure
, count
);
696 auto& resultButterfly
= *resultArray
->butterfly();
697 if (arrayType
== ArrayWithDouble
)
698 memcpy(resultButterfly
.contiguousDouble().data(), m_butterfly
->contiguousDouble().data() + startIndex
, sizeof(JSValue
) * count
);
700 memcpy(resultButterfly
.contiguous().data(), m_butterfly
->contiguous().data() + startIndex
, sizeof(JSValue
) * count
);
701 resultButterfly
.setPublicLength(count
);
710 EncodedJSValue
JSArray::fastConcatWith(ExecState
& exec
, JSArray
& otherArray
)
712 auto newArrayType
= indexingType();
715 ASSERT(newArrayType
== fastConcatType(vm
, *this, otherArray
));
717 unsigned thisArraySize
= m_butterfly
->publicLength();
718 unsigned otherArraySize
= otherArray
.m_butterfly
->publicLength();
719 ASSERT(thisArraySize
+ otherArraySize
< MIN_SPARSE_ARRAY_INDEX
);
721 Structure
* resultStructure
= exec
.lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(newArrayType
);
722 JSArray
* resultArray
= JSArray::tryCreateUninitialized(vm
, resultStructure
, thisArraySize
+ otherArraySize
);
724 return JSValue::encode(throwOutOfMemoryError(&exec
));
726 auto& resultButterfly
= *resultArray
->butterfly();
727 auto& otherButterfly
= *otherArray
.butterfly();
728 if (newArrayType
== ArrayWithDouble
) {
729 auto buffer
= resultButterfly
.contiguousDouble().data();
730 memcpy(buffer
, m_butterfly
->contiguousDouble().data(), sizeof(JSValue
) * thisArraySize
);
731 memcpy(buffer
+ thisArraySize
, otherButterfly
.contiguousDouble().data(), sizeof(JSValue
) * otherArraySize
);
733 auto buffer
= resultButterfly
.contiguous().data();
734 memcpy(buffer
, m_butterfly
->contiguous().data(), sizeof(JSValue
) * thisArraySize
);
735 memcpy(buffer
+ thisArraySize
, otherButterfly
.contiguous().data(), sizeof(JSValue
) * otherArraySize
);
738 resultButterfly
.setPublicLength(thisArraySize
+ otherArraySize
);
739 return JSValue::encode(resultArray
);
742 bool JSArray::shiftCountWithArrayStorage(VM
& vm
, unsigned startIndex
, unsigned count
, ArrayStorage
* storage
)
744 unsigned oldLength
= storage
->length();
745 RELEASE_ASSERT(count
<= oldLength
);
747 // If the array contains holes or is otherwise in an abnormal state,
748 // use the generic algorithm in ArrayPrototype.
749 if ((storage
->hasHoles() && this->structure(vm
)->holesMustForwardToPrototype(vm
))
751 || shouldUseSlowPut(indexingType())) {
758 unsigned length
= oldLength
- count
;
760 storage
->m_numValuesInVector
-= count
;
761 storage
->setLength(length
);
763 unsigned vectorLength
= storage
->vectorLength();
767 if (startIndex
>= vectorLength
)
770 if (startIndex
+ count
> vectorLength
)
771 count
= vectorLength
- startIndex
;
773 unsigned usedVectorLength
= min(vectorLength
, oldLength
);
775 unsigned numElementsBeforeShiftRegion
= startIndex
;
776 unsigned firstIndexAfterShiftRegion
= startIndex
+ count
;
777 unsigned numElementsAfterShiftRegion
= usedVectorLength
- firstIndexAfterShiftRegion
;
778 ASSERT(numElementsBeforeShiftRegion
+ count
+ numElementsAfterShiftRegion
== usedVectorLength
);
780 // The point of this comparison seems to be to minimize the amount of elements that have to
781 // be moved during a shift operation.
782 if (numElementsBeforeShiftRegion
< numElementsAfterShiftRegion
) {
783 // The number of elements before the shift region is less than the number of elements
784 // after the shift region, so we move the elements before to the right.
785 if (numElementsBeforeShiftRegion
) {
786 RELEASE_ASSERT(count
+ startIndex
<= vectorLength
);
787 if (storage
->hasHoles()) {
788 for (unsigned i
= startIndex
; i
-- > 0;) {
789 unsigned destinationIndex
= count
+ i
;
790 JSValue source
= storage
->m_vector
[i
].get();
791 JSValue dest
= storage
->m_vector
[destinationIndex
].get();
792 // Any time we overwrite a hole we know we overcounted the number of values we removed
793 // when we subtracted count from m_numValuesInVector above.
794 if (!dest
&& destinationIndex
>= firstIndexAfterShiftRegion
)
795 storage
->m_numValuesInVector
++;
796 storage
->m_vector
[count
+ i
].setWithoutWriteBarrier(source
);
799 memmove(storage
->m_vector
+ count
,
801 sizeof(JSValue
) * startIndex
);
804 // Adjust the Butterfly and the index bias. We only need to do this here because we're changing
805 // the start of the Butterfly, which needs to point at the first indexed property in the used
806 // portion of the vector.
807 m_butterfly
.setWithoutWriteBarrier(m_butterfly
->shift(structure(), count
));
808 storage
= m_butterfly
->arrayStorage();
809 storage
->m_indexBias
+= count
;
811 // Since we're consuming part of the vector by moving its beginning to the left,
812 // we need to modify the vector length appropriately.
813 storage
->setVectorLength(vectorLength
- count
);
815 // The number of elements before the shift region is greater than or equal to the number
816 // of elements after the shift region, so we move the elements after the shift region to the left.
817 if (storage
->hasHoles()) {
818 for (unsigned i
= 0; i
< numElementsAfterShiftRegion
; ++i
) {
819 unsigned destinationIndex
= startIndex
+ i
;
820 JSValue source
= storage
->m_vector
[firstIndexAfterShiftRegion
+ i
].get();
821 JSValue dest
= storage
->m_vector
[destinationIndex
].get();
822 // Any time we overwrite a hole we know we overcounted the number of values we removed
823 // when we subtracted count from m_numValuesInVector above.
824 if (!dest
&& destinationIndex
< firstIndexAfterShiftRegion
)
825 storage
->m_numValuesInVector
++;
826 storage
->m_vector
[startIndex
+ i
].setWithoutWriteBarrier(source
);
829 memmove(storage
->m_vector
+ startIndex
,
830 storage
->m_vector
+ firstIndexAfterShiftRegion
,
831 sizeof(JSValue
) * numElementsAfterShiftRegion
);
833 // Clear the slots of the elements we just moved.
834 unsigned startOfEmptyVectorTail
= usedVectorLength
- count
;
835 for (unsigned i
= startOfEmptyVectorTail
; i
< usedVectorLength
; ++i
)
836 storage
->m_vector
[i
].clear();
837 // We don't modify the index bias or the Butterfly pointer in this case because we're not changing
838 // the start of the Butterfly, which needs to point at the first indexed property in the used
839 // portion of the vector. We also don't modify the vector length because we're not actually changing
840 // its length; we're just using less of it.
846 bool JSArray::shiftCountWithAnyIndexingType(ExecState
* exec
, unsigned& startIndex
, unsigned count
)
849 RELEASE_ASSERT(count
> 0);
851 switch (indexingType()) {
855 case ArrayWithUndecided
:
856 // Don't handle this because it's confusing and it shouldn't come up.
860 case ArrayWithContiguous
: {
861 unsigned oldLength
= m_butterfly
->publicLength();
862 RELEASE_ASSERT(count
<= oldLength
);
864 // We may have to walk the entire array to do the shift. We're willing to do
865 // so only if it's not horribly slow.
866 if (oldLength
- (startIndex
+ count
) >= MIN_SPARSE_ARRAY_INDEX
)
867 return shiftCountWithArrayStorage(vm
, startIndex
, count
, ensureArrayStorage(vm
));
869 // Storing to a hole is fine since we're still having a good time. But reading from a hole
870 // is totally not fine, since we might have to read from the proto chain.
871 // We have to check for holes before we start moving things around so that we don't get halfway
872 // through shifting and then realize we should have been in ArrayStorage mode.
873 unsigned end
= oldLength
- count
;
874 if (this->structure(vm
)->holesMustForwardToPrototype(vm
)) {
875 for (unsigned i
= startIndex
; i
< end
; ++i
) {
876 JSValue v
= m_butterfly
->contiguous()[i
+ count
].get();
879 return shiftCountWithArrayStorage(vm
, startIndex
, count
, ensureArrayStorage(vm
));
881 m_butterfly
->contiguous()[i
].setWithoutWriteBarrier(v
);
884 memmove(m_butterfly
->contiguous().data() + startIndex
,
885 m_butterfly
->contiguous().data() + startIndex
+ count
,
886 sizeof(JSValue
) * (end
- startIndex
));
889 for (unsigned i
= end
; i
< oldLength
; ++i
)
890 m_butterfly
->contiguous()[i
].clear();
892 m_butterfly
->setPublicLength(oldLength
- count
);
896 case ArrayWithDouble
: {
897 unsigned oldLength
= m_butterfly
->publicLength();
898 RELEASE_ASSERT(count
<= oldLength
);
900 // We may have to walk the entire array to do the shift. We're willing to do
901 // so only if it's not horribly slow.
902 if (oldLength
- (startIndex
+ count
) >= MIN_SPARSE_ARRAY_INDEX
)
903 return shiftCountWithArrayStorage(vm
, startIndex
, count
, ensureArrayStorage(vm
));
905 // Storing to a hole is fine since we're still having a good time. But reading from a hole
906 // is totally not fine, since we might have to read from the proto chain.
907 // We have to check for holes before we start moving things around so that we don't get halfway
908 // through shifting and then realize we should have been in ArrayStorage mode.
909 unsigned end
= oldLength
- count
;
910 if (this->structure(vm
)->holesMustForwardToPrototype(vm
)) {
911 for (unsigned i
= startIndex
; i
< end
; ++i
) {
912 double v
= m_butterfly
->contiguousDouble()[i
+ count
];
913 if (UNLIKELY(v
!= v
)) {
915 return shiftCountWithArrayStorage(vm
, startIndex
, count
, ensureArrayStorage(vm
));
917 m_butterfly
->contiguousDouble()[i
] = v
;
920 memmove(m_butterfly
->contiguousDouble().data() + startIndex
,
921 m_butterfly
->contiguousDouble().data() + startIndex
+ count
,
922 sizeof(JSValue
) * (end
- startIndex
));
924 for (unsigned i
= end
; i
< oldLength
; ++i
)
925 m_butterfly
->contiguousDouble()[i
] = PNaN
;
927 m_butterfly
->setPublicLength(oldLength
- count
);
931 case ArrayWithArrayStorage
:
932 case ArrayWithSlowPutArrayStorage
:
933 return shiftCountWithArrayStorage(vm
, startIndex
, count
, arrayStorage());
941 // Returns true if the unshift can be handled, false to fallback.
942 bool JSArray::unshiftCountWithArrayStorage(ExecState
* exec
, unsigned startIndex
, unsigned count
, ArrayStorage
* storage
)
944 unsigned length
= storage
->length();
946 RELEASE_ASSERT(startIndex
<= length
);
948 // If the array contains holes or is otherwise in an abnormal state,
949 // use the generic algorithm in ArrayPrototype.
950 if (storage
->hasHoles() || storage
->inSparseMode() || shouldUseSlowPut(indexingType()))
953 bool moveFront
= !startIndex
|| startIndex
< length
/ 2;
955 unsigned vectorLength
= storage
->vectorLength();
957 if (moveFront
&& storage
->m_indexBias
>= count
) {
958 Butterfly
* newButterfly
= storage
->butterfly()->unshift(structure(), count
);
959 storage
= newButterfly
->arrayStorage();
960 storage
->m_indexBias
-= count
;
961 storage
->setVectorLength(vectorLength
+ count
);
962 setButterflyWithoutChangingStructure(exec
->vm(), newButterfly
);
963 } else if (!moveFront
&& vectorLength
- length
>= count
)
964 storage
= storage
->butterfly()->arrayStorage();
965 else if (unshiftCountSlowCase(exec
->vm(), moveFront
, count
))
966 storage
= arrayStorage();
968 throwOutOfMemoryError(exec
);
972 WriteBarrier
<Unknown
>* vector
= storage
->m_vector
;
976 memmove(vector
, vector
+ count
, startIndex
* sizeof(JSValue
));
977 else if (length
- startIndex
)
978 memmove(vector
+ startIndex
+ count
, vector
+ startIndex
, (length
- startIndex
) * sizeof(JSValue
));
981 for (unsigned i
= 0; i
< count
; i
++)
982 vector
[i
+ startIndex
].clear();
986 bool JSArray::unshiftCountWithAnyIndexingType(ExecState
* exec
, unsigned startIndex
, unsigned count
)
988 switch (indexingType()) {
990 case ArrayWithUndecided
:
991 // We could handle this. But it shouldn't ever come up, so we won't.
995 case ArrayWithContiguous
: {
996 unsigned oldLength
= m_butterfly
->publicLength();
998 // We may have to walk the entire array to do the unshift. We're willing to do so
999 // only if it's not horribly slow.
1000 if (oldLength
- startIndex
>= MIN_SPARSE_ARRAY_INDEX
)
1001 return unshiftCountWithArrayStorage(exec
, startIndex
, count
, ensureArrayStorage(exec
->vm()));
1003 ensureLength(exec
->vm(), oldLength
+ count
);
1005 // We have to check for holes before we start moving things around so that we don't get halfway
1006 // through shifting and then realize we should have been in ArrayStorage mode.
1007 for (unsigned i
= oldLength
; i
-- > startIndex
;) {
1008 JSValue v
= m_butterfly
->contiguous()[i
].get();
1010 return unshiftCountWithArrayStorage(exec
, startIndex
, count
, ensureArrayStorage(exec
->vm()));
1013 for (unsigned i
= oldLength
; i
-- > startIndex
;) {
1014 JSValue v
= m_butterfly
->contiguous()[i
].get();
1016 m_butterfly
->contiguous()[i
+ count
].setWithoutWriteBarrier(v
);
1019 // NOTE: we're leaving being garbage in the part of the array that we shifted out
1020 // of. This is fine because the caller is required to store over that area, and
1021 // in contiguous mode storing into a hole is guaranteed to behave exactly the same
1022 // as storing over an existing element.
1027 case ArrayWithDouble
: {
1028 unsigned oldLength
= m_butterfly
->publicLength();
1030 // We may have to walk the entire array to do the unshift. We're willing to do so
1031 // only if it's not horribly slow.
1032 if (oldLength
- startIndex
>= MIN_SPARSE_ARRAY_INDEX
)
1033 return unshiftCountWithArrayStorage(exec
, startIndex
, count
, ensureArrayStorage(exec
->vm()));
1035 ensureLength(exec
->vm(), oldLength
+ count
);
1037 // We have to check for holes before we start moving things around so that we don't get halfway
1038 // through shifting and then realize we should have been in ArrayStorage mode.
1039 for (unsigned i
= oldLength
; i
-- > startIndex
;) {
1040 double v
= m_butterfly
->contiguousDouble()[i
];
1041 if (UNLIKELY(v
!= v
))
1042 return unshiftCountWithArrayStorage(exec
, startIndex
, count
, ensureArrayStorage(exec
->vm()));
1045 for (unsigned i
= oldLength
; i
-- > startIndex
;) {
1046 double v
= m_butterfly
->contiguousDouble()[i
];
1048 m_butterfly
->contiguousDouble()[i
+ count
] = v
;
1051 // NOTE: we're leaving being garbage in the part of the array that we shifted out
1052 // of. This is fine because the caller is required to store over that area, and
1053 // in contiguous mode storing into a hole is guaranteed to behave exactly the same
1054 // as storing over an existing element.
1059 case ArrayWithArrayStorage
:
1060 case ArrayWithSlowPutArrayStorage
:
1061 return unshiftCountWithArrayStorage(exec
, startIndex
, count
, arrayStorage());
1069 void JSArray::fillArgList(ExecState
* exec
, MarkedArgumentBuffer
& args
)
1073 WriteBarrier
<Unknown
>* vector
;
1075 switch (indexingType()) {
1079 case ArrayWithUndecided
: {
1085 case ArrayWithInt32
:
1086 case ArrayWithContiguous
: {
1087 vectorEnd
= m_butterfly
->publicLength();
1088 vector
= m_butterfly
->contiguous().data();
1092 case ArrayWithDouble
: {
1095 for (; i
< m_butterfly
->publicLength(); ++i
) {
1096 double v
= butterfly()->contiguousDouble()[i
];
1099 args
.append(JSValue(JSValue::EncodeAsDouble
, v
));
1104 case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES
: {
1105 ArrayStorage
* storage
= m_butterfly
->arrayStorage();
1107 vector
= storage
->m_vector
;
1108 vectorEnd
= min(storage
->length(), storage
->vectorLength());
1114 #if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE)
1121 for (; i
< vectorEnd
; ++i
) {
1122 WriteBarrier
<Unknown
>& v
= vector
[i
];
1125 args
.append(v
.get());
1128 // FIXME: What prevents this from being called with a RuntimeArray? The length function will always return 0 in that case.
1129 for (; i
< length(); ++i
)
1130 args
.append(get(exec
, i
));
1133 void JSArray::copyToArguments(ExecState
* exec
, VirtualRegister firstElementDest
, unsigned offset
, unsigned length
)
1135 unsigned i
= offset
;
1136 WriteBarrier
<Unknown
>* vector
;
1138 length
+= offset
; // We like to think of the length as being our length, rather than the output length.
1140 // FIXME: What prevents this from being called with a RuntimeArray? The length function will always return 0 in that case.
1141 ASSERT(length
== this->length());
1143 switch (indexingType()) {
1147 case ArrayWithUndecided
: {
1153 case ArrayWithInt32
:
1154 case ArrayWithContiguous
: {
1155 vector
= m_butterfly
->contiguous().data();
1156 vectorEnd
= m_butterfly
->publicLength();
1160 case ArrayWithDouble
: {
1163 for (; i
< m_butterfly
->publicLength(); ++i
) {
1164 ASSERT(i
< butterfly()->vectorLength());
1165 double v
= m_butterfly
->contiguousDouble()[i
];
1168 exec
->r(firstElementDest
+ i
- offset
) = JSValue(JSValue::EncodeAsDouble
, v
);
1173 case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES
: {
1174 ArrayStorage
* storage
= m_butterfly
->arrayStorage();
1175 vector
= storage
->m_vector
;
1176 vectorEnd
= min(length
, storage
->vectorLength());
1182 #if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE)
1189 for (; i
< vectorEnd
; ++i
) {
1190 WriteBarrier
<Unknown
>& v
= vector
[i
];
1193 exec
->r(firstElementDest
+ i
- offset
) = v
.get();
1196 for (; i
< length
; ++i
) {
1197 exec
->r(firstElementDest
+ i
- offset
) = get(exec
, i
);
1198 if (UNLIKELY(exec
->vm().exception()))