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 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 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 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 = new Proxy({}, 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 new Proxy(prototype, handler); 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 = 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 = new Proxy({}, 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 new Proxy(asDry(Object.getPrototypeOf(wet)), dryRevokeHandler); 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 = new Proxy({}, 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 new Proxy(asWet(Object.getPrototypeOf(dry)), wetRevokeHandler); 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) 513