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 0 : js_GetSCOffset(JSStructuredCloneWriter* writer)
54 : {
55 0 : JS_ASSERT(writer);
56 0 : return writer->output().count() * sizeof(uint64_t);
57 : }
58 :
59 : namespace js {
60 :
61 : bool
62 54 : WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
63 : const JSStructuredCloneCallbacks *cb, void *cbClosure)
64 : {
65 108 : SCOutput out(cx);
66 108 : JSStructuredCloneWriter w(out, cb, cbClosure);
67 54 : return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
68 : }
69 :
70 : bool
71 9 : ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
72 : const JSStructuredCloneCallbacks *cb, void *cbClosure)
73 : {
74 9 : SCInput in(cx, data, nbytes);
75 18 : JSStructuredCloneReader r(in, cb, cbClosure);
76 9 : 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 0 : SwapBytes(uint16_t u)
114 : {
115 : #ifdef IS_BIG_ENDIAN
116 : return ((u & 0x00ff) << 8) | ((u & 0xff00) >> 8);
117 : #else
118 0 : 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 72 : 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 72 : 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 9 : SCInput::SCInput(JSContext *cx, const uint64_t *data, size_t nbytes)
160 9 : : cx(cx), point(data), end(data + nbytes / 8)
161 : {
162 9 : JS_ASSERT((uintptr_t(data) & 7) == 0);
163 9 : JS_ASSERT((nbytes & 7) == 0);
164 9 : }
165 :
166 : bool
167 9 : SCInput::read(uint64_t *p)
168 : {
169 9 : if (point == end)
170 0 : return eof();
171 9 : *p = SwapBytes(*point++);
172 9 : return true;
173 : }
174 :
175 : bool
176 9 : SCInput::readPair(uint32_t *tagp, uint32_t *datap)
177 : {
178 9 : uint64_t u = 0; /* initialize to shut GCC up */
179 9 : bool ok = read(&u);
180 9 : if (ok) {
181 9 : *tagp = uint32_t(u >> 32);
182 9 : *datap = uint32_t(u);
183 : }
184 9 : 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 54 : CanonicalizeNan(double d)
193 : {
194 54 : 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 0 : 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 0 : size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
221 0 : 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 0 : const T *q = (const T *) point;
228 0 : const T *qend = q + nelems;
229 0 : while (q != qend)
230 0 : *p++ = ::SwapBytes(*q++);
231 : }
232 0 : point += nwords;
233 0 : 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 0 : SCInput::readChars(jschar *p, size_t nchars)
244 : {
245 : JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
246 0 : return readArray((uint16_t *) p, nchars);
247 : }
248 :
249 54 : SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
250 :
251 : bool
252 63 : SCOutput::write(uint64_t u)
253 : {
254 63 : return buf.append(SwapBytes(u));
255 : }
256 :
257 : static inline uint64_t
258 18 : PairToUInt64(uint32_t tag, uint32_t data)
259 : {
260 18 : return uint64_t(data) | (uint64_t(tag) << 32);
261 : }
262 :
263 : bool
264 9 : 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 9 : return write(PairToUInt64(tag, data));
276 : }
277 :
278 : static inline uint64_t
279 54 : ReinterpretDoubleAsUInt64(double d)
280 : {
281 : union {
282 : double d;
283 : uint64_t u;
284 : } pun;
285 54 : pun.d = d;
286 54 : return pun.u;
287 : }
288 :
289 : static inline double
290 9 : ReinterpretUInt64AsDouble(uint64_t u)
291 : {
292 : union {
293 : uint64_t u;
294 : double d;
295 : } pun;
296 9 : pun.u = u;
297 9 : return pun.d;
298 : }
299 :
300 : static inline double
301 9 : ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
302 : {
303 9 : return ReinterpretUInt64AsDouble(PairToUInt64(tag, data));
304 : }
305 :
306 : bool
307 54 : SCOutput::writeDouble(double d)
308 : {
309 54 : return write(ReinterpretDoubleAsUInt64(CanonicalizeNan(d)));
310 : }
311 :
312 : template <class T>
313 : bool
314 0 : 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 0 : if (nelems == 0)
320 0 : return true;
321 :
322 0 : if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
323 0 : js_ReportAllocationOverflow(context());
324 0 : return false;
325 : }
326 0 : size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
327 0 : size_t start = buf.length();
328 0 : if (!buf.growByUninitialized(nwords))
329 0 : return false;
330 :
331 0 : buf.back() = 0; /* zero-pad to an 8-byte boundary */
332 :
333 0 : T *q = (T *) &buf[start];
334 : if (sizeof(T) == 1) {
335 0 : js_memcpy(q, p, nelems);
336 : } else {
337 0 : const T *pend = p + nelems;
338 0 : while (p != pend)
339 0 : *q++ = ::SwapBytes(*p++);
340 : }
341 0 : return true;
342 : }
343 :
344 : bool
345 0 : SCOutput::writeBytes(const void *p, size_t nbytes)
346 : {
347 0 : return writeArray((const uint8_t *) p, nbytes);
348 : }
349 :
350 : bool
351 0 : SCOutput::writeChars(const jschar *p, size_t nchars)
352 : {
353 : JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
354 0 : return writeArray((const uint16_t *) p, nchars);
355 : }
356 :
357 : bool
358 54 : SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
359 : {
360 54 : *sizep = buf.length() * sizeof(uint64_t);
361 54 : return (*datap = buf.extractRawBuffer()) != NULL;
362 : }
363 :
364 : JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
365 :
366 : bool
367 0 : JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
368 : {
369 0 : size_t length = str->length();
370 0 : const jschar *chars = str->getChars(context());
371 0 : if (!chars)
372 0 : return false;
373 0 : return out.writePair(tag, uint32_t(length)) && out.writeChars(chars, length);
374 : }
375 :
376 : bool
377 0 : JSStructuredCloneWriter::writeId(jsid id)
378 : {
379 0 : if (JSID_IS_INT(id))
380 0 : return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id)));
381 0 : JS_ASSERT(JSID_IS_STRING(id));
382 0 : return writeString(SCTAG_STRING, JSID_TO_STRING(id));
383 : }
384 :
385 : inline void
386 0 : JSStructuredCloneWriter::checkStack()
387 : {
388 : #ifdef DEBUG
389 : /* To avoid making serialization O(n^2), limit stack-checking at 10. */
390 0 : const size_t MAX = 10;
391 :
392 0 : size_t limit = JS_MIN(counts.length(), MAX);
393 0 : JS_ASSERT(objs.length() == counts.length());
394 0 : size_t total = 0;
395 0 : for (size_t i = 0; i < limit; i++) {
396 0 : JS_ASSERT(total + counts[i] >= total);
397 0 : total += counts[i];
398 : }
399 0 : if (counts.length() <= MAX)
400 0 : JS_ASSERT(total == ids.length());
401 : else
402 0 : JS_ASSERT(total <= ids.length());
403 :
404 0 : size_t j = objs.length();
405 0 : for (size_t i = 0; i < limit; i++)
406 0 : JS_ASSERT(memory.has(&objs[--j].toObject()));
407 : #endif
408 0 : }
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 0 : JSStructuredCloneWriter::startObject(JSObject *obj)
477 : {
478 0 : JS_ASSERT(obj->isArray() || obj->isObject());
479 :
480 : /* Handle cycles in the object graph. */
481 0 : CloneMemory::AddPtr p = memory.lookupForAdd(obj);
482 0 : if (p)
483 0 : return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value);
484 0 : if (!memory.add(p, obj, memory.count()))
485 0 : return false;
486 :
487 0 : 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 0 : size_t initialLength = ids.length();
498 0 : if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids))
499 0 : return false;
500 0 : jsid *begin = ids.begin() + initialLength, *end = ids.end();
501 0 : size_t count = size_t(end - begin);
502 0 : Reverse(begin, end);
503 :
504 : /* Push obj and count to the stack. */
505 0 : if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
506 0 : return false;
507 0 : checkStack();
508 :
509 : /* Write the header for obj. */
510 0 : return out.writePair(obj->isArray() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
511 : }
512 :
513 : class AutoEnterCompartmentAndPushPrincipal : public JSAutoEnterCompartment
514 9 : {
515 : public:
516 9 : bool enter(JSContext *cx, JSObject *target) {
517 : // First, enter the compartment.
518 9 : if (!JSAutoEnterCompartment::enter(cx, target))
519 0 : return false;
520 :
521 : // We only need to push a principal if we changed compartments.
522 9 : if (state != STATE_OTHER_COMPARTMENT)
523 9 : return true;
524 :
525 : // Push.
526 0 : const JSSecurityCallbacks *cb = cx->runtime->securityCallbacks;
527 0 : return cb->pushContextPrincipal(cx, target->principals(cx));
528 : };
529 :
530 18 : ~AutoEnterCompartmentAndPushPrincipal() {
531 : // Pop the principal if necessary.
532 9 : if (state == STATE_OTHER_COMPARTMENT) {
533 0 : AutoCompartment *ac = getAutoCompartment();
534 0 : const JSSecurityCallbacks *cb = ac->context->runtime->securityCallbacks;
535 0 : cb->popContextPrincipal(ac->context);
536 : }
537 9 : };
538 : };
539 :
540 :
541 : bool
542 54 : JSStructuredCloneWriter::startWrite(const js::Value &v)
543 : {
544 54 : assertSameCompartment(context(), v);
545 :
546 54 : if (v.isString()) {
547 0 : return writeString(SCTAG_STRING, v.toString());
548 54 : } else if (v.isNumber()) {
549 45 : return out.writeDouble(v.toNumber());
550 9 : } else if (v.isBoolean()) {
551 0 : return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
552 9 : } else if (v.isNull()) {
553 0 : return out.writePair(SCTAG_NULL, 0);
554 9 : } else if (v.isUndefined()) {
555 0 : return out.writePair(SCTAG_UNDEFINED, 0);
556 9 : } else if (v.isObject()) {
557 9 : JSObject *obj = &v.toObject();
558 :
559 : // The object might be a security wrapper. See if we can clone what's
560 : // behind it. If we can, unwrap the object.
561 9 : obj = UnwrapObjectChecked(context(), obj);
562 9 : if (!obj)
563 0 : return false;
564 :
565 : // If we unwrapped above, we'll need to enter the underlying compartment.
566 : // Let the AutoEnterCompartment do the right thing for us.
567 18 : AutoEnterCompartmentAndPushPrincipal ac;
568 9 : if (!ac.enter(context(), obj))
569 0 : return false;
570 :
571 9 : if (obj->isRegExp()) {
572 0 : RegExpObject &reobj = obj->asRegExp();
573 0 : return out.writePair(SCTAG_REGEXP_OBJECT, reobj.getFlags()) &&
574 0 : writeString(SCTAG_STRING, reobj.getSource());
575 9 : } else if (obj->isDate()) {
576 9 : double d = js_DateGetMsecSinceEpoch(context(), obj);
577 9 : return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d);
578 0 : } else if (obj->isObject() || obj->isArray()) {
579 0 : return startObject(obj);
580 0 : } else if (js_IsTypedArray(obj)) {
581 0 : return writeTypedArray(obj);
582 0 : } else if (js_IsArrayBuffer(obj)) {
583 0 : return writeArrayBuffer(obj);
584 0 : } else if (obj->isBoolean()) {
585 0 : return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->asBoolean().unbox());
586 0 : } else if (obj->isNumber()) {
587 0 : return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
588 0 : out.writeDouble(obj->asNumber().unbox());
589 0 : } else if (obj->isString()) {
590 0 : return writeString(SCTAG_STRING_OBJECT, obj->asString().unbox());
591 : }
592 :
593 0 : if (callbacks && callbacks->write)
594 0 : return callbacks->write(context(), this, obj, closure);
595 : /* else fall through */
596 : }
597 :
598 0 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_UNSUPPORTED_TYPE);
599 0 : return false;
600 : }
601 :
602 : bool
603 54 : JSStructuredCloneWriter::write(const Value &v)
604 : {
605 54 : if (!startWrite(v))
606 0 : return false;
607 :
608 108 : while (!counts.empty()) {
609 0 : JSObject *obj = &objs.back().toObject();
610 :
611 : // The objects in |obj| can live in other compartments.
612 0 : AutoEnterCompartmentAndPushPrincipal ac;
613 0 : if (!ac.enter(context(), obj))
614 0 : return false;
615 :
616 0 : if (counts.back()) {
617 0 : counts.back()--;
618 0 : jsid id = ids.back();
619 0 : ids.popBack();
620 0 : checkStack();
621 0 : if (JSID_IS_STRING(id) || JSID_IS_INT(id)) {
622 : /*
623 : * If obj still has an own property named id, write it out.
624 : * The cost of re-checking could be avoided by using
625 : * NativeIterators.
626 : */
627 : JSObject *obj2;
628 : JSProperty *prop;
629 0 : if (!js_HasOwnProperty(context(), obj->getOps()->lookupGeneric, obj, id,
630 0 : &obj2, &prop)) {
631 0 : return false;
632 : }
633 :
634 0 : if (prop) {
635 : Value val;
636 0 : if (!writeId(id) ||
637 0 : !obj->getGeneric(context(), id, &val) ||
638 0 : !startWrite(val))
639 0 : return false;
640 : }
641 : }
642 : } else {
643 0 : out.writePair(SCTAG_NULL, 0);
644 0 : objs.popBack();
645 0 : counts.popBack();
646 : }
647 : }
648 :
649 54 : memory.clear();
650 :
651 54 : return true;
652 : }
653 :
654 : bool
655 9 : JSStructuredCloneReader::checkDouble(double d)
656 : {
657 : jsval_layout l;
658 9 : l.asDouble = d;
659 9 : if (!JSVAL_IS_DOUBLE_IMPL(l)) {
660 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
661 0 : JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
662 0 : return false;
663 : }
664 9 : return true;
665 : }
666 :
667 : class Chars {
668 : JSContext *cx;
669 : jschar *p;
670 : public:
671 0 : Chars(JSContext *cx) : cx(cx), p(NULL) {}
672 0 : ~Chars() { if (p) cx->free_(p); }
673 :
674 0 : bool allocate(size_t len) {
675 0 : JS_ASSERT(!p);
676 : // We're going to null-terminate!
677 0 : p = (jschar *) cx->malloc_((len + 1) * sizeof(jschar));
678 0 : if (p) {
679 0 : p[len] = jschar(0);
680 0 : return true;
681 : }
682 0 : return false;
683 : }
684 0 : jschar *get() { return p; }
685 0 : void forget() { p = NULL; }
686 : };
687 :
688 : JSString *
689 0 : JSStructuredCloneReader::readString(uint32_t nchars)
690 : {
691 0 : if (nchars > JSString::MAX_LENGTH) {
692 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
693 0 : "string length");
694 0 : return NULL;
695 : }
696 0 : Chars chars(context());
697 0 : if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars))
698 0 : return NULL;
699 0 : JSString *str = js_NewString(context(), chars.get(), nchars);
700 0 : if (str)
701 0 : chars.forget();
702 0 : return str;
703 : }
704 :
705 : bool
706 0 : JSStructuredCloneReader::readTypedArray(uint32_t tag, uint32_t nelems, Value *vp)
707 : {
708 0 : uint32_t atype = TagToArrayType(tag);
709 0 : JSObject *obj = js_CreateTypedArray(context(), atype, nelems);
710 0 : if (!obj)
711 0 : return false;
712 0 : vp->setObject(*obj);
713 :
714 0 : JSObject *arr = TypedArray::getTypedArray(obj);
715 0 : JS_ASSERT(TypedArray::getLength(arr) == nelems);
716 0 : JS_ASSERT(TypedArray::getType(arr) == atype);
717 0 : switch (atype) {
718 : case TypedArray::TYPE_INT8:
719 : case TypedArray::TYPE_UINT8:
720 : case TypedArray::TYPE_UINT8_CLAMPED:
721 0 : return in.readArray((uint8_t *) TypedArray::getDataOffset(arr), nelems);
722 : case TypedArray::TYPE_INT16:
723 : case TypedArray::TYPE_UINT16:
724 0 : return in.readArray((uint16_t *) TypedArray::getDataOffset(arr), nelems);
725 : case TypedArray::TYPE_INT32:
726 : case TypedArray::TYPE_UINT32:
727 : case TypedArray::TYPE_FLOAT32:
728 0 : return in.readArray((uint32_t *) TypedArray::getDataOffset(arr), nelems);
729 : case TypedArray::TYPE_FLOAT64:
730 0 : return in.readArray((uint64_t *) TypedArray::getDataOffset(arr), nelems);
731 : default:
732 0 : JS_NOT_REACHED("unknown TypedArray type");
733 : return false;
734 : }
735 : }
736 :
737 : bool
738 0 : JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp)
739 : {
740 0 : JSObject *obj = js_CreateArrayBuffer(context(), nbytes);
741 0 : if (!obj)
742 0 : return false;
743 0 : vp->setObject(*obj);
744 0 : JS_ASSERT(obj->arrayBufferByteLength() == nbytes);
745 0 : return in.readArray(obj->arrayBufferDataOffset(), nbytes);
746 : }
747 :
748 : bool
749 9 : JSStructuredCloneReader::startRead(Value *vp)
750 : {
751 : uint32_t tag, data;
752 :
753 9 : if (!in.readPair(&tag, &data))
754 0 : return false;
755 9 : switch (tag) {
756 : case SCTAG_NULL:
757 0 : vp->setNull();
758 0 : break;
759 :
760 : case SCTAG_UNDEFINED:
761 0 : vp->setUndefined();
762 0 : break;
763 :
764 : case SCTAG_BOOLEAN:
765 : case SCTAG_BOOLEAN_OBJECT:
766 0 : vp->setBoolean(!!data);
767 0 : if (tag == SCTAG_BOOLEAN_OBJECT && !js_PrimitiveToObject(context(), vp))
768 0 : return false;
769 0 : break;
770 :
771 : case SCTAG_STRING:
772 : case SCTAG_STRING_OBJECT: {
773 0 : JSString *str = readString(data);
774 0 : if (!str)
775 0 : return false;
776 0 : vp->setString(str);
777 0 : if (tag == SCTAG_STRING_OBJECT && !js_PrimitiveToObject(context(), vp))
778 0 : return false;
779 0 : break;
780 : }
781 :
782 : case SCTAG_NUMBER_OBJECT: {
783 : double d;
784 0 : if (!in.readDouble(&d) || !checkDouble(d))
785 0 : return false;
786 0 : vp->setDouble(d);
787 0 : if (!js_PrimitiveToObject(context(), vp))
788 0 : return false;
789 0 : break;
790 : }
791 :
792 : case SCTAG_DATE_OBJECT: {
793 : double d;
794 0 : if (!in.readDouble(&d) || !checkDouble(d))
795 0 : return false;
796 0 : if (d == d && d != TIMECLIP(d)) {
797 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
798 0 : "date");
799 0 : return false;
800 : }
801 0 : JSObject *obj = js_NewDateObjectMsec(context(), d);
802 0 : if (!obj)
803 0 : return false;
804 0 : vp->setObject(*obj);
805 0 : break;
806 : }
807 :
808 : case SCTAG_REGEXP_OBJECT: {
809 0 : RegExpFlag flags = RegExpFlag(data);
810 : uint32_t tag2, nchars;
811 0 : if (!in.readPair(&tag2, &nchars))
812 0 : return false;
813 0 : if (tag2 != SCTAG_STRING) {
814 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
815 0 : "regexp");
816 0 : return false;
817 : }
818 0 : JSString *str = readString(nchars);
819 0 : if (!str)
820 0 : return false;
821 0 : size_t length = str->length();
822 0 : const jschar *chars = str->getChars(context());
823 0 : if (!chars)
824 0 : return false;
825 :
826 0 : RegExpObject *reobj = RegExpObject::createNoStatics(context(), chars, length, flags, NULL);
827 0 : if (!reobj)
828 0 : return false;
829 0 : vp->setObject(*reobj);
830 0 : break;
831 : }
832 :
833 : case SCTAG_ARRAY_OBJECT:
834 : case SCTAG_OBJECT_OBJECT: {
835 : JSObject *obj = (tag == SCTAG_ARRAY_OBJECT)
836 0 : ? NewDenseEmptyArray(context())
837 0 : : NewBuiltinClassInstance(context(), &ObjectClass);
838 0 : if (!obj || !objs.append(ObjectValue(*obj)) ||
839 0 : !allObjs.append(ObjectValue(*obj)))
840 0 : return false;
841 0 : vp->setObject(*obj);
842 0 : break;
843 : }
844 :
845 : case SCTAG_BACK_REFERENCE_OBJECT: {
846 0 : if (data >= allObjs.length()) {
847 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
848 : JSMSG_SC_BAD_SERIALIZED_DATA,
849 0 : "invalid input");
850 : }
851 0 : *vp = allObjs[data];
852 0 : break;
853 : }
854 :
855 : case SCTAG_ARRAY_BUFFER_OBJECT:
856 0 : return readArrayBuffer(data, vp);
857 :
858 : default: {
859 9 : if (tag <= SCTAG_FLOAT_MAX) {
860 9 : double d = ReinterpretPairAsDouble(tag, data);
861 9 : if (!checkDouble(d))
862 0 : return false;
863 9 : vp->setNumber(d);
864 9 : break;
865 : }
866 :
867 0 : if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX)
868 0 : return readTypedArray(tag, data, vp);
869 :
870 0 : if (!callbacks || !callbacks->read) {
871 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
872 0 : "unsupported type");
873 0 : return false;
874 : }
875 0 : JSObject *obj = callbacks->read(context(), this, tag, data, closure);
876 0 : if (!obj)
877 0 : return false;
878 0 : vp->setObject(*obj);
879 : }
880 : }
881 9 : return true;
882 : }
883 :
884 : bool
885 0 : JSStructuredCloneReader::readId(jsid *idp)
886 : {
887 : uint32_t tag, data;
888 0 : if (!in.readPair(&tag, &data))
889 0 : return false;
890 :
891 0 : if (tag == SCTAG_INDEX) {
892 0 : *idp = INT_TO_JSID(int32_t(data));
893 0 : return true;
894 : }
895 0 : if (tag == SCTAG_STRING) {
896 0 : JSString *str = readString(data);
897 0 : if (!str)
898 0 : return false;
899 0 : JSAtom *atom = js_AtomizeString(context(), str);
900 0 : if (!atom)
901 0 : return false;
902 0 : *idp = ATOM_TO_JSID(atom);
903 0 : return true;
904 : }
905 0 : if (tag == SCTAG_NULL) {
906 0 : *idp = JSID_VOID;
907 0 : return true;
908 : }
909 0 : JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "id");
910 0 : return false;
911 : }
912 :
913 : bool
914 9 : JSStructuredCloneReader::read(Value *vp)
915 : {
916 9 : if (!startRead(vp))
917 0 : return false;
918 :
919 18 : while (objs.length() != 0) {
920 0 : JSObject *obj = &objs.back().toObject();
921 :
922 : jsid id;
923 0 : if (!readId(&id))
924 0 : return false;
925 :
926 0 : if (JSID_IS_VOID(id)) {
927 0 : objs.popBack();
928 : } else {
929 : Value v;
930 0 : if (!startRead(&v) || !obj->defineGeneric(context(), id, v))
931 0 : return false;
932 : }
933 : }
934 :
935 9 : allObjs.clear();
936 :
937 9 : return true;
938 : }
|