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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10182387/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 08:56:07  来源:igfitidea点击:

How to generate call-graphs for given javascript?

javascriptgraphvisualizationcall-graph

提问by beatak

I have seen "https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript", and tried it. It works well, if you want to get an abstract syntax tree.

我看过“ https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript”,并尝试过。如果您想获得抽象语法树,它运行良好。

Unfortunately Closure Compiler only seems to offer --print_tree, --print_astand --print_pass_graph. None of them are useful for me.

不幸的是,Closure Compiler 似乎只提供--print_tree,--print_ast--print_pass_graph. 它们对我都没有用。

I want to see a chart of which function calls which other functions.

我想查看哪些函数调用哪些其他函数的图表。

回答by scottmrogowski

code2flowdoes exactly this. Full disclosure, I started this project

code2flow正是这样做的。完全公开,我开始了这个项目

To run

跑步

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

Then, open out.gv with graphviz

然后,用graphviz打开out.gv

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

编辑:目前,这个项目是无人维护的。我建议在使用 code2flow 之前尝试不同的解决方案。

回答by William Bettridge-Radford

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

如果你过滤你的输出,closure --print_tree你会得到你想要的。

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

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

./call_tree

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

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

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

parse_tree.awk

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(
//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, '}');
) root_indent = indent_offset - 1 } /FUNCTION/ { pl = get_previous_line() if (calculate_indent(pl) < calculate_indent(
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
)) print pl print } { lines_v[lines_c] = ##代码## lines_c += 1 } { indent = calculate_indent(##代码##) if (indent <= call_indent) { calling = 0 } if (calling) { print } } /CALL/ { calling = 1 call_indent = calculate_indent(##代码##) print } /EXPR/{ line_indent = calculate_indent(##代码##) if (line_indent == root_indent) { if (##代码## !~ /(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] }

回答by rorold

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

我最终使用UglifyJS2Dot/GraphViz管理了这个,上面的答案和链接问题的答案的组合。

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:

对我来说,缺少的部分是如何过滤解析后的 AST。原来 UglifyJS 有 TreeWalker 对象,它基本上对 AST 的每个节点应用了一个函数。这是我到目前为止的代码:

##代码##

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

我用node运行它,然后通过输出

dot -Tpng -o graph_name.png dot_file_name.dot

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 evalor witheither, as others have mentioned.

它根本没有捕获 ajax,并且可能不会像其他人提到的那样evalwith两者之一

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.

由于所有这些,它可能会遗漏相关的事物,或包括不相关的事物。不过,这是一个开始,似乎完成了我所追求的目标,以及最初是什么让我想到了这个问题。

回答by William Bettridge-Radford

https://github.com/mishoo/UglifyJSgives access to an ast in javascript.

https://github.com/mishoo/UglifyJS允许访问 javascript 中的 ast。

ast.coffee

咖啡

##代码##