Why does nesting a bunch of blocks causes a stack overflow in JavaScript
Default implementation of Recursive descend parser while simple and elegant, parses every language grammar rule with one method. These methods call other methods recursively, so when you have too much nested rules, it exceeds the stack size. Chrome and Firefox both use such implementation of interpreter.
You will notice that a lot of ' + ', while having nothing to do with scope, will cause the same exception:
+ + + + + + + + + ... // same error
First of all, ghord is completely correct. It is caused by the parser's recursive nature, so give him upvote love. But proof needs to be had, and OP wanted me to post this as a separate answer.
Firefox
So, where to find out about how it was done? Ask some guys who're in the engine making. So I went over to the #jsapi
channel on irc://irc.mozilla.org
and asked them:
< bhackett> zirak: well, with a recursive descent parser all the productions will roughly correspond to a frame on the C stack
< bhackett> zirak: the parser is at js/src/frontent/Parser.cpp
< Waldo> zirak: Parser<ParseHandler>::statement(bool canHaveDirectives) and Parser<ParseHandler>::statements() pretty much
< bhackett> zirak: in this case, the recursion will be Parser::blockStatement ->Parser::statements -> Parser::statement -> Parser::blockStatement
Which is pretty much the answer. Going to the mozilla-central repository and digging in, we have our suspects:
Parser<ParseHandler>::blockStatement
Parser<ParseHandler>::statements
So, what we have is this:
statements
which callsblockStatement
, which parses the block, to find another block, callingstatements
which callsblockStatement
, which parses the block, to find another block, callingstatements
which callsblockStatement
, which parses the block, to find another block, calling- ...
Until the stack collapses, I'm guessing here.
So we have the source for Firefox.
Chrome/Chromium/anything else based on v8
Learning my lesson from Firefox, I went to the v8 project and looked for a file named parser
. Sure enough, it was there!
The next thing was looking for when a block is parsed, so I naively searched for statements
, arriving on the promising ParseStatement.
And it's our lucky day, a giant switch
! And the first case is what we care about, a call to ParseBlock
, another promising name!
Indeed, inside ParseBlock
, we find a call to ParseStatement
. So, to be clear, we have two functions:
Parser::ParseBlock
Parser::Parser::ParseStatement
And they're calling each other like we saw in Firefox:
ParseStatement
which callsParseBlock
, which parses the block, to find another block, callingParseStatement
which callsParseBlock
, which parses the block, to find another block, callingParseStatement
which callsParseBlock
, which parses the block, to find another block, calling- ...
Until kaboom goes the stack.
Safari
(Sorry for calling it closed-source in the last edit!) Safari's js engine is JavaScriptCore, which resides in the WebKit project. Finding the functions was pretty much the same as finding them for Chrome, so let's skip to the interesting part:
Parser<LexerType>::parseSourceElements
Parser<LexerType>::parseStatement
Parser<LexerType>::parseBlockStatement
We have an extra function in the middle, but the principle is the same:
parseSourceElements
which callsparseStatement
which callsparseBlockStatement
, which parses the block, to find another block, callingparseSourceElements
which callsparseStatement
which callsparseBlockStatement
, which parses the block, to find another block, callingparseSourceElements
which callsparseStatement
which callsparseBlockStatement
, which parses the block, to find another block, calling- ...
BOOM
IE (and all other closed-source, like Opera)
...will remain a mystery, unless they feel the sudden urge to open their source or if an enterprising employee shared the internals with us. The two great engines above do it in the same fashion, so we can assume the other browsers do it similarly.
If a browser doesn't collapse, that's an interesting question, but one that this answer can't hope to cough answer.