You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



JavaScript Call Stack Dumper

Draft 1

Introduction

The purpose of this document is to explain how to use the code I've added to XPConnect which allows the native and JavaScript programmer to manually or programatically dump the current JavaScript call stack to the native console.

The system I describe below will allow programmers to see the current state of the JavaScript call stack (with arguments and locals) and do arbitrary JavaScript evaluations while stopped in the native debugger. I've also added support for dumping the JavaScript call stack via the 'debugger' keyword in JavaScript source code.

This support will work regardless of whether or not the JavaScript code uses XPConnect.

An alternative to the method described here is to iterate over Components.stack.

From within the native debugger...

I've exported two global symbols from the xpconnect and jsdom DLLs:

  extern "C" void DumpJSStack(void);
  extern "C" void DumpJSEval(PRUint32 frameno, const char* text);

These functions can be called manually from within the native debugger [I've tested only NT. This should work in Linux debuggers. On Mac???].

In msdev (Microsoft's Win32 debugger) these functions can be called from the "Quick Watch" dialog or by adding the call to the 'Watch" window. Note that this debugger can only resolve and call the functions if the current frame as selected in the "Call Stack" or "Variables" window is a frame from one of the DLLs where these symbols are exported; i.e. the xpconnect or jsdom DLLs. A successful call to one of these functions should send output to the native console and evaluate to <void> in the Watch or Quick Watch window. An attempt to call from a frame where the debugger cannot find the symbol will result in something like CXX0017: Error: symbol "DumpJSStack" not found.

DumpJSStack() will attempt to dump the JS call stack for the JSContext on the current thread. It uses the JSContext which is pushed onto the ThreadJSContextStack by the DOM and other code in mozilla before calling into JavaScript. The 'top' JSContext will be used regardless of which native frame is selected in the debugger.

The format for the dump is explained below.

DumpJSEval() takes two params: 1) the zero based frame number in which to do the eval, 2) the source text to eval. The text evaled and the result are echo'd to the native console:

Calling DumpJSEval(0, "1+1") might print:

js[0]>1+1
2

Keep in mind that although calling DumpJSStack() should have no effect on the running JS code, calls to DumpJSEval() may have side effects. This can be powerful if used creatively.

Dumping the JavaScript stack from native code...

You can easily write C++ code that will dump the JavaScript call stack using XPConnect service. Look here for the declarations and examples. Here is a simple sample call...

    nsresult rv;
    NS_WITH_SERVICE(nsIXPConnect, xpc, nsIXPConnect::GetCID(), &rv);
    if(NS_SUCCEEDED(rv))
        xpc->DebugDumpJSStack(PR_TRUE, PR_TRUE, PR_FALSE);

We might want to add such a call to the JavaScript error reporter.

The JavaScript debugger statement

JavaScript has a debugger keyword which can appear in JavaScript source. Older JavaScript engines will flag its use as a syntax error (use of a reserved word). But newer engines will allow the keyword. Engine embedders can install a callback hook to be called if and when the keyword is reached during script execution. At some future point there may be a JavaScript Debugger which will install such a hook and pop up a fully featured debugging window when this event fires. For now I have created such a hook in xpconnect which is set in DEBUG builds only. If the debugger keyword is reached then the JavaScript stack will be dumped to the native console (same as if DumpJSStack() had been called.

After hitting the debugger keword and dumping the JS call stack, JS execution will continue as if nothing unusual had happened. If you would like this to stop in your native debugger then set a breakpoint in xpc_DebuggerKeywordHandler in your native debugger.

The debugger statement can be very useful as a kind of super dump as you develop your JavaScript code. You can also use it as a kind of assert: if(some_unusual_condition) debugger;

Note 1) in the example above the test will always happen even though debugger might have no effect in non-DEBUG builds 2) at some future point someone is bound to ship a JavaScript Debugger that will catch these cases (even in non-DEBUG builds). When that happens, users trying to debug their own JS code might trip over your debugger statements. So, while these debugger statements might be very useful as you write code, you may not want to leave them in permanently.

The Dump Format

The format of the dump output is like:

frame_number function_name(argname = argvalue) ["filename":line_number]
    local_variable1 = value
    local_variable2 = value
    this = value

So an example frame might look like:

0 f(arg1 = [function], arg2 = "bar") ["debug.js":6]
    local1 = 1
    local2 = "second local"
    this = [object global]

Some things to note...

  • In the browser the 'filename' will often be a URL.
  • Some places in mozilla that evaluate JavaScript will not supply a filename, so the dump may report something like [<unknown>:0].
  • If a function is called with more arguments than its declaration names, then the additional trailing argument values will be displayed without the "argname = " part.
  • Functions passed as arguments or stored as variables are displayed as simply "[function]".
  • If the type of an argument or variable is 'string' then it will be displayed quoted.
  • Functions declared without a name are displayed with the name "anonymous".
  • Frames that are not functions are displayed as "<TOP LEVEL>".
  • Native (non-JavaScript) frames in the call stack are displayed as "[native frame]".

So, the following (contrived) code stored in a file called "debug.js"

/* an example... */
function f(arg1, arg2) {
    var local1 = 1;
    var local2 = "second local";
    debugger;       // <-- stack is dumped here
}

var o = {foo : "fu", bar : function(a,b){f(a,b)}};

function g(a1, a2, a3, a4) {
    var alocal = "something";
    var l4 = a4;
    var localFromArg = a1;
    o.bar(a1,a2);
}

g(f, "bar", {foo : "f", bar : "b"}, [1,2,3], "extra", 123);

...might cause output like...

------------------------------------------------------------------------
Hit JavaScript "debugger" keyword. JS call stack...
0 f(arg1 = [function], arg2 = "bar") ["debug.js":5]
    local1 = 1
    local2 = "second local"
    this = [object global]
1 anonymous(a = [function], b = "bar") ["debug.js":8]
    this = [object Object]
2 g(a1 = [function], a2 = "bar", a3 = [object Object], a4 = 1,2,3, "extra", 123) ["debug.js":14]
    alocal = "something"
    l4 = 1,2,3
    localFromArg = [function]
    this = [object global]
3 <TOP LEVEL> ["debug.js":17]
    this = [object global]
------------------------------------------------------------------------

Conclusions

Please people, use this to help yourselves understand exactly what your JavaScript code is doing.

And send feedback.