#include "config.h"
#include "JSArray.h"
#include "ArrayPrototype.h"
#include "ButterflyInlines.h"
#include "CodeBlock.h"
#include "CopiedSpace.h"
#include "Error.h"
#include "Executable.h"
#include "GetterSetter.h"
#include "IndexingHeaderInlines.h"
#include "JSCInlines.h"
#include "PropertyNameArray.h"
#include "Reject.h"
#include <wtf/Assertions.h>
using namespace std;
using namespace WTF;
namespace JSC {
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSArray);
const ClassInfo JSArray::s_info = {"Array", &JSNonFinalObject::s_info, 0, CREATE_METHOD_TABLE(JSArray)};
Butterfly* createArrayButterflyInDictionaryIndexingMode(
VM& vm, JSCell* intendedOwner, unsigned initialLength)
{
Butterfly* butterfly = Butterfly::create(
vm, intendedOwner, 0, 0, true, IndexingHeader(), ArrayStorage::sizeFor(0));
ArrayStorage* storage = butterfly->arrayStorage();
storage->setLength(initialLength);
storage->setVectorLength(0);
storage->m_indexBias = 0;
storage->m_sparseMap.clear();
storage->m_numValuesInVector = 0;
return butterfly;
}
void JSArray::setLengthWritable(ExecState* exec, bool writable)
{
ASSERT(isLengthWritable() || !writable);
if (!isLengthWritable() || writable)
return;
enterDictionaryIndexingMode(exec->vm());
SparseArrayValueMap* map = arrayStorage()->m_sparseMap.get();
ASSERT(map);
map->setLengthIsReadOnly();
}
bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
{
JSArray* array = jsCast<JSArray*>(object);
if (propertyName == exec->propertyNames().length) {
if (descriptor.configurablePresent() && descriptor.configurable())
return reject(exec, throwException, "Attempting to change configurable attribute of unconfigurable property.");
if (descriptor.enumerablePresent() && descriptor.enumerable())
return reject(exec, throwException, "Attempting to change enumerable attribute of unconfigurable property.");
if (descriptor.isAccessorDescriptor())
return reject(exec, throwException, UnconfigurablePropertyChangeAccessMechanismError);
if (!array->isLengthWritable() && descriptor.writablePresent() && descriptor.writable())
return reject(exec, throwException, "Attempting to change writable attribute of unconfigurable property.");
if (!descriptor.value()) {
if (descriptor.writablePresent())
array->setLengthWritable(exec, descriptor.writable());
return true;
}
unsigned newLen = descriptor.value().toUInt32(exec);
if (newLen != descriptor.value().toNumber(exec)) {
exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length")));
return false;
}
if (newLen == array->length()) {
if (descriptor.writablePresent())
array->setLengthWritable(exec, descriptor.writable());
return true;
}
if (!array->isLengthWritable())
return reject(exec, throwException, "Attempting to change value of a readonly property.");
if (!array->setLength(exec, newLen, throwException)) {
if (descriptor.writablePresent())
array->setLengthWritable(exec, descriptor.writable());
return false;
}
if (descriptor.writablePresent())
array->setLengthWritable(exec, descriptor.writable());
return true;
}
if (Optional<uint32_t> optionalIndex = parseIndex(propertyName)) {
uint32_t index = optionalIndex.value();
if (index >= array->length() && !array->isLengthWritable())
return reject(exec, throwException, "Attempting to define numeric property on array with non-writable length property.");
return array->defineOwnIndexedProperty(exec, index, descriptor, throwException);
}
return array->JSObject::defineOwnNonIndexProperty(exec, propertyName, descriptor, throwException);
}
bool JSArray::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
{
JSArray* thisObject = jsCast<JSArray*>(object);
if (propertyName == exec->propertyNames().length) {
unsigned attributes = thisObject->isLengthWritable() ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly;
slot.setValue(thisObject, attributes, jsNumber(thisObject->length()));
return true;
}
return JSObject::getOwnPropertySlot(thisObject, exec, propertyName, slot);
}
bool JSArray::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
if (UNLIKELY(isThisValueAltered(slot, thisObject)))
return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
if (propertyName == exec->propertyNames().length) {
unsigned newLength = value.toUInt32(exec);
if (value.toNumber(exec) != static_cast<double>(newLength)) {
exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length")));
return false;
}
return thisObject->setLength(exec, newLength, slot.isStrictMode());
}
return JSObject::put(thisObject, exec, propertyName, value, slot);
}
bool JSArray::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
if (propertyName == exec->propertyNames().length)
return false;
return JSObject::deleteProperty(thisObject, exec, propertyName);
}
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::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
{
JSArray* thisObject = jsCast<JSArray*>(object);
if (mode.includeDontEnumProperties())
propertyNames.add(exec->propertyNames().length);
JSObject::getOwnNonIndexPropertyNames(thisObject, exec, propertyNames, mode);
}
bool JSArray::unshiftCountSlowCase(VM& vm, bool addToFront, unsigned count)
{
ArrayStorage* storage = ensureArrayStorage(vm);
Butterfly* butterfly = storage->butterfly();
unsigned propertyCapacity = structure(vm)->outOfLineCapacity();
unsigned propertySize = structure(vm)->outOfLineSize();
ASSERT(!addToFront || count > storage->m_indexBias);
unsigned length = storage->length();
unsigned usedVectorLength = min(storage->vectorLength(), length);
ASSERT(usedVectorLength <= MAX_STORAGE_VECTOR_LENGTH);
if (count > MAX_STORAGE_VECTOR_LENGTH - usedVectorLength)
return false;
unsigned requiredVectorLength = usedVectorLength + count;
ASSERT(requiredVectorLength <= 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;
unsigned desiredCapacity = min(MAX_STORAGE_VECTOR_LENGTH, max(BASE_VECTOR_LEN, requiredVectorLength) << 1);
DeferGC deferGC(vm.heap);
void* newAllocBase = 0;
unsigned newStorageCapacity;
if (currentCapacity > desiredCapacity && isDenseEnoughForVector(currentCapacity, requiredVectorLength)) {
newAllocBase = butterfly->base(structure(vm));
newStorageCapacity = currentCapacity;
} else {
size_t newSize = Butterfly::totalSize(0, propertyCapacity, true, ArrayStorage::sizeFor(desiredCapacity));
if (!vm.heap.tryAllocateStorage(this, newSize, &newAllocBase))
return false;
newStorageCapacity = desiredCapacity;
}
unsigned postCapacity = 0;
if (!addToFront)
postCapacity = max(newStorageCapacity - requiredVectorLength, count);
else if (length < storage->vectorLength()) {
postCapacity = min((storage->vectorLength() - length) >> 1, newStorageCapacity - requiredVectorLength);
ASSERT(newAllocBase != butterfly->base(structure(vm)) || postCapacity < storage->vectorLength() - length);
}
unsigned newVectorLength = requiredVectorLength + postCapacity;
unsigned newIndexBias = newStorageCapacity - newVectorLength;
Butterfly* newButterfly = Butterfly::fromBase(newAllocBase, newIndexBias, propertyCapacity);
if (addToFront) {
ASSERT(count + usedVectorLength <= newVectorLength);
memmove(newButterfly->arrayStorage()->m_vector + count, storage->m_vector, sizeof(JSValue) * usedVectorLength);
memmove(newButterfly->propertyStorage() - propertySize, butterfly->propertyStorage() - propertySize, sizeof(JSValue) * propertySize + sizeof(IndexingHeader) + ArrayStorage::sizeFor(0));
} else if ((newAllocBase != butterfly->base(structure(vm))) || (newIndexBias != storage->m_indexBias)) {
memmove(newButterfly->propertyStorage() - propertySize, butterfly->propertyStorage() - propertySize, sizeof(JSValue) * propertySize + sizeof(IndexingHeader) + ArrayStorage::sizeFor(0));
memmove(newButterfly->arrayStorage()->m_vector, storage->m_vector, sizeof(JSValue) * usedVectorLength);
WriteBarrier<Unknown>* newVector = newButterfly->arrayStorage()->m_vector;
for (unsigned i = requiredVectorLength; i < newVectorLength; i++)
newVector[i].clear();
}
newButterfly->arrayStorage()->setVectorLength(newVectorLength);
newButterfly->arrayStorage()->m_indexBias = newIndexBias;
setButterflyWithoutChangingStructure(vm, newButterfly);
return true;
}
bool JSArray::setLengthWithArrayStorage(ExecState* exec, unsigned newLength, bool throwException, ArrayStorage* storage)
{
unsigned length = storage->length();
ASSERT(isLengthWritable() || storage->m_sparseMap);
if (SparseArrayValueMap* map = storage->m_sparseMap.get()) {
if (map->lengthIsReadOnly())
return reject(exec, throwException, StrictModeReadonlyPropertyWriteError);
if (newLength < length) {
Vector<unsigned, 0, UnsafeVectorOverflow> keys;
keys.reserveInitialCapacity(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);
}
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 & DontDelete) {
storage->setLength(index + 1);
return reject(exec, throwException, "Unable to delete property.");
}
map->remove(it);
}
} else {
for (unsigned i = 0; i < keys.size(); ++i)
map->remove(keys[i]);
if (map->isEmpty())
deallocateSparseIndexMap();
}
}
}
if (newLength < length) {
unsigned usedVectorLength = min(length, storage->vectorLength());
for (unsigned i = newLength; i < usedVectorLength; ++i) {
WriteBarrier<Unknown>& valueSlot = storage->m_vector[i];
bool hadValue = !!valueSlot;
valueSlot.clear();
storage->m_numValuesInVector -= hadValue;
}
}
storage->setLength(newLength);
return true;
}
bool JSArray::appendMemcpy(ExecState* exec, VM& vm, unsigned startIndex, JSC::JSArray* otherArray)
{
if (!canFastCopy(vm, otherArray))
return false;
IndexingType type = indexingType();
IndexingType copyType = mergeIndexingTypeForCopying(otherArray->indexingType());
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();
unsigned newLength = startIndex + otherLength;
if (newLength >= MIN_SPARSE_ARRAY_INDEX)
return false;
if (!ensureLength(vm, newLength)) {
throwOutOfMemoryError(exec);
return false;
}
ASSERT(copyType == indexingType());
if (type == ArrayWithDouble)
memcpy(butterfly()->contiguousDouble().data() + startIndex, otherArray->butterfly()->contiguousDouble().data(), sizeof(JSValue) * otherLength);
else
memcpy(butterfly()->contiguous().data() + startIndex, otherArray->butterfly()->contiguous().data(), sizeof(JSValue) * otherLength);
return true;
}
bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException)
{
Butterfly* butterfly = m_butterfly.get();
switch (indexingType()) {
case ArrayClass:
if (!newLength)
return true;
if (newLength >= MIN_SPARSE_ARRAY_INDEX) {
return setLengthWithArrayStorage(
exec, newLength, throwException,
ensureArrayStorage(exec->vm()));
}
createInitialUndecided(exec->vm(), newLength);
return true;
case ArrayWithUndecided:
case ArrayWithInt32:
case ArrayWithDouble:
case ArrayWithContiguous: {
if (newLength == butterfly->publicLength())
return true;
if (newLength >= MAX_ARRAY_INDEX || (newLength >= MIN_SPARSE_ARRAY_INDEX
&& !isDenseEnoughForVector(newLength, countElements()))) {
return setLengthWithArrayStorage(
exec, newLength, throwException,
ensureArrayStorage(exec->vm()));
}
if (newLength > butterfly->publicLength()) {
if (!ensureLength(exec->vm(), newLength)) {
throwOutOfMemoryError(exec);
return false;
}
return true;
}
unsigned lengthToClear = butterfly->publicLength() - newLength;
unsigned costToAllocateNewButterfly = 64; if (lengthToClear > newLength && lengthToClear > costToAllocateNewButterfly) {
reallocateAndShrinkButterfly(exec->vm(), newLength);
return true;
}
if (indexingType() == ArrayWithDouble) {
for (unsigned i = butterfly->publicLength(); i-- > newLength;)
butterfly->contiguousDouble()[i] = PNaN;
} else {
for (unsigned i = butterfly->publicLength(); i-- > newLength;)
butterfly->contiguous()[i].clear();
}
butterfly->setPublicLength(newLength);
return true;
}
case ArrayWithArrayStorage:
case ArrayWithSlowPutArrayStorage:
return setLengthWithArrayStorage(exec, newLength, throwException, arrayStorage());
default:
CRASH();
return false;
}
}
JSValue JSArray::pop(ExecState* exec)
{
Butterfly* butterfly = m_butterfly.get();
switch (indexingType()) {
case ArrayClass:
return jsUndefined();
case ArrayWithUndecided:
if (!butterfly->publicLength())
return jsUndefined();
break;
case ArrayWithInt32:
case ArrayWithContiguous: {
unsigned length = butterfly->publicLength();
if (!length--)
return jsUndefined();
RELEASE_ASSERT(length < butterfly->vectorLength());
JSValue value = butterfly->contiguous()[length].get();
if (value) {
butterfly->contiguous()[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()[length];
if (value == value) {
butterfly->contiguousDouble()[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(exec, StrictModeReadonlyPropertyWriteError);
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;
JSValue element = get(exec, index);
if (exec->hadException())
return jsUndefined();
if (!deletePropertyByIndex(this, exec, index)) {
throwTypeError(exec, ASCIILiteral("Unable to delete property."));
return jsUndefined();
}
setLength(exec, index, true);
return element;
}
void JSArray::push(ExecState* exec, JSValue value)
{
Butterfly* butterfly = m_butterfly.get();
switch (indexingType()) {
case ArrayClass: {
createInitialUndecided(exec->vm(), 0);
FALLTHROUGH;
}
case ArrayWithUndecided: {
convertUndecidedForValue(exec->vm(), value);
push(exec, value);
return;
}
case ArrayWithInt32: {
if (!value.isInt32()) {
convertInt32ForValue(exec->vm(), value);
push(exec, value);
return;
}
unsigned length = butterfly->publicLength();
ASSERT(length <= butterfly->vectorLength());
if (length < butterfly->vectorLength()) {
butterfly->contiguousInt32()[length].setWithoutWriteBarrier(value);
butterfly->setPublicLength(length + 1);
return;
}
if (length > MAX_ARRAY_INDEX) {
methodTable(exec->vm())->putByIndex(this, exec, length, value, true);
if (!exec->hadException())
exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length")));
return;
}
putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, length, value);
return;
}
case ArrayWithContiguous: {
unsigned length = butterfly->publicLength();
ASSERT(length <= butterfly->vectorLength());
if (length < butterfly->vectorLength()) {
butterfly->contiguous()[length].set(exec->vm(), this, value);
butterfly->setPublicLength(length + 1);
return;
}
if (length > MAX_ARRAY_INDEX) {
methodTable(exec->vm())->putByIndex(this, exec, length, value, true);
if (!exec->hadException())
exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length")));
return;
}
putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, length, value);
return;
}
case ArrayWithDouble: {
if (!value.isNumber()) {
convertDoubleToContiguous(exec->vm());
push(exec, value);
return;
}
double valueAsDouble = value.asNumber();
if (valueAsDouble != valueAsDouble) {
convertDoubleToContiguous(exec->vm());
push(exec, value);
return;
}
unsigned length = butterfly->publicLength();
ASSERT(length <= butterfly->vectorLength());
if (length < butterfly->vectorLength()) {
butterfly->contiguousDouble()[length] = valueAsDouble;
butterfly->setPublicLength(length + 1);
return;
}
if (length > MAX_ARRAY_INDEX) {
methodTable(exec->vm())->putByIndex(this, exec, length, value, true);
if (!exec->hadException())
exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length")));
return;
}
putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, length, value);
break;
}
case ArrayWithSlowPutArrayStorage: {
unsigned oldLength = length();
bool putResult = false;
if (attemptToInterceptPutByIndexOnHole(exec, oldLength, value, true, putResult)) {
if (!exec->hadException() && oldLength < 0xFFFFFFFFu)
setLength(exec, oldLength + 1, true);
return;
}
FALLTHROUGH;
}
case ArrayWithArrayStorage: {
ArrayStorage* storage = butterfly->arrayStorage();
unsigned length = storage->length();
if (length < storage->vectorLength()) {
storage->m_vector[length].set(exec->vm(), this, value);
storage->setLength(length + 1);
++storage->m_numValuesInVector;
return;
}
if (storage->length() > MAX_ARRAY_INDEX) {
methodTable(exec->vm())->putByIndex(this, exec, storage->length(), value, true);
if (!exec->hadException())
exec->vm().throwException(exec, createRangeError(exec, ASCIILiteral("Invalid array length")));
return;
}
putByIndexBeyondVectorLengthWithArrayStorage(exec, storage->length(), value, true, storage);
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
}
JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count)
{
auto arrayType = indexingType();
switch (arrayType) {
case ArrayWithDouble:
case ArrayWithInt32:
case ArrayWithContiguous: {
VM& vm = exec.vm();
if (count >= MIN_SPARSE_ARRAY_INDEX || structure(vm)->holesMustForwardToPrototype(vm))
return nullptr;
Structure* resultStructure = exec.lexicalGlobalObject()->arrayStructureForIndexingTypeDuringAllocation(arrayType);
JSArray* resultArray = JSArray::tryCreateUninitialized(vm, resultStructure, count);
if (!resultArray)
return nullptr;
auto& resultButterfly = *resultArray->butterfly();
if (arrayType == ArrayWithDouble)
memcpy(resultButterfly.contiguousDouble().data(), m_butterfly.get()->contiguousDouble().data() + startIndex, sizeof(JSValue) * count);
else
memcpy(resultButterfly.contiguous().data(), m_butterfly.get()->contiguous().data() + startIndex, sizeof(JSValue) * count);
resultButterfly.setPublicLength(count);
return resultArray;
}
default:
return nullptr;
}
}
bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned count, ArrayStorage* storage)
{
unsigned oldLength = storage->length();
RELEASE_ASSERT(count <= oldLength);
if ((storage->hasHoles() && this->structure(vm)->holesMustForwardToPrototype(vm))
|| 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;
if (startIndex + count > vectorLength)
count = vectorLength - startIndex;
unsigned usedVectorLength = min(vectorLength, oldLength);
unsigned numElementsBeforeShiftRegion = startIndex;
unsigned firstIndexAfterShiftRegion = startIndex + count;
unsigned numElementsAfterShiftRegion = usedVectorLength - firstIndexAfterShiftRegion;
ASSERT(numElementsBeforeShiftRegion + count + numElementsAfterShiftRegion == usedVectorLength);
if (numElementsBeforeShiftRegion < numElementsAfterShiftRegion) {
if (numElementsBeforeShiftRegion) {
RELEASE_ASSERT(count + startIndex <= vectorLength);
if (storage->hasHoles()) {
for (unsigned i = startIndex; i-- > 0;) {
unsigned destinationIndex = count + i;
JSValue source = storage->m_vector[i].get();
JSValue dest = storage->m_vector[destinationIndex].get();
if (!dest && destinationIndex >= firstIndexAfterShiftRegion)
storage->m_numValuesInVector++;
storage->m_vector[count + i].setWithoutWriteBarrier(source);
}
} else {
memmove(storage->m_vector + count,
storage->m_vector,
sizeof(JSValue) * startIndex);
}
}
Butterfly* butterfly = m_butterfly.get()->shift(structure(), count);
m_butterfly.setWithoutBarrier(butterfly);
storage = butterfly->arrayStorage();
storage->m_indexBias += count;
storage->setVectorLength(vectorLength - count);
} else {
if (storage->hasHoles()) {
for (unsigned i = 0; i < numElementsAfterShiftRegion; ++i) {
unsigned destinationIndex = startIndex + i;
JSValue source = storage->m_vector[firstIndexAfterShiftRegion + i].get();
JSValue dest = storage->m_vector[destinationIndex].get();
if (!dest && destinationIndex < firstIndexAfterShiftRegion)
storage->m_numValuesInVector++;
storage->m_vector[startIndex + i].setWithoutWriteBarrier(source);
}
} else {
memmove(storage->m_vector + startIndex,
storage->m_vector + firstIndexAfterShiftRegion,
sizeof(JSValue) * numElementsAfterShiftRegion);
}
unsigned startOfEmptyVectorTail = usedVectorLength - count;
for (unsigned i = startOfEmptyVectorTail; i < usedVectorLength; ++i)
storage->m_vector[i].clear();
}
return true;
}
bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned& startIndex, unsigned count)
{
VM& vm = exec->vm();
RELEASE_ASSERT(count > 0);
Butterfly* butterfly = m_butterfly.get();
switch (indexingType()) {
case ArrayClass:
return true;
case ArrayWithUndecided:
return false;
case ArrayWithInt32:
case ArrayWithContiguous: {
unsigned oldLength = butterfly->publicLength();
RELEASE_ASSERT(count <= oldLength);
if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX)
return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm));
unsigned end = oldLength - count;
if (this->structure(vm)->holesMustForwardToPrototype(vm)) {
for (unsigned i = startIndex; i < end; ++i) {
JSValue v = butterfly->contiguous()[i + count].get();
if (UNLIKELY(!v)) {
startIndex = i;
return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm));
}
butterfly->contiguous()[i].setWithoutWriteBarrier(v);
}
} else {
memmove(butterfly->contiguous().data() + startIndex,
butterfly->contiguous().data() + startIndex + count,
sizeof(JSValue) * (end - startIndex));
}
for (unsigned i = end; i < oldLength; ++i)
butterfly->contiguous()[i].clear();
butterfly->setPublicLength(oldLength - count);
return true;
}
case ArrayWithDouble: {
unsigned oldLength = butterfly->publicLength();
RELEASE_ASSERT(count <= oldLength);
if (oldLength - (startIndex + count) >= MIN_SPARSE_ARRAY_INDEX)
return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm));
unsigned end = oldLength - count;
if (this->structure(vm)->holesMustForwardToPrototype(vm)) {
for (unsigned i = startIndex; i < end; ++i) {
double v = butterfly->contiguousDouble()[i + count];
if (UNLIKELY(v != v)) {
startIndex = i;
return shiftCountWithArrayStorage(vm, startIndex, count, ensureArrayStorage(vm));
}
butterfly->contiguousDouble()[i] = v;
}
} else {
memmove(butterfly->contiguousDouble().data() + startIndex,
butterfly->contiguousDouble().data() + startIndex + count,
sizeof(JSValue) * (end - startIndex));
}
for (unsigned i = end; i < oldLength; ++i)
butterfly->contiguousDouble()[i] = PNaN;
butterfly->setPublicLength(oldLength - count);
return true;
}
case ArrayWithArrayStorage:
case ArrayWithSlowPutArrayStorage:
return shiftCountWithArrayStorage(vm, startIndex, count, arrayStorage());
default:
CRASH();
return false;
}
}
bool JSArray::unshiftCountWithArrayStorage(ExecState* exec, unsigned startIndex, unsigned count, ArrayStorage* storage)
{
unsigned length = storage->length();
RELEASE_ASSERT(startIndex <= length);
if (storage->hasHoles() || storage->inSparseMode() || shouldUseSlowPut(indexingType()))
return false;
bool moveFront = !startIndex || startIndex < length / 2;
unsigned vectorLength = storage->vectorLength();
if (moveFront && storage->m_indexBias >= count) {
Butterfly* newButterfly = storage->butterfly()->unshift(structure(), count);
storage = newButterfly->arrayStorage();
storage->m_indexBias -= count;
storage->setVectorLength(vectorLength + count);
setButterflyWithoutChangingStructure(exec->vm(), newButterfly);
} else if (!moveFront && vectorLength - length >= count)
storage = storage->butterfly()->arrayStorage();
else if (unshiftCountSlowCase(exec->vm(), moveFront, count))
storage = arrayStorage();
else {
throwOutOfMemoryError(exec);
return true;
}
WriteBarrier<Unknown>* vector = storage->m_vector;
if (startIndex) {
if (moveFront)
memmove(vector, vector + count, startIndex * sizeof(JSValue));
else if (length - startIndex)
memmove(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(ExecState* exec, unsigned startIndex, unsigned count)
{
Butterfly* butterfly = m_butterfly.get();
switch (indexingType()) {
case ArrayClass:
case ArrayWithUndecided:
return false;
case ArrayWithInt32:
case ArrayWithContiguous: {
unsigned oldLength = butterfly->publicLength();
if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX)
return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm()));
if (!ensureLength(exec->vm(), oldLength + count)) {
throwOutOfMemoryError(exec);
return false;
}
butterfly = m_butterfly.get();
for (unsigned i = oldLength; i-- > startIndex;) {
JSValue v = butterfly->contiguous()[i].get();
if (UNLIKELY(!v))
return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm()));
}
for (unsigned i = oldLength; i-- > startIndex;) {
JSValue v = butterfly->contiguous()[i].get();
ASSERT(v);
butterfly->contiguous()[i + count].setWithoutWriteBarrier(v);
}
return true;
}
case ArrayWithDouble: {
unsigned oldLength = butterfly->publicLength();
if (oldLength - startIndex >= MIN_SPARSE_ARRAY_INDEX)
return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm()));
if (!ensureLength(exec->vm(), oldLength + count)) {
throwOutOfMemoryError(exec);
return false;
}
butterfly = m_butterfly.get();
for (unsigned i = oldLength; i-- > startIndex;) {
double v = butterfly->contiguousDouble()[i];
if (UNLIKELY(v != v))
return unshiftCountWithArrayStorage(exec, startIndex, count, ensureArrayStorage(exec->vm()));
}
for (unsigned i = oldLength; i-- > startIndex;) {
double v = butterfly->contiguousDouble()[i];
ASSERT(v == v);
butterfly->contiguousDouble()[i + count] = v;
}
return true;
}
case ArrayWithArrayStorage:
case ArrayWithSlowPutArrayStorage:
return unshiftCountWithArrayStorage(exec, startIndex, count, arrayStorage());
default:
CRASH();
return false;
}
}
void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args)
{
unsigned i = 0;
unsigned vectorEnd;
WriteBarrier<Unknown>* vector;
Butterfly* butterfly = m_butterfly.get();
switch (indexingType()) {
case ArrayClass:
return;
case ArrayWithUndecided: {
vector = 0;
vectorEnd = 0;
break;
}
case ArrayWithInt32:
case ArrayWithContiguous: {
vectorEnd = butterfly->publicLength();
vector = butterfly->contiguous().data();
break;
}
case ArrayWithDouble: {
vector = 0;
vectorEnd = 0;
for (; i < butterfly->publicLength(); ++i) {
double v = butterfly->contiguousDouble()[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 = 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());
}
for (; i < length(); ++i)
args.append(get(exec, i));
}
void JSArray::copyToArguments(ExecState* exec, VirtualRegister firstElementDest, unsigned offset, unsigned length)
{
unsigned i = offset;
WriteBarrier<Unknown>* vector;
unsigned vectorEnd;
length += offset;
ASSERT(length == this->length());
Butterfly* butterfly = m_butterfly.get();
switch (indexingType()) {
case ArrayClass:
return;
case ArrayWithUndecided: {
vector = 0;
vectorEnd = 0;
break;
}
case ArrayWithInt32:
case ArrayWithContiguous: {
vector = butterfly->contiguous().data();
vectorEnd = butterfly->publicLength();
break;
}
case ArrayWithDouble: {
vector = 0;
vectorEnd = 0;
for (; i < butterfly->publicLength(); ++i) {
ASSERT(i < butterfly->vectorLength());
double v = butterfly->contiguousDouble()[i];
if (v != v)
break;
exec->r(firstElementDest + i - offset) = JSValue(JSValue::EncodeAsDouble, v);
}
break;
}
case ARRAY_WITH_ARRAY_STORAGE_INDEXING_TYPES: {
ArrayStorage* storage = butterfly->arrayStorage();
vector = storage->m_vector;
vectorEnd = 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;
exec->r(firstElementDest + i - offset) = v.get();
}
for (; i < length; ++i) {
exec->r(firstElementDest + i - offset) = get(exec, i);
if (UNLIKELY(exec->vm().exception()))
return;
}
}
}