Add support for structured cloning of String/Boolean/Number objects. Bug 602804, r=gal.
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 03 Dec 2010 10:07:08 -0600
changeset 58716 582b2499287698bf722bf45b6b73a7bdd0483b6e
parent 58715 25fd3451c0ae6de4f182bd48e02768369ed08c06
child 58717 3c1d1a61f75d260a492c6e8f243d11b6fc7e7927
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersgal
bugs602804
milestone2.0b8pre
Add support for structured cloning of String/Boolean/Number objects. Bug 602804, r=gal.
js/src/jsclone.cpp
js/src/jsclone.h
js/src/tests/js1_8_5/extensions/clone-leaf-object.js
js/src/tests/js1_8_5/extensions/clone-regexp.js
js/src/tests/js1_8_5/extensions/jstests.list
--- a/js/src/jsclone.cpp
+++ b/js/src/jsclone.cpp
@@ -74,16 +74,19 @@ enum StructuredDataType {
     SCTAG_BOOLEAN,
     SCTAG_INDEX,
     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_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);
 
@@ -340,31 +343,31 @@ SCOutput::extractBuffer(uint64_t **datap
 {
     *sizep = buf.length() * sizeof(uint64_t);
     return (*datap = buf.extractRawBuffer()) != NULL;
 }
 
 JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
 
 bool
-JSStructuredCloneWriter::writeString(JSString *str)
+JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
 {
     const jschar *chars;
     size_t length;
     str->getCharsAndLength(chars, length);
-    return out.writePair(SCTAG_STRING, uint32_t(length)) && out.writeChars(chars, length);
+    return out.writePair(tag, 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));
+    return writeString(SCTAG_STRING, 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;
@@ -491,40 +494,47 @@ JSStructuredCloneWriter::startObject(JSO
     /* 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());
+        return writeString(SCTAG_STRING, 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());
+                   writeString(SCTAG_STRING, 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 if (obj->isBoolean()) {
+            return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->getPrimitiveThis().toBoolean());
+        } else if (obj->isNumber()) {
+            return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
+                   out.writeDouble(obj->getPrimitiveThis().toNumber());
+        } else if (obj->isString()) {
+            return writeString(SCTAG_STRING_OBJECT, obj->getPrimitiveThis().toString());
         }
 
         const JSStructuredCloneCallbacks *cb = context()->runtime->structuredCloneCallbacks;
         if (cb)
             return cb->write(context(), this, obj);
         /* else fall through */
     }
 
@@ -569,16 +579,27 @@ JSStructuredCloneWriter::write(const Val
             memory.remove(obj);
             objs.popBack();
             counts.popBack();
         }
     }
     return true;
 }
 
