// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qv4arraydata_p.h" #include "qv4object_p.h" #include "qv4functionobject_p.h" #include #include "qv4runtime_p.h" #include "qv4argumentsobject_p.h" #include "qv4string_p.h" #include "qv4jscall_p.h" using namespace QV4; DEFINE_MANAGED_VTABLE(ArrayData); const ArrayVTable SimpleArrayData::static_vtbl = { DEFINE_MANAGED_VTABLE_INT(SimpleArrayData, nullptr), Heap::ArrayData::Simple, SimpleArrayData::reallocate, SimpleArrayData::get, SimpleArrayData::put, SimpleArrayData::putArray, SimpleArrayData::del, SimpleArrayData::setAttribute, SimpleArrayData::push_front, SimpleArrayData::pop_front, SimpleArrayData::truncate, SimpleArrayData::length }; const ArrayVTable SparseArrayData::static_vtbl = { DEFINE_MANAGED_VTABLE_INT(SparseArrayData, nullptr), Heap::ArrayData::Sparse, SparseArrayData::reallocate, SparseArrayData::get, SparseArrayData::put, SparseArrayData::putArray, SparseArrayData::del, SparseArrayData::setAttribute, SparseArrayData::push_front, SparseArrayData::pop_front, SparseArrayData::truncate, SparseArrayData::length }; Q_STATIC_ASSERT(sizeof(Heap::ArrayData) == sizeof(Heap::SimpleArrayData)); Q_STATIC_ASSERT(sizeof(Heap::ArrayData) == sizeof(Heap::SparseArrayData)); void Heap::ArrayData::markObjects(Heap::Base *base, MarkStack *stack) { ArrayData *a = static_cast(base); a->values.mark(stack); } void ArrayData::realloc(Object *o, Type newType, uint requested, bool enforceAttributes) { Scope scope(o->engine()); Scoped d(scope, o->arrayData()); uint alloc = 8; uint toCopy = 0; uint offset = 0; if (d) { bool hasAttrs = d->attrs(); enforceAttributes |= hasAttrs; if (requested <= d->alloc() && newType == d->type() && hasAttrs == enforceAttributes) return; if (alloc < d->alloc()) alloc = d->alloc(); if (d->type() < Heap::ArrayData::Sparse) { offset = d->d()->offset; toCopy = d->d()->values.size; } else { toCopy = d->alloc(); } if (d->type() > newType) newType = d->type(); } while (alloc < requested) alloc *= 2; size_t size = sizeof(Heap::ArrayData) + (alloc - 1)*sizeof(Value); if (enforceAttributes) size += alloc*sizeof(PropertyAttributes); Scoped newData(scope); if (newType < Heap::ArrayData::Sparse) { Heap::SimpleArrayData *n = scope.engine->memoryManager->allocManaged(size); n->init(); n->offset = 0; n->values.size = d ? d->d()->values.size : 0; newData = n; } else { Heap::SparseArrayData *n = scope.engine->memoryManager->allocManaged(size); n->init(); newData = n; } newData->setAlloc(alloc); newData->setType(newType); newData->setAttrs(enforceAttributes ? reinterpret_cast(newData->d()->values.values + alloc) : nullptr); o->setArrayData(newData); if (d) { if (enforceAttributes) { if (d->attrs()) memcpy(newData->attrs(), d->attrs(), sizeof(PropertyAttributes)*toCopy); else for (uint i = 0; i < toCopy; ++i) newData->attrs()[i] = Attr_Data; } if (toCopy > d->d()->values.alloc - offset) { uint copyFromStart = toCopy - (d->d()->values.alloc - offset); // no write barrier required here memcpy(newData->d()->values.values + toCopy - copyFromStart, d->d()->values.values, sizeof(Value)*copyFromStart); toCopy -= copyFromStart; } // no write barrier required here memcpy(newData->d()->values.values, d->d()->values.values + offset, sizeof(Value)*toCopy); } if (newType != Heap::ArrayData::Sparse) return; Heap::SparseArrayData *sparse = static_cast(newData->d()); Value *lastFree; if (d && d->type() == Heap::ArrayData::Sparse) { Heap::SparseArrayData *old = static_cast(d->d()); sparse->sparse = old->sparse; old->sparse = nullptr; lastFree = &sparse->sparse->freeList; } else { sparse->sparse = new SparseArray; lastFree = &sparse->sparse->freeList; *lastFree = Encode(0); for (uint i = 0; i < toCopy; ++i) { if (!sparse->values[i].isEmpty()) { SparseArrayNode *n = sparse->sparse->insert(i); n->value = i; } else { *lastFree = Encode(i); sparse->values.values[i].setEmpty(); lastFree = &sparse->values.values[i]; } } } if (toCopy < sparse->values.alloc) { for (uint i = toCopy; i < sparse->values.alloc; ++i) { *lastFree = Encode(i); sparse->values.values[i].setEmpty(); lastFree = &sparse->values.values[i]; } } *lastFree = Encode(-1); Q_ASSERT(sparse->sparse->freeList.isInteger()); // ### Could explicitly free the old data } Heap::ArrayData *SimpleArrayData::reallocate(Object *o, uint n, bool enforceAttributes) { realloc(o, Heap::ArrayData::Simple, n, enforceAttributes); return o->arrayData(); } void ArrayData::ensureAttributes(Object *o) { if (o->arrayData() && o->arrayData()->attrs) return; ArrayData::realloc(o, Heap::ArrayData::Simple, 0, true); } ReturnedValue SimpleArrayData::get(const Heap::ArrayData *d, uint index) { const Heap::SimpleArrayData *dd = static_cast(d); if (index >= dd->values.size) return Value::emptyValue().asReturnedValue(); return dd->data(index).asReturnedValue(); } bool SimpleArrayData::put(Object *o, uint index, const Value &value) { Heap::SimpleArrayData *dd = o->d()->arrayData.cast(); Q_ASSERT(index >= dd->values.size || !dd->attrs || !dd->attrs[index].isAccessor()); // ### honour attributes dd->setData(o->engine(), index, value); if (index >= dd->values.size) { if (dd->attrs) dd->attrs[index] = Attr_Data; dd->values.size = index + 1; } return true; } bool SimpleArrayData::del(Object *o, uint index) { Heap::SimpleArrayData *dd = o->d()->arrayData.cast(); if (index >= dd->values.size) return true; if (!dd->attrs || dd->attrs[index].isConfigurable()) { dd->setData(o->engine(), index, Value::emptyValue()); if (dd->attrs) dd->attrs[index] = Attr_Data; return true; } if (dd->data(index).isEmpty()) return true; return false; } void SimpleArrayData::setAttribute(Object *o, uint index, PropertyAttributes attrs) { o->arrayData()->attrs[index] = attrs; } void SimpleArrayData::push_front(Object *o, const Value *values, uint n) { Heap::SimpleArrayData *dd = o->d()->arrayData.cast(); Q_ASSERT(!dd->attrs); if (dd->values.size + n > dd->values.alloc) { realloc(o, Heap::ArrayData::Simple, dd->values.size + n, false); Q_ASSERT(o->d()->arrayData->type == Heap::ArrayData::Simple); dd = o->d()->arrayData.cast(); } if (n <= dd->offset) { dd->offset -= n; // there is enough space left in front } else { // we need to wrap around, so: dd->offset = dd->values.alloc - // start at the back, but subtract: (n - dd->offset); // the number of items we can put in the free space at the start of the allocated array } dd->values.size += n; for (uint i = 0; i < n; ++i) dd->setData(o->engine(), i, values[i]); } ReturnedValue SimpleArrayData::pop_front(Object *o) { Heap::SimpleArrayData *dd = o->d()->arrayData.cast(); Q_ASSERT(!dd->attrs); if (!dd->values.size) return Encode::undefined(); ReturnedValue v = dd->data(0).isEmpty() ? Encode::undefined() : dd->data(0).asReturnedValue(); dd->offset = (dd->offset + 1) % dd->values.alloc; --dd->values.size; return v; } uint SimpleArrayData::truncate(Object *o, uint newLen) { Heap::SimpleArrayData *dd = o->d()->arrayData.cast(); if (dd->values.size < newLen) return newLen; if (!dd->attrs) { dd->values.size = newLen; return newLen; } while (dd->values.size > newLen) { if (!dd->data(dd->values.size - 1).isEmpty() && !dd->attrs[dd->values.size - 1].isConfigurable()) return dd->values.size; --dd->values.size; } return dd->values.size; } uint SimpleArrayData::length(const Heap::ArrayData *d) { return d->values.size; } bool SimpleArrayData::putArray(Object *o, uint index, const Value *values, uint n) { Heap::SimpleArrayData *dd = o->d()->arrayData.cast(); if (index + n > dd->values.alloc) { reallocate(o, index + n + 1, false); dd = o->d()->arrayData.cast(); } QV4::ExecutionEngine *e = o->engine(); for (uint i = dd->values.size; i < index; ++i) dd->setData(e, i, Value::emptyValue()); for (uint i = 0; i < n; ++i) dd->setData(e, index + i, values[i]); dd->values.size = qMax(dd->values.size, index + n); return true; } void SparseArrayData::free(Heap::ArrayData *d, uint idx) { Q_ASSERT(d && d->type == Heap::ArrayData::Sparse); Value *v = d->values.values + idx; if (d->attrs && d->attrs[idx].isAccessor()) { // double slot, free both. Order is important, so we have a double slot for allocation again afterwards. v[1] = d->sparse->freeList; v[0] = Encode(idx + 1); } else { *v = d->sparse->freeList; } d->sparse->freeList = Encode(idx); if (d->attrs) d->attrs[idx].clear(); } Heap::ArrayData *SparseArrayData::reallocate(Object *o, uint n, bool enforceAttributes) { realloc(o, Heap::ArrayData::Sparse, n, enforceAttributes); return o->arrayData(); } // double slots are required for accessor properties uint SparseArrayData::allocate(Object *o, bool doubleSlot) { Q_ASSERT(o->d()->arrayData->type == Heap::ArrayData::Sparse); Heap::SimpleArrayData *dd = o->d()->arrayData.cast(); if (doubleSlot) { Value *last = &dd->sparse->freeList; while (1) { if (last->int_32() == -1) { reallocate(o, dd->values.alloc + 2, true); dd = o->d()->arrayData.cast(); last = &dd->sparse->freeList; Q_ASSERT(last->int_32() != -1); } Q_ASSERT(dd->values[static_cast(last->int_32())].int_32() != last->int_32()); if (dd->values[static_cast(last->int_32())].int_32() == last->int_32() + 1) { // found two slots in a row uint idx = static_cast(last->int_32()); *last = Encode(dd->values[static_cast(last->int_32()) + 1].int_32()); dd->attrs[idx] = Attr_Accessor; return idx; } last = &dd->values.values[last->int_32()]; } } else { if (dd->sparse->freeList.int_32() == -1) { reallocate(o, dd->values.alloc + 1, false); dd = o->d()->arrayData.cast(); } Q_ASSERT(dd->sparse->freeList.int_32() != -1); uint idx = static_cast(dd->sparse->freeList.int_32()); dd->sparse->freeList = dd->values[idx]; Q_ASSERT(dd->sparse->freeList.isInteger()); if (dd->attrs) dd->attrs[idx] = Attr_Data; return idx; } } ReturnedValue SparseArrayData::get(const Heap::ArrayData *d, uint index) { const Heap::SparseArrayData *s = static_cast(d); index = s->mappedIndex(index); if (index == UINT_MAX) return Value::emptyValue().asReturnedValue(); return s->values[index].asReturnedValue(); } bool SparseArrayData::put(Object *o, uint index, const Value &value) { if (value.isEmpty()) return true; Heap::SparseArrayData *s = o->d()->arrayData.cast(); SparseArrayNode *n = s->sparse->insert(index); Q_ASSERT(n->value == UINT_MAX || !s->attrs || !s->attrs[n->value].isAccessor()); if (n->value == UINT_MAX) n->value = allocate(o); s = o->d()->arrayData.cast(); s->setArrayData(o->engine(), n->value, value); if (s->attrs) s->attrs[n->value] = Attr_Data; return true; } bool SparseArrayData::del(Object *o, uint index) { Heap::SparseArrayData *dd = o->d()->arrayData.cast(); SparseArrayNode *n = dd->sparse->findNode(index); if (!n) return true; uint pidx = n->value; Q_ASSERT(!dd->values[pidx].isEmpty()); bool isAccessor = false; if (dd->attrs) { if (!dd->attrs[pidx].isConfigurable()) return false; isAccessor = dd->attrs[pidx].isAccessor(); dd->attrs[pidx] = Attr_Data; } if (isAccessor) { // free up both indices dd->values.values[pidx + 1] = dd->sparse->freeList; dd->values.values[pidx] = Encode(pidx + 1); } else { Q_ASSERT(dd->type == Heap::ArrayData::Sparse); dd->values.values[pidx] = dd->sparse->freeList; } dd->sparse->freeList = Encode(pidx); dd->sparse->erase(n); return true; } void SparseArrayData::setAttribute(Object *o, uint index, PropertyAttributes attrs) { Heap::SparseArrayData *d = o->d()->arrayData.cast(); SparseArrayNode *n = d->sparse->insert(index); if (n->value == UINT_MAX) { n->value = allocate(o, attrs.isAccessor()); d = o->d()->arrayData.cast(); } else if (attrs.isAccessor() != d->attrs[n->value].isAccessor()) { // need to convert the slot free(o->arrayData(), n->value); n->value = allocate(o, attrs.isAccessor()); d = o->d()->arrayData.cast(); } d->attrs[n->value] = attrs; } void SparseArrayData::push_front(Object *o, const Value *values, uint n) { Heap::SparseArrayData *d = o->d()->arrayData.cast(); Q_ASSERT(!d->attrs); for (int i = static_cast(n) - 1; i >= 0; --i) { uint idx = allocate(o); d = o->d()->arrayData.cast(); d->setArrayData(o->engine(), idx, values[i]); d->sparse->push_front(idx); } } ReturnedValue SparseArrayData::pop_front(Object *o) { Heap::SparseArrayData *d = o->d()->arrayData.cast(); Q_ASSERT(!d->attrs); uint idx = d->sparse->pop_front(); ReturnedValue v; if (idx != UINT_MAX) { v = d->values[idx].asReturnedValue(); free(o->arrayData(), idx); } else { v = Encode::undefined(); } return v; } uint SparseArrayData::truncate(Object *o, uint newLen) { Heap::SparseArrayData *d = o->d()->arrayData.cast(); SparseArrayNode *begin = d->sparse->lowerBound(newLen); if (begin != d->sparse->end()) { SparseArrayNode *it = d->sparse->end()->previousNode(); while (1) { if (d->attrs) { if (!d->attrs[it->value].isConfigurable()) { newLen = it->key() + 1; break; } } free(o->arrayData(), it->value); bool brk = (it == begin); SparseArrayNode *prev = it->previousNode(); d->sparse->erase(it); if (brk) break; it = prev; } } return newLen; } uint SparseArrayData::length(const Heap::ArrayData *d) { const Heap::SparseArrayData *dd = static_cast(d); if (!dd->sparse) return 0; SparseArrayNode *n = dd->sparse->end(); n = n->previousNode(); return n ? n->key() + 1 : 0; } bool SparseArrayData::putArray(Object *o, uint index, const Value *values, uint n) { for (uint i = 0; i < n; ++i) put(o, index + i, values[i]); return true; } uint ArrayData::append(Object *obj, ArrayObject *otherObj, uint n) { Q_ASSERT(!obj->d()->arrayData || !obj->d()->arrayData->attrs); if (!n) return obj->getLength(); Scope scope(obj->engine()); Scoped other(scope, otherObj->arrayData()); if (other && other->isSparse()) obj->initSparseArray(); else obj->arrayCreate(); uint oldSize = obj->getLength(); if (!other || ArgumentsObject::isNonStrictArgumentsObject(otherObj)) { ScopedValue v(scope); for (uint i = 0; i < n; ++i) obj->arraySet(oldSize + i, (v = otherObj->get(i))); } else if (other->isSparse()) { Heap::SparseArrayData *os = static_cast(other->d()); if (other->hasAttributes()) { ScopedValue v(scope); for (const SparseArrayNode *it = os->sparse->begin(); it != os->sparse->end(); it = it->nextNode()) { v = otherObj->getValue(os->values[it->value], other->d()->attrs[it->value]); obj->arraySet(oldSize + it->key(), v); } } else { for (const SparseArrayNode *it = other->d()->sparse->begin(); it != os->sparse->end(); it = it->nextNode()) obj->arraySet(oldSize + it->key(), os->values[it->value]); } } else { Heap::SimpleArrayData *os = static_cast(other->d()); uint toCopy = n; uint chunk = toCopy; if (chunk > os->values.alloc - os->offset) chunk = os->values.alloc - os->offset; obj->arrayPut(oldSize, os->values.data() + os->offset, chunk); toCopy -= chunk; if (toCopy) obj->arrayPut(oldSize + chunk, os->values.data(), toCopy); } return oldSize + n; } void ArrayData::insert(Object *o, uint index, const Value *v, bool isAccessor) { if (!isAccessor && o->d()->arrayData->type != Heap::ArrayData::Sparse) { Heap::SimpleArrayData *d = o->d()->arrayData.cast(); if (index < 0x1000 || index < d->values.size + (d->values.size >> 2)) { if (index >= d->values.alloc) { o->arrayReserve(index + 1); d = o->d()->arrayData.cast(); } if (index >= d->values.size) { // mark possible hole in the array for (uint i = d->values.size; i < index; ++i) d->setData(o->engine(), i, Value::emptyValue()); d->values.size = index + 1; } d->setData(o->engine(), index, *v); return; } } o->initSparseArray(); Heap::SparseArrayData *s = o->d()->arrayData.cast(); SparseArrayNode *n = s->sparse->insert(index); if (n->value == UINT_MAX) n->value = SparseArrayData::allocate(o, isAccessor); s = o->d()->arrayData.cast(); s->setArrayData(o->engine(), n->value, *v); if (isAccessor) s->setArrayData(o->engine(), n->value + Object::SetterOffset, v[Object::SetterOffset]); } bool ArrayElementLessThan::operator()(Value v1, Value v2) const { Scope scope(m_engine); if (v1.isUndefined() || v1.isEmpty()) return false; if (v2.isUndefined() || v2.isEmpty()) return true; ScopedFunctionObject o(scope, m_comparefn); if (o) { Scope scope(o->engine()); ScopedValue result(scope); JSCallArguments jsCallData(scope, 2); jsCallData.args[0] = v1; jsCallData.args[1] = v2; result = o->call(jsCallData); if (scope.hasException()) return false; return result->toNumber() < 0; } ScopedString p1s(scope, v1.toString(scope.engine)); ScopedString p2s(scope, v2.toString(scope.engine)); if (!p1s) return false; if (!p2s) return true; return p1s->toQString() < p2s->toQString(); } void ArrayData::sort(ExecutionEngine *engine, Object *thisObject, const Value &comparefn, uint len) { if (!len) return; Scope scope(engine); Scoped arrayData(scope, thisObject->arrayData()); if (!arrayData || !arrayData->length()) return; if (!comparefn.isUndefined() && !comparefn.isFunctionObject()) { engine->throwTypeError(); return; } // 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. if (arrayData->type() == Heap::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. Scoped sparse(scope, static_cast(arrayData->d())); if (!sparse->sparse()->nEntries()) return; thisObject->setArrayData(nullptr); ArrayData::realloc(thisObject, Heap::ArrayData::Simple, sparse->sparse()->nEntries(), sparse->attrs() ? true : false); Heap::SimpleArrayData *d = thisObject->d()->arrayData.cast(); SparseArrayNode *n = sparse->sparse()->begin(); uint i = 0; if (sparse->attrs()) { while (n != sparse->sparse()->end()) { if (n->value >= len) break; PropertyAttributes a = sparse->attrs() ? sparse->attrs()[n->value] : Attr_Data; d->setData(engine, i, Value::fromReturnedValue(thisObject->getValue(sparse->arrayData()[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->setData(engine, i, sparse->arrayData()[n->value]); n = n->nextNode(); ++i; } } d->values.size = 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(); while (n != sparse->sparse()->end()) { PropertyAttributes a = sparse->attrs() ? sparse->attrs()[n->value] : Attr_Data; thisObject->arraySet(n->value, reinterpret_cast(sparse->arrayData() + n->value), a); n = n->nextNode(); } } } else { Heap::SimpleArrayData *d = thisObject->d()->arrayData.cast(); if (len > d->values.size) len = d->values.size; // sort empty values to the end for (uint i = 0; i < len; i++) { if (d->data(i).isEmpty()) { while (--len > i) if (!d->data(len).isEmpty()) break; Q_ASSERT(!d->attrs || !d->attrs[len].isAccessor()); d->setData(engine, i, d->data(len)); d->setData(engine, len, Value::emptyValue()); } } if (!len) return; } ArrayElementLessThan lessThan(engine, comparefn); const auto thisArrayData = thisObject->arrayData(); uint startIndex = thisArrayData->mappedIndex(0); uint endIndex = thisArrayData->mappedIndex(len - 1) + 1; if (startIndex < endIndex) { // Values are contiguous. Sort right away. sortHelper( thisArrayData->values.values + startIndex, thisArrayData->values.values + endIndex, lessThan); } else { // Values wrap around the end of the allocation. Close the gap to form a contiguous array. // We're going to sort anyway. So we don't need to care about order. // ArrayElementLessThan sorts empty and undefined to the end of the array anyway, but we // probably shouldn't rely on the unused slots to be actually undefined or empty. const uint gap = startIndex - endIndex; const uint allocEnd = thisArrayData->values.alloc - 1; for (uint i = 0; i < gap; ++i) { const uint from = allocEnd - i; const uint to = endIndex + i; if (from < startIndex) break; std::swap(thisArrayData->values.values[from], thisArrayData->values.values[to]); } thisArrayData->offset = 0; sortHelper(thisArrayData->values.values, thisArrayData->values.values + len, lessThan); } #ifdef CHECK_SPARSE_ARRAYS thisObject->initSparseArray(); #endif }