Can I override a Javascript function object to register all function calls?

Can I override the behavior of a Function object so that I can enter a behavior before each function call and then continue as usual? In particular, (although the general idea is intriguing in itself), can I go into the console every function call without having to insert console.log instructions everywhere? And then the normal behavior continues?

I truly admit that this is likely to have serious performance issues; I am not going to launch this launch, even in my development environment. But if it works, it seems like an elegant solution to get a 1000-meter view of the current code. And I suspect the answer will show me something deeper about javascript.

+48
javascript
Mar 07 '11 at 23:24
source share
6 answers

The obvious answer is as follows:

var origCall = Function.prototype.call; Function.prototype.call = function (thisArg) { console.log("calling a function"); var args = Array.prototype.slice.call(arguments, 1); origCall.apply(thisArg, args); }; 

But it actually enters an infinite loop immediately, because the act of calling console.log makes a call to a function that calls console.log , which makes a call to a function that calls console.log , which ...

Point, I'm not sure if this is possible.

+18
Mar 09 2018-11-11T00: 00Z
source share

Interception of function calls

Many here tried to override .call. Some of them failed, some succeeded. I answer this old question since it was raised at my workplace, and this post is used as a link.

Only two functions related to functions are available for us: we can change: .call and .apply. I will demonstrate successful redefinition of both.

TL DR: what the OP asks is impossible. Some of the success reports in the responses are due to the console calling .call internally just before the evaluation, and not because of the call we want to intercept.

Overriding the .prototype.call function

This seems to be the first idea that people came up with. Some of them were more successful than others, but here is an implementation that works:

 // Store the original var origCall = Function.prototype.call; Function.prototype.call = function () { // If console.log is allowed to stringify by itself, it will // call .call 9 gajillion times. Therefore, lets do it by ourselves. console.log("Calling", Function.prototype.toString.apply(this, []), "with:", Array.prototype.slice.apply(arguments, [1]).toString() ); // A trace, for fun console.trace.apply(console, []); // The call. Apply is the only way we can pass all arguments, so don't touch that! origCall.apply(this, arguments); }; 

This successfully hooks the .prototype.call function

Take it for a spin, will we?

 // Some tests console.log("1"); // Does not show up console.log.apply(console,["2"]); // Does not show up console.log.call(console, "3"); // BINGO! 

It is important that this does not start from the console. Different browsers have all kinds of console tools that very often call .call, including once for each login, which can frighten the user at the moment. Another mistake is simply the console.log arguments that go through the api console for stringing, which in turn causes an infinite loop.

Overriding the .prototype.apply function also

Well, how then to apply? They are the only magical calling functions we have, so let's try this. Here comes the version that catches both:

 // Store apply and call var origApply = Function.prototype.apply; var origCall = Function.prototype.call; // We need to be able to apply the original functions, so we need // to restore the apply locally on both, including the apply itself. origApply.apply = origApply; origCall.apply = origApply; // Some utility functions we want to work Function.prototype.toString.apply = origApply; Array.prototype.slice.apply = origApply; console.trace.apply = origApply; function logCall(t, a) { // If console.log is allowed to stringify by itself, it will // call .call 9 gajillion times. Therefore, do it ourselves. console.log("Calling", Function.prototype.toString.apply(t, []), "with:", Array.prototype.slice.apply(a, [1]).toString() ); console.trace.apply(console, []); } Function.prototype.call = function () { logCall(this, arguments); origCall.apply(this, arguments); }; Function.prototype.apply = function () { logCall(this, arguments); origApply.apply(this, arguments); } 

... and try to try!

 // Some tests console.log("1"); // Passes by unseen console.log.apply(console,["2"]); // Caught console.log.call(console, "3"); // Caught 

As you can see, the calling bracket goes unnoticed.

Conclusion

Fortunately, calling brackets cannot be intercepted using JavaScript. But even if .call intercepts the parenthesis operator on function objects, how could we call the original without calling an infinite loop?

The only thing that overrides .call / .apply is intercepting the explicit calls of these prototypes. If the console is used with this hack in place, there will be a lot and a lot of spam. In addition, you must be very careful if it is used, since using the console API can quickly cause an infinite loop (console.log will use .call inside if it gives it a non-string).

+13
Apr 16 '14 at 8:30
source share