+bool
+JSStructuredCloneReader::checkDouble(jsdouble d)
+{
+    if (IsNonCanonicalizedNaN(d)) {
+        JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
+                             JSMSG_SC_BAD_SERIALIZED_DATA, "unrecognized NaN");
+        return false;
+    }
+    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) {
@@ -664,24 +685,40 @@ JSStructuredCloneReader::startRead(Value
         vp->setNull();
         break;
 
       case SCTAG_UNDEFINED:
         vp->setUndefined();
         break;
 
       case SCTAG_BOOLEAN:
+      case SCTAG_BOOLEAN_OBJECT:
         vp->setBoolean(!!data);
+        if (tag == SCTAG_BOOLEAN_OBJECT && !js_PrimitiveToObject(context(), vp))
+            return false;
         break;
 
-      case SCTAG_STRING: {
+      case SCTAG_STRING:
+      case SCTAG_STRING_OBJECT: {
         JSString *str = readString(data);
         if (!str)
             return false;
         vp->setString(str);
+        if (tag == SCTAG_STRING_OBJECT && !js_PrimitiveToObject(context(), vp))
+            return false;
+        break;
+      }
+
+      case SCTAG_NUMBER_OBJECT: {
+        jsdouble d;
+        if (!in.readDouble(&d) || !checkDouble(d))
+            return false;
+        vp->setDouble(d);
+        if (!js_PrimitiveToObject(context(), vp))
+            return false;
         break;
       }
 
       case SCTAG_DATE_OBJECT: {
         jsdouble d;
         if (!in.readDouble(&d))
             return false;
         JSObject *obj = js_NewDateObjectMsec(context(), d);
@@ -725,21 +762,18 @@ JSStructuredCloneReader::startRead(Value
       }
 
       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");
+            if (!checkDouble(d))
                 return false;
-            }
             vp->setNumber(d);
             break;
         }
 
         if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX)
             return readTypedArray(tag, data, vp);
 
         const JSStructuredCloneCallbacks *cb = context()->runtime->structuredCloneCallbacks;
--- a/js/src/jsclone.h
+++ b/js/src/jsclone.h
@@ -113,16 +113,17 @@ struct JSStructuredCloneReader {
         : in(in), objs(in.context()) {}
 
     js::SCInput &input() { return in; }
     bool read(js::Value *vp);
 
   private:
     JSContext *context() { return in.context(); }
 
+    bool checkDouble(jsdouble d);
     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;
 
@@ -140,17 +141,17 @@ struct JSStructuredCloneWriter {
 
     bool write(const js::Value &v);
 
     js::SCOutput &output() { return out; }
 
   private:
     JSContext *context() { return out.context(); }
 
-    bool writeString(JSString *str);
+    bool writeString(uint32_t tag, 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();
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_8_5/extensions/clone-leaf-object.js
@@ -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/
+
+var a = [new Boolean(true),
+         new Boolean(false),
+         new Number(0),
+         new Number(-0),
+         new Number(Math.PI),
+         new Number(0x7fffffff),
+         new Number(-0x7fffffff),
+         new Number(0x80000000),
+         new Number(-0x80000000),
+         new Number(0xffffffff),
+         new Number(-0xffffffff),
+         new Number(0x100000000),
+         new Number(-0x100000000),
+         new Number(Number.MIN_VALUE),
+         new Number(-Number.MIN_VALUE),
+         new Number(Number.MAX_VALUE),
+         new Number(-Number.MAX_VALUE),
+         new Number(1/0),
+         new Number(-1/0),
+         new Number(0/0),
+         new String(""),
+         new String("\0123\u4567"),
+         new Date(0),
+         new Date(-0),
+         new Date(Math.PI),
+         new Date(0x7fffffff),
+         new Date(-0x7fffffff),
+         new Date(0x80000000),
+         new Date(-0x80000000),
+         new Date(0xffffffff),
+         new Date(-0xffffffff),
+         new Date(0x100000000),
+         new Date(-0x100000000),
+         new Date(1286523948674),
+         new Date(8.64e15), // hard-coded in ES5 spec, hard-coded here
+         new Date(-8.64e15),
+         new Date(NaN)];
+
+for (var i = 0; i < a.length; i++) {
+    var x = a[i];
+    var expectedSource = x.toSource();
+    var expectedProto = x.__proto__;
+    var expectedString = Object.prototype.toString.call(x);
+    x.expando = 1;
+    x.__proto__ = {};
+
+    var y = deserialize(serialize(x));
+    assertEq(y.toSource(), expectedSource);
+    assertEq(y.__proto__, expectedProto);
+    assertEq(Object.prototype.toString.call(y), expectedString);
+    assertEq("expando" in y, false);
+}
+
+reportCompare(0, 0);
--- a/js/src/tests/js1_8_5/extensions/clone-regexp.js
+++ b/js/src/tests/js1_8_5/extensions/clone-regexp.js
@@ -10,16 +10,17 @@ function testRegExp(b) {
     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);
+    assertEq("expando" in a, false);
 }
 
 testRegExp(RegExp(""));
 testRegExp(/(?:)/);
 testRegExp(/^(.*)$/gimy);
 testRegExp(RegExp.prototype);
 
 var re = /\bx\b/gi;
--- a/js/src/tests/js1_8_5/extensions/jstests.list
+++ b/js/src/tests/js1_8_5/extensions/jstests.list
@@ -13,12 +13,13 @@ script array-length-protochange.js
 script parseInt-octal.js
 script proxy-enumerateOwn-duplicates.js
 skip-if(!xulRuntime.shell) script proxy-proto-setter.js
 skip-if(!xulRuntime.shell) script reflect-parse.js
 script destructure-accessor.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-leaf-object.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
 script set-property-non-extensible.js