js/src/vm/StructuredClone.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial