2012-04-16 16 views
29

Widziałem "https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript" i wypróbowałem to. Działa dobrze, jeśli chcesz uzyskać abstrakcyjne drzewo składniowe.Jak generować wykresy połączeń dla danego javascript?

Niestety kompilator zamknięcia wydaje się oferować tylko --print_tree, --print_ast i --print_pass_graph. Żadne z nich nie jest dla mnie użyteczne.

Chcę zobaczyć wykres, którego funkcja wywołuje inne funkcje.

+0

+1 świetne pytanie - – miku

+0

Dlaczego nie używasz narzędzi programistycznych wbudowanych w obsługę profilowania javascript? – Tushar

+0

Wygląda na to, że oryginalny wątek zniknął, a link jest teraz uszkodzony. :-( –

Odpowiedz

5

Po odfiltrowaniu wyjścia z closure --print_tree otrzymasz to, co chcesz.

Na przykład następujący plik:

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

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

Filter wyjście 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 

i można zobaczyć wszystkie instrukcje połączeń.

Napisałem następujące skrypty, aby to zrobić.

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

To jest bardzo interesujące podejście. Będę kopał trochę więcej, ale dzięki !! – beatak

+0

Czy istnieje sposób na uzyskanie numer wiersza każdego wywołania funkcji, który jest oceniany w skrypcie? –

2

https://github.com/mishoo/UglifyJS daje dostęp do ast w 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

udało mi to za pomocą UglifyJS2 i Dot/GraphViz, w rodzaju kombinacja powyższych odpowiedzi i odpowiedzi na pytanie powiązanego.

Brakujące części, dla mnie, było, jak filtrować analizowane AST. Okazuje się, że UglifyJS ma obiekt TreeWalker, który zasadniczo stosuje funkcję do każdego węzła AST. Jest to kod mam tak daleko:

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

go uruchomić z node, a następnie umieścić wyjście poprzez

dot -Tpng -o graph_name.png dot_file_name.dot

Uwagi:

Daje to dość podstawowy wykres - tylko czarno-biały i bez formatowania.

W ogóle nie przechwytuje ajaxa, i prawdopodobnie nie ma takich rzeczy, jak eval lub with albo jako others have mentioned.

Ponadto, w swoim obecnym stanie, zawiera on na wykresie: funkcje wywoływane przez inne funkcje (i w konsekwencji funkcje, które wywołują inne funkcje), funkcje, które są nazywane niezależnie, funkcje AND, które są zdefiniowane, ale nie wywoływane.

W wyniku tego wszystkiego może on ominąć rzeczy, które są istotne, lub zawierać rzeczy, które nie są istotne.Jest to jednak początek i wydaje się, że udało mi się osiągnąć to, o co prosiłem, i co doprowadziło mnie do tego pytania w pierwszej kolejności.

+0

Interesujący, tworzy wykres wywołań dla prostego javascriptu. Dzięki za twoje wysiłki! (uwaga strony: Ostatnio zacząłem kopać ten obszar z Esprima http: // esprima.org/ i Esprima jest bardzo interesująca.) – beatak

15

code2flow robi dokładnie to. Pełne ujawnienie, zacząłem ten projekt

Aby uruchomić

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

Następnie otwórz out.gv z graphviz

Edit: Na razie projekt jest unmaintained. Proponuję wypróbować inne rozwiązanie przed użyciem code2flow.

+0

To niesamowite, jeśli może działać na jQuery, to musi też być w stanie obsłużyć moje projekty, na pewno spróbuję, dziękuję !!! – beatak

+1

@scottmrogowski, twoje Projekt zadziałał dla mnie bardzo dobrze, dla każdego, kto używa tego rozwiązania, chciałbym wskazać [tę stronę] (http://dl9obn.darc.de/programming/python/dottoxml/), który konwertuje graphviz na pliki, które YES mogę otworzyć, Scott, poprawiłem twojego pytona skrypt do nazwania węzłów w oparciu o nazwy funkcji i wygenerował świetne wyjście czytelne dla odczytu. –

+0

Niestety, wygląda na to, że projekt nie został utrzymany. Nie udało mi się zrobić code2flow po prostu pracując na moim laptopie z systemem Windows i Linux. – Achille

Powiązane problemy