From 5c25379cd1889dc16187c0ec62f32d2b17a320cf Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Thu, 9 Jan 2014 11:05:08 +0100 Subject: Save memory on array data Store a simple vector of Values in the array data, instead of a Vector of Property's. This halfes the memory consumption on 64bit and simplifies our code. If an indexed property gets converted to an accessor property, we simply convert the ArrayData into a SparseArrayData. Add support in SparseArrayData to allocate double slots (two Value's) to hold a full Property in case someone sets an accessor on an indexed property. Some methods still return a Property*, but this is safe, as only the first Value in the Property pointer will ever get accessed if the Property doesn't contain an accessor. Change-Id: Ic9b0f309b09a2772a328d947a10faaf3be9fe56f Reviewed-by: Simon Hausmann --- src/qml/jsruntime/qv4argumentsobject.cpp | 10 +- src/qml/jsruntime/qv4arraydata.cpp | 321 ++++++++++++++++++++----------- src/qml/jsruntime/qv4arraydata_p.h | 12 +- src/qml/jsruntime/qv4arrayobject.cpp | 29 +-- src/qml/jsruntime/qv4functionobject.cpp | 4 +- src/qml/jsruntime/qv4object.cpp | 90 +++++---- src/qml/jsruntime/qv4object_p.h | 34 ++-- src/qml/jsruntime/qv4objectproto.cpp | 16 +- src/qml/jsruntime/qv4property_p.h | 24 +-- src/qml/jsruntime/qv4runtime.cpp | 8 +- src/qml/jsruntime/qv4sparsearray.cpp | 14 +- src/qml/jsruntime/qv4sparsearray_p.h | 2 +- src/qml/jsruntime/qv4stringobject.cpp | 2 +- 13 files changed, 340 insertions(+), 226 deletions(-) (limited to 'src') diff --git a/src/qml/jsruntime/qv4argumentsobject.cpp b/src/qml/jsruntime/qv4argumentsobject.cpp index 5094bafa66..fccfd50307 100644 --- a/src/qml/jsruntime/qv4argumentsobject.cpp +++ b/src/qml/jsruntime/qv4argumentsobject.cpp @@ -69,6 +69,7 @@ ArgumentsObject::ArgumentsObject(CallContext *context) arrayData->setLength(context->callData->argc); fullyCreated = true; } else { + hasAccessorProperty = 1; Q_ASSERT(CalleePropertyIndex == internalClass->find(context->engine->id_callee)); memberData[CalleePropertyIndex].value = context->function->asReturnedValue(); } @@ -92,14 +93,13 @@ void ArgumentsObject::fullyCreate() uint numAccessors = qMin((int)context->function->formalParameterCount, context->realArgumentCount); uint argCount = qMin(context->realArgumentCount, context->callData->argc); arrayReserve(argCount); + initSparseArray(); arrayData->ensureAttributes(); context->engine->requireArgumentsAccessors(numAccessors); for (uint i = 0; i < (uint)numAccessors; ++i) { mappedArguments.append(context->callData->args[i]); - arrayData->data[i] = context->engine->argumentsAccessors.at(i); - arrayData->setAttributes(i, Attr_Accessor); + arraySet(i, context->engine->argumentsAccessors.at(i), Attr_Accessor); } - arrayData->setLength(numAccessors); arrayData->put(numAccessors, context->callData->args + numAccessors, argCount - numAccessors); for (uint i = numAccessors; i < argCount; ++i) arrayData->setAttributes(i, Attr_Data); @@ -124,6 +124,7 @@ bool ArgumentsObject::defineOwnProperty(ExecutionContext *ctx, uint index, const map = *pd; mapAttrs = arrayData->attributes(index); arrayData->setAttributes(index, Attr_Data); + pd = arrayData->getProperty(index); pd->value = mappedArguments.at(index); } @@ -139,8 +140,9 @@ bool ArgumentsObject::defineOwnProperty(ExecutionContext *ctx, uint index, const map.setter()->call(callData); if (attrs.isWritable()) { - *pd = map; arrayData->setAttributes(index, mapAttrs); + pd = arrayData->getProperty(index); + *pd = map; } } diff --git a/src/qml/jsruntime/qv4arraydata.cpp b/src/qml/jsruntime/qv4arraydata.cpp index 0ec36bd9db..09e9a7f28f 100644 --- a/src/qml/jsruntime/qv4arraydata.cpp +++ b/src/qml/jsruntime/qv4arraydata.cpp @@ -82,8 +82,8 @@ void ArrayData::getHeadRoom(ArrayData *d) Q_ASSERT(d); Q_ASSERT(!d->offset); d->offset = qMax(d->len >> 2, (uint)16); - Property *newArray = new Property[d->offset + d->alloc]; - memcpy(newArray + d->offset, d->data, d->len*sizeof(Property)); + SafeValue *newArray = new SafeValue[d->offset + d->alloc]; + memcpy(newArray + d->offset, d->data, d->len*sizeof(SafeValue)); delete [] d->data; d->data = newArray + d->offset; if (d->attrs) { @@ -102,9 +102,9 @@ void ArrayData::reserve(ArrayData *d, uint n) return; d->alloc = qMax(n, 2*d->alloc); - Property *newArrayData = new Property[d->alloc + d->offset]; + SafeValue *newArrayData = new SafeValue[d->alloc + d->offset]; if (d->data) { - memcpy(newArrayData + d->offset, d->data, sizeof(Property)*d->len); + memcpy(newArrayData + d->offset, d->data, sizeof(SafeValue)*d->len); delete [] (d->data - d->offset); } d->data = newArrayData + d->offset; @@ -144,14 +144,19 @@ ReturnedValue ArrayData::get(const ArrayData *d, uint index) { if (index >= d->len) return Primitive::emptyValue().asReturnedValue(); - return d->data[index].value.asReturnedValue(); + return d->data[index].asReturnedValue(); } bool ArrayData::put(ArrayData *d, uint index, ValueRef value) { - Q_ASSERT(!d->attrs || !d->attrs->isAccessor()); + Q_ASSERT(index >= d->len || !d->attrs || !d->attrs[index].isAccessor()); // ### honour attributes - d->data[index].value = value; + d->data[index] = value; + if (index >= d->len) { + if (d->attrs) + d->attrs[index] = Attr_Data; + d->len = index; + } return true; } @@ -161,12 +166,12 @@ bool ArrayData::del(ArrayData *d, uint index) return true; if (!d->attrs || d->attrs[index].isConfigurable()) { - d->data[index].value = Primitive::emptyValue(); + d->data[index] = Primitive::emptyValue(); if (d->attrs) d->attrs[index] = Attr_Data; return true; } - if (d->data[index].value.isEmpty()) + if (d->data[index].isEmpty()) return true; return false; } @@ -192,7 +197,7 @@ void ArrayData::push_front(ArrayData *d, SafeValue *values, uint n) --d->data; ++d->len; ++d->alloc; - d->data->value = values[i].asReturnedValue(); + *d->data = values[i].asReturnedValue(); } } @@ -203,7 +208,7 @@ ReturnedValue ArrayData::pop_front(ArrayData *d) if (!d->len) return Encode::undefined(); - ReturnedValue v = d->data[0].value.isEmpty() ? Encode::undefined() : d->data[0].value.asReturnedValue(); + ReturnedValue v = d->data[0].isEmpty() ? Encode::undefined() : d->data[0].asReturnedValue(); ++d->offset; ++d->data; --d->len; @@ -214,14 +219,14 @@ ReturnedValue ArrayData::pop_front(ArrayData *d) uint ArrayData::truncate(ArrayData *d, uint newLen) { if (d->attrs) { - Property *it = d->data + d->len; - const Property *begin = d->data + newLen; + SafeValue *it = d->data + d->len; + const SafeValue *begin = d->data + newLen; while (--it >= begin) { - if (!it->value.isEmpty() && !d->attrs[it - d->data].isConfigurable()) { + if (!it->isEmpty() && !d->attrs[it - d->data].isConfigurable()) { newLen = it - d->data + 1; break; } - it->value = Primitive::emptyValue(); + *it = Primitive::emptyValue(); } } d->len = newLen; @@ -233,9 +238,9 @@ bool ArrayData::putArray(ArrayData *d, uint index, SafeValue *values, uint n) if (index + n > d->alloc) reserve(d, index + n + 1); for (uint i = d->len; i < index; ++i) - d->data[i].value = Primitive::emptyValue(); + d->data[i] = Primitive::emptyValue(); for (uint i = 0; i < n; ++i) - d->data[index + i].value = values[i]; + d->data[index + i] = values[i]; d->len = qMax(d->len, index + n); return true; } @@ -244,8 +249,17 @@ void SparseArrayData::free(ArrayData *d, uint idx) { Q_ASSERT(d && d->type == ArrayData::Sparse); SparseArrayData *dd = static_cast(d); - Property &pd = dd->data[idx]; - pd.value.uint_32 = dd->freeList; + SafeValue *v = dd->data + idx; + if (dd->attrs && dd->attrs[idx].isAccessor()) { + // double slot, free both. Order is important, so we have a double slot for allocation again afterwards. + v[1].tag = Value::Empty_Type; + v[1].uint_32 = dd->freeList; + v[0].tag = Value::Empty_Type; + v[0].uint_32 = idx + 1; + } else { + v->tag = Value::Empty_Type; + v->uint_32 = dd->freeList; + } dd->freeList = idx; if (dd->attrs) dd->attrs[idx].clear(); @@ -266,12 +280,13 @@ void SparseArrayData::reserve(ArrayData *d, uint n) return; SparseArrayData *dd = static_cast(d); + uint oldAlloc = dd->alloc; // ### FIXME dd->len = dd->alloc; dd->alloc = qMax(n, 2*dd->alloc); - Property *newArrayData = new Property[dd->alloc]; + SafeValue *newArrayData = new SafeValue[dd->alloc]; if (dd->data) { - memcpy(newArrayData, dd->data, sizeof(Property)*dd->len); + memcpy(newArrayData, dd->data, sizeof(SafeValue)*dd->len); delete [] dd->data; } dd->data = newArrayData; @@ -281,21 +296,41 @@ void SparseArrayData::reserve(ArrayData *d, uint n) delete [] dd->attrs; dd->attrs = newAttrs; } - for (uint i = dd->freeList; i < dd->alloc; ++i) - dd->data[i].value = Primitive::fromInt32(i + 1); + for (uint i = oldAlloc; i < dd->alloc; ++i) + dd->data[i] = Primitive::fromInt32(i + 1); } -uint SparseArrayData::allocate(ArrayData *d) +// double slots are required for accessor properties +uint SparseArrayData::allocate(ArrayData *d, bool doubleSlot) { Q_ASSERT(d->type == ArrayData::Sparse); SparseArrayData *dd = static_cast(d); - uint idx = dd->freeList; - if (dd->alloc == dd->freeList) - reserve(d, d->alloc + 1); - dd->freeList = dd->data[dd->freeList].value.uint_32; - if (dd->attrs) - dd->attrs[idx].setType(PropertyAttributes::Data); - return idx; + if (doubleSlot) { + uint *last = &dd->freeList; + while (1) { + if (*last + 1 >= dd->alloc) { + reserve(d, d->alloc + 2); + last = &dd->freeList; + } + + if (dd->data[*last].uint_32 == (*last + 1)) { + // found two slots in a row + uint idx = *last; + *last = dd->data[*last + 1].uint_32; + d->attrs[idx] = Attr_Accessor; + return idx; + } + last = &dd->data[*last].uint_32; + } + } else { + if (dd->alloc == dd->freeList) + reserve(d, d->alloc + 2); + uint idx = dd->freeList; + dd->freeList = dd->data[idx].uint_32; + if (dd->attrs) + dd->attrs[idx] = Attr_Data; + return idx; + } } ReturnedValue SparseArrayData::get(const ArrayData *d, uint index) @@ -303,16 +338,21 @@ ReturnedValue SparseArrayData::get(const ArrayData *d, uint index) SparseArrayNode *n = static_cast(d)->sparse->findNode(index); if (!n) return Primitive::emptyValue().asReturnedValue(); - return d->data[n->value].value.asReturnedValue(); + return d->data[n->value].asReturnedValue(); } bool SparseArrayData::put(ArrayData *d, uint index, ValueRef value) { - // ### honour attributes + if (value->isEmpty()) + return true; + SparseArrayNode *n = static_cast(d)->sparse->insert(index); + Q_ASSERT(n->value == UINT_MAX || !d->attrs || !d->attrs[n->value].isAccessor()); if (n->value == UINT_MAX) n->value = allocate(d); - d->data[n->value].value = value; + d->data[n->value] = value; + if (d->attrs) + d->attrs[n->value] = Attr_Data; return true; } @@ -324,22 +364,43 @@ bool SparseArrayData::del(ArrayData *d, uint index) return true; uint pidx = n->value; - Q_ASSERT(!dd->data[pidx].value.isEmpty()); + Q_ASSERT(!dd->data[pidx].isEmpty()); - if (!dd->attrs || dd->attrs[pidx].isConfigurable()) { - d->data[pidx].value.int_32 = static_cast(d)->freeList; - static_cast(d)->freeList = pidx; - static_cast(d)->sparse->erase(n); - return true; + bool isAccessor = false; + if (dd->attrs) { + if (!dd->attrs[pidx].isConfigurable()) + return false; + + isAccessor = dd->attrs[pidx].isAccessor(); + dd->attrs[pidx] = Attr_Data; } - return false; + + if (isAccessor) { + // free up both indices + d->data[pidx + 1].tag = Value::Undefined_Type; + d->data[pidx + 1].uint_32 = static_cast(d)->freeList; + d->data[pidx].tag = Value::Undefined_Type; + d->data[pidx].uint_32 = pidx + 1; + } else { + d->data[pidx].tag = Value::Undefined_Type; + d->data[pidx].uint_32 = static_cast(d)->freeList; + } + + static_cast(d)->freeList = pidx; + static_cast(d)->sparse->erase(n); + return true; } void SparseArrayData::setAttribute(ArrayData *d, uint index, PropertyAttributes attrs) { SparseArrayNode *n = static_cast(d)->sparse->insert(index); if (n->value == UINT_MAX) - n->value = allocate(d); + n->value = allocate(d, attrs.isAccessor()); + else if (attrs.isAccessor() != d->attrs[n->value].isAccessor()) { + // need to convert the slot + free(d, n->value); + n->value = allocate(d, attrs.isAccessor()); + } d->attrs[n->value] = attrs; } @@ -356,7 +417,7 @@ void SparseArrayData::push_front(ArrayData *d, SafeValue *values, uint n) Q_ASSERT(!d->attrs); for (int i = n - 1; i >= 0; --i) { uint idx = allocate(d); - d->data[idx].value = values[i]; + d->data[idx] = values[i]; static_cast(d)->sparse->push_front(idx); } } @@ -367,7 +428,7 @@ ReturnedValue SparseArrayData::pop_front(ArrayData *d) uint idx = static_cast(d)->sparse->pop_front(); ReturnedValue v; if (idx != UINT_MAX) { - v = d->data[idx].value.asReturnedValue(); + v = d->data[idx].asReturnedValue(); SparseArrayData::free(d, idx); } else { v = Encode::undefined(); @@ -381,16 +442,13 @@ uint SparseArrayData::truncate(ArrayData *d, uint newLen) if (begin != static_cast(d)->sparse->end()) { SparseArrayNode *it = static_cast(d)->sparse->end()->previousNode(); while (1) { - Property &pd = d->data[it->value]; if (d->attrs) { if (!d->attrs[it->value].isConfigurable()) { newLen = it->key() + 1; break; } } - pd.value.tag = Value::Empty_Type; - pd.value.int_32 = static_cast(d)->freeList; - static_cast(d)->freeList = it->value; + free(d, it->value); bool brk = (it == begin); SparseArrayNode *prev = it->previousNode(); static_cast(d)->sparse->erase(it); @@ -415,7 +473,7 @@ uint ArrayData::append(Object *o, const ArrayObject *otherObj, uint n) { ArrayData *d = o->arrayData; if (!n) - return d->len; + return o->getLength(); const ArrayData *other = otherObj->arrayData; @@ -424,39 +482,37 @@ uint ArrayData::append(Object *o, const ArrayObject *otherObj, uint n) d = o->arrayData; } - uint oldSize = d->len; + uint oldSize = o->getLength(); // ### copy attributes as well! if (d->type == ArrayData::Sparse) { if (other->isSparse()) { - for (const SparseArrayNode *it = static_cast(other)->sparse->begin(); - it != static_cast(other)->sparse->end(); it = it->nextNode()) - // ### accessor properties - o->arraySet(d->len + it->key(), other->data[it->value].value); - } else { - d->vtable->reserve(d, oldSize + n); - memcpy(d->data + oldSize, other->data, n*sizeof(Property)); - if (d->attrs) - std::fill(d->attrs + oldSize, d->attrs + oldSize + n, PropertyAttributes(Attr_Data)); - for (uint i = 0; i < n; ++i) { - SparseArrayNode *n = static_cast(d)->sparse->insert(d->len + i); - n->value = oldSize + i; + if (otherObj->hasAccessorProperty && other->hasAttributes()) { + for (const SparseArrayNode *it = static_cast(other)->sparse->begin(); + it != static_cast(other)->sparse->end(); it = it->nextNode()) + o->arraySet(oldSize + it->key(), *reinterpret_cast(other->data + it->value), other->attrs[it->value]); + } else { + for (const SparseArrayNode *it = static_cast(other)->sparse->begin(); + it != static_cast(other)->sparse->end(); it = it->nextNode()) + o->arraySet(oldSize + it->key(), other->data[it->value]); } + } else { + d->put(oldSize, other->data, n); } } else if (other->length()) { d->vtable->reserve(d, oldSize + other->length()); if (oldSize > d->len) { for (uint i = d->len; i < oldSize; ++i) - d->data[i].value = Primitive::emptyValue(); + d->data[i] = Primitive::emptyValue(); } if (other->attrs) { for (uint i = 0; i < other->len; ++i) { bool exists; - d->data[oldSize + i].value = const_cast(otherObj)->getIndexed(i, &exists); + d->data[oldSize + i] = const_cast(otherObj)->getIndexed(i, &exists); d->len = oldSize + i + 1; o->arrayData->setAttributes(oldSize + i, Attr_Data); if (!exists) - d->data[oldSize + i].value = Primitive::emptyValue(); + d->data[oldSize + i] = Primitive::emptyValue(); } } else { d->len = oldSize + other->len; @@ -469,95 +525,134 @@ uint ArrayData::append(Object *o, const ArrayObject *otherObj, uint n) return oldSize + n; } -Property *ArrayData::insert(Object *o, uint index) +Property *ArrayData::insert(Object *o, uint index, bool isAccessor) { Property *pd; if (o->arrayData->type != ArrayData::Sparse && (index < 0x1000 || index < o->arrayData->len + (o->arrayData->len >> 2))) { + Q_ASSERT(!isAccessor); if (index >= o->arrayData->alloc) o->arrayReserve(index + 1); if (index >= o->arrayData->len) { // mark possible hole in the array for (uint i = o->arrayData->len; i < index; ++i) - o->arrayData->data[i].value = Primitive::emptyValue(); + o->arrayData->data[i] = Primitive::emptyValue(); o->arrayData->len = index + 1; } - pd = o->arrayData->data + index; + pd = reinterpret_cast(o->arrayData->data + index); } else { o->initSparseArray(); SparseArrayNode *n = static_cast(o->arrayData)->sparse->insert(index); if (n->value == UINT_MAX) - n->value = SparseArrayData::allocate(o->arrayData); - pd = o->arrayData->data + n->value; + n->value = SparseArrayData::allocate(o->arrayData, isAccessor); + pd = reinterpret_cast(o->arrayData->data + n->value); } return pd; } void ArrayData::markObjects(ExecutionEngine *e) { - if (type == ArrayData::Simple) { - for (uint i = 0; i < len; ++i) - data[i].value.mark(e); - return; - } else { - for (uint i = 0; i < len; ++i) { - const Property &pd = data[i]; - if (attrs && attrs[i].isAccessor()) { - if (pd.getter()) - pd.getter()->mark(e); - if (pd.setter()) - pd.setter()->mark(e); - } else { - pd.value.mark(e); - } - } - } - + for (uint i = 0; i < len; ++i) + data[i].mark(e); } void ArrayData::sort(ExecutionContext *context, ObjectRef thisObject, const ValueRef comparefn, uint len) { + if (!len) + return; + ArrayData *d = thisObject->arrayData; - if (!d || !d->len) + if (!d || (!d->len && d->type != ArrayData::Sparse)) return; - if (d->type == ArrayData::Sparse) { - context->throwUnimplemented(QStringLiteral("Object::sort unimplemented for sparse arrays")); + if (!(comparefn->isUndefined() || comparefn->asObject())) { + context->throwTypeError(); return; } - if (len > d->len) - len = d->len; - // The spec says the sorting goes through a series of get,put and delete operations. // this implies that the attributes don't get sorted around. - // behavior of accessor properties is implementation defined. We simply turn them all - // into data properties and then sort. This is in line with the sentence above. - if (d->attrs) { + + if (d->type == ArrayData::Sparse) { + // since we sort anyway, we can simply iterate over the entries in the sparse + // array and append them one by one to a regular one. + SparseArrayData *sparse = static_cast(d); + + if (!sparse->sparse->nEntries()) + return; + + thisObject->arrayData = new ArrayData; + d = thisObject->arrayData; + d->vtable->reserve(d, sparse->sparse->nEntries()); + + SparseArrayNode *n = sparse->sparse->begin(); + uint i = 0; + if (sparse->attrs) { + d->ensureAttributes(); + while (n != sparse->sparse->end()) { + if (n->value >= len) + break; + + PropertyAttributes a = sparse->attrs ? sparse->attrs[n->value] : Attr_Data; + d->data[i] = thisObject->getValue(reinterpret_cast(sparse->data + n->value), a); + d->attrs[i] = a.isAccessor() ? Attr_Data : a; + + n = n->nextNode(); + ++i; + } + } else { + while (n != sparse->sparse->end()) { + if (n->value >= len) + break; + d->data[i] = sparse->data[n->value]; + n = n->nextNode(); + ++i; + } + } + d->len = i; + if (len > i) + len = i; + if (n != sparse->sparse->end()) { + // have some entries outside the sort range that we need to ignore when sorting + thisObject->initSparseArray(); + d = thisObject->arrayData; + while (n != sparse->sparse->end()) { + PropertyAttributes a = sparse->attrs ? sparse->attrs[n->value] : Attr_Data; + thisObject->arraySet(n->value, *reinterpret_cast(sparse->data + n->value), a); + + n = n->nextNode(); + } + + } + + sparse->ArrayData::free(); + } else { + if (len > d->len) + len = d->len; + + // sort empty values to the end for (uint i = 0; i < len; i++) { - if (d->data[i].value.isEmpty()) { + if (d->data[i].isEmpty()) { while (--len > i) - if (!d->data[len].value.isEmpty()) + if (!d->data[len].isEmpty()) break; - d->data[i].value = thisObject->getValue(d->data + len, d->attrs ? d->attrs[len] : Attr_Data); - if (d->attrs) - d->attrs[i] = Attr_Data; - d->data[len].value = Primitive::emptyValue(); - } else if (d->attrs && d->attrs[i].isAccessor()) { - d->data[i].value = thisObject->getValue(d->data + i, d->attrs[i]); - d->attrs[i] = Attr_Data; + Q_ASSERT(!d->attrs || !d->attrs[len].isAccessor()); + d->data[i] = d->data[len]; + d->data[len] = Primitive::emptyValue(); } } - } - if (!(comparefn->isUndefined() || comparefn->asObject())) { - context->throwTypeError(); - return; + if (!len) + return; } + ArrayElementLessThan lessThan(context, thisObject, comparefn); - if (!len) - return; - Property *begin = d->data; + SafeValue *begin = d->data; std::sort(begin, begin + len, lessThan); + +#ifdef CHECK_SPARSE_ARRAYS + thisObject->initSparseArray(); +#endif + } diff --git a/src/qml/jsruntime/qv4arraydata_p.h b/src/qml/jsruntime/qv4arraydata_p.h index 98968fada2..3e0901061c 100644 --- a/src/qml/jsruntime/qv4arraydata_p.h +++ b/src/qml/jsruntime/qv4arraydata_p.h @@ -95,7 +95,7 @@ struct Q_QML_EXPORT ArrayData uint alloc; uint type; PropertyAttributes *attrs; - Property *data; + SafeValue *data; bool isSparse() const { return this && type == Sparse; } @@ -170,7 +170,7 @@ struct Q_QML_EXPORT ArrayData static void sort(ExecutionContext *context, ObjectRef thisObject, const ValueRef comparefn, uint dataLen); static uint append(Object *o, const ArrayObject *otherObj, uint n); - static Property *insert(Object *o, uint index); + static Property *insert(Object *o, uint index, bool isAccessor = false); void markObjects(ExecutionEngine *e); static void getHeadRoom(ArrayData *d); @@ -200,7 +200,7 @@ struct Q_QML_EXPORT SparseArrayData : public ArrayData uint freeList; SparseArray *sparse; - static uint allocate(ArrayData *d); + static uint allocate(ArrayData *d, bool doubleSlot = false); static void free(ArrayData *d, uint idx); static void freeData(ArrayData *d); @@ -224,14 +224,14 @@ inline Property *ArrayData::getProperty(uint index) const if (!this) return 0; if (type != Sparse) { - if (index >= len || data[index].value.isEmpty()) + if (index >= len || data[index].isEmpty()) return 0; - return data + index; + return reinterpret_cast(data + index); } else { SparseArrayNode *n = static_cast(this)->sparse->findNode(index); if (!n) return 0; - return data + n->value; + return reinterpret_cast(data + n->value); } } diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp index eb8a5301de..4ea979d16e 100644 --- a/src/qml/jsruntime/qv4arrayobject.cpp +++ b/src/qml/jsruntime/qv4arrayobject.cpp @@ -613,7 +613,7 @@ ReturnedValue ArrayPrototype::method_indexOf(CallContext *ctx) ScopedValue value(scope); - if ((instance->arrayData && instance->arrayType() != ArrayData::Simple) || instance->protoHasArray()) { + if (instance->hasAccessorProperty || (instance->arrayType() >= ArrayData::Sparse) || instance->protoHasArray()) { // lets be safe and slow for (uint i = fromIndex; i < len; ++i) { bool exists; @@ -625,31 +625,22 @@ ReturnedValue ArrayPrototype::method_indexOf(CallContext *ctx) } } else if (!instance->arrayData) { return Encode(-1); - } else if (instance->arrayType() == ArrayData::Sparse) { - for (SparseArrayNode *n = static_cast(instance->arrayData)->sparse->lowerBound(fromIndex); - n != static_cast(instance->arrayData)->sparse->end() && n->key() < len; n = n->nextNode()) { - value = instance->getValue(instance->arrayData->data + n->value, - instance->arrayData->attrs ? instance->arrayData->attrs[n->value] : Attr_Data); - if (scope.hasException()) - return Encode::undefined(); - if (__qmljs_strict_equal(value, searchValue)) - return Encode(n->key()); - } } else { + Q_ASSERT(instance->arrayType() == ArrayData::Simple || instance->arrayType() == ArrayData::Complex); if (len > instance->arrayData->length()) len = instance->arrayData->length(); - Property *pd = instance->arrayData->data; - Property *end = pd + len; - pd += fromIndex; - while (pd < end) { - if (!pd->value.isEmpty()) { - value = instance->getValue(pd, instance->arrayData->attributes(pd - instance->arrayData->data)); + SafeValue *val = instance->arrayData->data; + SafeValue *end = val + len; + val += fromIndex; + while (val < end) { + if (!val->isEmpty()) { + value = *val; if (scope.hasException()) return Encode::undefined(); if (__qmljs_strict_equal(value, searchValue)) - return Encode((uint)(pd - instance->arrayData->data)); + return Encode((uint)(val - instance->arrayData->data)); } - ++pd; + ++val; } } return Encode(-1); diff --git a/src/qml/jsruntime/qv4functionobject.cpp b/src/qml/jsruntime/qv4functionobject.cpp index daa3d5b0de..858d625725 100644 --- a/src/qml/jsruntime/qv4functionobject.cpp +++ b/src/qml/jsruntime/qv4functionobject.cpp @@ -351,8 +351,8 @@ ReturnedValue FunctionPrototype::method_apply(CallContext *ctx) callData->args[i] = arr->getIndexed(i); } else { int alen = qMin(len, arr->arrayData->length()); - for (int i = 0; i < alen; ++i) - callData->args[i] = arr->arrayData->data[i].value; + if (alen) + memcpy(callData->args, arr->arrayData->data, alen*sizeof(SafeValue)); for (quint32 i = alen; i < len; ++i) callData->args[i] = Primitive::undefinedValue(); } diff --git a/src/qml/jsruntime/qv4object.cpp b/src/qml/jsruntime/qv4object.cpp index 4d02d46135..f3a4b8a5aa 100644 --- a/src/qml/jsruntime/qv4object.cpp +++ b/src/qml/jsruntime/qv4object.cpp @@ -229,14 +229,9 @@ void Object::markObjects(Managed *that, ExecutionEngine *e) } else { for (uint i = 0; i < o->internalClass->size; ++i) { const Property &pd = o->memberData[i]; - if (o->internalClass->propertyData[i].isAccessor()) { - if (pd.getter()) - pd.getter()->mark(e); - if (pd.setter()) - pd.setter()->mark(e); - } else { - pd.value.mark(e); - } + pd.value.mark(e); + if (o->internalClass->propertyData[i].isAccessor()) + pd.set.mark(e); } } if (o->arrayData) @@ -562,7 +557,7 @@ void Object::advanceIterator(Managed *m, ObjectIterator *it, StringRef name, uin while (it->arrayNode != o->sparseEnd()) { int k = it->arrayNode->key(); uint pidx = it->arrayNode->value; - Property *p = o->arrayData->data + pidx; + Property *p = reinterpret_cast(o->arrayData->data + pidx); it->arrayNode = it->arrayNode->nextNode(); PropertyAttributes a = o->arrayData->attributes(k); if (!(it->flags & ObjectIterator::EnumerableOnly) || a.isEnumerable()) { @@ -578,14 +573,14 @@ void Object::advanceIterator(Managed *m, ObjectIterator *it, StringRef name, uin } // dense arrays while (it->arrayIndex < o->arrayData->length()) { - Property *p = o->arrayData->data + it->arrayIndex; + SafeValue *val = o->arrayData->data + it->arrayIndex; PropertyAttributes a = o->arrayData->attributes(it->arrayIndex); ++it->arrayIndex; - if (!p->value.isEmpty() + if (!val->isEmpty() && (!(it->flags & ObjectIterator::EnumerableOnly) || a.isEnumerable())) { *index = it->arrayIndex - 1; *attrs = a; - *pd = *p; + pd->value = *val; return; } } @@ -874,6 +869,7 @@ bool Object::__defineOwnProperty__(ExecutionContext *ctx, const StringRef name, Scope scope(ctx); Property *current; PropertyAttributes *cattrs; + uint memberIndex; if (isArrayObject() && name->equals(ctx->engine->id_length)) { assert(ArrayObject::LengthPropertyIndex == internalClass->find(ctx->engine->id_length)); @@ -904,11 +900,9 @@ bool Object::__defineOwnProperty__(ExecutionContext *ctx, const StringRef name, } // Clause 1 - { - uint member = internalClass->find(name.getPointer()); - current = (member < UINT_MAX) ? memberData + member : 0; - cattrs = internalClass->propertyData.constData() + member; - } + memberIndex = internalClass->find(name.getPointer()); + current = (memberIndex < UINT_MAX) ? memberData + memberIndex : 0; + cattrs = internalClass->propertyData.constData() + memberIndex; if (!current) { // clause 3 @@ -921,7 +915,7 @@ bool Object::__defineOwnProperty__(ExecutionContext *ctx, const StringRef name, return true; } - return __defineOwnProperty__(ctx, current, name, p, attrs); + return __defineOwnProperty__(ctx, memberIndex, name, p, attrs); reject: if (ctx->strictMode) ctx->throwTypeError(); @@ -972,24 +966,28 @@ bool Object::defineOwnProperty2(ExecutionContext *ctx, uint index, const Propert return true; } - return __defineOwnProperty__(ctx, current, StringRef::null(), p, attrs); + return __defineOwnProperty__(ctx, index, StringRef::null(), p, attrs); reject: if (ctx->strictMode) ctx->throwTypeError(); return false; } -bool Object::__defineOwnProperty__(ExecutionContext *ctx, Property *current, const StringRef member, const Property &p, PropertyAttributes attrs) +bool Object::__defineOwnProperty__(ExecutionContext *ctx, uint index, const StringRef member, const Property &p, PropertyAttributes attrs) { // clause 5 if (attrs.isEmpty()) return true; + Property *current; PropertyAttributes cattrs; - if (!member.isNull()) - cattrs = internalClass->propertyData[current - memberData]; - else - cattrs = arrayData->attrs ? arrayData->attrs[current - arrayData->data] : Attr_Data; + if (!member.isNull()) { + current = memberData + index; + cattrs = internalClass->propertyData[index]; + } else { + current = arrayData->getProperty(index); + cattrs = arrayData->attributes(index); + } // clause 6 if (p.isSubset(attrs, *current, cattrs)) @@ -1016,12 +1014,23 @@ bool Object::__defineOwnProperty__(ExecutionContext *ctx, Property *current, con // 9b cattrs.setType(PropertyAttributes::Accessor); cattrs.clearWritable(); + if (member.isNull()) { + // need to convert the array and the slot + initSparseArray(); + arrayData->setAttributes(index, cattrs); + current = arrayData->getProperty(index); + } current->setGetter(0); current->setSetter(0); } else { // 9c cattrs.setType(PropertyAttributes::Data); cattrs.setWritable(false); + if (member.isNull()) { + // need to convert the array and the slot + arrayData->setAttributes(index, cattrs); + current = arrayData->getProperty(index); + } current->value = Primitive::undefinedValue(); } } else if (cattrs.isData() && attrs.isData()) { // clause 10 @@ -1030,11 +1039,11 @@ bool Object::__defineOwnProperty__(ExecutionContext *ctx, Property *current, con goto reject; } } else { // clause 10 - assert(cattrs.isAccessor() && attrs.isAccessor()); + Q_ASSERT(cattrs.isAccessor() && attrs.isAccessor()); if (!cattrs.isConfigurable()) { - if (p.getter() && !(current->getter() == p.getter() || (!current->getter() && (quintptr)p.getter() == 0x1))) + if (!p.value.isEmpty() && current->value.val != p.value.val) goto reject; - if (p.setter() && !(current->setter() == p.setter() || (!current->setter() && (quintptr)p.setter() == 0x1))) + if (!p.set.isEmpty() && current->set.val != p.set.val) goto reject; } } @@ -1045,9 +1054,9 @@ bool Object::__defineOwnProperty__(ExecutionContext *ctx, Property *current, con if (!member.isNull()) { internalClass = internalClass->changeMember(member.getPointer(), cattrs); } else { - arrayData->setAttributes(current - arrayData->data, cattrs); + arrayData->setAttributes(index, cattrs); } - if (attrs.isAccessor()) + if (cattrs.isAccessor()) hasAccessorProperty = 1; return true; reject: @@ -1078,6 +1087,13 @@ void Object::copyArrayData(Object *other) for (uint i = 0; i < len; ++i) { arraySet(i, (v = other->getIndexed(i))); } + } else if (!other->arrayData) { + ; + } else if (other->hasAccessorProperty && other->arrayData->attrs && other->arrayData->isSparse()){ + // do it the slow way + for (const SparseArrayNode *it = static_cast(other->arrayData)->sparse->begin(); + it != static_cast(other->arrayData)->sparse->end(); it = it->nextNode()) + arraySet(it->key(), *reinterpret_cast(other->arrayData->data + it->value), other->arrayData->attrs[it->value]); } else { Q_ASSERT(!arrayData && other->arrayData); if (other->arrayType() == ArrayData::Sparse) { @@ -1087,11 +1103,11 @@ void Object::copyArrayData(Object *other) dd->sparse = new SparseArray(*od->sparse); dd->freeList = od->freeList; arrayData = dd; + other->arrayData->len = other->arrayData->alloc; } arrayReserve(other->arrayData->len); arrayData->len = other->arrayData->len; - // ### correctly deal with accessor properties - memcpy(arrayData->data, other->arrayData->data, arrayData->len*sizeof(Property)); + memcpy(arrayData->data, other->arrayData->data, arrayData->len*sizeof(SafeValue)); arrayData->offset = 0; } @@ -1152,21 +1168,25 @@ void Object::initSparseArray() uint *lastFree = &data->freeList; for (uint i = 0; i < oldOffset; ++i) { *lastFree = i; - lastFree = &data->data[i].value.uint_32; + data->data[i].tag = Value::Empty_Type; + lastFree = &data->data[i].uint_32; } for (uint i = 0; i < data->len; ++i) { - if (!data->data[i + oldOffset].value.isEmpty()) { + if (!data->data[i + oldOffset].isEmpty()) { SparseArrayNode *n = data->sparse->insert(i); n->value = i + oldOffset; } else { *lastFree = i + oldOffset; - lastFree = &data->data[i + oldOffset].value.uint_32; + data->data[i + oldOffset].tag = Value::Empty_Type; + lastFree = &data->data[i + oldOffset].uint_32; } } for (uint i = data->len + oldOffset; i < data->alloc; ++i) { *lastFree = i; - lastFree = &data->data[i].value.uint_32; + data->data[i].tag = Value::Empty_Type; + lastFree = &data->data[i].uint_32; } + *lastFree = data->alloc; arrayData = data; } diff --git a/src/qml/jsruntime/qv4object_p.h b/src/qml/jsruntime/qv4object_p.h index f99191a24c..de44d5cfd3 100644 --- a/src/qml/jsruntime/qv4object_p.h +++ b/src/qml/jsruntime/qv4object_p.h @@ -136,7 +136,7 @@ struct Q_QML_EXPORT Object: Managed { bool hasOwnProperty(const StringRef name) const; bool hasOwnProperty(uint index) const; - bool __defineOwnProperty__(ExecutionContext *ctx, Property *current, const StringRef member, const Property &p, PropertyAttributes attrs); + bool __defineOwnProperty__(ExecutionContext *ctx, uint index, const StringRef member, const Property &p, PropertyAttributes attrs); bool __defineOwnProperty__(ExecutionContext *ctx, const StringRef name, const Property &p, PropertyAttributes attrs); bool __defineOwnProperty__(ExecutionContext *ctx, uint index, const Property &p, PropertyAttributes attrs); bool __defineOwnProperty__(ExecutionContext *ctx, const QString &name, const Property &p, PropertyAttributes attrs); @@ -209,17 +209,15 @@ public: void arrayCreate() { if (!arrayData) arrayData = new ArrayData; +#ifdef CHECK_SPARSE_ARRAYS + initSparseArray(); +#endif } void initSparseArray(); SparseArrayNode *sparseBegin() { return arrayType() == ArrayData::Sparse ? static_cast(arrayData)->sparse->begin() : 0; } SparseArrayNode *sparseEnd() { return arrayType() == ArrayData::Sparse ? static_cast(arrayData)->sparse->end() : 0; } - inline Property *arrayInsert(uint index) { - arrayCreate(); - return ArrayData::insert(this, index); - } - inline bool protoHasArray() { Scope scope(engine()); Scoped p(scope, this); @@ -344,7 +342,6 @@ inline void Object::setArrayLengthUnchecked(uint l) inline void Object::push_back(const ValueRef v) { arrayCreate(); - Q_ASSERT(!arrayData->isSparse()); uint idx = getLength(); arrayReserve(idx + 1); @@ -355,12 +352,21 @@ inline void Object::push_back(const ValueRef v) inline void Object::arraySet(uint index, const Property &p, PropertyAttributes attributes) { - if (attributes.isAccessor()) + // ### Clean up + arrayCreate(); + if (attributes.isAccessor()) { hasAccessorProperty = 1; - - Property *pd = arrayInsert(index); - *pd = p; + initSparseArray(); + } else if (index > 0x1000 && index > 2*arrayData->alloc) { + initSparseArray(); + } else { + arrayData->vtable->reserve(arrayData, index + 1); + } arrayData->setAttributes(index, attributes); + Property *pd = ArrayData::insert(this, index, attributes.isAccessor()); + pd->value = p.value; + if (attributes.isAccessor()) + pd->set = p.set; if (isArrayObject() && index >= getLength()) setArrayLengthUnchecked(index + 1); } @@ -368,7 +374,11 @@ inline void Object::arraySet(uint index, const Property &p, PropertyAttributes a inline void Object::arraySet(uint index, ValueRef value) { - Property *pd = arrayInsert(index); + arrayCreate(); + if (index > 0x1000 && index > 2*arrayData->alloc) { + initSparseArray(); + } + Property *pd = ArrayData::insert(this, index); pd->value = value ? *value : Primitive::undefinedValue(); if (isArrayObject() && index >= getLength()) setArrayLengthUnchecked(index + 1); diff --git a/src/qml/jsruntime/qv4objectproto.cpp b/src/qml/jsruntime/qv4objectproto.cpp index 410f243a3c..1096aa1b2d 100644 --- a/src/qml/jsruntime/qv4objectproto.cpp +++ b/src/qml/jsruntime/qv4objectproto.cpp @@ -597,8 +597,8 @@ void ObjectPrototype::toPropertyDescriptor(ExecutionContext *ctx, const ValueRef } attrs->clear(); - desc->setGetter(0); - desc->setSetter(0); + desc->value = Primitive::emptyValue(); + desc->set = Primitive::emptyValue(); ScopedValue tmp(scope); if (o->hasProperty(ctx->engine->id_enumerable)) @@ -610,10 +610,8 @@ void ObjectPrototype::toPropertyDescriptor(ExecutionContext *ctx, const ValueRef if (o->hasProperty(ctx->engine->id_get)) { ScopedValue get(scope, o->get(ctx->engine->id_get)); FunctionObject *f = get->asFunctionObject(); - if (f) { - desc->setGetter(f); - } else if (get->isUndefined()) { - desc->setGetter((FunctionObject *)0x1); + if (f || get->isUndefined()) { + desc->value = get; } else { ctx->throwTypeError(); return; @@ -624,10 +622,8 @@ void ObjectPrototype::toPropertyDescriptor(ExecutionContext *ctx, const ValueRef if (o->hasProperty(ctx->engine->id_set)) { ScopedValue set(scope, o->get(ctx->engine->id_set)); FunctionObject *f = set->asFunctionObject(); - if (f) { - desc->setSetter(f); - } else if (set->isUndefined()) { - desc->setSetter((FunctionObject *)0x1); + if (f || set->isUndefined()) { + desc->set = set; } else { ctx->throwTypeError(); return; diff --git a/src/qml/jsruntime/qv4property_p.h b/src/qml/jsruntime/qv4property_p.h index 4b0896b264..6381fe7687 100644 --- a/src/qml/jsruntime/qv4property_p.h +++ b/src/qml/jsruntime/qv4property_p.h @@ -62,10 +62,10 @@ struct Property { } if (attrs->type() == PropertyAttributes::Accessor) { attrs->clearWritable(); - if (value.managed() == (Managed *)0x1) - value = Primitive::fromManaged(0); - if (set.managed() == (Managed *)0x1) - set = Primitive::fromManaged(0); + if (value.isEmpty()) + value = Primitive::undefinedValue(); + if (set.isEmpty()) + set = Primitive::undefinedValue(); } attrs->resolve(); } @@ -91,8 +91,8 @@ struct Property { inline bool isSubset(const PropertyAttributes &attrs, const Property &other, PropertyAttributes otherAttrs) const; inline void merge(PropertyAttributes &attrs, const Property &other, PropertyAttributes otherAttrs); - inline FunctionObject *getter() const { return reinterpret_cast(value.managed()); } - inline FunctionObject *setter() const { return reinterpret_cast(set.managed()); } + inline FunctionObject *getter() const { return reinterpret_cast(value.asManaged()); } + inline FunctionObject *setter() const { return reinterpret_cast(set.asManaged()); } inline void setGetter(FunctionObject *g) { value = Primitive::fromManaged(reinterpret_cast(g)); } inline void setSetter(FunctionObject *s) { set = Primitive::fromManaged(reinterpret_cast(s)); } }; @@ -110,9 +110,9 @@ inline bool Property::isSubset(const PropertyAttributes &attrs, const Property & if (attrs.type() == PropertyAttributes::Data && !value.sameValue(other.value)) return false; if (attrs.type() == PropertyAttributes::Accessor) { - if (value.managed() != other.value.managed()) + if (value.asManaged() != other.value.asManaged()) return false; - if (set.managed() != other.set.managed()) + if (set.asManaged() != other.set.asManaged()) return false; } return true; @@ -128,10 +128,10 @@ inline void Property::merge(PropertyAttributes &attrs, const Property &other, Pr attrs.setWritable(otherAttrs.isWritable()); if (otherAttrs.type() == PropertyAttributes::Accessor) { attrs.setType(PropertyAttributes::Accessor); - if (other.value.managed()) - value = (other.value.managed() == (Managed *)0x1) ? Primitive::fromManaged(0).asReturnedValue() : other.value.asReturnedValue(); - if (other.set.managed()) - set = (other.set.managed() == (Managed *)0x1) ? Primitive::fromManaged(0).asReturnedValue() : other.set.asReturnedValue(); + if (!other.value.isEmpty()) + value = other.value; + if (!other.set.isEmpty()) + set = other.set; } else if (otherAttrs.type() == PropertyAttributes::Data){ attrs.setType(PropertyAttributes::Data); value = other.value; diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index 4c7ec6602d..a8b453309d 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -1120,8 +1120,8 @@ void __qmljs_builtin_define_getter_setter(ExecutionContext *ctx, const ValueRef uint idx = name->asArrayIndex(); Property pd; - pd.setGetter(getter ? getter->asFunctionObject() : 0); - pd.setSetter(setter ? setter->asFunctionObject() : 0); + pd.value = getter; + pd.set = setter; if (idx != UINT_MAX) { o->arraySet(idx, pd, Attr_Accessor); } else { @@ -1139,9 +1139,9 @@ ReturnedValue __qmljs_builtin_define_object_literal(QV4::ExecutionContext *ctx, if (klass->propertyData[i].isData()) o->memberData[i].value = *args++; else { - o->memberData[i].setGetter(args->asFunctionObject()); + o->memberData[i].value = *args; args++; - o->memberData[i].setSetter(args->asFunctionObject()); + o->memberData[i].set = *args; args++; } } diff --git a/src/qml/jsruntime/qv4sparsearray.cpp b/src/qml/jsruntime/qv4sparsearray.cpp index 97dd695067..7169f5c20e 100644 --- a/src/qml/jsruntime/qv4sparsearray.cpp +++ b/src/qml/jsruntime/qv4sparsearray.cpp @@ -53,13 +53,13 @@ using namespace QV4; -bool ArrayElementLessThan::operator()(const Property &p1, const Property &p2) const +bool ArrayElementLessThan::operator()(const SafeValue &v1, const SafeValue &v2) const { Scope scope(m_context); - if (p1.value.isUndefined() || p1.value.isEmpty()) + if (v1.isUndefined() || v1.isEmpty()) return false; - if (p2.value.isUndefined() || p2.value.isEmpty()) + if (v2.isUndefined() || v2.isEmpty()) return true; ScopedObject o(scope, m_comparefn); if (o) { @@ -67,14 +67,14 @@ bool ArrayElementLessThan::operator()(const Property &p1, const Property &p2) co ScopedValue result(scope); ScopedCallData callData(scope, 2); callData->thisObject = Primitive::undefinedValue(); - callData->args[0] = p1.value; - callData->args[1] = p2.value; + callData->args[0] = v1; + callData->args[1] = v2; result = __qmljs_call_value(m_context, m_comparefn, callData); return result->toNumber() < 0; } - ScopedString p1s(scope, p1.value.toString(m_context)); - ScopedString p2s(scope, p2.value.toString(m_context)); + ScopedString p1s(scope, v1.toString(m_context)); + ScopedString p2s(scope, v2.toString(m_context)); return p1s->toQString() < p2s->toQString(); } diff --git a/src/qml/jsruntime/qv4sparsearray_p.h b/src/qml/jsruntime/qv4sparsearray_p.h index f2ad167a17..6c2808b6b1 100644 --- a/src/qml/jsruntime/qv4sparsearray_p.h +++ b/src/qml/jsruntime/qv4sparsearray_p.h @@ -68,7 +68,7 @@ public: inline ArrayElementLessThan(ExecutionContext *context, ObjectRef thisObject, const ValueRef comparefn) : m_context(context), thisObject(thisObject), m_comparefn(comparefn) {} - bool operator()(const Property &v1, const Property &v2) const; + bool operator()(const SafeValue &v1, const SafeValue &v2) const; private: ExecutionContext *m_context; diff --git a/src/qml/jsruntime/qv4stringobject.cpp b/src/qml/jsruntime/qv4stringobject.cpp index dfd093caed..b9ec4202eb 100644 --- a/src/qml/jsruntime/qv4stringobject.cpp +++ b/src/qml/jsruntime/qv4stringobject.cpp @@ -140,7 +140,7 @@ void StringObject::advanceIterator(Managed *m, ObjectIterator *it, StringRef nam name = (String *)0; StringObject *s = static_cast(m); uint slen = s->value.stringValue()->toQString().length(); - if (it->arrayIndex < slen) { + if (it->arrayIndex <= slen) { while (it->arrayIndex < slen) { *index = it->arrayIndex; ++it->arrayIndex; -- cgit v1.2.3