Bug 595297 - Portable serialize/deserialize implementation of the HTML5 structured cloning algorithm (jsclone.cpp). r=gal.

This commit is contained in:
Jason Orendorff 2010-09-30 19:47:10 -05:00
parent b4b683166b
commit 00704fa52d
21 changed files with 1685 additions and 8 deletions

View File

@ -121,6 +121,7 @@ CPPSRCS = \
jsarray.cpp \ jsarray.cpp \
jsatom.cpp \ jsatom.cpp \
jsbool.cpp \ jsbool.cpp \
jsclone.cpp \
jscntxt.cpp \ jscntxt.cpp \
jsdate.cpp \ jsdate.cpp \
jsdbgapi.cpp \ jsdbgapi.cpp \
@ -177,6 +178,7 @@ INSTALLED_HEADERS = \
jsbit.h \ jsbit.h \
jsbool.h \ jsbool.h \
jsclist.h \ jsclist.h \
jsclone.h \
jscntxt.h \ jscntxt.h \
jscompat.h \ jscompat.h \
jsdate.h \ jsdate.h \

View File

@ -339,3 +339,6 @@ MSG_DEF(JSMSG_CALLER_IS_STRICT, 256, 0, JSEXN_TYPEERR, "access to strict m
MSG_DEF(JSMSG_NEED_DEBUG_MODE, 257, 0, JSEXN_ERR, "function can be called only in debug mode") MSG_DEF(JSMSG_NEED_DEBUG_MODE, 257, 0, JSEXN_ERR, "function can be called only in debug mode")
MSG_DEF(JSMSG_STRICT_CODE_LET_EXPR_STMT, 258, 0, JSEXN_ERR, "strict mode code may not contain unparenthesized let expression statements") MSG_DEF(JSMSG_STRICT_CODE_LET_EXPR_STMT, 258, 0, JSEXN_ERR, "strict mode code may not contain unparenthesized let expression statements")
MSG_DEF(JSMSG_CANT_CHANGE_EXTENSIBILITY, 259, 0, JSEXN_TYPEERR, "can't change object's extensibility") MSG_DEF(JSMSG_CANT_CHANGE_EXTENSIBILITY, 259, 0, JSEXN_TYPEERR, "can't change object's extensibility")
MSG_DEF(JSMSG_SC_BAD_SERIALIZED_DATA, 260, 1, JSEXN_INTERNALERR, "bad serialized structured data ({0})")
MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE, 261, 0, JSEXN_TYPEERR, "unsupported type for structured data")
MSG_DEF(JSMSG_SC_RECURSION, 262, 0, JSEXN_INTERNALERR, "recursive object")

View File

@ -57,6 +57,7 @@
#include "jsatom.h" #include "jsatom.h"
#include "jsbool.h" #include "jsbool.h"
#include "jsbuiltins.h" #include "jsbuiltins.h"
#include "jsclone.h"
#include "jscntxt.h" #include "jscntxt.h"
#include "jsversion.h" #include "jsversion.h"
#include "jsdate.h" #include "jsdate.h"
@ -5245,6 +5246,55 @@ JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver)
return js_FinishJSONParse(cx, jp, Valueify(reviver)); return js_FinishJSONParse(cx, jp, Valueify(reviver));
} }
JS_PUBLIC_API(JSBool)
JS_ReadStructuredClone(JSContext *cx, const uint64 *buf, size_t nbytes, jsval *vp)
{
return ReadStructuredClone(cx, buf, nbytes, Valueify(vp));
}
JS_PUBLIC_API(JSBool)
JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **bufp, size_t *nbytesp)
{
return WriteStructuredClone(cx, Valueify(v), (uint64_t **) bufp, nbytesp);
}
JS_PUBLIC_API(JSBool)
JS_StructuredClone(JSContext *cx, jsval v, jsval *vp)
{
JSAutoStructuredCloneBuffer buf(cx);
return buf.write(v) && buf.read(vp);
}
JS_PUBLIC_API(void)
JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks)
{
rt->structuredCloneCallbacks = callbacks;
}
JS_PUBLIC_API(JSBool)
JS_ReadPair(JSStructuredCloneReader *r, uint32 *p1, uint32 *p2)
{
return r->input().readPair((uint32_t *) p1, (uint32_t *) p2);
}
JS_PUBLIC_API(JSBool)
JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len)
{
return r->input().readBytes(p, len);
}
JS_PUBLIC_API(JSBool)
JS_WritePair(JSStructuredCloneWriter *w, uint32 tag, uint32 data)
{
return w->output().writePair(tag, data);
}
JS_PUBLIC_API(JSBool)
JS_WriteBytes(JSStructuredCloneWriter *w, const void *p, size_t len)
{
return w->output().writeBytes(p, len);
}
/* /*
* The following determines whether C Strings are to be treated as UTF-8 * The following determines whether C Strings are to be treated as UTF-8
* or ISO-8859-1. For correct operation, it must be set prior to the * or ISO-8859-1. For correct operation, it must be set prior to the

View File

@ -2844,6 +2844,112 @@ JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver);
/************************************************************************/ /************************************************************************/
/* API for the HTML5 internal structured cloning algorithm. */
/* The maximum supported structured-clone serialization format version. */
#define JS_STRUCTURED_CLONE_VERSION 1
JS_PUBLIC_API(JSBool)
JS_ReadStructuredClone(JSContext *cx, const uint64 *data, size_t nbytes, jsval *vp);
/* Note: On success, the caller is responsible for calling js_free(*datap). */
JS_PUBLIC_API(JSBool)
JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **datap, size_t *nbytesp);
JS_PUBLIC_API(JSBool)
JS_StructuredClone(JSContext *cx, jsval v, jsval *vp);
#ifdef __cplusplus
/* RAII sugar for JS_WriteStructuredClone. */
class JSAutoStructuredCloneBuffer {
JSContext *cx;
uint64 *data_;
size_t nbytes_;
public:
explicit JSAutoStructuredCloneBuffer(JSContext *cx) : cx(cx), data_(NULL), nbytes_(0) {}
~JSAutoStructuredCloneBuffer() { clear(); }
uint64 *data() const { return data_; }
size_t nbytes() const { return nbytes_; }
void clear() {
if (data_) {
JS_free(cx, data_);
data_ = NULL;
nbytes_ = 0;
}
}
/*
* Adopt some memory. It will be automatically freed by the destructor.
* data must have been allocated using JS_malloc.
*/
void adopt(uint64 *data, size_t nbytes) {
clear();
data_ = data;
nbytes_ = nbytes;
}
/*
* Remove the buffer so that it will not be automatically freed.
* After this, the caller is responsible for calling JS_free(*datap).
*/
void steal(uint64 **datap, size_t *nbytesp) {
*datap = data_;
*nbytesp = nbytes_;
data_ = NULL;
nbytes_ = 0;
}
bool read(jsval *vp) const {
JS_ASSERT(data_);
return !!JS_ReadStructuredClone(cx, data_, nbytes_, vp);
}
bool write(jsval v) {
clear();
bool ok = !!JS_WriteStructuredClone(cx, v, &data_, &nbytes_);
if (!ok) {
data_ = NULL;
nbytes_ = 0;
}
return ok;
}
};
#endif
/* API for implementing custom serialization behavior (for ImageData, File, etc.) */
/* The range of tag values the application may use for its own custom object types. */
#define JS_SCTAG_USER_MIN ((uint32) 0xFFFF8000)
#define JS_SCTAG_USER_MAX ((uint32) 0xFFFFFFFF)
#define JS_SCERR_RECURSION 0
struct JSStructuredCloneCallbacks {
ReadStructuredCloneOp read;
WriteStructuredCloneOp write;
StructuredCloneErrorOp reportError;
};
JS_PUBLIC_API(void)
JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks);
JS_PUBLIC_API(JSBool)
JS_ReadPair(JSStructuredCloneReader *r, uint32 *p1, uint32 *p2);
JS_PUBLIC_API(JSBool)
JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len);
JS_PUBLIC_API(JSBool)
JS_WritePair(JSStructuredCloneWriter *w, uint32 tag, uint32 data);
JS_PUBLIC_API(JSBool)
JS_WriteBytes(JSStructuredCloneWriter *w, const void *p, size_t len);
/************************************************************************/
/* /*
* Locale specific string conversion and error message callbacks. * Locale specific string conversion and error message callbacks.
*/ */

