Revision control
++ // 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
++ // Qt-Security score:significant
++
++ #include <QtCore/qmetasequence.h>
++
++ #include "qv4sequenceobject_p.h"
++
++ #include <private/qv4functionobject_p.h>
++ #include <private/qv4arrayobject_p.h>
++ #include <private/qqmlengine_p.h>
++ #include <private/qv4scopedvalue_p.h>
++ #include <private/qv4jscall_p.h>
++ #include <private/qqmlmetatype_p.h>
++ #include <private/qqmltype_p_p.h>
++ #include <private/qqmlvaluetypewrapper_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ * \class QV4::Sequence
++ * \internal
++ *
++ * A Sequence stores the contents of a sequential container and makes them acccessible
++ * to the JavaScript engine. It behaves mostly like a regular JavaScript array. The
++ * entries of the container are exposed as array elements.
++ *
++ * Sequence is a ReferenceObject. Therefore it writes back its contents to the property
++ * it was retrieved from whenever it changes. It also re-reads the property whenever
++ * that one changes.
++ *
++ * As long as a Sequence is attached to a property this way, it is the responsibility of
++ * the property's surrounding (C++) object to keep the contents valid. It has to, for
++ * example, track pointers to QObjects potentially deleted in other places so that they
++ * don't become dangling.
++ *
++ * However, the Sequence can also be detached. This happens predominantly by assigning
++ * it to a QML-declared property. In that case, it becomes the Sequence's responsibility
++ * to track its contents. To do so, it does not necessarily keep an actual instance of
++ * the original container in this case, but may rather stores its contents as actual
++ * JavaScript array elements. This includes QObjectWrappers for all QObject pointers it
++ * may contain. The contents are then marked like all JavaScript array elements when the
++ * garbage collector runs, and QObjectWrapper also guards against external deletion.
++ * There is no property to read or write back in this case, and neither does the
++ * internal container need to be updated. Therefore, the objects stored as array elements
++ * are created detached and won't read or write back.
++ *
++ * For element types that don't need to be marked, Sequence will still use the original
++ * container for storage, even in the detached case. This is usually more efficient
++ * because because it saves some data conversion. The only types that need to be marked
++ * are pointers to QObject-derived types, either stored as-is or hidden inside QVariant.
++ * Whenever the container cannot possibly hold such elements (directly or indirectly),
++ * the original container is used.
++ */
++
++ Q_STATIC_LOGGING_CATEGORY(lcListValueConversion, "qt.qml.listvalueconversion")
++
++ namespace QV4 {
++
++ DEFINE_OBJECT_VTABLE(Sequence);
++
++ static ReturnedValue doGetIndexed(Heap::Sequence *p, qsizetype index)
++ {
++ Q_ASSERT(p->isStoredInline());
++ QV4::Scope scope(p->internalClass->engine);
++
++ const QMetaType valueMetaType = p->valueMetaType();
++ const QMetaSequence metaSequence = p->metaSequence();
++
++ Heap::ReferenceObject::Flags flags = Heap::ReferenceObject::EnforcesLocation;
++ if (metaSequence.canSetValueAtIndex())
++ flags |= Heap::ReferenceObject::CanWriteBack;
++
++ const void *container = p->storagePointer();
++ Q_ASSERT(container); // Must readReference() before
++
++ QVariant result;
++ if (valueMetaType == QMetaType::fromType<QVariant>()) {
++ flags |= Heap::ReferenceObject::IsVariant;
++ metaSequence.valueAtIndex(container, index, &result);
++ } else {
++ result = QVariant(valueMetaType);
++ metaSequence.valueAtIndex(container, index, result.data());
++ }
++
++ QV4::ScopedValue v(scope, scope.engine->fromVariant(result, p, index, flags));
++ if (QQmlValueTypeWrapper *ref = v->as<QQmlValueTypeWrapper>()) {
++ if (CppStackFrame *frame = scope.engine->currentStackFrame)
++ ref->d()->setLocation(frame->v4Function, frame->statementNumber());
++ // No need to read the reference. We've done that above already.
++ }
++ return v->asReturnedValue();
++ }
++
++ static void *createVariantData(QMetaType type, QVariant *variant)
++ {
++ if (type == QMetaType::fromType<QVariant>())
++ return variant;
++ *variant = QVariant(type);
++ return variant->data();
++ }
++
++ static const void *retrieveVariantData(QMetaType type, const QVariant *variant)
++ {
++ if (type == QMetaType::fromType<QVariant>())
++ return variant;
++ return variant->constData();
++ }
++
++ // helper function to generate valid warnings if errors occur during sequence operations.
++ static void generateWarning(QV4::ExecutionEngine *v4, const QString& description)
++ {
++ QQmlEngine *engine = v4->qmlEngine();
++ if (!engine)
++ return;
++ QQmlError retn;
++ retn.setDescription(description);
++
++ QV4::CppStackFrame *stackFrame = v4->currentStackFrame;
++
++ retn.setLine(stackFrame->lineNumber());
++ retn.setUrl(QUrl(stackFrame->source()));
++ QQmlEnginePrivate::warning(engine, retn);
++ }
++
++ static qsizetype sizeInline(const Heap::Sequence *p)
++ {
++ Q_ASSERT(p->isStoredInline());
++
++ if (const void *container = p->storagePointer())
++ return p->metaSequence().size(container);
++
++ // It can be stored inline, and the container can still be nullptr.
++ // This happens if we construct it from a nullptr in the first place and never update it.
++ // It means it's empty.
++ return 0;
++ }
++
++ struct SequenceOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
++ {
++ ~SequenceOwnPropertyKeyIterator() override = default;
++ PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override
++ {
++ Heap::Sequence *p = static_cast<const Sequence *>(o)->d();
++ Q_ASSERT(p->isStoredInline());
++
++ if (p->isReference() && !p->loadReference())
++ return PropertyKey::invalid();
++
++ const qsizetype size = sizeInline(p);
++ if (size > 0 && qIsAtMostSizetypeLimit(arrayIndex, size - 1)) {
++ const uint index = arrayIndex;
++ ++arrayIndex;
++ if (attrs)
++ *attrs = QV4::Attr_Data;
++ if (pd)
++ pd->value = doGetIndexed(p, index);
++ return PropertyKey::fromArrayIndex(index);
++ }
++
++ if (memberIndex == 0) {
++ ++memberIndex;
++ return o->engine()->id_length()->propertyKey();
++ }
++
++ // You cannot add any own properties via the regular JavaScript interfaces.
++ return PropertyKey::invalid();
++ }
++ };
++
++ void Heap::Sequence::initTypes(QMetaType listType, QMetaSequence metaSequence)
++ {
++ m_listType = listType.iface();
++ Q_ASSERT(m_listType);
++ m_metaSequence = metaSequence.iface();
++ Q_ASSERT(m_metaSequence);
++ }
++
++ void Heap::Sequence::init(QMetaType listType, QMetaSequence metaSequence, const void *container)
++ {
++ ReferenceObject::init(nullptr, -1, NoFlag);
++ initTypes(listType, metaSequence);
++ if (isStoredInline())
++ createInlineStorage(container);
++ else
++ createElementWrappers(container);
++ }
++
++ void Heap::Sequence::init(
++ QMetaType listType, QMetaSequence metaSequence, const void *container,
++ Heap::Object *object, int propertyIndex, Heap::ReferenceObject::Flags flags)
++ {
++ ReferenceObject::init(object, propertyIndex, flags | IsDirty);
++ initTypes(listType, metaSequence);
++
++ if (isStoredInline()) {
++ if (CppStackFrame *frame = internalClass->engine->currentStackFrame)
++ setLocation(frame->v4Function, frame->statementNumber());
++ createInlineStorage(container);
++ if (!container && (flags & EnforcesLocation))
++ QV4::ReferenceObject::readReference(this);
++ } else {
++ createElementWrappers(container);
++ }
++ }
++
++ void Heap::Sequence::createInlineStorage(const void *container)
++ {
++ Q_ASSERT(isStoredInline());
++
++ QV4::Scope scope(internalClass->engine);
++ QV4::Scoped<QV4::Sequence> o(scope, this);
++ o->setArrayType(Heap::ArrayData::Custom);
++ if (container)
++ m_container = listType().create(container);
++ }
++
++ void Heap::Sequence::createElementWrappers(const void *container)
++ {
++ Q_ASSERT(!isStoredInline());
++
++ if (!container)
++ return;
++
++ const QMetaSequence metaSequence(m_metaSequence);
++ const QMetaType valueMetaType = metaSequence.valueMetaType();
++ const qsizetype size = metaSequence.size(container);
++
++ QV4::Scope scope(internalClass->engine);
++ if (!qIsAtMostUintLimit(size)) {
++ generateWarning(scope.engine, QLatin1String("Sequence length out of range"));
++ return;
++ }
++
++ QV4::Scoped<QV4::Sequence> self(scope, this);
++ self->arrayReserve(size);
++ QV4::ScopedValue v(scope);
++ if (valueMetaType == QMetaType::fromType<QVariant>()) {
++ QVariant var;
++ for (qsizetype i = 0; i < size; ++i) {
++ metaSequence.valueAtIndex(container, i, &var);
++ v = scope.engine->metaTypeToJS(var.metaType(), var.constData());
++ self->arraySet(i, v);
++ }
++ } else {
++ QVariant var(valueMetaType);
++ for (qsizetype i = 0; i < size; ++i) {
++ metaSequence.valueAtIndex(container, i, var.data());
++ v = scope.engine->metaTypeToJS(valueMetaType, var.constData());
++ self->arraySet(i, v);
++ }
++ }
++
++ m_size = size;
++ }
++
++ Heap::Sequence *Heap::Sequence::detached()
++ {
++ const QMetaType listType(m_listType);
++ const QMetaSequence metaSequence(m_metaSequence);
++ if (isStoredInline()) {
++ return internalClass->engine->memoryManager->allocate<QV4::Sequence>(
++ listType, metaSequence, m_container);
++ }
++
++ QVariant list(listType);
++
++ const QMetaType valueMetaType(m_metaSequence->valueMetaType);
++ QVariant element;
++ void *elementData = createVariantData(valueMetaType, &element);
++
++ QV4::Scope scope(internalClass->engine);
++ if (qIsAtMostSizetypeLimit(m_size)) {
++ QV4::Scoped<QV4::Sequence> self(scope, this);
++ QV4::ScopedValue v(scope);
++ for (uint i = 0; i < m_size; ++i) {
++ v = self->get(PropertyKey::fromArrayIndex(i));
++ ExecutionEngine::metaTypeFromJS(v, valueMetaType, elementData);
++ metaSequence.addValue(list.data(), elementData);
++ }
++ } else {
++ generateWarning(scope.engine, QLatin1String("Index out of range during toVariant()"));
++ }
++
++ return scope.engine->memoryManager->allocate<QV4::Sequence>(
++ listType, metaSequence, list.constData());
++ }
++
++ void Heap::Sequence::destroy()
++ {
++ if (isStoredInline() && m_container)
++ listType().destroy(m_container);
++ ReferenceObject::destroy();
++ }
++
++ void *Heap::Sequence::storagePointer()
++ {
++ if (!isStoredInline())
++ return nullptr;
++ if (!m_container)
++ m_container = listType().create();
++ return m_container;
++ }
++
++ bool Heap::Sequence::setVariant(const QVariant &variant)
++ {
++ // Should only happen from readReference(). Therefore we are attached.
++ Q_ASSERT(isStoredInline());
++
++ const QMetaType variantReferenceType = variant.metaType();
++ if (variantReferenceType != listType()) {
++ // This is a stale reference. That is, the property has been
++ // overwritten with a different type in the meantime.
++ // We need to modify this reference to the updated type, if
++ // possible, or return false if it is not a sequence.
++ const QQmlType newType = QQmlMetaType::qmlListType(variantReferenceType);
++ if (newType.isSequentialContainer()) {
++ if (m_container)
++ listType().destroy(m_container);
++ m_listType = newType.qListTypeId().iface();
++ m_metaSequence = newType.listMetaSequence().iface();
++ m_container = listType().create(variant.constData());
++ return true;
++ } else {
++ return false;
++ }
++ }
++ if (m_container) {
++ variantReferenceType.destruct(m_container);
++ variantReferenceType.construct(m_container, variant.constData());
++ } else {
++ m_container = variantReferenceType.create(variant.constData());
++ }
++ return true;
++ }
++ QVariant Heap::Sequence::toVariant() const
++ {
++ // Should only happen from readReference(). Therefore we are attached.
++ Q_ASSERT(isStoredInline());
++
++ return QVariant(listType(), m_container);
++ }
++
++ template<typename Action>
++ void convertAndDo(const QVariant &item, const QMetaType v, Action action)
++ {
++ if (item.metaType() == v) {
++ action(item.constData());
++ } else if (v == QMetaType::fromType<QVariant>()) {
++ action(&item);
++ } else {
++ QVariant converted = item;
++ if (!converted.convert(v))
++ converted = QVariant(v);
++ action(converted.constData());
++ }
++ }
++
++ static void appendInline(Heap::Sequence *p, const QVariant &item)
++ {
++ convertAndDo(item, p->valueMetaType(), [p](const void *data) {
++ p->metaSequence().addValueAtEnd(p->storagePointer(), data);
++ });
++ }
++
++ static void appendDefaultConstructedInline(Heap::Sequence *p, qsizetype num)
++ {
++ QVariant item;
++ const void *data = createVariantData(p->valueMetaType(), &item);
++ const QMetaSequence m = p->metaSequence();
++ void *container = p->storagePointer();
++ for (qsizetype i = 0; i < num; ++i)
++ m.addValueAtEnd(container, data);
++ }
++
++ static void replaceInline(Heap::Sequence *p, qsizetype index, const QVariant &item)
++ {
++ convertAndDo(item, p->valueMetaType(), [p, index](const void *data) {
++ p->metaSequence().setValueAtIndex(p->storagePointer(), index, data);
++ });
++ }
++
++ static void removeLastInline(Heap::Sequence *p, qsizetype num)
++ {
++ const QMetaSequence m = p->metaSequence();
++ void *container = p->storagePointer();
++
++ if (m.canEraseRangeAtIterator() && m.hasRandomAccessIterator() && num > 1) {
++ void *i = m.end(container);
++ m.advanceIterator(i, -num);
++ void *j = m.end(container);
++ m.eraseRangeAtIterator(container, i, j);
++ m.destroyIterator(i);
++ m.destroyIterator(j);
++ } else {
++ for (int i = 0; i < num; ++i)
++ m.removeValueAtEnd(container);
++ }
++ }
++
++ bool Heap::Sequence::loadReference()
++ {
++ Q_ASSERT(isReference());
++ // If locations are enforced we only read once
++ return enforcesLocation() || QV4::ReferenceObject::readReference(this);
++ }
++
++ bool Heap::Sequence::storeReference()
++ {
++ Q_ASSERT(isReference());
++ return isAttachedToProperty() && QV4::ReferenceObject::writeBack(this);
++ }
++
++ ReturnedValue Sequence::virtualGet(const Managed *that, PropertyKey id, const Value *receiver, bool *hasProperty)
++ {
++ Heap::Sequence *p = static_cast<const Sequence *>(that)->d();
++ if (!p->isStoredInline() || !id.isArrayIndex())
++ return ReferenceObject::virtualGet(that, id, receiver, hasProperty);
++
++ const uint arrayIndex = id.asArrayIndex();
++ if (!qIsAtMostSizetypeLimit(arrayIndex)) {
++ generateWarning(that->engine(), QLatin1String("Index out of range during indexed get"));
++ return false;
++ }
++
++ if (p->isReference() && !p->loadReference())
++ return Encode::undefined();
++
++ const qsizetype index = arrayIndex;
++ if (index < sizeInline(p)) {
++ if (hasProperty)
++ *hasProperty = true;
++ return doGetIndexed(p, index);
++ }
++
++ if (hasProperty)
++ *hasProperty = false;
++ return Encode::undefined();
++ }
++
++ qint64 Sequence::virtualGetLength(const Managed *m)
++ {
++ Heap::Sequence *p = static_cast<const Sequence *>(m)->d();
++ if (!p->isStoredInline())
++ return p->m_size;
++ if (p->isReference() && !p->loadReference())
++ return 0;
++ return sizeInline(p);
++ }
++
++ bool Sequence::virtualPut(Managed *that, PropertyKey id, const Value &value, Value *receiver)
++ {
++ if (!id.isArrayIndex())
++ return ReferenceObject::virtualPut(that, id, value, receiver);
++
++ const uint arrayIndex = id.asArrayIndex();
++ Sequence *s = static_cast<Sequence *>(that);
++ Heap::Sequence *p = s->d();
++
++ if (!p->isStoredInline()) {
++ s->arraySet(arrayIndex, value);
++ if (p->m_size <= arrayIndex)
++ p->m_size = arrayIndex + 1;
++ return true;
++ }
++
++ if (!qIsAtMostSizetypeLimit(arrayIndex)) {
++ generateWarning(that->engine(), QLatin1String("Index out of range during indexed set"));
++ return false;
++ }
++
++ if (p->isReadOnly()) {
++ p->internalClass->engine->throwTypeError(
++ QLatin1String("Cannot insert into a readonly container"));
++ return false;
++ }
++
++ if (p->isReference() && !p->loadReference())
++ return false;
++
++ const qsizetype index = arrayIndex;
++ const qsizetype count = sizeInline(p);
++ const QMetaType valueType = p->valueMetaType();
++ const QVariant element = ExecutionEngine::toVariant(value, valueType, false);
++
++ if (index == count) {
++ appendInline(p, element);
++ } else if (index < count) {
++ replaceInline(p, index, element);
++ } else {
++ /* according to ECMA262r3 we need to insert */
++ /* the value at the given index, increasing length to index+1. */
++ appendDefaultConstructedInline(p, index - count);
++ appendInline(p, element);
++ }
++
++ if (p->isReference())
++ p->storeReference();
++ return true;
++ }
++
++ bool Sequence::virtualDeleteProperty(Managed *that, PropertyKey id)
++ {
++ Heap::Sequence *p = static_cast<const Sequence *>(that)->d();
++ if (!p->isStoredInline() || !id.isArrayIndex())
++ return ReferenceObject::virtualDeleteProperty(that, id);
++
++ const uint arrayIndex = id.asArrayIndex();
++ if (!qIsAtMostSizetypeLimit(arrayIndex)) {
++ generateWarning(that->engine(), QLatin1String("Index out of range during indexed delete"));
++ return false;
++ }
++
++ if (p->isReadOnly()) {
++ p->internalClass->engine->throwTypeError(
++ QLatin1String("Cannot delete from a readonly container"));
++ return false;
++ }
++
++ if (p->isReference() && !p->loadReference())
++ return false;
++
++ const qsizetype index = arrayIndex;
++ if (index >= sizeInline(p))
++ return false;
++
++ /* according to ECMA262r3 it should be Undefined, */
++ /* but we cannot, so we insert a default-value instead. */
++ replaceInline(p, index, QVariant());
++
++ if (p->isReference())
++ p->storeReference();
++
++ return true;
++ }
++
++ bool Sequence::virtualIsEqualTo(Managed *that, Managed *other)
++ {
++ if (!other)
++ return false;
++
++ const Sequence *otherS = other->as<Sequence>();
++ if (!otherS)
++ return false;
++
++ const Sequence *s = static_cast<Sequence *>(that);
++ const Heap::Sequence *p = s->d();
++ const Heap::Sequence *otherP = otherS->d();
++
++ const Heap::Object *object = p->object();
++ const Heap::Object *otherObject = otherP->object();
++
++ if (object && otherObject)
++ return object == otherObject && p->property() == otherP->property();
++
++ if (!object && !otherObject)
++ return s == otherS;
++
++ return false;
++ }
++
++ OwnPropertyKeyIterator *Sequence::virtualOwnPropertyKeys(const Object *m, Value *target)
++ {
++ Heap::Sequence *p = static_cast<const Sequence *>(m)->d();
++ if (!p->isStoredInline())
++ return ReferenceObject::virtualOwnPropertyKeys(m, target);
++
++ *target = *m;
++ return new SequenceOwnPropertyKeyIterator;
++ }
++
++ int Sequence::virtualMetacall(Object *object, QMetaObject::Call call, int index, void **a)
++ {
++ Heap::Sequence *p = static_cast<Sequence *>(object)->d();
++ Q_ASSERT(p);
++
++ // We only create attached wrappers if this sequence is stored inline.
++ // When detaching, we re-create everything. Therefore, we can't get a metaCall if
++ // we aren't stored inline.
++ Q_ASSERT(p->isStoredInline());
++
++ switch (call) {
++ case QMetaObject::ReadProperty: {
++ const QMetaType valueType = p->valueMetaType();
++ if (p->isReference() && !p->loadReference())
++ return 0;
++ const QMetaSequence metaSequence = p->metaSequence();
++ if (metaSequence.valueMetaType() != valueType)
++ return 0; // value metatype is not what the caller expects anymore.
++
++ const void *storagePointer = p->storagePointer();
++ if (index < 0 || index >= metaSequence.size(storagePointer))
++ return 0;
++ metaSequence.valueAtIndex(storagePointer, index, a[0]);
++ break;
++ }
++ case QMetaObject::WriteProperty: {
++ if (p->isReadOnly())
++ return 0;
++
++ void *storagePointer = p->storagePointer();
++ const QMetaSequence metaSequence = p->metaSequence();
++ if (index < 0 || index >= metaSequence.size(storagePointer))
++ return 0;
++ metaSequence.setValueAtIndex(storagePointer, index, a[0]);
++ if (p->isReference())
++ p->storeReference();
++ break;
++ }
++ default:
++ return 0; // not supported
++ }
++
++ return -1;
++ }
++
++ QV4::ReturnedValue SequencePrototype::method_getLength(
++ const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ QV4::Scope scope(b);
++ QV4::Scoped<Sequence> This(scope, thisObject->as<Sequence>());
++ if (!This)
++ THROW_TYPE_ERROR();
++
++ Heap::Sequence *p = This->d();
++ if (!p->isStoredInline())
++ RETURN_RESULT(p->m_size);
++
++ if (p->isReference() && !p->loadReference())
++ return Encode::undefined();
++
++ const qsizetype size = sizeInline(p);
++ if (!qIsAtMostUintLimit(size)) {
++ generateWarning(scope.engine, QLatin1String("Sequence length out of range"));
++ RETURN_RESULT(uint(0));
++ }
++
++ RETURN_RESULT(uint(size));
++ }
++
++ QV4::ReturnedValue SequencePrototype::method_setLength(
++ const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ QV4::Scope scope(f);
++ QV4::Scoped<Sequence> This(scope, thisObject->as<Sequence>());
++ if (!This)
++ THROW_TYPE_ERROR();
++
++ Heap::Sequence *p = This->d();
++
++ bool ok = false;
++ const quint32 argv0 = argc ? argv[0].asArrayLength(&ok) : 0;
++ if (!ok) {
++ generateWarning(scope.engine, QLatin1String("Index out of range during length set"));
++ RETURN_UNDEFINED();
++ }
++
++ if (!p->isStoredInline()) {
++ if (argv0 < p->m_size)
++ p->arrayData->vtable()->truncate(This, argv0);
++ p->m_size = argv0;
++ RETURN_UNDEFINED();
++ }
++
++ if (!qIsAtMostSizetypeLimit(argv0)) {
++ generateWarning(scope.engine, QLatin1String("Sequence length out of range"));
++ RETURN_UNDEFINED();
++ }
++
++ if (p->isReadOnly())
++ THROW_TYPE_ERROR();
++
++ const qsizetype newCount = qsizetype(argv0);
++
++ /* Read the sequence from the QObject property if we're a reference */
++ if (p->isReference() && !p->loadReference())
++ RETURN_UNDEFINED();
++
++ /* Determine whether we need to modify the sequence */
++ const qsizetype count = sizeInline(p);
++ if (newCount == count) {
++ RETURN_UNDEFINED();
++ } else if (newCount > count) {
++ /* according to ECMA262r3 we need to insert */
++ /* undefined values increasing length to newLength. */
++ /* We cannot, so we insert default-values instead. */
++ appendDefaultConstructedInline(p, newCount - count);
++ } else {
++ /* according to ECMA262r3 we need to remove */
++ /* elements until the sequence is the required length. */
++ Q_ASSERT(newCount < count);
++ removeLastInline(p, count - newCount);
++ }
++
++ /* write back if required. */
++ if (p->isReference())
++ p->storeReference();
++
++ RETURN_UNDEFINED();
++ }
++
++ void SequencePrototype::init()
++ {
++ defineDefaultProperty(engine()->id_valueOf(), method_valueOf, 0);
++ defineAccessorProperty(QStringLiteral("length"), method_getLength, method_setLength);
++ defineDefaultProperty(QStringLiteral("shift"), method_shift, 0);
++ defineDefaultProperty(QStringLiteral("unshift"), method_unshift, 1);
++ defineDefaultProperty(QStringLiteral("push"), method_push, 1);
++ defineDefaultProperty(QStringLiteral("pop"), method_pop, 0);
++ }
++
++ ReturnedValue SequencePrototype::method_valueOf(
++ const FunctionObject *f, const Value *thisObject, const Value *, int)
++ {
++ return Encode(thisObject->toString(f->engine()));
++ }
++
++ ReturnedValue SequencePrototype::method_shift(
++ const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(b);
++ Scoped<Sequence> s(scope, thisObject);
++ if (!s)
++ return ArrayPrototype::method_shift(b, thisObject, argv, argc);
++
++ Heap::Sequence *p = s->d();
++ if (p->isReadOnly())
++ THROW_TYPE_ERROR();
++
++ if (!p->isStoredInline())
++ return ArrayPrototype::method_shift(b, thisObject, argv, argc);
++
++ if (p->isReference() && !p->loadReference())
++ RETURN_UNDEFINED();
++
++ const qsizetype len = sizeInline(p);
++ if (!len)
++ RETURN_UNDEFINED();
++
++ void *storage = p->storagePointer();
++ Q_ASSERT(storage); // Must readReference() before
++ const QMetaType v = p->valueMetaType();
++ const QMetaSequence m = p->metaSequence();
++
++ QVariant shifted;
++ void *resultData = createVariantData(v, &shifted);
++ m.valueAtIndex(storage, 0, resultData);
++
++ if (m.canRemoveValueAtBegin()) {
++ m.removeValueAtBegin(storage);
++ } else {
++ QVariant t;
++ void *tData = createVariantData(v, &t);
++ for (qsizetype i = 1, end = m.size(storage); i < end; ++i) {
++ m.valueAtIndex(storage, i, tData);
++ m.setValueAtIndex(storage, i - 1, tData);
++ }
++ m.removeValueAtEnd(storage);
++ }
++
++ if (p->isReference())
++ p->storeReference();
++
++ return scope.engine->fromVariant(shifted);
++ }
++
++ ReturnedValue SequencePrototype::method_unshift(
++ const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(f);
++ Scoped<Sequence> s(scope, thisObject);
++ if (!s)
++ return ArrayPrototype::method_unshift(f, thisObject, argv, argc);
++
++ Heap::Sequence *p = s->d();
++ if (p->isReadOnly())
++ THROW_TYPE_ERROR();
++
++ if (!p->isStoredInline())
++ return ArrayPrototype::method_unshift(f, thisObject, argv, argc);
++
++ if (p->isReference() && !p->loadReference())
++ RETURN_UNDEFINED();
++
++ qsizetype size;
++ if (qAddOverflow(sizeInline(p), qsizetype(argc), &size) || !qIsAtMostUintLimit(size)) {
++ generateWarning(scope.engine, QLatin1String("Index out of range during unshift"));
++ RETURN_UNDEFINED();
++ }
++
++ void *storage = p->storagePointer();
++ Q_ASSERT(storage); // Must readReference() before
++ const QMetaType v = p->valueMetaType();
++ const QMetaSequence m = p->metaSequence();
++
++ if (m.canAddValueAtBegin()) {
++ for (int i = argc - 1; i >= 0; --i) {
++ const QVariant item = scope.engine->toVariant(argv[i], p->valueMetaType(), false);
++ m.addValueAtBegin(storage, retrieveVariantData(v, &item));
++ }
++ } else {
++ QVariant t;
++ void *tData = createVariantData(v, &t);
++
++ const qsizetype oldSize = m.size(storage);
++
++ // Resize array by appending values to the end
++ for (qsizetype i = argc; i > 0; --i) {
++ if (i < oldSize)
++ m.valueAtIndex(storage, oldSize - i, tData);
++ m.addValueAtEnd(storage, tData);
++ }
++
++ // Move other existing values into now vacant storage
++ for (qsizetype i = oldSize - argc; i >= 0; --i) {
++ m.valueAtIndex(storage, i, tData);
++ m.setValueAtIndex(storage, i + argc, tData);
++ }
++
++ // Insert new values into vacant storage at front
++ for (qsizetype i = 0; i < argc; ++i) {
++ const QVariant item = scope.engine->toVariant(argv[i], p->valueMetaType(), false);
++ m.setValueAtIndex(storage, i, retrieveVariantData(v, &item));
++ }
++ }
++
++ if (p->isReference())
++ p->storeReference();
++ return Encode(uint(size));
++ }
++
++ ReturnedValue SequencePrototype::method_push(
++ const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(f);
++ Scoped<Sequence> s(scope, thisObject);
++ if (!s)
++ return ArrayPrototype::method_push(f, thisObject, argv, argc);
++
++ Heap::Sequence *p = s->d();
++ if (p->isReadOnly())
++ THROW_TYPE_ERROR();
++
++ if (!p->isStoredInline())
++ return ArrayPrototype::method_push(f, thisObject, argv, argc);
++
++ if (p->isReference() && !p->loadReference())
++ RETURN_UNDEFINED();
++
++ qsizetype size;
++ if (qAddOverflow(sizeInline(p), qsizetype(argc), &size) || !qIsAtMostUintLimit(size)) {
++ generateWarning(scope.engine, QLatin1String("Index out of range during push"));
++ RETURN_UNDEFINED();
++ }
++
++ for (int i = 0; i < argc; ++i)
++ appendInline(p, scope.engine->toVariant(argv[i], p->valueMetaType(), false));
++
++ if (p->isReference())
++ p->storeReference();
++ return Encode(uint(size));
++ }
++
++ ReturnedValue SequencePrototype::method_pop(
++ const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(f);
++ Scoped<Sequence> s(scope, thisObject);
++ if (!s)
++ return ArrayPrototype::method_pop(f, thisObject, argv, argc);
++
++ Heap::Sequence *p = s->d();
++ if (p->isReadOnly())
++ THROW_TYPE_ERROR();
++
++ if (!p->isStoredInline())
++ return ArrayPrototype::method_pop(f, thisObject, argv, argc);
++
++ if (p->isReference() && !p->loadReference())
++ RETURN_UNDEFINED();
++
++ const qsizetype len = sizeInline(p);
++ if (!len)
++ RETURN_UNDEFINED();
++
++ ScopedValue result(scope, doGetIndexed(p, len - 1));
++ removeLastInline(p, 1);
++
++ if (p->isReference())
++ p->storeReference();
++ return result->asReturnedValue();
++ }
++
++ ReturnedValue SequencePrototype::newSequence(
++ QV4::ExecutionEngine *engine, QMetaType type, QMetaSequence metaSequence, const void *data,
++ Heap::Object *object, int propertyIndex, Heap::ReferenceObject::Flags flags)
++ {
++ // This function is called when the property is a QObject Q_PROPERTY of
++ // the given sequence type. Internally we store a sequence
++ // (as well as object ptr + property index for updated-read and write-back)
++ // and so access/mutate avoids variant conversion.
++
++ return engine->memoryManager->allocate<Sequence>(
++ type, metaSequence, data, object, propertyIndex, flags)->asReturnedValue();
++ }
++
++ ReturnedValue SequencePrototype::fromVariant(QV4::ExecutionEngine *engine, const QVariant &v)
++ {
++ const QMetaType type = v.metaType();
++ const QQmlType qmlType = QQmlMetaType::qmlListType(type);
++ if (qmlType.isSequentialContainer())
++ return fromData(engine, type, qmlType.listMetaSequence(), v.constData());
++
++ QMetaSequence::Iterable iterable;
++ if (QMetaType::convert(
++ type, v.constData(), QMetaType::fromType<QMetaSequence::Iterable>(), &iterable)) {
++ return fromData(engine, type, iterable.metaContainer(), v.constData());
++ }
++
++ return Encode::undefined();
++ }
++
++ ReturnedValue SequencePrototype::fromData(
++ ExecutionEngine *engine, QMetaType type, QMetaSequence metaSequence, const void *data)
++ {
++ // This function is called when assigning a sequence value to a normal JS var
++ // in a JS block. Internally, we store a sequence of the specified type.
++ // Access and mutation is extremely fast since it will not need to modify any
++ // QObject property.
++
++ return engine->memoryManager->allocate<Sequence>(type, metaSequence, data)->asReturnedValue();
++ }
++
++ /*!
++ * \internal
++ *
++ * Produce a QVariant containing a copy of the list stored in \a object.
++ */
++ QVariant SequencePrototype::toVariant(const Sequence *object)
++ {
++ Q_ASSERT(object->isV4SequenceType());
++ Heap::Sequence *p = object->d();
++
++ if (p->isStoredInline()) {
++ // Note: For historical reasons, we ignore the result of loadReference()
++ // here. This allows us to retain sequences whose objects have vaninshed
++ // as "var" properties. It comes at the price of potentially returning
++ // outdated data. This is the behavior sequences have always shown.
++ if (p->isReference())
++ p->loadReference();
++
++ if (const void *storage = p->m_container)
++ return QVariant(p->listType(), storage);
++
++ return QVariant();
++ }
++
++ QV4::Scope scope(p->internalClass->engine);
++ QV4::ScopedObject q(scope, p);
++ return toVariant(q, p->listType());
++ }
++
++ bool convertToIterable(QMetaType metaType, void *data, QV4::Object *sequence)
++ {
++ QMetaSequence::Iterable iterable;
++ if (!QMetaType::view(metaType, data, QMetaType::fromType<QMetaSequence::Iterable>(), &iterable))
++ return false;
++
++ const QMetaType elementMetaType = iterable.metaContainer().valueMetaType();
++ QV4::Scope scope(sequence->engine());
++ QV4::ScopedValue v(scope);
++ for (qsizetype i = 0, end = sequence->getLength(); i < end; ++i) {
++ QVariant element(elementMetaType);
++ v = sequence->get(i);
++ ExecutionEngine::metaTypeFromJS(v, elementMetaType, element.data());
++ iterable.append(element);
++ }
++ return true;
++ }
++
++ /*!
++ * \internal
++ *
++ * Convert the Object \a array to \a targetType. If \a targetType is not a sequential container,
++ * or if \a array is not an Object, return an invalid QVariant. Otherwise return a QVariant of
++ * \a targetType with the converted data. It is assumed that this actual requires a conversion. If
++ * the conversion is not needed, the single-argument toVariant() is faster.
++ */
++ QVariant SequencePrototype::toVariant(const QV4::Value &array, QMetaType targetType)
++ {
++ if (!array.as<Object>())
++ return QVariant();
++
++ QMetaSequence meta;
++ QVariant result(targetType);
++ if (const QQmlType type = QQmlMetaType::qmlListType(targetType);
++ type.isSequentialContainer()) {
++ // If the QML type declares a custom sequential container, use that.
++ meta = type.priv()->extraData.sequentialContainerTypeData;
++ } else if (QMetaSequence::Iterable iterable;
++ QMetaType::view(
++ targetType, result.data(),
++ QMetaType::fromType<QMetaSequence::Iterable>(), &iterable)) {
++ // Otherwise try to convert to QMetaSequence::Iterable via QMetaType conversion.
++ meta = iterable.metaContainer();
++ }
++
++ if (!meta.canAddValue())
++ return QVariant();
++
++ QV4::Scope scope(array.as<Object>()->engine());
++ QV4::ScopedObject a(scope, array);
++
++ const QMetaType valueMetaType = meta.valueMetaType();
++ const qint64 length = a->getLength();
++ Q_ASSERT(length >= 0);
++ Q_ASSERT(length <= qint64(std::numeric_limits<quint32>::max()));
++
++ QV4::ScopedValue element(scope);
++ for (quint32 i = 0; i < quint32(length); ++i) {
++ QVariant variant;
++ element = a->get(i);
++
++ // Note: We can convert to any sequence type here, even those that don't have a specified
++ // order. Therefore the meta.addValue() below. meta.addValue() preferably adds to the
++ // end, but will also add in other places if that's not possible.
++
++ // If the target type is QVariant itself, let the conversion produce any interanl type.
++ if (valueMetaType == QMetaType::fromType<QVariant>()) {
++ variant = ExecutionEngine::toVariant(element, QMetaType(), false);
++ meta.addValue(result.data(), &variant);
++ continue;
++ }
++
++ // Try to convert to the specific type requested ...
++ variant = QVariant(valueMetaType);
++ if (ExecutionEngine::metaTypeFromJS(element, valueMetaType, variant.data())) {
++ meta.addValue(result.data(), variant.constData());
++ continue;
++ }
++
++ // If that doesn't work, produce any QVariant and try to convert it afterwards.
++ // toVariant() is free to ignore the type hint and can produce the "original" type.
++ variant = ExecutionEngine::toVariant(element, valueMetaType, false);
++ const QMetaType originalType = variant.metaType();
++
++ // Try value type constructors.
++ const QVariant converted = QQmlValueTypeProvider::createValueType(
++ variant, valueMetaType, scope.engine);
++ if (converted.isValid()) {
++ meta.addValue(result.data(), converted.constData());
++ continue;
++ }
++
++ const auto warn = [&](QLatin1String base) {
++ // If the original type was void, we're converting a "hole" in a sparse
++ // array. There is no point in warning about that.
++ if (!originalType.isValid())
++ return;
++
++ qCWarning(lcListValueConversion).noquote()
++ << base.arg(QString::number(i), QString::fromUtf8(originalType.name()),
++ QString::fromUtf8(valueMetaType.name()));
++ };
++
++ // Note: Ideally you should use constructible value types for everything below, but we'd
++ // probably get a lot of pushback for warning about that.
++
++ // Before attempting a conversion from the concrete types, check if there exists a
++ // conversion from QJSValue to the result type. Prefer that one for compatibility reasons.
++ // This is a rather surprising "feature". Therefore, warn if a concrete conversion wouldn't
++ // be possible. You should at least make your type conversions consistent.
++ if (QMetaType::canConvert(QMetaType::fromType<QJSValue>(), valueMetaType)) {
++ QVariant wrappedJsValue = QVariant::fromValue(QJSValuePrivate::fromReturnedValue(
++ element->asReturnedValue()));
++ if (wrappedJsValue.convert(valueMetaType)) {
++ if (!QMetaType::canConvert(originalType, valueMetaType)) {
++ warn(QLatin1String("Converting array value at position %1 from %2 to %3 via "
++ "QJSValue even though they are not directly convertible"));
++ }
++ meta.addValue(result.data(), wrappedJsValue.constData());
++ continue;
++ }
++ }
++
++ // Last ditch effort: Try QVariant::convert()
++ if (!variant.convert(valueMetaType))
++ warn(QLatin1String("Could not convert array value at position %1 from %2 to %3"));
++
++ meta.addValue(result.data(), variant.constData());
++ }
++ return result;
++
++ }
++
++ static Heap::Sequence *resolveHeapSequence(const Sequence *sequence, QMetaType typeHint)
++ {
++ if (!sequence)
++ return nullptr;
++ Heap::Sequence *p = sequence->d();
++ if (p->listType() != typeHint)
++ return nullptr;
++ return p;
++ }
++
++ void *SequencePrototype::rawContainerPtr(const Sequence *sequence, QMetaType typeHint)
++ {
++ Heap::Sequence *p = resolveHeapSequence(sequence, typeHint);
++ if (!p || !p->isStoredInline())
++ return nullptr;
++
++ return p->storagePointer();
++ }
++
++ SequencePrototype::RawCopyResult SequencePrototype::setRawContainer(
++ Sequence *sequence, const void *container, QMetaType typeHint)
++ {
++ Heap::Sequence *p = resolveHeapSequence(sequence, typeHint);
++ if (!p)
++ return TypeMismatch;
++
++ if (p->isStoredInline()) {
++ if (typeHint.equals(p->m_container, container))
++ return WasEqual;
++ typeHint.destruct(p->m_container);
++ typeHint.construct(p->storagePointer(), container);
++ return Copied;
++ }
++
++ p->createElementWrappers(container);
++ return Copied;
++ }
++
++ SequencePrototype::RawCopyResult SequencePrototype::getRawContainer(
++ const Sequence *sequence, void *container, QMetaType typeHint)
++ {
++ Heap::Sequence *p = resolveHeapSequence(sequence, typeHint);
++ if (!p)
++ return TypeMismatch;
++
++ if (p->isStoredInline()) {
++ if (typeHint.equals(p->m_container, container))
++ return WasEqual;
++ typeHint.destruct(container);
++ typeHint.construct(container, p->m_container);
++ return Copied;
++ }
++
++ typeHint.destruct(container);
++ typeHint.construct(container);
++
++ const QMetaSequence metaSequence = p->metaSequence();
++ QV4::Scope scope(p->internalClass->engine);
++ QV4::ScopedValue val(scope);
++ const QMetaType metaType = p->valueMetaType();
++ const qsizetype size = p->m_size;
++
++ Q_ASSERT(qIsAtMostUintLimit(size));
++
++ if (metaType == QMetaType::fromType<QVariant>()) {
++ for (qsizetype i = 0; i < size; ++i) {
++ val = sequence->get(PropertyKey::fromArrayIndex(i));
++ QVariant var = ExecutionEngine::toVariant(val, QMetaType(), false);
++ metaSequence.addValueAtEnd(container, &var);
++ }
++ return Copied;
++ }
++
++ QVariant var(metaType);
++ for (qsizetype i = 0; i < size; ++i) {
++ val = sequence->get(PropertyKey::fromArrayIndex(i));
++ scope.engine->metaTypeFromJS(val, metaType, var.data());
++ metaSequence.addValueAtEnd(container, var.data());
++ }
++ return Copied;
++ }
++
++ QMetaType SequencePrototype::metaTypeForSequence(const Sequence *object)
++ {
++ return object->d()->listType();
++ }
++
++ } // namespace QV4
++
++ QT_END_NAMESPACE
++
++ #include "moc_qv4sequenceobject_p.cpp"