Home | History | Annotate | Download | only in harmony
      1 // Copyright 2012 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 // Flags: --harmony-proxies --harmony-reflect
     29 
     30 
     31 
     32 ///////////////////////////////////////////////////////////////////////////////
     33 // JSON.stringify
     34 
     35 
     36 function testStringify(expected, object) {
     37   // Test fast case that bails out to slow case.
     38   assertEquals(expected, JSON.stringify(object));
     39   // Test slow case.
     40   assertEquals(expected, JSON.stringify(object, undefined, 0));
     41 }
     42 
     43 
     44 // Test serializing a proxy, a function proxy, and objects that contain them.
     45 
     46 var handler1 = {
     47   get: function(target, name) {
     48     return name.toUpperCase();
     49   },
     50   ownKeys: function() {
     51     return ['a', 'b', 'c'];
     52   },
     53   getOwnPropertyDescriptor: function() {
     54     return { enumerable: true, configurable: true };
     55   }
     56 }
     57 
     58 var proxy1 = new Proxy({}, handler1);
     59 testStringify('{"a":"A","b":"B","c":"C"}', proxy1);
     60 
     61 var proxy_fun = new Proxy(() => {}, handler1);
     62 assertTrue(typeof(proxy_fun) === 'function');
     63 testStringify(undefined, proxy_fun);
     64 testStringify('[1,null]', [1, proxy_fun]);
     65 
     66 handler1.apply = function() { return 666; };
     67 testStringify(undefined, proxy_fun);
     68 testStringify('[1,null]', [1, proxy_fun]);
     69 
     70 var parent1a = { b: proxy1 };
     71 testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
     72 
     73 var parent1b = { a: 123, b: proxy1, c: true };
     74 testStringify('{"a":123,"b":{"a":"A","b":"B","c":"C"},"c":true}', parent1b);
     75 
     76 var parent1c = [123, proxy1, true];
     77 testStringify('[123,{"a":"A","b":"B","c":"C"},true]', parent1c);
     78 
     79 
     80 // Proxy with side effect.
     81 
     82 var handler2 = {
     83   get: function(target, name) {
     84     delete parent2.c;
     85     return name.toUpperCase();
     86   },
     87   ownKeys: function() {
     88     return ['a', 'b', 'c'];
     89   },
     90   getOwnPropertyDescriptor: function() {
     91     return { enumerable: true, configurable: true };
     92   }
     93 }
     94 
     95 var proxy2 = new Proxy({}, handler2);
     96 var parent2 = { a: "delete", b: proxy2, c: "remove" };
     97 var expected2 = '{"a":"delete","b":{"a":"A","b":"B","c":"C"}}';
     98 assertEquals(expected2, JSON.stringify(parent2));
     99 parent2.c = "remove";  // Revert side effect.
    100 assertEquals(expected2, JSON.stringify(parent2, undefined, 0));
    101 
    102 
    103 // Proxy with a get function that uses the receiver argument.
    104 
    105 var handler3 = {
    106   get: function(target, name, receiver) {
    107     if (name == 'valueOf' || name === Symbol.toPrimitive) {
    108       return function() { return "proxy" };
    109     };
    110     if (typeof name !== 'symbol') return name + "(" + receiver + ")";
    111   },
    112   ownKeys: function() {
    113     return ['a', 'b', 'c'];
    114   },
    115   getOwnPropertyDescriptor: function() {
    116     return { enumerable: true, configurable: true };
    117   }
    118 }
    119 
    120 var proxy3 = new Proxy({}, handler3);
    121 var parent3 = { x: 123, y: proxy3 }
    122 testStringify('{"x":123,"y":{"a":"a(proxy)","b":"b(proxy)","c":"c(proxy)"}}',
    123               parent3);
    124 
    125 
    126 // Empty proxy.
    127 
    128 var handler4 = {
    129   get: function(target, name) {
    130     return 0;
    131   },
    132   enumerate: function(target) {
    133     return [][Symbol.iterator]();
    134   },
    135   has: function() {
    136     return true;
    137   },
    138   getOwnPropertyDescriptor: function(target, name) {
    139     return { enumerable: false };
    140   }
    141 }
    142 
    143 var proxy4 = new Proxy({}, handler4);
    144 testStringify('{}', proxy4);
    145 testStringify('{"a":{}}', { a: proxy4 });
    146 
    147 
    148 // Proxy that provides a toJSON function that uses this.
    149 
    150 var handler5 = {
    151   get: function(target, name) {
    152     if (name == 'z') return 97000;
    153     return function(key) { return key.charCodeAt(0) + this.z; };
    154   },
    155   enumerate: function(target) {
    156     return ['toJSON', 'z'][Symbol.iterator]();
    157   },
    158   has: function() {
    159     return true;
    160   },
    161   getOwnPropertyDescriptor: function(target, name) {
    162     return { enumerable: true };
    163   }
    164 }
    165 
    166 var proxy5 = new Proxy({}, handler5);
    167 testStringify('{"a":97097}', { a: proxy5 });
    168 
    169 
    170 // Proxy that provides a toJSON function that returns undefined.
    171 
    172 var handler6 = {
    173   get: function(target, name) {
    174     return function(key) { return undefined; };
    175   },
    176   enumerate: function(target) {
    177     return ['toJSON'][Symbol.iterator]();
    178   },
    179   has: function() {
    180     return true;
    181   },
    182   getOwnPropertyDescriptor: function(target, name) {
    183     return { enumerable: true };
    184   }
    185 }
    186 
    187 var proxy6 = new Proxy({}, handler6);
    188 testStringify('[1,null,true]', [1, proxy6, true]);
    189 testStringify('{"a":1,"c":true}', {a: 1, b: proxy6, c: true});
    190 
    191 
    192 // Object containing a proxy that changes the parent's properties.
    193 
    194 var handler7 = {
    195   get: function(target, name) {
    196     delete parent7.a;
    197     delete parent7.c;
    198     parent7.e = "5";
    199     return name.toUpperCase();
    200   },
    201   ownKeys: function() {
    202     return ['a', 'b', 'c'];
    203   },
    204   getOwnPropertyDescriptor: function() {
    205     return { enumerable: true, configurable: true };
    206   }
    207 }
    208 
    209 var proxy7 = new Proxy({}, handler7);
    210 var parent7 = { a: "1", b: proxy7, c: "3", d: "4" };
    211 assertEquals('{"a":"1","b":{"a":"A","b":"B","c":"C"},"d":"4"}',
    212              JSON.stringify(parent7));
    213 assertEquals('{"b":{"a":"A","b":"B","c":"C"},"d":"4","e":"5"}',
    214              JSON.stringify(parent7));
    215 
    216 
    217 // (Proxy handler to log trap calls)
    218 
    219 var log = [];
    220 var logger = {};
    221 var handler = new Proxy({}, logger);
    222 
    223 logger.get = function(t, trap, r) {
    224   return function() {
    225     log.push([trap, ...arguments]);
    226     return Reflect[trap](...arguments);
    227   }
    228 };
    229 
    230 
    231 // Object is a callable proxy
    232 
    233 log.length = 0;
    234 var target = () => 42;
    235 var proxy = new Proxy(target, handler);
    236 assertTrue(typeof proxy === 'function');
    237 
    238 assertEquals(undefined, JSON.stringify(proxy));
    239 assertEquals(1, log.length)
    240 for (var i in log) assertSame(target, log[i][1]);
    241 
    242 assertEquals(["get", target, "toJSON", proxy], log[0]);
    243 
    244 
    245 // Object is a non-callable non-arraylike proxy
    246 
    247 log.length = 0;
    248 var target = {foo: 42}
    249 var proxy = new Proxy(target, handler);
    250 assertFalse(Array.isArray(proxy));
    251 
    252 assertEquals('{"foo":42}', JSON.stringify(proxy));
    253 assertEquals(4, log.length)
    254 for (var i in log) assertSame(target, log[i][1]);
    255 
    256 assertEquals(
    257     ["get", target, "toJSON", proxy], log[0]);
    258 assertEquals(
    259     ["ownKeys", target], log[1]);  // EnumerableOwnNames
    260 assertEquals(
    261     ["getOwnPropertyDescriptor", target, "foo"], log[2]);  // EnumerableOwnNames
    262 assertEquals(
    263     ["get", target, "foo", proxy], log[3]);
    264 
    265 
    266 // Object is an arraylike proxy
    267 
    268 log.length = 0;
    269 var target = [42];
    270 var proxy = new Proxy(target, handler);
    271 assertTrue(Array.isArray(proxy));
    272 
    273 assertEquals('[42]', JSON.stringify(proxy));
    274 assertEquals(3, log.length)
    275 for (var i in log) assertSame(target, log[i][1]);
    276 
    277 assertEquals(["get", target, "toJSON", proxy], log[0]);
    278 assertEquals(["get", target, "length", proxy], log[1]);
    279 assertEquals(["get", target, "0", proxy], log[2]);
    280 
    281 
    282 // Replacer is a callable proxy
    283 
    284 log.length = 0;
    285 var object = {0: "foo", 1: 666};
    286 var target = (key, val) => key == "1" ? val + 42 : val;
    287 var proxy = new Proxy(target, handler);
    288 assertTrue(typeof proxy === 'function');
    289 
    290 assertEquals('{"0":"foo","1":708}', JSON.stringify(object, proxy));
    291 assertEquals(3, log.length)
    292 for (var i in log) assertSame(target, log[i][1]);
    293 
    294 assertEquals(4, log[0].length)
    295 assertEquals("apply", log[0][0]);
    296 assertEquals("", log[0][3][0]);
    297 assertEquals({0: "foo", 1: 666}, log[0][3][1]);
    298 assertEquals(4, log[1].length)
    299 assertEquals("apply", log[1][0]);
    300 assertEquals(["0", "foo"], log[1][3]);
    301 assertEquals(4, log[2].length)
    302 assertEquals("apply", log[2][0]);
    303 assertEquals(["1", 666], log[2][3]);
    304 
    305 
    306 // Replacer is an arraylike proxy
    307 
    308 log.length = 0;
    309 var object = {0: "foo", 1: 666};
    310 var target = [0];
    311 var proxy = new Proxy(target, handler);
    312 assertTrue(Array.isArray(proxy));
    313 
    314 assertEquals('{"0":"foo"}', JSON.stringify(object, proxy));
    315 assertEquals(2, log.length)
    316 for (var i in log) assertSame(target, log[i][1]);
    317 
    318 assertEquals(["get", target, "length", proxy], log[0]);
    319 assertEquals(["get", target, "0", proxy], log[1]);
    320 
    321 
    322 // Replacer is an arraylike proxy and object is an array
    323 
    324 log.length = 0;
    325 var object = ["foo", 42];
    326 var target = [0];
    327 var proxy = new Proxy(target, handler);
    328 assertTrue(Array.isArray(proxy));
    329 
    330 assertEquals('["foo",42]', JSON.stringify(object, proxy));
    331 assertEquals(2, log.length);
    332 for (var i in log) assertSame(target, log[i][1]);
    333 
    334 assertEquals(["get", target, "length", proxy], log[0]);
    335 assertEquals(["get", target, "0", proxy], log[1]);
    336 
    337 
    338 // Replacer is an arraylike proxy with a non-trivial length
    339 
    340 var getTrap = function(t, key) {
    341   if (key === "length") return {[Symbol.toPrimitive]() {return 42}};
    342   if (key === "41") return "foo";
    343   if (key === "42") return "bar";
    344 };
    345 var target = [];
    346 var proxy = new Proxy(target, {get: getTrap});
    347 assertTrue(Array.isArray(proxy));
    348 var object = {foo: true, bar: 666};
    349 assertEquals('{"foo":true}', JSON.stringify(object, proxy));
    350 
    351 
    352 // Replacer is an arraylike proxy with a bogus length
    353 
    354 var getTrap = function(t, key) {
    355   if (key === "length") return Symbol();
    356   if (key === "41") return "foo";
    357   if (key === "42") return "bar";
    358 };
    359 var target = [];
    360 var proxy = new Proxy(target, {get: getTrap});
    361 assertTrue(Array.isArray(proxy));
    362 var object = {foo: true, bar: 666};
    363 assertThrows(() => JSON.stringify(object, proxy), TypeError);
    364 
    365 
    366 // Replacer returns a non-callable non-arraylike proxy
    367 
    368 log.length = 0;
    369 var object = ["foo", 42];
    370 var target = {baz: 5};
    371 var proxy = new Proxy(target, handler);
    372 var replacer = (key, val) => key === "1" ? proxy : val;
    373 
    374 assertEquals('["foo",{"baz":5}]', JSON.stringify(object, replacer));
    375 assertEquals(3, log.length);
    376 for (var i in log) assertSame(target, log[i][1]);
    377 
    378 assertEquals(["ownKeys", target], log[0]);
    379 assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]);
    380 
    381 
    382 // Replacer returns an arraylike proxy
    383 
    384 log.length = 0;
    385 var object = ["foo", 42];
    386 var target = ["bar"];
    387 var proxy = new Proxy(target, handler);
    388 var replacer = (key, val) => key === "1" ? proxy : val;
    389 
    390 assertEquals('["foo",["bar"]]', JSON.stringify(object, replacer));
    391 assertEquals(2, log.length);
    392 for (var i in log) assertSame(target, log[i][1]);
    393 
    394 assertEquals(["get", target, "length", proxy], log[0]);
    395 assertEquals(["get", target, "0", proxy], log[1]);
    396 
    397 
    398 // Replacer returns an arraylike proxy with a non-trivial length
    399 
    400 var getTrap = function(t, key) {
    401   if (key === "length") return {[Symbol.toPrimitive]() {return 3}};
    402   if (key === "2") return "baz";
    403   if (key === "3") return "bar";
    404 };
    405 var target = [];
    406 var proxy = new Proxy(target, {get: getTrap});
    407 var replacer = (key, val) => key === "goo" ? proxy : val;
    408 var object = {foo: true, goo: false};
    409 assertEquals('{"foo":true,"goo":[null,null,"baz"]}',
    410     JSON.stringify(object, replacer));
    411 
    412 
    413 // Replacer returns an arraylike proxy with a bogus length
    414 
    415 var getTrap = function(t, key) {
    416   if (key === "length") return Symbol();
    417   if (key === "2") return "baz";
    418   if (key === "3") return "bar";
    419 };
    420 var target = [];
    421 var proxy = new Proxy(target, {get: getTrap});
    422 var replacer = (key, val) => key === "goo" ? proxy : val;
    423 var object = {foo: true, goo: false};
    424 assertThrows(() => JSON.stringify(object, replacer), TypeError);
    425 
    426 
    427 // Replacer returns a callable proxy
    428 
    429 log.length = 0;
    430 var target = () => 666;
    431 var proxy = new Proxy(target, handler);
    432 var replacer = (key, val) => key === "1" ? proxy : val;
    433 
    434 assertEquals('["foo",null]', JSON.stringify(["foo", 42], replacer));
    435 assertEquals(0, log.length);
    436 
    437 assertEquals('{"0":"foo"}', JSON.stringify({0: "foo", 1: 42}, replacer));
    438 assertEquals(0, log.length);
    439 
    440 
    441 
    442 ///////////////////////////////////////////////////////////////////////////////
    443 // JSON.parse
    444 
    445 
    446 // Reviver is a callable proxy
    447 
    448 log.length = 0;
    449 var target = () => 42;
    450 var proxy = new Proxy(target, handler);
    451 assertTrue(typeof proxy === "function");
    452 
    453 assertEquals(42, JSON.parse("[true, false]", proxy));
    454 assertEquals(3, log.length);
    455 for (var i in log) assertSame(target, log[i][1]);
    456 
    457 assertEquals(4, log[0].length);
    458 assertEquals("apply", log[0][0]);
    459 assertEquals(["0", true], log[0][3]);
    460 assertEquals(4, log[1].length);
    461 assertEquals("apply", log[1][0]);
    462 assertEquals(["1", false], log[1][3]);
    463 assertEquals(4, log[2].length);
    464 assertEquals("apply", log[2][0]);
    465 assertEquals(["", [42, 42]], log[2][3]);
    466 
    467 
    468 // Reviver plants a non-arraylike proxy into a yet-to-be-visited property
    469 
    470 log.length = 0;
    471 var target = {baz: 42};
    472 var proxy = new Proxy(target, handler);
    473 var reviver = function(p, v) {
    474   if (p === "baz") return 5;
    475   if (p === "foo") this.bar = proxy;
    476   return v;
    477 }
    478 
    479 assertEquals({foo: 0, bar: proxy}, JSON.parse('{"foo":0,"bar":1}', reviver));
    480 assertEquals(4, log.length);
    481 for (var i in log) assertSame(target, log[i][1]);
    482 
    483 assertEquals(["ownKeys", target], log[0]);
    484 assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]);
    485 assertEquals(["get", target, "baz", proxy], log[2]);
    486 assertEquals(["defineProperty", target, "baz",
    487     {value: 5, configurable: true, writable: true, enumerable: true}], log[3]);
    488 
    489 
    490 // Reviver plants an arraylike proxy into a yet-to-be-visited property
    491 
    492 log.length = 0;
    493 var target = [42];
    494 var proxy = new Proxy(target, handler);
    495 assertTrue(Array.isArray(proxy));
    496 var reviver = function(p, v) {
    497   if (p === "0") return undefined;
    498   if (p === "foo") this.bar = proxy;
    499   return v;
    500 }
    501 
    502 var result = JSON.parse('{"foo":0,"bar":1}', reviver);
    503 assertEquals({foo: 0, bar: proxy}, result);
    504 assertSame(result.bar, proxy);
    505 assertEquals(3, log.length);
    506 for (var i in log) assertSame(target, log[i][1]);
    507 
    508 assertEquals(["get", target, "length", proxy], log[0]);
    509 assertEquals(["get", target, "0", proxy], log[1]);
    510 assertEquals(["deleteProperty", target, "0"], log[2]);
    511