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