blob: d48d5390f6e803706e4df98e1905e2c0bc805d2d [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.
38 assertEquals(expected, JSON.stringify(object, undefined, 0));
39}
40
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000041
42// Test serializing a proxy, a function proxy, and objects that contain them.
43
Ben Murdochb8a8cc12014-11-26 15:28:44 +000044var handler1 = {
45 get: function(target, name) {
46 return name.toUpperCase();
47 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000048 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +000049 return ['a', 'b', 'c'];
50 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000051 getOwnPropertyDescriptor: function() {
52 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000053 }
54}
55
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000056var proxy1 = new Proxy({}, handler1);
Ben Murdochb8a8cc12014-11-26 15:28:44 +000057testStringify('{"a":"A","b":"B","c":"C"}', proxy1);
58
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000059var proxy_fun = new Proxy(() => {}, handler1);
60assertTrue(typeof(proxy_fun) === 'function');
61testStringify(undefined, proxy_fun);
62testStringify('[1,null]', [1, proxy_fun]);
63
64handler1.apply = function() { return 666; };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000065testStringify(undefined, proxy_fun);
66testStringify('[1,null]', [1, proxy_fun]);
67
68var parent1a = { b: proxy1 };
69testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
70
71var parent1b = { a: 123, b: proxy1, c: true };
72testStringify('{"a":123,"b":{"a":"A","b":"B","c":"C"},"c":true}', parent1b);
73
74var parent1c = [123, proxy1, true];
75testStringify('[123,{"a":"A","b":"B","c":"C"},true]', parent1c);
76
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000077
Ben Murdochb8a8cc12014-11-26 15:28:44 +000078// Proxy with side effect.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000079
Ben Murdochb8a8cc12014-11-26 15:28:44 +000080var handler2 = {
81 get: function(target, name) {
82 delete parent2.c;
83 return name.toUpperCase();
84 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000085 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +000086 return ['a', 'b', 'c'];
87 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000088 getOwnPropertyDescriptor: function() {
89 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000090 }
91}
92
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000093var proxy2 = new Proxy({}, handler2);
Ben Murdochb8a8cc12014-11-26 15:28:44 +000094var parent2 = { a: "delete", b: proxy2, c: "remove" };
95var expected2 = '{"a":"delete","b":{"a":"A","b":"B","c":"C"}}';
96assertEquals(expected2, JSON.stringify(parent2));
97parent2.c = "remove"; // Revert side effect.
98assertEquals(expected2, JSON.stringify(parent2, undefined, 0));
99
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000100
101// Proxy with a get function that uses the receiver argument.
102
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000103var handler3 = {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000104 get: function(target, name, receiver) {
105 if (name == 'valueOf' || name === Symbol.toPrimitive) {
106 return function() { return "proxy" };
107 };
108 if (typeof name !== 'symbol') return name + "(" + receiver + ")";
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000109 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000110 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000111 return ['a', 'b', 'c'];
112 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000113 getOwnPropertyDescriptor: function() {
114 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000115 }
116}
117
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000118var proxy3 = new Proxy({}, handler3);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000119var parent3 = { x: 123, y: proxy3 }
120testStringify('{"x":123,"y":{"a":"a(proxy)","b":"b(proxy)","c":"c(proxy)"}}',
121 parent3);
122
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000123
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000124// Empty proxy.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000125
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000126var handler4 = {
127 get: function(target, name) {
128 return 0;
129 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000130 has: function() {
131 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000132 },
133 getOwnPropertyDescriptor: function(target, name) {
134 return { enumerable: false };
135 }
136}
137
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000138var proxy4 = new Proxy({}, handler4);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000139testStringify('{}', proxy4);
140testStringify('{"a":{}}', { a: proxy4 });
141
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000142
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000143// Proxy that provides a toJSON function that uses this.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000144
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000145var handler5 = {
146 get: function(target, name) {
147 if (name == 'z') return 97000;
148 return function(key) { return key.charCodeAt(0) + this.z; };
149 },
Ben Murdochda12d292016-06-02 14:46:10 +0100150 ownKeys: function(target) {
151 return ['toJSON', 'z'];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000152 },
153 has: function() {
154 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000155 },
156 getOwnPropertyDescriptor: function(target, name) {
157 return { enumerable: true };
158 }
159}
160
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000161var proxy5 = new Proxy({}, handler5);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000162testStringify('{"a":97097}', { a: proxy5 });
163
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000164
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000165// Proxy that provides a toJSON function that returns undefined.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000166
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000167var handler6 = {
168 get: function(target, name) {
169 return function(key) { return undefined; };
170 },
Ben Murdochda12d292016-06-02 14:46:10 +0100171 ownKeys: function(target) {
172 return ['toJSON'];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000173 },
174 has: function() {
175 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000176 },
177 getOwnPropertyDescriptor: function(target, name) {
178 return { enumerable: true };
179 }
180}
181
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000182var proxy6 = new Proxy({}, handler6);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000183testStringify('[1,null,true]', [1, proxy6, true]);
184testStringify('{"a":1,"c":true}', {a: 1, b: proxy6, c: true});
185
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000186
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000187// Object containing a proxy that changes the parent's properties.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000188
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000189var handler7 = {
190 get: function(target, name) {
191 delete parent7.a;
192 delete parent7.c;
193 parent7.e = "5";
194 return name.toUpperCase();
195 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000196 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000197 return ['a', 'b', 'c'];
198 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000199 getOwnPropertyDescriptor: function() {
200 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000201 }
202}
203
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000204var proxy7 = new Proxy({}, handler7);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000205var parent7 = { a: "1", b: proxy7, c: "3", d: "4" };
206assertEquals('{"a":"1","b":{"a":"A","b":"B","c":"C"},"d":"4"}',
207 JSON.stringify(parent7));
208assertEquals('{"b":{"a":"A","b":"B","c":"C"},"d":"4","e":"5"}',
209 JSON.stringify(parent7));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000210
211
212// (Proxy handler to log trap calls)
213
214var log = [];
215var logger = {};
216var handler = new Proxy({}, logger);
217
218logger.get = function(t, trap, r) {
219 return function() {
220 log.push([trap, ...arguments]);
221 return Reflect[trap](...arguments);
222 }
223};
224
225
226// Object is a callable proxy
227
228log.length = 0;
229var target = () => 42;
230var proxy = new Proxy(target, handler);
231assertTrue(typeof proxy === 'function');
232
233assertEquals(undefined, JSON.stringify(proxy));
234assertEquals(1, log.length)
235for (var i in log) assertSame(target, log[i][1]);
236
237assertEquals(["get", target, "toJSON", proxy], log[0]);
238
239
240// Object is a non-callable non-arraylike proxy
241
242log.length = 0;
243var target = {foo: 42}
244var proxy = new Proxy(target, handler);
245assertFalse(Array.isArray(proxy));
246
247assertEquals('{"foo":42}', JSON.stringify(proxy));
248assertEquals(4, log.length)
249for (var i in log) assertSame(target, log[i][1]);
250
251assertEquals(
252 ["get", target, "toJSON", proxy], log[0]);
253assertEquals(
254 ["ownKeys", target], log[1]); // EnumerableOwnNames
255assertEquals(
256 ["getOwnPropertyDescriptor", target, "foo"], log[2]); // EnumerableOwnNames
257assertEquals(
258 ["get", target, "foo", proxy], log[3]);
259
260
261// Object is an arraylike proxy
262
263log.length = 0;
264var target = [42];
265var proxy = new Proxy(target, handler);
266assertTrue(Array.isArray(proxy));
267
268assertEquals('[42]', JSON.stringify(proxy));
269assertEquals(3, log.length)
270for (var i in log) assertSame(target, log[i][1]);
271
272assertEquals(["get", target, "toJSON", proxy], log[0]);
273assertEquals(["get", target, "length", proxy], log[1]);
274assertEquals(["get", target, "0", proxy], log[2]);
275
276
277// Replacer is a callable proxy
278
279log.length = 0;
280var object = {0: "foo", 1: 666};
281var target = (key, val) => key == "1" ? val + 42 : val;
282var proxy = new Proxy(target, handler);
283assertTrue(typeof proxy === 'function');
284
285assertEquals('{"0":"foo","1":708}', JSON.stringify(object, proxy));
286assertEquals(3, log.length)
287for (var i in log) assertSame(target, log[i][1]);
288
289assertEquals(4, log[0].length)
290assertEquals("apply", log[0][0]);
291assertEquals("", log[0][3][0]);
292assertEquals({0: "foo", 1: 666}, log[0][3][1]);
293assertEquals(4, log[1].length)
294assertEquals("apply", log[1][0]);
295assertEquals(["0", "foo"], log[1][3]);
296assertEquals(4, log[2].length)
297assertEquals("apply", log[2][0]);
298assertEquals(["1", 666], log[2][3]);
299
300
301// Replacer is an arraylike proxy
302
303log.length = 0;
304var object = {0: "foo", 1: 666};
305var target = [0];
306var proxy = new Proxy(target, handler);
307assertTrue(Array.isArray(proxy));
308
309assertEquals('{"0":"foo"}', JSON.stringify(object, proxy));
310assertEquals(2, log.length)
311for (var i in log) assertSame(target, log[i][1]);
312
313assertEquals(["get", target, "length", proxy], log[0]);
314assertEquals(["get", target, "0", proxy], log[1]);
315
316
317// Replacer is an arraylike proxy and object is an array
318
319log.length = 0;
320var object = ["foo", 42];
321var target = [0];
322var proxy = new Proxy(target, handler);
323assertTrue(Array.isArray(proxy));
324
325assertEquals('["foo",42]', JSON.stringify(object, proxy));
326assertEquals(2, log.length);
327for (var i in log) assertSame(target, log[i][1]);
328
329assertEquals(["get", target, "length", proxy], log[0]);
330assertEquals(["get", target, "0", proxy], log[1]);
331
332
333// Replacer is an arraylike proxy with a non-trivial length
334
335var getTrap = function(t, key) {
336 if (key === "length") return {[Symbol.toPrimitive]() {return 42}};
337 if (key === "41") return "foo";
338 if (key === "42") return "bar";
339};
340var target = [];
341var proxy = new Proxy(target, {get: getTrap});
342assertTrue(Array.isArray(proxy));
343var object = {foo: true, bar: 666};
344assertEquals('{"foo":true}', JSON.stringify(object, proxy));
345
346
347// Replacer is an arraylike proxy with a bogus length
348
349var getTrap = function(t, key) {
350 if (key === "length") return Symbol();
351 if (key === "41") return "foo";
352 if (key === "42") return "bar";
353};
354var target = [];
355var proxy = new Proxy(target, {get: getTrap});
356assertTrue(Array.isArray(proxy));
357var object = {foo: true, bar: 666};
358assertThrows(() => JSON.stringify(object, proxy), TypeError);
359
360
361// Replacer returns a non-callable non-arraylike proxy
362
363log.length = 0;
364var object = ["foo", 42];
365var target = {baz: 5};
366var proxy = new Proxy(target, handler);
367var replacer = (key, val) => key === "1" ? proxy : val;
368
369assertEquals('["foo",{"baz":5}]', JSON.stringify(object, replacer));
370assertEquals(3, log.length);
371for (var i in log) assertSame(target, log[i][1]);
372
373assertEquals(["ownKeys", target], log[0]);
374assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]);
375
376
377// Replacer returns an arraylike proxy
378
379log.length = 0;
380var object = ["foo", 42];
381var target = ["bar"];
382var proxy = new Proxy(target, handler);
383var replacer = (key, val) => key === "1" ? proxy : val;
384
385assertEquals('["foo",["bar"]]', JSON.stringify(object, replacer));
386assertEquals(2, log.length);
387for (var i in log) assertSame(target, log[i][1]);
388
389assertEquals(["get", target, "length", proxy], log[0]);
390assertEquals(["get", target, "0", proxy], log[1]);
391
392
393// Replacer returns an arraylike proxy with a non-trivial length
394
395var getTrap = function(t, key) {
396 if (key === "length") return {[Symbol.toPrimitive]() {return 3}};
397 if (key === "2") return "baz";
398 if (key === "3") return "bar";
399};
400var target = [];
401var proxy = new Proxy(target, {get: getTrap});
402var replacer = (key, val) => key === "goo" ? proxy : val;
403var object = {foo: true, goo: false};
404assertEquals('{"foo":true,"goo":[null,null,"baz"]}',
405 JSON.stringify(object, replacer));
406
407
408// Replacer returns an arraylike proxy with a bogus length
409
410var getTrap = function(t, key) {
411 if (key === "length") return Symbol();
412 if (key === "2") return "baz";
413 if (key === "3") return "bar";
414};
415var target = [];
416var proxy = new Proxy(target, {get: getTrap});
417var replacer = (key, val) => key === "goo" ? proxy : val;
418var object = {foo: true, goo: false};
419assertThrows(() => JSON.stringify(object, replacer), TypeError);
420
421
422// Replacer returns a callable proxy
423
424log.length = 0;
425var target = () => 666;
426var proxy = new Proxy(target, handler);
427var replacer = (key, val) => key === "1" ? proxy : val;
428
429assertEquals('["foo",null]', JSON.stringify(["foo", 42], replacer));
430assertEquals(0, log.length);
431
432assertEquals('{"0":"foo"}', JSON.stringify({0: "foo", 1: 42}, replacer));
433assertEquals(0, log.length);
434
435
436
437///////////////////////////////////////////////////////////////////////////////
438// JSON.parse
439
440
441// Reviver is a callable proxy
442
443log.length = 0;
444var target = () => 42;
445var proxy = new Proxy(target, handler);
446assertTrue(typeof proxy === "function");
447
448assertEquals(42, JSON.parse("[true, false]", proxy));
449assertEquals(3, log.length);
450for (var i in log) assertSame(target, log[i][1]);
451
452assertEquals(4, log[0].length);
453assertEquals("apply", log[0][0]);
454assertEquals(["0", true], log[0][3]);
455assertEquals(4, log[1].length);
456assertEquals("apply", log[1][0]);
457assertEquals(["1", false], log[1][3]);
458assertEquals(4, log[2].length);
459assertEquals("apply", log[2][0]);
460assertEquals(["", [42, 42]], log[2][3]);
461
462
463// Reviver plants a non-arraylike proxy into a yet-to-be-visited property
464
465log.length = 0;
466var target = {baz: 42};
467var proxy = new Proxy(target, handler);
468var reviver = function(p, v) {
469 if (p === "baz") return 5;
470 if (p === "foo") this.bar = proxy;
471 return v;
472}
473
474assertEquals({foo: 0, bar: proxy}, JSON.parse('{"foo":0,"bar":1}', reviver));
475assertEquals(4, log.length);
476for (var i in log) assertSame(target, log[i][1]);
477
478assertEquals(["ownKeys", target], log[0]);
479assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]);
480assertEquals(["get", target, "baz", proxy], log[2]);
481assertEquals(["defineProperty", target, "baz",
482 {value: 5, configurable: true, writable: true, enumerable: true}], log[3]);
483
484
485// Reviver plants an arraylike proxy into a yet-to-be-visited property
486
487log.length = 0;
488var target = [42];
489var proxy = new Proxy(target, handler);
490assertTrue(Array.isArray(proxy));
491var reviver = function(p, v) {
492 if (p === "0") return undefined;
493 if (p === "foo") this.bar = proxy;
494 return v;
495}
496
497var result = JSON.parse('{"foo":0,"bar":1}', reviver);
498assertEquals({foo: 0, bar: proxy}, result);
499assertSame(result.bar, proxy);
500assertEquals(3, log.length);
501for (var i in log) assertSame(target, log[i][1]);
502
503assertEquals(["get", target, "length", proxy], log[0]);
504assertEquals(["get", target, "0", proxy], log[1]);
505assertEquals(["deleteProperty", target, "0"], log[2]);