How to generate call-graphs for given javascript? [closed] How to generate call-graphs for given javascript? [closed] javascript javascript

How to generate call-graphs for given javascript? [closed]


code2flow does exactly this. Full disclosure, I started this project

To run

$ code2flow source1.js source2.js -o out.gv

Then, open out.gv with graphviz

Edit: For now, this project is unmaintained. I would suggest trying out a different solution before using code2flow.


If you filter the output of closure --print_tree you get what you want.

For example take the following file:

var fib = function(n) {    if (n < 2) {        return n;    } else {        return fib(n - 1) + fib(n - 2);    }};console.log(fib(fib(5)));

Filter the output of closure --print_tree

            NAME fib 1                 FUNCTION  1                                     CALL 5                                         NAME fib 5                                         SUB 5                                             NAME a 5                                             NUMBER 1.0 5                                     CALL 5                                         NAME fib 5                                         SUB 5                                             NAME a 5                                             NUMBER 2.0 5         EXPR_RESULT 9             CALL 9                 GETPROP 9                     NAME console 9                     STRING log 9                 CALL 9                 CALL 9                     NAME fib 9                     CALL 9                     CALL 9                         NAME fib 9                         NUMBER 5.0 9 

And you can see all the call statements.

I wrote the following scripts to do this.

./call_tree

#! /usr/bin/env shfunction make_tree() {    closure --print_tree $1 | grep $1}function parse_tree() {    gawk -f parse_tree.awk}if [[ "$1" = "--tree" ]]; then    make_tree $2else    make_tree $1 | parse_treefi

parse_tree.awk

BEGIN {    lines_c = 0    indent_width = 4    indent_offset = 0    string_offset = ""    calling = 0    call_indent = 0}{    sub(/\[source_file.*$/, "")    sub(/\[free_call.*$/, "")}/SCRIPT/ {    indent_offset = calculate_indent($0)    root_indent = indent_offset - 1}/FUNCTION/ {    pl  = get_previous_line()    if (calculate_indent(pl) < calculate_indent($0))        print pl    print}{    lines_v[lines_c] = $0    lines_c += 1}{    indent = calculate_indent($0)    if (indent <= call_indent) {        calling = 0    }    if (calling) {        print    }}/CALL/ {    calling = 1    call_indent = calculate_indent($0)    print}/EXPR/{    line_indent = calculate_indent($0)    if (line_indent == root_indent) {        if ($0 !~ /(FUNCTION)/) {            print        }    }}function calculate_indent(line) {    match(line, /^ */)    return int(RLENGTH / indent_width) - indent_offset}function get_previous_line() {    return lines_v[lines_c - 1]}


I finally managed this using UglifyJS2 and Dot/GraphViz, in a sort of combination of the above answer and the answers to the linked question.

The missing part, for me, was how to filter the parsed AST. It turns out that UglifyJS has the TreeWalker object, which basically applys a function to each node of the AST. This is the code I have so far:

//to be run using nodejsvar UglifyJS = require('uglify-js')var fs = require('fs');var util = require('util');var file = 'path/to/file...';//read in the codevar code = fs.readFileSync(file, "utf8");//parse it to ASTvar toplevel = UglifyJS.parse(code);//open the output DOT filevar out = fs.openSync('path/to/output/file...', 'w');//output the start of a directed graph in DOT notationfs.writeSync(out, 'digraph test{\n');//use a tree walker to examine each nodevar walker = new UglifyJS.TreeWalker(function(node){    //check for function calls    if (node instanceof UglifyJS.AST_Call) {        if(node.expression.name !== undefined)        {        //find where the calling function is defined        var p = walker.find_parent(UglifyJS.AST_Defun);        if(p !== undefined)        {            //filter out unneccessary stuff, eg calls to external libraries or constructors            if(node.expression.name == "$" || node.expression.name == "Number" || node.expression.name =="Date")            {                //NOTE: $ is from jquery, and causes problems if it's in the DOT file.                //It's also very frequent, so even replacing it with a safe string                //results in a very cluttered graph            }            else            {                fs.writeSync(out, p.name.name);                fs.writeSync(out, " -> ");                fs.writeSync(out, node.expression.name);                fs.writeSync(out, "\n");            }        }        else        {            //it's a top level function            fs.writeSync(out, node.expression.name);            fs.writeSync(out, "\n");        }    }}if(node instanceof UglifyJS.AST_Defun){    //defined but not called    fs.writeSync(out, node.name.name);    fs.writeSync(out, "\n");}});//analyse the ASTtoplevel.walk(walker);//finally, write out the closing bracketfs.writeSync(out, '}');

I run it with node, and then put the output through

dot -Tpng -o graph_name.png dot_file_name.dot

Notes:

It gives a pretty basic graph - only black and white and no formatting.

It doesn't catch ajax at all, and presumably not stuff like eval or with either, as others have mentioned.

Also, as it stands it includes in the graph: functions called by other functions (and consequently functions that call other functions), functions that are called independantly, AND functions that are defined but not called.

As a result of all this, it may miss things that are relevant, or include things that are not. It's a start though, and appears to accomplish what I was after, and what led me to this question in the first place.