Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does function.toString() output "[native code]", whereas logging to the console directly displays the function’s source code?

I decided to create a userscript for YouTube live chat. Here is the code:

const toString = Function.prototype.toString

unsafeWindow.setTimeout = function (fn, t, ...args) {
    unsafeWindow.console.log(fn, fn.toString(), toString.call(fn))
    unsafeWindow.fns = (unsafeWindow.fns ?? []).concat(fn)
    return setTimeout(fn, t, ...args)
}

Now look what the output looks like:

enter image description here

The output for some of the functions is predictable, but look at the other ones! When you do just console.log it, you will see the function body, but if you call fn.toString(), you will see function () { [native code] }.

But why? The script is loaded before the page, so the YouTube's scripts couldn't replace the methods.

like image 595
Wynell Avatar asked Jul 22 '21 13:07

Wynell


People also ask

What is the purpose of toString () method in JavaScript?

The toString() method returns a string as a string. The toString() method does not change the original string. The toString() method can be used to convert a string object into a string.

What does native code mean in JavaScript?

Native code is computer programming (code) that is compiled to run with a particular processor (such as an Intel x86-class processor) and its set of instructions. If the same program is run on a computer with a different processor, software can be provided so that the computer emulates the original processor.

Which method is used to get a string equivalent of a function?

We can use the toString() method on a function object to get the source code of a function. If we concatenate a string with a function, JavaScript will call the toString() method on the function internally and convert the function to a string.

What is the difference between tostring() and logging to the console?

javascript - Why does function.toString () output " [native code]", whereas logging to the console directly displays the function’s source code? - Stack Overflow Why does function.toString () output " [native code]", whereas logging to the console directly displays the function’s source code? I decided to create a userscript for YouTube live chat.

How to get the source code of a function using tostring?

If the toString () method is called on a function created by the Function constructor, toString () returns the source code of a synthesized function declaration named "anonymous" using the provided parameters and function body. It's also possible to explicitly get the string representation of a function using the + operator: ({ a(){} }. a)

What is the use of toString () method?

If the toString() method is called on a function created by the Function constructor, toString() returns the source code of a synthesized function declaration named "anonymous" using the provided parameters and function body.

What is the use of console log function in JavaScript?

The console.log() is a function in JavaScript which is used to print any kind of variables defined before in it or to just print any message that needs to be displayed to the user. Syntax: console.log(A); Parameters: It accepts a parameter which can be an array, an object or any message. Return value: It returns the value of the parameter given.


1 Answers

It is because those functions have been passed to Function.prototype.bind.

> (function () { return 42; }).toString()
'function () { return 42; }'
> (function () { return 42; }).bind(this).toString()
'function () { [native code] }'

The bind method transforms an arbitrary function object into a so-called bound function. Invoking a bound function has the same effect as invoking the original function, except that the this parameter and a certain number of initial positional parameters (which may be zero) will have values fixed at the time of the creation of the bound function. Functionally, bind is mostly equivalent to:

Function.prototype.bind = function (boundThis, ...boundArgs) {
    return (...args) => this.call(boundThis, ...boundArgs, ...args);
};

Except that the above will, of course, produce a different value after string conversion. Bound functions are specified to have the same string conversion behaviour as native functions, in accordance with ECMA-262 11th Ed., §19.2.3.5 ¶2:

2. If func is a bound function exotic object or a built-in function object, then return an implementation-dependent String source code representation of func. The representation must have the syntax of a NativeFunction. […]

[…]

NativeFunction:

function PropertyName [~Yield, ~Await]opt ( FormalParameters [~Yield, ~Await] ) { [native code] }

When printing the function to the console directly (instead of the stringification), the implementation is not bound to any specification: it may present the function in the console any way it wishes. Chromium’s console, when asked to print a bound function, simply displays the source code of the original unbound function, as a matter of convenience.


Proving that this is indeed what happens in YouTube’s case is a bit of a nuisance, since YouTube’s JavaScript is obfuscated, but not exceedingly difficult. We can open YouTube’s main site, then enter the developer console and install our trap:

window.setTimeout = ((oldSetTimeout) => {
    return function (...args) {
        if (/native code/.test(String(args[0])))
            debugger;
        return oldSetTimeout.call(this, ...args);
    };
})(window.setTimeout);

We should get a hit at the debugger statement very quickly. I hit it in this function:

g.mh = function(a, b, c) {
    if ("function" === typeof a)
        c && (a = (0, g.D)(a, c));
    else if (a && "function" == typeof a.handleEvent)
        a = (0, g.D)(a.handleEvent, a);
    else
        throw Error("Invalid listener argument");
    return 2147483647 < Number(b) ? -1 : g.C.setTimeout(a, b || 0)
}

The g.D function looks particularly interesting: it seems to be invoked with the first argument a, which is presumably a function. It looks like it might invoke bind under the hood. When I ask the console to inspect it, I get this:

> String(g.D)
"function(a,b,c){return a.call.apply(a.bind,arguments)}"

So while the process is a bit convoluted, we can clearly see that this is indeed what happens.

like image 131
user3840170 Avatar answered Oct 23 '22 05:10

user3840170