aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime/qv4engine.cpp
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2019-12-09 15:26:14 +0100
committerUlf Hermann <ulf.hermann@qt.io>2019-12-20 10:51:21 +0100
commitea3bfc91e2a0fc8309f75e960f1891f15d7351c9 (patch)
treeff66f2071c8c14f3b298660c37be21f832f7a867 /src/qml/jsruntime/qv4engine.cpp
parent155a2e0d8d1d9c07fa22f523d84e3d583697aff4 (diff)
QV4Engine: support conversion of QJSValue to SequenceType
8704c640946ac852668638e2980d3e2b78aa27ae introduced new conversions via sequentialIterableToJS. Due to that, QVariant properties which formerly stored e.g. std::vector<QObject*> now would store a QJSValue. Those would still claim to support a conversion to QVariantList, but -contrary to what our documentation says-, we were not able to do a conversion to QSequentialIterable. The default constructed QSequentialIterable would then crash when calling begin(), as that function pointer was null. This patch fixes this by adding the necessary support to convert a QJSValue containing an array. Non-array QJSValues will still return an "empty" QSequentialIterable. Note that this changes what happens when a QJSValue is converted to a QVariantList, as QVariantValueHelperInterface<QVariantList> will check first if there is a converter to QSequentialIterableImpl before attempting to call any directly installed converter to QVariantList. In order to not change the existing behavior, the QSequentialIterable returns the QVariant corresponding to the QJSValue at a given array position, intead of a QVariant containing the QJSValue. Fixes: QTBUG-80609 Change-Id: I8101229c0d2043b3f2d618ed035b279844802dd8 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src/qml/jsruntime/qv4engine.cpp')
-rw-r--r--src/qml/jsruntime/qv4engine.cpp89
1 files changed, 89 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);