Mr. TA
Mr. TA

Reputation: 5359

Determine which JavaScript functions were NOT called

Imagine you have a website which uses some JavaScript libraries, be it your own or third-party authored. In addition to minimizing and obfuscating, I was wondering if it would be possible to further reduce the size of the script payload by removing unused functions. Once you reach a certain level of complexity, it becomes difficult/impossible to know which functions end up getting called under all possible execution paths. And that led me to have this question: are there any tools or any ways to determine, after a period of usage of the web page, what user-defined functions (not built-in ones) have not been called?

One obvious possibility would be to wrap all function definitions in a call to a registration function, which 1) adds the function to a registrar, and 2) injects some code to mark the function as having been called. Then it would be possible to query the registrar of functions for those that haven't been called. However, this approach is extremely complicated. The best bet is to write a JavaScript code parser on the web server, which is enabled by a runtime flag ("is in JS diagnostic mode"), which captures all JavaScript responses and modifies the code accordingly. But it doesn't take a lot of imagination to realize how error prone and difficult this would be.

UPDATE: just to clarify, I'm not looking for a solution which automatically removes unused functions, I wouldn't be comfortable with that, for fear of introducing instability. Rather, development time analysis of function usage would let me choose which includes/functions to remove, so that the final solution can then be properly tested before being released.

Upvotes: 2

Views: 490

Answers (1)

Jörg W Mittag
Jörg W Mittag

Reputation: 369458

Once you reach a certain level of complexity, it becomes difficult/impossible to know which functions end up getting called under all possible execution paths.

There is no need for the slash in "difficult/impossible". Dead code elimination is equivalent to solving the Halting Problem and is thus straight-up impossible, period.

are there any tools or any ways to determine, after a period of usage of the web page, what user-defined functions (not built-in ones) have not been called?

This sits somewhere in between profiling (determining which parts of the code have been executed how often) and coverage analysis (determining which parts of the code have been executed at all). More precisely, it sounds like you are looking for function coverage.

Note that code coverage is often talked about in the context of testing, so much so that for some people the terms "code coverage" and "test coverage" are synonymous, but code coverage has nothing to do with testing. It simply refers to the question "when I run a specific workload, which parts of the code get executed?"

There are different was how to interpret the term "part" in the question above, and they give rise to different kinds of coverage:

  • line coverage: which lines get executed?
  • function coverage: which functions get executed? (This is the one you are interested in.)
  • statement coverage: which statements get executed?
  • expression coverage: which expressions get executed?
  • branch coverage: which branches of a conditional get executed?
  • path coverage: which paths through a block get executed?

Line coverage is typically not useful because you can change the number simply by re-formatting your code. In fact, in ECMAScript, I can trivially always make it so that all lines are executed by simply writing everything on one line.

The difference between branch coverage and path coverage can be exemplified by this piece of code:

function foo(bar, baz) {
  if (bar) {
    console.log("bar");
  } else {
    console.log("no bar");
  }

  if (baz) {
    console.log("baz");
  } else {
    console.log("no baz");
  }
}

In order to cover all branches, I need two calls:

foo(true,  false);
foo(false, true);

In order to cover all possible paths through the function, I need four calls:

foo(true,  true);
foo(true,  false);
foo(false, true);
foo(false, false);

If you do heuristic dead code elimination based on function coverage, you will be able to delete unused functions. If you do it based on branch coverage, you will be able to even delete unused branches from if or switch statements.

If you are looking for code coverage tools, you will probably have to look in the testing space, since that is where they are most often used. And you will probably have to write your own dead code elimination tool, or maybe patch an existing minifier so that it can read a log file from a code coverage run and make decisions based on that.

As far as I know, there are no existing tools to do that, although I might be wrong.

Upvotes: 2

Related Questions