☠☠ backed out by d0fa5c45cabf ☠ ☠ | |
author | Steve Fink <sfink@mozilla.com> |
Tue, 15 Oct 2013 17:26:19 -0700 | |
changeset 150850 | 8febf2f0e35dcc341b8acea6ae882a338144cc72 |
parent 150849 | e646195f32aea58681eb93eef3fe4dcc99579354 |
child 150851 | e7d649603075c8142770527b383bd2ede5541491 |
push id | 25469 |
push user | cbook@mozilla.com |
push date | Wed, 16 Oct 2013 10:46:01 +0000 |
treeherder | autoland@afae5911a1e0 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jorendorff |
bugs | 861925 |
milestone | 27.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/public/StructuredClone.h +++ b/js/public/StructuredClone.h @@ -43,17 +43,19 @@ typedef JSObject *(*ReadStructuredCloneO typedef bool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w, JS::Handle<JSObject*> obj, void *closure); // This is called when JS_WriteStructuredClone is given an invalid transferable. // To follow HTML5, the application must throw a DATA_CLONE_ERR DOMException // with error set to one of the JS_SCERR_* values. typedef void (*StructuredCloneErrorOp)(JSContext *cx, uint32_t errorid); -// The maximum supported structured-clone serialization format version. +// The maximum supported structured-clone serialization format version. Note +// that this does not need to be bumped for Transferable-only changes, since +// they are never saved to persistent storage. #define JS_STRUCTURED_CLONE_VERSION 2 struct JSStructuredCloneCallbacks { ReadStructuredCloneOp read; WriteStructuredCloneOp write; StructuredCloneErrorOp reportError; };
--- a/js/src/js.msg +++ b/js/src/js.msg @@ -229,17 +229,17 @@ MSG_DEF(JSMSG_NESTING_GENERATOR, 17 MSG_DEF(JSMSG_UNUSED176, 176, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED177, 177, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED178, 178, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED179, 179, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED180, 180, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED181, 181, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_BAD_GENERATOR_SEND, 182, 1, JSEXN_TYPEERR, "attempt to send {0} to newborn generator") MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE, 183, 0, JSEXN_TYPEERR, "invalid transferable array for structured clone") -MSG_DEF(JSMSG_UNUSED184, 184, 0, JSEXN_NONE, "") +MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE, 184, 0, JSEXN_TYPEERR, "duplicate transferable for structured clone") MSG_DEF(JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE, 185, 0, JSEXN_TYPEERR, "proxy can't report an extensible object as non-extensible") MSG_DEF(JSMSG_UNUSED186, 186, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED187, 187, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_INCOMPATIBLE_METHOD, 188, 3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}") MSG_DEF(JSMSG_UNUSED189, 189, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED190, 190, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED191, 191, 0, JSEXN_NONE, "") MSG_DEF(JSMSG_UNUSED192, 192, 0, JSEXN_NONE, "")
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -219,32 +219,39 @@ class AutoArrayRooter : private AutoGCRo MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER js::SkipRoot skip; }; template<class T> class AutoVectorRooter : protected AutoGCRooter { + typedef js::Vector<T, 8> VectorImpl; + VectorImpl vector; + + /* Prevent overwriting of inline elements in vector. */ + js::SkipRoot vectorRoot; + public: explicit AutoVectorRooter(JSContext *cx, ptrdiff_t tag MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : AutoGCRooter(cx, tag), vector(cx), vectorRoot(cx, &vector) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } explicit AutoVectorRooter(js::ContextFriendFields *cx, ptrdiff_t tag MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : AutoGCRooter(cx, tag), vector(cx), vectorRoot(cx, &vector) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } typedef T ElementType; + typedef typename VectorImpl::Range Range; size_t length() const { return vector.length(); } bool empty() const { return vector.empty(); } bool append(const T &v) { return vector.append(v); } bool appendAll(const AutoVectorRooter<T> &other) { return vector.appendAll(other.vector); } @@ -294,33 +301,29 @@ class AutoVectorRooter : protected AutoG } const T *begin() const { return vector.begin(); } T *begin() { return vector.begin(); } const T *end() const { return vector.end(); } T *end() { return vector.end(); } + Range all() { return vector.all(); } + const T &back() const { return vector.back(); } friend void AutoGCRooter::trace(JSTracer *trc); private: void makeRangeGCSafe(size_t oldLength) { T *t = vector.begin() + oldLength; for (size_t i = oldLength; i < vector.length(); ++i, ++t) memset(t, 0, sizeof(T)); } - typedef js::Vector<T, 8> VectorImpl; - VectorImpl vector; - - /* Prevent overwriting of inline elements in vector. */ - js::SkipRoot vectorRoot; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; template<class Key, class Value> class AutoHashMapRooter : protected AutoGCRooter { private: typedef js::HashMap<Key, Value> HashMapImpl; @@ -330,16 +333,17 @@ class AutoHashMapRooter : protected Auto MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : AutoGCRooter(cx, tag), map(cx) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } typedef Key KeyType; typedef Value ValueType; + typedef typename HashMapImpl::Entry Entry; typedef typename HashMapImpl::Lookup Lookup; typedef typename HashMapImpl::Ptr Ptr; typedef typename HashMapImpl::AddPtr AddPtr; bool init(uint32_t len = 16) { return map.init(len); } bool initialized() const {
new file mode 100644 --- /dev/null +++ b/js/src/tests/js1_8_5/extensions/clone-transferables.js @@ -0,0 +1,87 @@ +// |reftest| skip-if(!xulRuntime.shell) +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +function test() { + // Note: -8 and -200 will trigger asm.js link failures because 8 and 200 + // bytes are below the minimum allowed size, and the buffer will not + // actually be converted to an asm.js buffer. + for (var size of [0, 8, 16, 200, 1000, 4096, -8, -200, -8192, -65536]) { + var buffer_ctor = (size < 0) ? AsmJSArrayBuffer : ArrayBuffer; + size = Math.abs(size); + + var old = buffer_ctor(size); + var copy = deserialize(serialize(old, [old])); + assertEq(old.byteLength, 0); + assertEq(copy.byteLength, size); + + var constructors = [ Int8Array, + Uint8Array, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array, + Uint8ClampedArray ]; + + for (var ctor of constructors) { + var buf = buffer_ctor(size); + var old_arr = ctor(buf); + assertEq(buf.byteLength, size); + assertEq(buf, old_arr.buffer); + assertEq(old_arr.length, size / old_arr.BYTES_PER_ELEMENT); + + var copy_arr = deserialize(serialize(old_arr, [ buf ])); + assertEq(buf.byteLength, 0, "donor array buffer should be neutered"); + assertEq(old_arr.length, 0, "donor typed array should be neutered"); + assertEq(copy_arr.buffer.byteLength == size, true); + assertEq(copy_arr.length, size / old_arr.BYTES_PER_ELEMENT); + + buf = null; + old_arr = null; + gc(); // Tickle the ArrayBuffer -> view management + } + + for (var ctor of constructors) { + var buf = buffer_ctor(size); + var old_arr = ctor(buf); + var dv = DataView(buf); // Second view + var copy_arr = deserialize(serialize(old_arr, [ buf ])); + assertEq(buf.byteLength, 0, "donor array buffer should be neutered"); + assertEq(old_arr.length, 0, "donor typed array should be neutered"); + assertEq(dv.byteLength, 0, "all views of donor array buffer should be neutered"); + + buf = null; + old_arr = null; + gc(); // Tickle the ArrayBuffer -> view management + } + + // Mutate the buffer during the clone operation. The modifications should be visible. + if (size >= 4) { + old = buffer_ctor(size); + var view = Int32Array(old); + view[0] = 1; + var mutator = { get foo() { view[0] = 2; } }; + var copy = deserialize(serialize([ old, mutator ], [old])); + var viewCopy = Int32Array(copy[0]); + assertEq(view.length, 0); // Neutered + assertEq(viewCopy[0], 2); + } + + // Neuter the buffer during the clone operation. Should throw an exception. + if (size >= 4) { + old = buffer_ctor(size); + var mutator = { + get foo() { + deserialize(serialize(old, [old])); + } + }; + // The throw is not yet implemented, bug 919259. + //var copy = deserialize(serialize([ old, mutator ], [old])); + } + } +} + +test(); +reportCompare(0, 0, 'ok');
--- a/js/src/tests/js1_8_5/extensions/shell.js +++ b/js/src/tests/js1_8_5/extensions/shell.js @@ -192,8 +192,21 @@ function referencesVia(from, edge, to) { print("referent is not referenced via: " + uneval(edge)); print("but it is referenced via: " + uneval(alternatives)); } print("all incoming edges, from any object:"); for (var e in edges) print(e); return false; } + +// Note that AsmJS ArrayBuffers have a minimum size, currently 4096 bytes. If a +// smaller size is given, a regular ArrayBuffer will be returned instead. +function AsmJSArrayBuffer(size) { + var ab = new ArrayBuffer(size); + (new Function('global', 'foreign', 'buffer', '' + +' "use asm";' + +' var i32 = new global.Int32Array(buffer);' + +' function g() {};' + +' return g;' + +''))(this,null,ab); + return ab; +}
--- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -63,59 +63,86 @@ enum StructuredDataType { SCTAG_REGEXP_OBJECT, SCTAG_ARRAY_OBJECT, SCTAG_OBJECT_OBJECT, SCTAG_ARRAY_BUFFER_OBJECT, SCTAG_BOOLEAN_OBJECT, SCTAG_STRING_OBJECT, SCTAG_NUMBER_OBJECT, SCTAG_BACK_REFERENCE_OBJECT, - SCTAG_TRANSFER_MAP_HEADER, - SCTAG_TRANSFER_MAP, + SCTAG_DO_NOT_USE_1, + SCTAG_DO_NOT_USE_2, SCTAG_TYPED_ARRAY_OBJECT, SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100, SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_INT8, SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_UINT8, SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_INT16, SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_UINT16, SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_INT32, SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_UINT32, SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_FLOAT32, SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_FLOAT64, SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_UINT8_CLAMPED, SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_MAX - 1, + + /* + * Define a separate range of numbers for Transferable-only tags, since + * they are not used for persistent clone buffers and therefore do not + * require bumping JS_STRUCTURED_CLONE_VERSION. + */ + SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200, + SCTAG_TRANSFER_MAP_ENTRY, + SCTAG_END_OF_BUILTIN_TYPES }; +// Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the +// contents have been read out yet or not. enum TransferableMapHeader { - SCTAG_TM_NOT_MARKED = 0, - SCTAG_TM_MARKED + SCTAG_TM_UNREAD = 0, + SCTAG_TM_TRANSFERRED +}; + +enum TransferableObjectType { + // Transferable data has not been filled in yet + SCTAG_TM_UNFILLED = 0, + + // Structured clone buffer does not yet own the data + SCTAG_TM_UNOWNED = 1, + + // All values at least this large are owned by the clone buffer + SCTAG_TM_FIRST_OWNED = 2, + + // Data is a pointer that can be freed + SCTAG_TM_ALLOC_DATA = 2, }; namespace js { struct SCOutput { public: explicit SCOutput(JSContext *cx); + ~SCOutput(); JSContext *context() const { return cx; } bool write(uint64_t u); bool writePair(uint32_t tag, uint32_t data); bool writeDouble(double d); bool writeBytes(const void *p, size_t nbytes); bool writeChars(const jschar *p, size_t nchars); bool writePtr(const void *); template <class T> bool writeArray(const T *p, size_t nbytes); bool extractBuffer(uint64_t **datap, size_t *sizep); - uint64_t count() { return buf.length(); } + uint64_t count() const { return buf.length(); } + uint64_t *rawBuffer() { return buf.begin(); } private: JSContext *cx; js::Vector<uint64_t> buf; }; struct SCInput { public: @@ -131,16 +158,22 @@ struct SCInput { bool readPtr(void **); bool get(uint64_t *p); bool getPair(uint32_t *tagp, uint32_t *datap); bool replace(uint64_t u); bool replacePair(uint32_t tag, uint32_t data); + uint64_t *tell() const { return point; } + void seek(uint64_t *pos) { + JS_ASSERT(pos <= end); + point = pos; + } + template <class T> bool readArray(T *p, size_t nelems); private: bool eof(); void staticAssertions() { JS_STATIC_ASSERT(sizeof(jschar) == 2); @@ -201,18 +234,17 @@ struct JSStructuredCloneWriter { const JSStructuredCloneCallbacks *cb, void *cbClosure, jsval tVal) : out(out), objs(out.context()), counts(out.context()), ids(out.context()), memory(out.context()), callbacks(cb), closure(cbClosure), transferable(out.context(), tVal), transferableObjects(out.context()) { } - bool init() { return transferableObjects.init() && parseTransferable() && - memory.init() && writeTransferMap(); } + bool init() { return parseTransferable() && memory.init() && writeTransferMap(); } bool write(const js::Value &v); js::SCOutput &output() { return out; } private: JSContext *context() { return out.context(); } @@ -223,16 +255,17 @@ struct JSStructuredCloneWriter { bool writeArrayBuffer(JS::HandleObject obj); bool writeTypedArray(JS::HandleObject obj); bool startObject(JS::HandleObject obj, bool *backref); bool startWrite(const js::Value &v); bool traverseObject(JS::HandleObject obj); bool parseTransferable(); void reportErrorTransferable(); + bool transferOwnership(); inline void checkStack(); js::SCOutput &out; // Vector of objects with properties remaining to be written. // // NB: These can span multiple compartments, so the compartment must be @@ -255,17 +288,17 @@ struct JSStructuredCloneWriter { // The user defined callbacks that will be used for cloning. const JSStructuredCloneCallbacks *callbacks; // Any value passed to JS_WriteStructuredClone. void *closure; // List of transferable objects JS::RootedValue transferable; - js::AutoObjectHashSet transferableObjects; + JS::AutoObjectVector transferableObjects; friend bool JS_WriteTypedArray(JSStructuredCloneWriter *w, JS::Value v); }; JS_FRIEND_API(uint64_t) js_GetSCOffset(JSStructuredCloneWriter* writer) { JS_ASSERT(writer); @@ -292,64 +325,75 @@ bool ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, Value *vp, const JSStructuredCloneCallbacks *cb, void *cbClosure) { SCInput in(cx, data, nbytes); JSStructuredCloneReader r(in, cb, cbClosure); return r.read(vp); } -bool -ClearStructuredClone(const uint64_t *data, size_t nbytes) +// This may acquire new ways of discarding transfer map entries as new +// Transferables are implemented. +static void +DiscardEntry(uint32_t mapEntryDescriptor, const uint64_t *ptr) { - const uint64_t *point = data; - const uint64_t *end = data + nbytes / 8; + JS_ASSERT(mapEntryDescriptor == SCTAG_TM_ALLOC_DATA); + uint64_t u = LittleEndian::readUint64(ptr); + js_free(reinterpret_cast<void*>(u)); +} + +static void +Discard(const uint64_t *begin, const uint64_t *end) +{ + const uint64_t *point = begin; uint64_t u = LittleEndian::readUint64(point++); uint32_t tag = uint32_t(u >> 32); - if (tag == SCTAG_TRANSFER_MAP_HEADER) { - if ((TransferableMapHeader)uint32_t(u) == SCTAG_TM_NOT_MARKED) { - while (point != end) { - uint64_t u = LittleEndian::readUint64(point++); - uint32_t tag = uint32_t(u >> 32); - if (tag == SCTAG_TRANSFER_MAP) { - u = LittleEndian::readUint64(point++); - js_free(reinterpret_cast<void*>(u)); - } else { - // The only things in the transfer map should be - // SCTAG_TRANSFER_MAP tags paired with pointers. If we find - // any other tag, we've walked off the end of the transfer - // map. - break; - } - } + if (tag != SCTAG_TRANSFER_MAP_HEADER) + return; + + if (TransferableMapHeader(u) == SCTAG_TM_TRANSFERRED) + return; + + uint64_t numTransferables = LittleEndian::readUint64(point++); + while (numTransferables--) { + uint64_t u = LittleEndian::readUint64(point++); + JS_ASSERT(uint32_t(u >> 32) == SCTAG_TRANSFER_MAP_ENTRY); + uint32_t mapEntryDescriptor = uint32_t(u); + if (mapEntryDescriptor >= SCTAG_TM_FIRST_OWNED) { + DiscardEntry(mapEntryDescriptor, point); + point += 2; // Pointer and userdata } } +} - js_free((void *)data); - return true; +static void +ClearStructuredClone(const uint64_t *data, size_t nbytes) +{ + JS_ASSERT(nbytes % 8 == 0); + Discard(data, data + nbytes / 8); + js_free(const_cast<uint64_t*>(data)); } bool StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes, bool *hasTransferable) { *hasTransferable = false; if (data) { uint64_t u = LittleEndian::readUint64(data); uint32_t tag = uint32_t(u >> 32); - if (tag == SCTAG_TRANSFER_MAP_HEADER) { + if (tag == SCTAG_TRANSFER_MAP_HEADER) *hasTransferable = true; - } } return true; } -} +} /* namespace js */ static inline uint64_t PairToUInt64(uint32_t tag, uint32_t data) { return uint64_t(data) | (uint64_t(tag) << 32); } bool @@ -504,16 +548,22 @@ SCInput::readPtr(void **p) uint64_t tmp; bool ret = read(&tmp); *p = reinterpret_cast<void*>(tmp); return ret; } SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {} +SCOutput::~SCOutput() +{ + // Free any transferable data left lying around in the buffer + Discard(rawBuffer(), rawBuffer() + count()); +} + bool SCOutput::write(uint64_t u) { return buf.append(NativeEndian::swapToLittleEndian(u)); } bool SCOutput::writePair(uint32_t tag, uint32_t data) @@ -631,17 +681,17 @@ SCOutput::extractBuffer(uint64_t **datap return (*datap = buf.extractRawBuffer()) != nullptr; } JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX); bool JSStructuredCloneWriter::parseTransferable() { - transferableObjects.clear(); + MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data"); if (JSVAL_IS_NULL(transferable) || JSVAL_IS_VOID(transferable)) return true; if (!transferable.isObject()) { reportErrorTransferable(); return false; } @@ -667,31 +717,31 @@ JSStructuredCloneWriter::parseTransferab if (!v.isObject()) { reportErrorTransferable(); return false; } JSObject* tObj = CheckedUnwrap(&v.toObject()); if (!tObj) { - JS_ReportError(context(), "Permission denied to access object"); + JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED); return false; } if (!tObj->is<ArrayBufferObject>()) { reportErrorTransferable(); return false; } - // No duplicate: - if (transferableObjects.has(tObj)) { - reportErrorTransferable(); + // No duplicates allowed + if (std::find(transferableObjects.begin(), transferableObjects.end(), tObj) != transferableObjects.end()) { + JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_DUP_TRANSFERABLE); return false; } - if (!transferableObjects.putNew(tObj)) + if (!transferableObjects.append(tObj)) return false; } return true; } void JSStructuredCloneWriter::reportErrorTransferable() @@ -899,35 +949,77 @@ JSStructuredCloneWriter::startWrite(cons } bool JSStructuredCloneWriter::writeTransferMap() { if (transferableObjects.empty()) return true; - if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_NOT_MARKED)) + if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD)) return false; - for (HashSet<JSObject*>::Range r = transferableObjects.all(); - !r.empty(); r.popFront()) { - JSObject *obj = r.front(); + if (!out.write(transferableObjects.length())) + return false; + + for (JS::AutoObjectVector::Range tr = transferableObjects.all(); + !tr.empty(); tr.popFront()) + { + JSObject *obj = tr.front(); if (!memory.put(obj, memory.count())) return false; + // Emit a placeholder pointer. We will steal the data and neuter the + // transferable later. + if (!out.writePair(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_UNFILLED) || + !out.writePtr(NULL) || + !out.write(0)) + { + return false; + } + } + + return true; +} + +bool +JSStructuredCloneWriter::transferOwnership() +{ + if (transferableObjects.empty()) + return true; + + // Walk along the transferables and the transfer map at the same time, + // grabbing out pointers from the transferables and stuffing them into the + // transfer map. + uint64_t *point = out.rawBuffer(); + JS_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_HEADER); + point++; + JS_ASSERT(LittleEndian::readUint64(point) == transferableObjects.length()); + point++; + + for (JS::AutoObjectVector::Range tr = transferableObjects.all(); + !tr.empty(); + tr.popFront()) + { void *content; uint8_t *data; - if (!JS_StealArrayBufferContents(context(), obj, &content, &data)) - return false; + if (!JS_StealArrayBufferContents(context(), tr.front(), &content, &data)) + return false; // Destructor will clean up the already-transferred data - if (!out.writePair(SCTAG_TRANSFER_MAP, 0) || !out.writePtr(content)) - return false; + MOZ_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_ENTRY); + LittleEndian::writeUint64(point++, PairToUInt64(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_ALLOC_DATA)); + LittleEndian::writeUint64(point++, reinterpret_cast<uint64_t>(content)); + LittleEndian::writeUint64(point++, 0); } + JS_ASSERT(point <= out.rawBuffer() + out.count()); + JS_ASSERT_IF(point < out.rawBuffer() + out.count(), + uint32_t(LittleEndian::readUint64(point) >> 32) != SCTAG_TRANSFER_MAP_ENTRY); + return true; } bool JSStructuredCloneWriter::write(const Value &v) { if (!startWrite(v)) return false; @@ -964,18 +1056,17 @@ JSStructuredCloneWriter::write(const Val } else { out.writePair(SCTAG_NULL, 0); objs.popBack(); counts.popBack(); } } memory.clear(); - - return true; + return transferOwnership(); } bool JSStructuredCloneReader::checkDouble(double d) { jsval_layout l; l.asDouble = d; if (!JSVAL_IS_DOUBLE_IMPL(l)) { @@ -1291,17 +1382,17 @@ JSStructuredCloneReader::startRead(Value case SCTAG_TRANSFER_MAP_HEADER: // A map header cannot be here but just at the beginning of the buffer. JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input"); return false; - case SCTAG_TRANSFER_MAP: + case SCTAG_TRANSFER_MAP_ENTRY: // A map cannot be here but just at the beginning of the buffer. JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input"); return false; case SCTAG_ARRAY_BUFFER_OBJECT: if (!readArrayBuffer(data, vp)) @@ -1377,47 +1468,67 @@ JSStructuredCloneReader::readId(jsid *id JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, "id"); return false; } bool JSStructuredCloneReader::readTransferMap() { + uint64_t *headerPos = in.tell(); + uint32_t tag, data; if (!in.getPair(&tag, &data)) return false; - if (tag != SCTAG_TRANSFER_MAP_HEADER || - (TransferableMapHeader)data == SCTAG_TM_MARKED) + if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) return true; - if (!in.replacePair(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_MARKED)) - return false; - - if (!in.readPair(&tag, &data)) + uint64_t numTransferables; + MOZ_ALWAYS_TRUE(in.readPair(&tag, &data)); + if (!in.read(&numTransferables)) return false; - while (1) { - if (!in.getPair(&tag, &data)) + for (uint64_t i = 0; i < numTransferables; i++) { + uint64_t *pos = in.tell(); + + if (!in.readPair(&tag, &data)) + return false; + JS_ASSERT(tag == SCTAG_TRANSFER_MAP_ENTRY); + JS_ASSERT(data == SCTAG_TM_ALLOC_DATA); + + void *content; + if (!in.readPtr(&content)) return false; - if (tag != SCTAG_TRANSFER_MAP) - break; - - void *content; - - if (!in.readPair(&tag, &data) || !in.readPtr(&content)) + uint64_t userdata; + if (!in.read(&userdata)) return false; JSObject *obj = JS_NewArrayBufferWithContents(context(), content); - if (!obj || !allObjs.append(ObjectValue(*obj))) + if (!obj) + return false; + + // Rewind to the SCTAG_TRANSFER_MAP_ENTRY and mark this entry as unowned by + // the input buffer. + uint64_t *next = in.tell(); + in.seek(pos); + MOZ_ALWAYS_TRUE(in.replacePair(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_UNOWNED)); + in.seek(next); + + if (!allObjs.append(ObjectValue(*obj))) return false; } + // Mark the whole transfer map as consumed + uint64_t *endPos = in.tell(); + in.seek(headerPos); + MOZ_ALWAYS_TRUE(in.replacePair(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED)); + in.seek(endPos); + return true; } bool JSStructuredCloneReader::read(Value *vp) { if (!readTransferMap()) return false; @@ -1482,17 +1593,18 @@ JS_WriteStructuredClone(JSContext *cx, J optionalCallbacks : cx->runtime()->structuredCloneCallbacks; return WriteStructuredClone(cx, value, bufp, nbytesp, callbacks, closure, transferable); } JS_PUBLIC_API(bool) JS_ClearStructuredClone(const uint64_t *data, size_t nbytes) { - return ClearStructuredClone(data, nbytes); + ClearStructuredClone(data, nbytes); + return true; } JS_PUBLIC_API(bool) JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes, bool *hasTransferable) { bool transferable; if (!StructuredCloneHasTransferObjects(data, nbytes, &transferable))