Skip to content
This repository has been archived by the owner on Feb 1, 2019. It is now read-only.

JavaScript Tests Module

mattbasta edited this page Jul 6, 2011 · 10 revisions

Hi there! The JS tests bit here is quite a big undertaking and will probably never be 100% reliable or 100% accurate (I would be incredibly surprised if it even got above 90%). Granted, it’s better than using Regexs to do this stuff, it’s much slower, uses a lot more memory, and leaves a bad taste in the mouth of anyone that believes in purity of JavaScript.

Firing it up

Spidermonkey

Installing Spidermonkey can be challenging depending on what platform you’re running. Fortunately, there’s a wiki page detailing installation instructions.

Configuring the Validator

The last step (which isn’t bad at all) is to configure the validator to use the installation of Spidermonkey for its business. This is super easy.

If you used homebrew to install spidermonkey, you’re in luck! The validator should automatically detect the installation and pick it up right away. If it does, skip the rest of this step (you’re done). The shell should be at /usr/bin/js.

If you installed everything line-for-line like I have above, everything is already good. Your Mozilla code should all be in /moz/mozilla-central/. If it is, and you built it line-for-line like I showed you, you’re good as gold. The validator should automatically know where your Spidermonkey installation is. Pat yourself on the back and chalk it up to doing what you’re told.

If you decided to meander your own route through the installation, you’re going to want to learn where you put the binaries for Spidermonkey. In the commands that I listed above, the compiled code ended up in /moz/mozilla-central/js/src/build-release. The shell should be named js. Once you’ve located the shell, create a file called constants_local.py that looks like this in the /validator folder of your validator’s installation:

from validator.constants import *
SPIDERMONKEY_INSTALLATION = "/wherever/your/binary/is/js"

If you want to take a less hands-on approach, you can also add the path to the shell to your $PATH. The validator will try to find an executable named js in all of the directories listed in $PATH, so simply setting that up will get you up and running as well, no manual configuration needed.

And that’s it!

The Problem with AST Trees

The problem with AST trees is that the nodes are not homogenous. For instance, CallExpression nodes will contain more nodes in the “callee” and “arguments” properties, while MemberExpression nodes will contain more nodes in its “object” and “property” nodes.

This problem prevents simple recursion to iterate the tree. Because the nodes are also indicative of the scope of the objects being referred to, it is also difficult to accurately target the use of prohibited objects and functions. I’ll talk about that in a second.

Traversing the trees

So that method for traversing the trees that I’ve come up with boils down the a giant dict in validator.testcases.javascript.nodedefinitions that lists each node type, the branches of that node that can contain other nodes, and some other information about the node type. Based on this, we can lookup each node as it is encountered, and loop through each branch which contains code, recursing into each sub-node.

Doing something useful

Traversing the tree is wonderful, but it doesn’t actually “do” anything. In order to actually test the JS, we need to make some determinations about the code that is being iterated over. In nodedefinitions, there is a value for each node type that identifies whether the object is block-level (so we can track variables created with let) and a value that identifies whether the object declares a scope. These values add a JSContext object to the top of the traverser’s context stack. When an assignment or declaration node is encountered, these contexts are populated.

Some elements, though, are not as simple to figure out as “just traverse and search for bad stuff”. Things like function declarations are especially tricky, where the declaration of the function, assignment of the function to the scope, and the content traversal happen in the same node. In order to handle this, there is a value available in nodedefinitions that allows a lambda function (or reference to a function in validator.testcases.javascript.actions) to run before the branches are executed. For this function, if there is a return value, execution of the branches is skipped. Simply returning True will skip the execution step.

Two arguments are passed to the aforementioned function. The first is a reference to the traverser object. This contains the error bundler (handy for reporting errors) and the AST node.

Lastly, there is a value in the nodedefinitions file for each node type that returns whether the node returns a value. Setting this to true will cause the _traverse_node function to return the value of the lambda function from above.

Clone this wiki locally