Function-level JavaScript instrumentation with Closure Compiler

Overview

This post describes how to do function-level instrumentation of JavaScript programs using a Closure Compiler fork which is available here. The repository contains all code used here in the instrumentation-sample directory. Program points that can be hooked are function definition, invocation, and exit. Closure supports instrumentation internally as-is, this fork makes it more useful. Since Closure is already a popular part of JS build chains, it was an attractive target to add this feature to. I used this code as part of a project for JS hardening (ZigZag).

Update: The code has been merged into the official Closure repository.

“Hello World”

How to use the instrumentation feature:

$ java -jar compiler.jar --js file.js --instrumentation_template template.txt --formatting pretty_print

instrumentation_template FILE is the new option. The specified file contains the code that will be added to the JS file.</p>

Code specified as `"init"` will be prepended to the program, this is where function definitions for the report call/exit/defined functions go. The other three types: report_call, report_exit and report_defined specifiy the functions that should be invoked for those actions. These functions are where one wants to fill in the blanks with one's own code to see what's happening in a program. Here is a bare-bones instrumentation template:

init: "function instr_call(fun_id){}"
init: "function instr_exit(fun_id, return_value){return return_value;}"
init: "function instr_defined(fun_id){}"
report_call: "instr_call"
report_exit: "instr_exit"
report_defined: "instr_defined"
`fun_id` is a unique identifier of functions within the program. The report_exit function will be used in a return statement. It is important to keep in mind that user specified function has to return the return argument (`return_value`), otherwise the instrumented program will not work as hoped for. When compiling a program that consists of one function:
function a(e) {
   return e+1;
}
The output is:
function instr_call(b) {
}
function instr_exit(b, c) {
  return c;
}
function instr_defined(b) {
}
instr_defined(0);
function a(b) {
    instr_call(0);
    return instr_exit(0, b + 1);
}
;
# Accessing arguments An example that is more interesting would be logging arguments used in a function call. For that, the arguments variable can be used. Since this variable is not defined in the script otherwise, it needs to be defined as an extern. The externs file contains only one line: "arguments". To instrument the program, the command line has to be extended by: `--externs externs.txt --jscomp_off=externsValidation` The updated code for the `"init"` section of the instrumentation template:
function instr_call(fun_id){
    for ( var i = 0; i &lt; arguments.callee.caller.arguments.length; i++) {
        console.log('Argument ' + i + ': ' +
            arguments.callee.caller.arguments[i]
        );
    }
}
function instr_exit(fun_id, return_value){return return_value;}
function instr_defined(fun_id){}
# Closing This post explains how to use a modified version of Closure Compiler to instrument programs via templates. I found it a pity Closure doesn't allow for easier instrumentation out of the box, and hope this code can be useful to others working with JavaScript.