Background
Upstream quickjs-emscripten 0.32.0 (released 2026-02-16) reworked function binding in newFunction to use a new abstraction called HostRef. From the upstream changelog:
Re-works function binding in newFunction to use a different proxying strategy based on a new abstraction, HostRef. HostRef allows tracking references of host values from guest handles. Once all references to the HostRef object are disposed (either in the host, or GC'd in the guest), the host value will be dereferenced and become garbage collectable. Functions passed to newFunction are interned in the runtime's HostRefMap until dereferenced.
New API surface introduced upstream: newHostRef, toHostRef, unwrapHostRef, QuickJSContext.newConstructorFunction.
Why this matters for this wrapper
Naively bumping the quickjs-emscripten devDependency from 0.31.0 to 0.32.0 causes the following test to fail:
FAIL src/index.test.ts > memory management > memory limits work with marshaling
RuntimeError: Aborted(Assertion failed: list_empty(&rt->gc_obj_list), at: ../../vendor/quickjs/quickjs.c,2036,JS_FreeRuntime).
The remaining 136 tests pass. The specific failure path:
- The test sets a 200 KB memory limit on the runtime
- The marshaling layer exposes host functions to the sandbox via
newFunction — under 0.32 these get interned in the runtime's HostRefMap
- The QuickJS sandbox runs a loop that exceeds the memory limit; QuickJS aborts the operation mid-way
arena.dispose() cleans up the handles it tracks, but does not clean up the orphaned HostRefMap entries left by the aborted operation
ctx.dispose() → JS_FreeRuntime asserts the GC object list is empty — but it isn't, because the HostRefMap entries are still alive in the runtime
- The WASM module aborts at the C-level assertion
The underlying issue is broader than the specific memory-limit case: any abort path (memory limit, stack overflow, interrupt handler, etc.) that interrupts marshaling will likely leave HostRefs orphaned and trigger the same assertion on disposal.
Scope of work to upgrade
Estimate
Half a day to a full day depending on how deep the marshaling integration goes. Worth pairing with someone who has prior context on this wrapper.
Why this is being tracked separately
The secure-release-workflow PR (#70) is unrelated to this work and shouldn't be held up by it. Renovate will continue surfacing the 0.32.0 update on its normal cadence; this issue exists so the work doesn't get forgotten when someone gets to a "yes, finally" moment on the Renovate PR.
Background
Upstream
quickjs-emscripten0.32.0 (released 2026-02-16) reworked function binding innewFunctionto use a new abstraction calledHostRef. From the upstream changelog:New API surface introduced upstream:
newHostRef,toHostRef,unwrapHostRef,QuickJSContext.newConstructorFunction.Why this matters for this wrapper
Naively bumping the
quickjs-emscriptendevDependency from 0.31.0 to 0.32.0 causes the following test to fail:The remaining 136 tests pass. The specific failure path:
newFunction— under 0.32 these get interned in the runtime'sHostRefMaparena.dispose()cleans up the handles it tracks, but does not clean up the orphanedHostRefMapentries left by the aborted operationctx.dispose() → JS_FreeRuntimeasserts the GC object list is empty — but it isn't, because the HostRefMap entries are still alive in the runtimeThe underlying issue is broader than the specific memory-limit case: any abort path (memory limit, stack overflow, interrupt handler, etc.) that interrupts marshaling will likely leave HostRefs orphaned and trigger the same assertion on disposal.
Scope of work to upgrade
newHostRef,toHostRef,unwrapHostRef,newConstructorFunction) and document lifecycle guaranteesnewFunctioncall site insrc/(notablysrc/marshal/function.ts,src/marshal/object.ts, anywhere we expose host callables)newFunctionto the newnewHostRefprimitive (cleaner, more direct control over lifetime) or stay withnewFunctionand add explicitHostRefMapcleanup hooksArena.dispose()(andVMMap.dispose()if relevant) to handle HostRef cleanup on abort pathssetMaxStackSize, interrupt handler aborting an operation, etc.) so we know the fix is generalquickjs-emscripten0.31.0 → 0.32.0 in package.json and verify all 137 tests pass"*", but worth considering pinning to>=0.32once stable)Estimate
Half a day to a full day depending on how deep the marshaling integration goes. Worth pairing with someone who has prior context on this wrapper.
Why this is being tracked separately
The secure-release-workflow PR (#70) is unrelated to this work and shouldn't be held up by it. Renovate will continue surfacing the 0.32.0 update on its normal cadence; this issue exists so the work doesn't get forgotten when someone gets to a "yes, finally" moment on the Renovate PR.