Bug 748309 - Test structured clone handling of cyclic graphs. r=jorendorff
authorSteve Fink <sfink@mozilla.com>
Fri, 21 Sep 2012 13:10:28 -0700
changeset 107769 2c9976725a5779075efb21a087ccc644eac0684c
parent 107768 f0e182ab06a9a0aa65825544ac44135e9e67e42a
child 107770 259206b2c7b25d08ac11125c814de0ee43ea121a
push id23509
push userryanvm@gmail.com
push dateSat, 22 Sep 2012 12:28:38 +0000
treeherdermozilla-central@b461a7cd250e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs748309
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 748309 - Test structured clone handling of cyclic graphs. r=jorendorff
js/src/tests/js1_8_5/extensions/clone-complex-object.js
--- a/js/src/tests/js1_8_5/extensions/clone-complex-object.js
+++ b/js/src/tests/js1_8_5/extensions/clone-complex-object.js
@@ -1,77 +1,168 @@
 // |reftest| skip-if(!xulRuntime.shell)
 // -*- 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/
 
+// Set of properties on a cloned object that are legitimately non-enumerable,
+// grouped by object type.
+var non_enumerable = { 'Array': [ 'length' ],
+                       'String': [ 'length' ] };
+
+// Set of properties on a cloned object that are legitimately non-configurable,
+// grouped by object type. The property name '0' stands in for any indexed
+// property.
+var non_configurable = { 'String': [ 0 ],
+                         '(typed array)': [ 0 ] };
+
+// Set of properties on a cloned object that are legitimately non-writable,
+// grouped by object type. The property name '0' stands in for any indexed
+// property.
+var non_writable = { 'String': [ 0 ] };
+
+function classOf(obj) {
+    var classString = Object.prototype.toString.call(obj);
+    var [ all, classname ] = classString.match(/\[object (\w+)/);
+    return classname;
+}
+
+function isIndex(p) {
+    var u = p >>> 0;
+    return ("" + u == p && u != 0xffffffff);
+}
+
+function notIndex(p) {
+    return !isIndex(p);
+}
+
+function tableContains(table, cls, prop) {
+    if (isIndex(prop))
+        prop = 0;
+    if (cls.match(/\wArray$/))
+        cls = "(typed array)";
+    var exceptionalProps = table[cls] || [];
+    return exceptionalProps.indexOf(prop) != -1;
+}
+
+function shouldBeConfigurable(cls, prop) {
+    return !tableContains(non_configurable, cls, prop);
+}
+
+function shouldBeWritable(cls, prop) {
+    return !tableContains(non_writable, cls, prop);
+}
+
+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 compareProperties(a, b, stack, path) {
+    var ca = classOf(a);
+
+    // '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 String) or XMLName
+    // properties.
+    var pb = ownProperties(b).filter(isCloneable);
+    var pa = ownProperties(a);
+    for (var i = 0; i < pa.length; i++) {
+        var propname = pa[i][0];
+        assertEq(typeof propname, "string", "clone should not have E4X properties " + path);
+        if (!pa[i][1].enumerable) {
+            if (tableContains(non_enumerable, ca, propname)) {
+                // 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 = isIndex(aName) ? path + "[" + aName + "]" : path + "." + aName;
+        var da = pa[i][1];
+        var db = pb[i][1];
+        assertEq(da.configurable, shouldBeConfigurable(ca, aName), path2);
+        assertEq(da.writable, shouldBeWritable(ca, aName), path2);
+        assertEq("value" in da, true, path2);
+        var va = da.value;
+        var vb = b[pb[i][0]];
+        stack.push([va, vb, path2]);
+    }
+}
+
 function isClone(a, b) {
-    var stack = [[a, b]];
+    var stack = [[a, b, 'obj']];
     var memory = new WeakMap();
     var rmemory = new WeakMap();
 
     while (stack.length > 0) {
         var pair = stack.pop();
-        var x = pair[0], y = pair[1];
+        var x = pair[0], y = pair[1], path = pair[2];
         if (typeof x !== "object" || x === null) {
             // x is primitive.
-            if (x !== y)
-                return false;
+            assertEq(x, y, "equal primitives");
         } else if (x instanceof Date) {
-            if (x.getTime() !== y.getTime())
-                return false;
+            assertEq(x.getTime(), y.getTime(), "equal times for cloned Dates");
         } else if (memory.has(x)) {
             // x is an object we have seen before in a.
-            if (y !== memory.get(x))
-                return false;
-            assertEq(rmemory.get(y), x);
+            assertEq(y, memory.get(x), "repeated object the same");
+            assertEq(rmemory.get(y), x, "repeated object's clone already seen");
         } else {
             // x is an object we have not seen before.
-	          // Check that we have not seen y before either.
-            if (rmemory.has(y))
-                return false;
-
-            // x and y must be of the same [[Class]].
-            var xcls = Object.prototype.toString.call(x);
-            var ycls = Object.prototype.toString.call(y);
-            if (xcls !== ycls)
-                return false;
+	    // Check that we have not seen y before either.
+            assertEq(rmemory.has(y), false);
 
-            // This function is only designed to check Objects and Arrays.
-            assertEq(xcls === "[object Object]" || xcls === "[object Array]",
-                     true);
+            var xcls = classOf(x);
+            var ycls = classOf(y);
+            assertEq(xcls, ycls, "same [[Class]]");
 
-            // Compare objects.
-            var xk = Object.keys(x), yk = Object.keys(y);
-            if (xk.length !== yk.length)
-                return false;
-            for (var i = 0; i < xk.length; i++) {
-                // We must see the same property names in the same order.
-                if (xk[i] !== yk[i])
-                    return false;
+            // clone objects should have the default prototype of the class
+            assertEq(Object.getPrototypeOf(x), this[xcls].prototype);
 
-                // Put the property values on the stack to compare later.
-                stack.push([x[xk[i]], y[yk[i]]]);
-            }
+            compareProperties(x, y, stack, path);
 
             // Record that we have seen this pair of objects.
             memory.set(x, y);
             rmemory.set(y, x);
         }
     }
     return true;
 }
 
-function check(a) {
-    assertEq(isClone(a, deserialize(serialize(a))), true);
+function check(val) {
+    var clone = deserialize(serialize(val));
+    assertEq(isClone(val, clone), true);
+    return clone;
 }
 
-// Various recursive objects, i.e. those which the structured cloning
-// algorithm wants us to reject due to "memory".
-//
+// Various recursive objects
+
 // Recursive array.
 var a = [];
 a[0] = a;
 check(a);
 
 // Recursive Object.
 var b = {};
 b.next = b;
@@ -98,9 +189,125 @@ for (var i = 0; i < 10000; i++) {
     b[0] = {};
     b[1] = [];
     b = b[1];
 }
 b[0] = {owner: a};
 b[1] = [];
 check(a);
 
+// Date objects should not be identical even if representing the same date
+var ar = [ new Date(1000), new Date(1000) ];
+var clone = check(ar);
+assertEq(clone[0] === clone[1], false);
+
+// Identity preservation for various types of objects
+
+function checkSimpleIdentity(v)
+{
+    a = check([ v, v ]);
+    assertEq(a[0] === a[1], true);
+    return a;
+}
+
+var v = new Boolean(true);
+checkSimpleIdentity(v);
+
+v = new Number(17);
+checkSimpleIdentity(v);
+
+v = new String("yo");
+checkSimpleIdentity(v);
+
+v = "fish";
+checkSimpleIdentity(v);
+
+v = new Int8Array([ 10, 20 ]);
+checkSimpleIdentity(v);
+
+v = new ArrayBuffer(7);
+checkSimpleIdentity(v);
+
+v = new Date(1000);
+b = [ v, v, { 'date': v } ];
+clone = check(b);
+assertEq(clone[0] === clone[1], true);
+assertEq(clone[0], clone[2]['date']);
+assertEq(clone[0] === v, false);
+
+// Reduced and modified from postMessage_structured_clone test
+let foo = { };
+let baz = { };
+let obj = { 'foo': foo,
+            'bar': { 'foo': foo },
+            'expando': { 'expando': baz },
+            'baz': baz };
+check(obj);
+
+for (var obj of new getTestContent)
+    check(obj);
+
+// Stolen wholesale from postMessage_structured_clone_helper.js
+function getTestContent()
+{
+  yield "hello";
+  yield 2+3;
+  yield 12;
+  yield null;
+  yield "complex" + "string";
+  yield new Object();
+  yield new Date(1306113544);
+  yield [1, 2, 3, 4, 5];
+  let obj = new Object();
+  obj.foo = 3;
+  obj.bar = "hi";
+  obj.baz = new Date(1306113544);
+  obj.boo = obj;
+  yield obj;
+
+  let recursiveobj = new Object();
+  recursiveobj.a = recursiveobj;
+  recursiveobj.foo = new Object();
+  recursiveobj.foo.bar = "bar";
+  recursiveobj.foo.backref = recursiveobj;
+  recursiveobj.foo.baz = 84;
+  recursiveobj.foo.backref2 = recursiveobj;
+  recursiveobj.bar = new Object();
+  recursiveobj.bar.foo = "foo";
+  recursiveobj.bar.backref = recursiveobj;
+  recursiveobj.bar.baz = new Date(1306113544);
+  recursiveobj.bar.backref2 = recursiveobj;
+  recursiveobj.expando = recursiveobj;
+  yield recursiveobj;
+
+  let obj = new Object();
+  obj.expando1 = 1;
+  obj.foo = new Object();
+  obj.foo.bar = 2;
+  obj.bar = new Object();
+  obj.bar.foo = obj.foo;
+  obj.expando = new Object();
+  obj.expando.expando = new Object();
+  obj.expando.expando.obj = obj;
+  obj.expando2 = 4;
+  obj.baz = obj.expando.expando;
+  obj.blah = obj.bar;
+  obj.foo.baz = obj.blah;
+  obj.foo.blah = obj.blah;
+  yield obj;
+
+  let diamond = new Object();
+  let obj = new Object();
+  obj.foo = "foo";
+  obj.bar = 92;
+  obj.backref = diamond;
+  diamond.ref1 = obj;
+  diamond.ref2 = obj;
+  yield diamond;
+
+  let doubleref = new Object();
+  let obj = new Object();
+  doubleref.ref1 = obj;
+  doubleref.ref2 = obj;
+  yield doubleref;
+}
+
 reportCompare(0, 0, 'ok');