Detect if function is native to browser
Asked Answered
C

7

29

I am trying to iterate over all the globals defined in a website, but in doing so I am also getting the native browser functions.

var numf=0; var nump=0; var numo=0; 
for(var p in this) { 
    if(typeof(this[p]) === "function"){
        numf+=1;
        console.log(p+"()");
    } else if(typeof p != 'undefined'){
        nump+=1;
        console.log(p);
    } else { 
        numo+=1;
        console.log(p);
    }
}

Is there a way to determine if a function is native to the browser or created in a script?

Cornea answered 6/7, 2011 at 15:26 Comment(0)
C
24

You can call the inherited .toString() function on the methods and check the outcome. Native methods will have a block like [native code].

if( this[p].toString().indexOf('[native code]') > -1 ) {
    // yep, native in the browser
}

Update because a lot of commentators want some clarification and people really have a requirement for such a detection. To make this check really save, we should probably use a line line this:

if( /\{\s+\[native code\]/.test( Function.prototype.toString.call( this[ p ] ) ) ) {
    // yep, native
}

Now we're using the .toString method from the prototype of Function which makes it very unlikely if not impossible some other script has overwritten the toString method. Secondly we're checking with a regular expression so we can't get fooled by comments within the function body.

Cloutier answered 6/7, 2011 at 15:35 Comment(8)
This would only work if toString had not been overridden, no?Chafin
@Cloutier Is this foolproof? I thought toString doesn't work in all modern browsers or something.Ollieollis
@joekit if toString is overridden you should be able to do Function.prototype.toString.call(obj).indexOf('[native code]'); Also it would probably be a better idea to use RegExp. Try calling the function against itself, and it would come across as native because it appears in the string.Ollieollis
When bind is used to bind a method to certain context, the resulting method is though not native but your check would say its native. window.alert = function () {}; window.alert = window.alert.bind()Tymothy
Note this isn't the perfect solution. Checkout gist.github.com/jdalton/5e34d890105aca44399fIbsen
So if I create a function checkNative using jAndy's code, it will return true on itself.Quintinquintina
What if function () { /* [native code] */ console.log("I'm not native."); }?Snaggletooth
What if Function.prototype.toString is overridden? Your solution may not work. Also, your solution will determine the following function as native: function () { return; /* { [native code] } */ }. You should change the regex to /\{ \[native code\] \}$/.Joyajoyan
D
14
function isFuncNative(f) {
       return !!f && (typeof f).toLowerCase() == 'function' 
       && (f === Function.prototype 
       || /^\s*function\s*(\b[a-z$_][a-z0-9$_]*\b)*\s*\((|([a-z$_][a-z0-9$_]*)(\s*,[a-z$_][a-z0-9$_]*)*)\)\s*{\s*\[native code\]\s*}\s*$/i.test(String(f)));
}

this should be good enough. this function does the following tests:

  1. null or undefined;
  2. the param is actually a function;
  3. the param is Function.prototype itself (this is a special case, where Function.prototype.toString gives function Empty(){})
  4. the function body is exactly function <valid_function_name> (<valid_param_list>) { [native code] }

the regex is a little bit complicated, but it actually runs pretty decently fast in chrome on my 4GB lenovo laptop (duo core):

var n = (new Date).getTime(); 
for (var i = 0; i < 1000000; i++) {
    i%2 ? isFuncNative(isFuncNative) : 
          isFuncNative(document.getElementById);
}; 
(new Date).getTime() - n;

3023ms. so the function takes somewhere around 3 micro-sec to run once all is JIT'ed.

It works in all browsers. Previously, I used Function.prototype.toString.call, this crashes IE, since in IE, the DOM element methods and window methods are NOT functions, but objects, and they don't have toString method. String constructor solves the problem elegantly.

Destinydestitute answered 24/9, 2011 at 4:13 Comment(0)
F
3

Function.prototype.toString can be spoofed, something kinda like this:

Function.prototype.toString = (function(_toString){
  return function() {
    if (shouldSpoof) return 'function() { [native code] }'
    return _toString.apply(this, arguments)
  }
})(Function.prototype.toString)

You can detect if Function.prototype.toString is vandalized by trapping .apply(), .call(), .bind() (and others).

And if it was, you can grab a "clean" version of Function.prototype.toString from a newly injected IFRAME.

Frierson answered 27/7, 2018 at 15:34 Comment(1)
before initializing the script (if working with your own script): For method blocking Object.defineDescriptor(Function.prototype,'toString',{configurable:false,writable:false}); or let toString=Function.prototype.toString; .... toString.call(func); or toString method checking Function.prototype.toString.call(function empty(){})==='function empty(){}'Mcgrath
E
2

2022 answer

Now that we have the Proxy API, there's no fail proof way to determine if a native function was overridden or not.

For example:

function isNativeFunction(f) {
  return f.toString().includes("[native code]");
}

window.fetch = new Proxy(window.fetch, {
  apply: function (target, thisArg, argumentsList) {
    console.log("Fetch call intercepted:", ...argumentsList);
    Reflect.apply(...arguments);
  },
});

window.fetch.toString(); // → "function fetch() { [native code] }"

isNativeFunction(window.fetch); // → true

Following the specs, a proxied object should be indistinguishable from its target. Some runtimes (e.g., Node.js) offers some utilities to go against the spec and check if an object is proxied, but in the browser the only way to do so is by monkey patching the Proxy API itself before any proxy is applied.

So, back to the original question — I think the only available option nowadays is to hold a reference of the “clean” native function and, later on, compare your potentially monkey patched function with it:

<html>
  <head>
    <script>
      // Store a reference of the original "clean" native function before any
      // other script has a chance to modify it.
      // In this case, we're just holding a reference of the original fetch API
      // and hide it behind a closure. If you don't know in advance what API
      // you'll want to check, you might need to store a reference to multiple
      // `window` objects.
      (function () {
        const { fetch: originalFetch } = window;
        window.__isFetchMonkeyPatched = function () {
          return window.fetch !== originalFetch;
        };
      })();
      // From now on, you can check if the fetch API has been monkey patched
      // by invoking window.__isFetchMonkeyPatched().
      //
      // Example:
      window.fetch = new Proxy(window.fetch, {
        apply: function (target, thisArg, argumentsList) {
          console.log("Fetch call intercepted:", ...argumentsList);
          Reflect.apply(...arguments);
        },
      });
      window.__isFetchMonkeyPatched(); // → true
    </script>
  </head>
</html>

By using a strict reference check, we avoid all toString() loopholes. And it even works on proxies because they can’t trap equality comparisons.

The main drawback of this approach is that it can be impractical. It requires storing the original function reference before running any other code in your app (to ensure it’s still untouched), which sometimes you won’t be able to do (e.g., if you’re building a library).

For more info, I recently wrote an article going in-depth into the available approaches for determining if a native function was monkey patched. You can find it here.

Everywhere answered 2/8, 2022 at 8:29 Comment(1)
Very simple proxy, and pretty good article, this solved the doubt I left on July 28, thanks.Secateurs
S
0

For developers who want to use these detection methods to block user behavior (such as userscript), there are actually ways to bypass these methods.

For Example:
(The detection method for the isNative function below comes from methods provided by others in the past.)

function isNative(f) {
    if (!!/bound/.test(f.name)) return 1;
    if (!!!/\{\s+\[native code\]/.test(Function.prototype.toString.call(f))) return 2;
    if (!!!/\{\s+\[native code\]/.test(eval(f) + "")) return 3;
    if ((typeof f).toLowerCase() !== 'function') return 4;
    if (!/^\s*function\s*(\b[a-z$_][a-z0-9$_]*\b)*\s*\((|([a-z$_][a-z0-9$_]*)(\s*,[a-z$_][a-z0-9$_]*)*)\)\s*{\s*\[native code\]\s*}\s*$/i.test(String(f))) return 5;

    return true;
}

function fakeStringify(value) {
    return `Fake Stringify ${value}`;
};

const s = new Proxy(fakeStringify, {
    get(target, prop, receiver) {
        if (prop === "name") {
            return "stringify";
        } else if (prop === Symbol.toPrimitive) {
            return function () {
                return "function () { [native code] }";
            };
        }
    }
});

const obj = {
    a: 1
};
console.log("========= [native] =========");
console.log(isNative(JSON.stringify));
console.log(JSON.stringify(obj));
console.log(JSON.stringify.name);

JSON.stringify = s;

console.log("======== [override] ========");
console.log(isNative(JSON.stringify));
console.log(JSON.stringify(obj));
console.log(JSON.stringify.name);

After execution, it can be found that the isNative detection can be successfully deceived after passing through the Proxy.

As far as I know, there is no way to detect Proxy fucntion in the browser environment. But if you know how to detect it, please provide it, thanks!

Secateurs answered 28/7, 2022 at 16:35 Comment(0)
K
-1

I tried a different approach. This is only tested for firefox, and chrome.

function isNative(obj){
    //Is there a function?
    //You may throw an exception instead if you want only functions to get in here.

    if(typeof obj === 'function'){
        //Check does this prototype appear as an object?
        //Most natives will not have a prototype of [object Object]
        //If not an [object Object] just skip to true.
        if(Object.prototype.toString.call(obj.prototype) === '[object Object]'){
            //Prototype was an object, but is the function Object?
            //If it's not Object it is not native.
            //This only fails if the Object function is assigned to prototype.constructor, or
            //Object function is assigned to the prototype, but
            //why you wanna do that?
            if(String(obj.prototype.constructor) !== String(Object.prototype.constructor)){
                return false;
            }
        }
    }
    return true;
}

function bla(){}

isNative(bla); //false
isNative(Number); //true
isNative(Object); //true
isNative(Function); //true
isNative(RegExp); //true
Kermis answered 24/1, 2015 at 2:42 Comment(7)
Returns false for Promise even though my browser supports promises natively.Wail
Yes I don't know why that is. Promise.prototype.constructor does not equal Object.prototype.constructor. I usually don't test if it's native anyway, and look for the then method which is how native promises resolve library promises any way. If all else fails then I use Promise.resolve(nonNative) to normalize all promises.Kermis
@trusktr Interesting. In Firefox Object.prototype.toString.call(RegExp.prototype) returns [object RegExp]. I don't know about IE, or Opera.Kermis
I downvoted because I was able to easily make it return true on a non-native function: let f = function() {}; f.prototype = Object.prototype; isNative(f) // true.Mckinley
@Mckinley Wouldn't you only do that if you were making a constructor? If you're the one making it then wouldn't you know what it is? There is really no perfect solutions to this particular problem. I've known about the '[native code]' check for a long time, but there are situations where that doesn't work as well. I didn't include that in my answer because someone else already posted it.Kermis
The constructor could be imported from somewhere and not made by me. I guess there may not really be a fool-proof answer. I wish there was!Mckinley
If it's a constructor it's not a function. Even though it is. ;) You know polymorphism destroys identifiability, or something like that. But still, are you really going to check if a function is native if you know it's a constructor?Kermis
A
-1

almost all of these will fail, because:

function notNative(){}
notNative.toString = String.bind(0, "function notNative() { [native code] }");
console.log( notNative.toString() );

instead:

Function.prototype.isNative = function(){
return Function.prototype.toString.call(this).slice(-14, -3) === "native code";
};

console.log(alert.isNative());
console.log(String.isNative());
function foo(){}
console.log(foo.isNative());
Ansell answered 30/6, 2017 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.