The Problem:
Some time ago, developing ubiquity xforms, a large client side javascript library, we ran into a problem. On Internet Explorer, we would frequently see the message "Stack overflow at line: 0". The message always appeared after the completion of some deeply recursive function, not during, and the message did not correspond to any erroneous behaviour. The code seemed to execute correctly, wasn't prematurely aborted, and didn't seem to be displaying any kind of unpredictable behaviour.We found no helpful information about this message, Google returned many instances, on fora and mailing list archives, of unfortunates asking "why am I getting this error message?". The answer was always either "because you have a stack overflow", or STFW, pointing to responses of the former variety.
In a time of tight deadlines, there was an obvious "fix": cut out the recursion, so we did just that. Where plausible, we flattened the recursion out into a loop, or used window.setTimeout with a zero timeout period to "reset the stack".
Sadly, more situations started to crop up where these options were not applicable, and working around it was proving more and more difficult. Not to mention the fact that the recursiveness was sometimes user-defined and therefore effectively out of our control. So, I resolved to investigate the problem properly, in the hope of finding a genuine solution. Which I did.
Simple recursion:
A simple recursive function, such as:
function recurse(n) {
if (n) {
return recurse(n-1) + " " + n;
}
return "numbers:";
}
called thus: recurse(3), it will return "numbers: 1 2 3". This can successfully be called with much larger arguments (e.g. 257, 2049), returning the appropriate result without error message. When the number gets too big (1048576 was when it broke for me), it reports a stack overflow with a meaningful line number and stops working.
Recursive methods:
An object defined thus:
{
recurse:function(n){
if (n) {
return this.recurse(n-1) + " " + n;
}
return "object: ";
}
}
Also works as expected, with no stack overflow message until the stack genuinely overflows.
Recursive DOM methods:
Ubiquity XForms decorates DOM elements with members of a given object, essentially extending the DOM so that it can deal with the specifics of XForms elements, as well as HTML elements. As such, most functions are called as methods on DOM objects.
Once the document is loaded, adding the following method allows this to be tested:
document.body.recurse = function(n) {
if (n) {
return this.recurse(n-1) + " " + n;
}
return this.nodeName + ":";
}
As before, calling document.body.recurse(3), works as expected, returning "BODY: 1 2 3". As does document.body.recurse(13). However, although document.body.recurse(14) returns the expected value, it also displays the error message "Stack overflow at line 0". The root cause, therefore, appears to be that in IE, The stack for calling DOM member functions is mere 13 deep.
The Cure:
Since the problem only occurs when calling functions as members of DOM objects, it can be resolved by removing the recursiveness to a non-member function. Instead of defining document.body.recurse as above, it may be defined thus:
var recurse = function (self, n) {
if (n) {
return recurse(self, n-1) + " " + n;
}
return self.nodeName + ":";
}
document.body.recurse = function(n) {
return recurse(this,n);
}
By passing "self" explicitly as a parameter, rather than using the built in "this" keyword, document.body.recurse defined in this fashion, it may be called with higher values than 14, without engendering the stack overflow message.
Another possibility is to define recursive methods as members-of-members of DOM objects, thus:
document.body.recursor = {
self:document.body,
recurse: function(n) {
if (n) {
return this.recurse(n-1) + " " + n;
}
return self.nodeName + ":";
}
}
document.body.recurse = function(n) {
return this.recursor.recurse(n);
}
In either case, any code within your library should call the "recursion-safe" version, (recurse/2 or element.recursor.recurse), otherwise the problem will still occur. The element.recurse method is simply to be used as an entry point by clients of your library, or to maintain compatibility with an existing interface.