815
js/src/jsclone.cpp Normal file
View File

@ -0,0 +1,815 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is JavaScript structured data serialization.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jason Orendorff <jorendorff@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "jsclone.h"
#include "jsdate.h"
#include "jsregexp.h"
#include "jstypedarray.h"
#include "jsregexpinlines.h"
using namespace js;
namespace js
{
bool
WriteStructuredClone(JSContext *cx, const Value &v, uint64 **bufp, size_t *nbytesp)
{
SCOutput out(cx);
JSStructuredCloneWriter w(out);
return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
}
bool
ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp)
{
SCInput in(cx, data, nbytes);
JSStructuredCloneReader r(in);
return r.read(vp);
}
}
enum StructuredDataType {
/* Structured data types provided by the engine */
SCTAG_FLOAT_MAX = 0xFFF00000,
SCTAG_NULL = 0xFFFF0000,
SCTAG_UNDEFINED,
SCTAG_BOOLEAN,
SCTAG_INDEX,
SCTAG_STRING,
SCTAG_DATE_OBJECT,
SCTAG_REGEXP_OBJECT,
SCTAG_ARRAY_OBJECT,
SCTAG_OBJECT_OBJECT,
SCTAG_ARRAY_BUFFER_OBJECT,
SCTAG_TYPED_ARRAY_MIN = 0xFFFF0100,
SCTAG_TYPED_ARRAY_MAX = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_MAX - 1,
SCTAG_END_OF_BUILTIN_TYPES
};
JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX);
static uint8_t
SwapBytes(uint8_t u)
{
return u;
}
static uint16_t
SwapBytes(uint16_t u)
{
#ifdef IS_BIG_ENDIAN
return ((u & 0x00ff) << 8) | ((u & 0xff00) >> 8);
#else
return u;
#endif
}
static uint32_t
SwapBytes(uint32_t u)
{
#ifdef IS_BIG_ENDIAN
return ((u & 0x000000ffU) << 24) |
((u & 0x0000ff00U) << 8) |
((u & 0x00ff0000U) >> 8) |
((u & 0xff000000U) >> 24);
#else
return u;
#endif
}
static uint64_t
SwapBytes(uint64_t u)
{
#ifdef IS_BIG_ENDIAN
return ((u & 0x00000000000000ffLLU) << 56) |
((u & 0x000000000000ff00LLU) << 40) |
((u & 0x0000000000ff0000LLU) << 24) |
((u & 0x00000000ff000000LLU) << 8) |
((u & 0x000000ff00000000LLU) >> 8) |
((u & 0x0000ff0000000000LLU) >> 24) |
((u & 0x00ff000000000000LLU) >> 40) |
((u & 0xff00000000000000LLU) >> 56);
#else
return u;
#endif
}
bool
SCInput::eof()
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
return false;
}
SCInput::SCInput(JSContext *cx, const uint64_t *data, size_t nbytes)
: cx(cx), point(data), end(data + nbytes / 8)
{
JS_ASSERT((uintptr_t(data) & 7) == 0);
JS_ASSERT((nbytes & 7) == 0);
}
bool
SCInput::read(uint64_t *p)
{
if (point == end)
return eof();
*p = SwapBytes(*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::readDouble(jsdouble *p)
{
union {
uint64_t u;
jsdouble d;
} pun;
if (!read(&pun.u))
return false;
*p = JS_CANONICALIZE_NAN(pun.d);
return true;
}
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(end - point))
return eof();
if (sizeof(T) == 1) {
memcpy(p, point, nelems);
} else {
const T *q = (const T *) point;
const T *qend = q + nelems;
while (q != qend)
*p++ = SwapBytes(*q++);
}
point += nwords;
return true;
}
bool
SCInput::readBytes(void *p, size_t nbytes)
{
return readArray((uint8_t *) p, nbytes);
}
bool
SCInput::readChars(jschar *p, size_t nchars)
{
JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
return readArray((uint16_t *) p, nchars);
}
SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
bool
SCOutput::write(uint64_t u)
{
return buf.append(SwapBytes(u));
}
static inline uint64_t
PairToUInt64(uint32_t tag, uint32_t data)
{
return uint64_t(data) | (uint64_t(tag) << 32);
}
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 jsdoubles on the
* little-endian platforms we care most about.
*
* For example, JSVAL_TRUE 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 uint64_t
ReinterpretDoubleAsUInt64(jsdouble d)
{
union {
jsdouble d;
uint64_t u;
} pun;
pun.d = d;
return pun.u;
}
static inline jsdouble
ReinterpretUInt64AsDouble(uint64_t u)
{
union {
uint64_t u;
jsdouble d;
} pun;
pun.u = u;
return pun.d;
}
static inline jsdouble
ReinterpretPairAsDouble(uint32_t tag, uint32_t data)
{
return ReinterpretUInt64AsDouble(PairToUInt64(tag, data));
}
static inline bool
IsNonCanonicalizedNaN(jsdouble d)
{
return ReinterpretDoubleAsUInt64(d) != ReinterpretDoubleAsUInt64(JS_CANONICALIZE_NAN(d));
}
bool
SCOutput::writeDouble(jsdouble d)
{
JS_ASSERT(!IsNonCanonicalizedNaN(d));
return write(ReinterpretDoubleAsUInt64(d));
}
template <class T>
bool
SCOutput::writeArray(const T *p, size_t nelems)
{
JS_ASSERT(8 % sizeof(T) == 0);
JS_ASSERT(sizeof(uint64_t) % sizeof(T) == 0);
if (nelems == 0)
return true;
if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems) {
js_ReportAllocationOverflow(context());
return false;
}
uint64_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];
if (sizeof(T) == 1) {
memcpy(q, p, nelems);
} else {
const T *pend = p + nelems;
while (p != pend)
*q++ = SwapBytes(*p++);
}
return true;
}
bool
SCOutput::writeBytes(const void *p, size_t nbytes)
{
return writeArray((const uint8_t *) p, nbytes);
}
bool
SCOutput::writeChars(const jschar *p, size_t nchars)
{
JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
return writeArray((const uint16_t *) p, nchars);
}
bool
SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
{
*sizep = buf.length() * sizeof(uint64_t);
return (*datap = buf.extractRawBuffer()) != NULL;
}
JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
bool
JSStructuredCloneWriter::writeString(JSString *str)
{
const jschar *chars;
size_t length;
str->getCharsAndLength(chars, length);
return out.writePair(SCTAG_STRING, uint32_t(length)) && out.writeChars(chars, length);
}
bool
JSStructuredCloneWriter::writeId(jsid id)
{
if (JSID_IS_INT(id))
return out.writePair(SCTAG_INDEX, uint32_t(JSID_TO_INT(id)));
JS_ASSERT(JSID_IS_STRING(id));
return writeString(JSID_TO_STRING(id));
}
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 = JS_MIN(counts.length(), MAX);
JS_ASSERT(objs.length() == counts.length());
size_t total = 0;
for (size_t i = 0; i < limit; i++) {
JS_ASSERT(total + counts[i] >= total);
total += counts[i];
}
if (counts.length() <= MAX)
JS_ASSERT(total == ids.length());
else
JS_ASSERT(total <= ids.length());
JS_ASSERT(memory.count() == objs.length());
size_t j = objs.length();
for (size_t i = 0; i < limit; i++)
JS_ASSERT(memory.has(&objs[--j].toObject()));
#endif
}
static inline uint32_t
ArrayTypeToTag(uint32_t type)
{
/*
* As long as these are all true, we can just add. Note that for backward
* compatibility, the tags cannot change. So if the ArrayType type codes
* change, this function and TagToArrayType will have to do more work.
*/
JS_STATIC_ASSERT(TypedArray::TYPE_INT8 == 0);
JS_STATIC_ASSERT(TypedArray::TYPE_UINT8 == 1);
JS_STATIC_ASSERT(TypedArray::TYPE_INT16 == 2);
JS_STATIC_ASSERT(TypedArray::TYPE_UINT16 == 3);
JS_STATIC_ASSERT(TypedArray::TYPE_INT32 == 4);
JS_STATIC_ASSERT(TypedArray::TYPE_UINT32 == 5);
JS_STATIC_ASSERT(TypedArray::TYPE_FLOAT32 == 6);
JS_STATIC_ASSERT(TypedArray::TYPE_FLOAT64 == 7);
JS_STATIC_ASSERT(TypedArray::TYPE_UINT8_CLAMPED == 8);
JS_STATIC_ASSERT(TypedArray::TYPE_MAX == TypedArray::TYPE_UINT8_CLAMPED + 1);
JS_ASSERT(type < TypedArray::TYPE_MAX);
return SCTAG_TYPED_ARRAY_MIN + type;
}
static inline uint32_t
TagToArrayType(uint32_t tag)
{
JS_ASSERT(SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX);
return tag - SCTAG_TYPED_ARRAY_MIN;
}
bool
JSStructuredCloneWriter::writeTypedArray(JSObject *obj)
{
TypedArray *arr = TypedArray::fromJSObject(obj);
if (!out.writePair(ArrayTypeToTag(arr->type), arr->length))
return false;
switch (arr->type) {
case TypedArray::TYPE_INT8:
case TypedArray::TYPE_UINT8:
case TypedArray::TYPE_UINT8_CLAMPED:
return out.writeArray((const uint8_t *) arr->data, arr->length);
case TypedArray::TYPE_INT16:
case TypedArray::TYPE_UINT16:
return out.writeArray((const uint16_t *) arr->data, arr->length);
case TypedArray::TYPE_INT32:
case TypedArray::TYPE_UINT32:
case TypedArray::TYPE_FLOAT32:
return out.writeArray((const uint32_t *) arr->data, arr->length);
case TypedArray::TYPE_FLOAT64:
return out.writeArray((const uint64_t *) arr->data, arr->length);
default:
JS_NOT_REACHED("unknown TypedArray type");
return false;
}
}
bool
JSStructuredCloneWriter::writeArrayBuffer(JSObject *obj)
{
ArrayBuffer *abuf = ArrayBuffer::fromJSObject(obj);
return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, abuf->byteLength) &&
out.writeBytes(abuf->data, abuf->byteLength);
}
bool
JSStructuredCloneWriter::startObject(JSObject *obj)
{
JS_ASSERT(obj->isArray() || obj->isObject());
/* Fail if obj is already on the stack. */
HashSet<JSObject *>::AddPtr p = memory.lookupForAdd(obj);
if (p) {
JSContext *cx = context();
const JSStructuredCloneCallbacks *cb = cx->runtime->structuredCloneCallbacks;
if (cb && cb->reportError)
cb->reportError(cx, JS_SCERR_RECURSION);
else
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_RECURSION);
return false;
}
if (!memory.add(p, obj))
return false;
/*
* Get enumerable property ids and put them in reverse order so that they
* will come off the stack in forward order.
*/
size_t initialLength = ids.length();
if (!GetPropertyNames(context(), obj, JSITER_OWNONLY, &ids))
return false;
jsid *begin = ids.begin() + initialLength, *end = ids.end();
size_t count = size_t(end - begin);
Reverse(begin, end);
/* Push obj and count to the stack. */
if (!objs.append(ObjectValue(*obj)) || !counts.append(count))
return false;
checkStack();
/* Write the header for obj. */
return out.writePair(obj->isArray() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
}
bool
JSStructuredCloneWriter::startWrite(const js::Value &v)
{
if (v.isString()) {
return writeString(v.toString());
} else if (v.isNumber()) {
return out.writeDouble(v.toNumber());
} 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()) {
JSObject *obj = &v.toObject();
if (obj->isRegExp()) {
RegExp *re = RegExp::extractFrom(obj);
return out.writePair(SCTAG_REGEXP_OBJECT, re->getFlags()) &&
writeString(re->getSource());
} else if (obj->isDate()) {
jsdouble d = js_DateGetMsecSinceEpoch(context(), obj);
return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d);
} else if (obj->isObject() || obj->isArray()) {
return startObject(obj);
} else if (js_IsTypedArray(obj)) {
return writeTypedArray(obj);
} else if (js_IsArrayBuffer(obj) && ArrayBuffer::fromJSObject(obj)) {
return writeArrayBuffer(obj);
}
/* else fall through */
}
/*
* v is either an object or some strange value like JSVAL_HOLE. Even in the
* latter case, we call the write op anyway, to let the application throw
* the NOT_SUPPORTED_ERR exception.
*/
JSContext *cx = context();
JSRuntime *rt = cx->runtime;
if (rt->structuredCloneCallbacks)
return rt->structuredCloneCallbacks->write(cx, this, Jsvalify(v));
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_UNSUPPORTED_TYPE);
return false;
}
bool
JSStructuredCloneWriter::write(const Value &v)
{
if (!startWrite(v))
return false;
while (!counts.empty()) {
JSObject *obj = &objs.back().toObject();
if (counts.back()) {
counts.back()--;
jsid id = ids.back();
ids.popBack();
checkStack();
if (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.
*/
JSObject *obj2;
JSProperty *prop;
if (!js_HasOwnProperty(context(), obj->getOps()->lookupProperty, obj, id,
&obj2, &prop)) {
return false;
}
if (prop) {
obj2->dropProperty(context(), prop);
Value val;
if (!writeId(id) || !obj->getProperty(context(), id, &val) || !startWrite(val))
return false;
}
}
} else {
out.writePair(SCTAG_NULL, 0);
memory.remove(obj);
objs.popBack();
counts.popBack();
}
}
return true;
}
class Chars {
JSContext *cx;
jschar *p;
public:
Chars() : p(NULL) {}
~Chars() { if (p) cx->free(p); }
bool allocate(JSContext *cx, size_t len) {
JS_ASSERT(!p);
p = (jschar *) cx->malloc(len * sizeof(jschar));
this->cx = cx;
return p != NULL;
}
jschar *get() { return p; }
void forget() { p = NULL; }
};
JSString *
JSStructuredCloneReader::readString(uint32_t nchars)
{
if (nchars > JSString::MAX_LENGTH) {
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
"string length");
return NULL;
}
Chars chars;
if (!chars.allocate(context(), nchars) || !in.readChars(chars.get(), nchars))
return NULL;
JSString *str = js_NewString(context(), chars.get(), nchars);
if (str)
chars.forget();
return str;
}
bool
JSStructuredCloneReader::readTypedArray(uint32_t tag, uint32_t nelems, Value *vp)
{
uint32_t atype = TagToArrayType(tag);
JSObject *obj = js_CreateTypedArray(context(), atype, nelems);
if (!obj)
return false;
vp->setObject(*obj);
TypedArray *arr = TypedArray::fromJSObject(obj);
JS_ASSERT(arr->length == nelems);
JS_ASSERT(arr->type == atype);
switch (atype) {
case TypedArray::TYPE_INT8:
case TypedArray::TYPE_UINT8:
case TypedArray::TYPE_UINT8_CLAMPED:
return in.readArray((uint8_t *) arr->data, nelems);
case TypedArray::TYPE_INT16:
case TypedArray::TYPE_UINT16:
return in.readArray((uint16_t *) arr->data, nelems);
case TypedArray::TYPE_INT32:
case TypedArray::TYPE_UINT32:
case TypedArray::TYPE_FLOAT32:
return in.readArray((uint32_t *) arr->data, nelems);
case TypedArray::TYPE_FLOAT64:
return in.readArray((uint64_t *) arr->data, nelems);
default:
JS_NOT_REACHED("unknown TypedArray type");
return false;
}
}
bool
JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp)
{
JSObject *obj = js_CreateArrayBuffer(context(), nbytes);
if (!obj)
return false;
vp->setObject(*obj);
ArrayBuffer *abuf = ArrayBuffer::fromJSObject(obj);
JS_ASSERT(abuf->byteLength == nbytes);
return in.readArray((uint8_t *) abuf->data, nbytes);
}
bool
JSStructuredCloneReader::startRead(Value *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_BOOLEAN:
vp->setBoolean(!!data);
break;
case SCTAG_STRING: {
JSString *str = readString(data);
if (!str)
return false;
vp->setString(str);
break;
}
case SCTAG_DATE_OBJECT: {
jsdouble d;
if (!in.readDouble(&d))
return false;
JSObject *obj = js_NewDateObjectMsec(context(), d);
if (!obj)
return false;
vp->setObject(*obj);
break;
}
case SCTAG_REGEXP_OBJECT: {
uint32_t tag2, nchars;
if (!in.readPair(&tag2, &nchars))
return false;
if (tag2 != SCTAG_STRING) {
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
"regexp");
return false;
}
JSString *str = readString(nchars);
if (!str)
return false;
const jschar *chars;
size_t length;
str->getCharsAndLength(chars, length);
JSObject *obj = RegExp::createObjectNoStatics(context(), chars, length, data);
if (!obj)
return false;
vp->setObject(*obj);
break;
}
case SCTAG_ARRAY_OBJECT:
case SCTAG_OBJECT_OBJECT: {
JSObject *obj = (tag == SCTAG_ARRAY_OBJECT)
? js_NewArrayObject(context(), 0, NULL)
: NewBuiltinClassInstance(context(), &js_ObjectClass);
if (!obj || !objs.append(ObjectValue(*obj)))
return false;
vp->setObject(*obj);
break;
}
case SCTAG_ARRAY_BUFFER_OBJECT:
return readArrayBuffer(data, vp);
default:
if (tag <= SCTAG_FLOAT_MAX) {
jsdouble d = ReinterpretPairAsDouble(tag, data);
if (IsNonCanonicalizedNaN(d)) {
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
return false;
}
vp->setNumber(d);
break;
}
if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX)
return readTypedArray(tag, data, vp);
JSRuntime *rt = context()->runtime;
if (!rt->structuredCloneCallbacks) {
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
"unsupported type");
return false;
}
return rt->structuredCloneCallbacks->read(context(), this, tag, data, Jsvalify(vp));
}
return true;
}
bool
JSStructuredCloneReader::readId(jsid *idp)
{
uint32_t tag, data;
if (!in.readPair(&tag, &data))
return false;
if (tag == SCTAG_INDEX) {
*idp = INT_TO_JSID(int32_t(data));
return true;
}
if (tag == SCTAG_STRING) {
JSString *str = readString(data);
if (!str)
return false;
JSAtom *atom = js_AtomizeString(context(), str, 0);
if (!atom)
return false;
*idp = ATOM_TO_JSID(atom);
return true;
}
if (tag == SCTAG_NULL) {
*idp = JSID_VOID;
return true;
}
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "id");
return false;
}
bool
JSStructuredCloneReader::read(Value *vp)
{
if (!startRead(vp))
return false;
while (objs.length() != 0) {
JSObject *obj = &objs.back().toObject();
jsid id;
if (!readId(&id))
return false;
if (JSID_IS_VOID(id)) {
objs.popBack();
} else {
Value v;
if (!startRead(&v) || !obj->defineProperty(context(), id, v))
return false;
}
}
return true;
}

