Ben Murdoch | 3ef787d | 2012-04-12 10:51:47 +0100 | [diff] [blame^] | 1 | // Copyright 2011 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 |
| 29 | |
| 30 | |
| 31 | // A simple no-op handler. Adapted from: |
| 32 | // http://wiki.ecmascript.org/doku.php?id=harmony:proxies#examplea_no-op_forwarding_proxy |
| 33 | |
| 34 | function createHandler(obj) { |
| 35 | return { |
| 36 | getOwnPropertyDescriptor: function(name) { |
| 37 | var desc = Object.getOwnPropertyDescriptor(obj, name); |
| 38 | if (desc !== undefined) desc.configurable = true; |
| 39 | return desc; |
| 40 | }, |
| 41 | getPropertyDescriptor: function(name) { |
| 42 | var desc = Object.getOwnPropertyDescriptor(obj, name); |
| 43 | //var desc = Object.getPropertyDescriptor(obj, name); // not in ES5 |
| 44 | if (desc !== undefined) desc.configurable = true; |
| 45 | return desc; |
| 46 | }, |
| 47 | getOwnPropertyNames: function() { |
| 48 | return Object.getOwnPropertyNames(obj); |
| 49 | }, |
| 50 | getPropertyNames: function() { |
| 51 | return Object.getOwnPropertyNames(obj); |
| 52 | //return Object.getPropertyNames(obj); // not in ES5 |
| 53 | }, |
| 54 | defineProperty: function(name, desc) { |
| 55 | Object.defineProperty(obj, name, desc); |
| 56 | }, |
| 57 | delete: function(name) { |
| 58 | return delete obj[name]; |
| 59 | }, |
| 60 | fix: function() { |
| 61 | if (Object.isFrozen(obj)) { |
| 62 | var result = {}; |
| 63 | Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 64 | result[name] = Object.getOwnPropertyDescriptor(obj, name); |
| 65 | }); |
| 66 | return result; |
| 67 | } |
| 68 | // As long as obj is not frozen, the proxy won't allow itself to be fixed |
| 69 | return undefined; // will cause a TypeError to be thrown |
| 70 | }, |
| 71 | has: function(name) { return name in obj; }, |
| 72 | hasOwn: function(name) { return ({}).hasOwnProperty.call(obj, name); }, |
| 73 | get: function(receiver, name) { return obj[name]; }, |
| 74 | set: function(receiver, name, val) { |
| 75 | obj[name] = val; // bad behavior when set fails in non-strict mode |
| 76 | return true; |
| 77 | }, |
| 78 | enumerate: function() { |
| 79 | var result = []; |
| 80 | for (var name in obj) { result.push(name); }; |
| 81 | return result; |
| 82 | }, |
| 83 | keys: function() { return Object.keys(obj); } |
| 84 | }; |
| 85 | } |
| 86 | |
| 87 | |
| 88 | |
| 89 | // Auxiliary definitions enabling tracking of object identity in output. |
| 90 | |
| 91 | var objectMap = new WeakMap; |
| 92 | var objectCounter = 0; |
| 93 | |
| 94 | function registerObject(x, s) { |
| 95 | if (x === Object(x) && !objectMap.has(x)) |
| 96 | objectMap.set(x, ++objectCounter + (s == undefined ? "" : ":" + s)); |
| 97 | } |
| 98 | |
| 99 | registerObject(this, "global"); |
| 100 | registerObject(Object.prototype, "Object.prototype"); |
| 101 | |
| 102 | function str(x) { |
| 103 | if (x === Object(x)) return "[" + typeof x + " " + objectMap.get(x) + "]"; |
| 104 | if (typeof x == "string") return "\"" + x + "\""; |
| 105 | return "" + x; |
| 106 | } |
| 107 | |
| 108 | |
| 109 | |
| 110 | // A simple membrane. Adapted from: |
| 111 | // http://wiki.ecmascript.org/doku.php?id=harmony:proxies#a_simple_membrane |
| 112 | |
| 113 | function createSimpleMembrane(target) { |
| 114 | var enabled = true; |
| 115 | |
| 116 | function wrap(obj) { |
| 117 | registerObject(obj); |
| 118 | print("wrap enter", str(obj)); |
| 119 | try { |
| 120 | var x = wrap2(obj); |
| 121 | registerObject(x, "wrapped"); |
| 122 | print("wrap exit", str(obj), "as", str(x)); |
| 123 | return x; |
| 124 | } catch(e) { |
| 125 | print("wrap exception", str(e)); |
| 126 | throw e; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | function wrap2(obj) { |
| 131 | if (obj !== Object(obj)) { |
| 132 | return obj; |
| 133 | } |
| 134 | |
| 135 | function wrapCall(fun, that, args) { |
| 136 | registerObject(that); |
| 137 | print("wrapCall enter", fun, str(that)); |
| 138 | try { |
| 139 | var x = wrapCall2(fun, that, args); |
| 140 | print("wrapCall exit", fun, str(that), "returning", str(x)); |
| 141 | return x; |
| 142 | } catch(e) { |
| 143 | print("wrapCall exception", fun, str(that), str(e)); |
| 144 | throw e; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | function wrapCall2(fun, that, args) { |
| 149 | if (!enabled) { throw new Error("disabled"); } |
| 150 | try { |
| 151 | return wrap(fun.apply(that, Array.prototype.map.call(args, wrap))); |
| 152 | } catch (e) { |
| 153 | throw wrap(e); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | var baseHandler = createHandler(obj); |
| 158 | var handler = Proxy.create(Object.freeze({ |
| 159 | get: function(receiver, name) { |
| 160 | return function() { |
| 161 | var arg = (name === "get" || name == "set") ? arguments[1] : ""; |
| 162 | print("handler enter", name, arg); |
| 163 | var x = wrapCall(baseHandler[name], baseHandler, arguments); |
| 164 | print("handler exit", name, arg, "returning", str(x)); |
| 165 | return x; |
| 166 | } |
| 167 | } |
| 168 | })); |
| 169 | registerObject(baseHandler, "basehandler"); |
| 170 | registerObject(handler, "handler"); |
| 171 | |
| 172 | if (typeof obj === "function") { |
| 173 | function callTrap() { |
| 174 | print("call trap enter", str(obj), str(this)); |
| 175 | var x = wrapCall(obj, wrap(this), arguments); |
| 176 | print("call trap exit", str(obj), str(this), "returning", str(x)); |
| 177 | return x; |
| 178 | } |
| 179 | function constructTrap() { |
| 180 | if (!enabled) { throw new Error("disabled"); } |
| 181 | try { |
| 182 | function forward(args) { return obj.apply(this, args) } |
| 183 | return wrap(new forward(Array.prototype.map.call(arguments, wrap))); |
| 184 | } catch (e) { |
| 185 | throw wrap(e); |
| 186 | } |
| 187 | } |
| 188 | return Proxy.createFunction(handler, callTrap, constructTrap); |
| 189 | } else { |
| 190 | var prototype = wrap(Object.getPrototypeOf(obj)); |
| 191 | return Proxy.create(handler, prototype); |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | var gate = Object.freeze({ |
| 196 | enable: function() { enabled = true; }, |
| 197 | disable: function() { enabled = false; } |
| 198 | }); |
| 199 | |
| 200 | return Object.freeze({ |
| 201 | wrapper: wrap(target), |
| 202 | gate: gate |
| 203 | }); |
| 204 | } |
| 205 | |
| 206 | |
| 207 | var o = { |
| 208 | a: 6, |
| 209 | b: {bb: 8}, |
| 210 | f: function(x) { return x }, |
| 211 | g: function(x) { return x.a }, |
| 212 | h: function(x) { this.q = x } |
| 213 | }; |
| 214 | o[2] = {c: 7}; |
| 215 | var m = createSimpleMembrane(o); |
| 216 | var w = m.wrapper; |
| 217 | print("o =", str(o)) |
| 218 | print("w =", str(w)); |
| 219 | |
| 220 | var f = w.f; |
| 221 | var x = f(66); |
| 222 | var x = f({a: 1}); |
| 223 | var x = w.f({a: 1}); |
| 224 | var a = x.a; |
| 225 | assertEquals(6, w.a); |
| 226 | assertEquals(8, w.b.bb); |
| 227 | assertEquals(7, w[2]["c"]); |
| 228 | assertEquals(undefined, w.c); |
| 229 | assertEquals(1, w.f(1)); |
| 230 | assertEquals(1, w.f({a: 1}).a); |
| 231 | assertEquals(2, w.g({a: 2})); |
| 232 | assertEquals(3, (w.r = {a: 3}).a); |
| 233 | assertEquals(3, w.r.a); |
| 234 | assertEquals(3, o.r.a); |
| 235 | w.h(3); |
| 236 | assertEquals(3, w.q); |
| 237 | assertEquals(3, o.q); |
| 238 | assertEquals(4, (new w.h(4)).q); |
| 239 | |
| 240 | var wb = w.b; |
| 241 | var wr = w.r; |
| 242 | var wf = w.f; |
| 243 | var wf3 = w.f(3); |
| 244 | var wfx = w.f({a: 6}); |
| 245 | var wgx = w.g({a: {aa: 7}}); |
| 246 | var wh4 = new w.h(4); |
| 247 | m.gate.disable(); |
| 248 | assertEquals(3, wf3); |
| 249 | assertThrows(function() { w.a }, Error); |
| 250 | assertThrows(function() { w.r }, Error); |
| 251 | assertThrows(function() { w.r = {a: 4} }, Error); |
| 252 | assertThrows(function() { o.r.a }, Error); |
| 253 | assertEquals("object", typeof o.r); |
| 254 | assertEquals(5, (o.r = {a: 5}).a); |
| 255 | assertEquals(5, o.r.a); |
| 256 | assertThrows(function() { w[1] }, Error); |
| 257 | assertThrows(function() { w.c }, Error); |
| 258 | assertThrows(function() { wb.bb }, Error); |
| 259 | assertThrows(function() { wr.a }, Error); |
| 260 | assertThrows(function() { wf(4) }, Error); |
| 261 | assertThrows(function() { wfx.a }, Error); |
| 262 | assertThrows(function() { wgx.aa }, Error); |
| 263 | assertThrows(function() { wh4.q }, Error); |
| 264 | |
| 265 | m.gate.enable(); |
| 266 | assertEquals(6, w.a); |
| 267 | assertEquals(5, w.r.a); |
| 268 | assertEquals(5, o.r.a); |
| 269 | assertEquals(7, w.r = 7); |
| 270 | assertEquals(7, w.r); |
| 271 | assertEquals(7, o.r); |
| 272 | assertEquals(8, w.b.bb); |
| 273 | assertEquals(7, w[2]["c"]); |
| 274 | assertEquals(undefined, w.c); |
| 275 | assertEquals(8, wb.bb); |
| 276 | assertEquals(3, wr.a); |
| 277 | assertEquals(4, wf(4)); |
| 278 | assertEquals(3, wf3); |
| 279 | assertEquals(6, wfx.a); |
| 280 | assertEquals(7, wgx.aa); |
| 281 | assertEquals(4, wh4.q); |
| 282 | |
| 283 | |
| 284 | // An identity-preserving membrane. Adapted from: |
| 285 | // http://wiki.ecmascript.org/doku.php?id=harmony:proxies#an_identity-preserving_membrane |
| 286 | |
| 287 | function createMembrane(wetTarget) { |
| 288 | var wet2dry = WeakMap(); |
| 289 | var dry2wet = WeakMap(); |
| 290 | |
| 291 | function asDry(obj) { |
| 292 | registerObject(obj) |
| 293 | print("asDry enter", str(obj)) |
| 294 | try { |
| 295 | var x = asDry2(obj); |
| 296 | registerObject(x, "dry"); |
| 297 | print("asDry exit", str(obj), "as", str(x)); |
| 298 | return x; |
| 299 | } catch(e) { |
| 300 | print("asDry exception", str(e)); |
| 301 | throw e; |
| 302 | } |
| 303 | } |
| 304 | function asDry2(wet) { |
| 305 | if (wet !== Object(wet)) { |
| 306 | // primitives provide only irrevocable knowledge, so don't |
| 307 | // bother wrapping it. |
| 308 | return wet; |
| 309 | } |
| 310 | var dryResult = wet2dry.get(wet); |
| 311 | if (dryResult) { return dryResult; } |
| 312 | |
| 313 | var wetHandler = createHandler(wet); |
| 314 | var dryRevokeHandler = Proxy.create(Object.freeze({ |
| 315 | get: function(receiver, name) { |
| 316 | return function() { |
| 317 | var arg = (name === "get" || name == "set") ? arguments[1] : ""; |
| 318 | print("dry handler enter", name, arg); |
| 319 | var optWetHandler = dry2wet.get(dryRevokeHandler); |
| 320 | try { |
| 321 | var x = asDry(optWetHandler[name].apply( |
| 322 | optWetHandler, Array.prototype.map.call(arguments, asWet))); |
| 323 | print("dry handler exit", name, arg, "returning", str(x)); |
| 324 | return x; |
| 325 | } catch (eWet) { |
| 326 | var x = asDry(eWet); |
| 327 | print("dry handler exception", name, arg, "throwing", str(x)); |
| 328 | throw x; |
| 329 | } |
| 330 | }; |
| 331 | } |
| 332 | })); |
| 333 | dry2wet.set(dryRevokeHandler, wetHandler); |
| 334 | |
| 335 | if (typeof wet === "function") { |
| 336 | function callTrap() { |
| 337 | print("dry call trap enter", str(this)); |
| 338 | var x = asDry(wet.apply( |
| 339 | asWet(this), Array.prototype.map.call(arguments, asWet))); |
| 340 | print("dry call trap exit", str(this), "returning", str(x)); |
| 341 | return x; |
| 342 | } |
| 343 | function constructTrap() { |
| 344 | function forward(args) { return wet.apply(this, args) } |
| 345 | return asDry(new forward(Array.prototype.map.call(arguments, asWet))); |
| 346 | } |
| 347 | dryResult = |
| 348 | Proxy.createFunction(dryRevokeHandler, callTrap, constructTrap); |
| 349 | } else { |
| 350 | dryResult = |
| 351 | Proxy.create(dryRevokeHandler, asDry(Object.getPrototypeOf(wet))); |
| 352 | } |
| 353 | wet2dry.set(wet, dryResult); |
| 354 | dry2wet.set(dryResult, wet); |
| 355 | return dryResult; |
| 356 | } |
| 357 | |
| 358 | function asWet(obj) { |
| 359 | registerObject(obj) |
| 360 | print("asWet enter", str(obj)) |
| 361 | try { |
| 362 | var x = asWet2(obj) |
| 363 | registerObject(x, "wet") |
| 364 | print("asWet exit", str(obj), "as", str(x)) |
| 365 | return x |
| 366 | } catch(e) { |
| 367 | print("asWet exception", str(e)) |
| 368 | throw e |
| 369 | } |
| 370 | } |
| 371 | function asWet2(dry) { |
| 372 | if (dry !== Object(dry)) { |
| 373 | // primitives provide only irrevocable knowledge, so don't |
| 374 | // bother wrapping it. |
| 375 | return dry; |
| 376 | } |
| 377 | var wetResult = dry2wet.get(dry); |
| 378 | if (wetResult) { return wetResult; } |
| 379 | |
| 380 | var dryHandler = createHandler(dry); |
| 381 | var wetRevokeHandler = Proxy.create(Object.freeze({ |
| 382 | get: function(receiver, name) { |
| 383 | return function() { |
| 384 | var arg = (name === "get" || name == "set") ? arguments[1] : ""; |
| 385 | print("wet handler enter", name, arg); |
| 386 | var optDryHandler = wet2dry.get(wetRevokeHandler); |
| 387 | try { |
| 388 | var x = asWet(optDryHandler[name].apply( |
| 389 | optDryHandler, Array.prototype.map.call(arguments, asDry))); |
| 390 | print("wet handler exit", name, arg, "returning", str(x)); |
| 391 | return x; |
| 392 | } catch (eDry) { |
| 393 | var x = asWet(eDry); |
| 394 | print("wet handler exception", name, arg, "throwing", str(x)); |
| 395 | throw x; |
| 396 | } |
| 397 | }; |
| 398 | } |
| 399 | })); |
| 400 | wet2dry.set(wetRevokeHandler, dryHandler); |
| 401 | |
| 402 | if (typeof dry === "function") { |
| 403 | function callTrap() { |
| 404 | print("wet call trap enter", str(this)); |
| 405 | var x = asWet(dry.apply( |
| 406 | asDry(this), Array.prototype.map.call(arguments, asDry))); |
| 407 | print("wet call trap exit", str(this), "returning", str(x)); |
| 408 | return x; |
| 409 | } |
| 410 | function constructTrap() { |
| 411 | function forward(args) { return dry.apply(this, args) } |
| 412 | return asWet(new forward(Array.prototype.map.call(arguments, asDry))); |
| 413 | } |
| 414 | wetResult = |
| 415 | Proxy.createFunction(wetRevokeHandler, callTrap, constructTrap); |
| 416 | } else { |
| 417 | wetResult = |
| 418 | Proxy.create(wetRevokeHandler, asWet(Object.getPrototypeOf(dry))); |
| 419 | } |
| 420 | dry2wet.set(dry, wetResult); |
| 421 | wet2dry.set(wetResult, dry); |
| 422 | return wetResult; |
| 423 | } |
| 424 | |
| 425 | var gate = Object.freeze({ |
| 426 | revoke: function() { |
| 427 | dry2wet = wet2dry = Object.freeze({ |
| 428 | get: function(key) { throw new Error("revoked"); }, |
| 429 | set: function(key, val) { throw new Error("revoked"); } |
| 430 | }); |
| 431 | } |
| 432 | }); |
| 433 | |
| 434 | return Object.freeze({ wrapper: asDry(wetTarget), gate: gate }); |
| 435 | } |
| 436 | |
| 437 | |
| 438 | var receiver |
| 439 | var argument |
| 440 | var o = { |
| 441 | a: 6, |
| 442 | b: {bb: 8}, |
| 443 | f: function(x) { receiver = this; argument = x; return x }, |
| 444 | g: function(x) { receiver = this; argument = x; return x.a }, |
| 445 | h: function(x) { receiver = this; argument = x; this.q = x }, |
| 446 | s: function(x) { receiver = this; argument = x; this.x = {y: x}; return this } |
| 447 | } |
| 448 | o[2] = {c: 7} |
| 449 | var m = createMembrane(o) |
| 450 | var w = m.wrapper |
| 451 | print("o =", str(o)) |
| 452 | print("w =", str(w)) |
| 453 | |
| 454 | var f = w.f |
| 455 | var x = f(66) |
| 456 | var x = f({a: 1}) |
| 457 | var x = w.f({a: 1}) |
| 458 | var a = x.a |
| 459 | assertEquals(6, w.a) |
| 460 | assertEquals(8, w.b.bb) |
| 461 | assertEquals(7, w[2]["c"]) |
| 462 | assertEquals(undefined, w.c) |
| 463 | assertEquals(1, w.f(1)) |
| 464 | assertSame(o, receiver) |
| 465 | assertEquals(1, w.f({a: 1}).a) |
| 466 | assertSame(o, receiver) |
| 467 | assertEquals(2, w.g({a: 2})) |
| 468 | assertSame(o, receiver) |
| 469 | assertSame(w, w.f(w)) |
| 470 | assertSame(o, receiver) |
| 471 | assertSame(o, argument) |
| 472 | assertSame(o, w.f(o)) |
| 473 | assertSame(o, receiver) |
| 474 | // Note that argument !== o, since o isn't dry, so gets wrapped wet again. |
| 475 | assertEquals(3, (w.r = {a: 3}).a) |
| 476 | assertEquals(3, w.r.a) |
| 477 | assertEquals(3, o.r.a) |
| 478 | w.h(3) |
| 479 | assertEquals(3, w.q) |
| 480 | assertEquals(3, o.q) |
| 481 | assertEquals(4, (new w.h(4)).q) |
| 482 | assertEquals(5, w.s(5).x.y) |
| 483 | assertSame(o, receiver) |
| 484 | |
| 485 | var wb = w.b |
| 486 | var wr = w.r |
| 487 | var wf = w.f |
| 488 | var wf3 = w.f(3) |
| 489 | var wfx = w.f({a: 6}) |
| 490 | var wgx = w.g({a: {aa: 7}}) |
| 491 | var wh4 = new w.h(4) |
| 492 | var ws5 = w.s(5) |
| 493 | var ws5x = ws5.x |
| 494 | m.gate.revoke() |
| 495 | assertEquals(3, wf3) |
| 496 | assertThrows(function() { w.a }, Error) |
| 497 | assertThrows(function() { w.r }, Error) |
| 498 | assertThrows(function() { w.r = {a: 4} }, Error) |
| 499 | assertThrows(function() { o.r.a }, Error) |
| 500 | assertEquals("object", typeof o.r) |
| 501 | assertEquals(5, (o.r = {a: 5}).a) |
| 502 | assertEquals(5, o.r.a) |
| 503 | assertThrows(function() { w[1] }, Error) |
| 504 | assertThrows(function() { w.c }, Error) |
| 505 | assertThrows(function() { wb.bb }, Error) |
| 506 | assertEquals(3, wr.a) |
| 507 | assertThrows(function() { wf(4) }, Error) |
| 508 | assertEquals(6, wfx.a) |
| 509 | assertEquals(7, wgx.aa) |
| 510 | assertThrows(function() { wh4.q }, Error) |
| 511 | assertThrows(function() { ws5.x }, Error) |
| 512 | assertThrows(function() { ws5x.y }, Error) |