From: Dmitry Volyntsev Date: Mon, 17 Aug 2026 16:33:34 +0000 (-0700) Subject: QuickJS: fix compatibility with quickjs-ng 0.16.0 and later X-Git-Tag: 1.0.1~6 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=de76bf747641df80249a9047bb5f2b5c4bcbc1c0;p=njs.git QuickJS: fix compatibility with quickjs-ng 0.16.0 and later Two separate problems prevented njs from working with the recent quickjs-ng releases. JS_NewArrayBuffer() has grown a "max_len" argument and its free callback has become a realloc one. The calls are wrapped now, the callback has to implement the whole realloc contract, because transfer() reallocates the data even for a buffer which is not resizable. The class ids used by njs started at 64, while the engine reserves the ids below JS_CLASS_INIT_COUNT, which has reached 67. JS_NewClass() failed with a collision and the context could not be created. The ids are moved above the engine range and the remaining unchecked JS_NewClass() calls now report the failure. This fixes #1113 issue on Github. --- diff --git a/auto/quickjs b/auto/quickjs index 630f9870..785ee783 100644 --- a/auto/quickjs +++ b/auto/quickjs @@ -187,6 +187,33 @@ if [ $NJS_TRY_QUICKJS = YES ]; then . auto/feature + njs_feature="QuickJS JS_NewArrayBuffer() with max_len" + njs_feature_name=NJS_HAVE_QUICKJS_ARRAY_BUFFER_MAX_LEN + njs_feature_run=no + njs_feature_test="#include + + static void * + realloc_func(JSRuntime *rt, void *opaque, void *ptr, + size_t size) + { + return NULL; + } + + int main() { + JSRuntime *rt; + JSContext *ctx; + + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); + (void) JS_NewArrayBuffer(ctx, NULL, 0, 0, + realloc_func, NULL, 0); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 0; + }" + + . auto/feature + njs_feature="QuickJS JS_NewError() attaches stack" njs_feature_run=value njs_feature_name=NJS_HAVE_QUICKJS_NEW_ERROR_STACK diff --git a/external/njs_shell.c b/external/njs_shell.c index b65b98aa..1b9582a5 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -2081,9 +2081,8 @@ njs_qjs_agent(void *arg) pthread_cond_signal(&console->agent_cond); pthread_mutex_unlock(&console->agent_mutex); - args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf, - agent->broadcast_sab_size, - NULL, NULL, 1); + args[0] = qjs_new_external_array_buffer(ctx, + agent->broadcast_sab_buf, agent->broadcast_sab_size, 1); args[1] = JS_NewInt32(ctx, agent->broadcast_val); ret_val = JS_Call(ctx, agent->broadcast_func, JS_UNDEFINED, diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index d3da585f..e5c2b655 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -6681,8 +6681,8 @@ ngx_http_qjs_body_to_value(JSContext *cx, ngx_http_js_ctx_t *ctx, switch (type) { case NGX_JS_BODY_ARRAY_BUFFER: - return JS_NewArrayBuffer(cx, ctx->body_read_data, - ctx->body_read_len, NULL, NULL, 0); + return qjs_new_external_array_buffer(cx, ctx->body_read_data, + ctx->body_read_len, 0); case NGX_JS_BODY_JSON: if (ctx->body_read_nul) { diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 7adcda4f..5a8e98d7 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -2002,12 +2002,11 @@ ngx_qjs_ext_fetch_request_body(JSContext *cx, JSValueConst this_val, switch (magic) { case NGX_JS_BODY_ARRAY_BUFFER: /* - * no free_func for JS_NewArrayBuffer() - * because request->body is allocated from e->pool + * request->body is allocated from e->pool * and will be freed when context is freed. */ - result = JS_NewArrayBuffer(cx, request->body.data, request->body.len, - NULL, NULL, 0); + result = qjs_new_external_array_buffer(cx, request->body.data, + request->body.len, 0); if (JS_IsException(result)) { return JS_ThrowOutOfMemory(cx); } @@ -2331,12 +2330,11 @@ ngx_qjs_ext_fetch_response_body(JSContext *cx, JSValueConst this_val, } /* - * no free_func for JS_NewArrayBuffer() - * because string.start is allocated from e->pool + * string.start is allocated from e->pool * and will be freed when context is freed. */ - result = JS_NewArrayBuffer(cx, string.start, string.length, NULL, NULL, - 0); + result = qjs_new_external_array_buffer(cx, string.start, + string.length, 0); if (JS_IsException(result)) { return JS_ThrowOutOfMemory(cx); } diff --git a/src/qjs.c b/src/qjs.c index c8ed8eaf..90a464d6 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -1252,17 +1252,61 @@ qjs_typed_array_data(JSContext *ctx, JSValueConst value, njs_str_t *data) } +#ifdef NJS_HAVE_QUICKJS_ARRAY_BUFFER_MAX_LEN + +#define qjs_array_buffer_create(cx, src, len, free, shared) \ + JS_NewArrayBuffer(cx, src, len, 0, free, NULL, shared) + + +/* + * ArrayBuffer.prototype.transfer() reallocates the data even when the + * buffer is not resizable, so the whole realloc contract has to be + * implemented here. + */ + +static void * +qjs_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr, size_t size) +{ + if (size == 0) { + js_free_rt(rt, ptr); + return NULL; + } + + return js_realloc_rt(rt, ptr, size); +} + +#else + +#define qjs_array_buffer_create(cx, src, len, free, shared) \ + JS_NewArrayBuffer(cx, src, len, free, NULL, shared) + + static void -js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr) +qjs_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr) { js_free_rt(rt, ptr); } +#endif + JSValue qjs_new_array_buffer(JSContext *cx, uint8_t *src, size_t len) { - return JS_NewArrayBuffer(cx, src, len, js_array_buffer_free, NULL, 0); + return qjs_array_buffer_create(cx, src, len, qjs_array_buffer_free, 0); +} + + +/* + * The memory is not managed by the engine, the caller is responsible for + * keeping it alive while the buffer is reachable. + */ + +JSValue +qjs_new_external_array_buffer(JSContext *cx, uint8_t *src, size_t len, + int is_shared) +{ + return qjs_array_buffer_create(cx, src, len, NULL, is_shared); } diff --git a/src/qjs.h b/src/qjs.h index 36536152..cc45643f 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -22,8 +22,15 @@ #include +/* + * The engine reserves class ids below JS_CLASS_INIT_COUNT for its own + * classes. The value is internal and grows between releases, so the ids + * below start far enough above it. + */ +#define QJS_CORE_CLASS_ID_OFFSET 128 + enum { - QJS_CORE_CLASS_ID_BUFFER = 64, + QJS_CORE_CLASS_ID_BUFFER = QJS_CORE_CLASS_ID_OFFSET, QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, QJS_CORE_CLASS_ID_TEXT_DECODER, QJS_CORE_CLASS_ID_TEXT_ENCODER, @@ -55,6 +62,8 @@ JSValue qjs_call_exit_hook(JSContext *ctx); JSValue qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv); JSValue qjs_new_array_buffer(JSContext *cx, uint8_t *src, size_t len); +JSValue qjs_new_external_array_buffer(JSContext *cx, uint8_t *src, size_t len, + int is_shared); JSValue qjs_buffer_alloc(JSContext *ctx, size_t size); JSValue qjs_buffer_create(JSContext *ctx, u_char *start, size_t size); JSValue qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain); diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index b081d1e7..b6125135 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -2666,8 +2666,11 @@ qjs_buffer_builtin_init(JSContext *ctx) JSValue global_obj, buffer, proto, ctor, ta, ta_proto, symbol, species; JSClassID u8_ta_class_id; - JS_NewClass(JS_GetRuntime(ctx), QJS_CORE_CLASS_ID_BUFFER, - &qjs_buffer_class); + if (JS_NewClass(JS_GetRuntime(ctx), QJS_CORE_CLASS_ID_BUFFER, + &qjs_buffer_class) < 0) + { + return -1; + } global_obj = JS_GetGlobalObject(ctx); @@ -2683,8 +2686,12 @@ qjs_buffer_builtin_init(JSContext *ctx) * We use JS_SetClassProto()/JS_GetClassProto() as a key-value store * for fast value query by class ID without querying the global object. */ - JS_NewClass(JS_GetRuntime(ctx), QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, - &qjs_uint8_array_ctor_class); + if (JS_NewClass(JS_GetRuntime(ctx), QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, + &qjs_uint8_array_ctor_class) < 0) + { + return -1; + } + JS_SetClassProto(ctx, QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, JS_DupValue(ctx, ctor)); #endif