Is there a safe way to call `call` to call a function in JavaScript?

I want to call a function with a custom thisArg .

This seems trivial, I just need to call :

 func.call(thisArg, arg1, arg2, arg3); 

But wait! func.call may not be Function.prototype.call .

So I thought about using

 Function.prototype.call.call(func, thisArg, arg1, arg2, arg3); 

But wait! Function.prototype.call.call may not be Function.prototype.call .

So, assuming Function.prototype.call is native, but perhaps considering arbitrary non-internal properties, does ECMAScript provide a safe way to do the following?

 func.[[Call]](thisArg, argumentsList) 
+8
javascript function call
source share
3 answers

In order for the strength (and risk) of duck input: if typeof func.call === 'function' , you should treat it as if it were a normal, called function. Prior to the func provider, ensure that their call property matches the public signature. I actually use this in several places, as JS does not provide a way to overload operator () and provide a classic functor.

If you really need to avoid using func.call , I would go with func() and require func to take thisArg as the first argument. Since func() does not delegate the call value (i.e. f(g, h) does not drop to f.call(t, g, h) ) and you can use the variables on the left side of parens, this will give you predictable results.

You can also cache the link to Function.prototype.call when loading the library, if it is replaced later, and use it to call functions later. This is the pattern used by lodash / underscore to capture its own array methods, but it does not give any real guarantee that you will get the original native call method. It can be pretty close and not terribly ugly:

 const call = Function.prototype.call; export default function invokeFunctor(fn, thisArg, ...args) { return call.call(fn, thisArg, ...args); } // Later... function func(a, b) { console.log(this, a, b); } invokeFunctor(func, {}, 1, 2); 

This is a fundamental problem in any language with polymorphism. At some point, you must trust an object or library to behave in accordance with its contract. As in any other case, trust, but check:

 if (typeof duck.call === 'function') { func.call(thisArg, ...args); } 

With type checking, you can also perform error handling:

 try { func.call(thisArg, ...args); } catch (e) { if (e instanceof TypeError) { // probably not actually a function } else { throw e; } } 

If you can donate thisArg (or make it be the actual argument), you can enter and check with parens:

 if (func instanceof Function) { func(...args); } 
+4
source share

At some point you should trust what is available in the window. This either means caching the features you plan to use, or trying to isolate your code.

A β€œsimple” solution to calling call is to temporarily set the property:

 var safeCall = (function (call, id) { return function (fn, ctx) { var ret, args, i; args = []; // The temptation is great to use Array.prototype.slice.call here // but we can't rely on call being available for (i = 2; i < arguments.length; i++) { args.push(arguments[i]); } // set the call function on the call function so that it can be...called call[id] = call; // call call ret = call[id](fn, ctx, args); // unset the call function from the call function delete call[id]; return ret; }; }(Function.prototype.call, (''+Math.random()).slice(2))); 

Then it can be used as:

 safeCall(fn, ctx, ...params); 

Remember that parameters passed to safeCall will be combined into an array. You will need to apply to make this behave properly, and I'm just trying to simplify the dependencies here.


An improved version of safeCall that adds an add dependency:

 var safeCall = (function (call, apply, id) { return function (fn, ctx) { var ret, args, i; args = []; for (i = 2; i < arguments.length; i++) { args.push(arguments[i]); } apply[id] = call; ret = apply[id](fn, ctx, args); delete apply[id]; return ret; }; }(Function.prototype.call, Function.prototype.apply, (''+Math.random()).slice(2))); 

This can be used as:

 safeCall(fn, ctx, ...params); 

An alternative solution to calling safely is to use functions from a different window context.

You can do this by simply creating a new iframe function and capture in your window. You still have to assume some dependence on the DOM manipulation functions available, but this happens as an installation step, so any future changes will not affect the existing script:

 var sandboxCall = (function () { var sandbox, call; // create a sandbox to play in sandbox = document.createElement('iframe'); sandbox.src = 'about:blank'; document.body.appendChild(sandbox); // grab the function you need from the sandbox call = sandbox.contentWindow.Function.prototype.call; // dump the sandbox document.body.removeChild(sandbox); return call; }()); 

Then it can be used as:

 sandboxCall.call(fn, ctx, ...params); 

Both safeCall and sandboxCall safe from future changes to Function.prototype.call , but as you can see, they rely on some existing global functions to work at runtime. If a malicious script executes before this code, your code will still be vulnerable.

+2
source share

If you trust Function.prototype.call , you can do something like this:

 func.superSecureCallISwear = Function.prototype.call; func.superSecureCallISwear(thisArg, arg0, arg1 /*, ... */); 

If you trust Function..call but not Function..call.call , you can do this:

 var evilCall = Function.prototype.call.call; Function.prototype.call.call = Function.prototype.call; Function.prototype.call.call(fun, thisArg, arg0, arg1 /*, ... */); Function.prototype.call.call = evilCall; 

And perhaps even wrap it in an assistant.

If your functions are clean and your objects are serializable, you can create an iframe through message passing ( window.postMessage ), pass the function code and arguments to it, let it call you (since this is a new iframe without any third-party code that you are safe enough), and you are gold, something like (not tested at all, probably riddled with errors):

 // inside iframe window.addEventListener('message', (e) => { let { code: funcCode, thisArg, args } = e.data; let res = Function(code).apply(thisArg, args); e.source.postMessage(res, e.origin); }, false); 

The same thing can be done with web workers.

If so, you can take one more step and send it to your server. If you use node, you can safely run arbitrary scripts using vm module . In Java, you have projects like Rhino and Nashorn; I'm sure .Net has its own implementations (maybe it even runs it like JScript!), And there are probably bazillion broken javascript virtual machines implemented in php.

If you can do this, why not use a service like Runnable to create javascript sandboxes on the fly, maybe even install it for your own server environment.

+1
source share

All Articles