quickjs-emscripten
quickjs-emscripten copied to clipboard
`Out of bounds memory access` Error only on ARM Safari
I encountered a strange issue: when running a code that includes a negative number smaller than -2 on Safari, it will throw Out of bounds memory access (evaluating 'a.apply(null,h)') Error.
The following code will work on all platforms except iOS or Mac ARM Safari.
const QuickJS = await getQuickJS(); // 0.29.1
try {
for (let i = 0; i < 2000; i++) {
const runtime = QuickJS.newRuntime();
const context = runtime.newContext();
const result = context.evalCode(`-2`);
context.unwrapResult(result).dispose();
context.dispose();
runtime.dispose();
}
console.log('ok');
} catch (err) {
console.error(err);
}
To get the error, the code should be executed multiple times. In a real-world scenario, with longer code, the error will appear sooner.
I looked at a related issue but did not solve the problem: https://github.com/justjake/quickjs-emscripten/issues/35
Here is the replication:
quickjs-emscripten-ios-error.zip
Works on Chrome:
Mac M2 Safari 17.4 throws the error:
Thanks for the report. Does it need to be x < -128? Because to me the creation of many runtime/context instances seems more likely to be the cause. I wonder if Safari is hitting some limit with memory allocation so we end up with a runtime or context pointer that’s out of bounds for some reason.
Does the error occur with other build variants?
Does it need to be
x < -128?
Yes, it needs to be a negative number smaller than -2. The code will throw the error if it is -2, -3, -128...
My original scenario is a long piece of code that executes 60 times per second. There will be no error if I remove the negative number from the code. A strange behavior that I took a while to find.
Does the error occur with other build variants?
Yes, it also occurs on quickjs-ng.
Hi, we have encountered the same issue in our app on Safari.
The issue is somehow related to negative numbers.
Additional details
Browserstack testing
I was able to test and reproduce the issue on the following environments.
- Sonoma - Safari 17.3
- Ventura - Safari 16.5
- Monterey - Safari 15.6
- Big Sur - Safari 14.1
Variants
I couldn't reproduce the issue on asyncify builds.
| Variant | Works |
|---|---|
| @jitl/quickjs-wasmfile-release-sync | :x: |
| @jitl/quickjs-wasmfile-release-asyncify | :white_check_mark: |
| @jitl/quickjs-ng-wasmfile-release-sync | :x: |
| @jitl/quickjs-ng-wasmfile-release-asyncify | :white_check_mark: |
| @jitl/quickjs-singlefile-browser-release-sync | :x: |
| @jitl/quickjs-singlefile-browser-release-asyncify | :white_check_mark: |
Repro
We managed to get the out of bounds memory access by using a single context.
const context = QuickJS.newContext();
for (let i = 0; i < 1000000; i++) {
const result = context.evalCode(`1 < -128`);
context.unwrapResult(result).dispose();
}
context.dispose();
console.log("ok");
I tested it again and found that any negative integer literal that is less than -2 would throw the error.
Error: -2 -(2) -2.0 -0b10 1 < -2
OK: -1 -0 +2 -2.1 0 - 2 -(2 + 0) var x = 2; -x -0b1
I guess the problem may occur in js_atof function (quickjs.c#L10189) ?
We don't use this project directly, but we do use QuickJS embedded into a larger C++ project which is compiled to WASM via Emscripten.
If this is the same issue that I've been digging into, it seems it may have something to do with push_short_int() and the OP_push_i8 and OP_push_i16 SHORT_OPCODES.
Possible workaround:
- Modifying
push_short_int()as follows seems to avoid the crash on Safari (at least in some very initial testing in my own particular use-case - I have not tested with the repro above, as we don't use this project directly):
static void push_short_int(DynBuf *bc_out, int val)
{
#if SHORT_OPCODES
if (val >= -1 && val <= 7) {
dbuf_putc(bc_out, OP_push_0 + val);
return;
}
+ # if !defined(__EMSCRIPTEN__)
if (val == (int8_t)val) {
dbuf_putc(bc_out, OP_push_i8);
dbuf_putc(bc_out, val);
return;
}
if (val == (int16_t)val) {
dbuf_putc(bc_out, OP_push_i16);
dbuf_put_u16(bc_out, val);
return;
}
+ # endif
#endif
dbuf_putc(bc_out, OP_push_i32);
dbuf_put_u32(bc_out, val);
}
We don't use this project directly, but we do use QuickJS embedded into a larger C++ project which is compiled to WASM via Emscripten.
If this is the same issue that I've been digging into, it seems it may have something to do with
push_short_int()and theOP_push_i8andOP_push_i16SHORT_OPCODES.
Thanks a lot! Based on my testing, it will fix the problem.
This seems to be an upstream library issue, should we report it to QuickJS, Emscripten or Webkit?
Thanks a lot! Based on my testing, it will fix the problem.
This seems to be an upstream library issue, should we report it to QuickJS, Emscripten or Webkit?
I am only able to reproduce this on Safari (never had an issue on Chrome or Firefox), so it seems like a WebKit-specific issue.
As such, I’d suggest reporting it to Emscripten (who might have some further ideas as to what might be going wrong) and WebKit.
Something important to note: This issue is only reproducible on Safari / WebKit on Apple Silicon / ARM. The issue does not occur on Safari on Intel Macs. So it looks to be a WebKit + ARM / Apple Silicon specific issue.
I'm not sure if I can provide an informative enough issue for Emscripten, can @justjake help? Probably we should report the Out of bounds memory access error that is caused by OP_push_i8 and OP_push_i16 SHORT_OPCODES in push_short_int() only on ARM Safari. Thanks a lot!