Home | History | Annotate | Download | only in mjsunit
      1 // Copyright 2009 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 // Date toJSON
     29 assertEquals("1970-01-01T00:00:00.000Z", new Date(0).toJSON());
     30 assertEquals("1979-01-11T08:00:00.000Z", new Date("1979-01-11 08:00 GMT").toJSON());
     31 assertEquals("2005-05-05T05:05:05.000Z", new Date("2005-05-05 05:05:05 GMT").toJSON());
     32 var n1 = new Date(10000);
     33 n1.toISOString = function () { return "foo"; };
     34 assertEquals("foo", n1.toJSON());
     35 var n2 = new Date(10001);
     36 n2.toISOString = null;
     37 assertThrows(function () { n2.toJSON(); }, TypeError);
     38 var n4 = new Date(10003);
     39 n4.toISOString = function () {
     40   assertEquals(0, arguments.length);
     41   assertEquals(this, n4);
     42   return null;
     43 };
     44 assertEquals(null, n4.toJSON());
     45 
     46 assertTrue(Object.prototype === JSON.__proto__);
     47 assertEquals("[object JSON]", Object.prototype.toString.call(JSON));
     48 
     49 //Test Date.prototype.toJSON as generic function.
     50 var d1 = {toJSON: Date.prototype.toJSON,
     51          toISOString: function() { return 42; }};
     52 assertEquals(42, d1.toJSON());
     53 
     54 var d2 = {toJSON: Date.prototype.toJSON,
     55           valueOf: function() { return Infinity; },
     56           toISOString: function() { return 42; }};
     57 assertEquals(null, d2.toJSON());
     58 
     59 var d3 = {toJSON: Date.prototype.toJSON,
     60           valueOf: "not callable",
     61           toString: function() { return Infinity; },
     62           toISOString: function() { return 42; }};
     63 
     64 assertEquals(null, d3.toJSON());
     65 
     66 var d4 = {toJSON: Date.prototype.toJSON,
     67           valueOf: "not callable",
     68           toString: "not callable either",
     69           toISOString: function() { return 42; }};
     70 assertThrows("d4.toJSON()", TypeError);  // ToPrimitive throws.
     71 
     72 var d5 = {toJSON: Date.prototype.toJSON,
     73           valueOf: "not callable",
     74           toString: function() { return "Infinity"; },
     75           toISOString: function() { return 42; }};
     76 assertEquals(42, d5.toJSON());
     77 
     78 var d6 = {toJSON: Date.prototype.toJSON,
     79           toISOString: function() { return ["not primitive"]; }};
     80 assertEquals(["not primitive"], d6.toJSON());
     81 
     82 var d7 = {toJSON: Date.prototype.toJSON,
     83           ISOString: "not callable"};
     84 assertThrows("d7.toJSON()", TypeError);
     85 
     86 // DontEnum
     87 for (var p in this) {
     88   assertFalse(p == "JSON");
     89 }
     90 
     91 // Parse
     92 assertEquals({}, JSON.parse("{}"));
     93 assertEquals({42:37}, JSON.parse('{"42":37}'));
     94 assertEquals(null, JSON.parse("null"));
     95 assertEquals(true, JSON.parse("true"));
     96 assertEquals(false, JSON.parse("false"));
     97 assertEquals("foo", JSON.parse('"foo"'));
     98 assertEquals("f\no", JSON.parse('"f\\no"'));
     99 assertEquals("\b\f\n\r\t\"\u2028\/\\",
    100              JSON.parse('"\\b\\f\\n\\r\\t\\"\\u2028\\/\\\\"'));
    101 assertEquals([1.1], JSON.parse("[1.1]"));
    102 assertEquals([1], JSON.parse("[1.0]"));
    103 
    104 assertEquals(0, JSON.parse("0"));
    105 assertEquals(1, JSON.parse("1"));
    106 assertEquals(0.1, JSON.parse("0.1"));
    107 assertEquals(1.1, JSON.parse("1.1"));
    108 assertEquals(1.1, JSON.parse("1.100000"));
    109 assertEquals(1.111111, JSON.parse("1.111111"));
    110 assertEquals(-0, JSON.parse("-0"));
    111 assertEquals(-1, JSON.parse("-1"));
    112 assertEquals(-0.1, JSON.parse("-0.1"));
    113 assertEquals(-1.1, JSON.parse("-1.1"));
    114 assertEquals(-1.1, JSON.parse("-1.100000"));
    115 assertEquals(-1.111111, JSON.parse("-1.111111"));
    116 assertEquals(11, JSON.parse("1.1e1"));
    117 assertEquals(11, JSON.parse("1.1e+1"));
    118 assertEquals(0.11, JSON.parse("1.1e-1"));
    119 assertEquals(11, JSON.parse("1.1E1"));
    120 assertEquals(11, JSON.parse("1.1E+1"));
    121 assertEquals(0.11, JSON.parse("1.1E-1"));
    122 
    123 assertEquals([], JSON.parse("[]"));
    124 assertEquals([1], JSON.parse("[1]"));
    125 assertEquals([1, "2", true, null], JSON.parse('[1, "2", true, null]'));
    126 
    127 assertEquals("", JSON.parse('""'));
    128 assertEquals(["", "", -0, ""], JSON.parse('[    ""  ,    ""  ,   -0,    ""]'));
    129 assertEquals("", JSON.parse('""'));
    130 
    131 
    132 function GetFilter(name) {
    133   function Filter(key, value) {
    134     return (key == name) ? undefined : value;
    135   }
    136   return Filter;
    137 }
    138 
    139 var pointJson = '{"x": 1, "y": 2}';
    140 assertEquals({'x': 1, 'y': 2}, JSON.parse(pointJson));
    141 assertEquals({'x': 1}, JSON.parse(pointJson, GetFilter('y')));
    142 assertEquals({'y': 2}, JSON.parse(pointJson, GetFilter('x')));
    143 assertEquals([1, 2, 3], JSON.parse("[1, 2, 3]"));
    144 assertEquals([1, undefined, 3], JSON.parse("[1, 2, 3]", GetFilter(1)));
    145 assertEquals([1, 2, undefined], JSON.parse("[1, 2, 3]", GetFilter(2)));
    146 
    147 function DoubleNumbers(key, value) {
    148   return (typeof value == 'number') ? 2 * value : value;
    149 }
    150 
    151 var deepObject = '{"a": {"b": 1, "c": 2}, "d": {"e": {"f": 3}}}';
    152 assertEquals({"a": {"b": 1, "c": 2}, "d": {"e": {"f": 3}}},
    153              JSON.parse(deepObject));
    154 assertEquals({"a": {"b": 2, "c": 4}, "d": {"e": {"f": 6}}},
    155              JSON.parse(deepObject, DoubleNumbers));
    156 
    157 function TestInvalid(str) {
    158   assertThrows(function () { JSON.parse(str); }, SyntaxError);
    159 }
    160 
    161 TestInvalid('abcdef');
    162 TestInvalid('isNaN()');
    163 TestInvalid('{"x": [1, 2, deepObject]}');
    164 TestInvalid('[1, [2, [deepObject], 3], 4]');
    165 TestInvalid('function () { return 0; }');
    166 
    167 TestInvalid("[1, 2");
    168 TestInvalid('{"x": 3');
    169 
    170 // JavaScript number literals not valid in JSON.
    171 TestInvalid('[01]');
    172 TestInvalid('[.1]');
    173 TestInvalid('[1.]');
    174 TestInvalid('[1.e1]');
    175 TestInvalid('[-.1]');
    176 TestInvalid('[-1.]');
    177 
    178 // Plain invalid number literals.
    179 TestInvalid('-');
    180 TestInvalid('--1');
    181 TestInvalid('-1e');
    182 TestInvalid('1e--1]');
    183 TestInvalid('1e+-1');
    184 TestInvalid('1e-+1');
    185 TestInvalid('1e++1');
    186 
    187 // JavaScript string literals not valid in JSON.
    188 TestInvalid("'single quote'");  // Valid JavaScript
    189 TestInvalid('"\\a invalid escape"');
    190 TestInvalid('"\\v invalid escape"');  // Valid JavaScript
    191 TestInvalid('"\\\' invalid escape"');  // Valid JavaScript
    192 TestInvalid('"\\x42 invalid escape"');  // Valid JavaScript
    193 TestInvalid('"\\u202 invalid escape"');
    194 TestInvalid('"\\012 invalid escape"');
    195 TestInvalid('"Unterminated string');
    196 TestInvalid('"Unterminated string\\"');
    197 TestInvalid('"Unterminated string\\\\\\"');
    198 
    199 // Test bad JSON that would be good JavaScript (ES5).
    200 TestInvalid("{true:42}");
    201 TestInvalid("{false:42}");
    202 TestInvalid("{null:42}");
    203 TestInvalid("{'foo':42}");
    204 TestInvalid("{42:42}");
    205 TestInvalid("{0:42}");
    206 TestInvalid("{-1:42}");
    207 
    208 // Test for trailing garbage detection.
    209 TestInvalid('42 px');
    210 TestInvalid('42 .2');
    211 TestInvalid('42 2');
    212 TestInvalid('42 e1');
    213 TestInvalid('"42" ""');
    214 TestInvalid('"42" ""');
    215 TestInvalid('"" ""');
    216 TestInvalid('true ""');
    217 TestInvalid('false ""');
    218 TestInvalid('null ""');
    219 TestInvalid('null ""');
    220 TestInvalid('[] ""');
    221 TestInvalid('[true] ""');
    222 TestInvalid('{} ""');
    223 TestInvalid('{"x":true} ""');
    224 TestInvalid('"Garbage""After string"');
    225 
    226 // Stringify
    227 
    228 function TestStringify(expected, input) {
    229   assertEquals(expected, JSON.stringify(input));
    230   assertEquals(expected, JSON.stringify(input, null, 0));
    231 }
    232 
    233 TestStringify("true", true);
    234 TestStringify("false", false);
    235 TestStringify("null", null);
    236 TestStringify("false", {toJSON: function () { return false; }});
    237 TestStringify("4", 4);
    238 TestStringify('"foo"', "foo");
    239 TestStringify("null", Infinity);
    240 TestStringify("null", -Infinity);
    241 TestStringify("null", NaN);
    242 TestStringify("4", new Number(4));
    243 TestStringify('"bar"', new String("bar"));
    244 
    245 TestStringify('"foo\\u0000bar"', "foo\0bar");
    246 TestStringify('"f\\"o\'o\\\\b\\ba\\fr\\nb\\ra\\tz"',
    247               "f\"o\'o\\b\ba\fr\nb\ra\tz");
    248 
    249 TestStringify("[1,2,3]", [1, 2, 3]);
    250 assertEquals("[\n 1,\n 2,\n 3\n]", JSON.stringify([1, 2, 3], null, 1));
    251 assertEquals("[\n  1,\n  2,\n  3\n]", JSON.stringify([1, 2, 3], null, 2));
    252 assertEquals("[\n  1,\n  2,\n  3\n]",
    253              JSON.stringify([1, 2, 3], null, new Number(2)));
    254 assertEquals("[\n^1,\n^2,\n^3\n]", JSON.stringify([1, 2, 3], null, "^"));
    255 assertEquals("[\n^1,\n^2,\n^3\n]",
    256              JSON.stringify([1, 2, 3], null, new String("^")));
    257 assertEquals("[\n 1,\n 2,\n [\n  3,\n  [\n   4\n  ],\n  5\n ],\n 6,\n 7\n]",
    258              JSON.stringify([1, 2, [3, [4], 5], 6, 7], null, 1));
    259 assertEquals("[]", JSON.stringify([], null, 1));
    260 assertEquals("[1,2,[3,[4],5],6,7]",
    261              JSON.stringify([1, 2, [3, [4], 5], 6, 7], null));
    262 assertEquals("[2,4,[6,[8],10],12,14]",
    263              JSON.stringify([1, 2, [3, [4], 5], 6, 7], DoubleNumbers));
    264 TestStringify('["a","ab","abc"]', ["a","ab","abc"]);
    265 TestStringify('{"a":1,"c":true}', { a : 1,
    266                                     b : function() { 1 },
    267                                     c : true,
    268                                     d : function() { 2 } });
    269 TestStringify('[1,null,true,null]',
    270               [1, function() { 1 }, true, function() { 2 }]);
    271 TestStringify('"toJSON 123"',
    272               { toJSON : function() { return 'toJSON 123'; } });
    273 TestStringify('{"a":321}',
    274               { a : { toJSON : function() { return 321; } } });
    275 var counter = 0;
    276 assertEquals('{"getter":123}',
    277              JSON.stringify({ get getter() { counter++; return 123; } }));
    278 assertEquals(1, counter);
    279 assertEquals('{"getter":123}',
    280              JSON.stringify({ get getter() { counter++; return 123; } },
    281                             null,
    282                             0));
    283 assertEquals(2, counter);
    284 
    285 TestStringify('{"a":"abc","b":"\u1234bc"}',
    286               { a : "abc", b : "\u1234bc" });
    287 
    288 
    289 var a = { a : 1, b : 2 };
    290 delete a.a;
    291 TestStringify('{"b":2}', a);
    292 
    293 var b = {};
    294 b.__proto__ = { toJSON : function() { return 321;} };
    295 TestStringify("321", b);
    296 
    297 var array = [""];
    298 var expected = '""';
    299 for (var i = 0; i < 10000; i++) {
    300   array.push("");
    301   expected = '"",' + expected;
    302 }
    303 expected = '[' + expected + ']';
    304 TestStringify(expected, array);
    305 
    306 
    307 var circular = [1, 2, 3];
    308 circular[2] = circular;
    309 assertThrows(function () { JSON.stringify(circular); }, TypeError);
    310 assertThrows(function () { JSON.stringify(circular, null, 0); }, TypeError);
    311 
    312 var singleton = [];
    313 var multiOccurrence = [singleton, singleton, singleton];
    314 TestStringify("[[],[],[]]", multiOccurrence);
    315 
    316 TestStringify('{"x":5,"y":6}', {x:5,y:6});
    317 assertEquals('{"x":5}', JSON.stringify({x:5,y:6}, ['x']));
    318 assertEquals('{\n "a": "b",\n "c": "d"\n}',
    319              JSON.stringify({a:"b",c:"d"}, null, 1));
    320 assertEquals('{"y":6,"x":5}', JSON.stringify({x:5,y:6}, ['y', 'x']));
    321 
    322 // toJSON get string keys.
    323 var checker = {};
    324 var array = [checker];
    325 checker.toJSON = function(key) { return 1 + key; };
    326 TestStringify('["10"]', array);
    327 
    328 // The gap is capped at ten characters if specified as string.
    329 assertEquals('{\n          "a": "b",\n          "c": "d"\n}',
    330               JSON.stringify({a:"b",c:"d"}, null,
    331                              "          /*characters after 10th*/"));
    332 
    333 //The gap is capped at ten characters if specified as number.
    334 assertEquals('{\n          "a": "b",\n          "c": "d"\n}',
    335               JSON.stringify({a:"b",c:"d"}, null, 15));
    336 
    337 // Replaced wrapped primitives are unwrapped.
    338 function newx(k, v)  { return (k == "x") ? new v(42) : v; }
    339 assertEquals('{"x":"42"}', JSON.stringify({x: String}, newx));
    340 assertEquals('{"x":42}', JSON.stringify({x: Number}, newx));
    341 assertEquals('{"x":true}', JSON.stringify({x: Boolean}, newx));
    342 
    343 TestStringify(undefined, undefined);
    344 TestStringify(undefined, function () { });
    345 // Arrays with missing, undefined or function elements have those elements
    346 // replaced by null.
    347 TestStringify("[null,null,null]", [undefined,,function(){}]);
    348 
    349 // Objects with undefined or function properties (including replaced properties)
    350 // have those properties ignored.
    351 assertEquals('{}',
    352              JSON.stringify({a: undefined, b: function(){}, c: 42, d: 42},
    353                             function(k, v) { if (k == "c") return undefined;
    354                                              if (k == "d") return function(){};
    355                                              return v; }));
    356 
    357 TestInvalid('1); throw "foo"; (1');
    358 
    359 var x = 0;
    360 eval("(1); x++; (1)");
    361 TestInvalid('1); x++; (1');
    362 
    363 // Test string conversion of argument.
    364 var o = { toString: function() { return "42"; } };
    365 assertEquals(42, JSON.parse(o));
    366 
    367 
    368 for (var i = 0; i < 65536; i++) {
    369   var string = String.fromCharCode(i);
    370   var encoded = JSON.stringify(string);
    371   var expected = "uninitialized";
    372   // Following the ES5 specification of the abstraction function Quote.
    373   if (string == '"' || string == '\\') {
    374     // Step 2.a
    375     expected = '\\' + string;
    376   } else if ("\b\t\n\r\f".indexOf(string) >= 0) {
    377     // Step 2.b
    378     if (string == '\b') expected = '\\b';
    379     else if (string == '\t') expected = '\\t';
    380     else if (string == '\n') expected = '\\n';
    381     else if (string == '\f') expected = '\\f';
    382     else if (string == '\r') expected = '\\r';
    383   } else if (i < 32) {
    384     // Step 2.c
    385     if (i < 16) {
    386       expected = "\\u000" + i.toString(16);
    387     } else {
    388       expected = "\\u00" + i.toString(16);
    389     }
    390   } else {
    391     expected = string;
    392   }
    393   assertEquals('"' + expected + '"', encoded, "Codepoint " + i);
    394 }
    395 
    396 
    397 // Ensure that wrappers and callables are handled correctly.
    398 var num37 = new Number(42);
    399 num37.valueOf = function() { return 37; };
    400 
    401 var numFoo = new Number(42);
    402 numFoo.valueOf = "not callable";
    403 numFoo.toString = function() { return "foo"; };
    404 
    405 var numTrue = new Number(42);
    406 numTrue.valueOf = function() { return true; }
    407 
    408 var strFoo = new String("bar");
    409 strFoo.toString = function() { return "foo"; };
    410 
    411 var str37 = new String("bar");
    412 str37.toString = "not callable";
    413 str37.valueOf = function() { return 37; };
    414 
    415 var strTrue = new String("bar");
    416 strTrue.toString = function() { return true; }
    417 
    418 var func = function() { /* Is callable */ };
    419 
    420 var funcJSON = function() { /* Is callable */ };
    421 funcJSON.toJSON = function() { return "has toJSON"; };
    422 
    423 var re = /Is callable/;
    424 
    425 var reJSON = /Is callable/;
    426 reJSON.toJSON = function() { return "has toJSON"; };
    427 
    428 TestStringify('[37,null,1,"foo","37","true",null,"has toJSON",{},"has toJSON"]',
    429               [num37, numFoo, numTrue,
    430                strFoo, str37, strTrue,
    431                func, funcJSON, re, reJSON]);
    432 
    433 
    434 var oddball = Object(42);
    435 oddball.__proto__ = { __proto__: null, toString: function() { return true; } };
    436 TestStringify('1', oddball);
    437 
    438 var getCount = 0;
    439 var callCount = 0;
    440 var counter = { get toJSON() { getCount++;
    441                                return function() { callCount++;
    442                                                    return 42; }; } };
    443 
    444 // RegExps are not callable, so they are stringified as objects.
    445 TestStringify('{}', /regexp/);
    446 TestStringify('42', counter);
    447 assertEquals(2, getCount);
    448 assertEquals(2, callCount);
    449 
    450 var oddball2 = Object(42);
    451 var oddball3 = Object("foo");
    452 oddball3.__proto__ = { __proto__: null,
    453                        toString: "not callable",
    454                        valueOf: function() { return true; } };
    455 oddball2.__proto__ = { __proto__: null,
    456                        toJSON: function () { return oddball3; } }
    457 TestStringify('"true"', oddball2);
    458 
    459 
    460 var falseNum = Object("37");
    461 falseNum.__proto__ = Number.prototype;
    462 falseNum.toString = function() { return 42; };
    463 TestStringify('"42"', falseNum);
    464 
    465 // Parse an object value as __proto__.
    466 var o1 = JSON.parse('{"__proto__":[]}');
    467 assertEquals([], o1.__proto__);
    468 assertEquals(["__proto__"], Object.keys(o1));
    469 assertEquals([], Object.getOwnPropertyDescriptor(o1, "__proto__").value);
    470 assertEquals(["__proto__"], Object.getOwnPropertyNames(o1));
    471 assertTrue(o1.hasOwnProperty("__proto__"));
    472 assertTrue(Object.prototype.isPrototypeOf(o1));
    473 
    474 // Parse a non-object value as __proto__.
    475 var o2 = JSON.parse('{"__proto__":5}');
    476 assertEquals(5, o2.__proto__);
    477 assertEquals(["__proto__"], Object.keys(o2));
    478 assertEquals(5, Object.getOwnPropertyDescriptor(o2, "__proto__").value);
    479 assertEquals(["__proto__"], Object.getOwnPropertyNames(o2));
    480 assertTrue(o2.hasOwnProperty("__proto__"));
    481 assertTrue(Object.prototype.isPrototypeOf(o2));
    482 
    483 var json = '{"stuff before slash\\\\stuff after slash":"whatever"}';
    484 TestStringify(json, JSON.parse(json));
    485