Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jsclone.cpp
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 "jsregexp.h"
42 #include "jstypedarray.h"
43
44 #include "jsregexpinlines.h"
45
46 using namespace js;
47
48 namespace js
49 {
50
51 bool
52 WriteStructuredClone(JSContext *cx, const Value &v, uint64 **bufp, size_t *nbytesp,
53                      const JSStructuredCloneCallbacks *cb, void *cbClosure)
54 {
55     SCOutput out(cx);
56     JSStructuredCloneWriter w(out, cb, cbClosure);
57     return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
58 }
59
60 bool
61 ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
62                     const JSStructuredCloneCallbacks *cb, void *cbClosure)
63 {
64     SCInput in(cx, data, nbytes);
65     JSStructuredCloneReader r(in, cb, cbClosure);
66     return r.read(vp);
67 }
68
69 }
70
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
90 };
91
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);
94
95 static uint8_t
96 SwapBytes(uint8_t u)
97 {
98     return u;
99 }
100
101 static uint16_t
102 SwapBytes(uint16_t u)
103 {
104 #ifdef IS_BIG_ENDIAN
105     return ((u & 0x00ff) << 8) | ((u & 0xff00) >> 8);
106 #else
107     return u;
108 #endif
109 }
110
111 static uint32_t
112 SwapBytes(uint32_t u)
113 {
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
122 }
123
124 static uint64_t
125 SwapBytes(uint64_t u)
126 {
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
139 }
140
141 bool
142 SCInput::eof()
143 {
144     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
145     return false;
146 }
147
148 SCInput::SCInput(JSContext *cx, const uint64_t *data, size_t nbytes)
149     : cx(cx), point(data), end(data + nbytes / 8)
150 {
151     JS_ASSERT((uintptr_t(data) & 7) == 0);
152     JS_ASSERT((nbytes & 7) == 0);
153 }
154
155 bool
156 SCInput::read(uint64_t *p)
157 {
158     if (point == end)
159         return eof();
160     *p = SwapBytes(*point++);
161     return true;
162 }
163
164 bool
165 SCInput::readPair(uint32_t *tagp, uint32_t *datap)
166 {
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);
172     }
173     return ok;
174 }
175
176 /*
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).  :-(
179  */
180 static JS_NEVER_INLINE double
181 CanonicalizeNan(double d)
182 {
183     return JS_CANONICALIZE_NAN(d);
184 }
185
186 bool
187 SCInput::readDouble(jsdouble *p)
188 {
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;
197 }
198
199 template <class T>
200 bool
201 SCInput::readArray(T *p, size_t nelems)
202 {
203     JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
204
205     /*
206      * Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is
207      * larger than the remaining data.
208      */
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();
212
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++);
220     }
221     point += nwords;
222     return true;
223 }
224
225 bool
226 SCInput::readBytes(void *p, size_t nbytes)
227 {
228     return readArray((uint8_t *) p, nbytes);
229 }
230
231 bool
232 SCInput::readChars(jschar *p, size_t nchars)
233 {
234     JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
235     return readArray((uint16_t *) p, nchars);
236 }
237
238 SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
239
240 bool
241 SCOutput::write(uint64_t u)
242 {
243     return buf.append(SwapBytes(u));
244 }
245
246 static inline uint64_t
247 PairToUInt64(uint32_t tag, uint32_t data)
248 {
249     return uint64_t(data) | (uint64_t(tag) << 32);
250 }
251
252 bool
253 SCOutput::writePair(uint32_t tag, uint32_t data)
254 {
255     /*
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.
259      *
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.
263      */
264     return write(PairToUInt64(tag, data));
265 }
266
267 static inline uint64_t
268 ReinterpretDoubleAsUInt64(jsdouble d)
269 {
270     union {
271         jsdouble d;
272         uint64_t u;
273     } pun;
274     pun.d = d;
275     return pun.u;
276 }
277
278 static inline jsdouble
279 ReinterpretUInt64AsDouble(uint64_t u)
280 {
281     union {
282         uint64_t u;
283         jsdouble d;
284     } pun;
285     pun.u = u;
286     return pun.d;
287 }
288
289 static inline jsdouble
290 ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
291 {
292     return ReinterpretUInt64AsDouble(PairToUInt64(tag, data));
293 }
294
295 bool
296 SCOutput::writeDouble(jsdouble d)
297 {
298     return write(ReinterpretDoubleAsUInt64(CanonicalizeNan(d)));
299 }
300
301 template <class T>
302 bool
303 SCOutput::writeArray(const T *p, size_t nelems)
304 {
305     JS_ASSERT(8 % sizeof(T) == 0);
306     JS_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
307
308     if (nelems == 0)
309         return true;
310
311     if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
312         js_ReportAllocationOverflow(context());
313         return false;
314     }
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;
319
320     buf.back() = 0;  /* zero-pad to an 8-byte boundary */
321
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++);
329     }
330     return true;
331 }
332
333 bool
334 SCOutput::writeBytes(const void *p, size_t nbytes)
335 {
336     return writeArray((const uint8_t *) p, nbytes);
337 }
338
339 bool
340 SCOutput::writeChars(const jschar *p, size_t nchars)
341 {
342     JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
343     return writeArray((const uint16_t *) p, nchars);
344 }
345
346 bool
347 SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
348 {
349     *sizep = buf.length() * sizeof(uint64_t);
350     return (*datap = buf.extractRawBuffer()) != NULL;
351 }
352
353 JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
354
355 bool
356 JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
357 {
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);
363 }
364
365 bool
366 JSStructuredCloneWriter::writeId(jsid id)
367 {
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));
372 }
373
374 inline void
375 JSStructuredCloneWriter::checkStack()
376 {
377 #ifdef DEBUG
378     /* To avoid making serialization O(n^2), limit stack-checking at 10. */
379     const size_t MAX = 10;
380
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];
387     }
388     if (counts.length() <= MAX)
389         JS_ASSERT(total == ids.length());
390     else
391         JS_ASSERT(total <= ids.length());
392
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
398 }
399
400 static inline uint32_t
401 ArrayTypeToTag(uint32_t type)
402 {
403     /*
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.
407      */
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);
418
419     JS_ASSERT(type < TypedArray::TYPE_MAX);
420     return SCTAG_TYPED_ARRAY_MIN + type;
421 }
422
423 static inline uint32_t
424 TagToArrayType(uint32_t tag)
425 {
426     JS_ASSERT(SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX);
427     return tag - SCTAG_TYPED_ARRAY_MIN;
428 }
429
430 bool
431 JSStructuredCloneWriter::writeTypedArray(JSObject *obj)
432 {
433     TypedArray *arr = TypedArray::fromJSObject(obj);
434     if (!out.writePair(ArrayTypeToTag(arr->type), arr->length))
435         return false;
436
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;
454     }
455 }
456
457 bool
458 JSStructuredCloneWriter::writeArrayBuffer(JSObject *obj)
459 {
460     ArrayBuffer *abuf = ArrayBuffer::fromJSObject(obj);
461     return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, abuf->byteLength) &&
462            out.writeBytes(abuf->data, abuf->byteLength);
463 }
464
465 bool
466 JSStructuredCloneWriter::startObject(JSObject *obj)
467 {
468     JS_ASSERT(obj->isArray() || obj->isObject());
469
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;
479     }
480     if (!memory.add(p, obj))
481         return false;
482
483     /*
484      * Get enumerable property ids and put them in reverse order so that they
485      * will come off the stack in forward order.
486      */
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);
493
494     /* Push obj and count to the stack. */
495     if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
496         return false;
497     checkStack();
498
499     /* Write the header for obj. */
500     return out.writePair(obj->isArray() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
501 }
502
503 bool
504 JSStructuredCloneWriter::startWrite(const js::Value &v)
505 {
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());
538         }
539
540         if (callbacks && callbacks->write)
541             return callbacks->write(context(), this, obj, closure);
542         /* else fall through */
543     }
544
545     JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_UNSUPPORTED_TYPE);
546     return false;
547 }
548
549 bool
550 JSStructuredCloneWriter::write(const Value &v)
551 {
552     if (!startWrite(v))
553         return false;
554
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)) {
563                 /*
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.
567                  */
568                 JSObject *obj2;
569                 JSProperty *prop;
570                 if (!js_HasOwnProperty(context(), obj->getOps()->lookupProperty, obj, id,
571                                        &obj2, &prop)) {
572                     return false;
573                 }
574
575                 if (prop) {
576                     Value val;
577                     if (!writeId(id) || !obj->getProperty(context(), id, &val) || !startWrite(val))
578                         return false;
579                 }
580             }
581         } else {
582             out.writePair(SCTAG_NULL, 0);
583             memory.remove(obj);
584             objs.popBack();
585             counts.popBack();
586         }
587     }
588     return true;
589 }
590
591 bool
592 JSStructuredCloneReader::checkDouble(jsdouble d)
593 {
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;
600     }
601     return true;
602 }
603
604 class Chars {
605     JSContext *cx;
606     jschar *p;
607   public:
608     Chars() : p(NULL) {}
609     ~Chars() { if (p) cx->free(p); }
610
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;
619         }
620         return false;
621     }
622     jschar *get() { return p; }
623     void forget() { p = NULL; }
624 };
625
626 JSString *
627 JSStructuredCloneReader::readString(uint32_t nchars)
628 {
629     if (nchars > JSString::MAX_LENGTH) {
630         JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
631                              "string length");
632         return NULL;
633     }
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;
641 }
642
643 bool
644 JSStructuredCloneReader::readTypedArray(uint32_t tag, uint32_t nelems, Value *vp)
645 {
646     uint32_t atype = TagToArrayType(tag);
647     JSObject *obj = js_CreateTypedArray(context(), atype, nelems);
648     if (!obj)
649         return false;
650     vp->setObject(*obj);
651
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;
672     }
673 }
674
675 bool
676 JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp)
677 {
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);
685 }
686
687 bool
688 JSStructuredCloneReader::startRead(Value *vp)
689 {
690     uint32_t tag, data;
691
692     if (!in.readPair(&tag, &data))
693         return false;
694     switch (tag) {
695       case SCTAG_NULL:
696         vp->setNull();
697         break;
698
699       case SCTAG_UNDEFINED:
700         vp->setUndefined();
701         break;
702
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;
709
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;
719       }
720
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;
729       }
730
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;
739         }
740         JSObject *obj = js_NewDateObjectMsec(context(), d);
741         if (!obj)
742             return false;
743         vp->setObject(*obj);
744         break;
745       }
746
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;
755         }
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;
768       }
769
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;
779       }
780
781       case SCTAG_ARRAY_BUFFER_OBJECT:
782         return readArrayBuffer(data, vp);
783
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;
791         }
792
793         if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX)
794             return readTypedArray(tag, data, vp);
795
796         if (!callbacks || !callbacks->read) {
797             JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
798                                  "unsupported type");
799             return false;
800         }
801         JSObject *obj = callbacks->read(context(), this, tag, data, closure);
802         if (!obj)
803             return false;
804         vp->setObject(*obj);
805       }
806     }
807     return true;
808 }
809
810 bool
811 JSStructuredCloneReader::readId(jsid *idp)
812 {
813     uint32_t tag, data;
814     if (!in.readPair(&tag, &data))
815         return false;
816
817     if (tag == SCTAG_INDEX) {
818         *idp = INT_TO_JSID(int32_t(data));
819         return true;
820     }
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;
830     }
831     if (tag == SCTAG_NULL) {
832         *idp = JSID_VOID;
833         return true;
834     }
835     JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "id");
836     return false;
837 }
838
839 bool
840 JSStructuredCloneReader::read(Value *vp)
841 {
842     if (!startRead(vp))
843         return false;
844
845     while (objs.length() != 0) {
846         JSObject *obj = &objs.back().toObject();
847
848         jsid id;
849         if (!readId(&id))
850             return false;
851
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;
858         }
859     }
860     return true;
861 }