On Friday, November 8, 2019 at 8:27:13 PM UTC-5, Sam Clegg wrote:
Alas. :-( To try and establish a sort of baseline about stack overflow handling in JavaScript hosts, I posted a question on...Stack Overflow:
It's my attempt to clear the air a bit about whether or not there is such a thing as "safe" recovery from a JavaScript out-of-stack RangeError. My suspicion seems to be confirmed--that the JS spec does not have the systemic rigor to support it coherently. Even if there's some level at which a particular engine guarantees atomicity to avoid certain internal corruptions, the portions of the language implemented in JavaScript itself would likely not have any such bulletproofing. That's to say nothing of your own usermode code, which cannot maintain invariants when there's no firm ground to stand on.
There's a suggestion that coding directly in Wasm could offer enough confidence to build some kind of transaction mechanic. But with C being a layer of abstraction atop that, I do not believe Emscripten has this safety. e.g. the following code was always was in state `1` when the RangeError is trapped...and I'd guess that's the internals of malloc():
int state = 0; // 1=malloc(), 2=free(), 3=recurse() itself
EMSCRIPTEN_KEEPALIVE
void recurse(int depth) { // I called this from a JS try...catch
if (depth == 0)
return;
state = 1;
void *dummy = malloc(depth); // overflowed in here
state = 2;
free(dummy);
state = 3;
recurse(depth - 1);
state = 0;
}
I don't know an easy way to *prove* the heap can get corrupted by this. If anyone can build a repro case that does so, that might be an interesting emscripten GitHub issue for anyone researching the topic to find (even if resolved: WontFix) Maybe there's a clang equivalent to mcheck() to get this proof?
...BUT what I'd really like to see would be something in the tooling that helped mitigate this. It is unfortunate to force people into a shallow-stack custom bytecode interpreter just to get the sole benefit of (hopeful) safe recovery from stack overflows. I'll re-suggest that building to Wasm and having the hooks into the compiler (like those used to accomplish Asyncify) could permit a stack cost model, and you could choose to force a stack overflow *before* running operations that might otherwise overflow in mid-operation. Analogous to:
void transaction(void) {
void *data = malloc(...);
/* dostuff */
free(data);
}
void whatever(void) {
/* ... */
crash_if_not_enough_stack_for(transaction);
transaction(); // guaranteed to run if we get here
/* ... */
}
Again: I'm aware that you can abstract away the C into your own universe where you attempt something analogous (though much slower). It's just frustrating to already be inside of virtualized machines when doing so--and to be operating on guesswork even then--with the ball punted from '89 getting kicked even further down the road. :-/ If Wasm could be a turning point to more of a RTOS/embedded mindset, that could really help raise the bar on software reliability...and strengthen the argument for Wasm as the future.
Best,
--Brian