proxies-example-membrane.js revision b8a8cc1952d61a2f3a2568848933943a543b5d3e
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 --harmony-proxies 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 34function 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 sloppy 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 91var objectMap = new WeakMap; 92var objectCounter = 0; 93 94function registerObject(x, s) { 95 if (x === Object(x) && !objectMap.has(x)) 96 objectMap.set(x, ++objectCounter + (s == undefined ? "" : ":" + s)); 97} 98 99registerObject(this, "global"); 100registerObject(Object.prototype, "Object.prototype"); 101 102function 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 113function 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 207var 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}; 214o[2] = {c: 7}; 215var m = createSimpleMembrane(o); 216var w = m.wrapper; 217print("o =", str(o)) 218print("w =", str(w)); 219 220var f = w.f; 221var x = f(66); 222var x = f({a: 1}); 223var x = w.f({a: 1}); 224var a = x.a; 225assertEquals(6, w.a); 226assertEquals(8, w.b.bb); 227assertEquals(7, w[2]["c"]); 228assertEquals(undefined, w.c); 229assertEquals(1, w.f(1)); 230assertEquals(1, w.f({a: 1}).a); 231assertEquals(2, w.g({a: 2})); 232assertEquals(3, (w.r = {a: 3}).a); 233assertEquals(3, w.r.a); 234assertEquals(3, o.r.a); 235w.h(3); 236assertEquals(3, w.q); 237assertEquals(3, o.q); 238assertEquals(4, (new w.h(4)).q); 239 240var wb = w.b; 241var wr = w.r; 242var wf = w.f; 243var wf3 = w.f(3); 244var wfx = w.f({a: 6}); 245var wgx = w.g({a: {aa: 7}}); 246var wh4 = new w.h(4); 247m.gate.disable(); 248assertEquals(3, wf3); 249assertThrows(function() { w.a }, Error); 250assertThrows(function() { w.r }, Error); 251assertThrows(function() { w.r = {a: 4} }, Error); 252assertThrows(function() { o.r.a }, Error); 253assertEquals("object", typeof o.r); 254assertEquals(5, (o.r = {a: 5}).a); 255assertEquals(5, o.r.a); 256assertThrows(function() { w[1] }, Error); 257assertThrows(function() { w.c }, Error); 258assertThrows(function() { wb.bb }, Error); 259assertThrows(function() { wr.a }, Error); 260assertThrows(function() { wf(4) }, Error); 261assertThrows(function() { wfx.a }, Error); 262assertThrows(function() { wgx.aa }, Error); 263assertThrows(function() { wh4.q }, Error); 264 265m.gate.enable(); 266assertEquals(6, w.a); 267assertEquals(5, w.r.a); 268assertEquals(5, o.r.a); 269assertEquals(7, w.r = 7); 270assertEquals(7, w.r); 271assertEquals(7, o.r); 272assertEquals(8, w.b.bb); 273assertEquals(7, w[2]["c"]); 274assertEquals(undefined, w.c); 275assertEquals(8, wb.bb); 276assertEquals(3, wr.a); 277assertEquals(4, wf(4)); 278assertEquals(3, wf3); 279assertEquals(6, wfx.a); 280assertEquals(7, wgx.aa); 281assertEquals(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 287function createMembrane(wetTarget) { 288 var wet2dry = new WeakMap(); 289 var dry2wet = new 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 438var receiver 439var argument 440var 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} 448o[2] = {c: 7} 449var m = createMembrane(o) 450var w = m.wrapper 451print("o =", str(o)) 452print("w =", str(w)) 453 454var f = w.f 455var x = f(66) 456var x = f({a: 1}) 457var x = w.f({a: 1}) 458var a = x.a 459assertEquals(6, w.a) 460assertEquals(8, w.b.bb) 461assertEquals(7, w[2]["c"]) 462assertEquals(undefined, w.c) 463assertEquals(1, w.f(1)) 464assertSame(o, receiver) 465assertEquals(1, w.f({a: 1}).a) 466assertSame(o, receiver) 467assertEquals(2, w.g({a: 2})) 468assertSame(o, receiver) 469assertSame(w, w.f(w)) 470assertSame(o, receiver) 471assertSame(o, argument) 472assertSame(o, w.f(o)) 473assertSame(o, receiver) 474// Note that argument !== o, since o isn't dry, so gets wrapped wet again. 475assertEquals(3, (w.r = {a: 3}).a) 476assertEquals(3, w.r.a) 477assertEquals(3, o.r.a) 478w.h(3) 479assertEquals(3, w.q) 480assertEquals(3, o.q) 481assertEquals(4, (new w.h(4)).q) 482assertEquals(5, w.s(5).x.y) 483assertSame(o, receiver) 484 485var wb = w.b 486var wr = w.r 487var wf = w.f 488var wf3 = w.f(3) 489var wfx = w.f({a: 6}) 490var wgx = w.g({a: {aa: 7}}) 491var wh4 = new w.h(4) 492var ws5 = w.s(5) 493var ws5x = ws5.x 494m.gate.revoke() 495assertEquals(3, wf3) 496assertThrows(function() { w.a }, Error) 497assertThrows(function() { w.r }, Error) 498assertThrows(function() { w.r = {a: 4} }, Error) 499assertThrows(function() { o.r.a }, Error) 500assertEquals("object", typeof o.r) 501assertEquals(5, (o.r = {a: 5}).a) 502assertEquals(5, o.r.a) 503assertThrows(function() { w[1] }, Error) 504assertThrows(function() { w.c }, Error) 505assertThrows(function() { wb.bb }, Error) 506assertEquals(3, wr.a) 507assertThrows(function() { wf(4) }, Error) 508assertEquals(6, wfx.a) 509assertEquals(7, wgx.aa) 510assertThrows(function() { wh4.q }, Error) 511assertThrows(function() { ws5.x }, Error) 512assertThrows(function() { ws5x.y }, Error) 513