diff options
Diffstat (limited to 'src/qml/jsruntime/qv4sequenceobject.cpp')
| -rw-r--r-- | src/qml/jsruntime/qv4sequenceobject.cpp | 269 |
1 files changed, 233 insertions, 36 deletions
diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp index bf7c3d7c59..23a6bb5e23 100644 --- a/src/qml/jsruntime/qv4sequenceobject.cpp +++ b/src/qml/jsruntime/qv4sequenceobject.cpp @@ -16,6 +16,42 @@ 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 { @@ -24,6 +60,7 @@ 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(); @@ -80,6 +117,8 @@ static void generateWarning(QV4::ExecutionEngine *v4, const QString& description static qsizetype sizeInline(const Heap::Sequence *p) { + Q_ASSERT(p->isStoredInline()); + if (const void *container = p->storagePointer()) return p->metaSequence().size(container); @@ -95,6 +134,7 @@ struct SequenceOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator 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(); @@ -132,7 +172,10 @@ void Heap::Sequence::init(QMetaType listType, QMetaSequence metaSequence, const { ReferenceObject::init(nullptr, -1, NoFlag); initTypes(listType, metaSequence); - createInlineStorage(container); + if (isStoredInline()) + createInlineStorage(container); + else + createElementWrappers(container); } void Heap::Sequence::init( @@ -142,15 +185,21 @@ void Heap::Sequence::init( ReferenceObject::init(object, propertyIndex, flags | IsDirty); initTypes(listType, metaSequence); - if (CppStackFrame *frame = internalClass->engine->currentStackFrame) - setLocation(frame->v4Function, frame->statementNumber()); - createInlineStorage(container); - if (!container && (flags & EnforcesLocation)) - QV4::ReferenceObject::readReference(this); + 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); @@ -158,21 +207,88 @@ void Heap::Sequence::createInlineStorage(const void *container) m_container = listType().create(container); } -Heap::Sequence *Heap::Sequence::detached() const +void Heap::Sequence::createElementWrappers(const void *container) { - return internalClass->engine->memoryManager->allocate<QV4::Sequence>( - QMetaType(m_listType), QMetaSequence(m_metaSequence), m_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 (m_container) + 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; @@ -180,6 +296,9 @@ void *Heap::Sequence::storagePointer() 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 @@ -208,6 +327,9 @@ bool Heap::Sequence::setVariant(const QVariant &variant) } QVariant Heap::Sequence::toVariant() const { + // Should only happen from readReference(). Therefore we are attached. + Q_ASSERT(isStoredInline()); + return QVariant(listType(), m_container); } @@ -283,7 +405,8 @@ bool Heap::Sequence::storeReference() ReturnedValue Sequence::virtualGet(const Managed *that, PropertyKey id, const Value *receiver, bool *hasProperty) { - if (!id.isArrayIndex()) + 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(); @@ -292,8 +415,6 @@ ReturnedValue Sequence::virtualGet(const Managed *that, PropertyKey id, const Va return false; } - Heap::Sequence *p = static_cast<const Sequence *>(that)->d(); - if (p->isReference() && !p->loadReference()) return Encode::undefined(); @@ -312,6 +433,8 @@ ReturnedValue Sequence::virtualGet(const Managed *that, PropertyKey id, const Va 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); @@ -323,13 +446,21 @@ bool Sequence::virtualPut(Managed *that, PropertyKey id, const Value &value, Val 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; } - Heap::Sequence *p = static_cast<Sequence *>(that)->d(); - if (p->isReadOnly()) { p->internalClass->engine->throwTypeError( QLatin1String("Cannot insert into a readonly container")); @@ -362,7 +493,8 @@ bool Sequence::virtualPut(Managed *that, PropertyKey id, const Value &value, Val bool Sequence::virtualDeleteProperty(Managed *that, PropertyKey id) { - if (!id.isArrayIndex()) + Heap::Sequence *p = static_cast<const Sequence *>(that)->d(); + if (!p->isStoredInline() || !id.isArrayIndex()) return ReferenceObject::virtualDeleteProperty(that, id); const uint arrayIndex = id.asArrayIndex(); @@ -371,8 +503,6 @@ bool Sequence::virtualDeleteProperty(Managed *that, PropertyKey id) return false; } - Heap::Sequence *p = static_cast<Sequence *>(that)->d(); - if (p->isReadOnly()) { p->internalClass->engine->throwTypeError( QLatin1String("Cannot delete from a readonly container")); @@ -423,6 +553,10 @@ bool Sequence::virtualIsEqualTo(Managed *that, Managed *other) 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; } @@ -432,6 +566,11 @@ int Sequence::virtualMetacall(Object *object, QMetaObject::Call call, int index, 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(); @@ -476,6 +615,8 @@ QV4::ReturnedValue SequencePrototype::method_getLength( THROW_TYPE_ERROR(); Heap::Sequence *p = This->d(); + if (!p->isStoredInline()) + RETURN_RESULT(p->m_size); if (p->isReference() && !p->loadReference()) return Encode::undefined(); @@ -501,11 +642,23 @@ QV4::ReturnedValue SequencePrototype::method_setLength( bool ok = false; const quint32 argv0 = argc ? argv[0].asArrayLength(&ok) : 0; - if (!ok || !qIsAtMostSizetypeLimit(argv0)) { + 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(); @@ -562,6 +715,9 @@ ReturnedValue SequencePrototype::method_shift( if (p->isReadOnly()) THROW_TYPE_ERROR(); + if (!p->isStoredInline()) + return ArrayPrototype::method_shift(b, thisObject, argv, argc); + if (p->isReference() && !p->loadReference()) RETURN_UNDEFINED(); @@ -646,17 +802,23 @@ QVariant SequencePrototype::toVariant(const Sequence *object) Q_ASSERT(object->isV4SequenceType()); Heap::Sequence *p = object->d(); - // 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 (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); + if (const void *storage = p->m_container) + return QVariant(p->listType(), storage); - return QVariant(); + 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) @@ -804,7 +966,7 @@ static Heap::Sequence *resolveHeapSequence(const Sequence *sequence, QMetaType t void *SequencePrototype::rawContainerPtr(const Sequence *sequence, QMetaType typeHint) { Heap::Sequence *p = resolveHeapSequence(sequence, typeHint); - if (!p) + if (!p || !p->isStoredInline()) return nullptr; return p->storagePointer(); @@ -817,10 +979,15 @@ SequencePrototype::RawCopyResult SequencePrototype::setRawContainer( if (!p) return TypeMismatch; - if (typeHint.equals(p->m_container, container)) - return WasEqual; - typeHint.destruct(p->m_container); - typeHint.construct(p->storagePointer(), container); + 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; } @@ -831,10 +998,40 @@ SequencePrototype::RawCopyResult SequencePrototype::getRawContainer( if (!p) return TypeMismatch; - if (typeHint.equals(p->m_container, container)) - return WasEqual; + 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, p->m_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; } |
