Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / js / src / jsclone.cpp
blobc1d1f7e70dbd7b1c162b8fa2baf7d8e030076c02
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
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/
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.
15 * The Original Code is JavaScript structured data serialization.
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.
22 * Contributor(s):
23 * Jason Orendorff <jorendorff@mozilla.com>
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.
37 * ***** END LICENSE BLOCK ***** */
39 #include "jsclone.h"
40 #include "jsdate.h"
41 #include "jsregexp.h"
42 #include "jstypedarray.h"
44 #include "jsregexpinlines.h"
46 using namespace js;
48 namespace js
51 bool
52 WriteStructuredClone(JSContext *cx, const Value &v, uint64 **bufp, size_t *nbytesp,
53 const JSStructuredCloneCallbacks *cb, void *cbClosure)
55 SCOutput out(cx);
56 JSStructuredCloneWriter w(out, cb, cbClosure);
57 return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
60 bool
61 ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
62 const JSStructuredCloneCallbacks *cb, void *cbClosure)
64 SCInput in(cx, data, nbytes);
65 JSStructuredCloneReader r(in, cb, cbClosure);
66 return r.read(vp);
71 enum StructuredDataType {
72 /* Structured data types provided by the engine */
73 SCTAG_FLOAT_MAX = 0xFFF00000,
74 SCTAG_NULL = 0xFFFF0000,
75 SCTAG_UNDEFINED,
76 SCTAG_BOOLEAN,
77 SCTAG_INDEX,
78 SCTAG_STRING,
79 SCTAG_DATE_OBJECT,
80 SCTAG_REGEXP_OBJECT,
81 SCTAG_ARRAY_OBJECT,
82 SCTAG_OBJECT_OBJECT,
83 SCTAG_ARRAY_BUFFER_OBJECT,
84 SCTAG_BOOLEAN_OBJECT,
85 SCTAG_STRING_OBJECT,
86 SCTAG_NUMBER_OBJECT,
87 SCTAG_TYPED_ARRAY_MIN = 0xFFFF0100,
88 SCTAG_TYPED_ARRAY_MAX = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_MAX - 1,
89 SCTAG_END_OF_BUILTIN_TYPES
92 JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
93 JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
95 static uint8_t
96 SwapBytes(uint8_t u)
98 return u;
101 static uint16_t
102 SwapBytes(uint16_t u)
104 #ifdef IS_BIG_ENDIAN
105 return ((u & 0x00ff) << 8) | ((u & 0xff00) >> 8);
106 #else
107 return u;
108 #endif
111 static uint32_t
112 SwapBytes(uint32_t u)
114 #ifdef IS_BIG_ENDIAN
115 return ((u & 0x000000ffU) << 24) |
116 ((u & 0x0000ff00U) << 8) |
117 ((u & 0x00ff0000U) >> 8) |
118 ((u & 0xff000000U) >> 24);
119 #else
120 return u;
121 #endif
124 static uint64_t
125 SwapBytes(uint64_t u)
127 #ifdef IS_BIG_ENDIAN
128 return ((u & 0x00000000000000ffLLU) << 56) |
129 ((u & 0x000000000000ff00LLU) << 40) |
130 ((u & 0x0000000000ff0000LLU) << 24) |
131 ((u & 0x00000000ff000000LLU) << 8) |
132 ((u & 0x000000ff00000000LLU) >> 8) |
133 ((u & 0x0000ff0000000000LLU) >> 24) |
134 ((u & 0x00ff000000000000LLU) >> 40) |
135 ((u & 0xff00000000000000LLU) >> 56);
136 #else
137 return u;
138 #endif
141 bool
142 SCInput::eof()
144 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
145 return false;
148 SCInput::SCInput(JSContext *cx, const uint64_t *data, size_t nbytes)
149 : cx(cx), point(data), end(data + nbytes / 8)
151 JS_ASSERT((uintptr_t(data) & 7) == 0);
152 JS_ASSERT((nbytes & 7) == 0);
155 bool
156 SCInput::read(uint64_t *p)
158 if (point == end)
159 return eof();
160 *p = SwapBytes(*point++);
161 return true;
164 bool
165 SCInput::readPair(uint32_t *tagp, uint32_t *datap)
167 uint64_t u = 0; /* initialize to shut GCC up */
168 bool ok = read(&u);
169 if (ok) {
170 *tagp = uint32_t(u >> 32);
171 *datap = uint32_t(u);
173 return ok;
177 * The purpose of this never-inlined function is to avoid a strange g++ build
178 * error on OS X 10.5 (see bug 624080). :-(
180 static JS_NEVER_INLINE double
181 CanonicalizeNan(double d)
183 return JS_CANONICALIZE_NAN(d);
186 bool
187 SCInput::readDouble(jsdouble *p)
189 union {
190 uint64_t u;
191 jsdouble d;
192 } pun;
193 if (!read(&pun.u))
194 return false;
195 *p = CanonicalizeNan(pun.d);
196 return true;
199 template <class T>
200 bool
201 SCInput::readArray(T *p, size_t nelems)
203 JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
206 * Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is
207 * larger than the remaining data.
209 size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
210 if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(end - point))
211 return eof();
213 if (sizeof(T) == 1) {
214 memcpy(p, point, nelems);
215 } else {
216 const T *q = (const T *) point;
217 const T *qend = q + nelems;
218 while (q != qend)
219 *p++ = SwapBytes(*q++);
221 point += nwords;
222 return true;
225 bool
226 SCInput::readBytes(void *p, size_t nbytes)
228 return readArray((uint8_t *) p, nbytes);
231 bool
232 SCInput::readChars(jschar *p, size_t nchars)
234 JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
235 return readArray((uint16_t *) p, nchars);
238 SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
240 bool
241 SCOutput::write(uint64_t u)
243 return buf.append(SwapBytes(u));
246 static inline uint64_t
247 PairToUInt64(uint32_t tag, uint32_t data)
249 return uint64_t(data) | (uint64_t(tag) << 32);
252 bool
253 SCOutput::writePair(uint32_t tag, uint32_t data)
256 * As it happens, the tag word appears after the data word in the output.
257 * This is because exponents occupy the last 2 bytes of jsdoubles on the
258 * little-endian platforms we care most about.
260 * For example, JSVAL_TRUE is written using writePair(SCTAG_BOOLEAN, 1).
261 * PairToUInt64 produces the number 0xFFFF000200000001.
262 * That is written out as the bytes 01 00 00 00 02 00 FF FF.
264 return write(PairToUInt64(tag, data));
267 static inline uint64_t
268 ReinterpretDoubleAsUInt64(jsdouble d)
270 union {
271 jsdouble d;
272 uint64_t u;
273 } pun;
274 pun.d = d;
275 return pun.u;
278 static inline jsdouble
279 ReinterpretUInt64AsDouble(uint64_t u)
281 union {
282 uint64_t u;
283 jsdouble d;
284 } pun;
285 pun.u = u;
286 return pun.d;
289 static inline jsdouble
290 ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
292 return ReinterpretUInt64AsDouble(PairToUInt64(tag, data));
295 bool
296 SCOutput::writeDouble(jsdouble d)
298 return write(ReinterpretDoubleAsUInt64(CanonicalizeNan(d)));
301 template <class T>
302 bool
303 SCOutput::writeArray(const T *p, size_t nelems)
305 JS_ASSERT(8 % sizeof(T) == 0);
306 JS_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
308 if (nelems == 0)
309 return true;
311 if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
312 js_ReportAllocationOverflow(context());
313 return false;
315 size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
316 size_t start = buf.length();
317 if (!buf.growByUninitialized(nwords))
318 return false;
320 buf.back() = 0; /* zero-pad to an 8-byte boundary */
322 T *q = (T *) &buf[start];
323 if (sizeof(T) == 1) {
324 memcpy(q, p, nelems);
325 } else {
326 const T *pend = p + nelems;
327 while (p != pend)
328 *q++ = SwapBytes(*p++);
330 return true;
333 bool
334 SCOutput::writeBytes(const void *p, size_t nbytes)
336 return writeArray((const uint8_t *) p, nbytes);
339 bool
340 SCOutput::writeChars(const jschar *p, size_t nchars)
342 JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
343 return writeArray((const uint16_t *) p, nchars);
346 bool
347 SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
349 *sizep = buf.length() * sizeof(uint64_t);
350 return (*datap = buf.extractRawBuffer()) != NULL;
353 JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
355 bool
356 JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
358 size_t length = str->length();
359 const jschar *chars = str->getChars(context());
360 if (!chars)
361 return false;
362 return out.writePair(tag, uint32_t(length)) && out.writeChars(chars, length);
365 bool
366 JSStructuredCloneWriter::writeId(jsid id)
368 if (JSID_IS_INT(id))
369 return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id)));
370 JS_ASSERT(JSID_IS_STRING(id));
371 return writeString(SCTAG_STRING, JSID_TO_STRING(id));
374 inline void
375 JSStructuredCloneWriter::checkStack()
377 #ifdef DEBUG
378 /* To avoid making serialization O(n^2), limit stack-checking at 10. */
379 const size_t MAX = 10;
381 size_t limit = JS_MIN(counts.length(), MAX);
382 JS_ASSERT(objs.length() == counts.length());
383 size_t total = 0;
384 for (size_t i = 0; i < limit; i++) {
385 JS_ASSERT(total + counts[i] >= total);
386 total += counts[i];
388 if (counts.length() <= MAX)
389 JS_ASSERT(total == ids.length());
390 else
391 JS_ASSERT(total <= ids.length());
393 JS_ASSERT(memory.count() == objs.length());
394 size_t j = objs.length();
395 for (size_t i = 0; i < limit; i++)
396 JS_ASSERT(memory.has(&objs[--j].toObject()));
397 #endif
400 static inline uint32_t
401 ArrayTypeToTag(uint32_t type)
404 * As long as these are all true, we can just add. Note that for backward
405 * compatibility, the tags cannot change. So if the ArrayType type codes
406 * change, this function and TagToArrayType will have to do more work.
408 JS_STATIC_ASSERT(TypedArray::TYPE_INT8 == 0);
409 JS_STATIC_ASSERT(TypedArray::TYPE_UINT8 == 1);
410 JS_STATIC_ASSERT(TypedArray::TYPE_INT16 == 2);
411 JS_STATIC_ASSERT(TypedArray::TYPE_UINT16 == 3);
412 JS_STATIC_ASSERT(TypedArray::TYPE_INT32 == 4);
413 JS_STATIC_ASSERT(TypedArray::TYPE_UINT32 == 5);
414 JS_STATIC_ASSERT(TypedArray::TYPE_FLOAT32 == 6);
415 JS_STATIC_ASSERT(TypedArray::TYPE_FLOAT64 == 7);
416 JS_STATIC_ASSERT(TypedArray::TYPE_UINT8_CLAMPED == 8);
417 JS_STATIC_ASSERT(TypedArray::TYPE_MAX == TypedArray::TYPE_UINT8_CLAMPED + 1);
419 JS_ASSERT(type < TypedArray::TYPE_MAX);
420 return SCTAG_TYPED_ARRAY_MIN + type;
423 static inline uint32_t
424 TagToArrayType(uint32_t tag)
426 JS_ASSERT(SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX);
427 return tag - SCTAG_TYPED_ARRAY_MIN;
430 bool
431 JSStructuredCloneWriter::writeTypedArray(JSObject *obj)
433 TypedArray *arr = TypedArray::fromJSObject(obj);
434 if (!out.writePair(ArrayTypeToTag(arr->type), arr->length))
435 return false;
437 switch (arr->type) {
438 case TypedArray::TYPE_INT8:
439 case TypedArray::TYPE_UINT8:
440 case TypedArray::TYPE_UINT8_CLAMPED:
441 return out.writeArray((const uint8_t *) arr->data, arr->length);
442 case TypedArray::TYPE_INT16:
443 case TypedArray::TYPE_UINT16:
444 return out.writeArray((const uint16_t *) arr->data, arr->length);
445 case TypedArray::TYPE_INT32:
446 case TypedArray::TYPE_UINT32:
447 case TypedArray::TYPE_FLOAT32:
448 return out.writeArray((const uint32_t *) arr->data, arr->length);
449 case TypedArray::TYPE_FLOAT64:
450 return out.writeArray((const uint64_t *) arr->data, arr->length);
451 default:
452 JS_NOT_REACHED("unknown TypedArray type");
453 return false;
457 bool
458 JSStructuredCloneWriter::writeArrayBuffer(JSObject *obj)
460 ArrayBuffer *abuf = ArrayBuffer::fromJSObject(obj);
461 return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, abuf->byteLength) &&
462 out.writeBytes(abuf->data, abuf->byteLength);
465 bool
466 JSStructuredCloneWriter::startObject(JSObject *obj)
468 JS_ASSERT(obj->isArray() || obj->isObject());
470 /* Fail if obj is already on the stack. */
471 HashSet<JSObject *>::AddPtr p = memory.lookupForAdd(obj);
472 if (p) {
473 JSContext *cx = context();
474 if (callbacks && callbacks->reportError)
475 callbacks->reportError(cx, JS_SCERR_RECURSION);
476 else
477 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_RECURSION);
478 return false;
480 if (!memory.add(p, obj))
481 return false;
484 * Get enumerable property ids and put them in reverse order so that they
485 * will come off the stack in forward order.
487 size_t initialLength = ids.length();
488 if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids))
489 return false;
490 jsid *begin = ids.begin() + initialLength, *end = ids.end();
491 size_t count = size_t(end - begin);
492 Reverse(begin, end);
494 /* Push obj and count to the stack. */
495 if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
496 return false;
497 checkStack();
499 /* Write the header for obj. */
500 return out.writePair(obj->isArray() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
503 bool
504 JSStructuredCloneWriter::startWrite(const js::Value &v)
506 if (v.isString()) {
507 return writeString(SCTAG_STRING, v.toString());
508 } else if (v.isNumber()) {
509 return out.writeDouble(v.toNumber());
510 } else if (v.isBoolean()) {
511 return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
512 } else if (v.isNull()) {
513 return out.writePair(SCTAG_NULL, 0);
514 } else if (v.isUndefined()) {
515 return out.writePair(SCTAG_UNDEFINED, 0);
516 } else if (v.isObject()) {
517 JSObject *obj = &v.toObject();
518 if (obj->isRegExp()) {
519 RegExp *re = RegExp::extractFrom(obj);
520 return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) &&
521 writeString(SCTAG_STRING, re->getSource());
522 } else if (obj->isDate()) {
523 jsdouble d = js_DateGetMsecSinceEpoch(context(), obj);
524 return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d);
525 } else if (obj->isObject() || obj->isArray()) {
526 return startObject(obj);
527 } else if (js_IsTypedArray(obj)) {
528 return writeTypedArray(obj);
529 } else if (js_IsArrayBuffer(obj) && ArrayBuffer::fromJSObject(obj)) {
530 return writeArrayBuffer(obj);
531 } else if (obj->isBoolean()) {
532 return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->getPrimitiveThis().toBoolean());
533 } else if (obj->isNumber()) {
534 return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
535 out.writeDouble(obj->getPrimitiveThis().toNumber());
536 } else if (obj->isString()) {
537 return writeString(SCTAG_STRING_OBJECT, obj->getPrimitiveThis().toString());
540 if (callbacks && callbacks->write)
541 return callbacks->write(context(), this, obj, closure);
542 /* else fall through */
545 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_UNSUPPORTED_TYPE);
546 return false;
549 bool
550 JSStructuredCloneWriter::write(const Value &v)
552 if (!startWrite(v))
553 return false;
555 while (!counts.empty()) {
556 JSObject *obj = &objs.back().toObject();
557 if (counts.back()) {
558 counts.back()--;
559 jsid id = ids.back();
560 ids.popBack();
561 checkStack();
562 if (JSID_IS_STRING(id) || JSID_IS_INT(id)) {
564 * If obj still has an own property named id, write it out.
565 * The cost of re-checking could be avoided by using
566 * NativeIterators.
568 JSObject *obj2;
569 JSProperty *prop;
570 if (!js_HasOwnProperty(context(), obj->getOps()->lookupProperty, obj, id,
571 &obj2, &prop)) {
572 return false;
575 if (prop) {
576 Value val;
577 if (!writeId(id) || !obj->getProperty(context(), id, &val) || !startWrite(val))
578 return false;
581 } else {
582 out.writePair(SCTAG_NULL, 0);
583 memory.remove(obj);
584 objs.popBack();
585 counts.popBack();
588 return true;
591 bool
592 JSStructuredCloneReader::checkDouble(jsdouble d)
594 jsval_layout l;
595 l.asDouble = d;
596 if (!JSVAL_IS_DOUBLE(JSVAL_FROM_LAYOUT(l))) {
597 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
598 JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
599 return false;
601 return true;
604 class Chars {
605 JSContext *cx;
606 jschar *p;
607 public:
608 Chars() : p(NULL) {}
609 ~Chars() { if (p) cx->free(p); }
611 bool allocate(JSContext *cx, size_t len) {
612 JS_ASSERT(!p);
613 // We're going to null-terminate!
614 p = (jschar *) cx->malloc((len + 1) * sizeof(jschar));
615 this->cx = cx;
616 if (p) {
617 p[len] = jschar(0);
618 return true;
620 return false;
622 jschar *get() { return p; }
623 void forget() { p = NULL; }
626 JSString *
627 JSStructuredCloneReader::readString(uint32_t nchars)
629 if (nchars > JSString::MAX_LENGTH) {
630 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
631 "string length");
632 return NULL;
634 Chars chars;
635 if (!chars.allocate(context(), nchars) || !in.readChars(chars.get(), nchars))
636 return NULL;
637 JSString *str = js_NewString(context(), chars.get(), nchars);
638 if (str)
639 chars.forget();
640 return str;
643 bool
644 JSStructuredCloneReader::readTypedArray(uint32_t tag, uint32_t nelems, Value *vp)
646 uint32_t atype = TagToArrayType(tag);
647 JSObject *obj = js_CreateTypedArray(context(), atype, nelems);
648 if (!obj)
649 return false;
650 vp->setObject(*obj);
652 TypedArray *arr = TypedArray::fromJSObject(obj);
653 JS_ASSERT(arr->length == nelems);
654 JS_ASSERT(arr->type == atype);
655 switch (atype) {
656 case TypedArray::TYPE_INT8:
657 case TypedArray::TYPE_UINT8:
658 case TypedArray::TYPE_UINT8_CLAMPED:
659 return in.readArray((uint8_t *) arr->data, nelems);
660 case TypedArray::TYPE_INT16:
661 case TypedArray::TYPE_UINT16:
662 return in.readArray((uint16_t *) arr->data, nelems);
663 case TypedArray::TYPE_INT32:
664 case TypedArray::TYPE_UINT32:
665 case TypedArray::TYPE_FLOAT32:
666 return in.readArray((uint32_t *) arr->data, nelems);
667 case TypedArray::TYPE_FLOAT64:
668 return in.readArray((uint64_t *) arr->data, nelems);
669 default:
670 JS_NOT_REACHED("unknown TypedArray type");
671 return false;
675 bool
676 JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp)
678 JSObject *obj = js_CreateArrayBuffer(context(), nbytes);
679 if (!obj)
680 return false;
681 vp->setObject(*obj);
682 ArrayBuffer *abuf = ArrayBuffer::fromJSObject(obj);
683 JS_ASSERT(abuf->byteLength == nbytes);
684 return in.readArray((uint8_t *) abuf->data, nbytes);
687 bool
688 JSStructuredCloneReader::startRead(Value *vp)
690 uint32_t tag, data;
692 if (!in.readPair(&tag, &data))
693 return false;
694 switch (tag) {
695 case SCTAG_NULL:
696 vp->setNull();
697 break;
699 case SCTAG_UNDEFINED:
700 vp->setUndefined();
701 break;
703 case SCTAG_BOOLEAN:
704 case SCTAG_BOOLEAN_OBJECT:
705 vp->setBoolean(!!data);
706 if (tag == SCTAG_BOOLEAN_OBJECT && !js_PrimitiveToObject(context(), vp))
707 return false;
708 break;
710 case SCTAG_STRING:
711 case SCTAG_STRING_OBJECT: {
712 JSString *str = readString(data);
713 if (!str)
714 return false;
715 vp->setString(str);
716 if (tag == SCTAG_STRING_OBJECT && !js_PrimitiveToObject(context(), vp))
717 return false;
718 break;
721 case SCTAG_NUMBER_OBJECT: {
722 jsdouble d;
723 if (!in.readDouble(&d) || !checkDouble(d))
724 return false;
725 vp->setDouble(d);
726 if (!js_PrimitiveToObject(context(), vp))
727 return false;
728 break;
731 case SCTAG_DATE_OBJECT: {
732 jsdouble d;
733 if (!in.readDouble(&d) || !checkDouble(d))
734 return false;
735 if (d == d && d != TIMECLIP(d)) {
736 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
737 "date");
738 return false;
740 JSObject *obj = js_NewDateObjectMsec(context(), d);
741 if (!obj)
742 return false;
743 vp->setObject(*obj);
744 break;
747 case SCTAG_REGEXP_OBJECT: {
748 uint32_t tag2, nchars;
749 if (!in.readPair(&tag2, &nchars))
750 return false;
751 if (tag2 != SCTAG_STRING) {
752 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
753 "regexp");
754 return false;
756 JSString *str = readString(nchars);
757 if (!str)
758 return false;
759 size_t length = str->length();
760 const jschar *chars = str->getChars(context());
761 if (!chars)
762 return false;
763 JSObject *obj = RegExp::createObjectNoStatics(context(), chars, length, data);
764 if (!obj)
765 return false;
766 vp->setObject(*obj);
767 break;
770 case SCTAG_ARRAY_OBJECT:
771 case SCTAG_OBJECT_OBJECT: {
772 JSObject *obj = (tag == SCTAG_ARRAY_OBJECT)
773 ? NewDenseEmptyArray(context())
774 : NewBuiltinClassInstance(context(), &js_ObjectClass);
775 if (!obj || !objs.append(ObjectValue(*obj)))
776 return false;
777 vp->setObject(*obj);
778 break;
781 case SCTAG_ARRAY_BUFFER_OBJECT:
782 return readArrayBuffer(data, vp);
784 default: {
785 if (tag <= SCTAG_FLOAT_MAX) {
786 jsdouble d = ReinterpretPairAsDouble(tag, data);
787 if (!checkDouble(d))
788 return false;
789 vp->setNumber(d);
790 break;
793 if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX)
794 return readTypedArray(tag, data, vp);
796 if (!callbacks || !callbacks->read) {
797 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
798 "unsupported type");
799 return false;
801 JSObject *obj = callbacks->read(context(), this, tag, data, closure);
802 if (!obj)
803 return false;
804 vp->setObject(*obj);
807 return true;
810 bool
811 JSStructuredCloneReader::readId(jsid *idp)
813 uint32_t tag, data;
814 if (!in.readPair(&tag, &data))
815 return false;
817 if (tag == SCTAG_INDEX) {
818 *idp = INT_TO_JSID(int32_t(data));
819 return true;
821 if (tag == SCTAG_STRING) {
822 JSString *str = readString(data);
823 if (!str)
824 return false;
825 JSAtom *atom = js_AtomizeString(context(), str, 0);
826 if (!atom)
827 return false;
828 *idp = ATOM_TO_JSID(atom);
829 return true;
831 if (tag == SCTAG_NULL) {
832 *idp = JSID_VOID;
833 return true;
835 JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "id");
836 return false;
839 bool
840 JSStructuredCloneReader::read(Value *vp)
842 if (!startRead(vp))
843 return false;
845 while (objs.length() != 0) {
846 JSObject *obj = &objs.back().toObject();
848 jsid id;
849 if (!readId(&id))
850 return false;
852 if (JSID_IS_VOID(id)) {
853 objs.popBack();
854 } else {
855 Value v;
856 if (!startRead(&v) || !obj->defineProperty(context(), id, v))
857 return false;
860 return true;