blob: 19a13298df3cc8e01b872813a02ac56cba77b599 [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// Flags: --harmony-proxies --harmony-reflect
29
30
31
32///////////////////////////////////////////////////////////////////////////////
33// JSON.stringify
34
Ben Murdochb8a8cc12014-11-26 15:28:44 +000035
36function 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
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000043
44// Test serializing a proxy, a function proxy, and objects that contain them.
45
Ben Murdochb8a8cc12014-11-26 15:28:44 +000046var handler1 = {
47 get: function(target, name) {
48 return name.toUpperCase();
49 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000050 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +000051 return ['a', 'b', 'c'];
52 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000053 getOwnPropertyDescriptor: function() {
54 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000055 }
56}
57
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000058var proxy1 = new Proxy({}, handler1);
Ben Murdochb8a8cc12014-11-26 15:28:44 +000059testStringify('{"a":"A","b":"B","c":"C"}', proxy1);
60
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000061var proxy_fun = new Proxy(() => {}, handler1);
62assertTrue(typeof(proxy_fun) === 'function');
63testStringify(undefined, proxy_fun);
64testStringify('[1,null]', [1, proxy_fun]);
65
66handler1.apply = function() { return 666; };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000067testStringify(undefined, proxy_fun);
68testStringify('[1,null]', [1, proxy_fun]);
69
70var parent1a = { b: proxy1 };
71testStringify('{"b":{"a":"A","b":"B","c":"C"}}', parent1a);
72
73var parent1b = { a: 123, b: proxy1, c: true };
74testStringify('{"a":123,"b":{"a":"A","b":"B","c":"C"},"c":true}', parent1b);
75
76var parent1c = [123, proxy1, true];
77testStringify('[123,{"a":"A","b":"B","c":"C"},true]', parent1c);
78
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000079
Ben Murdochb8a8cc12014-11-26 15:28:44 +000080// Proxy with side effect.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000081
Ben Murdochb8a8cc12014-11-26 15:28:44 +000082var handler2 = {
83 get: function(target, name) {
84 delete parent2.c;
85 return name.toUpperCase();
86 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000087 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +000088 return ['a', 'b', 'c'];
89 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000090 getOwnPropertyDescriptor: function() {
91 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +000092 }
93}
94
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000095var proxy2 = new Proxy({}, handler2);
Ben Murdochb8a8cc12014-11-26 15:28:44 +000096var parent2 = { a: "delete", b: proxy2, c: "remove" };
97var expected2 = '{"a":"delete","b":{"a":"A","b":"B","c":"C"}}';
98assertEquals(expected2, JSON.stringify(parent2));
99parent2.c = "remove"; // Revert side effect.
100assertEquals(expected2, JSON.stringify(parent2, undefined, 0));
101
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000102
103// Proxy with a get function that uses the receiver argument.
104
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000105var handler3 = {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000106 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 + ")";
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000111 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000112 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000113 return ['a', 'b', 'c'];
114 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000115 getOwnPropertyDescriptor: function() {
116 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000117 }
118}
119
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000120var proxy3 = new Proxy({}, handler3);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000121var parent3 = { x: 123, y: proxy3 }
122testStringify('{"x":123,"y":{"a":"a(proxy)","b":"b(proxy)","c":"c(proxy)"}}',
123 parent3);
124
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000125
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000126// Empty proxy.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000127
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000128var handler4 = {
129 get: function(target, name) {
130 return 0;
131 },
132 enumerate: function(target) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000133 return [][Symbol.iterator]();
134 },
135 has: function() {
136 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000137 },
138 getOwnPropertyDescriptor: function(target, name) {
139 return { enumerable: false };
140 }
141}
142
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000143var proxy4 = new Proxy({}, handler4);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000144testStringify('{}', proxy4);
145testStringify('{"a":{}}', { a: proxy4 });
146
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000147
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000148// Proxy that provides a toJSON function that uses this.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000149
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000150var 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) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000156 return ['toJSON', 'z'][Symbol.iterator]();
157 },
158 has: function() {
159 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000160 },
161 getOwnPropertyDescriptor: function(target, name) {
162 return { enumerable: true };
163 }
164}
165
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000166var proxy5 = new Proxy({}, handler5);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000167testStringify('{"a":97097}', { a: proxy5 });
168
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000169
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000170// Proxy that provides a toJSON function that returns undefined.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000171
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000172var handler6 = {
173 get: function(target, name) {
174 return function(key) { return undefined; };
175 },
176 enumerate: function(target) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000177 return ['toJSON'][Symbol.iterator]();
178 },
179 has: function() {
180 return true;
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000181 },
182 getOwnPropertyDescriptor: function(target, name) {
183 return { enumerable: true };
184 }
185}
186
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000187var proxy6 = new Proxy({}, handler6);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000188testStringify('[1,null,true]', [1, proxy6, true]);
189testStringify('{"a":1,"c":true}', {a: 1, b: proxy6, c: true});
190
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000191
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000192// Object containing a proxy that changes the parent's properties.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000193
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000194var handler7 = {
195 get: function(target, name) {
196 delete parent7.a;
197 delete parent7.c;
198 parent7.e = "5";
199 return name.toUpperCase();
200 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000201 ownKeys: function() {
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000202 return ['a', 'b', 'c'];
203 },
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000204 getOwnPropertyDescriptor: function() {
205 return { enumerable: true, configurable: true };
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000206 }
207}
208
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000209var proxy7 = new Proxy({}, handler7);
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000210var parent7 = { a: "1", b: proxy7, c: "3", d: "4" };
211assertEquals('{"a":"1","b":{"a":"A","b":"B","c":"C"},"d":"4"}',
212 JSON.stringify(parent7));
213assertEquals('{"b":{"a":"A","b":"B","c":"C"},"d":"4","e":"5"}',
214 JSON.stringify(parent7));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000215
216
217// (Proxy handler to log trap calls)
218
219var log = [];
220var logger = {};
221var handler = new Proxy({}, logger);
222
223logger.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
233log.length = 0;
234var target = () => 42;
235var proxy = new Proxy(target, handler);
236assertTrue(typeof proxy === 'function');
237
238assertEquals(undefined, JSON.stringify(proxy));
239assertEquals(1, log.length)
240for (var i in log) assertSame(target, log[i][1]);
241
242assertEquals(["get", target, "toJSON", proxy], log[0]);
243
244
245// Object is a non-callable non-arraylike proxy
246
247log.length = 0;
248var target = {foo: 42}
249var proxy = new Proxy(target, handler);
250assertFalse(Array.isArray(proxy));
251
252assertEquals('{"foo":42}', JSON.stringify(proxy));
253assertEquals(4, log.length)
254for (var i in log) assertSame(target, log[i][1]);
255
256assertEquals(
257 ["get", target, "toJSON", proxy], log[0]);
258assertEquals(
259 ["ownKeys", target], log[1]); // EnumerableOwnNames
260assertEquals(
261 ["getOwnPropertyDescriptor", target, "foo"], log[2]); // EnumerableOwnNames
262assertEquals(
263 ["get", target, "foo", proxy], log[3]);
264
265
266// Object is an arraylike proxy
267
268log.length = 0;
269var target = [42];
270var proxy = new Proxy(target, handler);
271assertTrue(Array.isArray(proxy));
272
273assertEquals('[42]', JSON.stringify(proxy));
274assertEquals(3, log.length)
275for (var i in log) assertSame(target, log[i][1]);
276
277assertEquals(["get", target, "toJSON", proxy], log[0]);
278assertEquals(["get", target, "length", proxy], log[1]);
279assertEquals(["get", target, "0", proxy], log[2]);
280
281
282// Replacer is a callable proxy
283
284log.length = 0;
285var object = {0: "foo", 1: 666};
286var target = (key, val) => key == "1" ? val + 42 : val;
287var proxy = new Proxy(target, handler);
288assertTrue(typeof proxy === 'function');
289
290assertEquals('{"0":"foo","1":708}', JSON.stringify(object, proxy));
291assertEquals(3, log.length)
292for (var i in log) assertSame(target, log[i][1]);
293
294assertEquals(4, log[0].length)
295assertEquals("apply", log[0][0]);
296assertEquals("", log[0][3][0]);
297assertEquals({0: "foo", 1: 666}, log[0][3][1]);
298assertEquals(4, log[1].length)
299assertEquals("apply", log[1][0]);
300assertEquals(["0", "foo"], log[1][3]);
301assertEquals(4, log[2].length)
302assertEquals("apply", log[2][0]);
303assertEquals(["1", 666], log[2][3]);
304
305
306// Replacer is an arraylike proxy
307
308log.length = 0;
309var object = {0: "foo", 1: 666};
310var target = [0];
311var proxy = new Proxy(target, handler);
312assertTrue(Array.isArray(proxy));
313
314assertEquals('{"0":"foo"}', JSON.stringify(object, proxy));
315assertEquals(2, log.length)
316for (var i in log) assertSame(target, log[i][1]);
317
318assertEquals(["get", target, "length", proxy], log[0]);
319assertEquals(["get", target, "0", proxy], log[1]);
320
321
322// Replacer is an arraylike proxy and object is an array
323
324log.length = 0;
325var object = ["foo", 42];
326var target = [0];
327var proxy = new Proxy(target, handler);
328assertTrue(Array.isArray(proxy));
329
330assertEquals('["foo",42]', JSON.stringify(object, proxy));
331assertEquals(2, log.length);
332for (var i in log) assertSame(target, log[i][1]);
333
334assertEquals(["get", target, "length", proxy], log[0]);
335assertEquals(["get", target, "0", proxy], log[1]);
336
337
338// Replacer is an arraylike proxy with a non-trivial length
339
340var 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};
345var target = [];
346var proxy = new Proxy(target, {get: getTrap});
347assertTrue(Array.isArray(proxy));
348var object = {foo: true, bar: 666};
349assertEquals('{"foo":true}', JSON.stringify(object, proxy));
350
351
352// Replacer is an arraylike proxy with a bogus length
353
354var getTrap = function(t, key) {
355 if (key === "length") return Symbol();
356 if (key === "41") return "foo";
357 if (key === "42") return "bar";
358};
359var target = [];
360var proxy = new Proxy(target, {get: getTrap});
361assertTrue(Array.isArray(proxy));
362var object = {foo: true, bar: 666};
363assertThrows(() => JSON.stringify(object, proxy), TypeError);
364
365
366// Replacer returns a non-callable non-arraylike proxy
367
368log.length = 0;
369var object = ["foo", 42];
370var target = {baz: 5};
371var proxy = new Proxy(target, handler);
372var replacer = (key, val) => key === "1" ? proxy : val;
373
374assertEquals('["foo",{"baz":5}]', JSON.stringify(object, replacer));
375assertEquals(3, log.length);
376for (var i in log) assertSame(target, log[i][1]);
377
378assertEquals(["ownKeys", target], log[0]);
379assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]);
380
381
382// Replacer returns an arraylike proxy
383
384log.length = 0;
385var object = ["foo", 42];
386var target = ["bar"];
387var proxy = new Proxy(target, handler);
388var replacer = (key, val) => key === "1" ? proxy : val;
389
390assertEquals('["foo",["bar"]]', JSON.stringify(object, replacer));
391assertEquals(2, log.length);
392for (var i in log) assertSame(target, log[i][1]);
393
394assertEquals(["get", target, "length", proxy], log[0]);
395assertEquals(["get", target, "0", proxy], log[1]);
396
397
398// Replacer returns an arraylike proxy with a non-trivial length
399
400var 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};
405var target = [];
406var proxy = new Proxy(target, {get: getTrap});
407var replacer = (key, val) => key === "goo" ? proxy : val;
408var object = {foo: true, goo: false};
409assertEquals('{"foo":true,"goo":[null,null,"baz"]}',
410 JSON.stringify(object, replacer));
411
412
413// Replacer returns an arraylike proxy with a bogus length
414
415var getTrap = function(t, key) {
416 if (key === "length") return Symbol();
417 if (key === "2") return "baz";
418 if (key === "3") return "bar";
419};
420var target = [];
421var proxy = new Proxy(target, {get: getTrap});
422var replacer = (key, val) => key === "goo" ? proxy : val;
423var object = {foo: true, goo: false};
424assertThrows(() => JSON.stringify(object, replacer), TypeError);
425
426
427// Replacer returns a callable proxy
428
429log.length = 0;
430var target = () => 666;
431var proxy = new Proxy(target, handler);
432var replacer = (key, val) => key === "1" ? proxy : val;
433
434assertEquals('["foo",null]', JSON.stringify(["foo", 42], replacer));
435assertEquals(0, log.length);
436
437assertEquals('{"0":"foo"}', JSON.stringify({0: "foo", 1: 42}, replacer));
438assertEquals(0, log.length);
439
440
441
442///////////////////////////////////////////////////////////////////////////////
443// JSON.parse
444
445
446// Reviver is a callable proxy
447
448log.length = 0;
449var target = () => 42;
450var proxy = new Proxy(target, handler);
451assertTrue(typeof proxy === "function");
452
453assertEquals(42, JSON.parse("[true, false]", proxy));
454assertEquals(3, log.length);
455for (var i in log) assertSame(target, log[i][1]);
456
457assertEquals(4, log[0].length);
458assertEquals("apply", log[0][0]);
459assertEquals(["0", true], log[0][3]);
460assertEquals(4, log[1].length);
461assertEquals("apply", log[1][0]);
462assertEquals(["1", false], log[1][3]);
463assertEquals(4, log[2].length);
464assertEquals("apply", log[2][0]);
465assertEquals(["", [42, 42]], log[2][3]);
466
467
468// Reviver plants a non-arraylike proxy into a yet-to-be-visited property
469
470log.length = 0;
471var target = {baz: 42};
472var proxy = new Proxy(target, handler);
473var reviver = function(p, v) {
474 if (p === "baz") return 5;
475 if (p === "foo") this.bar = proxy;
476 return v;
477}
478
479assertEquals({foo: 0, bar: proxy}, JSON.parse('{"foo":0,"bar":1}', reviver));
480assertEquals(4, log.length);
481for (var i in log) assertSame(target, log[i][1]);
482
483assertEquals(["ownKeys", target], log[0]);
484assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]);
485assertEquals(["get", target, "baz", proxy], log[2]);
486assertEquals(["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
492log.length = 0;
493var target = [42];
494var proxy = new Proxy(target, handler);
495assertTrue(Array.isArray(proxy));
496var reviver = function(p, v) {
497 if (p === "0") return undefined;
498 if (p === "foo") this.bar = proxy;
499 return v;
500}
501
502var result = JSON.parse('{"foo":0,"bar":1}', reviver);
503assertEquals({foo: 0, bar: proxy}, result);
504assertSame(result.bar, proxy);
505assertEquals(3, log.length);
506for (var i in log) assertSame(target, log[i][1]);
507
508assertEquals(["get", target, "length", proxy], log[0]);
509assertEquals(["get", target, "0", proxy], log[1]);
510assertEquals(["deleteProperty", target, "0"], log[2]);