174
js/src/jsclone.h Normal file
View File

@ -0,0 +1,174 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is JavaScript structured data serialization.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jason Orendorff <jorendorff@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef jsclone_h___
#define jsclone_h___
#include "jsapi.h"
#include "jscntxt.h"
#include "jshashtable.h"
#include "jsstdint.h"
#include "jsvector.h"
#include "jsvalue.h"
namespace js {
bool
WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp);
bool
ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp);
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(jsdouble d);
bool writeBytes(const void *p, size_t nbytes);
bool writeChars(const jschar *p, size_t nchars);
template <class T>
bool writeArray(const T *p, size_t nbytes);
bool extractBuffer(uint64_t **datap, size_t *sizep);
private:
JSContext *cx;
js::Vector<uint64_t> buf;
};
struct SCInput {
public:
SCInput(JSContext *cx, const uint64_t *data, size_t nbytes);
JSContext *context() const { return cx; }
bool read(uint64_t *p);
bool readPair(uint32_t *tagp, uint32_t *datap);
bool readDouble(jsdouble *p);
bool readBytes(void *p, size_t nbytes);
bool readChars(jschar *p, size_t nchars);
template <class T>
bool readArray(T *p, size_t nelems);
private:
bool eof();
void staticAssertions() {
JS_STATIC_ASSERT(sizeof(jschar) == 2);
JS_STATIC_ASSERT(sizeof(uint32_t) == 4);
JS_STATIC_ASSERT(sizeof(jsdouble) == 8);
}
JSContext *cx;
const uint64_t *point;
const uint64_t *end;
};
}
struct JSStructuredCloneReader {
public:
explicit JSStructuredCloneReader(js::SCInput &in)
: in(in), objs(in.context()) {}
js::SCInput &input() { return in; }
bool read(js::Value *vp);
private:
JSContext *context() { return in.context(); }
JSString *readString(uint32_t nchars);
bool readTypedArray(uint32_t tag, uint32_t nelems, js::Value *vp);
bool readArrayBuffer(uint32_t nbytes, js::Value *vp);
bool readId(jsid *idp);
bool startRead(js::Value *vp);
js::SCInput &in;
// Stack of objects with properties remaining to be read.
js::AutoValueVector objs;
};
struct JSStructuredCloneWriter {
public:
explicit JSStructuredCloneWriter(js::SCOutput &out)
: out(out), objs(out.context()), counts(out.context()), ids(out.context()),
memory(out.context()) {}
bool init() { return memory.init(); }
bool write(const js::Value &v);
js::SCOutput &output() { return out; }
private:
JSContext *context() { return out.context(); }
bool writeString(JSString *str);
bool writeId(jsid id);
bool writeArrayBuffer(JSObject *obj);
bool writeTypedArray(JSObject *obj);
bool startObject(JSObject *obj);
bool startWrite(const js::Value &v);
inline void checkStack();
js::SCOutput &out;
// Stack of objects with properties remaining to be written.
js::AutoValueVector objs;
// counts[i] is the number of properties of objs[i] remaining to be written.
// counts.length() == objs.length() and sum(counts) == ids.length().
js::Vector<size_t> counts;
// Ids of properties remaining to be written.
js::AutoIdVector ids;
// The "memory" list described in the HTML5 internal structured cloning algorithm.
// memory has the same elements as objs.
js::HashSet<JSObject *> memory;
};
#endif /* jsclone_h___ */

