mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-10 02:29:43 +00:00
2446 lines
76 KiB
C++
2446 lines
76 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/*
|
|
* This file implements the structured clone algorithm of
|
|
* http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#safe-passing-of-structured-data
|
|
*
|
|
* The implementation differs slightly in that it uses an explicit stack, and
|
|
* the "memory" maps source objects to sequential integer indexes rather than
|
|
* directly pointing to destination objects. As a result, the order in which
|
|
* things are added to the memory must exactly match the order in which they
|
|
* are placed into 'allObjs', an analogous array of back-referenceable
|
|
* destination objects constructed while reading.
|
|
*
|
|
* For the most part, this is easy: simply add objects to the memory when first
|
|
* encountering them. But reading in a typed array requires an ArrayBuffer for
|
|
* construction, so objects cannot just be added to 'allObjs' in the order they
|
|
* are created. If they were, ArrayBuffers would come before typed arrays when
|
|
* in fact the typed array was added to 'memory' first.
|
|
*
|
|
* So during writing, we add objects to the memory when first encountering
|
|
* them. When reading a typed array, a placeholder is pushed onto allObjs until
|
|
* the ArrayBuffer has been read, then it is updated with the actual typed
|
|
* array object.
|
|
*/
|
|
|
|
#include "js/StructuredClone.h"
|
|
|
|
#include "mozilla/CheckedInt.h"
|
|
#include "mozilla/Endian.h"
|
|
#include "mozilla/FloatingPoint.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "jsapi.h"
|
|
#include "jscntxt.h"
|
|
#include "jsdate.h"
|
|
#include "jswrapper.h"
|
|
|
|
#include "builtin/MapObject.h"
|
|
#include "js/Date.h"
|
|
#include "js/GCHashTable.h"
|
|
#include "vm/SavedFrame.h"
|
|
#include "vm/SharedArrayObject.h"
|
|
#include "vm/TypedArrayObject.h"
|
|
#include "vm/WrapperObject.h"
|
|
|
|
#include "jscntxtinlines.h"
|
|
#include "jsobjinlines.h"
|
|
|
|
using namespace js;
|
|
|
|
using mozilla::BitwiseCast;
|
|
using mozilla::IsNaN;
|
|
using mozilla::LittleEndian;
|
|
using mozilla::NativeEndian;
|
|
using mozilla::NumbersAreIdentical;
|
|
using JS::CanonicalizeNaN;
|
|
|
|
// When you make updates here, make sure you consider whether you need to bump the
|
|
// value of JS_STRUCTURED_CLONE_VERSION in js/public/StructuredClone.h. You will
|
|
// likely need to increment the version if anything at all changes in the serialization
|
|
// format.
|
|
//
|
|
// Note that SCTAG_END_OF_KEYS is written into the serialized form and should have
|
|
// a stable ID, it need not be at the end of the list and should not be used for
|
|
// sizing data structures.
|
|
|
|
enum StructuredDataType : uint32_t {
|
|
/* Structured data types provided by the engine */
|
|
SCTAG_FLOAT_MAX = 0xFFF00000,
|
|
SCTAG_NULL = 0xFFFF0000,
|
|
SCTAG_UNDEFINED,
|
|
SCTAG_BOOLEAN,
|
|
SCTAG_INT32,
|
|
SCTAG_STRING,
|
|
SCTAG_DATE_OBJECT,
|
|
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_DO_NOT_USE_1, // Required for backwards compatibility
|
|
SCTAG_DO_NOT_USE_2, // Required for backwards compatibility
|
|
SCTAG_TYPED_ARRAY_OBJECT,
|
|
SCTAG_MAP_OBJECT,
|
|
SCTAG_SET_OBJECT,
|
|
SCTAG_END_OF_KEYS,
|
|
SCTAG_SHARED_TYPED_ARRAY_OBJECT,
|
|
SCTAG_DATA_VIEW_OBJECT,
|
|
SCTAG_SAVED_FRAME_OBJECT,
|
|
|
|
SCTAG_JSPRINCIPALS,
|
|
SCTAG_NULL_JSPRINCIPALS,
|
|
SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM,
|
|
SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM,
|
|
|
|
SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
|
|
SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8,
|
|
SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8,
|
|
SCTAG_TYPED_ARRAY_V1_INT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int16,
|
|
SCTAG_TYPED_ARRAY_V1_UINT16 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint16,
|
|
SCTAG_TYPED_ARRAY_V1_INT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int32,
|
|
SCTAG_TYPED_ARRAY_V1_UINT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint32,
|
|
SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32,
|
|
SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64,
|
|
SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped,
|
|
SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::MaxTypedArrayViewType - 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_PENDING_ENTRY,
|
|
SCTAG_TRANSFER_MAP_ARRAY_BUFFER,
|
|
SCTAG_TRANSFER_MAP_SHARED_BUFFER,
|
|
SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES,
|
|
|
|
SCTAG_END_OF_BUILTIN_TYPES
|
|
};
|
|
|
|
/*
|
|
* Format of transfer map:
|
|
* <SCTAG_TRANSFER_MAP_HEADER, TransferableMapHeader(UNREAD|TRANSFERRED)>
|
|
* numTransferables (64 bits)
|
|
* array of:
|
|
* <SCTAG_TRANSFER_MAP_*, TransferableOwnership>
|
|
* pointer (64 bits)
|
|
* extraData (64 bits), eg byte length for ArrayBuffers
|
|
*/
|
|
|
|
// Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
|
|
// contents have been read out yet or not.
|
|
enum TransferableMapHeader {
|
|
SCTAG_TM_UNREAD = 0,
|
|
SCTAG_TM_TRANSFERRED
|
|
};
|
|
|
|
static inline uint64_t
|
|
PairToUInt64(uint32_t tag, uint32_t data)
|
|
{
|
|
return uint64_t(data) | (uint64_t(tag) << 32);
|
|
}
|
|
|
|
namespace js {
|
|
|
|
struct SCOutput {
|
|
public:
|
|
explicit SCOutput(JSContext* cx);
|
|
|
|
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 Latin1Char* p, size_t nchars);
|
|
bool writeChars(const char16_t* 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() const { return buf.length(); }
|
|
uint64_t* rawBuffer() { return buf.begin(); }
|
|
|
|
private:
|
|
JSContext* cx;
|
|
Vector<uint64_t> buf;
|
|
};
|
|
|
|
class SCInput {
|
|
public:
|
|
SCInput(JSContext* cx, uint64_t* data, size_t nbytes);
|
|
|
|
JSContext* context() const { return cx; }
|
|
|
|
static void getPtr(const uint64_t* buffer, void** ptr);
|
|
static void getPair(const uint64_t* buffer, uint32_t* tagp, uint32_t* datap);
|
|
|
|
bool read(uint64_t* p);
|
|
bool readNativeEndian(uint64_t* p);
|
|
bool readPair(uint32_t* tagp, uint32_t* datap);
|
|
bool readDouble(double* p);
|
|
bool readBytes(void* p, size_t nbytes);
|
|
bool readChars(Latin1Char* p, size_t nchars);
|
|
bool readChars(char16_t* p, size_t nchars);
|
|
bool readPtr(void**);
|
|
|
|
bool get(uint64_t* p);
|
|
bool getPair(uint32_t* tagp, uint32_t* datap);
|
|
|
|
uint64_t* tell() const { return point; }
|
|
uint64_t* end() const { return bufEnd; }
|
|
|
|
template <class T>
|
|
bool readArray(T* p, size_t nelems);
|
|
template <class T>
|
|
bool readArraySwapped(T* p, size_t nelems);
|
|
|
|
bool reportTruncated() {
|
|
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
void staticAssertions() {
|
|
JS_STATIC_ASSERT(sizeof(char16_t) == 2);
|
|
JS_STATIC_ASSERT(sizeof(uint32_t) == 4);
|
|
}
|
|
|
|
JSContext* cx;
|
|
uint64_t* point;
|
|
uint64_t* bufEnd;
|
|
};
|
|
|
|
} /* namespace js */
|
|
|
|
struct JSStructuredCloneReader {
|
|
public:
|
|
explicit JSStructuredCloneReader(SCInput& in, const JSStructuredCloneCallbacks* cb,
|
|
void* cbClosure)
|
|
: in(in), objs(in.context()), allObjs(in.context()),
|
|
callbacks(cb), closure(cbClosure) { }
|
|
|
|
SCInput& input() { return in; }
|
|
bool read(MutableHandleValue vp);
|
|
|
|
private:
|
|
JSContext* context() { return in.context(); }
|
|
|
|
bool readTransferMap();
|
|
|
|
template <typename CharT>
|
|
JSString* readStringImpl(uint32_t nchars);
|
|
JSString* readString(uint32_t data);
|
|
|
|
bool checkDouble(double d);
|
|
bool readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp,
|
|
bool v1Read = false);
|
|
bool readDataView(uint32_t byteLength, MutableHandleValue vp);
|
|
bool readArrayBuffer(uint32_t nbytes, MutableHandleValue vp);
|
|
bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp);
|
|
JSObject* readSavedFrame(uint32_t principalsTag);
|
|
bool startRead(MutableHandleValue vp);
|
|
|
|
SCInput& in;
|
|
|
|
// Stack of objects with properties remaining to be read.
|
|
AutoValueVector objs;
|
|
|
|
// Stack of all objects read during this deserialization
|
|
AutoValueVector allObjs;
|
|
|
|
// The user defined callbacks that will be used for cloning.
|
|
const JSStructuredCloneCallbacks* callbacks;
|
|
|
|
// Any value passed to JS_ReadStructuredClone.
|
|
void* closure;
|
|
|
|
friend bool JS_ReadTypedArray(JSStructuredCloneReader* r, MutableHandleValue vp);
|
|
};
|
|
|
|
struct JSStructuredCloneWriter {
|
|
public:
|
|
explicit JSStructuredCloneWriter(JSContext* cx,
|
|
const JSStructuredCloneCallbacks* cb,
|
|
void* cbClosure,
|
|
Value tVal)
|
|
: out(cx), objs(out.context()),
|
|
counts(out.context()), entries(out.context()),
|
|
memory(out.context(), CloneMemory(out.context())), callbacks(cb),
|
|
closure(cbClosure), transferable(out.context(), tVal), transferableObjects(out.context())
|
|
{}
|
|
|
|
~JSStructuredCloneWriter();
|
|
|
|
bool init() { return memory.init() && parseTransferable() && writeTransferMap(); }
|
|
|
|
bool write(HandleValue v);
|
|
|
|
SCOutput& output() { return out; }
|
|
|
|
bool extractBuffer(uint64_t** datap, size_t* sizep) {
|
|
return out.extractBuffer(datap, sizep);
|
|
}
|
|
|
|
private:
|
|
JSStructuredCloneWriter() = delete;
|
|
JSStructuredCloneWriter(const JSStructuredCloneWriter&) = delete;
|
|
|
|
JSContext* context() { return out.context(); }
|
|
|
|
bool writeTransferMap();
|
|
|
|
bool writeString(uint32_t tag, JSString* str);
|
|
bool writeArrayBuffer(HandleObject obj);
|
|
bool writeTypedArray(HandleObject obj);
|
|
bool writeDataView(HandleObject obj);
|
|
bool writeSharedArrayBuffer(HandleObject obj);
|
|
bool startObject(HandleObject obj, bool* backref);
|
|
bool startWrite(HandleValue v);
|
|
bool traverseObject(HandleObject obj);
|
|
bool traverseMap(HandleObject obj);
|
|
bool traverseSet(HandleObject obj);
|
|
bool traverseSavedFrame(HandleObject obj);
|
|
|
|
bool parseTransferable();
|
|
bool reportErrorTransferable(uint32_t errorId);
|
|
bool transferOwnership();
|
|
|
|
inline void checkStack();
|
|
|
|
SCOutput out;
|
|
|
|
// Vector of objects with properties remaining to be written.
|
|
//
|
|
// NB: These can span multiple compartments, so the compartment must be
|
|
// entered before any manipulation is performed.
|
|
AutoValueVector objs;
|
|
|
|
// counts[i] is the number of entries of objs[i] remaining to be written.
|
|
// counts.length() == objs.length() and sum(counts) == entries.length().
|
|
Vector<size_t> counts;
|
|
|
|
// For JSObject: Property IDs as value
|
|
// For Map: Key followed by value
|
|
// For Set: Key
|
|
// For SavedFrame: parent SavedFrame
|
|
AutoValueVector entries;
|
|
|
|
// The "memory" list described in the HTML5 internal structured cloning algorithm.
|
|
// memory is a superset of objs; items are never removed from Memory
|
|
// until a serialization operation is finished
|
|
using CloneMemory = GCHashMap<JSObject*, uint32_t, MovableCellHasher<JSObject*>>;
|
|
Rooted<CloneMemory> memory;
|
|
|
|
// 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
|
|
RootedValue transferable;
|
|
AutoObjectVector transferableObjects;
|
|
|
|
friend bool JS_WriteString(JSStructuredCloneWriter* w, HandleString str);
|
|
friend bool JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v);
|
|
};
|
|
|
|
JS_FRIEND_API(uint64_t)
|
|
js::GetSCOffset(JSStructuredCloneWriter* writer)
|
|
{
|
|
MOZ_ASSERT(writer);
|
|
return writer->output().count() * sizeof(uint64_t);
|
|
}
|
|
|
|
JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
|
|
JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
|
|
JS_STATIC_ASSERT(Scalar::Int8 == 0);
|
|
|
|
static void
|
|
ReportErrorTransferable(JSContext* cx,
|
|
const JSStructuredCloneCallbacks* callbacks,
|
|
uint32_t errorId)
|
|
{
|
|
if (callbacks && callbacks->reportError)
|
|
callbacks->reportError(cx, errorId);
|
|
else if (errorId == JS_SCERR_DUP_TRANSFERABLE)
|
|
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SC_DUP_TRANSFERABLE);
|
|
else
|
|
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE);
|
|
}
|
|
|
|
bool
|
|
WriteStructuredClone(JSContext* cx, HandleValue v, uint64_t** bufp, size_t* nbytesp,
|
|
const JSStructuredCloneCallbacks* cb, void* cbClosure,
|
|
Value transferable)
|
|
{
|
|
JSStructuredCloneWriter w(cx, cb, cbClosure, transferable);
|
|
return w.init() && w.write(v) && w.extractBuffer(bufp, nbytesp);
|
|
}
|
|
|
|
bool
|
|
ReadStructuredClone(JSContext* cx, uint64_t* data, size_t nbytes, MutableHandleValue vp,
|
|
const JSStructuredCloneCallbacks* cb, void* cbClosure)
|
|
{
|
|
SCInput in(cx, data, nbytes);
|
|
JSStructuredCloneReader r(in, cb, cbClosure);
|
|
return r.read(vp);
|
|
}
|
|
|
|
// If the given buffer contains Transferables, free them. Note that custom
|
|
// Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to
|
|
// delete their transferables.
|
|
static void
|
|
DiscardTransferables(uint64_t* buffer, size_t nbytes,
|
|
const JSStructuredCloneCallbacks* cb, void* cbClosure)
|
|
{
|
|
MOZ_ASSERT(nbytes % sizeof(uint64_t) == 0);
|
|
uint64_t* end = buffer + nbytes / sizeof(uint64_t);
|
|
uint64_t* point = buffer;
|
|
if (point == end)
|
|
return; // Empty buffer
|
|
|
|
uint32_t tag, data;
|
|
SCInput::getPair(point++, &tag, &data);
|
|
if (tag != SCTAG_TRANSFER_MAP_HEADER)
|
|
return;
|
|
|
|
if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
|
|
return;
|
|
|
|
// freeTransfer should not GC
|
|
JS::AutoSuppressGCAnalysis nogc;
|
|
|
|
if (point == end)
|
|
return;
|
|
|
|
uint64_t numTransferables = LittleEndian::readUint64(point++);
|
|
while (numTransferables--) {
|
|
if (point == end)
|
|
return;
|
|
|
|
uint32_t ownership;
|
|
SCInput::getPair(point++, &tag, &ownership);
|
|
MOZ_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY);
|
|
if (point == end)
|
|
return;
|
|
|
|
void* content;
|
|
SCInput::getPtr(point++, &content);
|
|
if (point == end)
|
|
return;
|
|
|
|
uint64_t extraData = LittleEndian::readUint64(point++);
|
|
|
|
if (ownership < JS::SCTAG_TMO_FIRST_OWNED)
|
|
continue;
|
|
|
|
if (ownership == JS::SCTAG_TMO_ALLOC_DATA) {
|
|
js_free(content);
|
|
} else if (ownership == JS::SCTAG_TMO_MAPPED_DATA) {
|
|
JS_ReleaseMappedArrayBufferContents(content, extraData);
|
|
} else if (ownership == JS::SCTAG_TMO_SHARED_BUFFER) {
|
|
SharedArrayRawBuffer* raw = static_cast<SharedArrayRawBuffer*>(content);
|
|
if (raw)
|
|
raw->dropReference();
|
|
} else if (cb && cb->freeTransfer) {
|
|
cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure);
|
|
} else {
|
|
MOZ_ASSERT(false, "unknown ownership");
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
StructuredCloneHasTransferObjects(const uint64_t* data, size_t nbytes)
|
|
{
|
|
if (!data)
|
|
return false;
|
|
|
|
uint64_t u = LittleEndian::readUint64(data);
|
|
uint32_t tag = uint32_t(u >> 32);
|
|
return (tag == SCTAG_TRANSFER_MAP_HEADER);
|
|
}
|
|
|
|
namespace js {
|
|
|
|
SCInput::SCInput(JSContext* cx, uint64_t* data, size_t nbytes)
|
|
: cx(cx), point(data), bufEnd(data + nbytes / 8)
|
|
{
|
|
// On 32-bit, we sometimes construct an SCInput from an SCOutput buffer,
|
|
// which is not guaranteed to be 8-byte aligned
|
|
MOZ_ASSERT((uintptr_t(data) & (sizeof(int) - 1)) == 0);
|
|
MOZ_ASSERT((nbytes & 7) == 0);
|
|
}
|
|
|
|
bool
|
|
SCInput::read(uint64_t* p)
|
|
{
|
|
if (point == bufEnd) {
|
|
*p = 0; /* initialize to shut GCC up */
|
|
return reportTruncated();
|
|
}
|
|
*p = LittleEndian::readUint64(point++);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SCInput::readNativeEndian(uint64_t* p)
|
|
{
|
|
if (point == bufEnd) {
|
|
*p = 0; /* initialize to shut GCC up */
|
|
return reportTruncated();
|
|
}
|
|
*p = *(point++);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SCInput::readPair(uint32_t* tagp, uint32_t* datap)
|
|
{
|
|
uint64_t u;
|
|
bool ok = read(&u);
|
|
if (ok) {
|
|
*tagp = uint32_t(u >> 32);
|
|
*datap = uint32_t(u);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
bool
|
|
SCInput::get(uint64_t* p)
|
|
{
|
|
if (point == bufEnd)
|
|
return reportTruncated();
|
|
*p = LittleEndian::readUint64(point);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SCInput::getPair(uint32_t* tagp, uint32_t* datap)
|
|
{
|
|
uint64_t u = 0;
|
|
if (!get(&u))
|
|
return false;
|
|
|
|
*tagp = uint32_t(u >> 32);
|
|
*datap = uint32_t(u);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
SCInput::getPair(const uint64_t* p, uint32_t* tagp, uint32_t* datap)
|
|
{
|
|
uint64_t u = LittleEndian::readUint64(p);
|
|
*tagp = uint32_t(u >> 32);
|
|
*datap = uint32_t(u);
|
|
}
|
|
|
|
bool
|
|
SCInput::readDouble(double* p)
|
|
{
|
|
union {
|
|
uint64_t u;
|
|
double d;
|
|
} pun;
|
|
if (!read(&pun.u))
|
|
return false;
|
|
*p = CanonicalizeNaN(pun.d);
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
static void
|
|
copyAndSwapFromLittleEndian(T* dest, const void* src, size_t nelems)
|
|
{
|
|
if (nelems > 0)
|
|
NativeEndian::copyAndSwapFromLittleEndian(dest, src, nelems);
|
|
}
|
|
|
|
template <>
|
|
void
|
|
copyAndSwapFromLittleEndian(uint8_t* dest, const void* src, size_t nelems)
|
|
{
|
|
memcpy(dest, src, nelems);
|
|
}
|
|
|
|
template <class T>
|
|
bool
|
|
SCInput::readArray(T* p, size_t nelems)
|
|
{
|
|
JS_STATIC_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
|
|
|
|
/*
|
|
* Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is
|
|
* larger than the remaining data.
|
|
*/
|
|
size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
|
|
if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(bufEnd - point))
|
|
return reportTruncated();
|
|
|
|
copyAndSwapFromLittleEndian(p, point, nelems);
|
|
point += nwords;
|
|
return true;
|
|
}
|
|
|
|
// Almost the same, but with byte swapping for little endian emulation!
|
|
// Or, in this case, no swapping.
|
|
template <class T>
|
|
bool
|
|
SCInput::readArraySwapped(T* p, size_t nelems)
|
|
{
|
|
|
|
/*
|
|
* Fail if nelems is so huge as to make JS_HOWMANY overflow or if nwords is
|
|
* larger than the remaining data.
|
|
*/
|
|
size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
|
|
if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(bufEnd - point))
|
|
return reportTruncated();
|
|
|
|
if (nelems > 0)
|
|
memcpy(p, point, nelems * sizeof(T));
|
|
point += nwords;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SCInput::readBytes(void* p, size_t nbytes)
|
|
{
|
|
return readArray((uint8_t*) p, nbytes);
|
|
}
|
|
|
|
bool
|
|
SCInput::readChars(Latin1Char* p, size_t nchars)
|
|
{
|
|
static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");
|
|
return readBytes(p, nchars);
|
|
}
|
|
|
|
bool
|
|
SCInput::readChars(char16_t* p, size_t nchars)
|
|
{
|
|
MOZ_ASSERT(sizeof(char16_t) == sizeof(uint16_t));
|
|
return readArray((uint16_t*) p, nchars);
|
|
}
|
|
|
|
void
|
|
SCInput::getPtr(const uint64_t* p, void** ptr)
|
|
{
|
|
// No endianness conversion is used for pointers, since they are not sent
|
|
// across address spaces anyway.
|
|
*ptr = reinterpret_cast<void*>(*p);
|
|
}
|
|
|
|
bool
|
|
SCInput::readPtr(void** p)
|
|
{
|
|
uint64_t u;
|
|
if (!readNativeEndian(&u))
|
|
return false;
|
|
*p = reinterpret_cast<void*>(NativeEndian::swapFromLittleEndian(u));
|
|
return true;
|
|
}
|
|
|
|
SCOutput::SCOutput(JSContext* cx) : cx(cx), buf(cx) {}
|
|
|
|
bool
|
|
SCOutput::write(uint64_t u)
|
|
{
|
|
return buf.append(NativeEndian::swapToLittleEndian(u));
|
|
}
|
|
|
|
bool
|
|
SCOutput::writePair(uint32_t tag, uint32_t data)
|
|
{
|
|
/*
|
|
* As it happens, the tag word appears after the data word in the output.
|
|
* This is because exponents occupy the last 2 bytes of doubles on the
|
|
* little-endian platforms we care most about.
|
|
*
|
|
* For example, TrueValue() is written using writePair(SCTAG_BOOLEAN, 1).
|
|
* PairToUInt64 produces the number 0xFFFF000200000001.
|
|
* That is written out as the bytes 01 00 00 00 02 00 FF FF.
|
|
*/
|
|
return write(PairToUInt64(tag, data));
|
|
}
|
|
|
|
static inline double
|
|
ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
|
|
{
|
|
return BitwiseCast<double>(PairToUInt64(tag, data));
|
|
}
|
|
|
|
bool
|
|
SCOutput::writeDouble(double d)
|
|
{
|
|
return write(BitwiseCast<uint64_t>(CanonicalizeNaN(d)));
|
|
}
|
|
|
|
template <typename T>
|
|
static void
|
|
copyAndSwapToLittleEndian(void* dest, const T* src, size_t nelems)
|
|
{
|
|
if (nelems > 0)
|
|
NativeEndian::copyAndSwapToLittleEndian(dest, src, nelems);
|
|
}
|
|
|
|
template <>
|
|
void
|
|
copyAndSwapToLittleEndian(void* dest, const uint8_t* src, size_t nelems)
|
|
{
|
|
memcpy(dest, src, nelems);
|
|
}
|
|
|
|
template <class T>
|
|
bool
|
|
SCOutput::writeArray(const T* p, size_t nelems)
|
|
{
|
|
MOZ_ASSERT(8 % sizeof(T) == 0);
|
|
MOZ_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
|
|
|
|
if (nelems == 0)
|
|
return true;
|
|
|
|
if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
|
|
ReportAllocationOverflow(context());
|
|
return false;
|
|
}
|
|
size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T));
|
|
size_t start = buf.length();
|
|
if (!buf.growByUninitialized(nwords))
|
|
return false;
|
|
|
|
buf.back() = 0; /* zero-pad to an 8-byte boundary */
|
|
|
|
T* q = (T*) &buf[start];
|
|
copyAndSwapToLittleEndian(q, p, nelems);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SCOutput::writeBytes(const void* p, size_t nbytes)
|
|
{
|
|
return writeArray((const uint8_t*) p, nbytes);
|
|
}
|
|
|
|
bool
|
|
SCOutput::writeChars(const char16_t* p, size_t nchars)
|
|
{
|
|
static_assert(sizeof(char16_t) == sizeof(uint16_t),
|
|
"required so that treating char16_t[] memory as uint16_t[] "
|
|
"memory is permissible");
|
|
return writeArray((const uint16_t*) p, nchars);
|
|
}
|
|
|
|
bool
|
|
SCOutput::writeChars(const Latin1Char* p, size_t nchars)
|
|
{
|
|
static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");
|
|
return writeBytes(p, nchars);
|
|
}
|
|
|
|
bool
|
|
SCOutput::writePtr(const void* p)
|
|
{
|
|
return write(reinterpret_cast<uint64_t>(p));
|
|
}
|
|
|
|
bool
|
|
SCOutput::extractBuffer(uint64_t** datap, size_t* sizep)
|
|
{
|
|
*sizep = buf.length() * sizeof(uint64_t);
|
|
return (*datap = buf.extractRawBuffer()) != nullptr;
|
|
}
|
|
|
|
} /* namespace js */
|
|
|
|
JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
|
|
|
|
JSStructuredCloneWriter::~JSStructuredCloneWriter()
|
|
{
|
|
// Free any transferable data left lying around in the buffer
|
|
uint64_t* data;
|
|
size_t size;
|
|
{
|
|
AutoEnterOOMUnsafeRegion oomUnsafe;
|
|
if (!extractBuffer(&data, &size))
|
|
oomUnsafe.crash("Unable to extract clone buffer");
|
|
DiscardTransferables(data, size, callbacks, closure);
|
|
js_free(data);
|
|
}
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::parseTransferable()
|
|
{
|
|
MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data");
|
|
|
|
if (transferable.isNull() || transferable.isUndefined())
|
|
return true;
|
|
|
|
if (!transferable.isObject())
|
|
return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
|
|
|
|
JSContext* cx = context();
|
|
RootedObject array(cx, &transferable.toObject());
|
|
bool isArray;
|
|
if (!JS_IsArrayObject(cx, array, &isArray))
|
|
return false;
|
|
if (!isArray)
|
|
return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
|
|
|
|
uint32_t length;
|
|
if (!JS_GetArrayLength(cx, array, &length)) {
|
|
return false;
|
|
}
|
|
|
|
RootedValue v(context());
|
|
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
if (!JS_GetElement(cx, array, i, &v))
|
|
return false;
|
|
|
|
if (!v.isObject())
|
|
return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
|
|
RootedObject tObj(context(), &v.toObject());
|
|
|
|
// No duplicates allowed
|
|
if (std::find(transferableObjects.begin(), transferableObjects.end(), tObj) != transferableObjects.end()) {
|
|
return reportErrorTransferable(JS_SCERR_DUP_TRANSFERABLE);
|
|
}
|
|
|
|
if (!transferableObjects.append(tObj))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::reportErrorTransferable(uint32_t errorId)
|
|
{
|
|
ReportErrorTransferable(context(), callbacks, errorId);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str)
|
|
{
|
|
JSLinearString* linear = str->ensureLinear(context());
|
|
if (!linear)
|
|
return false;
|
|
|
|
static_assert(JSString::MAX_LENGTH <= INT32_MAX, "String length must fit in 31 bits");
|
|
|
|
uint32_t length = linear->length();
|
|
uint32_t lengthAndEncoding = length | (uint32_t(linear->hasLatin1Chars()) << 31);
|
|
if (!out.writePair(tag, lengthAndEncoding))
|
|
return false;
|
|
|
|
JS::AutoCheckCannotGC nogc;
|
|
return linear->hasLatin1Chars()
|
|
? out.writeChars(linear->latin1Chars(nogc), length)
|
|
: out.writeChars(linear->twoByteChars(nogc), length);
|
|
}
|
|
|
|
inline void
|
|
JSStructuredCloneWriter::checkStack()
|
|
{
|
|
#ifdef DEBUG
|
|
/* To avoid making serialization O(n^2), limit stack-checking at 10. */
|
|
const size_t MAX = 10;
|
|
|
|
size_t limit = Min(counts.length(), MAX);
|
|
MOZ_ASSERT(objs.length() == counts.length());
|
|
size_t total = 0;
|
|
for (size_t i = 0; i < limit; i++) {
|
|
MOZ_ASSERT(total + counts[i] >= total);
|
|
total += counts[i];
|
|
}
|
|
if (counts.length() <= MAX)
|
|
MOZ_ASSERT(total == entries.length());
|
|
else
|
|
MOZ_ASSERT(total <= entries.length());
|
|
|
|
size_t j = objs.length();
|
|
for (size_t i = 0; i < limit; i++)
|
|
MOZ_ASSERT(memory.has(&objs[--j].toObject()));
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Write out a typed array. Note that post-v1 structured clone buffers do not
|
|
* perform endianness conversion on stored data, so multibyte typed arrays
|
|
* cannot be deserialized into a different endianness machine. Endianness
|
|
* conversion would prevent sharing ArrayBuffers: if you have Int8Array and
|
|
* Int16Array views of the same ArrayBuffer, should the data bytes be
|
|
* byte-swapped when writing or not? The Int8Array requires them to not be
|
|
* swapped; the Int16Array requires that they are.
|
|
*/
|
|
bool
|
|
JSStructuredCloneWriter::writeTypedArray(HandleObject obj)
|
|
{
|
|
Rooted<TypedArrayObject*> tarr(context(), &CheckedUnwrap(obj)->as<TypedArrayObject>());
|
|
JSAutoCompartment ac(context(), tarr);
|
|
|
|
if (!TypedArrayObject::ensureHasBuffer(context(), tarr))
|
|
return false;
|
|
|
|
if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length()))
|
|
return false;
|
|
uint64_t type = tarr->type();
|
|
if (!out.write(type))
|
|
return false;
|
|
|
|
// Write out the ArrayBuffer tag and contents
|
|
RootedValue val(context(), TypedArrayObject::bufferValue(tarr));
|
|
if (!startWrite(val))
|
|
return false;
|
|
|
|
return out.write(tarr->byteOffset());
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::writeDataView(HandleObject obj)
|
|
{
|
|
Rooted<DataViewObject*> view(context(), &CheckedUnwrap(obj)->as<DataViewObject>());
|
|
JSAutoCompartment ac(context(), view);
|
|
|
|
if (!out.writePair(SCTAG_DATA_VIEW_OBJECT, view->byteLength()))
|
|
return false;
|
|
|
|
// Write out the ArrayBuffer tag and contents
|
|
RootedValue val(context(), DataViewObject::bufferValue(view));
|
|
if (!startWrite(val))
|
|
return false;
|
|
|
|
return out.write(view->byteOffset());
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj)
|
|
{
|
|
ArrayBufferObject& buffer = CheckedUnwrap(obj)->as<ArrayBufferObject>();
|
|
JSAutoCompartment ac(context(), &buffer);
|
|
|
|
return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) &&
|
|
out.writeBytes(buffer.dataPointer(), buffer.byteLength());
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::writeSharedArrayBuffer(HandleObject obj)
|
|
{
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_SC_SHMEM_MUST_TRANSFER);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::startObject(HandleObject obj, bool* backref)
|
|
{
|
|
/* Handle cycles in the object graph. */
|
|
CloneMemory::AddPtr p = memory.lookupForAdd(obj);
|
|
if ((*backref = p.found()))
|
|
return out.writePair(SCTAG_BACK_REFERENCE_OBJECT, p->value());
|
|
if (!memory.add(p, obj, memory.count()))
|
|
return false;
|
|
|
|
if (memory.count() == UINT32_MAX) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_NEED_DIET, "object graph to serialize");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::traverseObject(HandleObject obj)
|
|
{
|
|
/*
|
|
* Get enumerable property ids and put them in reverse order so that they
|
|
* will come off the stack in forward order.
|
|
*/
|
|
AutoIdVector properties(context());
|
|
if (!GetPropertyKeys(context(), obj, JSITER_OWNONLY, &properties))
|
|
return false;
|
|
|
|
for (size_t i = properties.length(); i > 0; --i) {
|
|
MOZ_ASSERT(JSID_IS_STRING(properties[i - 1]) || JSID_IS_INT(properties[i - 1]));
|
|
RootedValue val(context(), IdToValue(properties[i - 1]));
|
|
if (!entries.append(val))
|
|
return false;
|
|
}
|
|
|
|
/* Push obj and count to the stack. */
|
|
if (!objs.append(ObjectValue(*obj)) || !counts.append(properties.length()))
|
|
return false;
|
|
|
|
checkStack();
|
|
|
|
/* Write the header for obj. */
|
|
ESClassValue cls;
|
|
if (!GetBuiltinClass(context(), obj, &cls))
|
|
return false;
|
|
return out.writePair(cls == ESClass_Array ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::traverseMap(HandleObject obj)
|
|
{
|
|
AutoValueVector newEntries(context());
|
|
{
|
|
// If there is no wrapper, the compartment munging is a no-op.
|
|
RootedObject unwrapped(context(), CheckedUnwrap(obj));
|
|
MOZ_ASSERT(unwrapped);
|
|
JSAutoCompartment ac(context(), unwrapped);
|
|
if (!MapObject::getKeysAndValuesInterleaved(context(), unwrapped, &newEntries))
|
|
return false;
|
|
}
|
|
if (!context()->compartment()->wrap(context(), newEntries))
|
|
return false;
|
|
|
|
for (size_t i = newEntries.length(); i > 0; --i) {
|
|
if (!entries.append(newEntries[i - 1]))
|
|
return false;
|
|
}
|
|
|
|
/* Push obj and count to the stack. */
|
|
if (!objs.append(ObjectValue(*obj)) || !counts.append(newEntries.length()))
|
|
return false;
|
|
|
|
checkStack();
|
|
|
|
/* Write the header for obj. */
|
|
return out.writePair(SCTAG_MAP_OBJECT, 0);
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::traverseSet(HandleObject obj)
|
|
{
|
|
AutoValueVector keys(context());
|
|
{
|
|
// If there is no wrapper, the compartment munging is a no-op.
|
|
RootedObject unwrapped(context(), CheckedUnwrap(obj));
|
|
MOZ_ASSERT(unwrapped);
|
|
JSAutoCompartment ac(context(), unwrapped);
|
|
if (!SetObject::keys(context(), unwrapped, &keys))
|
|
return false;
|
|
}
|
|
if (!context()->compartment()->wrap(context(), keys))
|
|
return false;
|
|
|
|
for (size_t i = keys.length(); i > 0; --i) {
|
|
if (!entries.append(keys[i - 1]))
|
|
return false;
|
|
}
|
|
|
|
/* Push obj and count to the stack. */
|
|
if (!objs.append(ObjectValue(*obj)) || !counts.append(keys.length()))
|
|
return false;
|
|
|
|
checkStack();
|
|
|
|
/* Write the header for obj. */
|
|
return out.writePair(SCTAG_SET_OBJECT, 0);
|
|
}
|
|
|
|
// Objects are written as a "preorder" traversal of the object graph: object
|
|
// "headers" (the class tag and any data needed for initial construction) are
|
|
// visited first, then the children are recursed through (where children are
|
|
// properties, Set or Map entries, etc.). So for example
|
|
//
|
|
// m = new Map();
|
|
// m.set(key1 = {}, value1 = {})
|
|
//
|
|
// would be stored as
|
|
//
|
|
// <Map tag>
|
|
// <key1 class tag>
|
|
// <value1 class tag>
|
|
// <end-of-children marker for key1>
|
|
// <end-of-children marker for value1>
|
|
// <end-of-children marker for Map>
|
|
//
|
|
// Notice how the end-of-children marker for key1 is sandwiched between the
|
|
// value1 beginning and end.
|
|
bool
|
|
JSStructuredCloneWriter::traverseSavedFrame(HandleObject obj)
|
|
{
|
|
RootedObject unwrapped(context(), js::CheckedUnwrap(obj));
|
|
MOZ_ASSERT(unwrapped && unwrapped->is<SavedFrame>());
|
|
|
|
RootedSavedFrame savedFrame(context(), &unwrapped->as<SavedFrame>());
|
|
|
|
RootedObject parent(context(), savedFrame->getParent());
|
|
if (!context()->compartment()->wrap(context(), &parent))
|
|
return false;
|
|
|
|
if (!objs.append(ObjectValue(*obj)) ||
|
|
!entries.append(parent ? ObjectValue(*parent) : NullValue()) ||
|
|
!counts.append(1))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
checkStack();
|
|
|
|
// Write the SavedFrame tag and the SavedFrame's principals.
|
|
|
|
if (savedFrame->getPrincipals() == &ReconstructedSavedFramePrincipals::IsSystem) {
|
|
if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
|
|
SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM))
|
|
{
|
|
return false;
|
|
};
|
|
} else if (savedFrame->getPrincipals() == &ReconstructedSavedFramePrincipals::IsNotSystem) {
|
|
if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT,
|
|
SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM))
|
|
{
|
|
return false;
|
|
}
|
|
} else {
|
|
if (auto principals = savedFrame->getPrincipals()) {
|
|
if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_JSPRINCIPALS) ||
|
|
!principals->write(context(), this))
|
|
{
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!out.writePair(SCTAG_SAVED_FRAME_OBJECT, SCTAG_NULL_JSPRINCIPALS))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Write the SavedFrame's reserved slots, except for the parent, which is
|
|
// queued on objs for further traversal.
|
|
|
|
RootedValue val(context());
|
|
|
|
val = StringValue(savedFrame->getSource());
|
|
if (!startWrite(val))
|
|
return false;
|
|
|
|
val = NumberValue(savedFrame->getLine());
|
|
if (!startWrite(val))
|
|
return false;
|
|
|
|
val = NumberValue(savedFrame->getColumn());
|
|
if (!startWrite(val))
|
|
return false;
|
|
|
|
auto name = savedFrame->getFunctionDisplayName();
|
|
val = name ? StringValue(name) : NullValue();
|
|
if (!startWrite(val))
|
|
return false;
|
|
|
|
auto cause = savedFrame->getAsyncCause();
|
|
val = cause ? StringValue(cause) : NullValue();
|
|
if (!startWrite(val))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::startWrite(HandleValue v)
|
|
{
|
|
assertSameCompartment(context(), v);
|
|
|
|
if (v.isString()) {
|
|
return writeString(SCTAG_STRING, v.toString());
|
|
} else if (v.isInt32()) {
|
|
return out.writePair(SCTAG_INT32, v.toInt32());
|
|
} else if (v.isDouble()) {
|
|
return out.writeDouble(v.toDouble());
|
|
} else if (v.isBoolean()) {
|
|
return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
|
|
} else if (v.isNull()) {
|
|
return out.writePair(SCTAG_NULL, 0);
|
|
} else if (v.isUndefined()) {
|
|
return out.writePair(SCTAG_UNDEFINED, 0);
|
|
} else if (v.isObject()) {
|
|
RootedObject obj(context(), &v.toObject());
|
|
|
|
bool backref;
|
|
if (!startObject(obj, &backref))
|
|
return false;
|
|
if (backref)
|
|
return true;
|
|
|
|
ESClassValue cls;
|
|
if (!GetBuiltinClass(context(), obj, &cls))
|
|
return false;
|
|
|
|
if (cls == ESClass_RegExp) {
|
|
RegExpGuard re(context());
|
|
if (!RegExpToShared(context(), obj, &re))
|
|
return false;
|
|
return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) &&
|
|
writeString(SCTAG_STRING, re->getSource());
|
|
} else if (cls == ESClass_Date) {
|
|
RootedValue unboxed(context());
|
|
if (!Unbox(context(), obj, &unboxed))
|
|
return false;
|
|
return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(unboxed.toNumber());
|
|
} else if (JS_IsTypedArrayObject(obj)) {
|
|
return writeTypedArray(obj);
|
|
} else if (JS_IsDataViewObject(obj)) {
|
|
return writeDataView(obj);
|
|
} else if (JS_IsArrayBufferObject(obj) && JS_ArrayBufferHasData(obj)) {
|
|
return writeArrayBuffer(obj);
|
|
} else if (JS_IsSharedArrayBufferObject(obj)) {
|
|
return writeSharedArrayBuffer(obj);
|
|
} else if (cls == ESClass_Object) {
|
|
return traverseObject(obj);
|
|
} else if (cls == ESClass_Array) {
|
|
return traverseObject(obj);
|
|
} else if (cls == ESClass_Boolean) {
|
|
RootedValue unboxed(context());
|
|
if (!Unbox(context(), obj, &unboxed))
|
|
return false;
|
|
return out.writePair(SCTAG_BOOLEAN_OBJECT, unboxed.toBoolean());
|
|
} else if (cls == ESClass_Number) {
|
|
RootedValue unboxed(context());
|
|
if (!Unbox(context(), obj, &unboxed))
|
|
return false;
|
|
return out.writePair(SCTAG_NUMBER_OBJECT, 0) && out.writeDouble(unboxed.toNumber());
|
|
} else if (cls == ESClass_String) {
|
|
RootedValue unboxed(context());
|
|
if (!Unbox(context(), obj, &unboxed))
|
|
return false;
|
|
return writeString(SCTAG_STRING_OBJECT, unboxed.toString());
|
|
} else if (cls == ESClass_Map) {
|
|
return traverseMap(obj);
|
|
} else if (cls == ESClass_Set) {
|
|
return traverseSet(obj);
|
|
} else if (SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
|
|
return traverseSavedFrame(obj);
|
|
}
|
|
|
|
if (callbacks && callbacks->write)
|
|
return callbacks->write(context(), this, obj, closure);
|
|
/* else fall through */
|
|
}
|
|
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_SC_UNSUPPORTED_TYPE);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::writeTransferMap()
|
|
{
|
|
if (transferableObjects.empty())
|
|
return true;
|
|
|
|
if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD))
|
|
return false;
|
|
|
|
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, in the case of ArrayBufferObject.
|
|
if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED))
|
|
return false;
|
|
if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents or to SharedArrayRawBuffer.
|
|
return false;
|
|
if (!out.write(0)) // extraData
|
|
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();
|
|
MOZ_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_HEADER);
|
|
point++;
|
|
MOZ_ASSERT(LittleEndian::readUint64(point) == transferableObjects.length());
|
|
point++;
|
|
|
|
for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) {
|
|
RootedObject obj(context(), tr.front());
|
|
|
|
uint32_t tag;
|
|
JS::TransferableOwnership ownership;
|
|
void* content;
|
|
uint64_t extraData;
|
|
|
|
#if DEBUG
|
|
SCInput::getPair(point, &tag, (uint32_t*) &ownership);
|
|
MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY);
|
|
MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED);
|
|
#endif
|
|
|
|
ESClassValue cls;
|
|
if (!GetBuiltinClass(context(), obj, &cls))
|
|
return false;
|
|
|
|
if (cls == ESClass_ArrayBuffer) {
|
|
// The current setup of the array buffer inheritance hierarchy doesn't
|
|
// lend itself well to generic manipulation via proxies.
|
|
Rooted<ArrayBufferObject*> arrayBuffer(context(), &CheckedUnwrap(obj)->as<ArrayBufferObject>());
|
|
JSAutoCompartment ac(context(), arrayBuffer);
|
|
size_t nbytes = arrayBuffer->byteLength();
|
|
|
|
// Structured cloning currently only has optimizations for mapped
|
|
// and malloc'd buffers, not asm.js-ified buffers.
|
|
bool hasStealableContents = arrayBuffer->hasStealableContents() &&
|
|
(arrayBuffer->isMapped() || arrayBuffer->hasMallocedContents());
|
|
|
|
ArrayBufferObject::BufferContents bufContents =
|
|
ArrayBufferObject::stealContents(context(), arrayBuffer, hasStealableContents);
|
|
if (!bufContents)
|
|
return false; // Destructor will clean up the already-transferred data.
|
|
|
|
content = bufContents.data();
|
|
tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER;
|
|
if (bufContents.kind() == ArrayBufferObject::MAPPED)
|
|
ownership = JS::SCTAG_TMO_MAPPED_DATA;
|
|
else
|
|
ownership = JS::SCTAG_TMO_ALLOC_DATA;
|
|
extraData = nbytes;
|
|
} else if (cls == ESClass_SharedArrayBuffer) {
|
|
Rooted<SharedArrayBufferObject*> sharedArrayBuffer(context(), &CheckedUnwrap(obj)->as<SharedArrayBufferObject>());
|
|
SharedArrayRawBuffer* rawbuf = sharedArrayBuffer->rawBufferObject();
|
|
|
|
// Avoids a race condition where the parent thread frees the buffer
|
|
// before the child has accepted the transferable.
|
|
rawbuf->addReference();
|
|
|
|
tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER;
|
|
ownership = JS::SCTAG_TMO_SHARED_BUFFER;
|
|
content = rawbuf;
|
|
extraData = 0;
|
|
} else {
|
|
if (!callbacks || !callbacks->writeTransfer)
|
|
return reportErrorTransferable(JS_SCERR_TRANSFERABLE);
|
|
if (!callbacks->writeTransfer(context(), obj, closure, &tag, &ownership, &content, &extraData))
|
|
return false;
|
|
MOZ_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY);
|
|
}
|
|
|
|
LittleEndian::writeUint64(point++, PairToUInt64(tag, ownership));
|
|
LittleEndian::writeUint64(point++, reinterpret_cast<uint64_t>(content));
|
|
LittleEndian::writeUint64(point++, extraData);
|
|
}
|
|
|
|
MOZ_ASSERT(point <= out.rawBuffer() + out.count());
|
|
MOZ_ASSERT_IF(point < out.rawBuffer() + out.count(),
|
|
uint32_t(LittleEndian::readUint64(point) >> 32) < SCTAG_TRANSFER_MAP_HEADER);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneWriter::write(HandleValue v)
|
|
{
|
|
if (!startWrite(v))
|
|
return false;
|
|
|
|
while (!counts.empty()) {
|
|
RootedObject obj(context(), &objs.back().toObject());
|
|
AutoCompartment ac(context(), obj);
|
|
if (counts.back()) {
|
|
counts.back()--;
|
|
RootedValue key(context(), entries.back());
|
|
entries.popBack();
|
|
checkStack();
|
|
|
|
ESClassValue cls;
|
|
if (!GetBuiltinClass(context(), obj, &cls))
|
|
return false;
|
|
|
|
if (cls == ESClass_Map) {
|
|
counts.back()--;
|
|
RootedValue val(context(), entries.back());
|
|
entries.popBack();
|
|
checkStack();
|
|
|
|
if (!startWrite(key) || !startWrite(val))
|
|
return false;
|
|
} else if (cls == ESClass_Set || SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) {
|
|
if (!startWrite(key))
|
|
return false;
|
|
} else {
|
|
RootedId id(context());
|
|
if (!ValueToId<CanGC>(context(), key, &id))
|
|
return false;
|
|
MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
|
|
|
|
/*
|
|
* If obj still has an own property named id, write it out.
|
|
* The cost of re-checking could be avoided by using
|
|
* NativeIterators.
|
|
*/
|
|
bool found;
|
|
if (!HasOwnProperty(context(), obj, id, &found))
|
|
return false;
|
|
|
|
if (found) {
|
|
RootedValue val(context());
|
|
if (!startWrite(key) ||
|
|
!GetProperty(context(), obj, obj, id, &val) ||
|
|
!startWrite(val))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
out.writePair(SCTAG_END_OF_KEYS, 0);
|
|
objs.popBack();
|
|
counts.popBack();
|
|
}
|
|
}
|
|
|
|
memory.clear();
|
|
return transferOwnership();
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneReader::checkDouble(double d)
|
|
{
|
|
jsval_layout l;
|
|
l.asDouble = d;
|
|
if (!JSVAL_IS_DOUBLE_IMPL(l)) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
|
|
template <typename CharT>
|
|
class Chars {
|
|
JSContext* cx;
|
|
CharT* p;
|
|
public:
|
|
explicit Chars(JSContext* cx) : cx(cx), p(nullptr) {}
|
|
~Chars() { js_free(p); }
|
|
|
|
bool allocate(size_t len) {
|
|
MOZ_ASSERT(!p);
|
|
// We're going to null-terminate!
|
|
p = cx->pod_malloc<CharT>(len + 1);
|
|
if (p) {
|
|
p[len] = CharT(0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
CharT* get() { return p; }
|
|
void forget() { p = nullptr; }
|
|
};
|
|
|
|
} /* anonymous namespace */
|
|
|
|
template <typename CharT>
|
|
JSString*
|
|
JSStructuredCloneReader::readStringImpl(uint32_t nchars)
|
|
{
|
|
if (nchars > JSString::MAX_LENGTH) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "string length");
|
|
return nullptr;
|
|
}
|
|
Chars<CharT> chars(context());
|
|
if (!chars.allocate(nchars) || !in.readChars(chars.get(), nchars))
|
|
return nullptr;
|
|
JSString* str = NewString<CanGC>(context(), chars.get(), nchars);
|
|
if (str)
|
|
chars.forget();
|
|
return str;
|
|
}
|
|
|
|
JSString*
|
|
JSStructuredCloneReader::readString(uint32_t data)
|
|
{
|
|
uint32_t nchars = data & JS_BITMASK(31);
|
|
bool latin1 = data & (1 << 31);
|
|
return latin1 ? readStringImpl<Latin1Char>(nchars) : readStringImpl<char16_t>(nchars);
|
|
}
|
|
|
|
static uint32_t
|
|
TagToV1ArrayType(uint32_t tag)
|
|
{
|
|
MOZ_ASSERT(tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX);
|
|
return tag - SCTAG_TYPED_ARRAY_V1_MIN;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp,
|
|
bool v1Read)
|
|
{
|
|
if (arrayType > Scalar::Uint8Clamped) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "unhandled typed array element type");
|
|
return false;
|
|
}
|
|
|
|
// Push a placeholder onto the allObjs list to stand in for the typed array
|
|
uint32_t placeholderIndex = allObjs.length();
|
|
Value dummy = UndefinedValue();
|
|
if (!allObjs.append(dummy))
|
|
return false;
|
|
|
|
// Read the ArrayBuffer object and its contents (but no properties)
|
|
RootedValue v(context());
|
|
uint32_t byteOffset;
|
|
if (v1Read) {
|
|
if (!readV1ArrayBuffer(arrayType, nelems, &v))
|
|
return false;
|
|
byteOffset = 0;
|
|
} else {
|
|
if (!startRead(&v))
|
|
return false;
|
|
uint64_t n;
|
|
if (!in.read(&n))
|
|
return false;
|
|
byteOffset = n;
|
|
}
|
|
if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
|
|
"typed array must be backed by an ArrayBuffer");
|
|
return false;
|
|
}
|
|
RootedObject buffer(context(), &v.toObject());
|
|
RootedObject obj(context(), nullptr);
|
|
|
|
switch (arrayType) {
|
|
case Scalar::Int8:
|
|
obj = JS_NewInt8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
case Scalar::Uint8:
|
|
obj = JS_NewUint8ArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
case Scalar::Int16:
|
|
obj = JS_NewInt16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
case Scalar::Uint16:
|
|
obj = JS_NewUint16ArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
case Scalar::Int32:
|
|
obj = JS_NewInt32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
case Scalar::Uint32:
|
|
obj = JS_NewUint32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
case Scalar::Float32:
|
|
obj = JS_NewFloat32ArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
case Scalar::Float64:
|
|
obj = JS_NewFloat64ArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
case Scalar::Uint8Clamped:
|
|
obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems);
|
|
break;
|
|
default:
|
|
MOZ_CRASH("Can't happen: arrayType range checked above");
|
|
}
|
|
|
|
if (!obj)
|
|
return false;
|
|
vp.setObject(*obj);
|
|
|
|
allObjs[placeholderIndex].set(vp);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneReader::readDataView(uint32_t byteLength, MutableHandleValue vp)
|
|
{
|
|
// Push a placeholder onto the allObjs list to stand in for the DataView.
|
|
uint32_t placeholderIndex = allObjs.length();
|
|
Value dummy = UndefinedValue();
|
|
if (!allObjs.append(dummy))
|
|
return false;
|
|
|
|
// Read the ArrayBuffer object and its contents (but no properties).
|
|
RootedValue v(context());
|
|
if (!startRead(&v))
|
|
return false;
|
|
if (!v.isObject() || !v.toObject().is<ArrayBufferObjectMaybeShared>()) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
|
|
"DataView must be backed by an ArrayBuffer");
|
|
return false;
|
|
}
|
|
|
|
// Read byteOffset.
|
|
uint64_t n;
|
|
if (!in.read(&n))
|
|
return false;
|
|
uint32_t byteOffset = n;
|
|
|
|
RootedObject buffer(context(), &v.toObject());
|
|
RootedObject obj(context(), JS_NewDataView(context(), buffer, byteOffset, byteLength));
|
|
if (!obj)
|
|
return false;
|
|
vp.setObject(*obj);
|
|
|
|
allObjs[placeholderIndex].set(vp);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, MutableHandleValue vp)
|
|
{
|
|
JSObject* obj = ArrayBufferObject::create(context(), nbytes);
|
|
if (!obj)
|
|
return false;
|
|
vp.setObject(*obj);
|
|
ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
|
|
MOZ_ASSERT(buffer.byteLength() == nbytes);
|
|
return in.readArray(buffer.dataPointer(), nbytes);
|
|
}
|
|
|
|
/*
|
|
* Read in the data for a structured clone version 1 ArrayBuffer, performing
|
|
* endianness-conversion while reading.
|
|
*/
|
|
bool
|
|
JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems,
|
|
MutableHandleValue vp)
|
|
{
|
|
if (arrayType > Scalar::Uint8Clamped) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA,
|
|
"invalid TypedArray type");
|
|
return false;
|
|
}
|
|
|
|
mozilla::CheckedInt<size_t> nbytes =
|
|
mozilla::CheckedInt<size_t>(nelems) *
|
|
TypedArrayElemSize(static_cast<Scalar::Type>(arrayType));
|
|
if (!nbytes.isValid() || nbytes.value() > UINT32_MAX) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA,
|
|
"invalid typed array size");
|
|
return false;
|
|
}
|
|
|
|
JSObject* obj = ArrayBufferObject::create(context(), nbytes.value());
|
|
if (!obj)
|
|
return false;
|
|
vp.setObject(*obj);
|
|
ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
|
|
MOZ_ASSERT(buffer.byteLength() == nbytes);
|
|
|
|
switch (arrayType) {
|
|
case Scalar::Int8:
|
|
case Scalar::Uint8:
|
|
case Scalar::Uint8Clamped:
|
|
return in.readArray((uint8_t*) buffer.dataPointer(), nelems);
|
|
case Scalar::Int16:
|
|
case Scalar::Uint16:
|
|
return in.readArraySwapped((uint16_t*) buffer.dataPointer(), nelems);
|
|
case Scalar::Int32:
|
|
case Scalar::Uint32:
|
|
return in.readArraySwapped((uint32_t*) buffer.dataPointer(), nelems);
|
|
case Scalar::Float32:
|
|
return in.readArray((uint32_t*) buffer.dataPointer(), nelems);
|
|
case Scalar::Float64:
|
|
return in.readArray((uint64_t*) buffer.dataPointer(), nelems);
|
|
default:
|
|
MOZ_CRASH("Can't happen: arrayType range checked by caller");
|
|
}
|
|
}
|
|
|
|
static bool
|
|
PrimitiveToObject(JSContext* cx, MutableHandleValue vp)
|
|
{
|
|
JSObject* obj = js::PrimitiveToObject(cx, vp);
|
|
if (!obj)
|
|
return false;
|
|
|
|
vp.setObject(*obj);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneReader::startRead(MutableHandleValue vp)
|
|
{
|
|
uint32_t tag, data;
|
|
|
|
if (!in.readPair(&tag, &data))
|
|
return false;
|
|
|
|
switch (tag) {
|
|
case SCTAG_NULL:
|
|
vp.setNull();
|
|
break;
|
|
|
|
case SCTAG_UNDEFINED:
|
|
vp.setUndefined();
|
|
break;
|
|
|
|
case SCTAG_INT32:
|
|
vp.setInt32(data);
|
|
break;
|
|
|
|
case SCTAG_BOOLEAN:
|
|
case SCTAG_BOOLEAN_OBJECT:
|
|
vp.setBoolean(!!data);
|
|
if (tag == SCTAG_BOOLEAN_OBJECT && !PrimitiveToObject(context(), vp))
|
|
return false;
|
|
break;
|
|
|
|
case SCTAG_STRING:
|
|
case SCTAG_STRING_OBJECT: {
|
|
JSString* str = readString(data);
|
|
if (!str)
|
|
return false;
|
|
vp.setString(str);
|
|
if (tag == SCTAG_STRING_OBJECT && !PrimitiveToObject(context(), vp))
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
case SCTAG_NUMBER_OBJECT: {
|
|
double d;
|
|
if (!in.readDouble(&d) || !checkDouble(d))
|
|
return false;
|
|
vp.setDouble(d);
|
|
if (!PrimitiveToObject(context(), vp))
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
case SCTAG_DATE_OBJECT: {
|
|
double d;
|
|
if (!in.readDouble(&d) || !checkDouble(d))
|
|
return false;
|
|
JS::ClippedTime t = JS::TimeClip(d);
|
|
if (!NumbersAreIdentical(d, t.toDouble())) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "date");
|
|
return false;
|
|
}
|
|
JSObject* obj = NewDateObjectMsec(context(), t);
|
|
if (!obj)
|
|
return false;
|
|
vp.setObject(*obj);
|
|
break;
|
|
}
|
|
|
|
case SCTAG_REGEXP_OBJECT: {
|
|
RegExpFlag flags = RegExpFlag(data);
|
|
uint32_t tag2, stringData;
|
|
if (!in.readPair(&tag2, &stringData))
|
|
return false;
|
|
if (tag2 != SCTAG_STRING) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
|
|
return false;
|
|
}
|
|
JSString* str = readString(stringData);
|
|
if (!str)
|
|
return false;
|
|
|
|
RootedAtom atom(context(), AtomizeString(context(), str));
|
|
if (!atom)
|
|
return false;
|
|
|
|
RegExpObject* reobj = RegExpObject::createNoStatics(context(), atom, flags, nullptr,
|
|
context()->tempLifoAlloc());
|
|
if (!reobj)
|
|
return false;
|
|
vp.setObject(*reobj);
|
|
break;
|
|
}
|
|
|
|
case SCTAG_ARRAY_OBJECT:
|
|
case SCTAG_OBJECT_OBJECT: {
|
|
JSObject* obj = (tag == SCTAG_ARRAY_OBJECT)
|
|
? (JSObject*) NewDenseEmptyArray(context())
|
|
: (JSObject*) NewBuiltinClassInstance<PlainObject>(context());
|
|
if (!obj || !objs.append(ObjectValue(*obj)))
|
|
return false;
|
|
vp.setObject(*obj);
|
|
break;
|
|
}
|
|
|
|
case SCTAG_BACK_REFERENCE_OBJECT: {
|
|
if (data >= allObjs.length()) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA,
|
|
"invalid back reference in input");
|
|
return false;
|
|
}
|
|
vp.set(allObjs[data]);
|
|
return true;
|
|
}
|
|
|
|
case SCTAG_TRANSFER_MAP_HEADER:
|
|
case SCTAG_TRANSFER_MAP_PENDING_ENTRY:
|
|
// We should be past all the transfer map tags.
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, NULL,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA,
|
|
"invalid input");
|
|
return false;
|
|
|
|
case SCTAG_ARRAY_BUFFER_OBJECT:
|
|
if (!readArrayBuffer(data, vp))
|
|
return false;
|
|
break;
|
|
|
|
case SCTAG_TYPED_ARRAY_OBJECT: {
|
|
// readTypedArray adds the array to allObjs.
|
|
uint64_t arrayType;
|
|
if (!in.read(&arrayType))
|
|
return false;
|
|
return readTypedArray(arrayType, data, vp);
|
|
}
|
|
|
|
case SCTAG_DATA_VIEW_OBJECT: {
|
|
// readDataView adds the array to allObjs.
|
|
return readDataView(data, vp);
|
|
}
|
|
|
|
case SCTAG_MAP_OBJECT: {
|
|
JSObject* obj = MapObject::create(context());
|
|
if (!obj || !objs.append(ObjectValue(*obj)))
|
|
return false;
|
|
vp.setObject(*obj);
|
|
break;
|
|
}
|
|
|
|
case SCTAG_SET_OBJECT: {
|
|
JSObject* obj = SetObject::create(context());
|
|
if (!obj || !objs.append(ObjectValue(*obj)))
|
|
return false;
|
|
vp.setObject(*obj);
|
|
break;
|
|
}
|
|
|
|
case SCTAG_SAVED_FRAME_OBJECT: {
|
|
auto obj = readSavedFrame(data);
|
|
if (!obj || !objs.append(ObjectValue(*obj)))
|
|
return false;
|
|
vp.setObject(*obj);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
if (tag <= SCTAG_FLOAT_MAX) {
|
|
double d = ReinterpretPairAsDouble(tag, data);
|
|
if (!checkDouble(d))
|
|
return false;
|
|
vp.setNumber(d);
|
|
break;
|
|
}
|
|
|
|
if (SCTAG_TYPED_ARRAY_V1_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
|
|
// A v1-format typed array
|
|
// readTypedArray adds the array to allObjs
|
|
return readTypedArray(TagToV1ArrayType(tag), data, vp, true);
|
|
}
|
|
|
|
if (!callbacks || !callbacks->read) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "unsupported type");
|
|
return false;
|
|
}
|
|
JSObject* obj = callbacks->read(context(), this, tag, data, closure);
|
|
if (!obj)
|
|
return false;
|
|
vp.setObject(*obj);
|
|
}
|
|
}
|
|
|
|
if (vp.isObject() && !allObjs.append(vp))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
JSStructuredCloneReader::readTransferMap()
|
|
{
|
|
JSContext* cx = context();
|
|
uint64_t* headerPos = in.tell();
|
|
|
|
uint32_t tag, data;
|
|
if (!in.getPair(&tag, &data))
|
|
return in.reportTruncated();
|
|
|
|
if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
|
|
return true;
|
|
|
|
uint64_t numTransferables;
|
|
MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
|
|
if (!in.read(&numTransferables))
|
|
return false;
|
|
|
|
for (uint64_t i = 0; i < numTransferables; i++) {
|
|
uint64_t* pos = in.tell();
|
|
|
|
if (!in.readPair(&tag, &data))
|
|
return false;
|
|
|
|
MOZ_ASSERT(tag != SCTAG_TRANSFER_MAP_PENDING_ENTRY);
|
|
RootedObject obj(cx);
|
|
|
|
void* content;
|
|
if (!in.readPtr(&content))
|
|
return false;
|
|
|
|
uint64_t extraData;
|
|
if (!in.read(&extraData))
|
|
return false;
|
|
|
|
if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) {
|
|
size_t nbytes = extraData;
|
|
MOZ_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA ||
|
|
data == JS::SCTAG_TMO_MAPPED_DATA);
|
|
if (data == JS::SCTAG_TMO_ALLOC_DATA)
|
|
obj = JS_NewArrayBufferWithContents(cx, nbytes, content);
|
|
else if (data == JS::SCTAG_TMO_MAPPED_DATA)
|
|
obj = JS_NewMappedArrayBufferWithContents(cx, nbytes, content);
|
|
} else if (tag == SCTAG_TRANSFER_MAP_SHARED_BUFFER) {
|
|
MOZ_ASSERT(data == JS::SCTAG_TMO_SHARED_BUFFER);
|
|
obj = SharedArrayBufferObject::New(context(), (SharedArrayRawBuffer*)content);
|
|
} else {
|
|
if (!callbacks || !callbacks->readTransfer) {
|
|
ReportErrorTransferable(cx, callbacks, JS_SCERR_TRANSFERABLE);
|
|
return false;
|
|
}
|
|
if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure, &obj))
|
|
return false;
|
|
MOZ_ASSERT(obj);
|
|
MOZ_ASSERT(!cx->isExceptionPending());
|
|
}
|
|
|
|
// On failure, the buffer will still own the data (since its ownership
|
|
// will not get set to SCTAG_TMO_UNOWNED), so the data will be freed by
|
|
// DiscardTransferables.
|
|
if (!obj)
|
|
return false;
|
|
|
|
// Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input
|
|
// buffer.
|
|
*pos = PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED);
|
|
MOZ_ASSERT(headerPos < pos && pos < in.end());
|
|
|
|
if (!allObjs.append(ObjectValue(*obj)))
|
|
return false;
|
|
}
|
|
|
|
// Mark the whole transfer map as consumed.
|
|
MOZ_ASSERT(headerPos <= in.tell());
|
|
#ifdef DEBUG
|
|
SCInput::getPair(headerPos, &tag, &data);
|
|
MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER);
|
|
MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED);
|
|
#endif
|
|
*headerPos = PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED);
|
|
|
|
return true;
|
|
}
|
|
|
|
JSObject*
|
|
JSStructuredCloneReader::readSavedFrame(uint32_t principalsTag)
|
|
{
|
|
RootedSavedFrame savedFrame(context(), SavedFrame::create(context()));
|
|
if (!savedFrame)
|
|
return nullptr;
|
|
|
|
JSPrincipals* principals;
|
|
if (principalsTag == SCTAG_JSPRINCIPALS) {
|
|
if (!context()->runtime()->readPrincipals) {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_UNSUPPORTED_TYPE);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!context()->runtime()->readPrincipals(context(), this, &principals))
|
|
return nullptr;
|
|
} else if (principalsTag == SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM) {
|
|
principals = &ReconstructedSavedFramePrincipals::IsSystem;
|
|
principals->refcount++;
|
|
} else if (principalsTag == SCTAG_RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM) {
|
|
principals = &ReconstructedSavedFramePrincipals::IsNotSystem;
|
|
principals->refcount++;
|
|
} else if (principalsTag == SCTAG_NULL_JSPRINCIPALS) {
|
|
principals = nullptr;
|
|
} else {
|
|
JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "bad SavedFrame principals");
|
|
return nullptr;
|
|
}
|
|
savedFrame->initPrincipalsAlreadyHeld(principals);
|
|
|
|
RootedValue source(context());
|
|
if (!startRead(&source) || !source.isString())
|
|
return nullptr;
|
|
auto atomSource = AtomizeString(context(), source.toString());
|
|
if (!atomSource)
|
|
return nullptr;
|
|
savedFrame->initSource(atomSource);
|
|
|
|
RootedValue lineVal(context());
|
|
uint32_t line;
|
|
if (!startRead(&lineVal) || !lineVal.isNumber() || !ToUint32(context(), lineVal, &line))
|
|
return nullptr;
|
|
savedFrame->initLine(line);
|
|
|
|
RootedValue columnVal(context());
|
|
uint32_t column;
|
|
if (!startRead(&columnVal) || !columnVal.isNumber() || !ToUint32(context(), columnVal, &column))
|
|
return nullptr;
|
|
savedFrame->initColumn(column);
|
|
|
|
RootedValue name(context());
|
|
if (!startRead(&name) || !(name.isString() || name.isNull()))
|
|
return nullptr;
|
|
JSAtom* atomName = nullptr;
|
|
if (name.isString()) {
|
|
atomName = AtomizeString(context(), name.toString());
|
|
if (!atomName)
|
|
return nullptr;
|
|
}
|
|
savedFrame->initFunctionDisplayName(atomName);
|
|
|
|
RootedValue cause(context());
|
|
if (!startRead(&cause) || !(cause.isString() || cause.isNull()))
|
|
return nullptr;
|
|
JSAtom* atomCause = nullptr;
|
|
if (cause.isString()) {
|
|
atomCause = AtomizeString(context(), cause.toString());
|
|
if (!atomCause)
|
|
return nullptr;
|
|
}
|
|
savedFrame->initAsyncCause(atomCause);
|
|
|
|
return savedFrame;
|
|
}
|
|
|
|
// Perform the whole recursive reading procedure.
|
|
bool
|
|
JSStructuredCloneReader::read(MutableHandleValue vp)
|
|
{
|
|
if (!readTransferMap())
|
|
return false;
|
|
|
|
// Start out by reading in the main object and pushing it onto the 'objs'
|
|
// stack. The data related to this object and its descendants extends from
|
|
// here to the SCTAG_END_OF_KEYS at the end of the stream.
|
|
if (!startRead(vp))
|
|
return false;
|
|
|
|
// Stop when the stack shows that all objects have been read.
|
|
while (objs.length() != 0) {
|
|
// What happens depends on the top obj on the objs stack.
|
|
RootedObject obj(context(), &objs.back().toObject());
|
|
|
|
uint32_t tag, data;
|
|
if (!in.getPair(&tag, &data))
|
|
return false;
|
|
|
|
if (tag == SCTAG_END_OF_KEYS) {
|
|
// Pop the current obj off the stack, since we are done with it and
|
|
// its children.
|
|
MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
|
|
objs.popBack();
|
|
continue;
|
|
}
|
|
|
|
// The input stream contains a sequence of "child" values, whose
|
|
// interpretation depends on the type of obj. These values can be
|
|
// anything, and startRead() will push onto 'objs' for any non-leaf
|
|
// value (i.e., anything that may contain children).
|
|
//
|
|
// startRead() will allocate the (empty) object, but note that when
|
|
// startRead() returns, 'key' is not yet initialized with any of its
|
|
// properties. Those will be filled in by returning to the head of this
|
|
// loop, processing the first child obj, and continuing until all
|
|
// children have been fully created.
|
|
//
|
|
// Note that this means the ordering in the stream is a little funky
|
|
// for things like Map. See the comment above startWrite() for an
|
|
// example.
|
|
RootedValue key(context());
|
|
if (!startRead(&key))
|
|
return false;
|
|
|
|
if (key.isNull() &&
|
|
!(obj->is<MapObject>() || obj->is<SetObject>() || obj->is<SavedFrame>()))
|
|
{
|
|
// Backwards compatibility: Null formerly indicated the end of
|
|
// object properties.
|
|
objs.popBack();
|
|
continue;
|
|
}
|
|
|
|
// Set object: the values between obj header (from startRead()) and
|
|
// SCTAG_END_OF_KEYS are all interpreted as values to add to the set.
|
|
if (obj->is<SetObject>()) {
|
|
if (!SetObject::add(context(), obj, key))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
// SavedFrame object: there is one following value, the parent
|
|
// SavedFrame, which is either null or another SavedFrame object.
|
|
if (obj->is<SavedFrame>()) {
|
|
SavedFrame* parentFrame;
|
|
if (key.isNull())
|
|
parentFrame = nullptr;
|
|
else if (key.isObject() && key.toObject().is<SavedFrame>())
|
|
parentFrame = &key.toObject().as<SavedFrame>();
|
|
else
|
|
return false;
|
|
|
|
obj->as<SavedFrame>().initParent(parentFrame);
|
|
continue;
|
|
}
|
|
|
|
// Everything else uses a series of key,value,key,value,... Value
|
|
// objects.
|
|
RootedValue val(context());
|
|
if (!startRead(&val))
|
|
return false;
|
|
|
|
if (obj->is<MapObject>()) {
|
|
// For a Map, store those <key,value> pairs in the contained map
|
|
// data structure.
|
|
if (!MapObject::set(context(), obj, key, val))
|
|
return false;
|
|
} else {
|
|
// For any other Object, interpret them as plain properties.
|
|
RootedId id(context());
|
|
if (!ValueToId<CanGC>(context(), key, &id))
|
|
return false;
|
|
|
|
if (!DefineProperty(context(), obj, id, val))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
allObjs.clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
using namespace js;
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_ReadStructuredClone(JSContext* cx, uint64_t* buf, size_t nbytes,
|
|
uint32_t version, MutableHandleValue vp,
|
|
const JSStructuredCloneCallbacks* optionalCallbacks,
|
|
void* closure)
|
|
{
|
|
AssertHeapIsIdle(cx);
|
|
CHECK_REQUEST(cx);
|
|
|
|
if (version > JS_STRUCTURED_CLONE_VERSION) {
|
|
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SC_BAD_CLONE_VERSION);
|
|
return false;
|
|
}
|
|
const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
|
|
return ReadStructuredClone(cx, buf, nbytes, vp, callbacks, closure);
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_WriteStructuredClone(JSContext* cx, HandleValue value, uint64_t** bufp, size_t* nbytesp,
|
|
const JSStructuredCloneCallbacks* optionalCallbacks,
|
|
void* closure, HandleValue transferable)
|
|
{
|
|
AssertHeapIsIdle(cx);
|
|
CHECK_REQUEST(cx);
|
|
assertSameCompartment(cx, value);
|
|
|
|
const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
|
|
return WriteStructuredClone(cx, value, bufp, nbytesp, callbacks, closure, transferable);
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_ClearStructuredClone(uint64_t* data, size_t nbytes,
|
|
const JSStructuredCloneCallbacks* optionalCallbacks,
|
|
void* closure, bool freeData)
|
|
{
|
|
DiscardTransferables(data, nbytes, optionalCallbacks, closure);
|
|
if (freeData) {
|
|
js_free(data);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_StructuredCloneHasTransferables(const uint64_t* data, size_t nbytes,
|
|
bool* hasTransferable)
|
|
{
|
|
*hasTransferable = StructuredCloneHasTransferObjects(data, nbytes);
|
|
return true;
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_StructuredClone(JSContext* cx, HandleValue value, MutableHandleValue vp,
|
|
const JSStructuredCloneCallbacks* optionalCallbacks,
|
|
void* closure)
|
|
{
|
|
AssertHeapIsIdle(cx);
|
|
CHECK_REQUEST(cx);
|
|
|
|
// Strings are associated with zones, not compartments,
|
|
// so we copy the string by wrapping it.
|
|
if (value.isString()) {
|
|
RootedString strValue(cx, value.toString());
|
|
if (!cx->compartment()->wrap(cx, &strValue)) {
|
|
return false;
|
|
}
|
|
vp.setString(strValue);
|
|
return true;
|
|
}
|
|
|
|
const JSStructuredCloneCallbacks* callbacks = optionalCallbacks;
|
|
|
|
JSAutoStructuredCloneBuffer buf;
|
|
{
|
|
// If we use Maybe<AutoCompartment> here, G++ can't tell that the
|
|
// destructor is only called when Maybe::construct was called, and
|
|
// we get warnings about using uninitialized variables.
|
|
if (value.isObject()) {
|
|
AutoCompartment ac(cx, &value.toObject());
|
|
if (!buf.write(cx, value, callbacks, closure))
|
|
return false;
|
|
} else {
|
|
if (!buf.write(cx, value, callbacks, closure))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return buf.read(cx, vp, callbacks, closure);
|
|
}
|
|
|
|
JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer&& other)
|
|
{
|
|
ownTransferables_ = other.ownTransferables_;
|
|
other.steal(&data_, &nbytes_, &version_, &callbacks_, &closure_);
|
|
}
|
|
|
|
JSAutoStructuredCloneBuffer&
|
|
JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer&& other)
|
|
{
|
|
MOZ_ASSERT(&other != this);
|
|
clear();
|
|
ownTransferables_ = other.ownTransferables_;
|
|
other.steal(&data_, &nbytes_, &version_, &callbacks_, &closure_);
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
JSAutoStructuredCloneBuffer::clear(const JSStructuredCloneCallbacks* optionalCallbacks,
|
|
void* optionalClosure)
|
|
{
|
|
if (!data_)
|
|
return;
|
|
|
|
const JSStructuredCloneCallbacks* callbacks =
|
|
optionalCallbacks ? optionalCallbacks : callbacks_;
|
|
void* closure = optionalClosure ? optionalClosure : closure_;
|
|
|
|
if (ownTransferables_ == OwnsTransferablesIfAny)
|
|
DiscardTransferables(data_, nbytes_, callbacks, closure);
|
|
ownTransferables_ = NoTransferables;
|
|
js_free(data_);
|
|
data_ = nullptr;
|
|
nbytes_ = 0;
|
|
version_ = 0;
|
|
}
|
|
|
|
bool
|
|
JSAutoStructuredCloneBuffer::copy(const uint64_t* srcData, size_t nbytes, uint32_t version,
|
|
const JSStructuredCloneCallbacks* callbacks,
|
|
void* closure)
|
|
{
|
|
// transferable objects cannot be copied
|
|
if (StructuredCloneHasTransferObjects(data_, nbytes_))
|
|
return false;
|
|
|
|
uint64_t* newData = static_cast<uint64_t*>(js_malloc(nbytes));
|
|
if (!newData)
|
|
return false;
|
|
|
|
js_memcpy(newData, srcData, nbytes);
|
|
|
|
clear();
|
|
data_ = newData;
|
|
nbytes_ = nbytes;
|
|
version_ = version;
|
|
callbacks_ = callbacks;
|
|
closure_ = closure;
|
|
ownTransferables_ = NoTransferables;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
JSAutoStructuredCloneBuffer::adopt(uint64_t* data, size_t nbytes, uint32_t version,
|
|
const JSStructuredCloneCallbacks* callbacks,
|
|
void* closure)
|
|
{
|
|
clear();
|
|
data_ = data;
|
|
nbytes_ = nbytes;
|
|
version_ = version;
|
|
callbacks_ = callbacks;
|
|
closure_ = closure;
|
|
ownTransferables_ = OwnsTransferablesIfAny;
|
|
}
|
|
|
|
void
|
|
JSAutoStructuredCloneBuffer::steal(uint64_t** datap, size_t* nbytesp, uint32_t* versionp,
|
|
const JSStructuredCloneCallbacks** callbacks,
|
|
void** closure)
|
|
{
|
|
*datap = data_;
|
|
*nbytesp = nbytes_;
|
|
if (versionp)
|
|
*versionp = version_;
|
|
if (callbacks)
|
|
*callbacks = callbacks_;
|
|
if (closure)
|
|
*closure = closure_;
|
|
|
|
data_ = nullptr;
|
|
nbytes_ = 0;
|
|
version_ = 0;
|
|
callbacks_ = 0;
|
|
closure_ = 0;
|
|
ownTransferables_ = NoTransferables;
|
|
}
|
|
|
|
bool
|
|
JSAutoStructuredCloneBuffer::read(JSContext* cx, MutableHandleValue vp,
|
|
const JSStructuredCloneCallbacks* optionalCallbacks,
|
|
void* closure)
|
|
{
|
|
MOZ_ASSERT(cx);
|
|
MOZ_ASSERT(data_);
|
|
return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp,
|
|
optionalCallbacks, closure);
|
|
}
|
|
|
|
bool
|
|
JSAutoStructuredCloneBuffer::write(JSContext* cx, HandleValue value,
|
|
const JSStructuredCloneCallbacks* optionalCallbacks,
|
|
void* closure)
|
|
{
|
|
HandleValue transferable = UndefinedHandleValue;
|
|
return write(cx, value, transferable, optionalCallbacks, closure);
|
|
}
|
|
|
|
bool
|
|
JSAutoStructuredCloneBuffer::write(JSContext* cx, HandleValue value,
|
|
HandleValue transferable,
|
|
const JSStructuredCloneCallbacks* optionalCallbacks,
|
|
void* closure)
|
|
{
|
|
clear();
|
|
bool ok = JS_WriteStructuredClone(cx, value, &data_, &nbytes_,
|
|
optionalCallbacks, closure,
|
|
transferable);
|
|
|
|
if (ok) {
|
|
ownTransferables_ = OwnsTransferablesIfAny;
|
|
} else {
|
|
data_ = nullptr;
|
|
nbytes_ = 0;
|
|
version_ = JS_STRUCTURED_CLONE_VERSION;
|
|
ownTransferables_ = NoTransferables;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_ReadUint32Pair(JSStructuredCloneReader* r, uint32_t* p1, uint32_t* p2)
|
|
{
|
|
return r->input().readPair((uint32_t*) p1, (uint32_t*) p2);
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_ReadBytes(JSStructuredCloneReader* r, void* p, size_t len)
|
|
{
|
|
return r->input().readBytes(p, len);
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_ReadTypedArray(JSStructuredCloneReader* r, MutableHandleValue vp)
|
|
{
|
|
uint32_t tag, nelems;
|
|
if (!r->input().readPair(&tag, &nelems))
|
|
return false;
|
|
if (tag >= SCTAG_TYPED_ARRAY_V1_MIN && tag <= SCTAG_TYPED_ARRAY_V1_MAX) {
|
|
return r->readTypedArray(TagToV1ArrayType(tag), nelems, vp, true);
|
|
} else if (tag == SCTAG_TYPED_ARRAY_OBJECT) {
|
|
uint64_t arrayType;
|
|
if (!r->input().read(&arrayType))
|
|
return false;
|
|
return r->readTypedArray(arrayType, nelems, vp);
|
|
} else {
|
|
JS_ReportErrorNumber(r->context(), GetErrorMessage, nullptr,
|
|
JSMSG_SC_BAD_SERIALIZED_DATA, "expected type array");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_WriteUint32Pair(JSStructuredCloneWriter* w, uint32_t tag, uint32_t data)
|
|
{
|
|
return w->output().writePair(tag, data);
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_WriteBytes(JSStructuredCloneWriter* w, const void* p, size_t len)
|
|
{
|
|
return w->output().writeBytes(p, len);
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_WriteString(JSStructuredCloneWriter* w, HandleString str)
|
|
{
|
|
return w->writeString(SCTAG_STRING, str);
|
|
}
|
|
|
|
JS_PUBLIC_API(bool)
|
|
JS_WriteTypedArray(JSStructuredCloneWriter* w, HandleValue v)
|
|
{
|
|
MOZ_ASSERT(v.isObject());
|
|
assertSameCompartment(w->context(), v);
|
|
RootedObject obj(w->context(), &v.toObject());
|
|
return w->writeTypedArray(obj);
|
|
}
|