js/src/vm/StructuredClone.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial