I want to test for large call stacks. Specifically, I want a console warning when the call stack length reaches 1000. This usually means I did something stupid, and can lead to subtle bugs.
Can I compute the call stack length within JavaScript?
Here's a function that will work in all major browsers, although it won't work in ECMAScript 5 strict mode because arguments.callee and caller have been removed in strict mode.
function getCallStackSize() {
var count = 0, fn = arguments.callee;
while ( (fn = fn.caller) ) {
count++;
}
return count;
}
Example:
function f() { g(); }
function g() { h(); }
function h() { alert(getCallStackSize()); }
f(); // Alerts 3
UPDATE 1 November 2011
In ES5 strict mode, there is simply no way to navigate the call stack. The only option left is to parse the string returned by new Error().stack, which is non-standard, not universally supported and obviously problematic, and even this may not be possible for ever.
UPDATE 13 August 2013
This method is also limited by the fact that a function that is called more than once in a single call stack (e.g. via recursion) will throw getCallStackSize() into an infinite loop (as pointed out by @Randomblue in the comments). An improved version of getCallStackSize() is below: it keeps track of functions it has seen before to avoid going into an infinite loop. However, the returned value is the number of different function objects in the callstack before encountering a repeat rather than the true size of the complete call stack. This is the best you can do, unfortunately.
var arrayContains = Array.prototype.indexOf ?
function(arr, val) {
return arr.indexOf(val) > -1;
} :
function(arr, val) {
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i] === val) {
return true;
}
}
return false;
};
function getCallStackSize() {
var count = 0, fn = arguments.callee, functionsSeen = [fn];
while ( (fn = fn.caller) && !arrayContains(functionsSeen, fn) ) {
functionsSeen.push(fn);
count++;
}
return count;
}
6 but that's because there are seemingly 3 other functions being executed behind the scenes when using the Console.getCallStackSize does not return in that case.You can use this module: https://github.com/stacktracejs/stacktrace.js
Calling printStackTrace returns the stack trace inside an array, then you can check its length:
var trace = printStackTrace();
console.log(trace.length());
A different approach is measuring the available size on the stack in the top-level stack frame and then determining the used space on the stack by observing how much less space is available. In code:
function getRemainingStackSize()
{
var i = 0;
function stackSizeExplorer() {
i++;
stackSizeExplorer();
}
try {
stackSizeExplorer();
} catch (e) {
return i;
}
}
var baselineRemStackSize = getRemainingStackSize();
var largestSeenStackSize = 0;
function getStackSize()
{
var sz = baselineRemStackSize - getRemainingStackSize();
if (largestSeenStackSize < sz)
largestSeenStackSize = sz;
return sz;
}
For example:
function ackermann(m, n)
{
if (m == 0) {
console.log("Stack Size: " + getStackSize());
return n + 1;
}
if (n == 0)
return ackermann(m - 1, 1);
return ackermann(m - 1, ackermann(m, n-1));
}
function main()
{
var m, n;
for (var m = 0; m < 4; m++)
for (var n = 0; n < 5; n++)
console.log("A(" + m + ", " + n + ") = " + ackermann(m, n));
console.log("Deepest recursion: " + largestSeenStackSize + " (" +
(baselineRemStackSize-largestSeenStackSize) + " left)");
}
main();
There are of course two major downsides to this approach:
(1) determining the used up stack space is a potentially an expensive operation when the VM has a large stack size and
(2) the numbers reported are not necessarily the number of recursions, but instead are a measurement of the actual space used on the stack (of course, this can also be an advantage). I've seen auto-generated code that contains functions that use the same space on the stack per recursion as 2000 recursions of the stackSizeExplorer function above.
Note: I have only tested the code above with node.js. But I assume it would work with all VMs that use a static stack size.
eand inspects its properties, based on the browser. For Chrome and Mozilla, it usese.stack, for Opera 10+ it usese.stacktraceand for others it will try to make sense of thee.messageproperty.console.trace()arguments.calleeorcaller. See my revised answer.