7

In JavaScript, is it possible to obtain a list of all functions that are called by another function? I want to create a tree of function dependencies, to analyze how the functions in a script are related to each other (and which functions are required by which other functions).

For example:

getAllCalledFunctions(funcA); //this should return [funcB, funcC, funcD], since these are the functions that are required by funcA.

function getAllCalledFunctions(functionName){
    //how should I implement this?
}

function funcA(){
    funcB();
    funcC();
}

function funcB(){
    funcD();
}

function funcC(){
    funcD();
}

function funcD(){
    console.log("This function is called by funcC and funcD");
}
Anderson Green
  • 30,230
  • 67
  • 195
  • 328
  • I wonder if it would be possible to do something like this using a dead-code removal tool. I could simply define all the functions that I would need, and only call the function whose required functions I needed. Then I'd use the dead-code removal tool to remove all the functions that weren't being used in the script. – Anderson Green Mar 16 '13 at 04:59
  • 1
    what are you trying to achieve here? – smk Mar 16 '13 at 05:00
  • https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee – Derek 朕會功夫 Mar 16 '13 at 05:00
  • @smk I want to create a tree of function dependencies, to analyze how the functions in a script are related to each other. – Anderson Green Mar 16 '13 at 05:01
  • @AndersonGreen there really is not going to be a way to do this that isn't extremely complicated, if there's any way at all. You probably should look for another avenue by which to achieve your overall strategic goal. – Pointy Mar 16 '13 at 05:05
  • @Pointy I'll search for a duplicate of this question, then. It might be a bit tricky to find duplicates of this esoteric question, but I'm sure I'll find one eventually. Maybe I'll find a duplicate of this question here: https://www.google.com/#hl=en&sclient=psy-ab&q=javascript+function+(dependency%7Crequirement)+graph&oq=javascript+function+(dependency%7Crequirement)+graph&gs_l=serp.3...9100.16458.0.16989.16.15.0.0.0.3.107.915.13j2.15.0.les%3B..0.0...1c.1.6.psy-ab.2QZcAjgexf8&pbx=1&bav=on.2,or.r_cp.r_qf.&fp=c88c58e01e24d8b1&biw=1366&bih=639 – Anderson Green Mar 16 '13 at 05:06
  • 1
    Without modifying your functions or making it really complicated , I dont know of any. This may look stupid but atleast using an IDE like IntelliJ you can get a reasonable idea of which functions call what.. Not saying its the right way.. but perhaps a little practical? – smk Mar 16 '13 at 05:08
  • 2
    Yes there is a way - parse the JavaScript into an abstract syntax tree, step through the body of each function and recursively add each function being called inside the body to a list if it's not already in the list (omitting the current function itself in case of recursive or mutually recursive functions). There are lots of JavaScript parsers written in JavaScript. For example: [acorn](https://github.com/marijnh/acorn) – Aadit M Shah Mar 16 '13 at 05:09
  • @AaditMShah but because JavaScript is a dynamic language, you can't even do that unless you can predict the entirety of variation based on specific instances, user input, browser differences, etc. It's basically impossible. – Pointy Mar 16 '13 at 05:11
  • @Pointy - Oooh, yes that's a problem. However you can still get a partial list of function names from the current function itself. – Aadit M Shah Mar 16 '13 at 05:13
  • Take a look at [Tern](http://ternjs.net/ "Tern"). It looks pretty promising. Perhaps you can browse through the source code and pick out some routines you like. Kind of like going to the grocery. – Aadit M Shah Mar 16 '13 at 05:19

4 Answers4

15

Esprima may help you. It is a Javascript parser that can help you do static code analysis.

Here's a quick example (http://jsfiddle.net/fyBvT/):

var code = 'function funcA() { funcB(); funcC(); } function funcB(){ funcD(); } function funcC() { funcD(); } function funcD(){ console.log("This function is called by funcC and funcD"); }';
var syntax = esprima.parse(code);

var funcs = [];
_.each(syntax.body, function(i) {
    if (i.type == 'FunctionDeclaration') {
        var func = {name: i.id.name};

        _.each(i.body.body, function(j) {
            if (j.type == 'ExpressionStatement' && j.expression.type == 'CallExpression') {
                func.calls = func.calls || [];
                func.calls.push(j.expression.callee.name);
            }
        });

        funcs.push(func);
    }
});

console.log(funcs);

Clearly this needs a lot of help to offer much value, but it might give you some idea of what's possible and where to start.

Trevor Dixon
  • 23,216
  • 12
  • 72
  • 109
  • In Google Chrome, `console.log(funcs)` prints `Array[4]` to the console, instead of printing the contents of the array. If you wanted to print the contents of `funcs` to the console instead, you could use `console.log(JSON.stringify(funcs));`. – Anderson Green Mar 16 '13 at 15:50
  • I made similar solution, right now it analyze only function declaration and function expression calls https://github.com/sanex3339/javascript-obfuscator/blob/dev/src/StackTraceAnalyzer.ts – Качалов Тимофей Oct 03 '16 at 08:12
2

Interesting question. I too question the motive behind it... Hopefully it's just for debugging or understanding the structure of the application better.

Here's a WILD idea: Just throwing it out there...

If you could tie into each function, you can get the callee by:

arguments.callee.name

And write that to a global variable (perhaps an object with each key being the name of the function, and the value being an array of function names).

Community
  • 1
  • 1
Aaron Blenkush
  • 3,034
  • 2
  • 28
  • 54
  • That's precisely what I thought of originally, but there are a few problems. Firstly, you don't want to change every single function. You might as well draw the dependency graph by hand. Second, I think the OP wants to statically determine the dependency graph. This solution will only work when the program is executed, and then again only if all the functions are called by all possible functions that can call it to make a complete graph. That's like processing all possible code paths in the program in parallel. JavaScript is not a non-deterministic programming language. – Aadit M Shah Mar 16 '13 at 05:28
  • @AaditMShah You're right, I thought of that too. Interesting note though, if you add a `console.dir(arguments)` to a function and view Console in Chrome, you can drilldown to `arguments.callee..Closure`, and it gives the expected functions (funcB, funcC, and funcD). I don't know how to programmatically access `` however. Must be a built-in function to devtools? – Aaron Blenkush Mar 16 '13 at 05:33
0

Basically, you can't.

Objects/Functions won't know what will they execute unless you execute them, Unless you perform regular expressions on the function's java-script code itself.. unreliable at best.

If you want to do it backwards, tracing the stack back, questions like this have solutions: How can I get a Javascript stack trace when I throw an exception?

To achieve what you are probably looking for, You could create a generic class, from which your functions inherit, with your own implemented method to assign function calls to them.

Community
  • 1
  • 1
Jaibuu
  • 578
  • 3
  • 9
  • JavaScript can't be described by regular languages. You'll need a full blown parser to crunch the code of the function body: http://www.codinghorror.com/blog/2008/06/regular-expressions-now-you-have-two-problems.html – Aadit M Shah Mar 16 '13 at 05:14
  • That's why I call it unreliable at best ;). In any way, if the purpose of the question is analyzing the code, tracing the stack has utility on it. – Jaibuu Mar 16 '13 at 05:15
-3

The obvious answer is something like the following:

var origCall = Function.prototype.call;
Function.prototype.call = function (thisArg) {
    console.log("calling a function");

    var args = Array.prototype.slice.call(arguments, 1);
    origCall.apply(thisArg, args);
};

But this actually immediately enters an infinite loop, because the very act of calling console.log executes a function call, which calls console.log, which executes a function call, which calls console.log, which...

OR

I'm assuming you want to filter out native functions. In Firefox, Function.toString() returns the function body, which for native functions, will be in the form:

function addEventListener() { 
    [native code] 
}

You could match the pattern /\[native code\]/ in your loop and omit the functions that match.

Vitthal
  • 546
  • 3
  • 18
  • Your code presumes that `.call()` is invoked when functions are called, which is not the case. – Pointy Mar 16 '13 at 05:03
  • Is there some way that you could create a demonstration of this (so that I can understand how these functions are supposed to be used)? Also, which loop are you referring to? I don't see a for-loop or while-loop anywhere in this code sample. – Anderson Green Mar 16 '13 at 05:03