如何为给定的javascript生成调用图? [关闭]

Posted

技术标签:

【中文标题】如何为给定的javascript生成调用图? [关闭]【英文标题】:How to generate call-graphs for given javascript? [closed] 【发布时间】:2012-04-28 06:29:42 【问题描述】:

我看过“https://***.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript”,并尝试过。如果你想得到一个抽象的语法树,它工作得很好。

不幸的是,闭包编译器似乎只提供--print_tree--print_ast--print_pass_graph。它们都对我没有用。

我想看看哪个函数调用了其他函数的图表。

【问题讨论】:

为什么不使用内置支持分析 javascript 的开发工具? 看来原来的线程已经消失了,链接现在被破坏了。 :-( 看看这个。 github.com/cheburakshu/Javascript-Explorer-Callgraph 该问题上链接的帖子很久以前就被删除了。可以找到指向其最新存档版本的链接here。 嗨,beatak,在浏览了所有建议之后,您有什么解决方案或建议?请您在***.com/questions/62613726 做一个简短的总结好吗?谢谢。 【参考方案1】:

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

运行

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

然后,用 graphviz 打开 out.gv

编辑:目前,该项目尚未维护。我建议在使用 code2flow 之前尝试不同的解决方案。

【讨论】:

太棒了。如果它可以在 jQuery 上运行,它也必须能够处理我的项目。我一定会试试的。谢谢!!! @scottmrogowski,你的项目对我来说效果很好。对于使用此解决方案的其他任何人,我想指出 this page 它将 graphviz 转换为 yEd 可以打开的文件。斯科特,我调整了你的 python 脚本以根据函数名称命名节点,它产生了很好的 yEd 可读输出。 不幸的是,该项目似乎无人维护。我无法让 code2flow 仅在我的 Windows 和 Linux 笔记本电脑上运行。 @achille 你是对的,这是无人维护的,我现在已经更新了我的答案。我在 Mac 上开发了它,并在 Mac 和 Debian 系统上进行了测试。 Windows 用户无法使其正常工作。您可能想要验证您使用的是 Python 2.7x 而不是 3.x。 它适用于windows: 1. 安装graphviz: graphviz.gitlab.io/_pages/Download/windows/graphviz-2.38.msi 2. 在下面添加到Windows Environment 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 但是它不理解 asyc.parallel 和 javascript 中的其他内容,因此对于这些基本场景几乎没有用处。【参考方案2】:

如果你过滤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]

【讨论】:

这是非常有趣的方法。我会再挖一点,但是谢谢!! 有没有办法获取脚本中每个函数调用的行号?【参考方案3】:

我最终使用UglifyJS2 和Dot/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 之类的东西,如 others have mentioned。

此外,就目前而言,它在图中包括:由其他函数调用的函数(以及因此调用其他函数的函数)、独立调用的函数以及已定义但未调用的函数。

因此,它可能会遗漏相关的内容,或包含不相关的内容。虽然这是一个开始,而且似乎完成了我所追求的目标,也是最初导致我提出这个问题的原因。

【讨论】:

有趣。它确实为一个简单的 javascript 创建了一个调用图。感谢您的努力! (旁注:最近我开始用 Esprima esprima.org 挖掘这个区域,而 Esprima 非常有趣。) @beatak 您是否设法使用 esprima 生成了类似的图表?【参考方案4】:

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

【讨论】:

以上是关于如何为给定的javascript生成调用图? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Roslyn代码生成,如何为给定类型创建一个类型表达式。

如何为所有动态生成的 div 添加常用的 Javascript 函数? [复制]

如何为十亿张 png 图像生成统一的缩略图?

如何为每个班级和小组生成单独的小提琴图

如何为 JavaScript 生成的所有 div 放置一个 EventListener

如何为多个元素调用javascript函数