View File

@ -1478,6 +1478,9 @@ struct JSRuntime {
*/ */
JSSecurityCallbacks *securityCallbacks; JSSecurityCallbacks *securityCallbacks;
/* Structured data callbacks are runtime-wide. */
const JSStructuredCloneCallbacks *structuredCloneCallbacks;
/* /*
* Shared scope property tree, and arena-pool for allocating its nodes. * Shared scope property tree, and arena-pool for allocating its nodes.
* This really should be free of all locking overhead and allocated in * This really should be free of all locking overhead and allocated in

View File

@ -165,6 +165,10 @@ typedef struct JSSecurityCallbacks JSSecurityCallbacks;
typedef struct JSONParser JSONParser; typedef struct JSONParser JSONParser;
typedef struct JSCompartment JSCompartment; typedef struct JSCompartment JSCompartment;
typedef struct JSCrossCompartmentCall JSCrossCompartmentCall; typedef struct JSCrossCompartmentCall JSCrossCompartmentCall;
typedef struct JSStructuredCloneWriter JSStructuredCloneWriter;
typedef struct JSStructuredCloneReader JSStructuredCloneReader;
typedef struct JSStructuredCloneCallbacks JSStructuredCloneCallbacks;
#ifdef __cplusplus #ifdef __cplusplus
typedef class JSWrapper JSWrapper; typedef class JSWrapper JSWrapper;
typedef class JSCrossCompartmentWrapper JSCrossCompartmentWrapper; typedef class JSCrossCompartmentWrapper JSCrossCompartmentWrapper;
@ -565,6 +569,37 @@ typedef enum {
typedef JSBool typedef JSBool
(* JSCompartmentCallback)(JSContext *cx, JSCompartment *compartment, uintN compartmentOp); (* JSCompartmentCallback)(JSContext *cx, JSCompartment *compartment, uintN compartmentOp);
/*
* Read structured data from the reader r. This hook is used to read a value
* previously serialized by a call to the WriteStructuredCloneOp hook.
*
* tag and data are the pair of uint32 values from the header. The callback may
* use the JS_Read* APIs to read any other relevant parts of the object from
* the reader r. On success, it stores an object in *vp and returns JS_TRUE.
*/
typedef JSBool (*ReadStructuredCloneOp)(JSContext *cx, JSStructuredCloneReader *r,
uint32 tag, uint32 data, jsval *vp);
/*
* Structured data serialization hook. The engine can write primitive values,
* Objects, Arrays, Dates, and RegExps. Any other type of object requires
* application support. This callback must first use the JS_WritePair API to
* write an object header, passing a value greater than JS_SCTAG_USER to the
* tag parameter. Then it can use the JS_Write* APIs to write any other
* relevant parts of the value v to the writer w.
*
* If !JSVAL_IS_OBJECT(v), then the callback is expected to report an
* appropriate (application-specific) error and return JS_FALSE.
*/
typedef JSBool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w, jsval v);
/*
* This is called when JS_WriteStructuredClone finds that the object to be
* written is recursive. To follow HTML5, the application must throw a
* DATA_CLONE_ERR DOMException. errorid is always JS_SCERR_RECURSION.
*/
typedef void (*StructuredCloneErrorOp)(JSContext *cx, uint32 errorid);
JS_END_EXTERN_C JS_END_EXTERN_C
#endif /* jspubtd_h___ */ #endif /* jspubtd_h___ */

