diff options
author | Fabian Kosmale <fabian.kosmale@qt.io> | 2019-12-09 15:26:14 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2019-12-20 10:51:21 +0100 |
commit | ea3bfc91e2a0fc8309f75e960f1891f15d7351c9 (patch) | |
tree | ff66f2071c8c14f3b298660c37be21f832f7a867 /src/qml/jsruntime/qv4engine.cpp | |
parent | 155a2e0d8d1d9c07fa22f523d84e3d583697aff4 (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.cpp | 89 |
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); |