2012-04-16 12 views
29

He visto "https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript", y lo he intentado. Funciona bien, si desea obtener un árbol de sintaxis abstracta.¿Cómo generar gráficos de llamadas para javascript dado?

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 cuadro de qué función llama a qué otras funciones.

+0

+1 gran pregunta - – miku

+0

¿Por qué no utilizas las herramientas de desarrollo integradas para el perfil javascript? – Tushar

+0

Parece que el hilo original se ha ido y el enlace ahora está roto. :-( –

Respuesta

5

Si filtra la salida de closure --print_tree obtendrá lo que desea.

por ejemplo, adoptar 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))); 

filtrar la salida de 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 se puede ver todos los estados de llamada.

Escribí los siguientes scripts 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] 
} 
+0

Este es un enfoque muy interesante. Excavaré un poco más, ¡pero gracias! – beatak

+0

¿Hay alguna manera de obtener el número de línea de cada llamada de función que se evalúa en un script? –

2

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 
4

fin he conseguido esto utilizando 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 node, y luego poner la salida a través

dot -Tpng -o graph_name.png dot_file_name.dot

Notas:

Se da una gráfica bastante básico - solo en blanco y negro y sin formato.

No atrapa ajax en absoluto, y presumiblemente no cosas como eval o with tampoco, como others have mentioned.

También, tal como está, incluye en el gráfico: funciones llamadas por otras funciones (y por lo tanto funciones que llaman a otras funciones), funciones que se llaman de forma independiente, Y funciones definidas pero no llamadas.

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.

+0

Interesante. Hace un gráfico de llamada para un javascript simple. Gracias por su esfuerzo (nota al margen: Recientemente empecé a cavar esta área con Esprima http: // esprima.org/ y Esprima es tan interesante.) – beatak

15

code2flow hace exactamente esto. La revelación completa, empecé este proyecto

Para ejecutar

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

Entonces, out.gv abierto con graphviz

Editar: Por ahora, este proyecto es sin mantenimiento. Sugeriría probar una solución diferente antes de usar code2flow.

+0

Eso es increíble. Si se puede ejecutar en jQuery, también debe ser capaz de manejar mis proyectos. Definitivamente lo intentaré. ¡Gracias! – beatak

+1

@scottmrogowski, su proyecto funcionó muy bien para mí. Para cualquier otra persona que use esta solución, me gustaría señalar [esta página] (http://dl9obn.darc.de/programming/python/dottoxml/) que convierte graphviz a archivos que yEd puede abrir. Scott, pellizqué tu pitón script para nombrar los nodos en función de los nombres de las funciones, y produjo una excelente salida legible en yEd. –

+0

Desafortunadamente, parece que el proyecto no se mantuvo. No pude hacer que code2flow solo funcionara en mi computadora portátil con Windows y Linux. – Achille

Cuestiones relacionadas