|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* |
|
8 * This file implements the structured clone algorithm of |
|
9 * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#safe-passing-of-structured-data |
|
10 * |
|
11 * The implementation differs slightly in that it uses an explicit stack, and |
|
12 * the "memory" maps source objects to sequential integer indexes rather than |
|
13 * directly pointing to destination objects. As a result, the order in which |
|
14 * things are added to the memory must exactly match the order in which they |
|
15 * are placed into 'allObjs', an analogous array of back-referenceable |
|
16 * destination objects constructed while reading. |
|
17 * |
|
18 * For the most part, this is easy: simply add objects to the memory when first |
|
19 * encountering them. But reading in a typed array requires an ArrayBuffer for |
|
20 * construction, so objects cannot just be added to 'allObjs' in the order they |
|
21 * are created. If they were, ArrayBuffers would come before typed arrays when |
|
22 * in fact the typed array was added to 'memory' first. |
|
23 * |
|
24 * So during writing, we add objects to the memory when first encountering |
|
25 * them. When reading a typed array, a placeholder is pushed onto allObjs until |
|
26 * the ArrayBuffer has been read, then it is updated with the actual typed |
|
27 * array object. |
|
28 */ |
|
29 |
|
30 #include "js/StructuredClone.h" |
|
31 |
|
32 #include "mozilla/Endian.h" |
|
33 #include "mozilla/FloatingPoint.h" |
|
34 |
|
35 #include <algorithm> |
|
36 |
|
37 #include "jsapi.h" |
|
38 #include "jscntxt.h" |
|
39 #include "jsdate.h" |
|
40 #include "jswrapper.h" |
|
41 |
|
42 #include "vm/SharedArrayObject.h" |
|
43 #include "vm/TypedArrayObject.h" |
|
44 #include "vm/WrapperObject.h" |
|
45 |
|
46 #include "jscntxtinlines.h" |
|
47 #include "jsobjinlines.h" |
|
48 |
|
49 using namespace js; |
|
50 |
|
51 using mozilla::IsNaN; |
|
52 using mozilla::LittleEndian; |
|
53 using mozilla::NativeEndian; |
|
54 using JS::CanonicalizeNaN; |
|
55 |
|
56 enum StructuredDataType { |
|
57 /* Structured data types provided by the engine */ |
|
58 SCTAG_FLOAT_MAX = 0xFFF00000, |
|
59 SCTAG_NULL = 0xFFFF0000, |
|
60 SCTAG_UNDEFINED, |
|
61 SCTAG_BOOLEAN, |
|
62 SCTAG_INDEX, |
|
63 SCTAG_STRING, |
|
64 SCTAG_DATE_OBJECT, |
|
65 SCTAG_REGEXP_OBJECT, |
|
66 SCTAG_ARRAY_OBJECT, |
|
67 SCTAG_OBJECT_OBJECT, |
|
68 SCTAG_ARRAY_BUFFER_OBJECT, |
|
69 SCTAG_BOOLEAN_OBJECT, |
|
70 SCTAG_STRING_OBJECT, |
|
71 SCTAG_NUMBER_OBJECT, |
|
72 SCTAG_BACK_REFERENCE_OBJECT, |
|
73 SCTAG_DO_NOT_USE_1, // Required for backwards compatibility |
|
74 SCTAG_DO_NOT_USE_2, // Required for backwards compatibility |
|
75 SCTAG_TYPED_ARRAY_OBJECT, |
|
76 SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100, |
|
77 SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT8, |
|
78 SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT8, |
|
79 SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT16, |
|
80 SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT16, |
|
81 SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT32, |
|
82 SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT32, |
|
83 SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_FLOAT32, |
|
84 SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_FLOAT64, |
|
85 SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_UINT8_CLAMPED, |
|
86 SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_MAX - 1, |
|
87 |
|
88 /* |
|
89 * Define a separate range of numbers for Transferable-only tags, since |
|
90 * they are not used for persistent clone buffers and therefore do not |
|
91 * require bumping JS_STRUCTURED_CLONE_VERSION. |
|
92 */ |
|
93 SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200, |
|
94 SCTAG_TRANSFER_MAP_PENDING_ENTRY, |
|
95 SCTAG_TRANSFER_MAP_ARRAY_BUFFER, |
|
96 SCTAG_TRANSFER_MAP_SHARED_BUFFER, |
|
97 SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES, |
|
98 |
|
99 SCTAG_END_OF_BUILTIN_TYPES |
|
100 }; |
|
101 |
|
102 /* |
|
103 * Format of transfer map: |
|
104 * <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)> |
|
105 * numTransferables (64 bits) |
|
106 * array of: |
|
107 * <SCTAG_TRANSFER_MAP_*, TransferableOwnership> |
|
108 * pointer (64 bits) |
|
109 * extraData (64 bits), eg byte length for ArrayBuffers |
|
110 */ |
|
111 |
|
112 // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the |
|
113 // contents have been read out yet or not. |
|
114 enum TransferableMapHeader { |
|
115 SCTAG_TM_UNREAD = 0, |
|
116 SCTAG_TM_TRANSFERRED |
|
117 }; |
|
118 |
|
119 static inline uint64_t |
|
120 PairToUInt64(uint32_t tag, uint32_t data) |
|
121 { |
|
122 return uint64_t(data) | (uint64_t(tag) << 32); |
|
123 } |
|
124 |
|
125 namespace js { |
|
126 |
|
127 struct SCOutput { |
|
128 public: |
|
129 explicit SCOutput(JSContext *cx); |
|
130 |
|
131 JSContext *context() const { return cx; } |
|
132 |
|
133 bool write(uint64_t u); |
|
134 bool writePair(uint32_t tag, uint32_t data); |
|
135 bool writeDouble(double d); |
|
136 bool writeBytes(const void *p, size_t nbytes); |
|
137 bool writeChars(const jschar *p, size_t nchars); |
|
138 bool writePtr(const void *); |
|
139 |
|
140 template <class T> |
|
141 bool writeArray(const T *p, size_t nbytes); |
|
142 |
|
143 bool extractBuffer(uint64_t **datap, size_t *sizep); |
|
144 |
|
145 uint64_t count() const { return buf.length(); } |
|
146 uint64_t *rawBuffer() { return buf.begin(); } |
|
147 |
|
148 private: |
|
149 JSContext *cx; |
|
150 Vector<uint64_t> buf; |
|
151 }; |
|
152 |
|
153 class SCInput { |
|
154 public: |
|
155 SCInput(JSContext *cx, uint64_t *data, size_t nbytes); |
|
156 |
|
157 JSContext *context() const { return cx; } |
|
158 |
|
159 static void getPtr(const uint64_t *buffer, void **ptr); |
|
160 static void getPair(const uint64_t *buffer, uint32_t *tagp, uint32_t *datap); |
|
161 |
|
162 bool read(uint64_t *p); |
|
163 bool readNativeEndian(uint64_t *p); |
|
164 bool readPair(uint32_t *tagp, uint32_t *datap); |
|
165 bool readDouble(double *p); |
|
166 bool readBytes(void *p, size_t nbytes); |
|
167 bool readChars(jschar *p, size_t nchars); |
|
168 bool readPtr(void **); |
|
169 |
|
170 bool get(uint64_t *p); |
|
171 bool getPair(uint32_t *tagp, uint32_t *datap); |
|
172 |
|
173 uint64_t *tell() const { return point; } |
|
174 uint64_t *end() const { return bufEnd; } |
|
175 |
|
176 template <class T> |
|
177 bool readArray(T *p, size_t nelems); |
|
178 |
|
179 bool reportTruncated() { |
|
180 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, |
|
181 JSMSG_SC_BAD_SERIALIZED_DATA, "truncated"); |
|
182 return false; |
|
183 } |
|
184 |
|
185 private: |
|
186 void staticAssertions() { |
|
187 JS_STATIC_ASSERT(sizeof(jschar) == 2); |
|
188 JS_STATIC_ASSERT(sizeof(uint32_t) == 4); |
|
189 JS_STATIC_ASSERT(sizeof(double) == 8); |
|
190 } |
|
191 |
|
192 JSContext *cx; |
|
193 uint64_t *point; |
|
194 uint64_t *bufEnd; |
|
195 }; |
|
196 |
|
197 } /* namespace js */ |
|
198 |
|
199 struct JSStructuredCloneReader { |
|
200 public: |
|
201 explicit JSStructuredCloneReader(SCInput &in, const JSStructuredCloneCallbacks *cb, |
|
202 void *cbClosure) |
|
203 : in(in), objs(in.context()), allObjs(in.context()), |
|
204 callbacks(cb), closure(cbClosure) { } |
|
205 |
|
206 SCInput &input() { return in; } |
|
207 bool read(Value *vp); |
|
208 |
|
209 private: |
|
210 JSContext *context() { return in.context(); } |
|
211 |
|
212 bool readTransferMap(); |
|
213 |
|
214 bool checkDouble(double d); |
|
215 JSString *readString(uint32_t nchars); |
|
216 bool readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp, bool v1Read = false); |
|
217 bool readArrayBuffer(uint32_t nbytes, Value *vp); |
|
218 bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp); |
|
219 bool readId(jsid *idp); |
|
220 bool startRead(Value *vp); |
|
221 |
|
222 SCInput ∈ |
|
223 |
|
224 // Stack of objects with properties remaining to be read. |
|
225 AutoValueVector objs; |
|
226 |
|
227 // Stack of all objects read during this deserialization |
|
228 AutoValueVector allObjs; |
|
229 |
|
230 // The user defined callbacks that will be used for cloning. |
|
231 const JSStructuredCloneCallbacks *callbacks; |
|
232 |
|
233 // Any value passed to JS_ReadStructuredClone. |
|
234 void *closure; |
|
235 |
|
236 friend bool JS_ReadTypedArray(JSStructuredCloneReader *r, MutableHandleValue vp); |
|
237 }; |
|
238 |
|
239 struct JSStructuredCloneWriter { |
|
240 public: |
|
241 explicit JSStructuredCloneWriter(JSContext *cx, |
|
242 const JSStructuredCloneCallbacks *cb, |
|
243 void *cbClosure, |
|
244 jsval tVal) |
|
245 : out(cx), objs(out.context()), |
|
246 counts(out.context()), ids(out.context()), |
|
247 memory(out.context()), callbacks(cb), closure(cbClosure), |
|
248 transferable(out.context(), tVal), transferableObjects(out.context()) { } |
|
249 |
|
250 ~JSStructuredCloneWriter(); |
|
251 |
|
252 bool init() { return memory.init() && parseTransferable() && writeTransferMap(); } |
|
253 |
|
254 bool write(const Value &v); |
|
255 |
|
256 SCOutput &output() { return out; } |
|
257 |
|
258 bool extractBuffer(uint64_t **datap, size_t *sizep) { |
|
259 return out.extractBuffer(datap, sizep); |
|
260 } |
|
261 |
|
262 private: |
|
263 JSContext *context() { return out.context(); } |
|
264 |
|
265 bool writeTransferMap(); |
|
266 |
|
267 bool writeString(uint32_t tag, JSString *str); |
|
268 bool writeId(jsid id); |
|
269 bool writeArrayBuffer(HandleObject obj); |
|
270 bool writeTypedArray(HandleObject obj); |
|
271 bool startObject(HandleObject obj, bool *backref); |
|
272 bool startWrite(const Value &v); |
|
273 bool traverseObject(HandleObject obj); |
|
274 |
|
275 bool parseTransferable(); |
|
276 bool reportErrorTransferable(); |
|
277 bool transferOwnership(); |
|
278 |
|
279 inline void checkStack(); |
|
280 |
|
281 SCOutput out; |
|
282 |
|
283 // Vector of objects with properties remaining to be written. |
|
284 // |
|
285 // NB: These can span multiple compartments, so the compartment must be |
|
286 // entered before any manipulation is performed. |
|
287 AutoValueVector objs; |
|
288 |
|
289 // counts[i] is the number of properties of objs[i] remaining to be written. |
|
290 // counts.length() == objs.length() and sum(counts) == ids.length(). |
|
291 Vector<size_t> counts; |
|
292 |
|
293 // Ids of properties remaining to be written. |
|
294 AutoIdVector ids; |
|
295 |
|
296 // The "memory" list described in the HTML5 internal structured cloning algorithm. |
|
297 // memory is a superset of objs; items are never removed from Memory |
|
298 // until a serialization operation is finished |
|
299 typedef AutoObjectUnsigned32HashMap CloneMemory; |
|
300 CloneMemory memory; |
|
301 |
|
302 // The user defined callbacks that will be used for cloning. |
|
303 const JSStructuredCloneCallbacks *callbacks; |
|
304 |
|
305 // Any value passed to JS_WriteStructuredClone. |
|
306 void *closure; |
|
307 |
|
308 // List of transferable objects |
|
309 RootedValue transferable; |
|
310 AutoObjectVector transferableObjects; |
|
311 |
|
312 friend bool JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v); |
|
313 }; |
|
314 |
|
315 JS_FRIEND_API(uint64_t) |
|
316 js_GetSCOffset(JSStructuredCloneWriter* writer) |
|
317 { |
|
318 JS_ASSERT(writer); |
|
319 return writer->output().count() * sizeof(uint64_t); |
|
320 } |
|
321 |
|
322 JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN); |
|
323 JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX); |
|
324 JS_STATIC_ASSERT(ScalarTypeDescr::TYPE_INT8 == 0); |
|
325 |
|
326 static void |
|
327 ReportErrorTransferable(JSContext *cx, const JSStructuredCloneCallbacks *callbacks) |
|
328 { |
|
329 if (callbacks && callbacks->reportError) |
|
330 callbacks->reportError(cx, JS_SCERR_TRANSFERABLE); |
|
331 else |
|
332 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE); |
|
333 } |
|
334 |
|
335 bool |
|
336 WriteStructuredClone(JSContext *cx, HandleValue v, uint64_t **bufp, size_t *nbytesp, |
|
337 const JSStructuredCloneCallbacks *cb, void *cbClosure, |
|
338 jsval transferable) |
|
339 { |
|
340 JSStructuredCloneWriter w(cx, cb, cbClosure, transferable); |
|
341 return w.init() && w.write(v) && w.extractBuffer(bufp, nbytesp); |
|
342 } |
|
343 |
|
344 bool |
|
345 ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, MutableHandleValue vp, |
|
346 const JSStructuredCloneCallbacks *cb, void *cbClosure) |
|
347 { |
|
348 SCInput in(cx, data, nbytes); |
|
349 JSStructuredCloneReader r(in, cb, cbClosure); |
|
350 return r.read(vp.address()); |
|
351 } |
|
352 |
|
353 // If the given buffer contains Transferables, free them. Note that custom |
|
354 // Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to |
|
355 // delete their transferables. |
|
356 static void |
|
357 Discard(uint64_t *buffer, size_t nbytes, const JSStructuredCloneCallbacks *cb, void *cbClosure) |
|
358 { |
|
359 JS_ASSERT(nbytes % sizeof(uint64_t) == 0); |
|
360 if (nbytes < sizeof(uint64_t)) |
|
361 return; // Empty buffer |
|
362 |
|
363 uint64_t *point = buffer; |
|
364 uint32_t tag, data; |
|
365 SCInput::getPair(point++, &tag, &data); |
|
366 if (tag != SCTAG_TRANSFER_MAP_HEADER) |
|
367 return; |
|
368 |
|
369 if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) |
|
370 return; |
|
371 |
|
372 // freeTransfer should not GC |
|
373 JS::AutoAssertNoGC nogc; |
|
374 |
|
375 uint64_t numTransferables = LittleEndian::readUint64(point++); |
|
376 while (numTransferables--) { |
|
377 uint32_t ownership; |
|
378 SCInput::getPair(point++, &tag, &ownership); |
|
379 JS_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY); |
|
380 |
|
381 void *content; |
|
382 SCInput::getPtr(point++, &content); |
|
383 |
|
384 uint64_t extraData = LittleEndian::readUint64(point++); |
|
385 |
|
386 if (ownership < JS::SCTAG_TMO_FIRST_OWNED) |
|
387 continue; |
|
388 |
|
389 if (ownership == JS::SCTAG_TMO_ALLOC_DATA) { |
|
390 js_free(content); |
|
391 } else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) { |
|
392 JS_ReleaseMappedArrayBufferContents(content, extraData); |
|
393 } else if (ownership == JS::SCTAG_TMO_SHARED_BUFFER) { |
|
394 SharedArrayRawBuffer *raw = static_cast<SharedArrayRawBuffer*>(content); |
|
395 if (raw) |
|
396 raw->dropReference(); |
|
397 } else if (cb && cb->freeTransfer) { |
|
398 cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure); |
|
399 } else { |
|
400 MOZ_ASSERT(false, "unknown ownership"); |
|
401 } |
|
402 } |
|
403 } |
|
404 |
|
405 static void |
|
406 ClearStructuredClone(uint64_t *data, size_t nbytes, |
|
407 const JSStructuredCloneCallbacks *cb, void *cbClosure) |
|
408 { |
|
409 Discard(data, nbytes, cb, cbClosure); |
|
410 js_free(data); |
|
411 } |
|
412 |
|
413 bool |
|
414 StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes, bool *hasTransferable) |
|
415 { |
|
416 *hasTransferable = false; |
|
417 |
|
418 if (data) { |
|
419 uint64_t u = LittleEndian::readUint64(data); |
|
420 uint32_t tag = uint32_t(u >> 32); |
|
421 if (tag == SCTAG_TRANSFER_MAP_HEADER) |
|
422 *hasTransferable = true; |
|
423 } |
|
424 |
|
425 return true; |
|
426 } |
|
427 |
|
428 namespace js { |
|
429 |
|
430 SCInput::SCInput(JSContext *cx, uint64_t *data, size_t nbytes) |
|
431 : cx(cx), point(data), bufEnd(data + nbytes / 8) |
|
432 { |
|
433 // On 32-bit, we sometimes construct an SCInput from an SCOutput buffer, |
|
434 // which is not guaranteed to be 8-byte aligned |
|
435 JS_ASSERT((uintptr_t(data) & (sizeof(int) - 1)) == 0); |
|
436 JS_ASSERT((nbytes & 7) == 0); |
|
437 } |
|
438 |
|
439 bool |
|
440 SCInput::read(uint64_t *p) |
|
441 { |
|
442 if (point == bufEnd) { |
|
443 *p = 0; /* initialize to shut GCC up */ |
|
444 return reportTruncated(); |
|
445 } |
|
446 *p = LittleEndian::readUint64(point++); |
|
447 return true; |
|
448 } |
|
449 |
|
450 bool |
|
451 SCInput::readNativeEndian(uint64_t *p) |
|
452 { |
|
453 if (point == bufEnd) { |
|
454 *p = 0; /* initialize to shut GCC up */ |
|
455 return reportTruncated(); |
|
456 } |
|
457 *p = *(point++); |
|
458 return true; |
|
459 } |
|
460 |
|
461 bool |
|
462 SCInput::readPair(uint32_t *tagp, uint32_t *datap) |
|
463 { |
|
464 uint64_t u; |
|
465 bool ok = read(&u); |
|
466 if (ok) { |
|
467 *tagp = uint32_t(u >> 32); |
|
468 *datap = uint32_t(u); |
|
469 } |
|
470 return ok; |
|
471 } |
|
472 |
|
473 bool |
|
474 SCInput::get(uint64_t *p) |
|
475 { |
|
476 if (point == bufEnd) |
|
477 return reportTruncated(); |
|
478 *p = LittleEndian::readUint64(point); |
|
479 return true; |
|
480 } |
|
481 |
|
482 bool |
|
483 SCInput::getPair(uint32_t *tagp, uint32_t *datap) |
|
484 { |
|
485 uint64_t u = 0; |
|
486 if (!get(&u)) |
|
487 return false; |
|
488 |
|
489 *tagp = uint32_t(u >> 32); |
|
490 *datap = uint32_t(u); |
|
491 return true; |
|
492 } |
|
493 |
|
494 void |
|
495 SCInput::getPair(const uint64_t *p, uint32_t *tagp, uint32_t *datap) |
|
496 { |
|
497 uint64_t u = LittleEndian::readUint64(p); |
|
498 *tagp = uint32_t(u >> 32); |
|
499 *datap = uint32_t(u); |
|
500 } |
|
501 |
|
502 bool |
|
503 SCInput::readDouble(double *p) |
|
504 { |
|
505 union { |
|
506 uint64_t u; |
|
507 double d; |
|
508 } pun; |
|
509 if (!read(&pun.u)) |
|
510 return false; |
|
511 *p = CanonicalizeNaN(pun.d); |
|
512 return true; |
|
513 } |
|
514 |
|
515 template <typename T> |
|
516 static void |
|
517 copyAndSwapFromLittleEndian(T *dest, const void *src, size_t nelems) |
|
518 { |
|
519 if (nelems > 0) |
|
520 NativeEndian::copyAndSwapFromLittleEndian(dest, src, nelems); |
|
521 } |
|
522 |
|
523 template <> |
|
524 void |
|
525 copyAndSwapFromLittleEndian(uint8_t *dest, const void *src, size_t nelems) |
|
526 { |
|
527 memcpy(dest, src, nelems); |
|
528 } |
|
529 |
|
530 template <class T> |
|
531 bool |
|
532 SCInput::readArray(T *p, size_t nelems) |
|
533 { |
|
534 JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0); |
|
535 |
|
536 /* |
|
537 * Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is |
|
538 * larger than the remaining data. |
|
539 */ |
|
540 size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T)); |
|
541 if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(bufEnd - point)) |
|
542 return reportTruncated(); |
|
543 |
|
544 copyAndSwapFromLittleEndian(p, point, nelems); |
|
545 point += nwords; |
|
546 return true; |
|
547 } |
|
548 |
|
549 bool |
|
550 SCInput::readBytes(void *p, size_t nbytes) |
|
551 { |
|
552 return readArray((uint8_t *) p, nbytes); |
|
553 } |
|
554 |
|
555 bool |
|
556 SCInput::readChars(jschar *p, size_t nchars) |
|
557 { |
|
558 JS_ASSERT(sizeof(jschar) == sizeof(uint16_t)); |
|
559 return readArray((uint16_t *) p, nchars); |
|
560 } |
|
561 |
|
562 void |
|
563 SCInput::getPtr(const uint64_t *p, void **ptr) |
|
564 { |
|
565 // No endianness conversion is used for pointers, since they are not sent |
|
566 // across address spaces anyway. |
|
567 *ptr = reinterpret_cast<void*>(*p); |
|
568 } |
|
569 |
|
570 bool |
|
571 SCInput::readPtr(void **p) |
|
572 { |
|
573 uint64_t u; |
|
574 if (!readNativeEndian(&u)) |
|
575 return false; |
|
576 *p = reinterpret_cast<void*>(u); |
|
577 return true; |
|
578 } |
|
579 |
|
580 SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {} |
|
581 |
|
582 bool |
|
583 SCOutput::write(uint64_t u) |
|
584 { |
|
585 return buf.append(NativeEndian::swapToLittleEndian(u)); |
|
586 } |
|
587 |
|
588 bool |
|
589 SCOutput::writePair(uint32_t tag, uint32_t data) |
|
590 { |
|
591 /* |
|
592 * As it happens, the tag word appears after the data word in the output. |
|
593 * This is because exponents occupy the last 2 bytes of doubles on the |
|
594 * little-endian platforms we care most about. |
|
595 * |
|
596 * For example, JSVAL_TRUE is written using writePair(SCTAG_BOOLEAN, 1). |
|
597 * PairToUInt64 produces the number 0xFFFF000200000001. |
|
598 * That is written out as the bytes 01 00 00 00 02 00 FF FF. |
|
599 */ |
|
600 return write(PairToUInt64(tag, data)); |
|
601 } |
|
602 |
|
603 static inline uint64_t |
|
604 ReinterpretDoubleAsUInt64(double d) |
|
605 { |
|
606 union { |
|
607 double d; |
|
608 uint64_t u; |
|
609 } pun; |
|
610 pun.d = d; |
|
611 return pun.u; |
|
612 } |
|
613 |
|
614 static inline double |
|
615 ReinterpretUInt64AsDouble(uint64_t u) |
|
616 { |
|
617 union { |
|
618 uint64_t u; |
|
619 double d; |
|
620 } pun; |
|
621 pun.u = u; |
|
622 return pun.d; |
|
623 } |
|
624 |
|
625 static inline double |
|
626 ReinterpretPairAsDouble(uint32_t tag, uint32_t data) |
|
627 { |
|
628 return ReinterpretUInt64AsDouble(PairToUInt64(tag, data)); |
|
629 } |
|
630 |
|
631 bool |
|
632 SCOutput::writeDouble(double d) |
|
633 { |
|
634 return write(ReinterpretDoubleAsUInt64(CanonicalizeNaN(d))); |
|
635 } |
|
636 |
|
637 template <typename T> |
|
638 static void |
|
639 copyAndSwapToLittleEndian(void *dest, const T *src, size_t nelems) |
|
640 { |
|
641 if (nelems > 0) |
|
642 NativeEndian::copyAndSwapToLittleEndian(dest, src, nelems); |
|
643 } |
|
644 |
|
645 template <> |
|
646 void |
|
647 copyAndSwapToLittleEndian(void *dest, const uint8_t *src, size_t nelems) |
|
648 { |
|
649 memcpy(dest, src, nelems); |
|
650 } |
|
651 |
|
652 template <class T> |
|
653 bool |
|
654 SCOutput::writeArray(const T *p, size_t nelems) |
|
655 { |
|
656 JS_ASSERT(8 % sizeof(T) == 0); |
|
657 JS_ASSERT(sizeof(uint64_t) % sizeof(T) == 0); |
|
658 |
|
659 if (nelems == 0) |
|
660 return true; |
|
661 |
|
662 if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) { |
|
663 js_ReportAllocationOverflow(context()); |
|
664 return false; |
|
665 } |
|
666 size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T)); |
|
667 size_t start = buf.length(); |
|
668 if (!buf.growByUninitialized(nwords)) |
|
669 return false; |
|
670 |
|
671 buf.back() = 0; /* zero-pad to an 8-byte boundary */ |
|
672 |
|
673 T *q = (T *) &buf[start]; |
|
674 copyAndSwapToLittleEndian(q, p, nelems); |
|
675 return true; |
|
676 } |
|
677 |
|
678 bool |
|
679 SCOutput::writeBytes(const void *p, size_t nbytes) |
|
680 { |
|
681 return writeArray((const uint8_t *) p, nbytes); |
|
682 } |
|
683 |
|
684 bool |
|
685 SCOutput::writeChars(const jschar *p, size_t nchars) |
|
686 { |
|
687 JS_ASSERT(sizeof(jschar) == sizeof(uint16_t)); |
|
688 return writeArray((const uint16_t *) p, nchars); |
|
689 } |
|
690 |
|
691 bool |
|
692 SCOutput::writePtr(const void *p) |
|
693 { |
|
694 return write(reinterpret_cast<uint64_t>(p)); |
|
695 } |
|
696 |
|
697 bool |
|
698 SCOutput::extractBuffer(uint64_t **datap, size_t *sizep) |
|
699 { |
|
700 *sizep = buf.length() * sizeof(uint64_t); |
|
701 return (*datap = buf.extractRawBuffer()) != nullptr; |
|
702 } |
|
703 |
|
704 } /* namespace js */ |
|
705 |
|
706 JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX); |
|
707 |
|
708 JSStructuredCloneWriter::~JSStructuredCloneWriter() |
|
709 { |
|
710 // Free any transferable data left lying around in the buffer |
|
711 uint64_t *data; |
|
712 size_t size; |
|
713 MOZ_ALWAYS_TRUE(extractBuffer(&data, &size)); |
|
714 ClearStructuredClone(data, size, callbacks, closure); |
|
715 } |
|
716 |
|
717 bool |
|
718 JSStructuredCloneWriter::parseTransferable() |
|
719 { |
|
720 MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data"); |
|
721 |
|
722 if (JSVAL_IS_NULL(transferable) || JSVAL_IS_VOID(transferable)) |
|
723 return true; |
|
724 |
|
725 if (!transferable.isObject()) |
|
726 return reportErrorTransferable(); |
|
727 |
|
728 JSContext *cx = context(); |
|
729 RootedObject array(cx, &transferable.toObject()); |
|
730 if (!JS_IsArrayObject(cx, array)) |
|
731 return reportErrorTransferable(); |
|
732 |
|
733 uint32_t length; |
|
734 if (!JS_GetArrayLength(cx, array, &length)) { |
|
735 return false; |
|
736 } |
|
737 |
|
738 RootedValue v(context()); |
|
739 |
|
740 for (uint32_t i = 0; i < length; ++i) { |
|
741 if (!JS_GetElement(cx, array, i, &v)) |
|
742 return false; |
|
743 |
|
744 if (!v.isObject()) |
|
745 return reportErrorTransferable(); |
|
746 |
|
747 RootedObject tObj(context(), CheckedUnwrap(&v.toObject())); |
|
748 |
|
749 if (!tObj) { |
|
750 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); |
|
751 return false; |
|
752 } |
|
753 |
|
754 // No duplicates allowed |
|
755 if (std::find(transferableObjects.begin(), transferableObjects.end(), tObj) != transferableObjects.end()) { |
|
756 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_DUP_TRANSFERABLE); |
|
757 return false; |
|
758 } |
|
759 |
|
760 if (!transferableObjects.append(tObj)) |
|
761 return false; |
|
762 } |
|
763 |
|
764 return true; |
|
765 } |
|
766 |
|
767 bool |
|
768 JSStructuredCloneWriter::reportErrorTransferable() |
|
769 { |
|
770 ReportErrorTransferable(context(), callbacks); |
|
771 return false; |
|
772 } |
|
773 |
|
774 bool |
|
775 JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str) |
|
776 { |
|
777 size_t length = str->length(); |
|
778 const jschar *chars = str->getChars(context()); |
|
779 if (!chars) |
|
780 return false; |
|
781 return out.writePair(tag, uint32_t(length)) && out.writeChars(chars, length); |
|
782 } |
|
783 |
|
784 bool |
|
785 JSStructuredCloneWriter::writeId(jsid id) |
|
786 { |
|
787 if (JSID_IS_INT(id)) |
|
788 return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id))); |
|
789 JS_ASSERT(JSID_IS_STRING(id)); |
|
790 return writeString(SCTAG_STRING, JSID_TO_STRING(id)); |
|
791 } |
|
792 |
|
793 inline void |
|
794 JSStructuredCloneWriter::checkStack() |
|
795 { |
|
796 #ifdef DEBUG |
|
797 /* To avoid making serialization O(n^2), limit stack-checking at 10. */ |
|
798 const size_t MAX = 10; |
|
799 |
|
800 size_t limit = Min(counts.length(), MAX); |
|
801 JS_ASSERT(objs.length() == counts.length()); |
|
802 size_t total = 0; |
|
803 for (size_t i = 0; i < limit; i++) { |
|
804 JS_ASSERT(total + counts[i] >= total); |
|
805 total += counts[i]; |
|
806 } |
|
807 if (counts.length() <= MAX) |
|
808 JS_ASSERT(total == ids.length()); |
|
809 else |
|
810 JS_ASSERT(total <= ids.length()); |
|
811 |
|
812 size_t j = objs.length(); |
|
813 for (size_t i = 0; i < limit; i++) |
|
814 JS_ASSERT(memory.has(&objs[--j].toObject())); |
|
815 #endif |
|
816 } |
|
817 |
|
818 /* |
|
819 * Write out a typed array. Note that post-v1 structured clone buffers do not |
|
820 * perform endianness conversion on stored data, so multibyte typed arrays |
|
821 * cannot be deserialized into a different endianness machine. Endianness |
|
822 * conversion would prevent sharing ArrayBuffers: if you have Int8Array and |
|
823 * Int16Array views of the same ArrayBuffer, should the data bytes be |
|
824 * byte-swapped when writing or not? The Int8Array requires them to not be |
|
825 * swapped; the Int16Array requires that they are. |
|
826 */ |
|
827 bool |
|
828 JSStructuredCloneWriter::writeTypedArray(HandleObject obj) |
|
829 { |
|
830 Rooted<TypedArrayObject*> tarr(context(), &obj->as<TypedArrayObject>()); |
|
831 |
|
832 if (!TypedArrayObject::ensureHasBuffer(context(), tarr)) |
|
833 return false; |
|
834 |
|
835 if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length())) |
|
836 return false; |
|
837 uint64_t type = tarr->type(); |
|
838 if (!out.write(type)) |
|
839 return false; |
|
840 |
|
841 // Write out the ArrayBuffer tag and contents |
|
842 RootedValue val(context(), TypedArrayObject::bufferValue(tarr)); |
|
843 if (!startWrite(val)) |
|
844 return false; |
|
845 |
|
846 return out.write(tarr->byteOffset()); |
|
847 } |
|
848 |
|
849 bool |
|
850 JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) |
|
851 { |
|
852 ArrayBufferObject &buffer = obj->as<ArrayBufferObject>(); |
|
853 |
|
854 return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) && |
|
855 out.writeBytes(buffer.dataPointer(), buffer.byteLength()); |
|
856 } |
|
857 |
|
858 bool |
|
859 JSStructuredCloneWriter::startObject(HandleObject obj, bool *backref) |
|
860 { |
|
861 /* Handle cycles in the object graph. */ |
|
862 CloneMemory::AddPtr p = memory.lookupForAdd(obj); |
|
863 if ((*backref = p)) |
|
864 return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value()); |
|
865 if (!memory.add(p, obj, memory.count())) |
|
866 return false; |
|
867 |
|
868 if (memory.count() == UINT32_MAX) { |
|
869 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
870 JSMSG_NEED_DIET, "object graph to serialize"); |
|
871 return false; |
|
872 } |
|
873 |
|
874 return true; |
|
875 } |
|
876 |
|
877 bool |
|
878 JSStructuredCloneWriter::traverseObject(HandleObject obj) |
|
879 { |
|
880 /* |
|
881 * Get enumerable property ids and put them in reverse order so that they |
|
882 * will come off the stack in forward order. |
|
883 */ |
|
884 size_t initialLength = ids.length(); |
|
885 if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids)) |
|
886 return false; |
|
887 jsid *begin = ids.begin() + initialLength, *end = ids.end(); |
|
888 size_t count = size_t(end - begin); |
|
889 Reverse(begin, end); |
|
890 |
|
891 /* Push obj and count to the stack. */ |
|
892 if (!objs.append(ObjectValue(*obj)) || !counts.append(count)) |
|
893 return false; |
|
894 checkStack(); |
|
895 |
|
896 /* Write the header for obj. */ |
|
897 return out.writePair(obj->is<ArrayObject>() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0); |
|
898 } |
|
899 |
|
900 static bool |
|
901 PrimitiveToObject(JSContext *cx, Value *vp) |
|
902 { |
|
903 JSObject *obj = PrimitiveToObject(cx, *vp); |
|
904 if (!obj) |
|
905 return false; |
|
906 |
|
907 vp->setObject(*obj); |
|
908 return true; |
|
909 } |
|
910 |
|
911 bool |
|
912 JSStructuredCloneWriter::startWrite(const Value &v) |
|
913 { |
|
914 assertSameCompartment(context(), v); |
|
915 |
|
916 if (v.isString()) { |
|
917 return writeString(SCTAG_STRING, v.toString()); |
|
918 } else if (v.isNumber()) { |
|
919 return out.writeDouble(v.toNumber()); |
|
920 } else if (v.isBoolean()) { |
|
921 return out.writePair(SCTAG_BOOLEAN, v.toBoolean()); |
|
922 } else if (v.isNull()) { |
|
923 return out.writePair(SCTAG_NULL, 0); |
|
924 } else if (v.isUndefined()) { |
|
925 return out.writePair(SCTAG_UNDEFINED, 0); |
|
926 } else if (v.isObject()) { |
|
927 RootedObject obj(context(), &v.toObject()); |
|
928 |
|
929 // The object might be a security wrapper. See if we can clone what's |
|
930 // behind it. If we can, unwrap the object. |
|
931 obj = CheckedUnwrap(obj); |
|
932 if (!obj) { |
|
933 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); |
|
934 return false; |
|
935 } |
|
936 |
|
937 AutoCompartment ac(context(), obj); |
|
938 |
|
939 bool backref; |
|
940 if (!startObject(obj, &backref)) |
|
941 return false; |
|
942 if (backref) |
|
943 return true; |
|
944 |
|
945 if (obj->is<RegExpObject>()) { |
|
946 RegExpObject &reobj = obj->as<RegExpObject>(); |
|
947 return out.writePair(SCTAG_REGEXP_OBJECT, reobj.getFlags()) && |
|
948 writeString(SCTAG_STRING, reobj.getSource()); |
|
949 } else if (obj->is<DateObject>()) { |
|
950 double d = js_DateGetMsecSinceEpoch(obj); |
|
951 return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d); |
|
952 } else if (obj->is<TypedArrayObject>()) { |
|
953 return writeTypedArray(obj); |
|
954 } else if (obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().hasData()) { |
|
955 return writeArrayBuffer(obj); |
|
956 } else if (obj->is<JSObject>() || obj->is<ArrayObject>()) { |
|
957 return traverseObject(obj); |
|
958 } else if (obj->is<BooleanObject>()) { |
|
959 return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->as<BooleanObject>().unbox()); |
|
960 } else if (obj->is<NumberObject>()) { |
|
961 return out.writePair(SCTAG_NUMBER_OBJECT, 0) && |
|
962 out.writeDouble(obj->as<NumberObject>().unbox()); |
|
963 } else if (obj->is<StringObject>()) { |
|
964 return writeString(SCTAG_STRING_OBJECT, obj->as<StringObject>().unbox()); |
|
965 } |
|
966 |
|
967 if (callbacks && callbacks->write) |
|
968 return callbacks->write(context(), this, obj, closure); |
|
969 /* else fall through */ |
|
970 } |
|
971 |
|
972 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE); |
|
973 return false; |
|
974 } |
|
975 |
|
976 bool |
|
977 JSStructuredCloneWriter::writeTransferMap() |
|
978 { |
|
979 if (transferableObjects.empty()) |
|
980 return true; |
|
981 |
|
982 if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) |
|
983 return false; |
|
984 |
|
985 if (!out.write(transferableObjects.length())) |
|
986 return false; |
|
987 |
|
988 for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) { |
|
989 JSObject *obj = tr.front(); |
|
990 |
|
991 if (!memory.put(obj, memory.count())) |
|
992 return false; |
|
993 |
|
994 // Emit a placeholder pointer. We will steal the data and neuter the |
|
995 // transferable later, in the case of ArrayBufferObject. |
|
996 if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED)) |
|
997 return false; |
|
998 if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents or to SharedArrayRawBuffer. |
|
999 return false; |
|
1000 if (!out.write(0)) // extraData |
|
1001 return false; |
|
1002 } |
|
1003 |
|
1004 return true; |
|
1005 } |
|
1006 |
|
1007 bool |
|
1008 JSStructuredCloneWriter::transferOwnership() |
|
1009 { |
|
1010 if (transferableObjects.empty()) |
|
1011 return true; |
|
1012 |
|
1013 // Walk along the transferables and the transfer map at the same time, |
|
1014 // grabbing out pointers from the transferables and stuffing them into the |
|
1015 // transfer map. |
|
1016 uint64_t *point = out.rawBuffer(); |
|
1017 JS_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_HEADER); |
|
1018 point++; |
|
1019 JS_ASSERT(LittleEndian::readUint64(point) == transferableObjects.length()); |
|
1020 point++; |
|
1021 |
|
1022 for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) { |
|
1023 RootedObject obj(context(), tr.front()); |
|
1024 |
|
1025 uint32_t tag; |
|
1026 JS::TransferableOwnership ownership; |
|
1027 void *content; |
|
1028 uint64_t extraData; |
|
1029 |
|
1030 #if DEBUG |
|
1031 SCInput::getPair(point, &tag, (uint32_t*) &ownership); |
|
1032 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY); |
|
1033 MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED); |
|
1034 #endif |
|
1035 |
|
1036 if (obj->is<ArrayBufferObject>()) { |
|
1037 size_t nbytes = obj->as<ArrayBufferObject>().byteLength(); |
|
1038 content = JS_StealArrayBufferContents(context(), obj); |
|
1039 if (!content) |
|
1040 return false; // Destructor will clean up the already-transferred data |
|
1041 tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER; |
|
1042 if (obj->as<ArrayBufferObject>().isMappedArrayBuffer()) |
|
1043 ownership = JS::SCTAG_TMO_MAPPED_DATA; |
|
1044 else |
|
1045 ownership = JS::SCTAG_TMO_ALLOC_DATA; |
|
1046 extraData = nbytes; |
|
1047 } else if (obj->is<SharedArrayBufferObject>()) { |
|
1048 SharedArrayRawBuffer *rawbuf = obj->as<SharedArrayBufferObject>().rawBufferObject(); |
|
1049 |
|
1050 // Avoids a race condition where the parent thread frees the buffer |
|
1051 // before the child has accepted the transferable. |
|
1052 rawbuf->addReference(); |
|
1053 |
|
1054 tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER; |
|
1055 ownership = JS::SCTAG_TMO_SHARED_BUFFER; |
|
1056 content = rawbuf; |
|
1057 extraData = 0; |
|
1058 } else { |
|
1059 if (!callbacks || !callbacks->writeTransfer) |
|
1060 return reportErrorTransferable(); |
|
1061 if (!callbacks->writeTransfer(context(), obj, closure, &tag, &ownership, &content, &extraData)) |
|
1062 return false; |
|
1063 JS_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY); |
|
1064 } |
|
1065 |
|
1066 LittleEndian::writeUint64(point++, PairToUInt64(tag, ownership)); |
|
1067 LittleEndian::writeUint64(point++, reinterpret_cast<uint64_t>(content)); |
|
1068 LittleEndian::writeUint64(point++, extraData); |
|
1069 } |
|
1070 |
|
1071 JS_ASSERT(point <= out.rawBuffer() + out.count()); |
|
1072 JS_ASSERT_IF(point < out.rawBuffer() + out.count(), |
|
1073 uint32_t(LittleEndian::readUint64(point) >> 32) < SCTAG_TRANSFER_MAP_HEADER); |
|
1074 |
|
1075 return true; |
|
1076 } |
|
1077 |
|
1078 bool |
|
1079 JSStructuredCloneWriter::write(const Value &v) |
|
1080 { |
|
1081 if (!startWrite(v)) |
|
1082 return false; |
|
1083 |
|
1084 while (!counts.empty()) { |
|
1085 RootedObject obj(context(), &objs.back().toObject()); |
|
1086 AutoCompartment ac(context(), obj); |
|
1087 if (counts.back()) { |
|
1088 counts.back()--; |
|
1089 RootedId id(context(), ids.back()); |
|
1090 ids.popBack(); |
|
1091 checkStack(); |
|
1092 if (JSID_IS_STRING(id) || JSID_IS_INT(id)) { |
|
1093 /* |
|
1094 * If obj still has an own property named id, write it out. |
|
1095 * The cost of re-checking could be avoided by using |
|
1096 * NativeIterators. |
|
1097 */ |
|
1098 bool found; |
|
1099 if (!HasOwnProperty(context(), obj, id, &found)) |
|
1100 return false; |
|
1101 |
|
1102 if (found) { |
|
1103 RootedValue val(context()); |
|
1104 if (!writeId(id) || |
|
1105 !JSObject::getGeneric(context(), obj, obj, id, &val) || |
|
1106 !startWrite(val)) |
|
1107 return false; |
|
1108 } |
|
1109 } |
|
1110 } else { |
|
1111 out.writePair(SCTAG_NULL, 0); |
|
1112 objs.popBack(); |
|
1113 counts.popBack(); |
|
1114 } |
|
1115 } |
|
1116 |
|
1117 memory.clear(); |
|
1118 return transferOwnership(); |
|
1119 } |
|
1120 |
|
1121 bool |
|
1122 JSStructuredCloneReader::checkDouble(double d) |
|
1123 { |
|
1124 jsval_layout l; |
|
1125 l.asDouble = d; |
|
1126 if (!JSVAL_IS_DOUBLE_IMPL(l)) { |
|
1127 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
1128 JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN"); |
|
1129 return false; |
|
1130 } |
|
1131 return true; |
|
1132 } |
|
1133 |
|
1134 namespace { |
|
1135 |
|
1136 class Chars { |
|
1137 JSContext *cx; |
|
1138 jschar *p; |
|
1139 public: |
|
1140 Chars(JSContext *cx) : cx(cx), p(nullptr) {} |
|
1141 ~Chars() { js_free(p); } |
|
1142 |
|
1143 bool allocate(size_t len) { |
|
1144 JS_ASSERT(!p); |
|
1145 // We're going to null-terminate! |
|
1146 p = cx->pod_malloc<jschar>(len + 1); |
|
1147 if (p) { |
|
1148 p[len] = jschar(0); |
|
1149 return true; |
|
1150 } |
|
1151 return false; |
|
1152 } |
|
1153 jschar *get() { return p; } |
|
1154 void forget() { p = nullptr; } |
|
1155 }; |
|
1156 |
|
1157 } /* anonymous namespace */ |
|
1158 |
|
1159 JSString * |
|
1160 JSStructuredCloneReader::readString(uint32_t nchars) |
|
1161 { |
|
1162 if (nchars > JSString::MAX_LENGTH) { |
|
1163 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
1164 JSMSG_SC_BAD_SERIALIZED_DATA, "string length"); |
|
1165 return nullptr; |
|
1166 } |
|
1167 Chars chars(context()); |
|
1168 if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars)) |
|
1169 return nullptr; |
|
1170 JSString *str = js_NewString<CanGC>(context(), chars.get(), nchars); |
|
1171 if (str) |
|
1172 chars.forget(); |
|
1173 return str; |
|
1174 } |
|
1175 |
|
1176 static uint32_t |
|
1177 TagToV1ArrayType(uint32_t tag) |
|
1178 { |
|
1179 JS_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX); |
|
1180 return tag - SCTAG_TYPED_ARRAY_V1_MIN; |
|
1181 } |
|
1182 |
|
1183 bool |
|
1184 JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp, |
|
1185 bool v1Read) |
|
1186 { |
|
1187 if (arrayType > ScalarTypeDescr::TYPE_UINT8_CLAMPED) { |
|
1188 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
1189 JSMSG_SC_BAD_SERIALIZED_DATA, "unhandled typed array element type"); |
|
1190 return false; |
|
1191 } |
|
1192 |
|
1193 // Push a placeholder onto the allObjs list to stand in for the typed array |
|
1194 uint32_t placeholderIndex = allObjs.length(); |
|
1195 Value dummy = JSVAL_NULL; |
|
1196 if (!allObjs.append(dummy)) |
|
1197 return false; |
|
1198 |
|
1199 // Read the ArrayBuffer object and its contents (but no properties) |
|
1200 RootedValue v(context()); |
|
1201 uint32_t byteOffset; |
|
1202 if (v1Read) { |
|
1203 if (!readV1ArrayBuffer(arrayType, nelems, v.address())) |
|
1204 return false; |
|
1205 byteOffset = 0; |
|
1206 } else { |
|
1207 if (!startRead(v.address())) |
|
1208 return false; |
|
1209 uint64_t n; |
|
1210 if (!in.read(&n)) |
|
1211 return false; |
|
1212 byteOffset = n; |
|
1213 } |
|
1214 RootedObject buffer(context(), &v.toObject()); |
|
1215 RootedObject obj(context(), nullptr); |
|
1216 |
|
1217 switch (arrayType) { |
|
1218 case ScalarTypeDescr::TYPE_INT8: |
|
1219 obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1220 break; |
|
1221 case ScalarTypeDescr::TYPE_UINT8: |
|
1222 obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1223 break; |
|
1224 case ScalarTypeDescr::TYPE_INT16: |
|
1225 obj = JS_NewInt16ArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1226 break; |
|
1227 case ScalarTypeDescr::TYPE_UINT16: |
|
1228 obj = JS_NewUint16ArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1229 break; |
|
1230 case ScalarTypeDescr::TYPE_INT32: |
|
1231 obj = JS_NewInt32ArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1232 break; |
|
1233 case ScalarTypeDescr::TYPE_UINT32: |
|
1234 obj = JS_NewUint32ArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1235 break; |
|
1236 case ScalarTypeDescr::TYPE_FLOAT32: |
|
1237 obj = JS_NewFloat32ArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1238 break; |
|
1239 case ScalarTypeDescr::TYPE_FLOAT64: |
|
1240 obj = JS_NewFloat64ArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1241 break; |
|
1242 case ScalarTypeDescr::TYPE_UINT8_CLAMPED: |
|
1243 obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems); |
|
1244 break; |
|
1245 default: |
|
1246 MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type"); |
|
1247 } |
|
1248 |
|
1249 if (!obj) |
|
1250 return false; |
|
1251 vp->setObject(*obj); |
|
1252 |
|
1253 allObjs[placeholderIndex] = *vp; |
|
1254 |
|
1255 return true; |
|
1256 } |
|
1257 |
|
1258 bool |
|
1259 JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp) |
|
1260 { |
|
1261 JSObject *obj = ArrayBufferObject::create(context(), nbytes); |
|
1262 if (!obj) |
|
1263 return false; |
|
1264 vp->setObject(*obj); |
|
1265 ArrayBufferObject &buffer = obj->as<ArrayBufferObject>(); |
|
1266 JS_ASSERT(buffer.byteLength() == nbytes); |
|
1267 return in.readArray(buffer.dataPointer(), nbytes); |
|
1268 } |
|
1269 |
|
1270 static size_t |
|
1271 bytesPerTypedArrayElement(uint32_t arrayType) |
|
1272 { |
|
1273 switch (arrayType) { |
|
1274 case ScalarTypeDescr::TYPE_INT8: |
|
1275 case ScalarTypeDescr::TYPE_UINT8: |
|
1276 case ScalarTypeDescr::TYPE_UINT8_CLAMPED: |
|
1277 return sizeof(uint8_t); |
|
1278 case ScalarTypeDescr::TYPE_INT16: |
|
1279 case ScalarTypeDescr::TYPE_UINT16: |
|
1280 return sizeof(uint16_t); |
|
1281 case ScalarTypeDescr::TYPE_INT32: |
|
1282 case ScalarTypeDescr::TYPE_UINT32: |
|
1283 case ScalarTypeDescr::TYPE_FLOAT32: |
|
1284 return sizeof(uint32_t); |
|
1285 case ScalarTypeDescr::TYPE_FLOAT64: |
|
1286 return sizeof(uint64_t); |
|
1287 default: |
|
1288 MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type"); |
|
1289 } |
|
1290 } |
|
1291 |
|
1292 /* |
|
1293 * Read in the data for a structured clone version 1 ArrayBuffer, performing |
|
1294 * endianness-conversion while reading. |
|
1295 */ |
|
1296 bool |
|
1297 JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp) |
|
1298 { |
|
1299 JS_ASSERT(arrayType <= ScalarTypeDescr::TYPE_UINT8_CLAMPED); |
|
1300 |
|
1301 uint32_t nbytes = nelems * bytesPerTypedArrayElement(arrayType); |
|
1302 JSObject *obj = ArrayBufferObject::create(context(), nbytes); |
|
1303 if (!obj) |
|
1304 return false; |
|
1305 vp->setObject(*obj); |
|
1306 ArrayBufferObject &buffer = obj->as<ArrayBufferObject>(); |
|
1307 JS_ASSERT(buffer.byteLength() == nbytes); |
|
1308 |
|
1309 switch (arrayType) { |
|
1310 case ScalarTypeDescr::TYPE_INT8: |
|
1311 case ScalarTypeDescr::TYPE_UINT8: |
|
1312 case ScalarTypeDescr::TYPE_UINT8_CLAMPED: |
|
1313 return in.readArray((uint8_t*) buffer.dataPointer(), nelems); |
|
1314 case ScalarTypeDescr::TYPE_INT16: |
|
1315 case ScalarTypeDescr::TYPE_UINT16: |
|
1316 return in.readArray((uint16_t*) buffer.dataPointer(), nelems); |
|
1317 case ScalarTypeDescr::TYPE_INT32: |
|
1318 case ScalarTypeDescr::TYPE_UINT32: |
|
1319 case ScalarTypeDescr::TYPE_FLOAT32: |
|
1320 return in.readArray((uint32_t*) buffer.dataPointer(), nelems); |
|
1321 case ScalarTypeDescr::TYPE_FLOAT64: |
|
1322 return in.readArray((uint64_t*) buffer.dataPointer(), nelems); |
|
1323 default: |
|
1324 MOZ_ASSUME_UNREACHABLE("unknown TypedArrayObject type"); |
|
1325 } |
|
1326 } |
|
1327 |
|
1328 bool |
|
1329 JSStructuredCloneReader::startRead(Value *vp) |
|
1330 { |
|
1331 uint32_t tag, data; |
|
1332 |
|
1333 if (!in.readPair(&tag, &data)) |
|
1334 return false; |
|
1335 switch (tag) { |
|
1336 case SCTAG_NULL: |
|
1337 vp->setNull(); |
|
1338 break; |
|
1339 |
|
1340 case SCTAG_UNDEFINED: |
|
1341 vp->setUndefined(); |
|
1342 break; |
|
1343 |
|
1344 case SCTAG_BOOLEAN: |
|
1345 case SCTAG_BOOLEAN_OBJECT: |
|
1346 vp->setBoolean(!!data); |
|
1347 if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp)) |
|
1348 return false; |
|
1349 break; |
|
1350 |
|
1351 case SCTAG_STRING: |
|
1352 case SCTAG_STRING_OBJECT: { |
|
1353 JSString *str = readString(data); |
|
1354 if (!str) |
|
1355 return false; |
|
1356 vp->setString(str); |
|
1357 if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp)) |
|
1358 return false; |
|
1359 break; |
|
1360 } |
|
1361 |
|
1362 case SCTAG_NUMBER_OBJECT: { |
|
1363 double d; |
|
1364 if (!in.readDouble(&d) || !checkDouble(d)) |
|
1365 return false; |
|
1366 vp->setDouble(d); |
|
1367 if (!PrimitiveToObject(context(), vp)) |
|
1368 return false; |
|
1369 break; |
|
1370 } |
|
1371 |
|
1372 case SCTAG_DATE_OBJECT: { |
|
1373 double d; |
|
1374 if (!in.readDouble(&d) || !checkDouble(d)) |
|
1375 return false; |
|
1376 if (!IsNaN(d) && d != TimeClip(d)) { |
|
1377 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
1378 JSMSG_SC_BAD_SERIALIZED_DATA, "date"); |
|
1379 return false; |
|
1380 } |
|
1381 JSObject *obj = js_NewDateObjectMsec(context(), d); |
|
1382 if (!obj) |
|
1383 return false; |
|
1384 vp->setObject(*obj); |
|
1385 break; |
|
1386 } |
|
1387 |
|
1388 case SCTAG_REGEXP_OBJECT: { |
|
1389 RegExpFlag flags = RegExpFlag(data); |
|
1390 uint32_t tag2, nchars; |
|
1391 if (!in.readPair(&tag2, &nchars)) |
|
1392 return false; |
|
1393 if (tag2 != SCTAG_STRING) { |
|
1394 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
1395 JSMSG_SC_BAD_SERIALIZED_DATA, "regexp"); |
|
1396 return false; |
|
1397 } |
|
1398 JSString *str = readString(nchars); |
|
1399 if (!str) |
|
1400 return false; |
|
1401 JSFlatString *flat = str->ensureFlat(context()); |
|
1402 if (!flat) |
|
1403 return false; |
|
1404 |
|
1405 RegExpObject *reobj = RegExpObject::createNoStatics(context(), flat->chars(), |
|
1406 flat->length(), flags, nullptr); |
|
1407 if (!reobj) |
|
1408 return false; |
|
1409 vp->setObject(*reobj); |
|
1410 break; |
|
1411 } |
|
1412 |
|
1413 case SCTAG_ARRAY_OBJECT: |
|
1414 case SCTAG_OBJECT_OBJECT: { |
|
1415 JSObject *obj = (tag == SCTAG_ARRAY_OBJECT) |
|
1416 ? NewDenseEmptyArray(context()) |
|
1417 : NewBuiltinClassInstance(context(), &JSObject::class_); |
|
1418 if (!obj || !objs.append(ObjectValue(*obj))) |
|
1419 return false; |
|
1420 vp->setObject(*obj); |
|
1421 break; |
|
1422 } |
|
1423 |
|
1424 case SCTAG_BACK_REFERENCE_OBJECT: { |
|
1425 if (data >= allObjs.length()) { |
|
1426 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
1427 JSMSG_SC_BAD_SERIALIZED_DATA, |
|
1428 "invalid back reference in input"); |
|
1429 return false; |
|
1430 } |
|
1431 *vp = allObjs[data]; |
|
1432 return true; |
|
1433 } |
|
1434 |
|
1435 case SCTAG_TRANSFER_MAP_HEADER: |
|
1436 case SCTAG_TRANSFER_MAP_PENDING_ENTRY: |
|
1437 // We should be past all the transfer map tags. |
|
1438 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, |
|
1439 JSMSG_SC_BAD_SERIALIZED_DATA, |
|
1440 "invalid input"); |
|
1441 return false; |
|
1442 |
|
1443 case SCTAG_ARRAY_BUFFER_OBJECT: |
|
1444 if (!readArrayBuffer(data, vp)) |
|
1445 return false; |
|
1446 break; |
|
1447 |
|
1448 case SCTAG_TYPED_ARRAY_OBJECT: |
|
1449 // readTypedArray adds the array to allObjs |
|
1450 uint64_t arrayType; |
|
1451 if (!in.read(&arrayType)) |
|
1452 return false; |
|
1453 return readTypedArray(arrayType, data, vp); |
|
1454 break; |
|
1455 |
|
1456 default: { |
|
1457 if (tag <= SCTAG_FLOAT_MAX) { |
|
1458 double d = ReinterpretPairAsDouble(tag, data); |
|
1459 if (!checkDouble(d)) |
|
1460 return false; |
|
1461 vp->setNumber(d); |
|
1462 break; |
|
1463 } |
|
1464 |
|
1465 if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) { |
|
1466 // A v1-format typed array |
|
1467 // readTypedArray adds the array to allObjs |
|
1468 return readTypedArray(TagToV1ArrayType(tag), data, vp, true); |
|
1469 } |
|
1470 |
|
1471 if (!callbacks || !callbacks->read) { |
|
1472 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
1473 JSMSG_SC_BAD_SERIALIZED_DATA, "unsupported type"); |
|
1474 return false; |
|
1475 } |
|
1476 JSObject *obj = callbacks->read(context(), this, tag, data, closure); |
|
1477 if (!obj) |
|
1478 return false; |
|
1479 vp->setObject(*obj); |
|
1480 } |
|
1481 } |
|
1482 |
|
1483 if (vp->isObject() && !allObjs.append(*vp)) |
|
1484 return false; |
|
1485 |
|
1486 return true; |
|
1487 } |
|
1488 |
|
1489 bool |
|
1490 JSStructuredCloneReader::readId(jsid *idp) |
|
1491 { |
|
1492 uint32_t tag, data; |
|
1493 if (!in.readPair(&tag, &data)) |
|
1494 return false; |
|
1495 |
|
1496 if (tag == SCTAG_INDEX) { |
|
1497 *idp = INT_TO_JSID(int32_t(data)); |
|
1498 return true; |
|
1499 } |
|
1500 if (tag == SCTAG_STRING) { |
|
1501 JSString *str = readString(data); |
|
1502 if (!str) |
|
1503 return false; |
|
1504 JSAtom *atom = AtomizeString(context(), str); |
|
1505 if (!atom) |
|
1506 return false; |
|
1507 *idp = NON_INTEGER_ATOM_TO_JSID(atom); |
|
1508 return true; |
|
1509 } |
|
1510 if (tag == SCTAG_NULL) { |
|
1511 *idp = JSID_VOID; |
|
1512 return true; |
|
1513 } |
|
1514 JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, |
|
1515 JSMSG_SC_BAD_SERIALIZED_DATA, "id"); |
|
1516 return false; |
|
1517 } |
|
1518 |
|
1519 bool |
|
1520 JSStructuredCloneReader::readTransferMap() |
|
1521 { |
|
1522 JSContext *cx = context(); |
|
1523 uint64_t *headerPos = in.tell(); |
|
1524 |
|
1525 uint32_t tag, data; |
|
1526 if (!in.getPair(&tag, &data)) |
|
1527 return in.reportTruncated(); |
|
1528 |
|
1529 if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) |
|
1530 return true; |
|
1531 |
|
1532 uint64_t numTransferables; |
|
1533 MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); |
|
1534 if (!in.read(&numTransferables)) |
|
1535 return false; |
|
1536 |
|
1537 for (uint64_t i = 0; i < numTransferables; i++) { |
|
1538 uint64_t *pos = in.tell(); |
|
1539 |
|
1540 if (!in.readPair(&tag, &data)) |
|
1541 return false; |
|
1542 |
|
1543 JS_ASSERT(tag != SCTAG_TRANSFER_MAP_PENDING_ENTRY); |
|
1544 RootedObject obj(cx); |
|
1545 |
|
1546 void *content; |
|
1547 if (!in.readPtr(&content)) |
|
1548 return false; |
|
1549 |
|
1550 uint64_t extraData; |
|
1551 if (!in.read(&extraData)) |
|
1552 return false; |
|
1553 |
|
1554 if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) { |
|
1555 size_t nbytes = extraData; |
|
1556 JS_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA || |
|
1557 data == JS::SCTAG_TMO_MAPPED_DATA); |
|
1558 if (data == JS::SCTAG_TMO_ALLOC_DATA) |
|
1559 obj = JS_NewArrayBufferWithContents(cx, nbytes, content); |
|
1560 else if (data == JS::SCTAG_TMO_MAPPED_DATA) |
|
1561 obj = JS_NewMappedArrayBufferWithContents(cx, nbytes, content); |
|
1562 } else if (tag == SCTAG_TRANSFER_MAP_SHARED_BUFFER) { |
|
1563 JS_ASSERT(data == JS::SCTAG_TMO_SHARED_BUFFER); |
|
1564 obj = SharedArrayBufferObject::New(context(), (SharedArrayRawBuffer *)content); |
|
1565 } else { |
|
1566 if (!callbacks || !callbacks->readTransfer) { |
|
1567 ReportErrorTransferable(cx, callbacks); |
|
1568 return false; |
|
1569 } |
|
1570 if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure, &obj)) |
|
1571 return false; |
|
1572 MOZ_ASSERT(obj); |
|
1573 MOZ_ASSERT(!cx->isExceptionPending()); |
|
1574 } |
|
1575 |
|
1576 // On failure, the buffer will still own the data (since its ownership will not get set to SCTAG_TMO_UNOWNED), |
|
1577 // so the data will be freed by ClearStructuredClone |
|
1578 if (!obj) |
|
1579 return false; |
|
1580 |
|
1581 // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input |
|
1582 // buffer. |
|
1583 *pos = PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED); |
|
1584 MOZ_ASSERT(headerPos < pos && pos < in.end()); |
|
1585 |
|
1586 if (!allObjs.append(ObjectValue(*obj))) |
|
1587 return false; |
|
1588 } |
|
1589 |
|
1590 // Mark the whole transfer map as consumed. |
|
1591 MOZ_ASSERT(headerPos <= in.tell()); |
|
1592 #ifdef DEBUG |
|
1593 SCInput::getPair(headerPos, &tag, &data); |
|
1594 MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER); |
|
1595 MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED); |
|
1596 #endif |
|
1597 *headerPos = PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED); |
|
1598 |
|
1599 return true; |
|
1600 } |
|
1601 |
|
1602 bool |
|
1603 JSStructuredCloneReader::read(Value *vp) |
|
1604 { |
|
1605 if (!readTransferMap()) |
|
1606 return false; |
|
1607 |
|
1608 if (!startRead(vp)) |
|
1609 return false; |
|
1610 |
|
1611 while (objs.length() != 0) { |
|
1612 RootedObject obj(context(), &objs.back().toObject()); |
|
1613 |
|
1614 RootedId id(context()); |
|
1615 if (!readId(id.address())) |
|
1616 return false; |
|
1617 |
|
1618 if (JSID_IS_VOID(id)) { |
|
1619 objs.popBack(); |
|
1620 } else { |
|
1621 RootedValue v(context()); |
|
1622 if (!startRead(v.address()) || !JSObject::defineGeneric(context(), obj, id, v)) |
|
1623 return false; |
|
1624 } |
|
1625 } |
|
1626 |
|
1627 allObjs.clear(); |
|
1628 |
|
1629 return true; |
|
1630 } |
|
1631 |
|
1632 using namespace js; |
|
1633 |
|
1634 JS_PUBLIC_API(bool) |
|
1635 JS_ReadStructuredClone(JSContext *cx, uint64_t *buf, size_t nbytes, |
|
1636 uint32_t version, MutableHandleValue vp, |
|
1637 const JSStructuredCloneCallbacks *optionalCallbacks, |
|
1638 void *closure) |
|
1639 { |
|
1640 AssertHeapIsIdle(cx); |
|
1641 CHECK_REQUEST(cx); |
|
1642 |
|
1643 if (version > JS_STRUCTURED_CLONE_VERSION) { |
|
1644 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_CLONE_VERSION); |
|
1645 return false; |
|
1646 } |
|
1647 const JSStructuredCloneCallbacks *callbacks = |
|
1648 optionalCallbacks ? |
|
1649 optionalCallbacks : |
|
1650 cx->runtime()->structuredCloneCallbacks; |
|
1651 return ReadStructuredClone(cx, buf, nbytes, vp, callbacks, closure); |
|
1652 } |
|
1653 |
|
1654 JS_PUBLIC_API(bool) |
|
1655 JS_WriteStructuredClone(JSContext *cx, HandleValue value, uint64_t **bufp, size_t *nbytesp, |
|
1656 const JSStructuredCloneCallbacks *optionalCallbacks, |
|
1657 void *closure, HandleValue transferable) |
|
1658 { |
|
1659 AssertHeapIsIdle(cx); |
|
1660 CHECK_REQUEST(cx); |
|
1661 assertSameCompartment(cx, value); |
|
1662 |
|
1663 const JSStructuredCloneCallbacks *callbacks = |
|
1664 optionalCallbacks ? |
|
1665 optionalCallbacks : |
|
1666 cx->runtime()->structuredCloneCallbacks; |
|
1667 return WriteStructuredClone(cx, value, bufp, nbytesp, callbacks, closure, transferable); |
|
1668 } |
|
1669 |
|
1670 JS_PUBLIC_API(bool) |
|
1671 JS_ClearStructuredClone(uint64_t *data, size_t nbytes, |
|
1672 const JSStructuredCloneCallbacks *optionalCallbacks, |
|
1673 void *closure) |
|
1674 { |
|
1675 ClearStructuredClone(data, nbytes, optionalCallbacks, closure); |
|
1676 return true; |
|
1677 } |
|
1678 |
|
1679 JS_PUBLIC_API(bool) |
|
1680 JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes, |
|
1681 bool *hasTransferable) |
|
1682 { |
|
1683 bool transferable; |
|
1684 if (!StructuredCloneHasTransferObjects(data, nbytes, &transferable)) |
|
1685 return false; |
|
1686 |
|
1687 *hasTransferable = transferable; |
|
1688 return true; |
|
1689 } |
|
1690 |
|
1691 JS_PUBLIC_API(bool) |
|
1692 JS_StructuredClone(JSContext *cx, HandleValue value, MutableHandleValue vp, |
|
1693 const JSStructuredCloneCallbacks *optionalCallbacks, |
|
1694 void *closure) |
|
1695 { |
|
1696 AssertHeapIsIdle(cx); |
|
1697 CHECK_REQUEST(cx); |
|
1698 |
|
1699 // Strings are associated with zones, not compartments, |
|
1700 // so we copy the string by wrapping it. |
|
1701 if (value.isString()) { |
|
1702 RootedString strValue(cx, value.toString()); |
|
1703 if (!cx->compartment()->wrap(cx, strValue.address())) { |
|
1704 return false; |
|
1705 } |
|
1706 vp.setString(strValue); |
|
1707 return true; |
|
1708 } |
|
1709 |
|
1710 const JSStructuredCloneCallbacks *callbacks = |
|
1711 optionalCallbacks ? |
|
1712 optionalCallbacks : |
|
1713 cx->runtime()->structuredCloneCallbacks; |
|
1714 |
|
1715 JSAutoStructuredCloneBuffer buf; |
|
1716 { |
|
1717 // If we use Maybe<AutoCompartment> here, G++ can't tell that the |
|
1718 // destructor is only called when Maybe::construct was called, and |
|
1719 // we get warnings about using uninitialized variables. |
|
1720 if (value.isObject()) { |
|
1721 AutoCompartment ac(cx, &value.toObject()); |
|
1722 if (!buf.write(cx, value, callbacks, closure)) |
|
1723 return false; |
|
1724 } else { |
|
1725 if (!buf.write(cx, value, callbacks, closure)) |
|
1726 return false; |
|
1727 } |
|
1728 } |
|
1729 |
|
1730 return buf.read(cx, vp, callbacks, closure); |
|
1731 } |
|
1732 |
|
1733 JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer &&other) |
|
1734 { |
|
1735 other.steal(&data_, &nbytes_, &version_); |
|
1736 } |
|
1737 |
|
1738 JSAutoStructuredCloneBuffer& |
|
1739 JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer &&other) |
|
1740 { |
|
1741 JS_ASSERT(&other != this); |
|
1742 clear(); |
|
1743 other.steal(&data_, &nbytes_, &version_); |
|
1744 return *this; |
|
1745 } |
|
1746 |
|
1747 void |
|
1748 JSAutoStructuredCloneBuffer::clear() |
|
1749 { |
|
1750 if (data_) { |
|
1751 ClearStructuredClone(data_, nbytes_, callbacks_, closure_); |
|
1752 data_ = nullptr; |
|
1753 nbytes_ = 0; |
|
1754 version_ = 0; |
|
1755 } |
|
1756 } |
|
1757 |
|
1758 bool |
|
1759 JSAutoStructuredCloneBuffer::copy(const uint64_t *srcData, size_t nbytes, uint32_t version) |
|
1760 { |
|
1761 // transferable objects cannot be copied |
|
1762 bool hasTransferable; |
|
1763 if (!StructuredCloneHasTransferObjects(data_, nbytes_, &hasTransferable) || |
|
1764 hasTransferable) |
|
1765 return false; |
|
1766 |
|
1767 uint64_t *newData = static_cast<uint64_t *>(js_malloc(nbytes)); |
|
1768 if (!newData) |
|
1769 return false; |
|
1770 |
|
1771 js_memcpy(newData, srcData, nbytes); |
|
1772 |
|
1773 clear(); |
|
1774 data_ = newData; |
|
1775 nbytes_ = nbytes; |
|
1776 version_ = version; |
|
1777 return true; |
|
1778 } |
|
1779 |
|
1780 void |
|
1781 JSAutoStructuredCloneBuffer::adopt(uint64_t *data, size_t nbytes, uint32_t version) |
|
1782 { |
|
1783 clear(); |
|
1784 data_ = data; |
|
1785 nbytes_ = nbytes; |
|
1786 version_ = version; |
|
1787 } |
|
1788 |
|
1789 void |
|
1790 JSAutoStructuredCloneBuffer::steal(uint64_t **datap, size_t *nbytesp, uint32_t *versionp) |
|
1791 { |
|
1792 *datap = data_; |
|
1793 *nbytesp = nbytes_; |
|
1794 if (versionp) |
|
1795 *versionp = version_; |
|
1796 |
|
1797 data_ = nullptr; |
|
1798 nbytes_ = 0; |
|
1799 version_ = 0; |
|
1800 } |
|
1801 |
|
1802 bool |
|
1803 JSAutoStructuredCloneBuffer::read(JSContext *cx, MutableHandleValue vp, |
|
1804 const JSStructuredCloneCallbacks *optionalCallbacks, |
|
1805 void *closure) |
|
1806 { |
|
1807 JS_ASSERT(cx); |
|
1808 JS_ASSERT(data_); |
|
1809 return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp, |
|
1810 optionalCallbacks, closure); |
|
1811 } |
|
1812 |
|
1813 bool |
|
1814 JSAutoStructuredCloneBuffer::write(JSContext *cx, HandleValue value, |
|
1815 const JSStructuredCloneCallbacks *optionalCallbacks, |
|
1816 void *closure) |
|
1817 { |
|
1818 HandleValue transferable = UndefinedHandleValue; |
|
1819 return write(cx, value, transferable, optionalCallbacks, closure); |
|
1820 } |
|
1821 |
|
1822 bool |
|
1823 JSAutoStructuredCloneBuffer::write(JSContext *cx, HandleValue value, |
|
1824 HandleValue transferable, |
|
1825 const JSStructuredCloneCallbacks *optionalCallbacks, |
|
1826 void *closure) |
|
1827 { |
|
1828 clear(); |
|
1829 bool ok = !!JS_WriteStructuredClone(cx, value, &data_, &nbytes_, |
|
1830 optionalCallbacks, closure, |
|
1831 transferable); |
|
1832 if (!ok) { |
|
1833 data_ = nullptr; |
|
1834 nbytes_ = 0; |
|
1835 version_ = JS_STRUCTURED_CLONE_VERSION; |
|
1836 } |
|
1837 return ok; |
|
1838 } |
|
1839 |
|
1840 JS_PUBLIC_API(void) |
|
1841 JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks) |
|
1842 { |
|
1843 rt->structuredCloneCallbacks = callbacks; |
|
1844 } |
|
1845 |
|
1846 JS_PUBLIC_API(bool) |
|
1847 JS_ReadUint32Pair(JSStructuredCloneReader *r, uint32_t *p1, uint32_t *p2) |
|
1848 { |
|
1849 return r->input().readPair((uint32_t *) p1, (uint32_t *) p2); |
|
1850 } |
|
1851 |
|
1852 JS_PUBLIC_API(bool) |
|
1853 JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len) |
|
1854 { |
|
1855 return r->input().readBytes(p, len); |
|
1856 } |
|
1857 |
|
1858 JS_PUBLIC_API(bool) |
|
1859 JS_ReadTypedArray(JSStructuredCloneReader *r, MutableHandleValue vp) |
|
1860 { |
|
1861 uint32_t tag, nelems; |
|
1862 if (!r->input().readPair(&tag, &nelems)) |
|
1863 return false; |
|
1864 if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) { |
|
1865 return r->readTypedArray(TagToV1ArrayType(tag), nelems, vp.address(), true); |
|
1866 } else if (tag == SCTAG_TYPED_ARRAY_OBJECT) { |
|
1867 uint64_t arrayType; |
|
1868 if (!r->input().read(&arrayType)) |
|
1869 return false; |
|
1870 return r->readTypedArray(arrayType, nelems, vp.address()); |
|
1871 } else { |
|
1872 JS_ReportErrorNumber(r->context(), js_GetErrorMessage, nullptr, |
|
1873 JSMSG_SC_BAD_SERIALIZED_DATA, "expected type array"); |
|
1874 return false; |
|
1875 } |
|
1876 } |
|
1877 |
|
1878 JS_PUBLIC_API(bool) |
|
1879 JS_WriteUint32Pair(JSStructuredCloneWriter *w, uint32_t tag, uint32_t data) |
|
1880 { |
|
1881 return w->output().writePair(tag, data); |
|
1882 } |
|
1883 |
|
1884 JS_PUBLIC_API(bool) |
|
1885 JS_WriteBytes(JSStructuredCloneWriter *w, const void *p, size_t len) |
|
1886 { |
|
1887 return w->output().writeBytes(p, len); |
|
1888 } |
|
1889 |
|
1890 JS_PUBLIC_API(bool) |
|
1891 JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v) |
|
1892 { |
|
1893 JS_ASSERT(v.isObject()); |
|
1894 assertSameCompartment(w->context(), v); |
|
1895 RootedObject obj(w->context(), &v.toObject()); |
|
1896 |
|
1897 // If the object is a security wrapper, see if we're allowed to unwrap it. |
|
1898 // If we aren't, throw. |
|
1899 if (obj->is<WrapperObject>()) |
|
1900 obj = CheckedUnwrap(obj); |
|
1901 if (!obj) { |
|
1902 JS_ReportErrorNumber(w->context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); |
|
1903 return false; |
|
1904 } |
|
1905 return w->writeTypedArray(obj); |
|
1906 } |