c/interface.c doesn't compile against quickjs-ng 0.16.0, because JS_NewArrayBuffer gained a parameter and its free callback became a realloc callback. Filing this with a working patch in case it's useful — happy to open a PR if you'd like it in that form.
The break
main (7b7af98) vendors quickjs-ng v0.12.1. Dropping in the published quickjs-amalgam.zip for v0.16.0 fails at two call sites:
c/interface.c:706:85: error: too few arguments to function call, expected 7, have 6
c/interface.c:1555:92: error: too few arguments to function call, expected 7, have 6
The signature changed to support ES2024 resizable ArrayBuffers:
// <= 0.15.1
JS_NewArrayBuffer(ctx, buf, len, free_func, opaque, is_shared)
typedef void JSFreeArrayBufferDataFunc (JSRuntime*, void *opaque, void *ptr);
// 0.16.0
JS_NewArrayBuffer(ctx, buf, len, max_len, realloc_func, opaque, is_shared)
typedef void *JSReallocArrayBufferDataFunc(JSRuntime*, void *opaque, void *ptr, size_t size);
Growing a buffer requires reallocation, so freeing became a special case of realloc — the engine calls abuf->realloc_func(rt, abuf->opaque, abuf->data, 0) on collection.
0.16.0 is the first affected release. I checked the published amalgams for v0.13.0 and v0.15.1 and both still carry the 6-argument form; the upstream commit ("Support resizable externally managed ArrayBuffers") is dated 2026-07-21, between the 0.15.1 and 0.16.0 releases.
Two things that make the port less mechanical than it looks
The two call sites use different allocators. QTS_NewArrayBuffer gets a host malloc buffer, so it needs free/realloc. QTS_bjson_encode gets a buffer from JS_WriteObject, i.e. quickjs's own allocator, so it needs js_realloc_rt — which conveniently already frees at size 0, making that shim identical to the engine's internal js_array_buffer_realloc. Using the wrong one is heap corruption rather than a compile error.
Passing NULL for realloc_func compiles and leaks. It's the smallest change that builds, but the header defines NULL as "the memory must not be managed by quickjs at all", so nothing frees the buffer.
I used max_len = 0 at both sites, which keeps the buffers fixed-length and preserves the pre-0.16 behaviour exactly.
Patch
-void qts_free_buffer(JSRuntime *unused_rt, void *unused_opaque, void *ptr) { free(ptr); }
+void *qts_realloc_buffer(JSRuntime *unused_rt, void *unused_opaque, void *ptr, size_t size) {
+ if (size == 0) {
+ free(ptr);
+ return NULL;
+ }
+ return realloc(ptr, size);
+}
JSValue *QTS_NewArrayBuffer(JSContext *ctx, JSVoid *buffer, size_t length) {
return jsvalue_to_heap(
- JS_NewArrayBuffer(ctx, (uint8_t *)buffer, length, qts_free_buffer, NULL, false));
+ JS_NewArrayBuffer(ctx, (uint8_t *)buffer, length, 0, qts_realloc_buffer, NULL, false));
}
-static void qts_free_bjson_buffer(JSRuntime *rt, void *opaque, void *ptr) {
- js_free_rt(rt, ptr);
+static void *qts_realloc_bjson_buffer(JSRuntime *rt, void *opaque, void *ptr, size_t size) {
+ return js_realloc_rt(rt, ptr, size);
}
...
- JSValue array = JS_NewArrayBuffer(ctx, buffer, length, qts_free_bjson_buffer, NULL, false);
+ JSValue array = JS_NewArrayBuffer(ctx, buffer, length, 0, qts_realloc_bjson_buffer, NULL, false);
Since this only changes the callback plumbing, it should be conditional on the vendored version if you want to keep building against older quickjs-ng.
One other note for anyone attempting the same upgrade: replacing the amalgam also drops vendor/quickjs-ng-patches/0001-bellard-module-detection.patch, which surfaces later as undefined symbol: QTS_DetectModule at link time. It re-applies cleanly to 0.16.0 with an offset.
Verification
Built the quickjs-ng-wasmfile-release-sync variant from 0.16.0 with the above (Docker emsdk, unmodified Makefile). The resulting wasm reports 0.16.0, evalCode works, the interrupt handler still terminates while(true), setMemoryLimit is still enforced, and a 41-call async workload returns byte-identical output about 10% faster than the 0.12.1 build.
Why I care about the upgrade specifically
quickjs-ng has published ten security advisories, all of which postdate 0.12.1 — including a critical RCE in js_array_toReversed fixed in 0.15.0, and eight more fixed in 0.16.0. Anyone consuming the published @jitl/quickjs-ng-* variants is getting an engine from before all of them, and since the npm packages are the only distribution channel there's no way to pick up the fixes downstream.
Entirely understand this is your call and your time — thanks for maintaining this, it's a genuinely useful project.
c/interface.cdoesn't compile against quickjs-ng 0.16.0, becauseJS_NewArrayBuffergained a parameter and its free callback became a realloc callback. Filing this with a working patch in case it's useful — happy to open a PR if you'd like it in that form.The break
main(7b7af98) vendors quickjs-ngv0.12.1. Dropping in the publishedquickjs-amalgam.zipforv0.16.0fails at two call sites:The signature changed to support ES2024 resizable ArrayBuffers:
Growing a buffer requires reallocation, so freeing became a special case of realloc — the engine calls
abuf->realloc_func(rt, abuf->opaque, abuf->data, 0)on collection.0.16.0 is the first affected release. I checked the published amalgams for
v0.13.0andv0.15.1and both still carry the 6-argument form; the upstream commit ("Support resizable externally managed ArrayBuffers") is dated 2026-07-21, between the 0.15.1 and 0.16.0 releases.Two things that make the port less mechanical than it looks
The two call sites use different allocators.
QTS_NewArrayBuffergets a hostmallocbuffer, so it needsfree/realloc.QTS_bjson_encodegets a buffer fromJS_WriteObject, i.e. quickjs's own allocator, so it needsjs_realloc_rt— which conveniently already frees at size 0, making that shim identical to the engine's internaljs_array_buffer_realloc. Using the wrong one is heap corruption rather than a compile error.Passing
NULLforrealloc_funccompiles and leaks. It's the smallest change that builds, but the header defines NULL as "the memory must not be managed by quickjs at all", so nothing frees the buffer.I used
max_len = 0at both sites, which keeps the buffers fixed-length and preserves the pre-0.16 behaviour exactly.Patch
Since this only changes the callback plumbing, it should be conditional on the vendored version if you want to keep building against older quickjs-ng.
One other note for anyone attempting the same upgrade: replacing the amalgam also drops
vendor/quickjs-ng-patches/0001-bellard-module-detection.patch, which surfaces later asundefined symbol: QTS_DetectModuleat link time. It re-applies cleanly to 0.16.0 with an offset.Verification
Built the
quickjs-ng-wasmfile-release-syncvariant from 0.16.0 with the above (Docker emsdk, unmodified Makefile). The resulting wasm reports0.16.0,evalCodeworks, the interrupt handler still terminateswhile(true),setMemoryLimitis still enforced, and a 41-call async workload returns byte-identical output about 10% faster than the 0.12.1 build.Why I care about the upgrade specifically
quickjs-ng has published ten security advisories, all of which postdate 0.12.1 — including a critical RCE in
js_array_toReversedfixed in 0.15.0, and eight more fixed in 0.16.0. Anyone consuming the published@jitl/quickjs-ng-*variants is getting an engine from before all of them, and since the npm packages are the only distribution channel there's no way to pick up the fixes downstream.Entirely understand this is your call and your time — thanks for maintaining this, it's a genuinely useful project.