Comment générer des call-graphs pour javascript donné? [fermé]

j'ai vu " https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript , et essayé. Il fonctionne bien, si vous voulez obtenir un arbre de syntaxe abstraite.

malheureusement, le compilateur de fermeture ne semble offrir que --print_tree , --print_ast et --print_pass_graph . Aucun d'entre eux sont utiles pour moi.

je veux voir un graphique de quelle fonction appelle quelles autres fonctions.

31
demandé sur Community 2012-04-17 02:05:39

4 réponses

code2flow fait exactement cela. Divulgation complète, j'ai commencé ce projet

courir

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

alors, ouvrez.gv avec graphviz

Modifier : Pour l'instant, ce projet n'est plus maintenu. Je suggère d'essayer une solution différente avant d'utiliser code2flow.

16
répondu scottmrogowski 2016-03-21 18:37:55

Si vous filtrez la sortie de closure --print_tree vous obtenez ce que vous voulez.

par exemple, prendre le fichier suivant:

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

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

filtrer la sortie 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 

et vous pouvez voir toutes les instructions d'appel.

j'ai écrit les scripts suivants pour le faire.

./call_tree

#! /usr/bin/env sh
function make_tree() {
    closure --print_tree  | grep 
}

function parse_tree() {
    gawk -f parse_tree.awk
}

if [[ "" = "--tree" ]]; then
    make_tree 
else
    make_tree  | 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("151930920")
    root_indent = indent_offset - 1
}

/FUNCTION/ {
    pl  = get_previous_line()
    if (calculate_indent(pl) < calculate_indent("151930920"))
        print pl
    print
}

{
    lines_v[lines_c] = "151930920"
    lines_c += 1
}

{
    indent = calculate_indent("151930920")
    if (indent <= call_indent) {
        calling = 0
    }
    if (calling) {
        print
    }
}

/CALL/ {
    calling = 1
    call_indent = calculate_indent("151930920")
    print
}

/EXPR/{
    line_indent = calculate_indent("151930920")
    if (line_indent == root_indent) {
        if ("151930920" !~ /(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]
}
6
répondu William Bettridge-Radford 2015-09-24 00:52:32

j'ai finalement réussi cela en utilisant UglifyJS2 et Dot/GraphViz , dans une sorte de combinaison de la réponse ci-dessus et les réponses à la question liée.

la partie manquante, pour moi, était comment filtrer le parsed AST. Il s'avère que UglifyJS a L'objet TreeWalker, qui applique essentiellement une fonction à chaque noeud de L'AST. C'est le code que j'ai jusqu'à présent:

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

Je l'exécute avec noeud , puis mettre la sortie à travers

dot -Tpng -o graph_name.png dot_file_name.dot

Notes:

il donne un graphique assez basique - seulement noir et blanc et aucun formatage.

il n'attrape pas ajax du tout, et probablement pas des trucs comme eval ou with soit, comme d'autres ont mentionné .

aussi, en l'état il inclut dans le graphique: les fonctions appelées par d'autres fonctions (et par conséquent les fonctions qui appellent d'autres fonctions), les fonctions qui sont appelées de façon indépendante, et les fonctions qui sont définies mais non appelées.

en conséquence de tout cela, il peut manquer des choses qui sont pertinentes, ou inclure des choses qui ne sont pas. C'est un début cependant, et il semble accomplir ce que j'étais après, et ce qui m'a conduit à cette question en premier lieu.

5
répondu rorold 2017-05-23 12:32:29

https://github.com/mishoo/UglifyJS donne accès à un ast en javascript.

ast.café 151940920"

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
2
répondu William Bettridge-Radford 2012-04-19 16:44:04