Javascript recursion limit

T

Thomas 'PointedEars' Lahn

VK said:
Yet even more closer than :)

This is gibberish again.
Ah, OK, I see your problem now. The function f calls itself over and
over again until all allowed resources are used - it is an endless
loop. The "loop" term is not exclusively attached neither to
iterations nor to recursions.

You are mistaken. When a function calls itself, i.e. recursion occurs, that
is _not_ considered a loop. A loop is something that closes back on itself.
To make it easier for you to understand the difference, a loop (i.e.
iteration) looks as follows:

* n
,---->----.
| |
-->o |---
^ |
`----<----'

The execution context remains the same with each loop.

Recursion, on the other hand, looks like this:

-->o-----------------
^
|
v
o
^
|
v
o
 
L

Lasse Reichstein Nielsen

Thomas 'PointedEars' Lahn said:
You are mistaken. When a function calls itself, i.e. recursion occurs, that
is _not_ considered a loop.

That depends.
A loop is something that closes back on itself.

I.e., a loop is something that repeats - that comes back to the
same state, in some sense (not the exact same state, since that would
cause an infinite loop, but the same state wrt. control flow).


An recursive function can do that, if the language allows it.
In languages with proper tail recursion, a recursive function is the
way to create loops. E.g.,

fun sum nil acc = 0
| sum (x::xs) acc = sum xs (acc+x)

In ECMAScript terms: While the called object remains the same, the execution
context does not.

True. ECMAScript does not require an implementation to have tail recursion,
and even then, not all recursion is tail recursion.

/L
 
V

VK

A recursive algorithm does not *close back* *on* itself, it *calls forward*
*to* itself. Hence the requirement of a stack for recursion, but not for
iteration.

OK, let's us set the grounds first: iterations, recursions, finite
loops, infinite loops etc. these are all top level mental constructs
used in the programming to distinguish certain types of top level
coding. On the engine level itself there is the stack, its size and
respectively certain amount of RET values it may store in LIFO schema.
Respectively the engine itself doesn't care what RETs are these:
function F0001 calling F0002,... calling F1000 - or the same function
F calling itself 1000 times. It is all the same by the allocation
demands. So in order do not switch onto Assembly language yet being
technically correct let's say that each engine has some limit of
nested calls where the chain goes from the initial Global RET point
and up to the currently executed function. This limit is the same for
the regular nested calls and for recursions as well because again on
the lower level it is all the same. So coming back to OP's question
his real question was: "How many nested control flow transfers are
allowed in the most prominent ECMAScript implementations?"

The answer is that they are still rather generous for the regular
coding. Even Safari with its lesser than 500 RET positions doesn't put
some real life limits on coding. When did anyone really needed 500 or
more different functions making nested call chain at once? I mean,
com'on now, guys...

At the same time these limits may hit noticeably recursion based code,
especially one based on infinite loops where the loop's break
determined by some condition check and not by a fixed amount of
iterations.

The other thing is that, as I said, the engine doesn't have a "stack
for regulars", a "stack for recursions" etc. It is all the same
program stack and it nearly never completely empty at runtime, so your
recursion depth tests - if done from the language itself - are going
to the top of whatever is already stored in the stack. This is why
such tests will always differ a bit circa +/-10 depending on the
structure of your test and on additional scripts already running by
the engine. So in order to get _true_ stack size one should to read
the engine specs or to query stack from outside from a C program.

I played a bit with the original test I posted so now you can run it
on any browser without manually changing every time the work-on-denial
value.
It also reminded me that JScript 5.6 had a severe stack vulnerability
bug in the original IE6 back in 2001 that allowed to do whatever you
want with the visitor's computer using specially crafted stack
overflow script. The Microsoft was in such secret panic that time that
they suggested to their corporate customers to "upgrade" from MSDN
from JScript 5.6 to JScript 5.1
I see that the current JScript 5.6 for IE6 has a quick-n-dirty
vulnerability fix for that - so it just shuts down the window being
under attack. An often question in this group is how to close the
original browser window. Here is the Astalavista way for IE6 :)


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en_US">
<head>
<meta http-equiv="Content-type"
content="text/html; charset=iso-8859-1">
<title>Stack depth measuring</title>
<script type="text/javascript">

var fun = new Array(4000);

function test() {
for (var i=0; i<fun.length; i++) {
fun = new Function('i', ''.concat(
'i++; try {fun(i);}',
'catch(e) {window.alert(',
'e.message.concat("\\n","max allowed: ",i));}'
)
);
}
fun[fun.length-1] = new Function('window.alert(\'OK\');');
eval(fun[0](0));
}
</script>
</head>
<body>
<p>No content</p>
</body>
</html>
 
T

Thomas 'PointedEars' Lahn

VK said:
OK, let's us set the grounds first: iterations, recursions, finite
loops, infinite loops etc. these are all top level mental constructs
used in the programming to distinguish certain types of top level
coding. On the engine level itself there is the stack, its size and
respectively certain amount of RET values it may store in LIFO schema.
Respectively the engine itself doesn't care what RETs are these:
function F0001 calling F0002,... calling F1000 - or the same function
F calling itself 1000 times. It is all the same by the allocation
demands.

No, it is not. With recursion it is a different execution context than
iteration, and therefore the allocation demands must be quite different.

Besides, arguing with assembler when we know that we are dealing with a
Virtual Machine, and using eval() for testing recursion, shows again what
little your long-winded "explanations", flawed "tests", and bloated
"examples" are worth.


PointedEars
 
V

VK

No, it is not. With recursion it is a different execution context than
iteration, and therefore the allocation demands must be quite different.

Besides, arguing with assembler when we know that we are dealing with a
Virtual Machine, and using eval() for testing recursion, shows again what
little your long-winded "explanations", flawed "tests", and bloated
"examples" are worth.

Do you want to learn and to find the real nature of things or just
being nasty on everyone? For the latter just start a new thread then
like "Why does the world suck" or similar so do not be OT. For the
first remove eval() wrapper and call the function directly:
//eval(fun[0](0));
fun[0](0);
That takes one extra RET position in the stack for the function test()
from where the initial call is made, so you are getting 998 instead of
999.
 
P

Peter Michaux

As viable as in any other *language* - i.e., limited by the implementation
and/or platform that it runs under.
Javascript in web pages just has the extra problem that the author doesn't
get to pick the language implementation.

I would say there are languages where recursion is more viable than
JavaScript. Any language that guarantees proper tail call elimination
would make it possible to program with tail call recursion and no
worries about call stack depth. Scheme is one language that makes this
guarantee.

Unfortunately one of the long standing ECMAScript 4 features was going
to be proper tail calls but apparently that made type checking too
difficult. They decided that type checking was more important. [waits
for gasp to end] I know. I was very disappointed also. I don't give a
hoot about type checking but I do care about functional programming a
lot. Oh well. That about sums up my feelings on ES4.

Peter
 
J

Jorge

Jeff said:
So, it appears that Javascript has a recursion limit of about 1000
levels on FF, maybe less/more on other browsers.

javascript:(function f(p){document.write(p+'<br>'); f(p+1); })(0)

WebKit/Safari r33029 --> 139808
FF3.0pre --> 2999
IE8.0.6001 --> 2340

--Jorge
 
T

Thomas 'PointedEars' Lahn

Jorge said:
javascript:(function f(p){document.write(p+'<br>'); f(p+1); })(0)

WebKit/Safari r33029 --> 139808
FF3.0pre --> 2999

I presume you mean Fx 3 RC 1 because I get that number there.
IE8.0.6001 --> 2340

IE 7.0.5730.11 --> 2507


PointedEars
 
E

Evertjan.

Thomas 'PointedEars' Lahn wrote on 23 mei 2008 in comp.lang.javascript:
IE 7.0.5730.11 --> 2507

Mine (IE 7.0.5730.11 under XP) does 2553

====================
function f(p){
if (!(p%500)||p>2550)
document.write(p+'<br>');
f(p+1);
};
f(0);
====================

Why the difference?
XP - Vista??

It gives a popup warning "Out of memory".

Limited "subroutine" return stack?

Other memory use does not make any difference:

====================
function f(p){
if (!(p%500)||p>2550)
document.write(p+'<br>');
var z = 'Hello world';
f(p+1);
};

f(0);
====================
 
E

Evertjan.

VK wrote on 24 mei 2008 in comp.lang.javascript:
document.write method is implemented as a pipe so useless for stack
studies.

I don't see why. Please explain.
 
V

VK

VK wrote on 24 mei 2008 in comp.lang.javascript:





I don't see why. Please explain.

I was wrong - it did change.
So coming back to the original issue, the maximum universal stack size
is defined by the smallest value among the browsers in considerations.
Also the factual stack size is at least 1 position smaller than the
physical stack size because the position[0] is always taken by the RET
address of the initial entry point into current context, so at least
RET 0 (exit from the current context).
This way if we do care about Safari then the universal max of RET
points is 497 (Safari 3.x 498-1)
Without Safari in consideration universal max of RET points is 999
(Firefox 2.x 1000-1)
 
V

VK

This way if we do care about Safari then the universal max of RET
points is 497 (Safari 3.x 498-1)
Without Safari in consideration universal max of RET points is 999
(Firefox 2.x 1000-1)

Because no educated guess can be made about the call chain length at
the moment of starting the recursion, one either has to trace the
chain length by using deprecated yet functional arguments.caller
property or just cut the max value for another ten positions thus 488
or 989 depending on with Safari or without it. The latter way is very
approximate of course though I believe that any algorithm of any
descent sanity never goes above that call chain size. At least I never
saw a sane counterexample and all other counterexamples were clearly
insane.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,058
Latest member
QQXCharlot

Latest Threads

Top