// 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. // A simple membrane. Adapted from: // http://wiki.ecmascript.org/doku.php?id=harmony:proxies#a_simple_membrane function createSimpleMembrane(target) { let enabled = true; function wrap(obj) { if (obj !== Object(obj)) return obj; let handler = new Proxy({}, {get: function(_, key) { if (!enabled) throw new Error("disabled"); switch (key) { case "apply": return (_, that, args) => { try { return wrap(Reflect.apply( obj, wrap(that), args.map((x) => wrap(x)))); } catch(e) { throw wrap(e); } } case "construct": return (_, args, newt) => { try { return wrap(Reflect.construct( obj, args.map((x) => wrap(x)), wrap(newt))); } catch(e) { throw wrap(e); } } default: return (_, ...args) => { try { return wrap(Reflect[key](obj, ...(args.map(wrap)))); } catch(e) { throw wrap(e); } } } }}); return new Proxy(obj, handler); } const gate = Object.freeze({ enable: () => enabled = true, disable: () => enabled = false }); return Object.freeze({ wrapper: wrap(target), gate: gate }); } // Test the simple membrane. { 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; 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(target) { const wet2dry = 0; const dry2wet = 1; function flip(dir) { return (dir + 1) % 2 } let maps = [new WeakMap(), new WeakMap()]; let revoked = false; function wrap(dir, obj) { if (obj !== Object(obj)) return obj; let wrapper = maps[dir].get(obj); if (wrapper) return wrapper; let handler = new Proxy({}, {get: function(_, key) { if (revoked) throw new Error("revoked"); switch (key) { case "apply": return (_, that, args) => { try { return wrap(dir, Reflect.apply( obj, wrap(flip(dir), that), args.map((x) => wrap(flip(dir), x)))); } catch(e) { throw wrap(dir, e); } } case "construct": return (_, args, newt) => { try { return wrap(dir, Reflect.construct( obj, args.map((x) => wrap(flip(dir), x)), wrap(flip(dir), newt))); } catch(e) { throw wrap(dir, e); } } default: return (_, ...args) => { try { return wrap(dir, Reflect[key]( obj, ...(args.map((x) => wrap(flip(dir), x))))) } catch(e) { throw wrap(dir, e); } } } }}); wrapper = new Proxy(obj, handler); maps[dir].set(obj, wrapper); maps[flip(dir)].set(wrapper, obj); return wrapper; } const gate = Object.freeze({ revoke: () => revoked = true }); return Object.freeze({ wrapper: wrap(wet2dry, target), gate: gate }); } // Test the identity-preserving membrane. { 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 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) }