Подтвердить что ты не робот

Как сгенерировать диаграммы вызовов для данного javascript?

Я видел "https://stackoverflow.com/info/1385335/how-to-generate-function-call-graphs-for-javascript" и пробовал. Он работает хорошо, если вы хотите получить абстрактное синтаксическое дерево.

К сожалению, компилятор Closure, похоже, предлагает --print_tree, --print_ast и --print_pass_graph. Ни один из них не полезен для меня.

Я хочу увидеть диаграмму, какая функция вызывает другие функции.

4b9b3361

Ответ 1

code2flow делает именно это. Полное раскрытие, я начал этот проект

Для запуска

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

Затем откройте .gv с помощью graphviz

Изменить. На данный момент этот проект не поддерживается. Я бы предложил попробовать другое решение, прежде чем использовать code2flow.

Ответ 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, в виде комбинации вышеупомянутого ответа и ответов на связанный вопрос.

Недостающая часть для меня - это то, как фильтровать анализируемый АСТ. Оказывается, UglifyJS имеет объект TreeWalker, который в основном применяет функцию к каждому node 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 in the DOT file.
                //It 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 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 вообще, и, предположительно, не такие вещи, как eval или with, как упомянули другие.

Кроме того, в его основе он включает в себя: функции, называемые другими функциями (и, следовательно, функциями, вызывающими другие функции), функциями, которые называются независимыми, и функциями, которые определены, но не вызваны.

В результате всего этого он может пропустить то, что имеет значение, или включать в себя то, что нет. Это начало, хотя и, похоже, выполняет то, что мне нужно, и что привело меня к этому вопросу в первую очередь.

Ответ 4

https://github.com/mishoo/UglifyJS дает доступ к астрам в javascript.

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