Как сгенерировать диаграммы вызовов для данного javascript?
Я видел "https://stackoverflow.com/info/1385335/how-to-generate-function-call-graphs-for-javascript" и пробовал. Он работает хорошо, если вы хотите получить абстрактное синтаксическое дерево.
К сожалению, компилятор Closure, похоже, предлагает --print_tree
, --print_ast
и --print_pass_graph
. Ни один из них не полезен для меня.
Я хочу увидеть диаграмму, какая функция вызывает другие функции.
Ответы
Ответ 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