Proxy Reflect.has() call causes infinite recursion?

#!/JoePea joe at trusktr.io
Thu Nov 21 21:25:49 UTC 2019


Sorry you all, I realized I should've simplified it. Here's a [simpler
fiddle](https://jsfiddle.net/trusktr/tf6hdn48/6/).

On Thu, Nov 21, 2019 at 1:17 PM #!/JoePea <joe at trusktr.io> wrote:
>
> I was trying to implement "multiple inheritance" in the following code
> ([jsfiddle](https://jsfiddle.net/trusktr/tf6hdn48/)), but it gives a
> max call stack (infinite recursion) error.
>
> However, the infinite recursion does not execute any of my console.log
> statements repeatedly like I'd expect, so it seems that the infinite
> recursion is happening inside the JS engine?
>
> What's going on?
>
> Here's the code for reference, see "PROXY INFINITE RECURSION" comments
> for the site where the problem is happening (as far as I can tell in
> devtools):
>
> ```js
> "use strict";
>
> function multiple(...classes) {
>     if (classes.length === 0)
>         return Object;
>     if (classes.length === 1) {
>         const result = classes[0];
>         return (typeof result === 'function' ? result : Object);
>     }
>
>     const FirstClass = classes.shift();
>     const __instances__ = new WeakMap();
>     const getInstances = (inst) => {
>         let result = __instances__.get(inst);
>         if (!result)
>             __instances__.set(inst, (result = []));
>         return result;
>     };
>
>     class MultiClass extends FirstClass {
>         constructor(...args) {
>             super(...args);
>
>             const protoBeforeMultiClassProto =
> findPrototypeBeforeMultiClassPrototype(this, MultiClass.prototype);
>             if (protoBeforeMultiClassProto)
>                 Object.setPrototypeOf(protoBeforeMultiClassProto,
> newMultiClassPrototype);
>
>             const instances = getInstances(this);
>
>             for (const Ctor of classes)
>                 instances.push(new Ctor(...args));
>         }
>     }
>
>     let count = 0;
>     const newMultiClassPrototype = new Proxy({
>         __proto__: MultiClass.prototype,
>     }, {
>         get(target, key, self) {
>             if (count++ < 500) console.log(' --------------- get', key);
>
>             if (Reflect.has(MultiClass.prototype, key))
>                 return Reflect.get(MultiClass.prototype, key, self);
>
>             for (const instance of getInstances(self))
>                 if (Reflect.has(instance, key))
>                     return Reflect.get(instance, key, self);
>
>             return undefined;
>         },
>
>         set(target, key, value, self) {
>             console.log(' ----- set1', key, value);
>
>             // PROXY INFINITE RECURSION HERE:
>             console.log('hmmmmmmmmmmmmmmmm?',
> Reflect.has(MultiClass.prototype, key));
>
>             console.log(' ----- set1.5', key, value);
>
>             if (Reflect.has(MultiClass.prototype, key)) {
>                 return Reflect.set(target, key, value, self);
>             }
>
>             const instances = getInstances(self);
>
>             for (const instance of instances) {
>                 if (Reflect.has(instance, key)) {
>                     return Reflect.set(instance, key, value, self);
>                 }
>             }
>
>             return Reflect.set(target, key, value, self);
>         },
>     });
>
>     return MultiClass;
> }
>
> function findPrototypeBeforeMultiClassPrototype(obj, multiClassPrototype) {
>     let previous = obj;
>     let current = Object.getPrototypeOf(obj);
>     while (current) {
>         // debugger
>         if (current === multiClassPrototype)
>             return previous;
>         previous = current;
>         current = Object.getPrototypeOf(current);
>     }
>     return null;
> }
>
> async function test() {
>     await new Promise(r => setTimeout(r, 3000));
>     console.log('-------------------------------------');
>     const R1 = multiple();
>     const r1 = new R1();
>     console.log(Object.keys(r1));
>     console.log('-------------------------------------');
>     class Foo {
>         constructor() {
>             this.f = false;
>         }
>     }
>     const R2 = multiple(Foo);
>     const r2 = new R2();
>     console.log(r2.hasOwnProperty);
>     console.log('f', r2.f);
>     console.log('-------------------------------------');
>     class Bar {
>         constructor() {
>             this.b = 'asf';
>         }
>     }
>     const R3 = multiple(Foo, Bar);
>     const r3 = new R3();
>     console.log(r3.hasOwnProperty);
>     console.log('f', r3.f);
>     console.log('b', r3.b);
>     console.log('-------------------------------------');
>     class Baz {
>         constructor() {
>             this.z = 1;
>         }
>     }
>     const R4 = multiple(Foo, Bar, Baz);
>     const r4 = new R4();
>     console.log(r4.hasOwnProperty);
>     console.log('f', r4.f);
>     console.log('b', r4.b);
>     console.log('z', r4.z);
>     class One {
>         constructor(arg) {
>             this.one = 1;
>             console.log('One constructor');
>             // this.one = arg
>         }
>         foo() {
>             console.log('foo', this.one);
>         }
>         setVar() {
>             this.var = 'bright';
>         }
>     }
>     class Two {
>         constructor(arg) {
>             this.two = 2;
>             console.log('Two constructor');
>             // this.two = arg
>         }
>         bar() {
>             console.log('bar', this.two);
>         }
>         readVar() {
>             console.log(this.var); // should be "bright"
>         }
>     }
>     class Three extends Two {
>         constructor(arg1, arg2) {
>             super(arg1);
>             this.three = 3;
>             console.log('Three constructor');
>             // this.three = arg2
>         }
>         baz() {
>             console.log(' - baz: call super.bar');
>             super.bar();
>             console.log('baz', this.three, this.two);
>         }
>     }
>     class FooBar extends multiple(Three, One) {
>         constructor(...args) {
>             super();
>             // call each constructor. We can pas specific args to each
> constructor if we like.
>             //
>             // XXX The following is not allowed with ES6 classes,
> class constructors are not callable. :[ How to solve?
>             // One.call(this, ...args)
>             // Three.call(this, ...args)
>             //
>             // XXX Solved with the callSuperConstructor helper.
>             // ;(this as any).callSuperConstructor(One, args[0])
>             // ;(this as any).callSuperConstructor(Three, args[1], args[2])
>         }
>         yeah() {
>             console.log(' -- yeah', this.one, this.two, this.three);
>             super.baz();
>             super.foo();
>         }
>     }
>     let f = new FooBar(1, 2, 3);
>     // this shows that the modifications to `this` by each constructor worked:
>     console.log(f.one, f.two, f.three); // logs "1 2 3"
>     console.log(' ---- call methods:');
>     // all methods work:
>     f.foo();
>     f.bar();
>     f.baz();
>     f.yeah();
>     f.setVar();
>     f.readVar();
>     console.log(' --------------------------- ');
>     class Lorem {
>         constructor() {
>             this.lo = 'rem';
>         }
>     }
>     // class Ipsum extends multiple(Lorem, FooBar) {
>     class Ipsum extends multiple(FooBar, Lorem) {
>         constructor() {
>             super(...arguments);
>             // THIS LINE TRIGGER PROXY INFINITE RECURSION
>             this.ip = 'sum';
>         }
>         test() {
>             console.log(' -- Ipsum: call super.bar()');
>             console.log(super.foo);
>             console.log(super.bar);
>             console.log(super.baz);
>             console.log(super.yeah);
>             console.log(super.setVar);
>             console.log(super.readVar);
>             super.bar();
>             console.log(this.lo, this.ip);
>         }
>     }
>     const i = new Ipsum();
>     i.foo();
>     i.bar();
>     i.baz();
>     i.yeah();
>     i.setVar();
>     i.readVar();
>     i.test();
>     function createOnChangeProxy(onChange, target) {
>         return new Proxy(target, {
>             get(target, property) {
>                 const item = target[property];
>                 if (isMutableObject(item))
>                     return createOnChangeProxy(onChange, item);
>                 return item;
>             },
>             set(target, property, newValue) {
>                 ;
>                 target[property] = newValue;
>                 onChange();
>                 return true;
>             },
>         });
>     }
>     function isMutableObject(maybe) {
>         if (maybe === null)
>             return false;
>         if (maybe instanceof Date)
>             return false;
>         // treat Uint8Arrays as immutable, even though they
> technically aren't, because we use them a lot and we treat them as
> immutable
>         if (maybe instanceof Uint8Array)
>             return false;
>         // TODO: filter out any other special cases we can find, where
> something identifies as an `object` but is effectively immutable
>         return typeof maybe === 'object';
>     }
>     let changeCount = 0;
>     const o = createOnChangeProxy(() => changeCount++, {});
>     o.foo = 1;
>     o.bar = 2;
>     o.baz = {};
>     o.baz.lorem = true;
>     o.baz.yeee = {};
>     o.baz.yeee.wooo = 12;
>     console.log(changeCount === 6);
> }
> test();
> ```


More information about the es-discuss mailing list