1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is JavaScript structured data serialization.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * the Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2010
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Jason Orendorff <jorendorff@mozilla.com>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "jsclone.h"
40 : #include "jsdate.h"
41 : #include "jstypedarray.h"
42 :
43 : #include "jstypedarrayinlines.h"
44 :
45 : #include "vm/BooleanObject-inl.h"
46 : #include "vm/NumberObject-inl.h"
47 : #include "vm/RegExpObject-inl.h"
48 : #include "vm/StringObject-inl.h"
49 :
50 : using namespace js;
51 :
52 : JS_FRIEND_API(uint64_t)
53 16 : js_GetSCOffset(JSStructuredCloneWriter* writer)
54 : {
55 16 : JS_ASSERT(writer);
56 16 : return writer->output().count() * sizeof(uint64_t);
57 : }
58 :
59 : namespace js {
60 :
61 : bool
62 1891 : WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
63 : const JSStructuredCloneCallbacks *cb, void *cbClosure)
64 : {
65 3782 : SCOutput out(cx);
66 3782 : JSStructuredCloneWriter w(out, cb, cbClosure);
67 1891 : return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
68 : }
69 :
70 : bool
71 756 : ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
72 : const JSStructuredCloneCallbacks *cb, void *cbClosure)
73 : {
74 756 : SCInput in(cx, data, nbytes);
75 1512 : JSStructuredCloneReader r(in, cb, cbClosure);
76 756 : return r.read(vp);
77 : }
78 :
79 : } /* namespace js */
80 :
81 : enum StructuredDataType {
82 : /* Structured data types provided by the engine */
83 : SCTAG_FLOAT_MAX = 0xFFF00000,
84 : SCTAG_NULL = 0xFFFF0000,
85 : SCTAG_UNDEFINED,
86 : SCTAG_BOOLEAN,
87 : SCTAG_INDEX,
88 : SCTAG_STRING,
89 : SCTAG_DATE_OBJECT,
90 : SCTAG_REGEXP_OBJECT,
91 : SCTAG_ARRAY_OBJECT,
92 : SCTAG_OBJECT_OBJECT,
93 : SCTAG_ARRAY_BUFFER_OBJECT,
94 : SCTAG_BOOLEAN_OBJECT,
95 : SCTAG_STRING_OBJECT,
96 : SCTAG_NUMBER_OBJECT,
97 : SCTAG_BACK_REFERENCE_OBJECT,
98 : SCTAG_TYPED_ARRAY_MIN = 0xFFFF0100,
99 : SCTAG_TYPED_ARRAY_MAX = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_MAX - 1,
100 : SCTAG_END_OF_BUILTIN_TYPES
101 : };
102 :
103 : JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
104 : JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
105 :
106 : static uint8_t
107 0 : SwapBytes(uint8_t u)
108 : {
109 0 : return u;
110 : }
111 :
112 : static uint16_t
113 11072 : SwapBytes(uint16_t u)
114 : {
115 : #ifdef IS_BIG_ENDIAN
116 : return ((u & 0x00ff) << 8) | ((u & 0xff00) >> 8);
117 : #else
118 11072 : return u;
119 : #endif
120 : }
121 :
122 : static uint32_t
123 0 : SwapBytes(uint32_t u)
124 : {
125 : #ifdef IS_BIG_ENDIAN
126 : return ((u & 0x000000ffU) << 24) |
127 : ((u & 0x0000ff00U) << 8) |
128 : ((u & 0x00ff0000U) >> 8) |
129 : ((u & 0xff000000U) >> 24);
130 : #else
131 0 : return u;
132 : #endif
133 : }
134 :
135 : static uint64_t
136 8644 : SwapBytes(uint64_t u)
137 : {
138 : #ifdef IS_BIG_ENDIAN
139 : return ((u & 0x00000000000000ffLLU) << 56) |
140 : ((u & 0x000000000000ff00LLU) << 40) |
141 : ((u & 0x0000000000ff0000LLU) << 24) |
142 : ((u & 0x00000000ff000000LLU) << 8) |
143 : ((u & 0x000000ff00000000LLU) >> 8) |
144 : ((u & 0x0000ff0000000000LLU) >> 24) |
145 : ((u & 0x00ff000000000000LLU) >> 40) |
146 : ((u & 0xff00000000000000LLU) >> 56);
147 : #else
148 8644 : return u;
149 : #endif
150 : }
151 :
152 : bool
153 0 : SCInput::eof()
154 : {
155 0 : JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
156 0 : return false;
157 : }
158 :
159 756 : SCInput::SCInput(JSContext *cx, const uint64_t *data, size_t nbytes)
160 756 : : cx(cx), point(data), end(data + nbytes / 8)
161 : {
162 756 : JS_ASSERT((uintptr_t(data) & 7) == 0);
163 756 : JS_ASSERT((nbytes & 7) == 0);
164 756 : }
165 :
166 : bool
167 3493 : SCInput::read(uint64_t *p)
168 : {
169 3493 : if (point == end)
170 0 : return eof();
171 3493 : *p = SwapBytes(*point++);
172 3493 : return true;
173 : }
174 :
175 : bool
176 3493 : SCInput::readPair(uint32_t *tagp, uint32_t *datap)
177 : {
178 3493 : uint64_t u = 0; /* initialize to shut GCC up */
179 3493 : bool ok = read(&u);
180 3493 : if (ok) {
181 3493 : *tagp = uint32_t(u >> 32);
182 3493 : *datap = uint32_t(u);
183 : }
184 3493 : return ok;
185 : }
186 :
187 : /*
188 : * The purpose of this never-inlined function is to avoid a strange g++ build
189 : * error on OS X 10.5 (see bug 624080). :-(
190 : */
191 : static JS_NEVER_INLINE double
192 677 : CanonicalizeNan(double d)
193 : {
194 677 : return JS_CANONICALIZE_NAN(d);
195 : }
196 :
197 : bool
198 0 : SCInput::readDouble(double *p)
199 : {
200 : union {
201 : uint64_t u;
202 : double d;
203 : } pun;
204 0 : if (!read(&pun.u))
205 0 : return false;
206 0 : *p = CanonicalizeNan(pun.d);
207 0 : return true;
208 : }
209 :
210 : template <class T>
211 : bool
212 1501 : SCInput::readArray(T *p, size_t nelems)
213 : {
214 : JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
215 :
216 : /*
217 : * Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is
218 : * larger than the remaining data.
219 : */
220 1501 : size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
221 1501 : if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(end - point))
222 0 : return eof();
223 :
224 : if (sizeof(T) == 1) {
225 0 : js_memcpy(p, point, nelems);
226 : } else {
227 1501 : const T *q = (const T *) point;
228 1501 : const T *qend = q + nelems;
229 9864 : while (q != qend)
230 6862 : *p++ = ::SwapBytes(*q++);
231 : }
232 1501 : point += nwords;
233 1501 : return true;
234 : }
235 :
236 : bool
237 0 : SCInput::readBytes(void *p, size_t nbytes)
238 : {
239 0 : return readArray((uint8_t *) p, nbytes);
240 : }
241 :
242 : bool
243 1501 : SCInput::readChars(jschar *p, size_t nchars)
244 : {
245 : JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
246 1501 : return readArray((uint16_t *) p, nchars);
247 : }
248 :
249 1891 : SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
250 :
251 : bool
252 5151 : SCOutput::write(uint64_t u)
253 : {
254 5151 : return buf.append(SwapBytes(u));
255 : }
256 :
257 : static inline uint64_t
258 5267 : PairToUInt64(uint32_t tag, uint32_t data)
259 : {
260 5267 : return uint64_t(data) | (uint64_t(tag) << 32);
261 : }
262 :
263 : bool
264 4474 : SCOutput::writePair(uint32_t tag, uint32_t data)
265 : {
266 : /*
267 : * As it happens, the tag word appears after the data word in the output.
268 : * This is because exponents occupy the last 2 bytes of doubles on the
269 : * little-endian platforms we care most about.
270 : *
271 : * For example, JSVAL_TRUE is written using writePair(SCTAG_BOOLEAN, 1).
272 : * PairToUInt64 produces the number 0xFFFF000200000001.
273 : * That is written out as the bytes 01 00 00 00 02 00 FF FF.
274 : */
275 4474 : return write(PairToUInt64(tag, data));
276 : }
277 :
278 : static inline uint64_t
279 677 : ReinterpretDoubleAsUInt64(double d)
280 : {
281 : union {
282 : double d;
283 : uint64_t u;
284 : } pun;
285 677 : pun.d = d;
286 677 : return pun.u;
287 : }
288 :
289 : static inline double
290 793 : ReinterpretUInt64AsDouble(uint64_t u)
291 : {
292 : union {
293 : uint64_t u;
294 : double d;
295 : } pun;
296 793 : pun.u = u;
297 793 : return pun.d;
298 : }
299 :
300 : static inline double
301 793 : ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
302 : {
303 793 : return ReinterpretUInt64AsDouble(PairToUInt64(tag, data));
304 : }
305 :
306 : bool
307 677 : SCOutput::writeDouble(double d)
308 : {
309 677 : return write(ReinterpretDoubleAsUInt64(CanonicalizeNan(d)));
310 : }
311 :
312 : template <class T>
313 : bool
314 997 : SCOutput::writeArray(const T *p, size_t nelems)
315 : {
316 : JS_ASSERT(8 % sizeof(T) == 0);
317 : JS_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
318 :
319 997 : if (nelems == 0)
320 0 : return true;
321 :
322 997 : if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
323 0 : js_ReportAllocationOverflow(context());
324 0 : return false;
325 : }
326 997 : size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
327 997 : size_t start = buf.length();
328 997 : if (!buf.growByUninitialized(nwords))
329 0 : return false;
330 :
331 997 : buf.back() = 0; /* zero-pad to an 8-byte boundary */
332 :
333 997 : T *q = (T *) &buf[start];
334 : if (sizeof(T) == 1) {
335 16 : js_memcpy(q, p, nelems);
336 : } else {
337 981 : const T *pend = p + nelems;
338 6172 : while (p != pend)
339 4210 : *q++ = ::SwapBytes(*p++);
340 : }
341 997 : return true;
342 : }
343 :
344 : bool
345 16 : SCOutput::writeBytes(const void *p, size_t nbytes)
346 : {
347 16 : return writeArray((const uint8_t *) p, nbytes);
348 : }
349 :
350 : bool
351 981 : SCOutput::writeChars(const jschar *p, size_t nchars)
352 : {
353 : JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
354 981 : return writeArray((const uint16_t *) p, nchars);
355 : }
356 :
357 : bool
358 1891 : SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
359 : {
360 1891 : *sizep = buf.length() * sizeof(uint64_t);
361 1891 : return (*datap = buf.extractRawBuffer()) != NULL;
362 : }
363 :
364 : JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
365 :
366 : bool
367 981 : JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
368 : {
369 981 : size_t length = str->length();
370 981 : const jschar *chars = str->getChars(context());
371 981 : if (!chars)
372 0 : return false;
373 981 : return out.writePair(tag, uint32_t(length)) && out.writeChars(chars, length);
374 : }
375 :
376 : bool
377 784 : JSStructuredCloneWriter::writeId(jsid id)
378 : {
379 784 : if (JSID_IS_INT(id))
380 38 : return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id)));
381 746 : JS_ASSERT(JSID_IS_STRING(id));
382 746 : return writeString(SCTAG_STRING, JSID_TO_STRING(id));
383 : }
384 :
385 : inline void
386 2481 : JSStructuredCloneWriter::checkStack()
387 : {
388 : #ifdef DEBUG
389 : /* To avoid making serialization O(n^2), limit stack-checking at 10. */
390 2481 : const size_t MAX = 10;
391 :
392 2481 : size_t limit = JS_MIN(counts.length(), MAX);
393 2481 : JS_ASSERT(objs.length() == counts.length());
394 2481 : size_t total = 0;
395 5029 : for (size_t i = 0; i < limit; i++) {
396 2548 : JS_ASSERT(total + counts[i] >= total);
397 2548 : total += counts[i];
398 : }
399 2481 : if (counts.length() <= MAX)
400 2481 : JS_ASSERT(total == ids.length());
401 : else
402 0 : JS_ASSERT(total <= ids.length());
403 :
404 2481 : size_t j = objs.length();
405 5029 : for (size_t i = 0; i < limit; i++)
406 2548 : JS_ASSERT(memory.has(&objs[--j].toObject()));
407 : #endif
408 2481 : }
409 :
410 : static inline uint32_t
411 0 : ArrayTypeToTag(uint32_t type)
412 : {
413 : /*
414 : * As long as these are all true, we can just add. Note that for backward
415 : * compatibility, the tags cannot change. So if the ArrayType type codes
416 : * change, this function and TagToArrayType will have to do more work.
417 : */
418 : JS_STATIC_ASSERT(TypedArray::TYPE_INT8 == 0);
419 : JS_STATIC_ASSERT(TypedArray::TYPE_UINT8 == 1);
420 : JS_STATIC_ASSERT(TypedArray::TYPE_INT16 == 2);
421 : JS_STATIC_ASSERT(TypedArray::TYPE_UINT16 == 3);
422 : JS_STATIC_ASSERT(TypedArray::TYPE_INT32 == 4);
423 : JS_STATIC_ASSERT(TypedArray::TYPE_UINT32 == 5);
424 : JS_STATIC_ASSERT(TypedArray::TYPE_FLOAT32 == 6);
425 : JS_STATIC_ASSERT(TypedArray::TYPE_FLOAT64 == 7);
426 : JS_STATIC_ASSERT(TypedArray::TYPE_UINT8_CLAMPED == 8);
427 : JS_STATIC_ASSERT(TypedArray::TYPE_MAX == TypedArray::TYPE_UINT8_CLAMPED + 1);
428 :
429 0 : JS_ASSERT(type < TypedArray::TYPE_MAX);
430 0 : return SCTAG_TYPED_ARRAY_MIN + type;
431 : }
432 :
433 : static inline uint32_t
434 0 : TagToArrayType(uint32_t tag)
435 : {
436 0 : JS_ASSERT(SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX);
437 0 : return tag - SCTAG_TYPED_ARRAY_MIN;
438 : }
439 :
440 : bool
441 0 : JSStructuredCloneWriter::writeTypedArray(JSObject *obj)
442 : {
443 0 : JSObject *arr = TypedArray::getTypedArray(obj);
444 0 : if (!out.writePair(ArrayTypeToTag(TypedArray::getType(arr)), TypedArray::getLength(arr)))
445 0 : return false;
446 :
447 0 : switch (TypedArray::getType(arr)) {
448 : case TypedArray::TYPE_INT8:
449 : case TypedArray::TYPE_UINT8:
450 : case TypedArray::TYPE_UINT8_CLAMPED:
451 0 : return out.writeArray((const uint8_t *) TypedArray::getDataOffset(arr), TypedArray::getLength(arr));
452 : case TypedArray::TYPE_INT16:
453 : case TypedArray::TYPE_UINT16:
454 0 : return out.writeArray((const uint16_t *) TypedArray::getDataOffset(arr), TypedArray::getLength(arr));
455 : case TypedArray::TYPE_INT32:
456 : case TypedArray::TYPE_UINT32:
457 : case TypedArray::TYPE_FLOAT32:
458 0 : return out.writeArray((const uint32_t *) TypedArray::getDataOffset(arr), TypedArray::getLength(arr));
459 : case TypedArray::TYPE_FLOAT64:
460 0 : return out.writeArray((const uint64_t *) TypedArray::getDataOffset(arr), TypedArray::getLength(arr));
461 : default:
462 0 : JS_NOT_REACHED("unknown TypedArray type");
463 : return false;
464 : }
465 : }
466 :
467 : bool
468 0 : JSStructuredCloneWriter::writeArrayBuffer(JSObject *obj)
469 : {
470 0 : obj = ArrayBuffer::getArrayBuffer(obj);
471 0 : return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, obj->arrayBufferByteLength()) &&
472 0 : out.writeBytes(obj->arrayBufferDataOffset(), obj->arrayBufferByteLength());
473 : }
474 :
475 : bool
476 1697 : JSStructuredCloneWriter::startObject(JSObject *obj)
477 : {
478 1697 : JS_ASSERT(obj->isArray() || obj->isObject());
479 :
480 : /* Handle cycles in the object graph. */
481 1697 : CloneMemory::AddPtr p = memory.lookupForAdd(obj);
482 1697 : if (p)
483 0 : return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value);
484 1697 : if (!memory.add(p, obj, memory.count()))
485 0 : return false;
486 :
487 1697 : if (memory.count() == UINT32_MAX) {
488 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
489 0 : JSMSG_NEED_DIET, "object graph to serialize");
490 0 : return false;
491 : }
492 :
493 : /*
494 : * Get enumerable property ids and put them in reverse order so that they
495 : * will come off the stack in forward order.
496 : */
497 1697 : size_t initialLength = ids.length();
498 1697 : if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids))
499 0 : return false;
500 1697 : jsid *begin = ids.begin() + initialLength, *end = ids.end();
501 1697 : size_t count = size_t(end - begin);
502 1697 : Reverse(begin, end);
503 :
504 : /* Push obj and count to the stack. */
505 1697 : if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
506 0 : return false;
507 1697 : checkStack();
508 :
509 : /* Write the header for obj. */
510 1697 : return out.writePair(obj->isArray() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
511 : }
512 :
513 : bool
514 2675 : JSStructuredCloneWriter::startWrite(const js::Value &v)
515 : {
516 2675 : if (v.isString()) {
517 233 : return writeString(SCTAG_STRING, v.toString());
518 2442 : } else if (v.isNumber()) {
519 668 : return out.writeDouble(v.toNumber());
520 1774 : } else if (v.isBoolean()) {
521 46 : return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
522 1728 : } else if (v.isNull()) {
523 2 : return out.writePair(SCTAG_NULL, 0);
524 1726 : } else if (v.isUndefined()) {
525 2 : return out.writePair(SCTAG_UNDEFINED, 0);
526 1724 : } else if (v.isObject()) {
527 1724 : JSObject *obj = &v.toObject();
528 1724 : if (obj->isRegExp()) {
529 2 : RegExpObject &reobj = obj->asRegExp();
530 2 : return out.writePair(SCTAG_REGEXP_OBJECT, reobj.getFlags()) &&
531 2 : writeString(SCTAG_STRING, reobj.getSource());
532 1722 : } else if (obj->isDate()) {
533 9 : double d = js_DateGetMsecSinceEpoch(context(), obj);
534 9 : return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d);
535 1713 : } else if (obj->isObject() || obj->isArray()) {
536 1697 : return startObject(obj);
537 16 : } else if (js_IsTypedArray(obj)) {
538 0 : return writeTypedArray(obj);
539 16 : } else if (js_IsArrayBuffer(obj)) {
540 0 : return writeArrayBuffer(obj);
541 16 : } else if (obj->isBoolean()) {
542 0 : return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->asBoolean().unbox());
543 16 : } else if (obj->isNumber()) {
544 0 : return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
545 0 : out.writeDouble(obj->asNumber().unbox());
546 16 : } else if (obj->isString()) {
547 0 : return writeString(SCTAG_STRING_OBJECT, obj->asString().unbox());
548 : }
549 :
550 16 : if (callbacks && callbacks->write)
551 16 : return callbacks->write(context(), this, obj, closure);
552 : /* else fall through */
553 : }
554 :
555 0 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_UNSUPPORTED_TYPE);
556 0 : return false;
557 : }
558 :
559 : bool
560 1891 : JSStructuredCloneWriter::write(const Value &v)
561 : {
562 1891 : if (!startWrite(v))
563 0 : return false;
564 :
565 6263 : while (!counts.empty()) {
566 2481 : JSObject *obj = &objs.back().toObject();
567 2481 : if (counts.back()) {
568 784 : counts.back()--;
569 784 : jsid id = ids.back();
570 784 : ids.popBack();
571 784 : checkStack();
572 784 : if (JSID_IS_STRING(id) || JSID_IS_INT(id)) {
573 : /*
574 : * If obj still has an own property named id, write it out.
575 : * The cost of re-checking could be avoided by using
576 : * NativeIterators.
577 : */
578 : JSObject *obj2;
579 : JSProperty *prop;
580 1568 : if (!js_HasOwnProperty(context(), obj->getOps()->lookupGeneric, obj, id,
581 784 : &obj2, &prop)) {
582 0 : return false;
583 : }
584 :
585 784 : if (prop) {
586 : Value val;
587 2352 : if (!writeId(id) ||
588 784 : !obj->getGeneric(context(), id, &val) ||
589 784 : !startWrite(val))
590 0 : return false;
591 : }
592 : }
593 : } else {
594 1697 : out.writePair(SCTAG_NULL, 0);
595 1697 : objs.popBack();
596 1697 : counts.popBack();
597 : }
598 : }
599 :
600 1891 : memory.clear();
601 :
602 1891 : return true;
603 : }
604 :
605 : bool
606 793 : JSStructuredCloneReader::checkDouble(double d)
607 : {
608 : jsval_layout l;
609 793 : l.asDouble = d;
610 793 : if (!JSVAL_IS_DOUBLE_IMPL(l)) {
611 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
612 0 : JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
613 0 : return false;
614 : }
615 793 : return true;
616 : }
617 :
618 : class Chars {
619 : JSContext *cx;
620 : jschar *p;
621 : public:
622 1501 : Chars(JSContext *cx) : cx(cx), p(NULL) {}
623 1501 : ~Chars() { if (p) cx->free_(p); }
624 :
625 1501 : bool allocate(size_t len) {
626 1501 : JS_ASSERT(!p);
627 : // We're going to null-terminate!
628 1501 : p = (jschar *) cx->malloc_((len + 1) * sizeof(jschar));
629 1501 : if (p) {
630 1501 : p[len] = jschar(0);
631 1501 : return true;
632 : }
633 0 : return false;
634 : }
635 3002 : jschar *get() { return p; }
636 1501 : void forget() { p = NULL; }
637 : };
638 :
639 : JSString *
640 1501 : JSStructuredCloneReader::readString(uint32_t nchars)
641 : {
642 1501 : if (nchars > JSString::MAX_LENGTH) {
643 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
644 0 : "string length");
645 0 : return NULL;
646 : }
647 3002 : Chars chars(context());
648 1501 : if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars))
649 0 : return NULL;
650 1501 : JSString *str = js_NewString(context(), chars.get(), nchars);
651 1501 : if (str)
652 1501 : chars.forget();
653 1501 : return str;
654 : }
655 :
656 : bool
657 0 : JSStructuredCloneReader::readTypedArray(uint32_t tag, uint32_t nelems, Value *vp)
658 : {
659 0 : uint32_t atype = TagToArrayType(tag);
660 0 : JSObject *obj = js_CreateTypedArray(context(), atype, nelems);
661 0 : if (!obj)
662 0 : return false;
663 0 : vp->setObject(*obj);
664 :
665 0 : JSObject *arr = TypedArray::getTypedArray(obj);
666 0 : JS_ASSERT(TypedArray::getLength(arr) == nelems);
667 0 : JS_ASSERT(TypedArray::getType(arr) == atype);
668 0 : switch (atype) {
669 : case TypedArray::TYPE_INT8:
670 : case TypedArray::TYPE_UINT8:
671 : case TypedArray::TYPE_UINT8_CLAMPED:
672 0 : return in.readArray((uint8_t *) TypedArray::getDataOffset(arr), nelems);
673 : case TypedArray::TYPE_INT16:
674 : case TypedArray::TYPE_UINT16:
675 0 : return in.readArray((uint16_t *) TypedArray::getDataOffset(arr), nelems);
676 : case TypedArray::TYPE_INT32:
677 : case TypedArray::TYPE_UINT32:
678 : case TypedArray::TYPE_FLOAT32:
679 0 : return in.readArray((uint32_t *) TypedArray::getDataOffset(arr), nelems);
680 : case TypedArray::TYPE_FLOAT64:
681 0 : return in.readArray((uint64_t *) TypedArray::getDataOffset(arr), nelems);
682 : default:
683 0 : JS_NOT_REACHED("unknown TypedArray type");
684 : return false;
685 : }
686 : }
687 :
688 : bool
689 0 : JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp)
690 : {
691 0 : JSObject *obj = js_CreateArrayBuffer(context(), nbytes);
692 0 : if (!obj)
693 0 : return false;
694 0 : vp->setObject(*obj);
695 0 : JS_ASSERT(obj->arrayBufferByteLength() == nbytes);
696 0 : return in.readArray(obj->arrayBufferDataOffset(), nbytes);
697 : }
698 :
699 : bool
700 1862 : JSStructuredCloneReader::startRead(Value *vp)
701 : {
702 : uint32_t tag, data;
703 :
704 1862 : if (!in.readPair(&tag, &data))
705 0 : return false;
706 1862 : switch (tag) {
707 : case SCTAG_NULL:
708 7 : vp->setNull();
709 7 : break;
710 :
711 : case SCTAG_UNDEFINED:
712 4 : vp->setUndefined();
713 4 : break;
714 :
715 : case SCTAG_BOOLEAN:
716 : case SCTAG_BOOLEAN_OBJECT:
717 58 : vp->setBoolean(!!data);
718 58 : if (tag == SCTAG_BOOLEAN_OBJECT && !js_PrimitiveToObject(context(), vp))
719 0 : return false;
720 58 : break;
721 :
722 : case SCTAG_STRING:
723 : case SCTAG_STRING_OBJECT: {
724 475 : JSString *str = readString(data);
725 475 : if (!str)
726 0 : return false;
727 475 : vp->setString(str);
728 475 : if (tag == SCTAG_STRING_OBJECT && !js_PrimitiveToObject(context(), vp))
729 0 : return false;
730 475 : break;
731 : }
732 :
733 : case SCTAG_NUMBER_OBJECT: {
734 : double d;
735 0 : if (!in.readDouble(&d) || !checkDouble(d))
736 0 : return false;
737 0 : vp->setDouble(d);
738 0 : if (!js_PrimitiveToObject(context(), vp))
739 0 : return false;
740 0 : break;
741 : }
742 :
743 : case SCTAG_DATE_OBJECT: {
744 : double d;
745 0 : if (!in.readDouble(&d) || !checkDouble(d))
746 0 : return false;
747 0 : if (d == d && d != TIMECLIP(d)) {
748 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
749 0 : "date");
750 0 : return false;
751 : }
752 0 : JSObject *obj = js_NewDateObjectMsec(context(), d);
753 0 : if (!obj)
754 0 : return false;
755 0 : vp->setObject(*obj);
756 0 : break;
757 : }
758 :
759 : case SCTAG_REGEXP_OBJECT: {
760 4 : RegExpFlag flags = RegExpFlag(data);
761 : uint32_t tag2, nchars;
762 4 : if (!in.readPair(&tag2, &nchars))
763 0 : return false;
764 4 : if (tag2 != SCTAG_STRING) {
765 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
766 0 : "regexp");
767 0 : return false;
768 : }
769 4 : JSString *str = readString(nchars);
770 4 : if (!str)
771 0 : return false;
772 4 : size_t length = str->length();
773 4 : const jschar *chars = str->getChars(context());
774 4 : if (!chars)
775 0 : return false;
776 :
777 4 : RegExpObject *reobj = RegExpObject::createNoStatics(context(), chars, length, flags, NULL);
778 4 : if (!reobj)
779 0 : return false;
780 4 : vp->setObject(*reobj);
781 4 : break;
782 : }
783 :
784 : case SCTAG_ARRAY_OBJECT:
785 : case SCTAG_OBJECT_OBJECT: {
786 : JSObject *obj = (tag == SCTAG_ARRAY_OBJECT)
787 28 : ? NewDenseEmptyArray(context())
788 549 : : NewBuiltinClassInstance(context(), &ObjectClass);
789 1042 : if (!obj || !objs.append(ObjectValue(*obj)) ||
790 521 : !allObjs.append(ObjectValue(*obj)))
791 0 : return false;
792 521 : vp->setObject(*obj);
793 521 : break;
794 : }
795 :
796 : case SCTAG_BACK_REFERENCE_OBJECT: {
797 0 : if (data >= allObjs.length()) {
798 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
799 : JSMSG_SC_BAD_SERIALIZED_DATA,
800 0 : "invalid input");
801 : }
802 0 : *vp = allObjs[data];
803 0 : break;
804 : }
805 :
806 : case SCTAG_ARRAY_BUFFER_OBJECT:
807 0 : return readArrayBuffer(data, vp);
808 :
809 : default: {
810 793 : if (tag <= SCTAG_FLOAT_MAX) {
811 793 : double d = ReinterpretPairAsDouble(tag, data);
812 793 : if (!checkDouble(d))
813 0 : return false;
814 793 : vp->setNumber(d);
815 793 : break;
816 : }
817 :
818 0 : if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX)
819 0 : return readTypedArray(tag, data, vp);
820 :
821 0 : if (!callbacks || !callbacks->read) {
822 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
823 0 : "unsupported type");
824 0 : return false;
825 : }
826 0 : JSObject *obj = callbacks->read(context(), this, tag, data, closure);
827 0 : if (!obj)
828 0 : return false;
829 0 : vp->setObject(*obj);
830 : }
831 : }
832 1862 : return true;
833 : }
834 :
835 : bool
836 1627 : JSStructuredCloneReader::readId(jsid *idp)
837 : {
838 : uint32_t tag, data;
839 1627 : if (!in.readPair(&tag, &data))
840 0 : return false;
841 :
842 1627 : if (tag == SCTAG_INDEX) {
843 84 : *idp = INT_TO_JSID(int32_t(data));
844 84 : return true;
845 : }
846 1543 : if (tag == SCTAG_STRING) {
847 1022 : JSString *str = readString(data);
848 1022 : if (!str)
849 0 : return false;
850 1022 : JSAtom *atom = js_AtomizeString(context(), str);
851 1022 : if (!atom)
852 0 : return false;
853 1022 : *idp = ATOM_TO_JSID(atom);
854 1022 : return true;
855 : }
856 521 : if (tag == SCTAG_NULL) {
857 521 : *idp = JSID_VOID;
858 521 : return true;
859 : }
860 0 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "id");
861 0 : return false;
862 : }
863 :
864 : bool
865 756 : JSStructuredCloneReader::read(Value *vp)
866 : {
867 756 : if (!startRead(vp))
868 0 : return false;
869 :
870 3139 : while (objs.length() != 0) {
871 1627 : JSObject *obj = &objs.back().toObject();
872 :
873 : jsid id;
874 1627 : if (!readId(&id))
875 0 : return false;
876 :
877 1627 : if (JSID_IS_VOID(id)) {
878 521 : objs.popBack();
879 : } else {
880 : Value v;
881 1106 : if (!startRead(&v) || !obj->defineGeneric(context(), id, v))
882 0 : return false;
883 : }
884 : }
885 :
886 756 : allObjs.clear();
887 :
888 756 : return true;
889 : }
|