JavaScript’s eval() and Function()

[originally posted 2014-03-14]

In this post, I examine JavaScript’s eval function and the Function constructor to highlight their similarities as well as their subtle differences, and how to use them without problems.

We’ll list some of the problems – at a higher level – then proceed with the API and how to work around the problems, particularly with scope, including a “paranoid sandbox” approach.

Finally we’ll visit debugging, performance considerations and list of projects for further reading.

Note
In a future post, I may argue that the Function constructor in JavaScript can be used for good, solving specific problems.

update 31 July 2015
Due to the arrival Content Security Policy restrictions in the browser, I would argue that Function should be used only on a server-side environment that you control.

good or bad?

As JavaScript developers we have long been warned about using the eval, Function, setInterval and setTimeout functions, most famously by Douglas Crockford (scroll to the bottom).

Since then several authors have shown safe use cases for eval and Function:

Problems

Naive Overuse

The general case we are warned against is naive overuse. The following example is taken from a stackoverflow thread on not using eval – to wit, doing this

eval('document.' + potato + '.style.color = "red"');

instead of either of these

// DOM Level 1 element access
document[potato].style.color = 'red';

// DOM Level 2 element access
document.getElementById(potato).style.color = 'red';

Errors

The special case is passing in source code from over the wire – an ajax response, for example – in order to create an object and apply it to already running code. The danger is that malicious or malformed code will result in errors, causing your application to misbehave or stop behaving altogether.

In response to this issue in particular, Douglas Crockford promoted the use of JSON.parse and JSON.stringify methods for processing ajax response text to avoid evaluating JSON as JavaScript.

Local Scope Access

eval also has access to all variables within the calling context or execution scope (i.e., inside the function) in which it is used. That makes it unsafe, if you want to avoid clobbering local variables inadvertently.

Function does not have access to the calling scope, but malicious or malformed code will still cause problems.

Global Scope Access

The eval function has access to the global scope, so it can clobber any globals as well. Function shares this problem.

Debuggers

The eval function acts as a code generator of sorts. Code generated at runtime is harder to debug – that is, step through with a debugger – because you can’t set break points on code that hasn’t been evaluated. Function shares this problem.

Update
See the Live debugging section for Paul Irish’s chrome dev tools live recompilation demo.

Performance

Another problem is the performance hit that eval incurs because it must parse, evaluate, then interpret, source code of unpredictable size – it may contain few statements, or very many. Function shares this problem.

That last point, that the input size is not knowable beforehand, means that code minifiers can’t minify the blocks of strings ahead of time, and that runtime engines may not be able to optimize lookahead caching (a fancy way of saying, they can’t compile it).

API

eval evaluates a string representing JavaScript code and executes it.

eval(code)

Function can be called with or without the new operator. Function takes one or more string arguments and produces a new function object. The last argument is a string representing JavaScript code.

// no param names
var F = Function(code)

Arguments before the code are evaluated as parameter names to be applied to the new function object. This can take one of 3 forms.

// explicit param name strings
var F = Function('a', 'b', code);

// comma-separated param names in a string
var F = Function('a, b', code);

// array of param name strings
var F = Function(['a', 'b'], code);

The array signature turns out to be quite handy, not only for the param names,

 var argNames = ['a', 'b'];

…but for the code or function body parts, too,

var lines = ["console.log(arguments.length);",
             "console.log(a);",
             "console.log(b)"];

…allowing us to create a factory that takes two arrays of strings

function factory(params, lines) {

  // ...preprocessing statements here...

  return Function(params || '', lines.join('\n'));
}

var test = factory(argNames, lines);

test(3, 5); // 2, 3, 5
test(88); // 1, 88, undefined
test(0, 1, 4); // 3, 0, 1
test(null); // 1, null, undefined   

Scope object of this keyword

eval

In eval, this refers to the scope of the calling context. In general, this will refer to the global scope

(function testEval() {
  eval('console.log(this);'); // window or global
}());

(function testEval() {
  (function nested() {
    eval('console.log(this);'); // window or global
  }());
}());

inside a constructor

Where eval is called inside a function invoked with the new operator, this refers to the constructor (rather than the newly created object):

function EvalTest() {
  eval('console.log(this);');
  eval('console.log(this instanceof EvalTest);');
}

new EvalTest();

// EvalTest !
// true

That can be done with inline definition and instantiation as

new function EvalTest(){
  eval('console.log(this);'); // EvalTest !
  eval('console.log(this instanceof EvalTest);'); // true
};

(new function EvalTest() {
  eval('console.log(this);'); // EvalTest !
  eval('console.log(this instanceof EvalTest);'); // true
}());

new (function EvalTest(){
  eval('console.log(this);'); // EvalTest !
  eval('console.log(this instanceof EvalTest);'); // true      
});

Function

In the Function constructor, this is the global scope by default. These examples use immediate invocations of the new function object

inside a function

(function testFunction() {     
  Function('console.log(this);')(); // window or global
}());

as an anonymous constructor

(function testFunction() {     
  new Function('console.log(this);')(); // window or global
}());

inside a constructor

(new function testFunction() {
  Function('console.log(this);')();  // Window or global
}());

new function testFunction() {
  Function('console.log(this);')();  // Window or global
};

When the created function object is executed as a named constructor, this refers to the instantiated object

(function testFunction() {
  var F = Function('console.log(this);');
  new F();  // anonymous (FF) or Object { } (webkit)
}());

(function testFunction() {
  var F = Function('console.log(this.toString());');
  new F();  // [object Object]
}());

If you set the scope dynamically, as with any function, using call or apply, this refers to that scope

(function testFunction() {     
  Function('console.log(this.id);').call({ id: 'fake' }); // 'fake'
}());

Using call or apply without a defined scope, this refers by default to the global scope

