diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index c5afdb303..06d13ab90 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -59,8 +59,8 @@ jobs: run: | git clone https://github.com/quickjs-ng/quickjs quickjs-ng cd quickjs-ng - git checkout v0.11.0 - CFLAGS="$CC_OPT -fPIC" LDFLAGS=$LD_OPT meson setup build --prefix=$HOME/.local --libdir=lib + git checkout v0.16.2 + CFLAGS="$CC_OPT -fPIC" LDFLAGS=$LD_OPT meson setup build --prefix=$HOME/.local --libdir=lib -Ddefault_library=static meson compile -C build meson install -C build echo "PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH" >> $GITHUB_ENV diff --git a/auto/quickjs b/auto/quickjs index 630f98700..80e79b2c6 100644 --- a/auto/quickjs +++ b/auto/quickjs @@ -187,26 +187,26 @@ if [ $NJS_TRY_QUICKJS = YES ]; then . auto/feature - njs_feature="QuickJS JS_NewError() attaches stack" - njs_feature_run=value - njs_feature_name=NJS_HAVE_QUICKJS_NEW_ERROR_STACK + 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() { - int rc; - JSAtom atom; - JSValue err; JSRuntime *rt; JSContext *ctx; rt = JS_NewRuntime(); ctx = JS_NewContext(rt); - err = JS_NewError(ctx); - atom = JS_NewAtom(ctx, \"stack\"); - rc = JS_HasProperty(ctx, err, atom); - printf(\"%d\", rc); - JS_FreeAtom(ctx, atom); - JS_FreeValue(ctx, err); + (void) JS_NewArrayBuffer(ctx, NULL, 0, 0, + realloc_func, NULL, 0); JS_FreeContext(ctx); JS_FreeRuntime(rt); return 0; diff --git a/external/njs_shell.c b/external/njs_shell.c index b65b98aae..1b9582a54 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 d3da585f8..e5c2b655d 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 7adcda4f6..5a8e98d78 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 c8ed8eafb..90a464d6e 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 365361520..32aa80d99 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); @@ -141,30 +150,26 @@ static inline JS_BOOL JS_IsNullOrUndefined(JSValueConst v) } /* - * QuickJS-NG attaches the "stack" property to an Error object too early, - * which results in empty stack trace when called from C code. - * Removing it allows the stack to be attached later during unwinding. + * JS_NewError() records the stack of the caller, which is empty for an + * error created outside of a JS frame. Since QuickJS-NG 0.16.0 the + * recorded stack is also final, the engine never replaces it. Creating + * the object without letting the engine record anything allows the stack + * to be attached where the error is actually thrown. */ -static inline JSValue qjs_new_error2(JSContext *cx) +static inline JSValue qjs_new_error(JSContext *cx) { - JSAtom stack; - JSValue error; - - stack = JS_NewAtom(cx, "stack"); - if (stack == JS_ATOM_NULL) { - return JS_EXCEPTION; - } + JSValue error; + JSClassID class_id; error = JS_NewError(cx); if (JS_IsException(error)) { - JS_FreeAtom(cx, stack); return JS_EXCEPTION; } - JS_DeleteProperty(cx, error, stack, 0); - JS_FreeAtom(cx, stack); + class_id = JS_GetClassID(error); + JS_FreeValue(cx, error); - return error; + return JS_NewObjectClass(cx, class_id); } #ifdef NJS_HAVE_QUICKJS_IS_SAME_VALUE @@ -185,12 +190,6 @@ static inline JSValue qjs_new_error2(JSContext *cx) #define qjs_is_error(cx, a) JS_IsError(cx, a) #endif -#ifdef NJS_HAVE_QUICKJS_NEW_ERROR_STACK -#define qjs_new_error(cx) qjs_new_error2(cx) -#else -#define qjs_new_error(cx) JS_NewError(cx) -#endif - extern qjs_module_t *qjs_modules[]; #endif /* _QJS_H_INCLUDED_ */ diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index b081d1e74..b61251353 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