如何为给定的JavaScript生成调用图?

45

为什么不使用开发工具内置的支持来分析 JavaScript 的性能? - tusharmath
1
看起来原始线程已经消失,链接现在已经失效了。 :-( - Simon East
1
看看这个。https://github.com/cheburakshu/Javascript-Explorer-Callgraph - Sud
1
问题中链接的帖子早已被删除。可以在这里找到其最新的存档版本链接。 - OfirD
嗨,beatak,经过所有的建议,你最终选择或推荐了什么?你能否在 https://stackoverflow.com/questions/62613726/ 这里给一个快速的总结呢?谢谢。 - xpt
https://github.com/Persper/js-callgraph 看起来很现代。 - Bergi
4个回答

21
code2flow 正是做这件事情的。 全部披露,这个项目是我开始的。
要运行:
$ code2flow source1.js source2.js -o out.gv

然后,使用Graphviz打开out.gv文件。

编辑:目前该项目未得到维护。在使用code2flow之前,建议尝试其他解决方案。


太棒了。如果它可以在jQuery上运行,那么它肯定也能处理我的项目。我一定会尝试的。谢谢!!! - beatak
3
@scottmrogowski,你的项目对我非常有效。对于任何其他使用此解决方案的人,我想指出这个页面,它可以将Graphviz转换为yEd可打开的文件。Scott,我调整了您的Python脚本以根据函数名称命名节点,并且它产生了很好的yEd可读输出。 - John Walthour
不幸的是,这个项目似乎没有得到维护。我无法让code2flow在我的Windows和Linux笔记本电脑上正常工作。 - Achille
1
@achille 你说得对,这个代码已经没有维护了,我现在已经更新了我的回答。我是在Mac上开发的,并在Mac和Debian系统上进行了测试。Windows用户在使用此代码时可能会遇到麻烦。你可能需要验证一下你是否使用的是Python 2.7x而不是3.x。 - scottmrogowski
1
它在Windows上运行:
  1. 安装Graphviz:https://graphviz.gitlab.io/_pages/Download/windows/graphviz-2.38.msi
  2. 将以下内容添加到Windows环境变量PATH中,即C:\Program Files (x86)\Graphviz2.38\bin
  3. cd C:\code2flow-master\code2flow-master python setup.py install python code2flow myfile.js -o myfile.jpeg
然而,它不理解JavaScript中的asyc.parallel和其他一些基本场景,因此对这些场景来说几乎没有用处。
- Manohar Reddy Poreddy

6
如果您筛选closure --print_tree的输出,您将获得所需内容。
例如,取以下文件:
var fib = function(n) {
    if (n < 2) {
        return n;
    } else {
        return fib(n - 1) + fib(n - 2);
    }
};

console.log(fib(fib(5)));

过滤 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 

您可以查看所有的调用语句。

我编写了以下脚本来实现这一功能。

./call_tree

#! /usr/bin/env sh
function make_tree() {
    closure --print_tree $1 | grep $1
}

function parse_tree() {
    gawk -f parse_tree.awk
}

if [[ "$1" = "--tree" ]]; then
    make_tree $2
else
    make_tree $1 | parse_tree
fi

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]
}

这是非常有趣的方法。我会再多挖掘一下,但谢谢!! - beatak
有没有一种方法可以获取脚本中评估的每个函数调用的行号? - Anderson Green

4

我最终使用 UglifyJS2Dot/GraphViz 成功了,这是上面答案和相关问题的答案的一种组合。

对我来说缺失的部分是如何过滤解析出的 AST。事实证明,UglifyJS 有 TreeWalker 对象,它基本上将一个函数应用于 AST 的每个节点。目前我的代码如下:

//to be run using nodejs
var UglifyJS = require('uglify-js')
var fs = require('fs');
var util = require('util');

var file = 'path/to/file...';
//read in the code
var code = fs.readFileSync(file, "utf8");
//parse it to AST
var toplevel = UglifyJS.parse(code);
//open the output DOT file
var out = fs.openSync('path/to/output/file...', 'w');
//output the start of a directed graph in DOT notation
fs.writeSync(out, 'digraph test{\n');

//use a tree walker to examine each node
var 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 AST
toplevel.walk(walker);

//finally, write out the closing bracket
fs.writeSync(out, '}');

我用node运行它,然后通过dot -Tpng -o graph_name.png dot_file_name.dot输出。
注意:
它只提供了一个非常基本的图表——只有黑白两色,没有任何格式。
它无法捕获ajax,而且可能也无法捕获像evalwith这样的内容,正如其他人所提到的的那样。
此外,它的图表包括:被其他函数调用的函数(因此调用其他函数的函数),独立调用的函数和被定义但未被调用的函数。
由于所有这些原因,它可能会错过一些相关的内容,或者包含一些不相关的内容。不过,它是一个起点,并似乎达到了我最初追求的目标,也正是因为这个目标,我才来到这个问题。

有趣。它确实为一个简单的JavaScript生成了调用图。感谢您的努力!(附注:最近我开始使用Esprima http://esprima.org/来挖掘这个领域,Esprima非常有趣。) - beatak
@beatak,你能用esprima生成类似的图表吗? - tomasz

1

https://github.com/mishoo/UglifyJS提供了JavaScript中的AST访问。

ast.coffee

util = require 'util'
jsp = require('uglify-js').parser

orig_code = """

var a = function (x) {
  return x * x;
};

function b (x) {
  return a(x)
}

console.log(a(5));
console.log(b(5));

"""

ast = jsp.parse(orig_code)

console.log util.inspect ast, true, null, true

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接