Home | History | Annotate | Download | only in harmony
      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