(function testFunction() {     
  Function('console.log(this);').apply(); // Window or global
}());

Scope Access

eval

Because eval has access to the global scope, the following creates a new global variable

(function testEval() {
  eval('answer = 42');
  console.log(answer); // 42
}());

console.log(answer); // 42

Because eval has access to the calling or local scope, eval will clobber the variable named inside that scope without affecting the global scope

var answer = 'default';

(function testEval() {
  var answer;
  eval('answer = 42');
  console.log(answer); // 42
}());

console.log(answer); // 'default'

Function

Function has access to the global scope and can create or clobber global variables

(function testFunction() {     
  Function('answer = 42')();
  console.log(answer); // 42
}());

console.log(answer); // 42 ! here's our leak

Function does not have access to the calling scope

(function testFunction() {
  var answer = 'default';
  Function('answer = 42')();
  console.log(answer); // 'default'
}());

However that will create or clobber a global variable

console.log(answer); // 42 ! here's our leak

Strict Mode

As Nicholas Zakas argues, we can start using strict mode in ES5 runtimes to prevent the creation and/or clobbering of accidental globals.

Here’s the general usage of strict mode within a function

(function testFunction() {     
  "use strict";
  (function() {
    answer = 42; // ReferenceError: assignment to undeclared variable answer
  })();
  console.log(answer); // n/a
}());

eval

We can use strict mode with eval to prevent leaking and clobbering from within a local scope

(function testEval() {
  var answer;
  eval('"use strict"; answer = 42');
  console.log(answer); // 42
}());

(function testEval() {
  "use strict"; 
  eval('answer = 42'); // ReferenceError: assignment to undeclared variable answer
  console.log(answer); // n/a
}());

(function testEval() {
  eval('"use strict"; answer = 42'); // ReferenceError: assignment to undeclared variable answer
  console.log(answer); // n/a
}());

Function constructor

Because Function does not have access to the local scope, the “use strict” pragma must be included in the Function body in order to prevent leaking and clobbering from within a local scope.

This fails

(function testFunction() {     
  "use strict";
  Function('answer = 42')();
  console.log(answer); // 42
}());

console.log(answer); // 42 -- leaking

This works

(function testFunction() {     
  Function('"use strict"; answer = 42')(); // ReferenceError: assignment to undeclared variable answer
  console.log(answer); // n/a
}());

Paranoid Sandbox

For runtimes that do not support strict mode you need to implement a sandbox that cleans up any accidental or temporary globals created when running Function or eval.

Here’s a quick implementation of such a sandbox function

function sandbox(fn) {

  // hack for cross-platform global
  global = global || window;

  var keys = {};
  var result, k;

  for (k in global) {
    keys[k] = k;
  }

  result = fn();

  for (k in global) {
    if (!(k in keys)) {
      delete global[k];
    }
  }

  return result;
}

First, there is a pre-test that collects all keys currently assigned to the global namespace. After the target function is run, clean up any new keys found in the global namespace.

The following is a drastically reduced example of using it, the key being to use Function inside another function that is actually passed to the sandbox function.

var context = {
  name:       'david',
  occupation: 'typist'
};

var code = ['for (var key in context) {',
            '  if (context.hasOwnProperty(key)) {',
            '    console.log(key);',
            '  }',
            '}'];

sandbox(function () {
  Function('context', code.join('\n'))(context);
  return context;
});

Live debugging ~ breakpoints, etc.

I confess I do not use line debuggers much when isolating problems in JavaScript, as I prefer the healthy practice of test-driven development. However, following Paul Irish’s chrome dev tools live recompilation demo I was able to live edit this fragment using breakpoints, live-edit, ctrl+s or cmd+s, and play, with a successful result.

;(function () {
  var id = 'rest'; // should be 'result' instead
  var code = ['var result = document.getElementById(\'' + id + '\');',
              'result.textContent = \'success\';'
             ];
  Function(code.join('\n'))();
}());

So, yes, it can be done, as our tools are maturing.

Performance

It depends. Not all JavaScript runtimes optimize the same things, or even in the same way. OK, truism, yes. Everyone immersed in the performance wars has learned that performance varies, and not all things require performance optimization.

Nicholas Zakas in High Performance JavaScript,
illustrates the performance cost that use of eval or Function incurs. Here’s the relevant excerpt with a data table showing the cost in time for each browser runtime.

Published in 2010, that table shows performance slowing by whole orders of magnitude (10 to 100 times) depending which runtime is used.

Since then, the browser engine wars have narrowed this difference significantly.

From this jsperf test, comparing eval, Function() and function expression, the only consistent results I’ve seen on so far:

  • Function is consistently slowest on FF 27 by 60% or more ~ this is the worst disparity among the three approaches on any of the modern browsers I’ve tried.
  • no strategy wins on IE 11 ~ each approach has shown up as ‘fastest’ on repeated test runs
  • performance distribution is narrowest on Chrome 33 and Opera 19

(BTW, Don’t abuse JSPerf ~ thanks ;))

Projects

Initially I examined these cases after looking through a couple of interesting projects for node.js.

  • load ~ a “paranoid sandbox” that executes browser-like JavaScript on node.js without the module pattern, and removing global assignments after use.
  • Testing Private State and Mocking Dependencies
    ~ interesting example using node.js vm module as a code sandbox for mocking during tests.

You can also visit some projects I have done using the Function constructor in various ways:

  • vm-shim ~ create a code sandbox similar to node.js vm.runInContext, vm.runInNewContext, and vm.runInThisContext methods, using the “paranoid sandbox” technique.
  • metafunction ~ introspection module for mocking and testing a function’s internals, also uses the “paranoid sandbox” technique.
  • where.js ~ test library helper method for running data-driven tests against a commented text data table directly in the test.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s