2012-04-16 12 views
29

Ho visto "https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript" e l'ho provato. Funziona bene, se vuoi ottenere un albero di sintassi astratto.Come generare call-graphs per un determinato javascript?

Purtroppo Closure Compiler sembra offrire solo --print_tree, --print_ast e --print_pass_graph. Nessuno di loro è utile per me.

Voglio vedere un grafico di quale funzione chiama quali altre funzioni.

+0

+1 ottima domanda - – miku

+0

Perché non usi gli strumenti di sviluppo integrati nel supporto per la creazione di profili di javascript? – Tushar

+0

Sembra che il thread originale sia andato e il link sia ora rotto. :-( –

risposta

5

Se si filtra l'output di closure --print_tree si ottiene ciò che si desidera.

Per esempio prendere il seguente file:

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

console.log(fib(fib(5))); 

filtrare l'output di 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 

e si può vedere tutte le dichiarazioni di chiamata.

Ho scritto i seguenti script per fare questo.

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

Questo è un approccio molto interessante. Scoverò un po 'di più, ma grazie !! – beatak

+0

C'è un modo per ottenere il numero di linea di ogni chiamata di funzione che viene valutata in uno script? –

2

https://github.com/mishoo/UglifyJS dà accesso ad un ast in 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

sono finalmente riuscito questo utilizzando UglifyJS2 e Dot/GraphViz, in una sorta di combinazione tra la risposta di cui sopra e le risposte alla domanda collegata.

La parte mancante, per me, era come filtrare l'AST analizzato. Si scopre che UglifyJS ha l'oggetto TreeWalker, che sostanzialmente applica una funzione a ciascun nodo dell'AST. Questo è il codice che ho finora:

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

l'eseguo con node, e poi mettere l'uscita attraverso

dot -Tpng -o graph_name.png dot_file_name.dot

Note:

Si dà un grafico piuttosto semplice - solo in bianco e nero e senza formattazione.

Non cattura affatto ajax e presumibilmente non corrisponde a eval o with, come others have mentioned.

Inoltre, come al solito include nel grafico: funzioni chiamate da altre funzioni (e di conseguenza funzioni che chiamano altre funzioni), funzioni chiamate indipendenti, funzioni AND definite ma non chiamate.

Come risultato di tutto ciò, possono mancare le cose che sono rilevanti o includere cose che non lo sono.È comunque un inizio e sembra realizzare ciò che cercavo, e ciò che mi ha portato a questa domanda in primo luogo.

+0

Interessante. Fa un grafico di chiamata per un semplice javascript. Grazie per i tuoi sforzi! (nota a margine: Recentemente ho iniziato a scavare questa area con Esprima http: // esprima.org/ ed Esprima è così interessante.) – beatak

15

code2flow fa esattamente questo. L'informativa completa, ho iniziato questo progetto

Per eseguire

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

Poi, out.gv aperto con graphviz

Edit: Per ora, questo progetto è più mantenuto. Suggerirei di provare una soluzione diversa prima di utilizzare code2flow.

+0

È fantastico. Se può essere eseguito su jQuery, deve essere in grado di gestire anche i miei progetti. Lo proverò sicuramente. Grazie !!! – beatak

+1

@ scottmrogowski, il tuo il progetto ha funzionato molto bene per me. Per chiunque altro utilizzi questa soluzione, vorrei sottolineare [questa pagina] (http://dl9obn.darc.de/programming/python/dottoxml/) che converte graficviz in file che yEd posso aprire Scott, ho ottimizzato il tuo pitone script per nominare i nodi in base ai nomi delle funzioni, e ha prodotto un ottimo output leggibile tramite YEd. –

+0

Sfortunatamente, sembra che il progetto non sia stato mantenuto. Non ero in grado di rendere code2flow solo lavorando sul mio portatile Windows e Linux. – Achille