Repository navigation
Fix collectUnicodes() leak and throw when malloc() fails - #238
dannyball710 wants to merge 2 commits into
Conversation
typed_array_from_set() returned a view of the array it allocated in the WASM memory and never freed it, leaking 4 bytes per code point on every call. The view was also detached as soon as the memory grew, after which the returned array had length 0 and threw when iterated. Copy the result out and free the array. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The WASM memory cannot grow past 2 GiB, and once it is full malloc() returns 0. Every caller wrote through the returned pointer regardless: a Blob that no longer fits copied its font data over the start of the memory, overwriting the module's static data, and later calls returned garbage or trapped with "memory access out of bounds". Route all allocations through a helper that throws instead. In Face.getName(), allocate before stackAlloc(), so that a failure cannot skip the stackRestore(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Is this compiler setting relevant? https://emscripten.org/docs/tools_reference/settings_reference.html#aborting-malloc |
It is related, but I don't think it can replace the check here. harfbuzzjs builds with
The check in this PR only covers the allocations the JS wrapper makes itself, before anything has been written. The caller gets an ordinary |
Two memory bugs I ran into in an application that shapes text with many user-supplied fonts in one process. One commit each, see the commit messages.
collectUnicodes() leak.
typed_array_from_set()returned aHEAPU32view of an array it never freed, so every call leaked 4 bytes per code point. With NotoSans-Regular.ttf (3053 code points), 20,000 calls grow RSS from 52 MiB to 291 MiB on main; with this change even 100,000 calls stay at 91 MiB. The view was also detached whenever the WASM memory grew, so a result kept across a later allocation came back empty. The new test fails on main withTypeError: Cannot perform %TypedArray%.prototype.values on a detached ArrayBuffer.malloc() returning 0. The WASM memory cannot grow past 2 GiB, and once it is full
malloc()returns 0, which every caller wrote through anyway. On main:With real font data in the blobs the corruption can also take the process down later: making a Blob + Face + Font from 13 MB of font data 200 times in a row corrupts the memory at round 157, and when the dropped objects are finalized afterwards, a finalizer traps inside
FinalizationRegistry.cleanupSomeand the uncaughtRuntimeError: memory access out of boundsexits Node.js with code 1.With this change the 32nd Blob throws
Error: Failed to allocate 67108864 bytes, and the module keeps working (versionString()is intact, shaping a small font still works, and finalization runs without errors). All eightexports.malloc()call sites now go through the helper. The test for this has to fill the WASM memory, so it lives in its own file to get its own module instance; it takes about 1.6 s.Tested with
make testandmake doc(Emscripten 4.0.13, Node.js 22).