| 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 |