Posted: 3 years ago

Filed under: Javascript

Tagged with: debugging favourite Javascript

Follow comments

DIY javascript stack trace

Javascript functions have a handy local variable called arguments that you can use to get information about the currently executing function. This can be used to build your own stack trace in any browser for situations where you can’t use a debugger to get information about your code.

That’s madness!

Yes it is. In most cases there are better tools like Firebug that have a full javascript debugger that you can use to get a stack trace whenever you want without having to do any tricksy javascript coding:
firebug's stack trace

I originally wrote this code because I had to debug a page that was running in the WinForms web browser component. The page worked fine when running in regular IE but totally locked up when it was running in the browser component. I was on my own debugging a huge tangle of javascript event code without any debugger to help me.

Other places this might help is if you want to log stack traces on mouse events where breaking into the debugger will stop the execution, when you’re automating browser testing or if you want to be able to silently log information a nasty to test error condition running on someone else’s browser.

The code

Here’s a stack trace implementation that adds a new debugging method of trace() to all functions. It uses the function.caller property to walk the call stack and the arguments collection to create a method signature for each function in the call stack.

It’s been tested in Firefox 3, IE7, Safari 3 and Opera 9.6.

  1. Function.prototype.trace = function()
  2. {
  3.     var trace = [];
  4.     var current = this;
  5.     while(current)
  6.     {
  7.         trace.push(current.signature());
  8.         current = current.caller;
  9.     }
  10.     return trace;
  11. }
  12. Function.prototype.signature = function()
  13. {
  14.     var signature = {
  15.         name: this.getName(),
  16.         params: [],
  17.         toString: function()
  18.         {
  19.             var params = this.params.length > 0 ?
  20.                 "'" + this.params.join("', '") + "'" : "";
  21.             return this.name + "(" + params + ")"
  22.         }
  23.     };
  24.     if(this.arguments)
  25.     {
  26.         for(var x=0; x<this .arguments.length; x++)
  27.             signature.params.push(this.arguments[x]);
  28.     }
  29.     return signature;
  30. }
  31. Function.prototype.getName = function()
  32. {
  33.     if(this.name)
  34.         return this.name;
  35.     var definition = this.toString().split("\n")[0];
  36.     var exp = /^function ([^\s(]+).+/|>;
  37.     if(exp.test(definition))
  38.         return definition.split("\n")[0].replace(exp, "$1") || "anonymous";
  39.     return "anonymous";
  40. }

Javascript file

To use the code, call the trace() method on any function. You will only get a meaningful stack trace on a function that is currently running so in most cases you’d want to call trace() on the arguments.callee method of the current function:

  1. var trace = arguments.callee.trace();
  2. document.getElementById("output").innerHTML = trace.join("<br />\n");

Complete example

How it works

The arguments collection is a local variable that is automatically created inside all javascript functions that contains a list of all parameters that are passed to the function. The arguments variable has a property called callee which is reference to the function itself. This is possible because functions are just a special type of object in javascript so they can be stored in a regular variable just like a number or a string can.

The arguments.callee property gives a reference to the currently running function. The function variable has a property called caller that gives a reference to the function that called the current function. This can be used to walk all the way up the function stack trace. We’ve reached the top of the stack when the caller property of a function returns null:

  1. var current = this;
  2. while(current)
  3. {
  4.     trace.push(current.signature());
  5.     current = current.caller;
  6. }

In most browsers, the function has a property called name that returns the name given to the function when it was created. IE doesn’t support this but calling the function’s toString() method will return the whole function definition and the name can be extracted from the definition. Anonymous functions don’t have to be given a name, so these functions will just return “anonymous” instead of a name:

  1. Function.prototype.getName = function()
  2. {
  3.     if(this.name)
  4.         return this.name;
  5.     var definition = this.toString().split("\n")[0];
  6.     var exp = /^function ([^\s(]+).+/|>;
  7.     if(exp.test(definition))
  8.         return definition.split("\n")[0].replace(exp, "$1") || "anonymous";
  9.     return "anonymous";
  10. }

Limitations

It won’t work with recursive code. The caller property gives a reference to the function not to the function’s activation object. The activation object is like an instance of the function that sits on the call stack. I’d be interested in hearing if anyone finds a way to work around this problem.

More information

For ASP.NET users, Joel Rumerman has created an version of the trace code and has included a neat trick for getting useful function names from functions created using prototypes.

Comments

  1. Ganja Says:

    Thanks for the clean implementation/examples! Very useful.

  2. UUDIBUUDI Says:

    Good stuff! One suggestion to make it work in a .HTA file (W2k3, IE7):

    in the function getSignature I get an error when it is apparently processing a TypeError, which has no arguments. Hence theFunction.arguments doesn’t seem to exist and javascript throws a nice error there. I changed it into this:

    if (theFunction.arguments) {
    for (…) {
    }
    }

    and it worked like a charm.

  3. Henadzi Says:

    Great thx for the example! I think that creating javascript stack trace is a fantastic!

  4. TJ Says:

    nice stuff. Unfortunately looping endlessly on recursive function calls.

  5. Lee Says:

    It does fall over if an invalid argument is being passed into a function – theFunction.arguments.length is 1, say, but theFunction.arguments[ 0 ] is null. In this case, adding “if ( nextArgument == null ) nextArgument = ‘?’” will give a sensible output.

  6. zeeman Says:

    Thx for this great code!

  7. sebasd Says:

    I get a parse error when running “Function.prototype.getName = function()” under Safari. The reg expression is not accepted.

    I changed:

    var exp = /^function ([^\s(]+).+/|>;

    To:

    var exp = /^function ([^\s(]+).+/;

    Maybe a typo because the code snippet was parsed by the Website ?

    Thanks, this function is usefull by itself :

    function myFunction()
    {
    var myCaller = myFunction.caller.getName();
    }

  8. Fritz Schenk Says:

    Happy to report that it works in IE6 and IE8
    Thanks for the great code.

Leave a Reply