OLD | NEW |
| (Empty) |
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 has: function() { | |
133 return true; | |
134 }, | |
135 getOwnPropertyDescriptor: function(target, name) { | |
136 return { enumerable: false }; | |
137 } | |
138 } | |
139 | |
140 var proxy4 = new Proxy({}, handler4); | |
141 testStringify('{}', proxy4); | |
142 testStringify('{"a":{}}', { a: proxy4 }); | |
143 | |
144 | |
145 // Proxy that provides a toJSON function that uses this. | |
146 | |
147 var handler5 = { | |
148 get: function(target, name) { | |
149 if (name == 'z') return 97000; | |
150 return function(key) { return key.charCodeAt(0) + this.z; }; | |
151 }, | |
152 ownKeys: function(target) { | |
153 return ['toJSON', 'z']; | |
154 }, | |
155 has: function() { | |
156 return true; | |
157 }, | |
158 getOwnPropertyDescriptor: function(target, name) { | |
159 return { enumerable: true }; | |
160 } | |
161 } | |
162 | |
163 var proxy5 = new Proxy({}, handler5); | |
164 testStringify('{"a":97097}', { a: proxy5 }); | |
165 | |
166 | |
167 // Proxy that provides a toJSON function that returns undefined. | |
168 | |
169 var handler6 = { | |
170 get: function(target, name) { | |
171 return function(key) { return undefined; }; | |
172 }, | |
173 ownKeys: function(target) { | |
174 return ['toJSON']; | |
175 }, | |
176 has: function() { | |
177 return true; | |
178 }, | |
179 getOwnPropertyDescriptor: function(target, name) { | |
180 return { enumerable: true }; | |
181 } | |
182 } | |
183 | |
184 var proxy6 = new Proxy({}, handler6); | |
185 testStringify('[1,null,true]', [1, proxy6, true]); | |
186 testStringify('{"a":1,"c":true}', {a: 1, b: proxy6, c: true}); | |
187 | |
188 | |
189 // Object containing a proxy that changes the parent's properties. | |
190 | |
191 var handler7 = { | |
192 get: function(target, name) { | |
193 delete parent7.a; | |
194 delete parent7.c; | |
195 parent7.e = "5"; | |
196 return name.toUpperCase(); | |
197 }, | |
198 ownKeys: function() { | |
199 return ['a', 'b', 'c']; | |
200 }, | |
201 getOwnPropertyDescriptor: function() { | |
202 return { enumerable: true, configurable: true }; | |
203 } | |
204 } | |
205 | |
206 var proxy7 = new Proxy({}, handler7); | |
207 var parent7 = { a: "1", b: proxy7, c: "3", d: "4" }; | |
208 assertEquals('{"a":"1","b":{"a":"A","b":"B","c":"C"},"d":"4"}', | |
209 JSON.stringify(parent7)); | |
210 assertEquals('{"b":{"a":"A","b":"B","c":"C"},"d":"4","e":"5"}', | |
211 JSON.stringify(parent7)); | |
212 | |
213 | |
214 // (Proxy handler to log trap calls) | |
215 | |
216 var log = []; | |
217 var logger = {}; | |
218 var handler = new Proxy({}, logger); | |
219 | |
220 logger.get = function(t, trap, r) { | |
221 return function() { | |
222 log.push([trap, ...arguments]); | |
223 return Reflect[trap](...arguments); | |
224 } | |
225 }; | |
226 | |
227 | |
228 // Object is a callable proxy | |
229 | |
230 log.length = 0; | |
231 var target = () => 42; | |
232 var proxy = new Proxy(target, handler); | |
233 assertTrue(typeof proxy === 'function'); | |
234 | |
235 assertEquals(undefined, JSON.stringify(proxy)); | |
236 assertEquals(1, log.length) | |
237 for (var i in log) assertSame(target, log[i][1]); | |
238 | |
239 assertEquals(["get", target, "toJSON", proxy], log[0]); | |
240 | |
241 | |
242 // Object is a non-callable non-arraylike proxy | |
243 | |
244 log.length = 0; | |
245 var target = {foo: 42} | |
246 var proxy = new Proxy(target, handler); | |
247 assertFalse(Array.isArray(proxy)); | |
248 | |
249 assertEquals('{"foo":42}', JSON.stringify(proxy)); | |
250 assertEquals(4, log.length) | |
251 for (var i in log) assertSame(target, log[i][1]); | |
252 | |
253 assertEquals( | |
254 ["get", target, "toJSON", proxy], log[0]); | |
255 assertEquals( | |
256 ["ownKeys", target], log[1]); // EnumerableOwnNames | |
257 assertEquals( | |
258 ["getOwnPropertyDescriptor", target, "foo"], log[2]); // EnumerableOwnNames | |
259 assertEquals( | |
260 ["get", target, "foo", proxy], log[3]); | |
261 | |
262 | |
263 // Object is an arraylike proxy | |
264 | |
265 log.length = 0; | |
266 var target = [42]; | |
267 var proxy = new Proxy(target, handler); | |
268 assertTrue(Array.isArray(proxy)); | |
269 | |
270 assertEquals('[42]', JSON.stringify(proxy)); | |
271 assertEquals(3, log.length) | |
272 for (var i in log) assertSame(target, log[i][1]); | |
273 | |
274 assertEquals(["get", target, "toJSON", proxy], log[0]); | |
275 assertEquals(["get", target, "length", proxy], log[1]); | |
276 assertEquals(["get", target, "0", proxy], log[2]); | |
277 | |
278 | |
279 // Replacer is a callable proxy | |
280 | |
281 log.length = 0; | |
282 var object = {0: "foo", 1: 666}; | |
283 var target = (key, val) => key == "1" ? val + 42 : val; | |
284 var proxy = new Proxy(target, handler); | |
285 assertTrue(typeof proxy === 'function'); | |
286 | |
287 assertEquals('{"0":"foo","1":708}', JSON.stringify(object, proxy)); | |
288 assertEquals(3, log.length) | |
289 for (var i in log) assertSame(target, log[i][1]); | |
290 | |
291 assertEquals(4, log[0].length) | |
292 assertEquals("apply", log[0][0]); | |
293 assertEquals("", log[0][3][0]); | |
294 assertEquals({0: "foo", 1: 666}, log[0][3][1]); | |
295 assertEquals(4, log[1].length) | |
296 assertEquals("apply", log[1][0]); | |
297 assertEquals(["0", "foo"], log[1][3]); | |
298 assertEquals(4, log[2].length) | |
299 assertEquals("apply", log[2][0]); | |
300 assertEquals(["1", 666], log[2][3]); | |
301 | |
302 | |
303 // Replacer is an arraylike proxy | |
304 | |
305 log.length = 0; | |
306 var object = {0: "foo", 1: 666}; | |
307 var target = [0]; | |
308 var proxy = new Proxy(target, handler); | |
309 assertTrue(Array.isArray(proxy)); | |
310 | |
311 assertEquals('{"0":"foo"}', JSON.stringify(object, proxy)); | |
312 assertEquals(2, log.length) | |
313 for (var i in log) assertSame(target, log[i][1]); | |
314 | |
315 assertEquals(["get", target, "length", proxy], log[0]); | |
316 assertEquals(["get", target, "0", proxy], log[1]); | |
317 | |
318 | |
319 // Replacer is an arraylike proxy and object is an array | |
320 | |
321 log.length = 0; | |
322 var object = ["foo", 42]; | |
323 var target = [0]; | |
324 var proxy = new Proxy(target, handler); | |
325 assertTrue(Array.isArray(proxy)); | |
326 | |
327 assertEquals('["foo",42]', JSON.stringify(object, proxy)); | |
328 assertEquals(2, log.length); | |
329 for (var i in log) assertSame(target, log[i][1]); | |
330 | |
331 assertEquals(["get", target, "length", proxy], log[0]); | |
332 assertEquals(["get", target, "0", proxy], log[1]); | |
333 | |
334 | |
335 // Replacer is an arraylike proxy with a non-trivial length | |
336 | |
337 var getTrap = function(t, key) { | |
338 if (key === "length") return {[Symbol.toPrimitive]() {return 42}}; | |
339 if (key === "41") return "foo"; | |
340 if (key === "42") return "bar"; | |
341 }; | |
342 var target = []; | |
343 var proxy = new Proxy(target, {get: getTrap}); | |
344 assertTrue(Array.isArray(proxy)); | |
345 var object = {foo: true, bar: 666}; | |
346 assertEquals('{"foo":true}', JSON.stringify(object, proxy)); | |
347 | |
348 | |
349 // Replacer is an arraylike proxy with a bogus length | |
350 | |
351 var getTrap = function(t, key) { | |
352 if (key === "length") return Symbol(); | |
353 if (key === "41") return "foo"; | |
354 if (key === "42") return "bar"; | |
355 }; | |
356 var target = []; | |
357 var proxy = new Proxy(target, {get: getTrap}); | |
358 assertTrue(Array.isArray(proxy)); | |
359 var object = {foo: true, bar: 666}; | |
360 assertThrows(() => JSON.stringify(object, proxy), TypeError); | |
361 | |
362 | |
363 // Replacer returns a non-callable non-arraylike proxy | |
364 | |
365 log.length = 0; | |
366 var object = ["foo", 42]; | |
367 var target = {baz: 5}; | |
368 var proxy = new Proxy(target, handler); | |
369 var replacer = (key, val) => key === "1" ? proxy : val; | |
370 | |
371 assertEquals('["foo",{"baz":5}]', JSON.stringify(object, replacer)); | |
372 assertEquals(3, log.length); | |
373 for (var i in log) assertSame(target, log[i][1]); | |
374 | |
375 assertEquals(["ownKeys", target], log[0]); | |
376 assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]); | |
377 | |
378 | |
379 // Replacer returns an arraylike proxy | |
380 | |
381 log.length = 0; | |
382 var object = ["foo", 42]; | |
383 var target = ["bar"]; | |
384 var proxy = new Proxy(target, handler); | |
385 var replacer = (key, val) => key === "1" ? proxy : val; | |
386 | |
387 assertEquals('["foo",["bar"]]', JSON.stringify(object, replacer)); | |
388 assertEquals(2, log.length); | |
389 for (var i in log) assertSame(target, log[i][1]); | |
390 | |
391 assertEquals(["get", target, "length", proxy], log[0]); | |
392 assertEquals(["get", target, "0", proxy], log[1]); | |
393 | |
394 | |
395 // Replacer returns an arraylike proxy with a non-trivial length | |
396 | |
397 var getTrap = function(t, key) { | |
398 if (key === "length") return {[Symbol.toPrimitive]() {return 3}}; | |
399 if (key === "2") return "baz"; | |
400 if (key === "3") return "bar"; | |
401 }; | |
402 var target = []; | |
403 var proxy = new Proxy(target, {get: getTrap}); | |
404 var replacer = (key, val) => key === "goo" ? proxy : val; | |
405 var object = {foo: true, goo: false}; | |
406 assertEquals('{"foo":true,"goo":[null,null,"baz"]}', | |
407 JSON.stringify(object, replacer)); | |
408 | |
409 | |
410 // Replacer returns an arraylike proxy with a bogus length | |
411 | |
412 var getTrap = function(t, key) { | |
413 if (key === "length") return Symbol(); | |
414 if (key === "2") return "baz"; | |
415 if (key === "3") return "bar"; | |
416 }; | |
417 var target = []; | |
418 var proxy = new Proxy(target, {get: getTrap}); | |
419 var replacer = (key, val) => key === "goo" ? proxy : val; | |
420 var object = {foo: true, goo: false}; | |
421 assertThrows(() => JSON.stringify(object, replacer), TypeError); | |
422 | |
423 | |
424 // Replacer returns a callable proxy | |
425 | |
426 log.length = 0; | |
427 var target = () => 666; | |
428 var proxy = new Proxy(target, handler); | |
429 var replacer = (key, val) => key === "1" ? proxy : val; | |
430 | |
431 assertEquals('["foo",null]', JSON.stringify(["foo", 42], replacer)); | |
432 assertEquals(0, log.length); | |
433 | |
434 assertEquals('{"0":"foo"}', JSON.stringify({0: "foo", 1: 42}, replacer)); | |
435 assertEquals(0, log.length); | |
436 | |
437 | |
438 | |
439 /////////////////////////////////////////////////////////////////////////////// | |
440 // JSON.parse | |
441 | |
442 | |
443 // Reviver is a callable proxy | |
444 | |
445 log.length = 0; | |
446 var target = () => 42; | |
447 var proxy = new Proxy(target, handler); | |
448 assertTrue(typeof proxy === "function"); | |
449 | |
450 assertEquals(42, JSON.parse("[true, false]", proxy)); | |
451 assertEquals(3, log.length); | |
452 for (var i in log) assertSame(target, log[i][1]); | |
453 | |
454 assertEquals(4, log[0].length); | |
455 assertEquals("apply", log[0][0]); | |
456 assertEquals(["0", true], log[0][3]); | |
457 assertEquals(4, log[1].length); | |
458 assertEquals("apply", log[1][0]); | |
459 assertEquals(["1", false], log[1][3]); | |
460 assertEquals(4, log[2].length); | |
461 assertEquals("apply", log[2][0]); | |
462 assertEquals(["", [42, 42]], log[2][3]); | |
463 | |
464 | |
465 // Reviver plants a non-arraylike proxy into a yet-to-be-visited property | |
466 | |
467 log.length = 0; | |
468 var target = {baz: 42}; | |
469 var proxy = new Proxy(target, handler); | |
470 var reviver = function(p, v) { | |
471 if (p === "baz") return 5; | |
472 if (p === "foo") this.bar = proxy; | |
473 return v; | |
474 } | |
475 | |
476 assertEquals({foo: 0, bar: proxy}, JSON.parse('{"foo":0,"bar":1}', reviver)); | |
477 assertEquals(4, log.length); | |
478 for (var i in log) assertSame(target, log[i][1]); | |
479 | |
480 assertEquals(["ownKeys", target], log[0]); | |
481 assertEquals(["getOwnPropertyDescriptor", target, "baz"], log[1]); | |
482 assertEquals(["get", target, "baz", proxy], log[2]); | |
483 assertEquals(["defineProperty", target, "baz", | |
484 {value: 5, configurable: true, writable: true, enumerable: true}], log[3]); | |
485 | |
486 | |
487 // Reviver plants an arraylike proxy into a yet-to-be-visited property | |
488 | |
489 log.length = 0; | |
490 var target = [42]; | |
491 var proxy = new Proxy(target, handler); | |
492 assertTrue(Array.isArray(proxy)); | |
493 var reviver = function(p, v) { | |
494 if (p === "0") return undefined; | |
495 if (p === "foo") this.bar = proxy; | |
496 return v; | |
497 } | |
498 | |
499 var result = JSON.parse('{"foo":0,"bar":1}', reviver); | |
500 assertEquals({foo: 0, bar: proxy}, result); | |
501 assertSame(result.bar, proxy); | |
502 assertEquals(3, log.length); | |
503 for (var i in log) assertSame(target, log[i][1]); | |
504 | |
505 assertEquals(["get", target, "length", proxy], log[0]); | |
506 assertEquals(["get", target, "0", proxy], log[1]); | |
507 assertEquals(["deleteProperty", target, "0"], log[2]); | |
OLD | NEW |