2013 NEWS 2013-01-24 - Quarantined Code Execution
The Uize
module now provides static methods to facilitate the quarantined execution of JavaScript code.
Quarantined execution of JavaScript code can be divided into two types: quarantined code evaluation and quarantined nested functions.
1. What is Quarantined Code Execution?
Quarantined code execution is the execution of some JavaScript from within a deep local scope, but where the code being executed is executed outside of that deep scope - in a "quarantined" scope.
In the quarantined scope, the only scope that the quarantined code has access to in its scope chain is the global scope. The executed code is, thereby, quarantined from the local scope so that it cannot accidentally contaminate (or be contaminated by) the local scope, by accessing or assigning to identifiers within the local scope (or anywhere else up the local scope's scope chain).
This is best illustrated by an example...
NON-QUARANTINED
function foo () { var bar = 5; eval ('var baz = 10; alert (bar);'); // doesn't throw error, but we'd want it to } foo ();
In the above example, some code is being evaluated inside a function scope using JavaScript's built-in eval
function. Now, for argument's sake, let's say that this code has a typo in it where it was supposed to access the baz
variable that it defined, but it's actually trying to access a bar
variable. If the surrounding scope in which the eval
call is being made contains a bar
variable, then the code will not produce an error as it should and will appear to work, but it will have a bug.
This kind of cross-contamination between evaluated code and a deep local scope can be alleviated by using either of the Uize.eval
or Uize.laxEval
methods, as follows...
QUARANTINED
function foo () { var bar = 5; Uize.eval ('var baz = 10; alert (bar);'); // throws an error, as we would like } foo ();
In the above example, we have replaced the use of JavaScript's built-in eval
function with a call to the Uize.eval
method. The code being evaluated is now evaluated in a quarantined fashion so that it no longer has access to scope inside the foo
function, and the only scope it has access to is the global scope. In this example, the global scope does not define a bar
variable, so the code being evaluated produces a JavaScript error as we would like and exposes the typo in the code.
Now, the global scope could always still have identifiers that could cross-contaminate with code being evaluated using the Uize.eval
or Uize.laxEval
methods, but the potential for such cross-contamination is much reduced, especially as you consider that the deeper and deeper you go into nested scopes, the more identifiers get "tacked" on as a result of an increasingly long scope chain. The most important thing is that the Uize.eval
and Uize.laxEval
methods allow you to quarantine code evaluation from the current / local scope within which the methods are called.
2. Quarantined Code Evaluation
The Uize
module provides two static methods to facilitate quarantined evaluation of JavaScript code strings.
The general wisdom is to avoid using JavaScript's built-in eval
function at all costs, and this is, for the most part, good advice. However, JavaScript is a dynamic language, and when you're doing more sophisticated (and risky) things with JavaScript such as dynamic code construction, it becomes useful to evaluate strings that contain JavaScript code.
Now, a risk with careless use of JavaScript's eval
function from deep within the nested functions of your code is that you may not be intending to have the code evaluated within the scope of your deep function.
It could be a benefit to the code you're eval'ing that it has access to the scope of the function in which you're eval'ing it, but it may also be a curse in two respects...
1) | the code being eval'ed may unexpectedly conflict with identifiers in your function's scope (or any scope up the scope chain) |
2) | function closures within the code being eval'ed will hang on to your function's scope state (with "interesting" memory usage implications) |
To address these risks and to allow you to perform quarantined code evaluation, the Uize
module provides methods for strict mode quarantined evaluation and non-strict mode quarantined evaluation.
2.1. Strict Mode Quarantined Evaluation
To perform quarantined code evaluation in JavaScript strict mode, the Uize.eval
method can be used.
SYNTAX
evalResultANYTYPE = Uize.eval (codeToEvalSTR);
EXAMPLE
function foo () { var bar = 5; Uize.eval ('bar = 10'); // throws an error, because bar is not declared inside eval'd code alert (bar); } foo ();
In the above example, the code being evaluated in the call to the Uize.eval
method results in a JavaScript error being thrown. This is for two reasons:
1) | the Uize.eval method evaluates the code in a quarantined fashion, so it doesn't have access to the bar variable defined in the local scope |
2) | the Uize.eval method evaluates code using JavaScript strict mode, so the code that now appears to assigning a value to a variable not declared in the global scope (the only scope that the quarantined code evaluation has access to) throws an error because this is not permitted in strict mode |
2.2. Non-strict Mode Quarantined Evaluation
To perform quarantined code evaluation in non-strict mode, the Uize.laxEval
method can be used.
SYNTAX
evalResultANYTYPE = Uize.laxEval (codeToEvalSTR);
EXAMPLE
function foo () { var bar = 5; Uize.laxEval ('bar = 10'); // doesn't throw an error, but doesn't set value of local scope bar alert (bar); // alerts "5", because the quarantined eval'd code didn't set local bar variable } foo ();
In the above example, the code being evaluated in the call to the Uize.laxEval
method does not throw a JavaScript as the Uize.eval
method would, but it still doesn't set the value of the bar
variable in the local scope. There are two things in play here...
1) | the Uize.laxEval method evaluates the code in a quarantined fashion, so it doesn't have access to the bar variable defined in the local scope |
2) | the Uize.laxEval method evaluates code using non-strict mode, so it allows the quarantined code to assign a value to a variable that is not declared in the quarantined code's scope chain, which has the effect of declaring bar as a global variable rather than throwing an error (as would be the case in strict mode) |
3. Quarantined Nested Functions
To declare a function from within some deep local scope, but have that function be quarantined from the local scope, one can use the Uize.quarantine
method.
The Uize.quarantine
method allows us to essentially generate a quarantined version of the supplied function. When the quarantined function is then called, it won't have access to the same scope chain that the original function had access to - it will only have access to the global scope.
SYNTAX
quarantinedFunctionFUNC = Uize.quarantine (sourceFunctionFUNC);
The behavior of the Uize.quarantine
method is best illustrated by an example...
EXAMPLE
var bar = 10; function Foo () { var bar = 5; this.baz = function () { alert (bar); }; this.qux = Uize.quarantine (this.baz); } var myFoo = new Foo (); myFoo.baz (); // alerts 5, because the baz method has access to the Foo scope myFoo.qux (); // alerts 10, because it is quarantined from the Foo scope, and global bar is 10
In the above example, the instance method baz
of the Foo
object is defined inside the constructor. As a result, it has access to the Foo
scope and the bar
variable defined in it, and it alerts the value of local bar
variable when it is called. Now, the qux
instance method, on the other hand, is defined as being a quarantined version of the baz
instance method, so it will only have access to the global scope when called.
As a result, when the baz
method is called on the instance myFoo
, it alerts "5", which is the value of the bar
variable in the Foo
scope. In contrast, when the qux
method is called on myFoo
, it alerts "10", which is the value of the bar
variable in the global scope - because it is quarantined from the Foo
scope, it only has access to the global scope and the global bar
variable.
3.1. Benefits of Uize.quarantine Over Uize.eval or Uize.laxEval
The quarantining effect of the Uize.quarantine
method can also be achieved by using either of the Uize.eval
or Uize.laxEval
methods, but the Uize.quarantine
method has a few benefits over the quarantined code evaluation methods...
3.1.1. Quarantine Existing Functions
The Uize.quarantine
method lets you create a quarantined version of a function that already exists.
This is something that you can't easily do with the Uize.eval
or Uize.laxEval
methods - you'd basically have to manually re-implement the Uize.quarantine
method.
3.1.2. Easier to Write Code in Function Form
The Uize.quarantine
method lets you write quarantined code using a regular function, which is just plain easier to do.
By writing quarantined code in function form, you can avoid getting caught up in the character escaping and multi-line string concatenation issues associated with constructing a JavaScript code string for evaluation. This becomes increasinly an issue as the amount of code that you want to quarantine increases and constructing the code as a string becomes increasingly cumbersome.
3.1.3. Code in Function Form is Scrunchable
Quarantined code written in function form has the benefit of being scrunchable by the scruncher (or otherwise minified by other JavaScript minifiers or code obfuscators).
Naturally, the benefits of this will be greater the larger the quarantined code is.
3.1.4. Some Early Error Detection
Quarantined code written in function form will be parsed by the JavaScript interpreter early.
Unlike the Uize.eval
and Uize.laxEval
methods, where the quarantined code will be evaluated at runtime and where errors in the code will only surface when the code is evaluated, the function supplied to the Uize.quarantine
method will be parsed just like any other function, and syntax errors (and some errors relating to non-compliance with JavaScript strict mode) will be caught early - before the quarantined code is even executed.
3.2. Immediately Invoked Quarantined Functions
In cases where one wishes to execute code immediately from within some deep local scope, but where one wishes to have that code be executed in a quarantined fashion, one can call the function returned by the Uize.quarantine
method immediately.
EXAMPLE
// code executed before quarantined code Uize.quarantine (function () { // quarantined code execution }) (); // code executed after quarantined code
This is just like immediate invokation of an anonymous function, except with the additional wrapper of the Uize.quarantine
call.