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()
}
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());