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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | using 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 | |
32 | enum 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 | |
51 | static inline quint32 (Type type, quint32 size = 0) |
52 | { |
53 | return quint8(type) << 24 | (size & 0xFFFFFF); |
54 | } |
55 | |
56 | static inline Type (quint32 ) |
57 | { |
58 | return (Type)(header >> 24); |
59 | } |
60 | |
61 | static inline quint32 (quint32 ) |
62 | { |
63 | return header & 0xFFFFFF; |
64 | } |
65 | |
66 | static inline void push(QByteArray &data, quint32 value) |
67 | { |
68 | data.append(s: (const char *)&value, len: sizeof(quint32)); |
69 | } |
70 | |
71 | static inline void push(QByteArray &data, double value) |
72 | { |
73 | data.append(s: (const char *)&value, len: sizeof(double)); |
74 | } |
75 | |
76 | static inline void push(QByteArray &data, void *ptr) |
77 | { |
78 | data.append(s: (const char *)&ptr, len: sizeof(void *)); |
79 | } |
80 | |
81 | static inline void reserve(QByteArray &data, int ) |
82 | { |
83 | data.reserve(asize: data.size() + extra); |
84 | } |
85 | |
86 | static inline quint32 popUint32(const char *&data) |
87 | { |
88 | quint32 rv = *((const quint32 *)data); |
89 | data += sizeof(quint32); |
90 | return rv; |
91 | } |
92 | |
93 | static inline double popDouble(const char *&data) |
94 | { |
95 | double rv = *((const double *)data); |
96 | data += sizeof(double); |
97 | return rv; |
98 | } |
99 | |
100 | static 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) |
108 | static 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 | |
130 | void 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 | |
264 | struct 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 | |
294 | QT_END_NAMESPACE |
295 | Q_DECLARE_METATYPE(VariantRef) |
296 | Q_DECLARE_METATYPE(QV4::ExecutionEngine *) |
297 | QT_BEGIN_NAMESPACE |
298 | |
299 | ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) |
300 | { |
301 | quint32 = 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 | |
407 | QByteArray Serialize::serialize(const QV4::Value &value, ExecutionEngine *engine) |
408 | { |
409 | QByteArray rv; |
410 | serialize(data&: rv, v: value, engine); |
411 | return rv; |
412 | } |
413 | |
414 | ReturnedValue Serialize::deserialize(const QByteArray &data, ExecutionEngine *engine) |
415 | { |
416 | const char *stream = data.constData(); |
417 | return deserialize(data&: stream, engine); |
418 | } |
419 | |
420 | QT_END_NAMESPACE |
421 | |