1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qv4serialize_p.h"
5
6#include <private/qv4dateobject_p.h>
7#include <private/qv4objectproto_p.h>
8#include <private/qv4qobjectwrapper_p.h>
9#include <private/qv4regexp_p.h>
10#include <private/qv4regexpobject_p.h>
11#include <private/qv4sequenceobject_p.h>
12#include <private/qv4value_p.h>
13
14QT_BEGIN_NAMESPACE
15
16using namespace QV4;
17
18// We allow the following JavaScript types to be passed between the main and
19// the secondary thread:
20// + undefined
21// + null
22// + Boolean
23// + String
24// + Function
25// + Array
26// + "Simple" Objects
27// + Number
28// + Date
29// + RegExp
30// <quint8 type><quint24 size><data>
31
32enum Type {
33 WorkerUndefined,
34 WorkerNull,
35 WorkerTrue,
36 WorkerFalse,
37 WorkerString,
38 WorkerFunction,
39 WorkerArray,
40 WorkerObject,
41 WorkerInt32,
42 WorkerUint32,
43 WorkerNumber,
44 WorkerDate,
45 WorkerRegexp,
46 WorkerListModel,
47 WorkerUrl,
48 WorkerSequence
49};
50
51static inline quint32 valueheader(Type type, quint32 size = 0)
52{
53 return quint8(type) << 24 | (size & 0xFFFFFF);
54}
55
56static inline Type headertype(quint32 header)
57{
58 return (Type)(header >> 24);
59}
60
61static inline quint32 headersize(quint32 header)
62{
63 return header & 0xFFFFFF;
64}
65
66static inline void push(QByteArray &data, quint32 value)
67{
68 data.append(s: (const char *)&value, len: sizeof(quint32));
69}
70
71static inline void push(QByteArray &data, double value)
72{
73 data.append(s: (const char *)&value, len: sizeof(double));
74}
75
76static inline void push(QByteArray &data, void *ptr)
77{
78 data.append(s: (const char *)&ptr, len: sizeof(void *));
79}
80
81static inline void reserve(QByteArray &data, int extra)
82{
83 data.reserve(asize: data.size() + extra);
84}
85
86static inline quint32 popUint32(const char *&data)
87{
88 quint32 rv = *((const quint32 *)data);
89 data += sizeof(quint32);
90 return rv;
91}
92
93static inline double popDouble(const char *&data)
94{
95 double rv = *((const double *)data);
96 data += sizeof(double);
97 return rv;
98}
99
100static inline void *popPtr(const char *&data)
101{
102 void *rv = *((void *const *)data);
103 data += sizeof(void *);
104 return rv;
105}
106
107#define ALIGN(size) (((size) + 3) & ~3)
108static inline void serializeString(QByteArray &data, const QString &str, Type type)
109{
110 int length = str.size();
111 if (length > 0xFFFFFF) {
112 push(data, value: valueheader(type: WorkerUndefined));
113 return;
114 }
115 int utf16size = ALIGN(length * sizeof(quint16));
116
117 reserve(data, extra: utf16size + sizeof(quint32));
118 push(data, value: valueheader(type, size: length));
119
120 int offset = data.size();
121 data.resize(size: data.size() + utf16size);
122 char *buffer = data.data() + offset;
123
124 memcpy(dest: buffer, src: str.constData(), n: length*sizeof(QChar));
125}
126
127// XXX TODO: Check that worker script is exception safe in the case of
128// serialization/deserialization failures
129
130void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine *engine)
131{
132 QV4::Scope scope(engine);
133
134 if (v.isEmpty()) {
135 Q_ASSERT(!"Serialize: got empty value");
136 } else if (v.isUndefined()) {
137 push(data, value: valueheader(type: WorkerUndefined));
138 } else if (v.isNull()) {
139 push(data, value: valueheader(type: WorkerNull));
140 } else if (v.isBoolean()) {
141 push(data, value: valueheader(type: v.booleanValue() == true ? WorkerTrue : WorkerFalse));
142 } else if (v.isString()) {
143 serializeString(data, str: v.toQString(), type: WorkerString);
144 } else if (v.as<FunctionObject>()) {
145 // XXX TODO: Implement passing function objects between the main and
146 // worker scripts
147 push(data, value: valueheader(type: WorkerUndefined));
148 } else if (const QV4::ArrayObject *array = v.as<ArrayObject>()) {
149 uint length = array->getLength();
150 if (length > 0xFFFFFF) {
151 push(data, value: valueheader(type: WorkerUndefined));
152 return;
153 }
154 reserve(data, extra: sizeof(quint32) + length * sizeof(quint32));
155 push(data, value: valueheader(type: WorkerArray, size: length));
156 ScopedValue val(scope);
157 for (uint ii = 0; ii < length; ++ii)
158 serialize(data, v: (val = array->get(idx: ii)), engine);
159 } else if (v.isInteger()) {
160 reserve(data, extra: 2 * sizeof(quint32));
161 push(data, value: valueheader(type: WorkerInt32));
162 push(data, value: (quint32)v.integerValue());
163// } else if (v.IsUint32()) {
164// reserve(data, 2 * sizeof(quint32));
165// push(data, valueheader(WorkerUint32));
166// push(data, v.Uint32Value());
167 } else if (v.isNumber()) {
168 reserve(data, extra: sizeof(quint32) + sizeof(double));
169 push(data, value: valueheader(type: WorkerNumber));
170 push(data, value: v.asDouble());
171 } else if (const QV4::DateObject *d = v.as<DateObject>()) {
172 reserve(data, extra: sizeof(quint32) + sizeof(double));
173 push(data, value: valueheader(type: WorkerDate));
174 push(data, value: d->date());
175 } else if (const RegExpObject *re = v.as<RegExpObject>()) {
176 quint32 flags = re->flags();
177 QString pattern = re->source();
178 int length = pattern.size() + 1;
179 if (length > 0xFFFFFF) {
180 push(data, value: valueheader(type: WorkerUndefined));
181 return;
182 }
183 int utf16size = ALIGN(length * sizeof(quint16));
184
185 reserve(data, extra: sizeof(quint32) + utf16size);
186 push(data, value: valueheader(type: WorkerRegexp, size: flags));
187 push(data, value: (quint32)length);
188
189 int offset = data.size();
190 data.resize(size: data.size() + utf16size);
191 char *buffer = data.data() + offset;
192
193 memcpy(dest: buffer, src: pattern.constData(), n: length*sizeof(QChar));
194 } else if (const QObjectWrapper *qobjectWrapper = v.as<QV4::QObjectWrapper>()) {
195 // XXX TODO: Generalize passing objects between the main thread and worker scripts so
196 // that others can trivially plug in their elements.
197 if (QObject *lm = qobjectWrapper->object()) {
198 if (QObject *agent = qvariant_cast<QObject *>(v: lm->property(name: "agent"))) {
199 if (QMetaObject::invokeMethod(obj: agent, member: "addref")) {
200 push(data, value: valueheader(type: WorkerListModel));
201 push(data, ptr: (void *)agent);
202 return;
203 }
204 }
205 }
206 // No other QObject's are allowed to be sent
207 push(data, value: valueheader(type: WorkerUndefined));
208 } else if (const Sequence *s = v.as<Sequence>()) {
209 // valid sequence. we generate a length (sequence length + 1 for the sequence type)
210 uint seqLength = ScopedValue(scope, s->get(name: engine->id_length()))->toUInt32();
211 uint length = seqLength + 1;
212 if (length > 0xFFFFFF) {
213 push(data, value: valueheader(type: WorkerUndefined));
214 return;
215 }
216 reserve(data, extra: sizeof(quint32) + length * sizeof(quint32));
217 push(data, value: valueheader(type: WorkerSequence, size: length));
218
219 // sequence type
220 serialize(data, v: QV4::Value::fromInt32(
221 i: QV4::SequencePrototype::metaTypeForSequence(object: s).id()), engine);
222
223 ScopedValue val(scope);
224 for (uint ii = 0; ii < seqLength; ++ii)
225 serialize(data, v: (val = s->get(idx: ii)), engine); // sequence elements
226
227 return;
228 } else if (const Object *o = v.as<Object>()) {
229 const QVariant variant = QV4::ExecutionEngine::toVariant(
230 value: v, typeHint: QMetaType::fromType<QUrl>(), createJSValueForObjectsAndSymbols: false);
231 if (variant.userType() == QMetaType::QUrl) {
232 serializeString(data, str: variant.value<QUrl>().toString(), type: WorkerUrl);
233 return;
234 }
235
236 // regular object
237 QV4::ScopedValue val(scope, v);
238 QV4::ScopedArrayObject properties(scope, QV4::ObjectPrototype::getOwnPropertyNames(v4: engine, o: val));
239 quint32 length = properties->getLength();
240 if (length > 0xFFFFFF) {
241 push(data, value: valueheader(type: WorkerUndefined));
242 return;
243 }
244 push(data, value: valueheader(type: WorkerObject, size: length));
245
246 QV4::ScopedValue s(scope);
247 for (quint32 ii = 0; ii < length; ++ii) {
248 s = properties->get(idx: ii);
249 serialize(data, v: s, engine);
250
251 QV4::String *str = s->as<String>();
252 val = o->get(name: str);
253 if (scope.hasException())
254 scope.engine->catchException();
255
256 serialize(data, v: val, engine);
257 }
258 return;
259 } else {
260 push(data, value: valueheader(type: WorkerUndefined));
261 }
262}
263
264struct VariantRef
265{
266 VariantRef() : obj(nullptr) {}
267 VariantRef(const VariantRef &r) : obj(r.obj) { addref(); }
268 VariantRef(QObject *a) : obj(a) { addref(); }
269 ~VariantRef() { release(); }
270
271 VariantRef &operator=(const VariantRef &o) {
272 o.addref();
273 release();
274 obj = o.obj;
275 return *this;
276 }
277
278 void addref() const
279 {
280 if (obj)
281 QMetaObject::invokeMethod(obj, member: "addref");
282 }
283
284 void release() const
285 {
286 if (obj)
287 QMetaObject::invokeMethod(obj, member: "release");
288
289 }
290
291 QObject *obj;
292};
293
294QT_END_NAMESPACE
295Q_DECLARE_METATYPE(VariantRef)
296Q_DECLARE_METATYPE(QV4::ExecutionEngine *)
297QT_BEGIN_NAMESPACE
298
299ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine)
300{
301 quint32 header = popUint32(data);
302 Type type = headertype(header);
303
304 Scope scope(engine);
305
306 switch (type) {
307 case WorkerUndefined:
308 return QV4::Encode::undefined();
309 case WorkerNull:
310 return QV4::Encode::null();
311 case WorkerTrue:
312 return QV4::Encode(true);
313 case WorkerFalse:
314 return QV4::Encode(false);
315 case WorkerString:
316 case WorkerUrl:
317 {
318 quint32 size = headersize(header);
319 QString qstr((const QChar *)data, size);
320 data += ALIGN(size * sizeof(quint16));
321 return (type == WorkerUrl)
322 ? engine->fromVariant(QVariant::fromValue(value: QUrl(qstr)))
323 : Encode(engine->newString(s: qstr));
324 }
325 case WorkerFunction:
326 Q_ASSERT(!"Unreachable");
327 break;
328 case WorkerArray:
329 {
330 quint32 size = headersize(header);
331 ScopedArrayObject a(scope, engine->newArrayObject());
332 ScopedValue v(scope);
333 for (quint32 ii = 0; ii < size; ++ii) {
334 v = deserialize(data, engine);
335 a->put(idx: ii, v);
336 }
337 return a.asReturnedValue();
338 }
339 case WorkerObject:
340 {
341 quint32 size = headersize(header);
342 ScopedObject o(scope, engine->newObject());
343 ScopedValue name(scope);
344 ScopedString n(scope);
345 ScopedValue value(scope);
346 for (quint32 ii = 0; ii < size; ++ii) {
347 name = deserialize(data, engine);
348 value = deserialize(data, engine);
349 n = name->asReturnedValue();
350 o->put(name: n, v: value);
351 }
352 return o.asReturnedValue();
353 }
354 case WorkerInt32:
355 return QV4::Encode((qint32)popUint32(data));
356 case WorkerUint32:
357 return QV4::Encode(popUint32(data));
358 case WorkerNumber:
359 return QV4::Encode(popDouble(data));
360 case WorkerDate:
361 return QV4::Encode(engine->newDateObject(dateTime: popDouble(data)));
362 case WorkerRegexp:
363 {
364 quint32 flags = headersize(header);
365 quint32 length = popUint32(data);
366 QString pattern = QString((const QChar *)data, length - 1);
367 data += ALIGN(length * sizeof(quint16));
368 return Encode(engine->newRegExpObject(pattern, flags));
369 }
370 case WorkerListModel:
371 {
372 QObject *agent = reinterpret_cast<QObject *>(popPtr(data));
373 QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine, object: agent));
374 // ### Find a better solution then the ugly property
375 VariantRef ref(agent);
376 QVariant var = QVariant::fromValue(value: ref);
377 QV4::ScopedValue v(scope, scope.engine->fromVariant(var));
378 QV4::ScopedString s(scope, engine->newString(QStringLiteral("__qml:hidden:ref")));
379 rv->as<Object>()->defineReadonlyProperty(name: s, value: v);
380
381 QMetaObject::invokeMethod(obj: agent, member: "release");
382 agent->setProperty(name: "engine", value: QVariant::fromValue(value: engine));
383 return rv->asReturnedValue();
384 }
385 case WorkerSequence:
386 {
387 ScopedValue value(scope);
388 quint32 length = headersize(header);
389 quint32 seqLength = length - 1;
390 value = deserialize(data, engine);
391 int sequenceType = value->integerValue();
392 ScopedArrayObject array(scope, engine->newArrayObject());
393 array->arrayReserve(n: seqLength);
394 for (quint32 ii = 0; ii < seqLength; ++ii) {
395 value = deserialize(data, engine);
396 array->arrayPut(index: ii, value);
397 }
398 array->setArrayLengthUnchecked(seqLength);
399 QVariant seqVariant = QV4::SequencePrototype::toVariant(array, typeHint: QMetaType(sequenceType));
400 return QV4::SequencePrototype::fromVariant(engine, vd: seqVariant);
401 }
402 }
403 Q_ASSERT(!"Unreachable");
404 return QV4::Encode::undefined();
405}
406
407QByteArray Serialize::serialize(const QV4::Value &value, ExecutionEngine *engine)
408{
409 QByteArray rv;
410 serialize(data&: rv, v: value, engine);
411 return rv;
412}
413
414ReturnedValue Serialize::deserialize(const QByteArray &data, ExecutionEngine *engine)
415{
416 const char *stream = data.constData();
417 return deserialize(data&: stream, engine);
418}
419
420QT_END_NAMESPACE
421

source code of qtdeclarative/src/qmlworkerscript/qv4serialize.cpp