View File

@ -55,4 +55,4 @@ MSG_DEF(JSSMSG_ASSERT_EQ_FAILED, 10, 2, JSEXN_NONE, "Assertion failed: go
MSG_DEF(JSSMSG_ASSERT_EQ_FAILED_MSG, 11, 3, JSEXN_NONE, "Assertion failed: got {0}, expected {1}: {2}") MSG_DEF(JSSMSG_ASSERT_EQ_FAILED_MSG, 11, 3, JSEXN_NONE, "Assertion failed: got {0}, expected {1}: {2}")
MSG_DEF(JSSMSG_INVALID_ARGS, 12, 1, JSEXN_NONE, "{0}: invalid arguments") MSG_DEF(JSSMSG_INVALID_ARGS, 12, 1, JSEXN_NONE, "{0}: invalid arguments")
MSG_DEF(JSSMSG_ASSERT_JIT_FAILED, 13, 0, JSEXN_NONE, "unexpected failure to JIT") MSG_DEF(JSSMSG_ASSERT_JIT_FAILED, 13, 0, JSEXN_NONE, "unexpected failure to JIT")
MSG_DEF(JSSMSG_BAD_ALIGNMENT, 14, 0, JSEXN_NONE, "serialized data must be 8-byte-aligned")

View File

@ -41,6 +41,7 @@
#define jsvector_h_ #define jsvector_h_
#include "jstl.h" #include "jstl.h"
#include "jsprvtd.h"
/* Silence dire "bugs in previous versions of MSVC have been fixed" warnings */ /* Silence dire "bugs in previous versions of MSVC have been fixed" warnings */
#ifdef _MSC_VER #ifdef _MSC_VER

View File

@ -75,6 +75,7 @@
#include "jsscope.h" #include "jsscope.h"
#include "jsscript.h" #include "jsscript.h"
#include "jstracer.h" #include "jstracer.h"
#include "jstypedarray.h"
#include "jsxml.h" #include "jsxml.h"
#include "jsperf.h" #include "jsperf.h"
@ -4099,8 +4100,6 @@ Snarl(JSContext *cx, uintN argc, jsval *vp)
return JS_TRUE; return JS_TRUE;
} }
JSBool JSBool
Wrap(JSContext *cx, uintN argc, jsval *vp) Wrap(JSContext *cx, uintN argc, jsval *vp)
{ {
@ -4120,6 +4119,49 @@ Wrap(JSContext *cx, uintN argc, jsval *vp)
return true; return true;
} }
JSBool
Serialize(JSContext *cx, uintN argc, jsval *vp)
{
jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
uint64 *datap;
size_t nbytes;
if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes))
return false;
JSObject *arrayobj = js_CreateTypedArray(cx, TypedArray::TYPE_UINT8, nbytes);
if (!arrayobj) {
JS_free(cx, datap);
return false;
}
TypedArray *array = TypedArray::fromJSObject(arrayobj);
JS_ASSERT((uintptr_t(array->data) & 7) == 0);
memcpy(array->data, datap, nbytes);
JS_free(cx, datap);
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(arrayobj));
return true;
}
JSBool
Deserialize(JSContext *cx, uintN argc, jsval *vp)
{
jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
JSObject *obj;
if (!JSVAL_IS_OBJECT(v) || !js_IsTypedArray((obj = JSVAL_TO_OBJECT(v)))) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "deserialize");
return false;
}
TypedArray *array = TypedArray::fromJSObject(obj);
if ((uintptr_t(array->data) & 7) != 0) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_BAD_ALIGNMENT);
return false;
}
if (!JS_ReadStructuredClone(cx, (uint64 *) array->data, array->byteLength, &v))
return false;
JS_SET_RVAL(cx, vp, v);
return true;
}
/* We use a mix of JS_FS and JS_FN to test both kinds of natives. */ /* We use a mix of JS_FS and JS_FN to test both kinds of natives. */
static JSFunctionSpec shell_functions[] = { static JSFunctionSpec shell_functions[] = {
JS_FN("version", Version, 0,0), JS_FN("version", Version, 0,0),
@ -4216,6 +4258,8 @@ static JSFunctionSpec shell_functions[] = {
JS_FN("elapsed", Elapsed, 0,0), JS_FN("elapsed", Elapsed, 0,0),
JS_FN("parent", Parent, 1,0), JS_FN("parent", Parent, 1,0),
JS_FN("wrap", Wrap, 1,0), JS_FN("wrap", Wrap, 1,0),
JS_FN("serialize", Serialize, 1,0),
JS_FN("deserialize", Deserialize, 1,0),
JS_FS_END JS_FS_END
}; };
@ -4341,7 +4385,9 @@ static const char *const shell_help_messages[] = {
" A negative value (default) means that the execution time is unlimited.", " A negative value (default) means that the execution time is unlimited.",
"elapsed() Execution time elapsed for the current context.", "elapsed() Execution time elapsed for the current context.",
"parent(obj) Returns the parent of obj.\n", "parent(obj) Returns the parent of obj.\n",
"wrap(obj) Wrap an object into a noop wrapper.\n" "wrap(obj) Wrap an object into a noop wrapper.\n",
"serialize(sd) Serialize sd using JS_WriteStructuredClone. Returns a TypedArray.\n",
"deserialize(a) Deserialize data generated by serialize.\n"
}; };
/* Help messages must match shell functions. */ /* Help messages must match shell functions. */

