javascriptes6-proxy

How to proxy Function.prototype?


I tried to proxy Function.prototype.

I created a new Proxy and rewrote the toString method, however console.log('cas') doesn't work, why?

Function.prototype = new Proxy(Function.prototype, {
  get: function(target, property, receiver) {},
  set: function(target, property, value, receiver) {
    console.log('cas')
  }
})
Function.prototype._toString = Function.prototype.toString;
Function.prototype.toString = function() {
  console.log('bb')
  return this._toString()
}

Solution

  • Ok, we cannot overwrite Function.prototype but we can set the prototype of Function.prototype (an evil professor's laugh here):

    const props = Object.getOwnPropertyDescriptors(Function.prototype);
    
    const masked = {};
    
    for (const [name, value] of Object.entries(props)) {
        Object.defineProperty(masked, name, value);
    }
    
    const prototype = new Proxy({}, {
        get: function (target, property, receiver) {
            // avoid recursion and stack overflow
            if(!['call', 'apply'].includes(property)){
              console.log(`getting ${property}`);
            }
            return masked[property];
        },
        set: function (target, property, value, receiver) {
            console.log(`setting ${property}`);
            masked[property] = value;
        }
    });
    
    // clear Function.prototype and assign our proxy prototype to it
    
    for (const k in props) {
        delete Function.prototype[k];
    }
    
    Object.setPrototypeOf(Function.prototype, prototype);
    
    Function.prototype._toString = Function.prototype.toString;
    Function.prototype.toString = function () {
        console.log("I'm new toString()");
        return this._toString();
    };
    
    const test = () => false;
    
    console.log(test.toString());


    UPDATE
    The author noticed that the solution doesn't work in node.js.
    The reason that console.log is environment specific plus overriding Function.prototype means that if you call a function that calls apply, call, bind you go into recursion and stack overflow (RangeError: Maximum call stack size exceeded).
    So the code should be adjusted to your specific execution environment. On my node.js version 18 these tweaks helped. Moreover I had to override console.log since the output was empty. Again, environment-specific (seems another investigation...). During debugging console.log worked but required to avoid getting listener property.
    So my conclusion: it works if you care about recursion with specific methods on your platform AND I will be working on an environment-independent solution, the plan is to register function calls and avoid recursion by checking whether there's already a function call that leads to the stack overflow.

    const props = Object.getOwnPropertyDescriptors(Function.prototype);
    
    const masked = {};
    
    for (const [name, value] of Object.entries(props)) {
        Object.defineProperty(masked, name, value);
    }
    
    const log = msg => process.stdout.write(msg + '\n');
    
    const prototype = new Proxy({}, {
        get: function (target, property, receiver) {
            // avoid recursion and stack overflow
            if (!['call', 'apply', 'bind'].includes(property)) {
                log(`getting ${property}`);
            }
            return masked[property];
        },
        set: function (target, property, value, receiver) {
            log(`setting ${property}`);
            masked[property] = value;
        }
    });
    
    // clear Function.prototype and assign our proxy prototype to it
    
    for (const k in props) {
        delete Function.prototype[k];
    }
    
    Object.setPrototypeOf(Function.prototype, prototype);
    
    Function.prototype._toString = Function.prototype.toString;
    Function.prototype.toString = function () {
        log('I\'m new toString()');
        return this._toString();
    };
    
    const test = () => false;
    
    log(test.toString());
    enter image description here