diff options
-rw-r--r-- | src/qml/jsruntime/qv4engine.cpp | 89 | ||||
-rw-r--r-- | tests/auto/qml/qjsvalue/tst_qjsvalue.cpp | 64 | ||||
-rw-r--r-- | tests/auto/qml/qjsvalue/tst_qjsvalue.h | 2 |
3 files changed, 155 insertions, 0 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index db3ca97780..885f06a3e3 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -43,6 +43,7 @@ #include <private/qqmljsdiagnosticmessage_p.h> #include <QtCore/QTextStream> +#include <QtCore/private/qvariant_p.h> #include <QDateTime> #include <QDir> #include <QFileInfo> @@ -118,6 +119,7 @@ #endif #include <private/qv4sqlerrors_p.h> #include <qqmlfile.h> +#include <qmetatype.h> #if USE(PTHREADS) # include <pthread.h> @@ -185,6 +187,91 @@ static void restoreJSValue(QDataStream &stream, void *data) } } +struct JSArrayIterator { + QJSValue const* data; + quint32 index; +}; + +namespace { +void createNewIteratorIfNonExisting(void **iterator) { + if (*iterator == nullptr) + *iterator = new JSArrayIterator; +} +} + +static QtMetaTypePrivate::QSequentialIterableImpl jsvalueToSequence (const QJSValue& value) { + using namespace QtMetaTypePrivate; + + QSequentialIterableImpl iterator {}; + if (!value.isArray()) { + // set up some functions so that non-array QSequentialIterables do not crash + // but instead appear as an empty sequence + iterator._size = [](const void *) {return 0;}; + iterator._moveToBegin = [](const void *, void **) {}; + iterator._moveToEnd = [](const void *, void **) {}; + iterator._advance = [](void **, int) {}; + iterator._equalIter = [](void * const *, void * const *){return true; /*all iterators are nullptr*/}; + iterator._destroyIter = [](void **){}; + return iterator; + } + + iterator._iterable = &value; + iterator._iterator = nullptr; + iterator._metaType_id = qMetaTypeId<QVariant>(); + iterator._metaType_flags = QVariantConstructionFlags::ShouldDeleteVariantData; + iterator._iteratorCapabilities = RandomAccessCapability | BiDirectionalCapability | ForwardCapability; + iterator._size = [](const void *p) -> int { + return static_cast<QJSValue const *>(p)->property(QString::fromLatin1("length")).toInt(); + }; + /* Lifetime management notes: + * _at and _get return a pointer to a JSValue allocated via QMetaType::create + * Because we set QVariantConstructionFlags::ShouldDeleteVariantData, QSequentialIterable::at + * and QSequentialIterable::operator*() will free that memory + */ + + iterator._at = [](const void *iterable, int index) -> void const * { + auto const value = static_cast<QJSValue const *>(iterable)->property(quint32(index)).toVariant(); + return QMetaType::create(qMetaTypeId<QVariant>(), &value); + }; + iterator._moveToBegin = [](const void *iterable, void **iterator) { + createNewIteratorIfNonExisting(iterator); + auto jsArrayIterator = static_cast<JSArrayIterator *>(*iterator); + jsArrayIterator->index = 0; + jsArrayIterator->data = reinterpret_cast<QJSValue const*>(iterable); + }; + iterator._moveToEnd = [](const void *iterable, void **iterator) { + createNewIteratorIfNonExisting(iterator); + auto jsArrayIterator = static_cast<JSArrayIterator *>(*iterator); + auto length = static_cast<QJSValue const *>(iterable)->property(QString::fromLatin1("length")).toInt(); + jsArrayIterator->data = reinterpret_cast<QJSValue const*>(iterable); + jsArrayIterator->index = quint32(length); + }; + iterator._advance = [](void **iterator, int advanceBy) { + static_cast<JSArrayIterator *>(*iterator)->index += quint32(advanceBy); + }; + iterator._get = []( void * const *iterator, int metaTypeId, uint flags) -> VariantData { + auto const * const arrayIterator = static_cast<const JSArrayIterator *>(*iterator); + QJSValue const * const jsArray = arrayIterator->data; + auto const value = jsArray->property(arrayIterator->index).toVariant(); + Q_ASSERT(flags & QVariantConstructionFlags::ShouldDeleteVariantData); + return {metaTypeId, QMetaType::create(qMetaTypeId<QVariant>(), &value), flags}; + }; + iterator._destroyIter = [](void **iterator) { + delete static_cast<JSArrayIterator *>(*iterator); + }; + iterator._equalIter = [](void * const *p, void * const *other) { + auto this_ = static_cast<const JSArrayIterator *>(*p); + auto that_ = static_cast<const JSArrayIterator *>(*other); + return this_->index == that_->index && this_->data == that_->data; + }; + iterator._copyIter = [](void **iterator, void * const * otherIterator) { + auto *otherIter = (static_cast<JSArrayIterator const *>(*otherIterator)); + static_cast<JSArrayIterator *>(*iterator)->index = otherIter->index; + static_cast<JSArrayIterator *>(*iterator)->data = otherIter->data; + }; + return iterator; +} + ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) : executableAllocator(new QV4::ExecutableAllocator) , regExpAllocator(new QV4::ExecutableAllocator) @@ -708,6 +795,8 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) QMetaType::registerConverter<QJSValue, QVariantList>(convertJSValueToVariantType<QVariantList>); if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QStringList>()) QMetaType::registerConverter<QJSValue, QStringList>(convertJSValueToVariantType<QStringList>); + if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QtMetaTypePrivate::QSequentialIterableImpl>()) + QMetaType::registerConverter<QJSValue, QtMetaTypePrivate::QSequentialIterableImpl>(jsvalueToSequence); QMetaType::registerStreamOperators(qMetaTypeId<QJSValue>(), saveJSValue, restoreJSValue); QV4::QObjectWrapper::initializeBindings(this); diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp index 37d0ea4dea..95f554776f 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp @@ -2708,6 +2708,70 @@ void tst_QJSValue::nestedObjectToVariant() QCOMPARE(o.toVariant(), expected); } +static int instanceCount = 0; + +struct MyType +{ + MyType(int n = 0, const char *t=nullptr): number(n), text(t) + { + ++instanceCount; + } + MyType(const MyType &other) + : number(other.number), text(other.text) + { + ++instanceCount; + } + ~MyType() + { + --instanceCount; + } + int number; + const char *text; +}; + +Q_DECLARE_METATYPE(MyType) +Q_DECLARE_METATYPE(MyType*) + +void tst_QJSValue::jsvalueArrayToSequenceType() +{ + QCOMPARE(instanceCount, 0); + { + QJSEngine eng {}; + auto testObject = eng.newObject(); + testObject.setProperty("test", 42); + testObject.setProperty("mytypeobject", eng.toScriptValue(QVariant::fromValue(MyType {42, "hello"}))); + auto array = eng.newArray(4); + array.setProperty(0, QLatin1String("Hello World")); + array.setProperty(1, 42); + array.setProperty(2, QJSValue(QJSValue::UndefinedValue)); + array.setProperty(3, testObject); + auto asVariant = QVariant::fromValue(array); + QVERIFY(asVariant.canConvert<QVariantList>()); + auto asIterable = asVariant.value<QSequentialIterable>(); + for (auto it = asIterable.begin(); it != asIterable.end(); ++it) { + Q_UNUSED(*it) + } + int i = 0; + for (QVariant myVariant: asIterable) { + QCOMPARE(myVariant.isValid(), i != 2); + ++i; + } + QVERIFY(asIterable.at(2).value<QVariant>().isNull()); + QCOMPARE(asIterable.at(3).value<QVariantMap>().find("mytypeobject")->value<MyType>().number, 42); + QCOMPARE(asIterable.at(0).value<QVariant>().toString(), QLatin1String("Hello World")); + auto it1 = asIterable.begin(); + auto it2 = asIterable.begin(); + QCOMPARE((*it1).value<QVariant>().toString(), (*it2).value<QVariant>().toString()); + QCOMPARE((*it1).value<QVariant>().toString(), QLatin1String("Hello World")); + ++it2; + QCOMPARE((*it1).value<QVariant>().toString(), QLatin1String("Hello World")); + QCOMPARE((*it2).value<QVariant>().toInt(), 42); + } + // tests need to be done after engine has been destroyed, else it will hold a reference until + // the gc decides to collect it + QCOMPARE(instanceCount, 0); +} + void tst_QJSValue::deleteFromDifferentThread() { #if !QT_CONFIG(thread) diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.h b/tests/auto/qml/qjsvalue/tst_qjsvalue.h index f704169d43..d85b9a0552 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.h +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.h @@ -142,6 +142,8 @@ private slots: void nestedObjectToVariant_data(); void nestedObjectToVariant(); + void jsvalueArrayToSequenceType(); + void deleteFromDifferentThread(); private: |