The Pipeline: Transforming JavaScript

When the Sandbox transforms target code, it modifies object accesses to intercept them and injects checks and other QoS metrics at various points. These transformations preserve the meaning of the code, but in many cases change the way the code operates.

In this document, various operations within the sandbox are represented by symbols in bold. These symbols and their meanings are:

Property accesses are often "unrolled" into parameters to Sandbox calls. The basic mechanism for this is as follows: an object o with property p (usually denoted as o.p) is passed as two parameters, o and "p" (as in G(o, "p")), while an object o indexed by i (denoted o[i]) is passed as two parameters o and i (as in G(o, i)). This unrolling process is used whenever a property reference is passed to a Sandbox call (see G(), S(), D(), I(), and P()).

1.  Variable hoisting and explicit declaration

This process makes explicit JavaScript's "blockless" scoping: all variable declarations are positioned at the beginning of the scope. All declarations, even those within the initialiser of a for(), are processed. Variable declarations with initialisations are split into a plain declaration and an explicit assignment, then all plain declarations are moved to the beginning of scope.

  1. var a = 1, b → var a; a = 1; var b
  2. for(var a = 0; a<n ; a++) → var a ; for(a = 0; a<n; a++)
  3. var decls collected and hoisted to top of block

2.  Function declaration conversion and hoisting

Function declarations are converted into variable declarations with initialisation to a function expression. All of these insitialised declarations are then moved to the beginning of scope. This makes explicit the way in which JavaScript interprets function declarations.

  1. function a() { } → var a = function() { }
  2. All converted decls hoisted to top of block

3.  Explicit scoping

All non-local identifiers are explicitly scoped. In this case, local scope refers to the nested function scope chain; this leaves global variables (explicit and implicit) and the window object. window itself is replaced by the Sandbox symbol W, known window properties are explicitly converted to property accessors of W, and core JavaScript properties are treated as window properties. Explicit global variables are converted to property accesses of the Sandbox symbol V, implict globals are detected and treated the same way. The special identifier arguments is, when not overridden, wrapped in the Sandbox call GA().

  1. window → W
  2. core_propertyW.core_property
  3. window_propertyW.window_property
  4. (vars in global scope): var a; a = 1 → V.a = 1
  5. arguments → GA(arguments)

4.  Keyword transformation

Several important keywords (pertaining to the type system and object lifetime) are converted to Sandbox symbols. delete is converted to the symbol D(), with property accesses unrolled into parameters to D(). Calls to new are replaced with the symbol N(). Function expressions are wrapped with the Sandbox call F(). typeof is converted to TO(). References to the special identifier this are wrapped in the Sandbox call GT(). instanceof is converted to IO(). The in operator is wrapped in a short-circuit mechanism using the Sandbox call FV(). Debugger breakpoints are routed to DBG(). By default, the debugger is disabled in the Sandbox. You can enable debugging support on any of the sample page by appending ?debug=true to the URL.

  1. delete(a.b) → D(a, "b")
  2. delete(a[b]) → D(a, b)
  3. delete(a) → D(a)
  4. new a(b) → N(a, [b])
  5. function() { } → F(function() { })
  6. typeof aTO(a)
  7. this → GT(this)
  8. a instanceof bIO(a, b)
  9. a in bFV(b, a) && (a in b)
  10. debugger → DBG()

5.  Affix operator transformation

Prefix and postfix increment and decrement are replaced with the Sandbox call P(). Property accesses are unrolled into parameters to P(), with a third parameter indicating the operation to perform.

  1. a.b++ → P(a, "b", 1)
  2. --a.bP(a, "b", 2)
  3. a[b]-- → P(a, b, 0)
  4. ++a [b] → P(a, b, 3)

6.  Loop augmenting

Property-iteration loops are augmented in two ways: first, the object being iterated is wrapped in the Sandbox call FC(); second, the body of the loop is wrapped in a conditional making the Sandbox call FV(). Then, all loop bodies are preceded by the Sandbox QoS call C().

  1. for(a in b) { c } → for(a in FC(b)) { if(FV(b, a)) { c } }
  2. All loop blocks: loop { c } → loop { C(); c }

7.  Try augmenting

Catch blocks in try/catch/finally structures are augmented by preceding the body with the Sandbox call E(). This call filters exceptions to ensure that Sandbox-generated exceptions do not enter client code. As well, E() provides an opportunity for policy to modify exceptions as they're processed.

  1. try { a } catch(b) { c } → try { a } catch(b) { b = E(b); c }

8.  Assignment transformation

Property assignments are replaced with the Sandbox symbol S(). The property access is unrolled, and the assignment value is passed as a parameter. Compound assignments use an explicit expansion for the assignment value.

  1. a.b = cS(a, "b", c)
  2. a[b] = cS(a, b, c)
  3. a.b += cS(a, "b", a.b + c)

9.  Invocation transformation

Property invocations are replaced with the Sandbox symbol I(). The property access is unrolled, and parameters to the invocation are passed as an array.

  1. a.b(c) → I(a, "b", [c])
  2. a[b](c) → I(a, b, [c])

10.  Accessor transformation

Simple property accesses are replaced with the Sandbox symbol G(). Property accesses are unrolled as parameters. As a special case, W.document is replaced with the symbol WD.

  1. a.bG(a, "b")
  2. a[b] → G(a , b)
  3. W.document → WD

11.  Preamble transformation

The entire program (that is, the set of global statements) is wrapped into a function taking the safe context object as a parameter. The global shortcuts are short assignments which alias Sandbox operations available through the context object A; these assignments precede the program's statements and help avoid repetetive lookups.

  1. program → function(A) { global_shortcuts; program }