Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/check-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 12 additions & 12 deletions auto/quickjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <quickjs_compat.h>

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;
Expand Down
5 changes: 2 additions & 3 deletions external/njs_shell.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions nginx/ngx_http_js_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 6 additions & 8 deletions nginx/ngx_qjs_fetch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
48 changes: 46 additions & 2 deletions src/qjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand Down
43 changes: 21 additions & 22 deletions src/qjs.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,15 @@
#include <quickjs_compat.h>


/*
* 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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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_ */
15 changes: 11 additions & 4 deletions src/qjs_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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
Expand Down
Loading