proxies-example-membrane.js revision 592a9fc1d8ea420377a2e7efd0600e20b058be2b
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
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 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
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 = 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
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