View File

@ -0,0 +1,58 @@
// -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
function check(v) {
try {
serialize(v);
} catch (exc) {
return;
}
throw new Error("serializing " + uneval(v) + " should have failed with an exception");
}
// Unsupported object types.
check(new Error("oops"));
check(this);
check(Math);
check(function () {});
check(Proxy.create({enumerate: function () { return []; }}));
check(<element/>);
check(new Namespace("x"));
check(new QName("x", "y"));
// A failing getter.
check({get x() { throw new Error("fail"); }});
// Various recursive objects, i.e. those which the structured cloning
// algorithm wants us to reject due to "memory".
//
// Recursive array.
var a = [];
a[0] = a;
check(a);
// Recursive Object.
var b = {};
b.next = b;
check(b);
// Mutually recursive objects.
a[0] = b;
b.next = a;
check(a);
check(b);
// A recursive object that doesn't fail until 'memory' contains lots of objects.
a = [];
b = a;
for (var i = 0; i < 10000; i++) {
b[0] = {};
b[1] = [];
b = b[1];
}
b[0] = {owner: a};
b[1] = [];
check(a);
reportCompare(0, 0, "ok");

View File

@ -0,0 +1,233 @@
// -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
// Assert that cloning b does the right thing as far as we can tell.
// Caveat: getters in b must produce the same value each time they're
// called. We may call them several times.
//
// If desc is provided, then the very first thing we do to b is clone it.
// (The self-modifying object test counts on this.)
//
function check(b, desc) {
function classOf(obj) {
return Object.prototype.toString.call(obj);
}
function ownProperties(obj) {
return Object.getOwnPropertyNames(obj).
map(function (p) { return [p, Object.getOwnPropertyDescriptor(obj, p)]; });
}
function isCloneable(pair) {
return typeof pair[0] === 'string' && pair[1].enumerable;
}
function notIndex(p) {
var u = p >>> 0;
return !("" + u == p && u != 0xffffffff);
}
function assertIsCloneOf(a, b, path) {
assertEq(a === b, false);
var ca = classOf(a);
assertEq(ca, classOf(b), path);
assertEq(Object.getPrototypeOf(a),
ca == "[object Object]" ? Object.prototype : Array.prototype,
path);
// 'b', the original object, may have non-enumerable or XMLName
// properties; ignore them. 'a', the clone, should not have any
// non-enumerable properties (except .length, if it's an Array) or
// XMLName properties.
var pb = ownProperties(b).filter(isCloneable);
var pa = ownProperties(a);
for (var i = 0; i < pa.length; i++) {
assertEq(typeof pa[i][0], "string", "clone should not have E4X properties " + path);
if (!pa[i][1].enumerable) {
if (Array.isArray(a) && pa[i][0] == "length") {
// remove it so that the comparisons below will work
pa.splice(i, 1);
i--;
} else {
throw new Error("non-enumerable clone property " + uneval(pa[i][0]) + " " + path);
}
}
}
// Check that, apart from properties whose names are array indexes,
// the enumerable properties appear in the same order.
var aNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex);
var bNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex);
assertEq(aNames.join(","), bNames.join(","), path);
// Check that the lists are the same when including array indexes.
function byName(a, b) { a = a[0]; b = b[0]; return a < b ? -1 : a === b ? 0 : 1; }
pa.sort(byName);
pb.sort(byName);
assertEq(pa.length, pb.length, "should see the same number of properties " + path);
for (var i = 0; i < pa.length; i++) {
var aName = pa[i][0];
var bName = pb[i][0];
assertEq(aName, bName, path);
var path2 = path + "." + aName;
var da = pa[i][1];
var db = pb[i][1];
assertEq(da.configurable, true, path2);
assertEq(da.writable, true, path2);
assertEq("value" in da, true, path2);
var va = da.value;
var vb = b[pb[i][0]];
if (typeof va === "object" && va !== null)
queue.push([va, vb, path2]);
else
assertEq(va, vb, path2);
}
}
var banner = "while testing clone of " + (desc || uneval(b));
var a = deserialize(serialize(b));
var queue = [[a, b, banner]];
while (queue.length) {
var triple = queue.shift();
assertIsCloneOf(triple[0], triple[1], triple[2]);
}
return a; // for further testing
}
function test() {
check({});
check([]);
check({x: 0});
check({x: 0.7, p: "forty-two", y: null, z: undefined});
check(Array.prototype);
check(Object.prototype);
// before and after
var b, a;
// Slow array.
b = [, 1, 2, 3];
b.expando = true;
b[5] = 5;
b[0] = 0;
b[4] = 4;
delete b[2];
check(b);
// Check cloning properties other than basic data properties. (check()
// asserts that the properties of the clone are configurable, writable,
// enumerable data properties.)
b = {};
Object.defineProperties(b, {
x: {enumerable: true, get: function () { return 12479; }},
y: {enumerable: true, configurable: true, writable: false, value: 0},
z: {enumerable: true, configurable: false, writable: true, value: 0},
hidden: {enumerable:false, value: 1334}});
check(b);
// Check corner cases involving property names.
b = {"-1": -1,
0xffffffff: null,
0x100000000: null,
"": 0,
"\xff\x7f\u7fff\uffff\ufeff\ufffe": 1, // random unicode id
"\ud800 \udbff \udc00 \udfff": 2}; // busted surrogate pairs
check(b);
b = [];
b[-1] = -1;
b[0xffffffff] = null;
b[0x100000000] = null;
b[""] = 0;
b["\xff\x7f\u7fff\uffff\ufeff\ufffe"] = 1;
b["\ud800 \udbff \udc00 \udfff"] = 2;
check(b);
// An array's .length property is not enumerable, so it is not cloned.
b = Array(5);
assertEq(b.length, 5);
a = check(b);
assertEq(a.length, 0);
b[1] = "ok";
a = check(b);
assertEq(a.length, 2);
// Check that prototypes are not cloned, per spec.
b = Object.create({x:1});
b.y = 2;
b.z = 3;
check(b);
// Check that cloning separates merge points in the tree, per spec.
var same = {};
b = {one: same, two: same};
a = check(b);
assertEq(a.one === a.two, false);
b = [same, same];
a = check(b);
assertEq(a[0] === a[1], false);
// Try cloning a deep object. Don't fail with "too much recursion".
b = {};
var current = b;
for (var i = 0; i < 10000; i++) {
var next = {};
current['x' + i] = next;
current = next;
}
check(b, "deepObject"); // takes 2 seconds :-\
/*
XXX TODO spin this out into its own test
// This fails quickly with an OOM error. An exception would be nicer.
function Infinitree() {
return { get left() { return new Infinitree; },
get right() { return new Infinitree; }};
}
var threw = false;
try {
serialize(new Infinitree);
} catch (exc) {
threw = true;
}
assertEq(threw, true);
*/
// Clone an array with holes.
check([0, 1, 2, , 4, 5, 6]);
// Array holes should not take up space.
b = [];
b[255] = 1;
check(b);
assertEq(serialize(b).length < 255, true);
// Self-modifying object.
// This should never read through to b's prototype.
b = Object.create({y: 2},
{x: {enumerable: true,
configurable: true,
get: function() { if (this.hasOwnProperty("y")) delete this.y; return 1; }},
y: {enumerable: true,
configurable: true,
writable: true,
value: 3}});
check(b, "selfModifyingObject");
// Ignore properties with object-ids.
var uri = "http://example.net";
b = {x: 1, y: 2};
Object.defineProperty(b, AttributeName(uri, "x"), {enumerable: true, value: 3});
Object.defineProperty(b, QName(uri, "y"), {enumerable: true, value: 5});
check(b);
}
test();
reportCompare(0, 0, 'ok');

