#include "config.h"
#include "JSArray.h"
#include "ArrayPrototype.h"
#include "CopiedSpace.h"
#include "CopiedSpaceInlineMethods.h"
#include "CachedCall.h"
#include "Error.h"
#include "Executable.h"
#include "GetterSetter.h"
#include "PropertyNameArray.h"
#include <wtf/AVLTree.h>
#include <wtf/Assertions.h>
#include <wtf/OwnPtr.h>
#include <Operations.h>
using namespace std;
using namespace WTF;
namespace JSC {
ASSERT_CLASS_FITS_IN_CELL(JSArray);
ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSArray);
#define MAX_STORAGE_VECTOR_LENGTH static_cast<unsigned>((0xFFFFFFFFU - (sizeof(ArrayStorage) - sizeof(WriteBarrier<Unknown>))) / sizeof(WriteBarrier<Unknown>))
#define MIN_SPARSE_ARRAY_INDEX 10000U
#define MAX_STORAGE_VECTOR_INDEX (MAX_STORAGE_VECTOR_LENGTH - 1)
#define MAX_ARRAY_INDEX 0xFFFFFFFEU
#define BASE_VECTOR_LEN 4U
#define FIRST_VECTOR_GROW 4U
static const unsigned minDensityMultiplier = 8;
const ClassInfo JSArray::s_info = {"Array", &JSNonFinalObject::s_info, 0, 0, CREATE_METHOD_TABLE(JSArray)};
static unsigned lastArraySize = 0;
static inline bool isDenseEnoughForVector(unsigned length, unsigned numValues)
{
return length <= MIN_SPARSE_ARRAY_INDEX || length / minDensityMultiplier <= numValues;
}
static bool reject(ExecState* exec, bool throwException, const char* message)
{
if (throwException)
throwTypeError(exec, message);
return false;
}
#if !CHECK_ARRAY_CONSISTENCY
inline void JSArray::checkConsistency(ConsistencyCheckType)
{
}
#endif
void JSArray::finishCreation(JSGlobalData& globalData, unsigned initialLength)
{
Base::finishCreation(globalData);
ASSERT(inherits(&s_info));
unsigned initialVectorLength = BASE_VECTOR_LEN;
unsigned initialStorageSize = storageSize(initialVectorLength);
void* newStorage = 0;
if (!globalData.heap.tryAllocateStorage(initialStorageSize, &newStorage))
CRASH();
m_storage = static_cast<ArrayStorage*>(newStorage);
m_storage->m_allocBase = m_storage;
m_storage->m_length = initialLength;
m_vectorLength = initialVectorLength;
m_storage->m_numValuesInVector = 0;
#if CHECK_ARRAY_CONSISTENCY
m_storage->m_inCompactInitialization = false;
#endif
checkConsistency();
}
JSArray* JSArray::tryFinishCreationUninitialized(JSGlobalData& globalData, unsigned initialLength)
{
Base::finishCreation(globalData);
ASSERT(inherits(&s_info));
if (initialLength > MAX_STORAGE_VECTOR_LENGTH)
return 0;
unsigned initialVectorLength = max(initialLength, BASE_VECTOR_LEN);
unsigned initialStorageSize = storageSize(initialVectorLength);
void* newStorage = 0;
if (!globalData.heap.tryAllocateStorage(initialStorageSize, &newStorage))
CRASH();
m_storage = static_cast<ArrayStorage*>(newStorage);
m_storage->m_allocBase = m_storage;
m_storage->m_length = initialLength;
m_vectorLength = initialVectorLength;
m_storage->m_numValuesInVector = initialLength;
#if CHECK_ARRAY_CONSISTENCY
m_storage->m_initializationIndex = 0;
m_storage->m_inCompactInitialization = true;
#endif
return this;
}
void JSArray::finalize(JSCell* cell)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
thisObject->checkConsistency(DestructorConsistencyCheck);
thisObject->deallocateSparseMap();
}
inline SparseArrayValueMap::AddResult SparseArrayValueMap::add(JSArray* array, unsigned i)
{
SparseArrayEntry entry;
entry.setWithoutWriteBarrier(jsUndefined());
AddResult result = m_map.add(i, entry);
size_t capacity = m_map.capacity();
if (capacity != m_reportedCapacity) {
Heap::heap(array)->reportExtraMemoryCost((capacity - m_reportedCapacity) * (sizeof(unsigned) + sizeof(WriteBarrier<Unknown>)));
m_reportedCapacity = capacity;
}
return result;
}
inline void SparseArrayValueMap::put(ExecState* exec, JSArray* array, unsigned i, JSValue value, bool shouldThrow)
{
AddResult result = add(array, i);
SparseArrayEntry& entry = result.iterator->second;
if (result.isNewEntry && !array->isExtensible()) {
remove(result.iterator);
if (shouldThrow)
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return;
}
if (!(entry.attributes & Accessor)) {
if (entry.attributes & ReadOnly) {
if (shouldThrow)
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return;
}
entry.set(exec->globalData(), array, value);
return;
}
JSValue accessor = entry.Base::get();
ASSERT(accessor.isGetterSetter());
JSObject* setter = asGetterSetter(accessor)->setter();
if (!setter) {
if (shouldThrow)
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return;
}
CallData callData;
CallType callType = setter->methodTable()->getCallData(setter, callData);
MarkedArgumentBuffer args;
args.append(value);
call(exec, setter, callType, callData, array, args);
}
inline bool SparseArrayValueMap::putDirect(ExecState* exec, JSArray* array, unsigned i, JSValue value, bool shouldThrow)
{
AddResult result = add(array, i);
SparseArrayEntry& entry = result.iterator->second;
if (result.isNewEntry && !array->isExtensible()) {
remove(result.iterator);
return reject(exec, shouldThrow, "Attempting to define property on object that is not extensible.");
}
entry.attributes = 0;
entry.set(exec->globalData(), array, value);
return true;
}
inline void SparseArrayEntry::get(PropertySlot& slot) const
{
JSValue value = Base::get();
ASSERT(value);
if (LIKELY(!value.isGetterSetter())) {
slot.setValue(value);
return;
}
JSObject* getter = asGetterSetter(value)->getter();
if (!getter) {
slot.setUndefined();
return;
}
slot.setGetterSlot(getter);
}
inline void SparseArrayEntry::get(PropertyDescriptor& descriptor) const
{
descriptor.setDescriptor(Base::get(), attributes);
}
inline JSValue SparseArrayEntry::get(ExecState* exec, JSArray* array) const
{
JSValue result = Base::get();
ASSERT(result);
if (LIKELY(!result.isGetterSetter()))
return result;
JSObject* getter = asGetterSetter(result)->getter();
if (!getter)
return jsUndefined();
CallData callData;
CallType callType = getter->methodTable()->getCallData(getter, callData);
return call(exec, getter, callType, callData, array, exec->emptyList());
}
inline JSValue SparseArrayEntry::getNonSparseMode() const
{
ASSERT(!attributes);
return Base::get();
}
inline void SparseArrayValueMap::visitChildren(SlotVisitor& visitor)
{
iterator end = m_map.end();
for (iterator it = m_map.begin(); it != end; ++it)
visitor.append(&it->second);
}
void JSArray::allocateSparseMap(JSGlobalData& globalData)
{
m_sparseValueMap = new SparseArrayValueMap;
globalData.heap.addFinalizer(this, finalize);
}
void JSArray::deallocateSparseMap()
{
delete m_sparseValueMap;
m_sparseValueMap = 0;
}
void JSArray::enterDictionaryMode(JSGlobalData& globalData)
{
ArrayStorage* storage = m_storage;
SparseArrayValueMap* map = m_sparseValueMap;
if (!map) {
allocateSparseMap(globalData);
map = m_sparseValueMap;
}
if (map->sparseMode())
return;
map->setSparseMode();
unsigned usedVectorLength = min(storage->m_length, m_vectorLength);
for (unsigned i = 0; i < usedVectorLength; ++i) {
JSValue value = storage->m_vector[i].get();
if (value)
map->add(this, i).iterator->second.set(globalData, this, value);
}
void* newRawStorage = 0;
if (!globalData.heap.tryAllocateStorage(storageSize(0), &newRawStorage))
CRASH();
ArrayStorage* newStorage = static_cast<ArrayStorage*>(newRawStorage);
memcpy(newStorage, m_storage, storageSize(0));
newStorage->m_allocBase = newStorage;
m_storage = newStorage;
m_indexBias = 0;
m_vectorLength = 0;
}
void JSArray::putDescriptor(ExecState* exec, SparseArrayEntry* entryInMap, PropertyDescriptor& descriptor, PropertyDescriptor& oldDescriptor)
{
if (descriptor.isDataDescriptor()) {
if (descriptor.value())
entryInMap->set(exec->globalData(), this, descriptor.value());
else if (oldDescriptor.isAccessorDescriptor())
entryInMap->set(exec->globalData(), this, jsUndefined());
entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor) & ~Accessor;
return;
}
if (descriptor.isAccessorDescriptor()) {
JSObject* getter = 0;
if (descriptor.getterPresent())
getter = descriptor.getterObject();
else if (oldDescriptor.isAccessorDescriptor())
getter = oldDescriptor.getterObject();
JSObject* setter = 0;
if (descriptor.setterPresent())
setter = descriptor.setterObject();
else if (oldDescriptor.isAccessorDescriptor())
setter = oldDescriptor.setterObject();
GetterSetter* accessor = GetterSetter::create(exec);
if (getter)
accessor->setGetter(exec->globalData(), getter);
if (setter)
accessor->setSetter(exec->globalData(), setter);
entryInMap->set(exec->globalData(), this, accessor);
entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor) & ~ReadOnly;
return;
}
ASSERT(descriptor.isGenericDescriptor());
entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor);
}
bool JSArray::defineOwnNumericProperty(ExecState* exec, unsigned index, PropertyDescriptor& descriptor, bool throwException)
{
ASSERT(index != 0xFFFFFFFF);
if (!inSparseMode()) {
if (!descriptor.attributes()) {
ASSERT(!descriptor.isAccessorDescriptor());
return putDirectIndex(exec, index, descriptor.value(), throwException);
}
enterDictionaryMode(exec->globalData());
}
SparseArrayValueMap* map = m_sparseValueMap;
ASSERT(map);
SparseArrayValueMap::AddResult result = map->add(this, index);
SparseArrayEntry* entryInMap = &result.iterator->second;
if (result.isNewEntry) {
if (!isExtensible()) {
map->remove(result.iterator);
return reject(exec, throwException, "Attempting to define property on object that is not extensible.");
}
PropertyDescriptor defaults;
entryInMap->setWithoutWriteBarrier(jsUndefined());
entryInMap->attributes = DontDelete | DontEnum | ReadOnly;
entryInMap->get(defaults);
putDescriptor(exec, entryInMap, descriptor, defaults);
if (index >= m_storage->m_length)
m_storage->m_length = index + 1;
return true;
}
PropertyDescriptor current;
entryInMap->get(current);
if (descriptor.isEmpty() || descriptor.equalTo(exec, current))
return true;
if (!current.configurable()) {
if (descriptor.configurablePresent() && descriptor.configurable())
return reject(exec, throwException, "Attempting to change configurable attribute of unconfigurable property.");
if (descriptor.enumerablePresent() && current.enumerable() != descriptor.enumerable())
return reject(exec, throwException, "Attempting to change enumerable attribute of unconfigurable property.");
}
if (!descriptor.isGenericDescriptor()) {
if (current.isDataDescriptor() != descriptor.isDataDescriptor()) {
if (!current.configurable())
return reject(exec, throwException, "Attempting to change access mechanism for an unconfigurable property.");
} else if (current.isDataDescriptor() && descriptor.isDataDescriptor()) {
if (!current.configurable() && !current.writable()) {
if (descriptor.writable())
return reject(exec, throwException, "Attempting to change writable attribute of unconfigurable property.");
if (descriptor.value() && !sameValue(exec, descriptor.value(), current.value()))
return reject(exec, throwException, "Attempting to change value of a readonly property.");
}
} else {
ASSERT(current.isAccessorDescriptor() && current.getterPresent() && current.setterPresent());
if (!current.configurable()) {
if (descriptor.setterPresent() && descriptor.setter() != current.setter())
return reject(exec, throwException, "Attempting to change the setter of an unconfigurable property.");
if (descriptor.getterPresent() && descriptor.getter() != current.getter())
return reject(exec, throwException, "Attempting to change the getter of an unconfigurable property.");
}
}
}
putDescriptor(exec, entryInMap, descriptor, current);
return true;
}
void JSArray::setLengthWritable(ExecState* exec, bool writable)
{
ASSERT(isLengthWritable() || !writable);
if (!isLengthWritable() || writable)
return;
enterDictionaryMode(exec->globalData());
SparseArrayValueMap* map = m_sparseValueMap;
ASSERT(map);
map->setLengthIsReadOnly();
}
bool JSArray::defineOwnProperty(JSObject* object, ExecState* exec, const Identifier& propertyName, 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, "Attempting to change access mechanism for an unconfigurable property.");
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)) {
throwError(exec, createRangeError(exec, "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;
}
bool isArrayIndex;
unsigned index = propertyName.toArrayIndex(isArrayIndex);
if (isArrayIndex) {
if (index >= array->length() && !array->isLengthWritable())
return reject(exec, throwException, "Attempting to define numeric property on array with non-writable length property.");
return array->defineOwnNumericProperty(exec, index, descriptor, throwException);
}
return JSObject::defineOwnProperty(object, exec, propertyName, descriptor, throwException);
}
bool JSArray::getOwnPropertySlotByIndex(JSCell* cell, ExecState* exec, unsigned i, PropertySlot& slot)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
ArrayStorage* storage = thisObject->m_storage;
if (i >= storage->m_length) {
if (i > MAX_ARRAY_INDEX)
return thisObject->methodTable()->getOwnPropertySlot(thisObject, exec, Identifier::from(exec, i), slot);
return false;
}
if (i < thisObject->m_vectorLength) {
JSValue value = storage->m_vector[i].get();
if (value) {
slot.setValue(value);
return true;
}
} else if (SparseArrayValueMap* map = thisObject->m_sparseValueMap) {
SparseArrayValueMap::iterator it = map->find(i);
if (it != map->notFound()) {
it->second.get(slot);
return true;
}
}
return JSObject::getOwnPropertySlot(thisObject, exec, Identifier::from(exec, i), slot);
}
bool JSArray::getOwnPropertySlot(JSCell* cell, ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
if (propertyName == exec->propertyNames().length) {
slot.setValue(jsNumber(thisObject->length()));
return true;
}
bool isArrayIndex;
unsigned i = propertyName.toArrayIndex(isArrayIndex);
if (isArrayIndex)
return JSArray::getOwnPropertySlotByIndex(thisObject, exec, i, slot);
return JSObject::getOwnPropertySlot(thisObject, exec, propertyName, slot);
}
bool JSArray::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
{
JSArray* thisObject = jsCast<JSArray*>(object);
if (propertyName == exec->propertyNames().length) {
descriptor.setDescriptor(jsNumber(thisObject->length()), thisObject->isLengthWritable() ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly);
return true;
}
ArrayStorage* storage = thisObject->m_storage;
bool isArrayIndex;
unsigned i = propertyName.toArrayIndex(isArrayIndex);
if (isArrayIndex) {
if (i >= storage->m_length)
return false;
if (i < thisObject->m_vectorLength) {
WriteBarrier<Unknown>& value = storage->m_vector[i];
if (value) {
descriptor.setDescriptor(value.get(), 0);
return true;
}
} else if (SparseArrayValueMap* map = thisObject->m_sparseValueMap) {
SparseArrayValueMap::iterator it = map->find(i);
if (it != map->notFound()) {
it->second.get(descriptor);
return true;
}
}
}
return JSObject::getOwnPropertyDescriptor(thisObject, exec, propertyName, descriptor);
}
void JSArray::put(JSCell* cell, ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
bool isArrayIndex;
unsigned i = propertyName.toArrayIndex(isArrayIndex);
if (isArrayIndex) {
putByIndex(thisObject, exec, i, value, slot.isStrictMode());
return;
}
if (propertyName == exec->propertyNames().length) {
unsigned newLength = value.toUInt32(exec);
if (value.toNumber(exec) != static_cast<double>(newLength)) {
throwError(exec, createRangeError(exec, "Invalid array length"));
return;
}
thisObject->setLength(exec, newLength, slot.isStrictMode());
return;
}
JSObject::put(thisObject, exec, propertyName, value, slot);
}
void JSArray::putByIndex(JSCell* cell, ExecState* exec, unsigned i, JSValue value, bool shouldThrow)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
thisObject->checkConsistency();
ArrayStorage* storage = thisObject->m_storage;
if (i < thisObject->m_vectorLength) {
WriteBarrier<Unknown>& valueSlot = storage->m_vector[i];
unsigned length = storage->m_length;
if (i >= length) {
length = i + 1;
storage->m_length = length;
++storage->m_numValuesInVector;
} else if (!valueSlot)
++storage->m_numValuesInVector;
valueSlot.set(exec->globalData(), thisObject, value);
thisObject->checkConsistency();
return;
}
if (UNLIKELY(i > MAX_ARRAY_INDEX)) {
PutPropertySlot slot(shouldThrow);
thisObject->methodTable()->put(thisObject, exec, Identifier::from(exec, i), value, slot);
return;
}
thisObject->putByIndexBeyondVectorLength(exec, i, value, shouldThrow);
thisObject->checkConsistency();
}
void JSArray::putByIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue value, bool shouldThrow)
{
JSGlobalData& globalData = exec->globalData();
ASSERT(i >= m_vectorLength);
ASSERT(i <= MAX_ARRAY_INDEX);
ArrayStorage* storage = m_storage;
SparseArrayValueMap* map = m_sparseValueMap;
if (LIKELY(!map)) {
ASSERT(isExtensible());
if (i >= storage->m_length)
storage->m_length = i + 1;
if (LIKELY((isDenseEnoughForVector(i, storage->m_numValuesInVector)) && increaseVectorLength(globalData, i + 1))) {
storage = m_storage;
storage->m_vector[i].set(globalData, this, value);
++storage->m_numValuesInVector;
return;
}
allocateSparseMap(exec->globalData());
map = m_sparseValueMap;
map->put(exec, this, i, value, shouldThrow);
return;
}
unsigned length = storage->m_length;
if (i >= length) {
if (map->lengthIsReadOnly() || !isExtensible()) {
if (shouldThrow)
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return;
}
length = i + 1;
storage->m_length = length;
}
unsigned numValuesInArray = storage->m_numValuesInVector + map->size();
if (map->sparseMode() || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(exec->globalData(), length)) {
map->put(exec, this, i, value, shouldThrow);
return;
}
storage = m_storage;
storage->m_numValuesInVector = numValuesInArray;
WriteBarrier<Unknown>* vector = storage->m_vector;
SparseArrayValueMap::const_iterator end = map->end();
for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it)
vector[it->first].set(globalData, this, it->second.getNonSparseMode());
deallocateSparseMap();
WriteBarrier<Unknown>& valueSlot = vector[i];
if (!valueSlot)
++storage->m_numValuesInVector;
valueSlot.set(globalData, this, value);
}
bool JSArray::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue value, bool shouldThrow)
{
JSGlobalData& globalData = exec->globalData();
ASSERT(i >= m_vectorLength);
ASSERT(i <= MAX_ARRAY_INDEX);
ArrayStorage* storage = m_storage;
SparseArrayValueMap* map = m_sparseValueMap;
if (LIKELY(!map)) {
ASSERT(isExtensible());
if (i >= storage->m_length)
storage->m_length = i + 1;
if (LIKELY((isDenseEnoughForVector(i, storage->m_numValuesInVector)) && increaseVectorLength(globalData, i + 1))) {
storage = m_storage;
storage->m_vector[i].set(globalData, this, value);
++storage->m_numValuesInVector;
return true;
}
allocateSparseMap(exec->globalData());
map = m_sparseValueMap;
return map->putDirect(exec, this, i, value, shouldThrow);
}
unsigned length = storage->m_length;
if (i >= length) {
if (map->lengthIsReadOnly())
return reject(exec, shouldThrow, StrictModeReadonlyPropertyWriteError);
if (!isExtensible())
return reject(exec, shouldThrow, "Attempting to define property on object that is not extensible.");
length = i + 1;
storage->m_length = length;
}
unsigned numValuesInArray = storage->m_numValuesInVector + map->size();
if (map->sparseMode() || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(exec->globalData(), length))
return map->putDirect(exec, this, i, value, shouldThrow);
storage = m_storage;
storage->m_numValuesInVector = numValuesInArray;
WriteBarrier<Unknown>* vector = storage->m_vector;
SparseArrayValueMap::const_iterator end = map->end();
for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it)
vector[it->first].set(globalData, this, it->second.getNonSparseMode());
deallocateSparseMap();
WriteBarrier<Unknown>& valueSlot = vector[i];
if (!valueSlot)
++storage->m_numValuesInVector;
valueSlot.set(globalData, this, value);
return true;
}
bool JSArray::deleteProperty(JSCell* cell, ExecState* exec, const Identifier& propertyName)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
bool isArrayIndex;
unsigned i = propertyName.toArrayIndex(isArrayIndex);
if (isArrayIndex)
return thisObject->methodTable()->deletePropertyByIndex(thisObject, exec, i);
if (propertyName == exec->propertyNames().length)
return false;
return JSObject::deleteProperty(thisObject, exec, propertyName);
}
bool JSArray::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned i)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
thisObject->checkConsistency();
if (i > MAX_ARRAY_INDEX)
return thisObject->methodTable()->deleteProperty(thisObject, exec, Identifier::from(exec, i));
ArrayStorage* storage = thisObject->m_storage;
if (i < thisObject->m_vectorLength) {
WriteBarrier<Unknown>& valueSlot = storage->m_vector[i];
if (valueSlot) {
valueSlot.clear();
--storage->m_numValuesInVector;
}
} else if (SparseArrayValueMap* map = thisObject->m_sparseValueMap) {
SparseArrayValueMap::iterator it = map->find(i);
if (it != map->notFound()) {
if (it->second.attributes & DontDelete)
return false;
map->remove(it);
}
}
thisObject->checkConsistency();
return true;
}
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::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
{
JSArray* thisObject = jsCast<JSArray*>(object);
ArrayStorage* storage = thisObject->m_storage;
unsigned usedVectorLength = min(storage->m_length, thisObject->m_vectorLength);
for (unsigned i = 0; i < usedVectorLength; ++i) {
if (storage->m_vector[i])
propertyNames.add(Identifier::from(exec, i));
}
if (SparseArrayValueMap* map = thisObject->m_sparseValueMap) {
Vector<unsigned> keys;
keys.reserveCapacity(map->size());
SparseArrayValueMap::const_iterator end = map->end();
for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it) {
if (mode == IncludeDontEnumProperties || !(it->second.attributes & DontEnum))
keys.append(static_cast<unsigned>(it->first));
}
qsort(keys.begin(), keys.size(), sizeof(unsigned), compareKeysForQSort);
for (unsigned i = 0; i < keys.size(); ++i)
propertyNames.add(Identifier::from(exec, keys[i]));
}
if (mode == IncludeDontEnumProperties)
propertyNames.add(exec->propertyNames().length);
JSObject::getOwnPropertyNames(thisObject, exec, propertyNames, mode);
}
ALWAYS_INLINE unsigned JSArray::getNewVectorLength(unsigned desiredLength)
{
ASSERT(desiredLength <= MAX_STORAGE_VECTOR_LENGTH);
unsigned increasedLength;
unsigned maxInitLength = min(m_storage->m_length, 100000U);
if (desiredLength < maxInitLength)
increasedLength = maxInitLength;
else if (!m_vectorLength)
increasedLength = max(desiredLength, lastArraySize);
else {
increasedLength = desiredLength + (desiredLength >> 1) + (desiredLength & 1);
}
ASSERT(increasedLength >= desiredLength);
lastArraySize = min(increasedLength, FIRST_VECTOR_GROW);
return min(increasedLength, MAX_STORAGE_VECTOR_LENGTH);
}
bool JSArray::increaseVectorLength(JSGlobalData& globalData, unsigned newLength)
{
if (newLength > MAX_STORAGE_VECTOR_LENGTH)
return false;
ArrayStorage* storage = m_storage;
unsigned vectorLength = m_vectorLength;
ASSERT(newLength > vectorLength);
unsigned newVectorLength = getNewVectorLength(newLength);
if (LIKELY(!m_indexBias)) {
void* newStorage = storage->m_allocBase;
if (!globalData.heap.tryReallocateStorage(&newStorage, storageSize(vectorLength), storageSize(newVectorLength)))
return false;
storage = m_storage = reinterpret_cast_ptr<ArrayStorage*>(static_cast<char*>(newStorage));
m_storage->m_allocBase = newStorage;
ASSERT(m_storage->m_allocBase);
m_vectorLength = newVectorLength;
return true;
}
unsigned newIndexBias = min(m_indexBias >> 1, MAX_STORAGE_VECTOR_LENGTH - newVectorLength);
unsigned newStorageCapacity = newVectorLength + newIndexBias;
void* newAllocBase = 0;
if (!globalData.heap.tryAllocateStorage(storageSize(newStorageCapacity), &newAllocBase))
return false;
ASSERT(m_vectorLength <= MAX_STORAGE_VECTOR_LENGTH && (MAX_STORAGE_VECTOR_LENGTH - m_vectorLength) >= m_indexBias);
m_vectorLength = newVectorLength;
m_indexBias = newIndexBias;
m_storage = reinterpret_cast_ptr<ArrayStorage*>(reinterpret_cast<WriteBarrier<Unknown>*>(newAllocBase) + m_indexBias);
memmove(m_storage, storage, storageSize(vectorLength));
m_storage->m_allocBase = newAllocBase;
return true;
}
bool JSArray::unshiftCountSlowCase(JSGlobalData& globalData, unsigned count)
{
ASSERT(count > m_indexBias);
ArrayStorage* storage = m_storage;
unsigned length = storage->m_length;
unsigned usedVectorLength = min(m_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(m_vectorLength <= MAX_STORAGE_VECTOR_LENGTH && (MAX_STORAGE_VECTOR_LENGTH - m_vectorLength) >= m_indexBias);
unsigned currentCapacity = m_vectorLength + m_indexBias;
unsigned desiredCapacity = min(MAX_STORAGE_VECTOR_LENGTH, max(BASE_VECTOR_LEN, requiredVectorLength) << 1);
void* newAllocBase = 0;
unsigned newStorageCapacity;
if (currentCapacity > desiredCapacity && isDenseEnoughForVector(currentCapacity, requiredVectorLength)) {
newAllocBase = storage->m_allocBase;
newStorageCapacity = currentCapacity;
} else {
if (!globalData.heap.tryAllocateStorage(storageSize(desiredCapacity), &newAllocBase))
return false;
newStorageCapacity = desiredCapacity;
}
unsigned postCapacity = 0;
if (length < m_vectorLength) {
postCapacity = min((m_vectorLength - length) >> 1, newStorageCapacity - requiredVectorLength);
ASSERT(newAllocBase != storage->m_allocBase || postCapacity < m_vectorLength - length);
}
m_vectorLength = requiredVectorLength + postCapacity;
m_indexBias = newStorageCapacity - m_vectorLength;
m_storage = reinterpret_cast_ptr<ArrayStorage*>(reinterpret_cast<WriteBarrier<Unknown>*>(newAllocBase) + m_indexBias);
memmove(m_storage->m_vector + count, storage->m_vector, sizeof(WriteBarrier<Unknown>) * usedVectorLength);
memmove(m_storage, storage, storageSize(0));
if (newAllocBase != m_storage->m_allocBase) {
m_storage->m_allocBase = newAllocBase;
}
return true;
}
bool JSArray::setLength(ExecState* exec, unsigned newLength, bool throwException)
{
checkConsistency();
ArrayStorage* storage = m_storage;
unsigned length = storage->m_length;
ASSERT(isLengthWritable() || m_sparseValueMap);
if (SparseArrayValueMap* map = m_sparseValueMap) {
if (map->lengthIsReadOnly())
return reject(exec, throwException, StrictModeReadonlyPropertyWriteError);
if (newLength < length) {
Vector<unsigned> keys;
keys.reserveCapacity(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->first);
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->second.attributes & DontDelete) {
storage->m_length = 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())
deallocateSparseMap();
}
}
}
if (newLength < length) {
unsigned usedVectorLength = min(length, m_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->m_length = newLength;
checkConsistency();
return true;
}
JSValue JSArray::pop(ExecState* exec)
{
checkConsistency();
ArrayStorage* storage = m_storage;
unsigned length = storage->m_length;
if (!length) {
if (!isLengthWritable())
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return jsUndefined();
}
unsigned index = length - 1;
if (index < m_vectorLength) {
WriteBarrier<Unknown>& valueSlot = storage->m_vector[index];
if (valueSlot) {
--storage->m_numValuesInVector;
JSValue element = valueSlot.get();
valueSlot.clear();
ASSERT(isLengthWritable());
storage->m_length = index;
checkConsistency();
return element;
}
}
JSValue element = get(exec, index);
if (exec->hadException())
return jsUndefined();
deletePropertyByIndex(this, exec, index);
setLength(exec, index, true);
checkConsistency();
return element;
}
void JSArray::push(ExecState* exec, JSValue value)
{
checkConsistency();
ArrayStorage* storage = m_storage;
unsigned length = storage->m_length;
if (length < m_vectorLength) {
storage->m_vector[length].set(exec->globalData(), this, value);
storage->m_length = length + 1;
++storage->m_numValuesInVector;
checkConsistency();
return;
}
if (UNLIKELY(storage->m_length == 0xFFFFFFFFu)) {
methodTable()->putByIndex(this, exec, storage->m_length, value, true);
if (!exec->hadException())
throwError(exec, createRangeError(exec, "Invalid array length"));
return;
}
putByIndexBeyondVectorLength(exec, storage->m_length, value, true);
checkConsistency();
}
bool JSArray::shiftCount(ExecState*, unsigned count)
{
ASSERT(count > 0);
ArrayStorage* storage = m_storage;
unsigned oldLength = storage->m_length;
ASSERT(count <= oldLength);
if (oldLength != storage->m_numValuesInVector || inSparseMode())
return false;
if (!oldLength)
return true;
storage->m_numValuesInVector -= count;
storage->m_length -= count;
if (m_vectorLength) {
count = min(m_vectorLength, (unsigned)count);
m_vectorLength -= count;
if (m_vectorLength) {
char* newBaseStorage = reinterpret_cast<char*>(storage) + count * sizeof(WriteBarrier<Unknown>);
memmove(newBaseStorage, storage, storageSize(0));
m_storage = reinterpret_cast_ptr<ArrayStorage*>(newBaseStorage);
m_indexBias += count;
}
}
return true;
}
bool JSArray::unshiftCount(ExecState* exec, unsigned count)
{
ArrayStorage* storage = m_storage;
unsigned length = storage->m_length;
if (length != storage->m_numValuesInVector || inSparseMode())
return false;
if (m_indexBias >= count) {
m_indexBias -= count;
char* newBaseStorage = reinterpret_cast<char*>(storage) - count * sizeof(WriteBarrier<Unknown>);
memmove(newBaseStorage, storage, storageSize(0));
m_storage = reinterpret_cast_ptr<ArrayStorage*>(newBaseStorage);
m_vectorLength += count;
} else if (!unshiftCountSlowCase(exec->globalData(), count)) {
throwOutOfMemoryError(exec);
return true;
}
WriteBarrier<Unknown>* vector = m_storage->m_vector;
for (unsigned i = 0; i < count; i++)
vector[i].clear();
return true;
}
void JSArray::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
JSArray* thisObject = jsCast<JSArray*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info);
COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag);
ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren());
JSNonFinalObject::visitChildren(thisObject, visitor);
if (thisObject->m_storage) {
ArrayStorage* storage = thisObject->m_storage;
void* baseStorage = storage->m_allocBase;
visitor.copyAndAppend(reinterpret_cast<void**>(&baseStorage), storageSize(thisObject->m_vectorLength + thisObject->m_indexBias), storage->m_vector->slot(), thisObject->m_vectorLength);
if (baseStorage != thisObject->m_storage->m_allocBase) {
thisObject->m_storage = reinterpret_cast_ptr<ArrayStorage*>(static_cast<char*>(baseStorage) + sizeof(JSValue) * thisObject->m_indexBias);
thisObject->m_storage->m_allocBase = baseStorage;
ASSERT(thisObject->m_storage->m_allocBase);
}
}
if (SparseArrayValueMap* map = thisObject->m_sparseValueMap)
map->visitChildren(visitor);
}
static int compareNumbersForQSort(const void* a, const void* b)
{
double da = static_cast<const JSValue*>(a)->asNumber();
double db = static_cast<const JSValue*>(b)->asNumber();
return (da > db) - (da < db);
}
static int compareByStringPairForQSort(const void* a, const void* b)
{
const ValueStringPair* va = static_cast<const ValueStringPair*>(a);
const ValueStringPair* vb = static_cast<const ValueStringPair*>(b);
return codePointCompare(va->second, vb->second);
}
void JSArray::sortNumeric(ExecState* exec, JSValue compareFunction, CallType callType, const CallData& callData)
{
ASSERT(!inSparseMode());
ArrayStorage* storage = m_storage;
unsigned lengthNotIncludingUndefined = compactForSorting();
ASSERT(!m_sparseValueMap);
if (!lengthNotIncludingUndefined)
return;
bool allValuesAreNumbers = true;
size_t size = storage->m_numValuesInVector;
for (size_t i = 0; i < size; ++i) {
if (!storage->m_vector[i].isNumber()) {
allValuesAreNumbers = false;
break;
}
}
if (!allValuesAreNumbers)
return sort(exec, compareFunction, callType, callData);
qsort(storage->m_vector, size, sizeof(WriteBarrier<Unknown>), compareNumbersForQSort);
checkConsistency(SortConsistencyCheck);
}
void JSArray::sort(ExecState* exec)
{
ASSERT(!inSparseMode());
unsigned lengthNotIncludingUndefined = compactForSorting();
ASSERT(!m_sparseValueMap);
if (!lengthNotIncludingUndefined)
return;
Vector<ValueStringPair> values(lengthNotIncludingUndefined);
if (!values.begin()) {
throwOutOfMemoryError(exec);
return;
}
Heap::heap(this)->pushTempSortVector(&values);
bool isSortingPrimitiveValues = true;
for (size_t i = 0; i < lengthNotIncludingUndefined; i++) {
JSValue value = m_storage->m_vector[i].get();
ASSERT(!value.isUndefined());
values[i].first = value;
isSortingPrimitiveValues = isSortingPrimitiveValues && value.isPrimitive();
}
for (size_t i = 0; i < lengthNotIncludingUndefined; i++)
values[i].second = values[i].first.toUStringInline(exec);
if (exec->hadException()) {
Heap::heap(this)->popTempSortVector(&values);
return;
}
#if HAVE(MERGESORT)
if (isSortingPrimitiveValues)
qsort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
else
mergesort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
#else
qsort(values.begin(), values.size(), sizeof(ValueStringPair), compareByStringPairForQSort);
#endif
if (m_vectorLength < lengthNotIncludingUndefined)
increaseVectorLength(exec->globalData(), lengthNotIncludingUndefined);
if (m_storage->m_length < lengthNotIncludingUndefined)
m_storage->m_length = lengthNotIncludingUndefined;
JSGlobalData& globalData = exec->globalData();
for (size_t i = 0; i < lengthNotIncludingUndefined; i++)
m_storage->m_vector[i].set(globalData, this, values[i].first);
Heap::heap(this)->popTempSortVector(&values);
checkConsistency(SortConsistencyCheck);
}
struct AVLTreeNodeForArrayCompare {
JSValue value;
int32_t gt;
int32_t lt;
};
struct AVLTreeAbstractorForArrayCompare {
typedef int32_t handle; typedef JSValue key;
typedef int32_t size;
Vector<AVLTreeNodeForArrayCompare> m_nodes;
ExecState* m_exec;
JSValue m_compareFunction;
CallType m_compareCallType;
const CallData* m_compareCallData;
OwnPtr<CachedCall> m_cachedCall;
handle get_less(handle h) { return m_nodes[h].lt & 0x7FFFFFFF; }
void set_less(handle h, handle lh) { m_nodes[h].lt &= 0x80000000; m_nodes[h].lt |= lh; }
handle get_greater(handle h) { return m_nodes[h].gt & 0x7FFFFFFF; }
void set_greater(handle h, handle gh) { m_nodes[h].gt &= 0x80000000; m_nodes[h].gt |= gh; }
int get_balance_factor(handle h)
{
if (m_nodes[h].gt & 0x80000000)
return -1;
return static_cast<unsigned>(m_nodes[h].lt) >> 31;
}
void set_balance_factor(handle h, int bf)
{
if (bf == 0) {
m_nodes[h].lt &= 0x7FFFFFFF;
m_nodes[h].gt &= 0x7FFFFFFF;
} else {
m_nodes[h].lt |= 0x80000000;
if (bf < 0)
m_nodes[h].gt |= 0x80000000;
else
m_nodes[h].gt &= 0x7FFFFFFF;
}
}
int compare_key_key(key va, key vb)
{
ASSERT(!va.isUndefined());
ASSERT(!vb.isUndefined());
if (m_exec->hadException())
return 1;
double compareResult;
if (m_cachedCall) {
m_cachedCall->setThis(jsUndefined());
m_cachedCall->setArgument(0, va);
m_cachedCall->setArgument(1, vb);
compareResult = m_cachedCall->call().toNumber(m_cachedCall->newCallFrame(m_exec));
} else {
MarkedArgumentBuffer arguments;
arguments.append(va);
arguments.append(vb);
compareResult = call(m_exec, m_compareFunction, m_compareCallType, *m_compareCallData, jsUndefined(), arguments).toNumber(m_exec);
}
return (compareResult < 0) ? -1 : 1; }
int compare_key_node(key k, handle h) { return compare_key_key(k, m_nodes[h].value); }
int compare_node_node(handle h1, handle h2) { return compare_key_key(m_nodes[h1].value, m_nodes[h2].value); }
static handle null() { return 0x7FFFFFFF; }
};
void JSArray::sort(ExecState* exec, JSValue compareFunction, CallType callType, const CallData& callData)
{
ASSERT(!inSparseMode());
checkConsistency();
ASSERT(m_storage->m_length <= static_cast<unsigned>(std::numeric_limits<int>::max()));
if (m_storage->m_length > static_cast<unsigned>(std::numeric_limits<int>::max()))
return;
unsigned usedVectorLength = min(m_storage->m_length, m_vectorLength);
unsigned nodeCount = usedVectorLength;
if (!nodeCount)
return;
AVLTree<AVLTreeAbstractorForArrayCompare, 44> tree; tree.abstractor().m_exec = exec;
tree.abstractor().m_compareFunction = compareFunction;
tree.abstractor().m_compareCallType = callType;
tree.abstractor().m_compareCallData = &callData;
tree.abstractor().m_nodes.grow(nodeCount);
if (callType == CallTypeJS)
tree.abstractor().m_cachedCall = adoptPtr(new CachedCall(exec, jsCast<JSFunction*>(compareFunction), 2));
if (!tree.abstractor().m_nodes.begin()) {
throwOutOfMemoryError(exec);
return;
}
unsigned numDefined = 0;
unsigned numUndefined = 0;
for (; numDefined < usedVectorLength; ++numDefined) {
if (numDefined >= m_vectorLength)
break;
JSValue v = m_storage->m_vector[numDefined].get();
if (!v || v.isUndefined())
break;
tree.abstractor().m_nodes[numDefined].value = v;
tree.insert(numDefined);
}
for (unsigned i = numDefined; i < usedVectorLength; ++i) {
if (i >= m_vectorLength)
break;
JSValue v = m_storage->m_vector[i].get();
if (v) {
if (v.isUndefined())
++numUndefined;
else {
tree.abstractor().m_nodes[numDefined].value = v;
tree.insert(numDefined);
++numDefined;
}
}
}
unsigned newUsedVectorLength = numDefined + numUndefined;
ASSERT(!m_sparseValueMap);
unsigned newestUsedVectorLength = min(m_storage->m_length, m_vectorLength);
unsigned elementsToExtractThreshold = min(min(newestUsedVectorLength, numDefined), static_cast<unsigned>(tree.abstractor().m_nodes.size()));
unsigned undefinedElementsThreshold = min(newestUsedVectorLength, newUsedVectorLength);
unsigned clearElementsThreshold = min(newestUsedVectorLength, usedVectorLength);
AVLTree<AVLTreeAbstractorForArrayCompare, 44>::Iterator iter;
iter.start_iter_least(tree);
JSGlobalData& globalData = exec->globalData();
for (unsigned i = 0; i < elementsToExtractThreshold; ++i) {
m_storage->m_vector[i].set(globalData, this, tree.abstractor().m_nodes[*iter].value);
++iter;
}
for (unsigned i = elementsToExtractThreshold; i < undefinedElementsThreshold; ++i)
m_storage->m_vector[i].setUndefined();
for (unsigned i = undefinedElementsThreshold; i < clearElementsThreshold; ++i)
m_storage->m_vector[i].clear();
m_storage->m_numValuesInVector = undefinedElementsThreshold;
checkConsistency(SortConsistencyCheck);
}
void JSArray::fillArgList(ExecState* exec, MarkedArgumentBuffer& args)
{
ArrayStorage* storage = m_storage;
WriteBarrier<Unknown>* vector = storage->m_vector;
unsigned vectorEnd = min(storage->m_length, m_vectorLength);
unsigned i = 0;
for (; i < vectorEnd; ++i) {
WriteBarrier<Unknown>& v = vector[i];
if (!v)
break;
args.append(v.get());
}
for (; i < storage->m_length; ++i)
args.append(get(exec, i));
}
void JSArray::copyToArguments(ExecState* exec, CallFrame* callFrame, uint32_t length)
{
ASSERT(length == this->length());
UNUSED_PARAM(length);
unsigned i = 0;
WriteBarrier<Unknown>* vector = m_storage->m_vector;
unsigned vectorEnd = min(length, m_vectorLength);
for (; i < vectorEnd; ++i) {
WriteBarrier<Unknown>& v = vector[i];
if (!v)
break;
callFrame->setArgument(i, v.get());
}
for (; i < length; ++i)
callFrame->setArgument(i, get(exec, i));
}
unsigned JSArray::compactForSorting()
{
ASSERT(!inSparseMode());
checkConsistency();
ArrayStorage* storage = m_storage;
unsigned usedVectorLength = min(storage->m_length, m_vectorLength);
unsigned numDefined = 0;
unsigned numUndefined = 0;
for (; numDefined < usedVectorLength; ++numDefined) {
JSValue v = storage->m_vector[numDefined].get();
if (!v || v.isUndefined())
break;
}
for (unsigned i = numDefined; i < usedVectorLength; ++i) {
JSValue v = storage->m_vector[i].get();
if (v) {
if (v.isUndefined())
++numUndefined;
else
storage->m_vector[numDefined++].setWithoutWriteBarrier(v);
}
}
unsigned newUsedVectorLength = numDefined + numUndefined;
ASSERT(!m_sparseValueMap);
for (unsigned i = numDefined; i < newUsedVectorLength; ++i)
storage->m_vector[i].setUndefined();
for (unsigned i = newUsedVectorLength; i < usedVectorLength; ++i)
storage->m_vector[i].clear();
storage->m_numValuesInVector = newUsedVectorLength;
checkConsistency(SortConsistencyCheck);
return numDefined;
}
#if CHECK_ARRAY_CONSISTENCY
void JSArray::checkConsistency(ConsistencyCheckType type)
{
ArrayStorage* storage = m_storage;
ASSERT(!storage->m_inCompactInitialization);
ASSERT(storage);
if (type == SortConsistencyCheck)
ASSERT(!m_sparseValueMap);
unsigned numValuesInVector = 0;
for (unsigned i = 0; i < m_vectorLength; ++i) {
if (JSValue value = storage->m_vector[i].get()) {
ASSERT(i < storage->m_length);
if (type != DestructorConsistencyCheck)
value.isUndefined(); ++numValuesInVector;
} else {
if (type == SortConsistencyCheck)
ASSERT(i >= storage->m_numValuesInVector);
}
}
ASSERT(numValuesInVector == storage->m_numValuesInVector);
ASSERT(numValuesInVector <= storage->m_length);
if (m_sparseValueMap) {
SparseArrayValueMap::const_iterator end = m_sparseValueMap->end();
for (SparseArrayValueMap::const_iterator it = m_sparseValueMap->begin(); it != end; ++it) {
unsigned index = it->first;
ASSERT(index < storage->m_length);
ASSERT(index >= m_vectorLength);
ASSERT(index <= MAX_ARRAY_INDEX);
ASSERT(it->second);
if (type != DestructorConsistencyCheck)
it->second.getNonSparseMode().isUndefined(); }
}
}
#endif
}