-
Notifications
You must be signed in to change notification settings - Fork 41
JavaScript Tests Module
The JS test module is very complicated and crazy, yet entirely unfinished. Here is what is going on with it.
Since there is currently no way of pushing code directly into Tracemonkey for reflection/parsing, the best solution works in the following way:
- JS is encountered!
- For files, the file’s path is used as its identifier.
- For snippets of JS (i.e.: script tags, script attributes, etc), a hash of the code is used as its identifier. The file from which it came is used for the source filename.
- Does the AST tree for the JS’s identifier exist in the
testfiles/js_ast/directory?- If the AST file exists, just open it and proceed to the testing of the tree.
- Put the JS code in the file
testfiles/js/<identifier>.
It is then the user’s responsibility to do the following:
- Upload the contents of
/testfiles/js/to khan into/home/mbasta/stage. - Run
python makejs.pyin/home/mbasta. - Download the contents of
/home/mbasta/outputto yourtestfiles/js_astdirectory.
The next time the validator is run on the add-on, it should load the trees as if it was able to contact Tracemonkey on its own.
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.
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.
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.