View File

@ -0,0 +1,31 @@
// -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
function testRegExp(b) {
var a = deserialize(serialize(b));
assertEq(a === b, false);
assertEq(Object.getPrototypeOf(a), RegExp.prototype);
assertEq(Object.prototype.toString.call(a), "[object RegExp]");
for (p in a)
throw new Error("cloned RegExp should have no enumerable properties");
assertEq(a.source, b.source);
assertEq(a.global, b.global);
assertEq(a.ignoreCase, b.ignoreCase);
assertEq(a.multiline, b.multiline);
assertEq(a.sticky, b.sticky);
}
testRegExp(RegExp(""));
testRegExp(/(?:)/);
testRegExp(/^(.*)$/gimy);
testRegExp(RegExp.prototype);
var re = /\bx\b/gi;
re.expando = true;
testRegExp(re);
re.__proto__ = {};
testRegExp(re);
reportCompare(0, 0, 'ok');

View File

@ -0,0 +1,32 @@
// -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
function testEq(b) {
var a = deserialize(serialize(b));
assertEq(a, b);
}
testEq(void 0);
testEq(null);
testEq(true);
testEq(false);
testEq(0);
testEq(-0);
testEq(1/0);
testEq(-1/0);
testEq(0/0);
testEq(Math.PI);
testEq("");
testEq("\0");
testEq("a"); // unit string
testEq("ab"); // length-2 string
testEq("abc\0123\r\n"); // nested null character
testEq("\xff\x7f\u7fff\uffff\ufeff\ufffe"); // random unicode stuff
testEq("\ud800 \udbff \udc00 \udfff"); // busted surrogate pairs
testEq(Array(1024).join(Array(1024).join("x"))); // 2MB string
reportCompare(0, 0, 'ok');

