bun
bun copied to clipboard
Bun does not throw when call stack is exceeded
Version
0.1.5
Platform
Darwin maxbookpro.local 22.0.0 Darwin Kernel Version 22.0.0: Tue Jun 28 20:46:36 PDT 2022; root:xnu-8792.0.134.131.2~1/RELEASE_ARM64_T6000 arm64
What steps will reproduce the bug?
bun run
the following code:
const foo = () => bar();
const bar = () => foo();
foo();
How often does it reproduce? Is there a required condition?
Every time
What is the expected behavior?
bun
should throw similar to jsc
:
Exception: RangeError: Maximum call stack size exceeded.
[email protected]:2:22
[email protected]:1:22
...
What do you see instead?
The call stack grows until bun
consumes 100% of the CPU.
Additional information
No response
call stack isn't actually growing because of tail call optimization, the question is do we want to limit how much you can take advantage of it
Test case can be simplified.
const foo = () => foo();
foo(); // does not throw
const foo = function () {
return foo();
}
foo(); // does throw
@evanwashere In my opinion, it should be limited, because infinite loops should be avoided.
A lot of current JS is being written in a more functional style, making TCO quite favorable
@JL102 That second example no longer throws, the tail call is being optimised per ES6
I recommend to resolve this as "no issue." This is working as intended by bun, and also implemented as specified.
Node.js does not implement TCO (See: https://node.green and this blog and their proposal) but there is no rule that bun or other JS runtimes (system, browser, embedded, etc) must follow Node.js instead of the language specification. Straying from the spec makes code less portable and less valuable.
BTW if anyone legitimately needs a workaround to force a stack overflow, and you own the code then you can simply make your recursive call not in the tail position.
Example with one extra line for a variable bind:
const noTco = () => {
const res = noTco();
return res;
}
Demo with bun-repl:
$ bunx bun-repl
Welcome to Bun v1.0.1
Type ".help" for more information.
[!] Please note that the REPL implementation is still experimental!
Don't consider it to be representative of the stability or behavior of Bun overall.
> const noTco = () => { const res = noTco(); return res; }
undefined
> noTco()
1 | const noTco = ()=>{
2 |
3 | const res = noTco();
^
RangeError: Maximum call stack size exceeded.
at noTco (:3:16)
at noTco (:3:16)
at noTco (:3:16)
at noTco (:3:16)
at noTco (:3:16)
at noTco (:3:16)
at noTco (:3:16)
at noTco (:3:16)
at noTco (:3:16)
at noTco (:3:16)
This is because WebKit supports tail-call optimization, which is not supported by V8 or Node.js. While it has its downsides, like the example you provided, it overall is an improvement to performance.