I get SOME results and no page crashes with the following:

 (function () { var origCall = Function.prototype.call, log = document.getElementById ('call_log'); // Override call only if call_log element is present log && (Function.prototype.call = function (self) { var r = (typeof self === 'string' ? '"' + self + '"' : self) + '.' + this + ' ('; for (var i = 1; i < arguments.length; i++) r += (i > 1 ? ', ' : '') + arguments[i]; log.innerHTML += r + ')<br/>'; this.apply (self, Array.prototype.slice.apply (arguments, [1])); }); }) (); 

Checked only in Chrome version 9.xxx.

This, of course, is not logging all function calls, but it is registering some! I suspect that only actual calls to 'call' int are handled

+4
Mar 09 2018-11-11T00:
source share

Just a quick test, but it works for me. Perhaps this is not so useful, but I basically restore the prototype while in my replacement body, and then β€œnot restore” it before exiting.

This example simply logs all function calls, although there may be some fatal flaw that I have not yet discovered; doing it over a coffee break

implementation

 callLog = []; /* set up an override for the Function call prototype * @param func the new function wrapper */ function registerOverride(func) { oldCall = Function.prototype.call; Function.prototype.call = func; } /* restore you to your regular programming */ function removeOverride() { Function.prototype.call = oldCall; } /* a simple example override * nb: if you use this from the node.js REPL you'll get a lot of buffer spam * as every keypress is processed through a function * Any useful logging would ideally compact these calls */ function myCall() { // first restore the normal call functionality Function.prototype.call = oldCall; // gather the data we wish to log var entry = {this:this, name:this.name, args:{}}; for (var key in arguments) { if (arguments.hasOwnProperty(key)) { entry.args[key] = arguments[key]; } } callLog.push(entry); // call the original (I may be doing this part naughtily, not a js guru) this(arguments); // put our override back in power Function.prototype.call = myCall; } 

Using

I had some problems, including calls for this in one big pair, so here is what I typed in REPL to test the above functions:

 /* example usage * (only tested through the node.js REPL) */ registerOverride(myCall); console.log("hello, world!"); removeOverride(myCall); console.log(callLog); 
+3
Sep 14 '12 at 13:23
source share

You can override Function.prototype.call , just make sure that there are only apply functions in your override.

 window.callLog = []; Function.prototype.call = function() { Array.prototype.push.apply(window.callLog, [[this, arguments]]); return this.apply(arguments[0], Array.prototype.slice.apply(arguments,[1])); }; 
+1
Feb 07 '14 at 22:34
source share

It was easiest for me to process the file using an automatic process. I built this little tool to make it easier for myself. Maybe someone else will find this helpful. This is mostly awk, but easier to use by a Javascript programmer.

 // This tool reads a file and builds a buffer of say ten lines. // When a line falls off the end of the buffer, it gets written to the output file. // When a line is read from the input file, it gets written to the first line of the buffer. // After each occurrence of a line being read from the input file and/or written to the output // file, a routine is given control. The routine has the option of operating on the buffer. // It can insert a line before or after a line that is there, based on the lines surrounding. // // The immediate case is that if I have a set of lines like this: // // getNum: function (a, c) { // console.log(`getNum: function (a, c) {`); // console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`); // console.log(`arguments.length = ${arguments.length}`); // for (var i = 0; i < arguments.length; i++) { console.log(`arguments[${i}] = ${arguments[i] ? arguments[i].toString().substr(0,100) : 'falsey'}`); } // var d = b.isStrNum(a) ? (c && b.isString(c) ? RegExp(c) : b.getNumRegx).exec(a) : null; // return d ? d[0] : null // }, // compareNums: function (a, c, d) { // console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`); // // I want to change that to a set of lines like this: // // getNum: function (a, c) { // console.log(`getNum: function (a, c) {`); // console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`); // console.log(`arguments.length = ${arguments.length}`); // for (var i = 0; i < arguments.length; i++) { console.log(`arguments[${i}] = ${arguments[i] ? arguments[i].toString().substr(0,100) : 'falsey'}`); } // var d = b.isStrNum(a) ? (c && b.isString(c) ? RegExp(c) : b.getNumRegx).exec(a) : null; // return d ? d[0] : null // }, // compareNums: function (a, c, d) { // console.log(`compareNums: function (a, c, d) {`); // console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`); // // We are trying to figure out how a set of functions work, and I want each function to report // its name when we enter it. // // To save time, options and the function that is called on each cycle appear at the beginning // of this file. Ideally, they would be --something options on the command line. const readline = require('readline'); //------------------------------------------------------------------------------------------------ // Here are the things that would properly be options on the command line. Put here for // speed of building the tool. const frameSize = 10; const shouldReportFrame = false; function reportFrame() { for (i = frame.length - 1; i >= 0; i--) { console.error(`${i}. ${frame[i]}`); // Using the error stream because the stdout stream may have been coopted. } } function processFrame() { // console.log(`******** ${frame[0]}`); // if (frame[0].search('console.log(\`arguments.callee = \$\{arguments.callee.toString().substr(0,100)\}\`);') !== -1) { // if (frame[0].search('arguments.callee') !== -1) { // if (frame[0].search(/console.log\(`arguments.callee = \$\{arguments.callee.toString\(\).substr\(0,100\)\}`\);/) !== -1) { var matchArray = frame[0].match(/([ \t]*)console.log\(`arguments.callee = \$\{arguments.callee.toString\(\).substr\(0,100\)\}`\);/); if (matchArray) { // console.log('******** Matched'); frame.splice(1, 0, `${matchArray[1]}console.log('${frame[1]}');`); } } //------------------------------------------------------------------------------------------------ var i; var frame = []; const rl = readline.createInterface({ input: process.stdin }); rl.on('line', line => { if (frame.length > frameSize - 1) { for (i = frame.length - 1; i > frameSize - 2; i--) { process.stdout.write(`${frame[i]}\n`); } } frame.splice(frameSize - 1, frame.length - frameSize + 1); frame.splice(0, 0, line); if (shouldReportFrame) reportFrame(); processFrame(); // process.stdout.write(`${line}\n`); // readline gives us the line with the newline stripped off }); rl.on('close', () => { for (i = frame.length - 1; i > -1; i--) { process.stdout.write(`${frame[i]}\n`); } }); // Notes // // We are not going to control the writing to the output stream. In particular, we are not // going to listen for drain events. Nodejs' buffering may get overwhelmed. // 
0
Feb 22 '17 at 20:46
source share



All Articles