View File

@ -0,0 +1,83 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
function assertArraysEqual(a, b) {
assertEq(a.constructor, b.constructor);
assertEq(a.length, b.length);
for (var i = 0; i < a.length; i++)
assertEq(a[i], b[i]);
}
function check(b) {
var a = deserialize(serialize(b));
assertArraysEqual(a, b);
}
function checkPrototype(ctor) {
var threw = false;
try {
serialize(ctor.prototype);
throw new Error("serializing " + ctor.name + ".prototype should throw a TypeError");
} catch (exc) {
if (!(exc instanceof TypeError))
throw exc;
}
}
function test() {
// Test cloning ArrayBuffer objects.
check(ArrayBuffer(0));
check(ArrayBuffer(7));
checkPrototype(ArrayBuffer);
// Test cloning typed array objects.
var ctors = [
Int8Array,
Uint8Array,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array,
Uint8ClampedArray];
var b;
for (var i = 0; i < ctors.length; i++) {
var ctor = ctors[i];
// check empty array
b = ctor(0);
check(b);
// check array with some elements
b = ctor(100);
var v = 1;
for (var j = 0; j < 100; j++) {
b[j] = v;
v *= 7;
}
b[99] = NaN; // check serializing NaNs too
check(b);
// try the prototype
checkPrototype(ctor);
}
// Cloning should separately copy two TypedArrays backed by the same
// ArrayBuffer. This also tests cloning TypedArrays where the arr->data
// pointer is not 8-byte-aligned.
var base = Int8Array([0, 1, 2, 3]);
b = [Int8Array(base.buffer, 0, 3), Int8Array(base.buffer, 1, 3)];
var a = deserialize(serialize(b));
base[1] = -1;
a[0][2] = -2;
assertArraysEqual(b[0], Int8Array([0, -1, 2])); // shared with base
assertArraysEqual(b[1], Int8Array([-1, 2, 3])); // shared with base
assertArraysEqual(a[0], Int8Array([0, 1, -2])); // not shared with base
assertArraysEqual(a[1], Int8Array([1, 2, 3])); // not shared with base or a[0]
}
test();
reportCompare(0, 0, 'ok');

View File

@ -15,3 +15,8 @@ script proxy-enumerateOwn-duplicates.js
skip-if(!xulRuntime.shell) script reflect-parse.js skip-if(!xulRuntime.shell) script reflect-parse.js
script destructure-accessor.js script destructure-accessor.js
script censor-strict-caller.js script censor-strict-caller.js
skip-if(!xulRuntime.shell) script clone-simple.js
skip-if(!xulRuntime.shell) script clone-regexp.js
skip-if(!xulRuntime.shell) script clone-object.js
skip-if(!xulRuntime.shell) script clone-typed-array.js
skip-if(!xulRuntime.shell) script clone-errors.js