blob: 6b40e3ee7d8d68796be0e8a22f28a888004ac06e [file] [log] [blame]
Ben Murdochb8a8cc12014-11-26 15:28:44 +00001// 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
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000028
29
30///////////////////////////////////////////////////////////////////////////////
31// JSON.stringify
32
Ben Murdochb8a8cc12014-11-26 15:28:44 +000033
34function testStringify(expected, object) {
35 // Test fast case that bails out to slow case.
36 assertEquals(expected, JSON.stringify(object));
37 // Test slow case.
Ben Murdoch61f157c2016-09-16 13:49:30 +010038 assertEquals(expected, JSON.stringify(object, (key, value) => value));
39 // Test gap.
40 assertEquals(JSON.stringify(object, null, "="),
41 JSON.stringify(object, (key, value) => value, "="));
Ben Murdochb8a8cc12014-11-26 15:28:44 +000042}
43
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000044
45// Test serializing a proxy, a function proxy, and objects that contain them.
46
Ben Murdochb8a8cc12014-11-26 15:28:44 +000047var handler1 = {
48 get: function(target, name) {
49 return name.toUpperCase();
50 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000051 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +000052 return ['a', 'b', 'c'];
53 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000054 getOwnPropertyDescriptor: function() {
55 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000056 }
57}
58
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000059var proxy1 = new Proxy({}, handler1);
Ben Murdochb8a8cc12014-11-26 15:28:44 +000060testStringify('{"a":"A","b":"B","c":"C"}', proxy1);
61
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000062var proxy_fun = new Proxy(() => {}, handler1);
63assertTrue(typeof(proxy_fun) === 'function');
64testStringify(undefined, proxy_fun);
65testStringify('[1,null]', [1, proxy_fun]);
66
67handler1.apply = function() { return 666; };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000068testStringify(undefined, proxy_fun);
69testStringify('[1,null]', [1, proxy_fun]);
70
71var parent1a = { b: proxy1 };
72testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
Ben Murdoch61f157c2016-09-16 13:49:30 +010073testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
Ben Murdochb8a8cc12014-11-26 15:28:44 +000074
75var parent1b = { a: 123, b: proxy1, c: true };
76testStringify('{"a":123,"b":{"a":"A","b":"B","c":"C"},"c":true}', parent1b);
77
78var parent1c = [123, proxy1, true];
79testStringify('[123,{"a":"A","b":"B","c":"C"},true]', parent1c);
80
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000081
Ben Murdochb8a8cc12014-11-26 15:28:44 +000082// Proxy with side effect.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000083
Ben Murdochb8a8cc12014-11-26 15:28:44 +000084var handler2 = {
85 get: function(target, name) {
86 delete parent2.c;
87 return name.toUpperCase();
88 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000089 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +000090 return ['a', 'b', 'c'];
91 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000092 getOwnPropertyDescriptor: function() {
93 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000094 }
95}
96
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000097var proxy2 = new Proxy({}, handler2);
Ben Murdochb8a8cc12014-11-26 15:28:44 +000098var parent2 = { a: "delete", b: proxy2, c: "remove" };
99var expected2 = '{"a":"delete","b":{"a":"A","b":"B","c":"C"}}';
100assertEquals(expected2, JSON.stringify(parent2));
101parent2.c = "remove"; // Revert side effect.
102assertEquals(expected2, JSON.stringify(parent2, undefined, 0));
103
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000104
105// Proxy with a get function that uses the receiver argument.
106
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000107var handler3 = {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000108 get: function(target, name, receiver) {
109 if (name == 'valueOf' || name === Symbol.toPrimitive) {
110 return function() { return "proxy" };
111 };
112 if (typeof name !== 'symbol') return name + "(" + receiver + ")";
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000113 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000114 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000115 return ['a', 'b', 'c'];
116 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000117 getOwnPropertyDescriptor: function() {
118 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000119 }
120}
121
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000122var proxy3 = new Proxy({}, handler3);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000123var parent3 = { x: 123, y: proxy3 }
124testStringify('{"x":123,"y":{"a":"a(proxy)","b":"b(proxy)","c":"c(proxy)"}}',
125 parent3);
126
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000127
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000128// Empty proxy.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000129
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000130var handler4 = {
131 get: function(target, name) {
132 return 0;
133 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000134 has: function() {
135 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000136 },
137 getOwnPropertyDescriptor: function(target, name) {
138 return { enumerable: false };
139 }
140}
141
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000142var proxy4 = new Proxy({}, handler4);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000143testStringify('{}', proxy4);
144testStringify('{"a":{}}', { a: proxy4 });
145
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000146
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000147// Proxy that provides a toJSON function that uses this.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000148
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000149var handler5 = {
150 get: function(target, name) {
151 if (name == 'z') return 97000;
152 return function(key) { return key.charCodeAt(0) + this.z; };
153 },
Ben Murdochda12d292016-06-02 14:46:10 +0100154 ownKeys: function(target) {
155 return ['toJSON', 'z'];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000156 },
157 has: function() {
158 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000159 },
160 getOwnPropertyDescriptor: function(target, name) {
161 return { enumerable: true };
162 }
163}
164
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000165var proxy5 = new Proxy({}, handler5);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000166testStringify('{"a":97097}', { a: proxy5 });
167
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000168
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000169// Proxy that provides a toJSON function that returns undefined.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000170
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000171var handler6 = {
172 get: function(target, name) {
173 return function(key) { return undefined; };
174 },
Ben Murdochda12d292016-06-02 14:46:10 +0100175 ownKeys: function(target) {
176 return ['toJSON'];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000177 },
178 has: function() {
179 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000180 },
181 getOwnPropertyDescriptor: function(target, name) {
182 return { enumerable: true };
183 }
184}
185
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000186var proxy6 = new Proxy({}, handler6);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000187testStringify('[1,null,true]', [1, proxy6, true]);
188testStringify('{"a":1,"c":true}', {a: 1, b: proxy6, c: true});
189
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000190
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000191// Object containing a proxy that changes the parent's properties.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000192
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000193var handler7 = {
194 get: function(target, name) {
195 delete parent7.a;
196 delete parent7.c;
197 parent7.e = "5";
198 return name.toUpperCase();
199 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000200 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000201 return ['a', 'b', 'c'];
202 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000203 getOwnPropertyDescriptor: function() {
204 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000205 }
206}
207
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000208var proxy7 = new Proxy({}, handler7);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000209var parent7 = { a: "1", b: proxy7, c: "3", d: "4" };
210assertEquals('{"a":"1","b":{"a":"A","b":"B","c":"C"},"d":"4"}',
211 JSON.stringify(parent7));
212assertEquals('{"b":{"a":"A","b":"B","c":"C"},"d":"4","e":"5"}',
213 JSON.stringify(parent7));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000214
215
216// (Proxy handler to log trap calls)
217
218var log = [];
219var logger = {};
220var handler = new Proxy({}, logger);
221
222logger.get = function(t, trap, r) {
223 return function() {
224 log.push([trap, ...arguments]);
225 return Reflect[trap](...arguments);
226 }
227};
228
229
230// Object is a callable proxy
231
232log.length = 0;
233var target = () => 42;
234var proxy = new Proxy(target, handler);
235assertTrue(typeof proxy === 'function');
236
237assertEquals(undefined, JSON.stringify(proxy));
238assertEquals(1, log.length)
239for (var i in log) assertSame(target, log[i][1]);
240
241assertEquals(["get", target, "toJSON", proxy], log[0]);
242
243
244// Object is a non-callable non-arraylike proxy
245
246log.length = 0;
247var target = {foo: 42}
248var proxy = new Proxy(target, handler);
249assertFalse(Array.isArray(proxy));
250
251assertEquals('{"foo":42}', JSON.stringify(proxy));
252assertEquals(4, log.length)
253for (var i in log) assertSame(target, log[i][1]);
254
255assertEquals(
256 ["get", target, "toJSON", proxy], log[0]);
257assertEquals(
258 ["ownKeys", target], log[1]); // EnumerableOwnNames
259assertEquals(
260 ["getOwnPropertyDescriptor", target, "foo"], log[2]); // EnumerableOwnNames
261assertEquals(
262 ["get", target, "foo", proxy], log[3]);
263
264
265// Object is an arraylike proxy
266
267log.length = 0;
268var target = [42];
269var proxy = new Proxy(target, handler);
270assertTrue(Array.isArray(proxy));
271
272assertEquals('[42]', JSON.stringify(proxy));
273assertEquals(3, log.length)
274for (var i in log) assertSame(target, log[i][1]);
275
276assertEquals(["get", target, "toJSON", proxy], log[0]);
277assertEquals(["get", target, "length", proxy], log[1]);
278assertEquals(["get", target, "0", proxy], log[2]);
279
280
281// Replacer is a callable proxy
282
283log.length = 0;
284var object = {0: "foo", 1: 666};
285var target = (key, val) => key == "1" ? val + 42 : val;
286var proxy = new Proxy(target, handler);
287assertTrue(typeof proxy === 'function');
288
289assertEquals('{"0":"foo","1":708}', JSON.stringify(object, proxy));
290assertEquals(3, log.length)
291for (var i in log) assertSame(target, log[i][1]);
292
293assertEquals(4, log[0].length)
294assertEquals("apply", log[0][0]);
295assertEquals("", log[0][3][0]);
296assertEquals({0: "foo", 1: 666}, log[0][3][1]);
297assertEquals(4, log[1].length)
298assertEquals("apply", log[1][0]);
299assertEquals(["0", "foo"], log[1][3]);
300assertEquals(4, log[2].length)
301assertEquals("apply", log[2][0]);
302assertEquals(["1", 666], log[2][3]);
303
304
305// Replacer is an arraylike proxy
306
307log.length = 0;
308var object = {0: "foo", 1: 666};
309var target = [0];
310var proxy = new Proxy(target, handler);
311assertTrue(Array.isArray(proxy));
312
313assertEquals('{"0":"foo"}', JSON.stringify(object, proxy));
314assertEquals(2, log.length)
315for (var i in log) assertSame(target, log[i][1]);
316
317assertEquals(["get", target, "length", proxy], log[0]);
318assertEquals(["get", target, "0", proxy], log[1]);
319
320
321// Replacer is an arraylike proxy and object is an array
322
323log.length = 0;
324var object = ["foo", 42];
325var target = [0];
326var proxy = new Proxy(target, handler);
327assertTrue(Array.isArray(proxy));
328
329assertEquals('["foo",42]', JSON.stringify(object, proxy));
330assertEquals(2, log.length);
331for (var i in log) assertSame(target, log[i][1]);
332
333assertEquals(["get", target, "length", proxy], log[0]);
334assertEquals(["get", target, "0", proxy], log[1]);
335
336
337// Replacer is an arraylike proxy with a non-trivial length
338
339var getTrap = function(t, key) {
340 if (key === "length") return {[Symbol.toPrimitive]() {return 42}};
341 if (key === "41") return "foo";
342 if (key === "42") return "bar";
343};
344var target = [];
345var proxy = new Proxy(target, {get: getTrap});
346assertTrue(Array.isArray(proxy));
347var object = {foo: true, bar: 666};
348assertEquals('{"foo":true}', JSON.stringify(object, proxy));
349
350
351// Replacer is an arraylike proxy with a bogus length
352
353var getTrap = function(t, key) {
354 if (key === "length") return Symbol();
355 if (key === "41") return "foo";
356 if (key === "42") return "bar";
357};
358var target = [];
359var proxy = new Proxy(target, {get: getTrap});
360assertTrue(Array.isArray(proxy));
361var object = {foo: true, bar: 666};
362assertThrows(() => JSON.stringify(object, proxy), TypeError);
363
364
365// Replacer returns a non-callable non-arraylike proxy
366
367log.length = 0;
368var object = ["foo", 42];
369var target = {baz: 5};
370var proxy = new Proxy(target, handler);
371var replacer = (key, val) => key === "1" ? proxy : val;
372
373assertEquals('["foo",{"baz":5}]', JSON.stringify(object, replacer));
374assertEquals(3, log.length);
375for (var i in log) assertSame(target, log[i][1]);
376
377assertEquals(["ownKeys", target], log[0]);
378assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]);
379
380
381// Replacer returns an arraylike proxy
382
383log.length = 0;
384var object = ["foo", 42];
385var target = ["bar"];
386var proxy = new Proxy(target, handler);
387var replacer = (key, val) => key === "1" ? proxy : val;
388
389assertEquals('["foo",["bar"]]', JSON.stringify(object, replacer));
390assertEquals(2, log.length);
391for (var i in log) assertSame(target, log[i][1]);
392
393assertEquals(["get", target, "length", proxy], log[0]);
394assertEquals(["get", target, "0", proxy], log[1]);
395
396
397// Replacer returns an arraylike proxy with a non-trivial length
398
399var getTrap = function(t, key) {
400 if (key === "length") return {[Symbol.toPrimitive]() {return 3}};
401 if (key === "2") return "baz";
402 if (key === "3") return "bar";
403};
404var target = [];
405var proxy = new Proxy(target, {get: getTrap});
406var replacer = (key, val) => key === "goo" ? proxy : val;
407var object = {foo: true, goo: false};
408assertEquals('{"foo":true,"goo":[null,null,"baz"]}',
409 JSON.stringify(object, replacer));
410
411
412// Replacer returns an arraylike proxy with a bogus length
413
414var getTrap = function(t, key) {
415 if (key === "length") return Symbol();
416 if (key === "2") return "baz";
417 if (key === "3") return "bar";
418};
419var target = [];
420var proxy = new Proxy(target, {get: getTrap});
421var replacer = (key, val) => key === "goo" ? proxy : val;
422var object = {foo: true, goo: false};
423assertThrows(() => JSON.stringify(object, replacer), TypeError);
424
425
426// Replacer returns a callable proxy
427
428log.length = 0;
429var target = () => 666;
430var proxy = new Proxy(target, handler);
431var replacer = (key, val) => key === "1" ? proxy : val;
432
433assertEquals('["foo",null]', JSON.stringify(["foo", 42], replacer));
434assertEquals(0, log.length);
435
436assertEquals('{"0":"foo"}', JSON.stringify({0: "foo", 1: 42}, replacer));
437assertEquals(0, log.length);
438
439
440
441///////////////////////////////////////////////////////////////////////////////
442// JSON.parse
443
444
445// Reviver is a callable proxy
446
447log.length = 0;
448var target = () => 42;
449var proxy = new Proxy(target, handler);
450assertTrue(typeof proxy === "function");
451
452assertEquals(42, JSON.parse("[true, false]", proxy));
453assertEquals(3, log.length);
454for (var i in log) assertSame(target, log[i][1]);
455
456assertEquals(4, log[0].length);
457assertEquals("apply", log[0][0]);
458assertEquals(["0", true], log[0][3]);
459assertEquals(4, log[1].length);
460assertEquals("apply", log[1][0]);
461assertEquals(["1", false], log[1][3]);
462assertEquals(4, log[2].length);
463assertEquals("apply", log[2][0]);
464assertEquals(["", [42, 42]], log[2][3]);
465
466
467// Reviver plants a non-arraylike proxy into a yet-to-be-visited property
468
469log.length = 0;
470var target = {baz: 42};
471var proxy = new Proxy(target, handler);
472var reviver = function(p, v) {
473 if (p === "baz") return 5;
474 if (p === "foo") this.bar = proxy;
475 return v;
476}
477
478assertEquals({foo: 0, bar: proxy}, JSON.parse('{"foo":0,"bar":1}', reviver));
479assertEquals(4, log.length);
480for (var i in log) assertSame(target, log[i][1]);
481
482assertEquals(["ownKeys", target], log[0]);
483assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]);
484assertEquals(["get", target, "baz", proxy], log[2]);
485assertEquals(["defineProperty", target, "baz",
486 {value: 5, configurable: true, writable: true, enumerable: true}], log[3]);
487
488
489// Reviver plants an arraylike proxy into a yet-to-be-visited property
490
491log.length = 0;
492var target = [42];
493var proxy = new Proxy(target, handler);
494assertTrue(Array.isArray(proxy));
495var reviver = function(p, v) {
496 if (p === "0") return undefined;
497 if (p === "foo") this.bar = proxy;
498 return v;
499}
500
501var result = JSON.parse('{"foo":0,"bar":1}', reviver);
502assertEquals({foo: 0, bar: proxy}, result);
503assertSame(result.bar, proxy);
504assertEquals(3, log.length);
505for (var i in log) assertSame(target, log[i][1]);
506
507assertEquals(["get", target, "length", proxy], log[0]);
508assertEquals(["get", target, "0", proxy], log[1]);
509assertEquals(["deleteProperty", target, "0"], log[2]);
Ben Murdoch61f157c2016-09-16 13:49:30 +0100510
511proxy = new Proxy([], {
512 get: function(target, property) {
513 if (property == "length") return 7;
514 return 0;
515 },
516});
517assertEquals('[[0,0,0,0,0,0,0]]', JSON.stringify([proxy]));
518
519proxy = new Proxy([], {
520 get: function(target, property) {
521 if (property == "length") return 1E40;
522 return 0;
523 },
524});
525assertThrows(() => JSON.stringify([proxy]), RangeError);
526
527log = [];
528proxy = new Proxy({}, {
529 ownKeys: function() {
530 log.push("ownKeys");
531 return ["0", "a", "b"];
532 },
533 get: function(target, property) {
534 log.push("get " + property);
535 return property.toUpperCase();
536 },
537 getOwnPropertyDescriptor: function(target, property) {
538 log.push("descriptor " + property);
539 return {enumerable: true, configurable: true};
540 },
541 isExtensible: assertUnreachable,
542 has: assertUnreachable,
543 getPrototypeOf: assertUnreachable,
544 setPrototypeOf: assertUnreachable,
545 preventExtensions: assertUnreachable,
546 setPrototypeOf: assertUnreachable,
547 defineProperty: assertUnreachable,
548 set: assertUnreachable,
549 deleteProperty: assertUnreachable,
550 apply: assertUnreachable,
551 construct: assertUnreachable,
552});
553
554assertEquals('[{"0":"0","a":"A","b":"B"}]', JSON.stringify([proxy]));
555assertEquals(['get toJSON',
556 'ownKeys',
557 'descriptor 0',
558 'descriptor a',
559 'descriptor b',
560 'get 0',
561 'get a',
562 'get b'], log);