A non intrusive JS code analysis.
I’m keen to develop a rudimentary instrumentation tool capable of measuring aspects such as code coverage, correctness attraction experimentation, and code analysis in JS programs, all without altering the JS code itself.
Remember Graal and Truffle? There exists an open source JS engine implementation featuring these technologies. Why choose GraalJS over V8’s source code? Primarily, it’s due to our affinity for Java and the desire to implement this idea as swiftly as possible. Moreover, the architecture of GraalJS is more comprehensible than that of C.
All languages can be translated into an Abstract Syntax Tree (AST), which the compiler then traverses at least once. During the interpreter evaluation of code, each node of the AST is visited, executing the specific semantics of the nodes. For instance, for a mathematical operation node:
- Evaluate the left child.
- Evaluate the right child.
- Perform the operation on the results.
- Return the result.
Suppose we wish to count the number of sum operations in any JS script. The simplest approach would be to identify the operator type and increment a global counter variable within the node behavior.
Registering function entering
Let’s consider a specific example: measuring the function coverage of a JS script execution. The architecture of the GraalJS implementation ensures that each AST node is responsible for its own behavior. As such, there could very well be a “function node” or something similar.
So, our first step is to examine the implementation of the Function Node.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.oracle.truffle.js.nodes.function;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeCost;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.js.nodes.JavaScriptNode;
@NodeInfo(cost = NodeCost.NONE)
public final class FunctionBodyNode extends AbstractBodyNode {
@Child private JavaScriptNode body;
public FunctionBodyNode(JavaScriptNode body) {
this.body = body;
}
public static FunctionBodyNode create(JavaScriptNode body) {
return new FunctionBodyNode(body);
}
public JavaScriptNode getBody() {
return body;
}
@Override
public Object execute(VirtualFrame frame) {
return body.execute(frame);
}
@Override
protected JavaScriptNode copyUninitialized() {
return create(cloneUninitialized(body));
}
}
The above code contains a method named execute, which serves as the entry point for the AST evaluator to carry out the behavior of the node. This would be an ideal location to introduce our function coverage instrumentation.
1
2
3
4
5
6
@Override
public Object execute(VirtualFrame frame) {
System.out.println("" + this.getBody());
// API call, or anything else
return body.execute(frame);
}
Executing this JS script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function a(){
console.log("I am a");
b();
}
function b(){
console.log("I am b");
}
function main(){
a();
b();
b();
}
main();
And voila !