style body attribute javascript graph visualization call-graph

body - ¿Cómo generar call-graphs para javascript dado?



title css (4)

He visto " https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript " y lo he probado. Funciona bien, si desea obtener un árbol de sintaxis abstracta.

Desafortunadamente, el compilador de cierre solo parece ofrecer --print_tree , --print_ast y --print_pass_graph . Ninguno de ellos es útil para mí.

Quiero ver un gráfico de qué función llama qué otras funciones.


Finalmente logré esto usando UglifyJS2 y Dot/GraphViz , en una especie de combinación de la respuesta anterior y las respuestas a la pregunta vinculada.

La parte que faltaba, para mí, era cómo filtrar el AST analizado. Resulta que UglifyJS tiene el objeto TreeWalker, que básicamente aplica una función a cada nodo del AST. Este es el código que tengo hasta ahora:

//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, ''}'');

Lo ejecuto con un node y luego paso la salida

dot -Tpng -o graph_name.png dot_file_name.dot

Notas:

Proporciona un gráfico bastante básico, solo en blanco y negro y sin formato.

No atrapa ajax en absoluto, y presumiblemente no cosas como eval o with cualquiera, como otros lo han mencionado .

Además, tal como está, incluye en el gráfico: funciones llamadas por otras funciones (y, en consecuencia, funciones que llaman a otras funciones), funciones que se llaman de forma independiente, AND funciones que se definen pero no se llaman.

Como resultado de todo esto, puede perder cosas que son relevantes, o incluir cosas que no lo son. Sin embargo, es un comienzo, y parece lograr lo que buscaba, y lo que me llevó a esta pregunta en primer lugar.


Si filtra la salida del closure --print_tree obtiene lo que desea.

Por ejemplo, tome el siguiente archivo:

var fib = function(n) { if (n < 2) { return n; } else { return fib(n - 1) + fib(n - 2); } }; console.log(fib(fib(5)));

Filtra la salida del 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

Y puedes ver todas las declaraciones de llamadas.

Escribí los siguientes guiones para hacer esto.

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


https://github.com/mishoo/UglifyJS da acceso a un ast en 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


code2flow hace exactamente esto. Divulgación completa, comencé este proyecto

Correr

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

Luego, abre out.gv con graphviz

Editar : por ahora, este proyecto no se ha mantenido. Sugeriría probar una solución diferente antes de usar code2flow.