diff options
Diffstat (limited to 'src/qmlworkerscript/qv4serialize.cpp')
-rw-r--r-- | src/qmlworkerscript/qv4serialize.cpp | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/src/qmlworkerscript/qv4serialize.cpp b/src/qmlworkerscript/qv4serialize.cpp new file mode 100644 index 0000000000..cacaf1b0dd --- /dev/null +++ b/src/qmlworkerscript/qv4serialize.cpp @@ -0,0 +1,420 @@ +// 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 + +#include "qv4serialize_p.h" + +#include <private/qv4dateobject_p.h> +#include <private/qv4objectproto_p.h> +#include <private/qv4qobjectwrapper_p.h> +#include <private/qv4regexp_p.h> +#include <private/qv4regexpobject_p.h> +#include <private/qv4sequenceobject_p.h> +#include <private/qv4value_p.h> + +QT_BEGIN_NAMESPACE + +using namespace QV4; + +// We allow the following JavaScript types to be passed between the main and +// the secondary thread: +// + undefined +// + null +// + Boolean +// + String +// + Function +// + Array +// + "Simple" Objects +// + Number +// + Date +// + RegExp +// <quint8 type><quint24 size><data> + +enum Type { + WorkerUndefined, + WorkerNull, + WorkerTrue, + WorkerFalse, + WorkerString, + WorkerFunction, + WorkerArray, + WorkerObject, + WorkerInt32, + WorkerUint32, + WorkerNumber, + WorkerDate, + WorkerRegexp, + WorkerListModel, + WorkerUrl, + WorkerSequence +}; + +static inline quint32 valueheader(Type type, quint32 size = 0) +{ + return quint8(type) << 24 | (size & 0xFFFFFF); +} + +static inline Type headertype(quint32 header) +{ + return (Type)(header >> 24); +} + +static inline quint32 headersize(quint32 header) +{ + return header & 0xFFFFFF; +} + +static inline void push(QByteArray &data, quint32 value) +{ + data.append((const char *)&value, sizeof(quint32)); +} + +static inline void push(QByteArray &data, double value) +{ + data.append((const char *)&value, sizeof(double)); +} + +static inline void push(QByteArray &data, void *ptr) +{ + data.append((const char *)&ptr, sizeof(void *)); +} + +static inline void reserve(QByteArray &data, int extra) +{ + data.reserve(data.size() + extra); +} + +static inline quint32 popUint32(const char *&data) +{ + quint32 rv = *((const quint32 *)data); + data += sizeof(quint32); + return rv; +} + +static inline double popDouble(const char *&data) +{ + double rv = *((const double *)data); + data += sizeof(double); + return rv; +} + +static inline void *popPtr(const char *&data) +{ + void *rv = *((void *const *)data); + data += sizeof(void *); + return rv; +} + +#define ALIGN(size) (((size) + 3) & ~3) +static inline void serializeString(QByteArray &data, const QString &str, Type type) +{ + int length = str.size(); + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + int utf16size = ALIGN(length * sizeof(quint16)); + + reserve(data, utf16size + sizeof(quint32)); + push(data, valueheader(type, length)); + + int offset = data.size(); + data.resize(data.size() + utf16size); + char *buffer = data.data() + offset; + + memcpy(buffer, str.constData(), length*sizeof(QChar)); +} + +// XXX TODO: Check that worker script is exception safe in the case of +// serialization/deserialization failures + +void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine *engine) +{ + QV4::Scope scope(engine); + + if (v.isEmpty()) { + Q_ASSERT(!"Serialize: got empty value"); + } else if (v.isUndefined()) { + push(data, valueheader(WorkerUndefined)); + } else if (v.isNull()) { + push(data, valueheader(WorkerNull)); + } else if (v.isBoolean()) { + push(data, valueheader(v.booleanValue() == true ? WorkerTrue : WorkerFalse)); + } else if (v.isString()) { + serializeString(data, v.toQString(), WorkerString); + } else if (v.as<FunctionObject>()) { + // XXX TODO: Implement passing function objects between the main and + // worker scripts + push(data, valueheader(WorkerUndefined)); + } else if (const QV4::ArrayObject *array = v.as<ArrayObject>()) { + uint length = array->getLength(); + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + reserve(data, sizeof(quint32) + length * sizeof(quint32)); + push(data, valueheader(WorkerArray, length)); + ScopedValue val(scope); + for (uint ii = 0; ii < length; ++ii) + serialize(data, (val = array->get(ii)), engine); + } else if (v.isInteger()) { + reserve(data, 2 * sizeof(quint32)); + push(data, valueheader(WorkerInt32)); + push(data, (quint32)v.integerValue()); +// } else if (v.IsUint32()) { +// reserve(data, 2 * sizeof(quint32)); +// push(data, valueheader(WorkerUint32)); +// push(data, v.Uint32Value()); + } else if (v.isNumber()) { + reserve(data, sizeof(quint32) + sizeof(double)); + push(data, valueheader(WorkerNumber)); + push(data, v.asDouble()); + } else if (const QV4::DateObject *d = v.as<DateObject>()) { + reserve(data, sizeof(quint32) + sizeof(double)); + push(data, valueheader(WorkerDate)); + push(data, d->date()); + } else if (const RegExpObject *re = v.as<RegExpObject>()) { + quint32 flags = re->flags(); + QString pattern = re->source(); + int length = pattern.size() + 1; + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + int utf16size = ALIGN(length * sizeof(quint16)); + + reserve(data, sizeof(quint32) + utf16size); + push(data, valueheader(WorkerRegexp, flags)); + push(data, (quint32)length); + + int offset = data.size(); + data.resize(data.size() + utf16size); + char *buffer = data.data() + offset; + + memcpy(buffer, pattern.constData(), length*sizeof(QChar)); + } else if (const QObjectWrapper *qobjectWrapper = v.as<QV4::QObjectWrapper>()) { + // XXX TODO: Generalize passing objects between the main thread and worker scripts so + // that others can trivially plug in their elements. + if (QObject *lm = qobjectWrapper->object()) { + if (QObject *agent = qvariant_cast<QObject *>(lm->property("agent"))) { + if (QMetaObject::invokeMethod(agent, "addref")) { + push(data, valueheader(WorkerListModel)); + push(data, (void *)agent); + return; + } + } + } + // No other QObject's are allowed to be sent + push(data, valueheader(WorkerUndefined)); + } else if (const Sequence *s = v.as<Sequence>()) { + // valid sequence. we generate a length (sequence length + 1 for the sequence type) + uint seqLength = ScopedValue(scope, s->get(engine->id_length()))->toUInt32(); + uint length = seqLength + 1; + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + reserve(data, sizeof(quint32) + length * sizeof(quint32)); + push(data, valueheader(WorkerSequence, length)); + + // sequence type + serialize(data, QV4::Value::fromInt32( + QV4::SequencePrototype::metaTypeForSequence(s).id()), engine); + + ScopedValue val(scope); + for (uint ii = 0; ii < seqLength; ++ii) + serialize(data, (val = s->get(ii)), engine); // sequence elements + + return; + } else if (const Object *o = v.as<Object>()) { + const QVariant variant = QV4::ExecutionEngine::toVariant( + v, QMetaType::fromType<QUrl>(), false); + if (variant.userType() == QMetaType::QUrl) { + serializeString(data, variant.value<QUrl>().toString(), WorkerUrl); + return; + } + + // regular object + QV4::ScopedValue val(scope, v); + QV4::ScopedArrayObject properties(scope, QV4::ObjectPrototype::getOwnPropertyNames(engine, val)); + quint32 length = properties->getLength(); + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + push(data, valueheader(WorkerObject, length)); + + QV4::ScopedValue s(scope); + for (quint32 ii = 0; ii < length; ++ii) { + s = properties->get(ii); + serialize(data, s, engine); + + QV4::String *str = s->as<String>(); + val = o->get(str); + if (scope.hasException()) + scope.engine->catchException(); + + serialize(data, val, engine); + } + return; + } else { + push(data, valueheader(WorkerUndefined)); + } +} + +struct VariantRef +{ + VariantRef() : obj(nullptr) {} + VariantRef(const VariantRef &r) : obj(r.obj) { addref(); } + VariantRef(QObject *a) : obj(a) { addref(); } + ~VariantRef() { release(); } + + VariantRef &operator=(const VariantRef &o) { + o.addref(); + release(); + obj = o.obj; + return *this; + } + + void addref() const + { + if (obj) + QMetaObject::invokeMethod(obj, "addref"); + } + + void release() const + { + if (obj) + QMetaObject::invokeMethod(obj, "release"); + + } + + QObject *obj; +}; + +QT_END_NAMESPACE +Q_DECLARE_METATYPE(VariantRef) +Q_DECLARE_METATYPE(QV4::ExecutionEngine *) +QT_BEGIN_NAMESPACE + +ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) +{ + quint32 header = popUint32(data); + Type type = headertype(header); + + Scope scope(engine); + + switch (type) { + case WorkerUndefined: + return QV4::Encode::undefined(); + case WorkerNull: + return QV4::Encode::null(); + case WorkerTrue: + return QV4::Encode(true); + case WorkerFalse: + return QV4::Encode(false); + case WorkerString: + case WorkerUrl: + { + quint32 size = headersize(header); + QString qstr((const QChar *)data, size); + data += ALIGN(size * sizeof(quint16)); + return (type == WorkerUrl) + ? engine->fromVariant(QVariant::fromValue(QUrl(qstr))) + : Encode(engine->newString(qstr)); + } + case WorkerFunction: + Q_ASSERT(!"Unreachable"); + break; + case WorkerArray: + { + quint32 size = headersize(header); + ScopedArrayObject a(scope, engine->newArrayObject()); + ScopedValue v(scope); + for (quint32 ii = 0; ii < size; ++ii) { + v = deserialize(data, engine); + a->put(ii, v); + } + return a.asReturnedValue(); + } + case WorkerObject: + { + quint32 size = headersize(header); + ScopedObject o(scope, engine->newObject()); + ScopedValue name(scope); + ScopedString n(scope); + ScopedValue value(scope); + for (quint32 ii = 0; ii < size; ++ii) { + name = deserialize(data, engine); + value = deserialize(data, engine); + n = name->asReturnedValue(); + o->put(n, value); + } + return o.asReturnedValue(); + } + case WorkerInt32: + return QV4::Encode((qint32)popUint32(data)); + case WorkerUint32: + return QV4::Encode(popUint32(data)); + case WorkerNumber: + return QV4::Encode(popDouble(data)); + case WorkerDate: + return QV4::Encode(engine->newDateObject(popDouble(data))); + case WorkerRegexp: + { + quint32 flags = headersize(header); + quint32 length = popUint32(data); + QString pattern = QString((const QChar *)data, length - 1); + data += ALIGN(length * sizeof(quint16)); + return Encode(engine->newRegExpObject(pattern, flags)); + } + case WorkerListModel: + { + QObject *agent = reinterpret_cast<QObject *>(popPtr(data)); + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine, agent)); + // ### Find a better solution then the ugly property + VariantRef ref(agent); + QVariant var = QVariant::fromValue(ref); + QV4::ScopedValue v(scope, scope.engine->fromVariant(var)); + QV4::ScopedString s(scope, engine->newString(QStringLiteral("__qml:hidden:ref"))); + rv->as<Object>()->defineReadonlyProperty(s, v); + + QMetaObject::invokeMethod(agent, "release"); + agent->setProperty("engine", QVariant::fromValue(engine)); + return rv->asReturnedValue(); + } + case WorkerSequence: + { + ScopedValue value(scope); + quint32 length = headersize(header); + quint32 seqLength = length - 1; + value = deserialize(data, engine); + int sequenceType = value->integerValue(); + ScopedArrayObject array(scope, engine->newArrayObject()); + array->arrayReserve(seqLength); + for (quint32 ii = 0; ii < seqLength; ++ii) { + value = deserialize(data, engine); + array->arrayPut(ii, value); + } + array->setArrayLengthUnchecked(seqLength); + QVariant seqVariant = QV4::SequencePrototype::toVariant(array, QMetaType(sequenceType)); + return QV4::SequencePrototype::fromVariant(engine, seqVariant); + } + } + Q_ASSERT(!"Unreachable"); + return QV4::Encode::undefined(); +} + +QByteArray Serialize::serialize(const QV4::Value &value, ExecutionEngine *engine) +{ + QByteArray rv; + serialize(rv, value, engine); + return rv; +} + +ReturnedValue Serialize::deserialize(const QByteArray &data, ExecutionEngine *engine) +{ + const char *stream = data.constData(); + return deserialize(stream, engine); +} + +QT_END_NAMESPACE |