// Copyright 2011 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Flags: --harmony // A simple no-op handler. Adapted from: // http://wiki.ecmascript.org/doku.php?id=harmony:proxies#examplea_no-op_forwarding_proxy function createHandler(obj) { return { getOwnPropertyDescriptor: function(name) { var desc = Object.getOwnPropertyDescriptor(obj, name); if (desc !== undefined) desc.configurable = true; return desc; }, getPropertyDescriptor: function(name) { var desc = Object.getOwnPropertyDescriptor(obj, name); //var desc = Object.getPropertyDescriptor(obj, name); // not in ES5 if (desc !== undefined) desc.configurable = true; return desc; }, getOwnPropertyNames: function() { return Object.getOwnPropertyNames(obj); }, getPropertyNames: function() { return Object.getOwnPropertyNames(obj); //return Object.getPropertyNames(obj); // not in ES5 }, defineProperty: function(name, desc) { Object.defineProperty(obj, name, desc); }, delete: function(name) { return delete obj[name]; }, fix: function() { if (Object.isFrozen(obj)) { var result = {}; Object.getOwnPropertyNames(obj).forEach(function(name) { result[name] = Object.getOwnPropertyDescriptor(obj, name); }); return result; } // As long as obj is not frozen, the proxy won't allow itself to be fixed return undefined; // will cause a TypeError to be thrown }, has: function(name) { return name in obj; }, hasOwn: function(name) { return ({}).hasOwnProperty.call(obj, name); }, get: function(receiver, name) { return obj[name]; }, set: function(receiver, name, val) { obj[name] = val; // bad behavior when set fails in non-strict mode return true; }, enumerate: function() { var result = []; for (var name in obj) { result.push(name); }; return result; }, keys: function() { return Object.keys(obj); } }; } // Auxiliary definitions enabling tracking of object identity in output. var objectMap = new WeakMap; var objectCounter = 0; function registerObject(x, s) { if (x === Object(x) && !objectMap.has(x)) objectMap.set(x, ++objectCounter + (s == undefined ? "" : ":" + s)); } registerObject(this, "global"); registerObject(Object.prototype, "Object.prototype"); function str(x) { if (x === Object(x)) return "[" + typeof x + " " + objectMap.get(x) + "]"; if (typeof x == "string") return "\"" + x + "\""; return "" + x; } // A simple membrane. Adapted from: // http://wiki.ecmascript.org/doku.php?id=harmony:proxies#a_simple_membrane function createSimpleMembrane(target) { var enabled = true; function wrap(obj) { registerObject(obj); print("wrap enter", str(obj)); try { var x = wrap2(obj); registerObject(x, "wrapped"); print("wrap exit", str(obj), "as", str(x)); return x; } catch(e) { print("wrap exception", str(e)); throw e; } } function wrap2(obj) { if (obj !== Object(obj)) { return obj; } function wrapCall(fun, that, args) { registerObject(that); print("wrapCall enter", fun, str(that)); try { var x = wrapCall2(fun, that, args); print("wrapCall exit", fun, str(that), "returning", str(x)); return x; } catch(e) { print("wrapCall exception", fun, str(that), str(e)); throw e; } } function wrapCall2(fun, that, args) { if (!enabled) { throw new Error("disabled"); } try { return wrap(fun.apply(that, Array.prototype.map.call(args, wrap))); } catch (e) { throw wrap(e); } } var baseHandler = createHandler(obj); var handler = Proxy.create(Object.freeze({ get: function(receiver, name) { return function() { var arg = (name === "get" || name == "set") ? arguments[1] : ""; print("handler enter", name, arg); var x = wrapCall(baseHandler[name], baseHandler, arguments); print("handler exit", name, arg, "returning", str(x)); return x; } } })); registerObject(baseHandler, "basehandler"); registerObject(handler, "handler"); if (typeof obj === "function") { function callTrap() { print("call trap enter", str(obj), str(this)); var x = wrapCall(obj, wrap(this), arguments); print("call trap exit", str(obj), str(this), "returning", str(x)); return x; } function constructTrap() { if (!enabled) { throw new Error("disabled"); } try { function forward(args) { return obj.apply(this, args) } return wrap(new forward(Array.prototype.map.call(arguments, wrap))); } catch (e) { throw wrap(e); } } return Proxy.createFunction(handler, callTrap, constructTrap); } else { var prototype = wrap(Object.getPrototypeOf(obj)); return Proxy.create(handler, prototype); } } var gate = Object.freeze({ enable: function() { enabled = true; }, disable: function() { enabled = false; } }); return Object.freeze({ wrapper: wrap(target), gate: gate }); } var o = { a: 6, b: {bb: 8}, f: function(x) { return x }, g: function(x) { return x.a }, h: function(x) { this.q = x } }; o[2] = {c: 7}; var m = createSimpleMembrane(o); var w = m.wrapper; print("o =", str(o)) print("w =", str(w)); var f = w.f; var x = f(66); var x = f({a: 1}); var x = w.f({a: 1}); var a = x.a; assertEquals(6, w.a); assertEquals(8, w.b.bb); assertEquals(7, w[2]["c"]); assertEquals(undefined, w.c); assertEquals(1, w.f(1)); assertEquals(1, w.f({a: 1}).a); assertEquals(2, w.g({a: 2})); assertEquals(3, (w.r = {a: 3}).a); assertEquals(3, w.r.a); assertEquals(3, o.r.a); w.h(3); assertEquals(3, w.q); assertEquals(3, o.q); assertEquals(4, (new w.h(4)).q); var wb = w.b; var wr = w.r; var wf = w.f; var wf3 = w.f(3); var wfx = w.f({a: 6}); var wgx = w.g({a: {aa: 7}}); var wh4 = new w.h(4); m.gate.disable(); assertEquals(3, wf3); assertThrows(function() { w.a }, Error); assertThrows(function() { w.r }, Error); assertThrows(function() { w.r = {a: 4} }, Error); assertThrows(function() { o.r.a }, Error); assertEquals("object", typeof o.r); assertEquals(5, (o.r = {a: 5}).a); assertEquals(5, o.r.a); assertThrows(function() { w[1] }, Error); assertThrows(function() { w.c }, Error); assertThrows(function() { wb.bb }, Error); assertThrows(function() { wr.a }, Error); assertThrows(function() { wf(4) }, Error); assertThrows(function() { wfx.a }, Error); assertThrows(function() { wgx.aa }, Error); assertThrows(function() { wh4.q }, Error); m.gate.enable(); assertEquals(6, w.a); assertEquals(5, w.r.a); assertEquals(5, o.r.a); assertEquals(7, w.r = 7); assertEquals(7, w.r); assertEquals(7, o.r); assertEquals(8, w.b.bb); assertEquals(7, w[2]["c"]); assertEquals(undefined, w.c); assertEquals(8, wb.bb); assertEquals(3, wr.a); assertEquals(4, wf(4)); assertEquals(3, wf3); assertEquals(6, wfx.a); assertEquals(7, wgx.aa); assertEquals(4, wh4.q); // An identity-preserving membrane. Adapted from: // http://wiki.ecmascript.org/doku.php?id=harmony:proxies#an_identity-preserving_membrane function createMembrane(wetTarget) { var wet2dry = WeakMap(); var dry2wet = WeakMap(); function asDry(obj) { registerObject(obj) print("asDry enter", str(obj)) try { var x = asDry2(obj); registerObject(x, "dry"); print("asDry exit", str(obj), "as", str(x)); return x; } catch(e) { print("asDry exception", str(e)); throw e; } } function asDry2(wet) { if (wet !== Object(wet)) { // primitives provide only irrevocable knowledge, so don't // bother wrapping it. return wet; } var dryResult = wet2dry.get(wet); if (dryResult) { return dryResult; } var wetHandler = createHandler(wet); var dryRevokeHandler = Proxy.create(Object.freeze({ get: function(receiver, name) { return function() { var arg = (name === "get" || name == "set") ? arguments[1] : ""; print("dry handler enter", name, arg); var optWetHandler = dry2wet.get(dryRevokeHandler); try { var x = asDry(optWetHandler[name].apply( optWetHandler, Array.prototype.map.call(arguments, asWet))); print("dry handler exit", name, arg, "returning", str(x)); return x; } catch (eWet) { var x = asDry(eWet); print("dry handler exception", name, arg, "throwing", str(x)); throw x; } }; } })); dry2wet.set(dryRevokeHandler, wetHandler); if (typeof wet === "function") { function callTrap() { print("dry call trap enter", str(this)); var x = asDry(wet.apply( asWet(this), Array.prototype.map.call(arguments, asWet))); print("dry call trap exit", str(this), "returning", str(x)); return x; } function constructTrap() { function forward(args) { return wet.apply(this, args) } return asDry(new forward(Array.prototype.map.call(arguments, asWet))); } dryResult = Proxy.createFunction(dryRevokeHandler, callTrap, constructTrap); } else { dryResult = Proxy.create(dryRevokeHandler, asDry(Object.getPrototypeOf(wet))); } wet2dry.set(wet, dryResult); dry2wet.set(dryResult, wet); return dryResult; } function asWet(obj) { registerObject(obj) print("asWet enter", str(obj)) try { var x = asWet2(obj) registerObject(x, "wet") print("asWet exit", str(obj), "as", str(x)) return x } catch(e) { print("asWet exception", str(e)) throw e } } function asWet2(dry) { if (dry !== Object(dry)) { // primitives provide only irrevocable knowledge, so don't // bother wrapping it. return dry; } var wetResult = dry2wet.get(dry); if (wetResult) { return wetResult; } var dryHandler = createHandler(dry); var wetRevokeHandler = Proxy.create(Object.freeze({ get: function(receiver, name) { return function() { var arg = (name === "get" || name == "set") ? arguments[1] : ""; print("wet handler enter", name, arg); var optDryHandler = wet2dry.get(wetRevokeHandler); try { var x = asWet(optDryHandler[name].apply( optDryHandler, Array.prototype.map.call(arguments, asDry))); print("wet handler exit", name, arg, "returning", str(x)); return x; } catch (eDry) { var x = asWet(eDry); print("wet handler exception", name, arg, "throwing", str(x)); throw x; } }; } })); wet2dry.set(wetRevokeHandler, dryHandler); if (typeof dry === "function") { function callTrap() { print("wet call trap enter", str(this)); var x = asWet(dry.apply( asDry(this), Array.prototype.map.call(arguments, asDry))); print("wet call trap exit", str(this), "returning", str(x)); return x; } function constructTrap() { function forward(args) { return dry.apply(this, args) } return asWet(new forward(Array.prototype.map.call(arguments, asDry))); } wetResult = Proxy.createFunction(wetRevokeHandler, callTrap, constructTrap); } else { wetResult = Proxy.create(wetRevokeHandler, asWet(Object.getPrototypeOf(dry))); } dry2wet.set(dry, wetResult); wet2dry.set(wetResult, dry); return wetResult; } var gate = Object.freeze({ revoke: function() { dry2wet = wet2dry = Object.freeze({ get: function(key) { throw new Error("revoked"); }, set: function(key, val) { throw new Error("revoked"); } }); } }); return Object.freeze({ wrapper: asDry(wetTarget), gate: gate }); } var receiver var argument var o = { a: 6, b: {bb: 8}, f: function(x) { receiver = this; argument = x; return x }, g: function(x) { receiver = this; argument = x; return x.a }, h: function(x) { receiver = this; argument = x; this.q = x }, s: function(x) { receiver = this; argument = x; this.x = {y: x}; return this } } o[2] = {c: 7} var m = createMembrane(o) var w = m.wrapper print("o =", str(o)) print("w =", str(w)) var f = w.f var x = f(66) var x = f({a: 1}) var x = w.f({a: 1}) var a = x.a assertEquals(6, w.a) assertEquals(8, w.b.bb) assertEquals(7, w[2]["c"]) assertEquals(undefined, w.c) assertEquals(1, w.f(1)) assertSame(o, receiver) assertEquals(1, w.f({a: 1}).a) assertSame(o, receiver) assertEquals(2, w.g({a: 2})) assertSame(o, receiver) assertSame(w, w.f(w)) assertSame(o, receiver) assertSame(o, argument) assertSame(o, w.f(o)) assertSame(o, receiver) // Note that argument !== o, since o isn't dry, so gets wrapped wet again. assertEquals(3, (w.r = {a: 3}).a) assertEquals(3, w.r.a) assertEquals(3, o.r.a) w.h(3) assertEquals(3, w.q) assertEquals(3, o.q) assertEquals(4, (new w.h(4)).q) assertEquals(5, w.s(5).x.y) assertSame(o, receiver) var wb = w.b var wr = w.r var wf = w.f var wf3 = w.f(3) var wfx = w.f({a: 6}) var wgx = w.g({a: {aa: 7}}) var wh4 = new w.h(4) var ws5 = w.s(5) var ws5x = ws5.x m.gate.revoke() assertEquals(3, wf3) assertThrows(function() { w.a }, Error) assertThrows(function() { w.r }, Error) assertThrows(function() { w.r = {a: 4} }, Error) assertThrows(function() { o.r.a }, Error) assertEquals("object", typeof o.r) assertEquals(5, (o.r = {a: 5}).a) assertEquals(5, o.r.a) assertThrows(function() { w[1] }, Error) assertThrows(function() { w.c }, Error) assertThrows(function() { wb.bb }, Error) assertEquals(3, wr.a) assertThrows(function() { wf(4) }, Error) assertEquals(6, wfx.a) assertEquals(7, wgx.aa) assertThrows(function() { wh4.q }, Error) assertThrows(function() { ws5.x }, Error) assertThrows(function() { ws5x.y }, Error)