diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index d71addbd0..f54aed01c 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -68,6 +68,7 @@ jobs: - name: Configure and make njs run: | ./configure \ + --debug=YES \ --cc-opt="$CC_OPT" \ --ld-opt="$LD_OPT" \ || cat build/autoconf.err @@ -81,6 +82,7 @@ jobs: - name: Configure and make njs, 32-bit run: | ./configure \ + --debug=YES \ --cc-opt="$CC_OPT -m32" \ --ld-opt="$LD_OPT" \ || cat build/autoconf.err @@ -94,6 +96,7 @@ jobs: - name: Configure and make njs with quickjs run: | ./configure \ + --debug=YES \ --with-quickjs \ --cc-opt="$CC_OPT -Iquickjs" \ --ld-opt="$LD_OPT -Lquickjs" \ @@ -108,6 +111,7 @@ jobs: - name: Configure and make njs with quickjs-ng run: | ./configure \ + --debug=YES \ --with-quickjs \ --cc-opt="$CC_OPT" \ --ld-opt="$LD_OPT" \ diff --git a/nginx/config b/nginx/config index 5e2d9277f..f54234a83 100644 --- a/nginx/config +++ b/nginx/config @@ -157,7 +157,7 @@ fi if [ $HTTP != NO ]; then ngx_module_type=HTTP_AUX_FILTER - ngx_module_name=ngx_http_js_module + ngx_module_name="ngx_http_js_module ngx_http_js_core_module" ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build \ $NJS_QUICKJS_INC" ngx_module_deps="$NJS_ENGINE_DEP $NJS_DEPS $QJS_DEPS" @@ -174,7 +174,7 @@ fi if [ $STREAM != NO ]; then ngx_module_type=STREAM - ngx_module_name=ngx_stream_js_module + ngx_module_name="ngx_stream_js_module ngx_stream_js_core_module" ngx_module_incs="$ngx_addon_dir/../src $ngx_addon_dir/../build \ $NJS_QUICKJS_INC" ngx_module_deps="$NJS_ENGINE_DEP $NJS_DEPS $QJS_DEPS" diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 8b38dbfde..2027e6a56 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -608,6 +608,42 @@ static ngx_command_t ngx_http_js_commands[] = { }; +static ngx_command_t ngx_js_core_commands[] = { + + { ngx_string("js_load_http_native_module"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE13, + ngx_js_core_load_native_module, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_js_core_module_ctx = { + ngx_string("ngx_http_js_core"), + ngx_js_core_create_conf, + NULL +}; + + +ngx_module_t ngx_http_js_core_module = { + NGX_MODULE_V1, + &ngx_js_core_module_ctx, /* module context */ + ngx_js_core_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + static ngx_http_module_t ngx_http_js_module_ctx = { NULL, /* preconfiguration */ ngx_http_js_init, /* postconfiguration */ @@ -7760,6 +7796,9 @@ ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) options.u.qjs.metas = ngx_http_js_uptr; options.u.qjs.addons = njs_http_qjs_addon_modules; options.clone = ngx_engine_qjs_clone; + + options.core_conf = (ngx_js_core_conf_t *) + ngx_get_conf(cf->cycle->conf_ctx, ngx_http_js_core_module); } #endif diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index b0b64d974..bd1b5f237 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "ngx_js.h" #include "ngx_js_http.h" @@ -541,6 +542,8 @@ ngx_create_engine(ngx_engine_opts_t *opts) engine->string = ngx_engine_qjs_string; engine->destroy = opts->destroy ? opts->destroy : ngx_engine_qjs_destroy; + + engine->core_conf = opts->core_conf; break; #endif @@ -1005,6 +1008,7 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) ngx_int_t rc; JSRuntime *rt; JSContext *cx; + qjs_module_t *mod; ngx_engine_t *engine; ngx_js_code_entry_t *pc; @@ -1050,6 +1054,19 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) JS_SetHostPromiseRejectionTracker(rt, ngx_qjs_rejection_tracker, ctx); + if (engine->native_modules != NULL) { + mod = engine->native_modules->start; + length = engine->native_modules->items; + + for (i = 0; i < length; i++) { + if (mod[i].init(cx, mod[i].name) == NULL) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js native module init failed: %s", mod[i].name); + goto destroy; + } + } + } + rv = JS_UNDEFINED; pc = engine->precompiled->start; length = engine->precompiled->items; @@ -2026,6 +2043,55 @@ ngx_qjs_ext_console_time_end(JSContext *cx, JSValueConst this_val, int argc, } +static JSModuleDef * +ngx_qjs_native_module_lookup(JSContext *cx, const char *module_name, + ngx_js_loc_conf_t *conf) +{ + ngx_uint_t i; + JSModuleDef *m; + qjs_module_t *mod, *modules; + ngx_js_core_conf_t *jccf; + + jccf = conf->engine->core_conf; + if (jccf == NULL || jccf->native_modules == NULL) { + return NULL; + } + + modules = jccf->native_modules->elts; + + for (i = 0; i < jccf->native_modules->nelts; i++) { + if (ngx_strcmp(modules[i].name, module_name) == 0) { + m = modules[i].init(cx, module_name); + if (m == NULL) { + return NULL; + } + + if (conf->engine->native_modules == NULL) { + conf->engine->native_modules = njs_arr_create( + conf->engine->pool, 4, + sizeof(qjs_module_t)); + if (conf->engine->native_modules == NULL) { + JS_ThrowOutOfMemory(cx); + return NULL; + } + } + + mod = njs_arr_add(conf->engine->native_modules); + if (mod == NULL) { + JS_ThrowOutOfMemory(cx); + return NULL; + } + + *mod = modules[i]; + + return m; + } + } + + return NULL; +} + + static JSModuleDef * ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque) { @@ -2039,6 +2105,11 @@ ngx_qjs_module_loader(JSContext *cx, const char *module_name, void *opaque) conf = opaque; + m = ngx_qjs_native_module_lookup(cx, module_name, conf); + if (m != NULL) { + return m; + } + njs_memzero(&info, sizeof(njs_module_info_t)); info.name.start = (u_char *) module_name; @@ -3597,6 +3668,17 @@ ngx_js_init_preload_vm(njs_vm_t *vm, ngx_js_loc_conf_t *conf) } +/* + * Merge configuration values used at configuration time. + */ +static void +ngx_js_merge_conftime_loc_conf(ngx_js_loc_conf_t *conf, + ngx_js_loc_conf_t *prev) +{ + ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS); +} + + ngx_int_t ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, ngx_js_loc_conf_t *prev, @@ -3612,6 +3694,9 @@ ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, * special handling to preserve conf->engine * in the "http" or "stream" section to inherit it to all servers */ + + ngx_js_merge_conftime_loc_conf(prev, conf); + if (init_vm(cf, (ngx_js_loc_conf_t *) prev) != NGX_OK) { return NGX_ERROR; } @@ -4316,10 +4401,7 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_js_loc_conf_t *prev = parent; ngx_js_loc_conf_t *conf = child; - ngx_conf_merge_uint_value(conf->type, prev->type, NGX_ENGINE_NJS); - if (prev->type == NGX_CONF_UNSET_UINT) { - prev->type = NGX_ENGINE_NJS; - } + ngx_js_merge_conftime_loc_conf(conf, prev); ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); ngx_conf_merge_size_value(conf->reuse, prev->reuse, 128); @@ -4380,6 +4462,144 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, } +void * +ngx_js_core_create_conf(ngx_cycle_t *cycle) +{ + ngx_js_core_conf_t *jccf; + + jccf = ngx_pcalloc(cycle->pool, sizeof(ngx_js_core_conf_t)); + if (jccf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * jccf->native_modules = NULL; + */ + + return jccf; +} + + +void +ngx_js_native_module_cleanup(void *data) +{ + void *handle = data; + + if (dlclose(handle) != 0) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, + "dlclose() failed: %s", dlerror()); + } +} + + +char * +ngx_js_core_load_native_module(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ +#if (NJS_HAVE_QUICKJS) + void *handle; + u_char *p; + ngx_str_t *value, file, name; + qjs_module_t *module; + qjs_addon_init_pt init; + ngx_pool_cleanup_t *cln; + + ngx_js_core_conf_t *jccf = conf; + + if (cf->cycle->modules_used) { + return "is specified too late"; + } + + value = cf->args->elts; + file = value[1]; + + if (ngx_conf_full_name(cf->cycle, &file, 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 4) { + if (ngx_strcmp(value[2].data, "as") != 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\", expected \"as\"", + &value[2]); + return NGX_CONF_ERROR; + } + + name = value[3]; + + } else { + name = file; + + for (p = file.data + file.len - 1; p >= file.data; p--) { + if (*p == '/') { + name.data = p + 1; + name.len = file.data + file.len - name.data; + break; + } + } + } + + handle = dlopen((char *) file.data, RTLD_NOW | RTLD_LOCAL); + if (handle == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "dlopen(\"%V\") failed: %s", &file, dlerror()); + return NGX_CONF_ERROR; + } + + cln = ngx_pool_cleanup_add(cf->cycle->pool, 0); + if (cln == NULL) { + dlclose(handle); + return NGX_CONF_ERROR; + } + + cln->handler = ngx_js_native_module_cleanup; + cln->data = handle; + + init = dlsym(handle, "js_init_module"); + if (init == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "dlsym(\"%V\", \"js_init_module\") failed: %s", + &file, dlerror()); + return NGX_CONF_ERROR; + } + + if (jccf->native_modules == NULL) { + jccf->native_modules = ngx_array_create(cf->cycle->pool, 4, + sizeof(qjs_module_t)); + if (jccf->native_modules == NULL) { + return NGX_CONF_ERROR; + } + } + + module = ngx_array_push(jccf->native_modules); + if (module == NULL) { + return NGX_CONF_ERROR; + } + + p = ngx_palloc(cf->cycle->pool, name.len + 1); + if (p == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memcpy(p, name.data, name.len); + p[name.len] = '\0'; + + module->name = (const char *) p; + module->init = init; + + return NGX_CONF_OK; + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"load_js_native_module\" requires QuickJS support"); + return NGX_CONF_ERROR; + +#endif +} + + static uint64_t ngx_js_monotonic_time(void) { diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index f3c2493b8..a25dc65a0 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -85,6 +85,11 @@ typedef ngx_js_loc_conf_t *(*ngx_js_external_loc_conf_pt)(njs_external_ptr_t e); typedef ngx_js_ctx_t *(*ngx_js_external_ctx_pt)(njs_external_ptr_t e); +typedef struct { + ngx_array_t *native_modules; +} ngx_js_core_conf_t; + + typedef struct { ngx_str_t name; ngx_str_t path; @@ -245,6 +250,7 @@ typedef struct ngx_engine_opts_s { } u; njs_str_t file; + ngx_js_core_conf_t *core_conf; ngx_js_loc_conf_t *conf; ngx_engine_t *(*clone)(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, njs_int_t pr_id, @@ -292,6 +298,8 @@ struct ngx_engine_s { const char *name; njs_mp_t *pool; njs_arr_t *precompiled; + njs_arr_t *native_modules; + ngx_js_core_conf_t *core_conf; }; @@ -455,6 +463,11 @@ char * ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, char *ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, void *tag); +void *ngx_js_core_create_conf(ngx_cycle_t *cycle); +char *ngx_js_core_load_native_module(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +void ngx_js_native_module_cleanup(void *data); + njs_int_t ngx_js_ext_string(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); njs_int_t ngx_js_ext_uint(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index cbceaf923..b21b701dd 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -440,6 +440,42 @@ static ngx_command_t ngx_stream_js_commands[] = { }; +static ngx_command_t ngx_js_core_commands[] = { + + { ngx_string("js_load_stream_native_module"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE13, + ngx_js_core_load_native_module, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_js_core_module_ctx = { + ngx_string("ngx_stream_js_core"), + ngx_js_core_create_conf, + NULL +}; + + +ngx_module_t ngx_stream_js_core_module = { + NGX_MODULE_V1, + &ngx_js_core_module_ctx, /* module context */ + ngx_js_core_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + static ngx_stream_module_t ngx_stream_js_module_ctx = { NULL, /* preconfiguration */ ngx_stream_js_init, /* postconfiguration */ @@ -3036,6 +3072,9 @@ ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) options.u.qjs.addons = njs_stream_qjs_addon_modules; options.clone = ngx_engine_qjs_clone; options.destroy = ngx_stream_qjs_destroy; + + options.core_conf = (ngx_js_core_conf_t *) + ngx_get_conf(cf->cycle->conf_ctx, ngx_stream_js_core_module); } #endif diff --git a/nginx/t/js_native_module.t b/nginx/t/js_native_module.t new file mode 100644 index 000000000..d8ec683ea --- /dev/null +++ b/nginx/t/js_native_module.t @@ -0,0 +1,211 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for QuickJS native module support. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $cc; +for my $c ('gcc', 'clang') { + if (system("which $c >/dev/null 2>&1") == 0) { + $cc = $c; + last; + } +} + +plan(skip_all => "gcc or clang not found") unless defined $cc; + +my $configure_args = `$Test::Nginx::NGINX -V 2>&1`; +my $m32 = $configure_args =~ /-m32/ ? '-m32' : ''; +my $quickjs_inc = $configure_args =~ /(-I\S*quickjs(?:-ng)?[^\s'"]*)/ + ? $1 : undef; + +plan(skip_all => "QuickJS development files not found") unless $quickjs_inc; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +js_load_http_native_module %%TESTDIR%%/test.so; +js_load_http_native_module %%TESTDIR%%/test.so as test; + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import main from test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /add { + js_content main.test_add; + } + + location /reverse { + js_content main.test_reverse; + } + } +} + +EOF + +my $d = $t->testdir(); + +$t->write_file('test.js', <write_file('test.c', < + +#define countof(x) (sizeof(x) / sizeof((x)[0])) + +static JSValue +js_add(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + int a, b; + + if (argc < 2) { + return JS_ThrowTypeError(ctx, "expected 2 arguments"); + } + + if (JS_ToInt32(ctx, &a, argv[0]) < 0) { + return JS_EXCEPTION; + } + + if (JS_ToInt32(ctx, &b, argv[1]) < 0) { + return JS_EXCEPTION; + } + + return JS_NewInt32(ctx, a + b); +} + + +static JSValue +js_reverse_string(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + char *reversed; + size_t i, len; + JSValue result; + const char *str; + + if (argc < 1) { + return JS_ThrowTypeError(ctx, "expected 1 argument"); + } + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) { + return JS_EXCEPTION; + } + + reversed = js_malloc(ctx, len + 1); + if (!reversed) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + for (i = 0; i < len; i++) { + reversed[i] = str[len - 1 - i]; + } + + reversed[len] = 0; + + result = JS_NewString(ctx, reversed); + + js_free(ctx, reversed); + JS_FreeCString(ctx, str); + + return result; +} + + +static const JSCFunctionListEntry js_test_native_funcs[] = { + JS_CFUNC_DEF("add", 2, js_add), + JS_CFUNC_DEF("reverseString", 1, js_reverse_string), +}; + + +static int +js_test_native_init(JSContext *ctx, JSModuleDef *m) +{ + return JS_SetModuleExportList(ctx, m, js_test_native_funcs, + countof(js_test_native_funcs)); +} + + +JSModuleDef * +js_init_module(JSContext *ctx, const char *module_name) +{ + int rc; + JSModuleDef *m; + + m = JS_NewCModule(ctx, module_name, js_test_native_init); + if (!m) { + return NULL; + } + + rc = JS_AddModuleExportList(ctx, m, js_test_native_funcs, + countof(js_test_native_funcs)); + if (rc < 0) { + return NULL; + } + + rc = JS_AddModuleExport(ctx, m, "default"); + if (rc < 0) { + return NULL; + } + + return m; +} +EOF + +system("$cc -fPIC $m32 -O $quickjs_inc -shared -o $d/test.so $d/test.c") == 0 + or die "failed to build QuickJS native module: $!\n"; + +$t->try_run('no QuickJS native module support')->plan(2); + +############################################################################### + +like(http_get('/add?a=7&b=9'), qr/16$/, 'native module add'); +like(http_get('/reverse?str=hello'), qr/olleh$/, 'native module reverseString'); + +############################################################################### diff --git a/nginx/t/stream_js_native_module.t b/nginx/t/stream_js_native_module.t new file mode 100644 index 000000000..8d7d727cc --- /dev/null +++ b/nginx/t/stream_js_native_module.t @@ -0,0 +1,229 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for QuickJS native module support in stream. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $cc; +for my $c ('gcc', 'clang') { + if (system("which $c >/dev/null 2>&1") == 0) { + $cc = $c; + last; + } +} + +plan(skip_all => "gcc or clang not found") unless defined $cc; + +my $configure_args = `$Test::Nginx::NGINX -V 2>&1`; +my $m32 = $configure_args =~ /-m32/ ? '-m32' : ''; +my $quickjs_inc = $configure_args =~ /(-I\S*quickjs(?:-ng)?[^\s'"]*)/ + ? $1 : undef; + +plan(skip_all => "QuickJS development files not found") unless $quickjs_inc; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +js_load_stream_native_module %%TESTDIR%%/test.so; +js_load_stream_native_module %%TESTDIR%%/test.so as test; + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_set $reverse test.reverse; + js_set $duplicate test.duplicate; + + js_import test.js; + + server { + listen 127.0.0.1:8081; + return $reverse; + } + + server { + listen 127.0.0.1:8082; + return $duplicate; + } +} + +EOF + +my $d = $t->testdir(); + +$t->write_file('test.js', <write_file('test.c', < +#include + +#define countof(x) (sizeof(x) / sizeof((x)[0])) + +static JSValue +js_reverse_string(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + char *reversed; + size_t i, len; + JSValue result; + const char *str; + + if (argc < 1) { + return JS_ThrowTypeError(ctx, "expected 1 argument"); + } + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) { + return JS_EXCEPTION; + } + + reversed = js_malloc(ctx, len + 1); + if (!reversed) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + for (i = 0; i < len; i++) { + reversed[i] = str[len - 1 - i]; + } + + reversed[len] = 0; + + result = JS_NewString(ctx, reversed); + + js_free(ctx, reversed); + JS_FreeCString(ctx, str); + + return result; +} + + +static JSValue +js_duplicate(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + char *dup; + size_t len; + JSValue result; + const char *str; + + if (argc < 1) { + return JS_ThrowTypeError(ctx, "expected 1 argument"); + } + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) { + return JS_EXCEPTION; + } + + dup = js_malloc(ctx, len * 2 + 1); + if (!dup) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + + memcpy(dup, str, len); + memcpy(dup + len, str, len); + dup[len * 2] = 0; + + result = JS_NewString(ctx, dup); + + js_free(ctx, dup); + JS_FreeCString(ctx, str); + + return result; +} + + +static const JSCFunctionListEntry js_test_native_funcs[] = { + JS_CFUNC_DEF("reverseString", 1, js_reverse_string), + JS_CFUNC_DEF("duplicate", 1, js_duplicate), +}; + + +static int +js_test_native_init(JSContext *ctx, JSModuleDef *m) +{ + return JS_SetModuleExportList(ctx, m, js_test_native_funcs, + countof(js_test_native_funcs)); +} + + +JSModuleDef * +js_init_module(JSContext *ctx, const char *module_name) +{ + int rc; + JSModuleDef *m; + + m = JS_NewCModule(ctx, module_name, js_test_native_init); + if (!m) { + return NULL; + } + + rc = JS_AddModuleExportList(ctx, m, js_test_native_funcs, + countof(js_test_native_funcs)); + if (rc < 0) { + return NULL; + } + + rc = JS_AddModuleExport(ctx, m, "default"); + if (rc < 0) { + return NULL; + } + + return m; +} +EOF + +system("$cc -fPIC $m32 -O $quickjs_inc -shared -o $d/test.so $d/test.c") == 0 + or die "failed to build QuickJS native module: $!\n"; + +$t->try_run('no QuickJS native module support')->plan(2); + +############################################################################### + +like(stream('127.0.0.1:' . port(8081))->read(), qr/1\.0\.0\.721$/, + 'native module reverseString'); +like(stream('127.0.0.1:' . port(8082))->read(), qr/127\.0\.0\.1127\.0\.0\.1$/, + 'native module duplicate'); + +############################################################################### diff --git a/src/njs.h b/src/njs.h index a2781a446..59223aadc 100644 --- a/src/njs.h +++ b/src/njs.h @@ -431,8 +431,10 @@ NJS_EXPORT njs_int_t njs_vm_value_string_create_chb(njs_vm_t *vm, NJS_EXPORT njs_int_t njs_vm_string_compare(njs_vm_t *vm, const njs_value_t *v1, const njs_value_t *v2); -NJS_EXPORT njs_int_t njs_vm_value_array_buffer_set(njs_vm_t *vm, - njs_value_t *value, const u_char *start, uint32_t size); +NJS_EXPORT njs_int_t njs_vm_value_array_buffer_set2(njs_vm_t *vm, + njs_value_t *value, u_char *start, uint32_t size, njs_bool_t shared); +#define njs_vm_value_array_buffer_set(vm, value, start, size) \ + njs_vm_value_array_buffer_set2(vm, value, start, size, 0) NJS_EXPORT njs_int_t njs_value_buffer_get(njs_vm_t *vm, njs_value_t *value, njs_str_t *dst); @@ -515,6 +517,8 @@ NJS_EXPORT njs_int_t njs_value_is_external(const njs_value_t *value, NJS_EXPORT njs_int_t njs_value_is_array(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_function(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_buffer(const njs_value_t *value); +NJS_EXPORT njs_int_t njs_value_is_array_buffer(const njs_value_t *value); +NJS_EXPORT njs_int_t njs_value_is_shared_array_buffer(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_data_view(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_promise(const njs_value_t *value); NJS_EXPORT njs_promise_type_t njs_promise_state(const njs_value_t *value); @@ -559,6 +563,17 @@ NJS_EXPORT njs_int_t njs_vm_promise_create(njs_vm_t *vm, njs_value_t *retval, njs_value_t *callbacks); +typedef struct { + void *(*sab_alloc)(void *opaque, size_t size); + void (*sab_free)(void *opaque, void *ptr); + void (*sab_dup)(void *opaque, void *ptr); + void *sab_opaque; +} njs_sab_functions_t; + +NJS_EXPORT void njs_vm_set_sab_functions(njs_vm_t *vm, + const njs_sab_functions_t *functions); + + njs_inline njs_int_t njs_value_property_val(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, njs_value_t *retval) diff --git a/src/njs_array_buffer.c b/src/njs_array_buffer.c index 8a0703dc5..0fd52b7a0 100644 --- a/src/njs_array_buffer.c +++ b/src/njs_array_buffer.c @@ -7,12 +7,56 @@ #include -njs_array_buffer_t * -njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size, njs_bool_t zeroing) +typedef struct { + njs_vm_t *vm; + void *data; +} njs_sab_cleanup_data_t; + + +njs_int_t +njs_vm_value_array_buffer_set2(njs_vm_t *vm, njs_value_t *value, + u_char *start, uint32_t size, njs_bool_t shared) { - njs_object_t *proto; njs_array_buffer_t *array; + array = njs_array_buffer_alloc(vm, 0, 0, shared); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + if (shared) { + if (vm->sab_funcs.sab_dup != NULL) { + vm->sab_funcs.sab_dup(vm->sab_funcs.sab_opaque, start); + } + + if (njs_shared_array_buffer_destructor_set(vm, start) != NJS_OK) { + return NJS_ERROR; + } + } + + array->u.data = (u_char *) start; + array->size = size; + + if (shared) { + njs_set_shared_array_buffer(value, array); + + } else { + njs_set_array_buffer(value, array); + } + + return NJS_OK; +} + + +njs_array_buffer_t * +njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size, njs_bool_t zeroing, + njs_bool_t shared) +{ + void *data; + njs_object_t *proto; + njs_array_buffer_t *array; + njs_sab_functions_t *sab_funcs; + if (njs_slow_path(size > UINT32_MAX)) { goto overflow; } @@ -22,29 +66,53 @@ njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size, njs_bool_t zeroing) goto memory_error; } - if (zeroing) { - array->u.data = njs_mp_zalloc(vm->mem_pool, size); + if (size != 0 && shared && vm->sab_funcs.sab_alloc != NULL) { + sab_funcs = &vm->sab_funcs; + + data = sab_funcs->sab_alloc(sab_funcs->sab_opaque, size); + if (njs_slow_path(data == NULL)) { + njs_internal_error(vm, "SharedArrayBuffer allocation failed"); + return NULL; + } + + if (njs_shared_array_buffer_destructor_set(vm, data) + != NJS_OK) + { + if (sab_funcs->sab_free != NULL) { + sab_funcs->sab_free(sab_funcs->sab_opaque, data); + } + + goto memory_error; + } } else { - array->u.data = njs_mp_alloc(vm->mem_pool, size); + data = njs_mp_alloc(vm->mem_pool, size); } - if (njs_slow_path(array->u.data == NULL)) { + if (njs_slow_path(data == NULL)) { goto memory_error; } - proto = njs_vm_proto(vm, NJS_OBJ_TYPE_ARRAY_BUFFER); + if (zeroing) { + memset(data, 0, size); + } + + array->u.data = data; + + proto = njs_vm_proto(vm, shared ? NJS_OBJ_TYPE_SHARED_ARRAY_BUFFER + : NJS_OBJ_TYPE_ARRAY_BUFFER); njs_flathsh_init(&array->object.hash); njs_flathsh_init(&array->object.shared_hash); array->object.__proto__ = proto; array->object.slots = NULL; - array->object.type = NJS_ARRAY_BUFFER; + array->object.type = shared ? NJS_SHARED_ARRAY_BUFFER : NJS_ARRAY_BUFFER; array->object.shared = 0; array->object.extensible = 1; array->object.error_data = 0; array->object.fast_array = 0; array->size = size; + array->shared = shared; return array; @@ -64,7 +132,7 @@ njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size, njs_bool_t zeroing) static njs_int_t njs_array_buffer_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, - njs_index_t unused, njs_value_t *retval) + njs_index_t shared, njs_value_t *retval) { uint64_t size; njs_int_t ret; @@ -72,7 +140,8 @@ njs_array_buffer_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_array_buffer_t *array; if (!vm->top_frame->ctor) { - njs_type_error(vm, "Constructor ArrayBuffer requires 'new'"); + njs_type_error(vm, "Constructor %s requires 'new'", + shared ? "SharedArrayBuffer" : "ArrayBuffer"); return NJS_ERROR; } @@ -84,12 +153,18 @@ njs_array_buffer_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - array = njs_array_buffer_alloc(vm, size, 1); + array = njs_array_buffer_alloc(vm, size, 1, shared); + if (njs_slow_path(array == NULL)) { return NJS_ERROR; } - njs_set_array_buffer(retval, array); + if (shared) { + njs_set_shared_array_buffer(retval, array); + + } else { + njs_set_array_buffer(retval, array); + } return NJS_OK; } @@ -164,25 +239,36 @@ const njs_object_init_t njs_array_buffer_constructor_init = { static njs_int_t njs_array_buffer_prototype_byte_length(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) + njs_uint_t nargs, njs_index_t shared, njs_value_t *retval) { njs_value_t *value; njs_array_buffer_t *array; value = njs_argument(args, 0); - if (!njs_is_array_buffer(value)) { - njs_type_error(vm, "Method ArrayBuffer.prototype.byteLength called " - "on incompatible receiver"); - return NJS_ERROR; - } + if (shared) { + if (value->type != NJS_SHARED_ARRAY_BUFFER) { + njs_type_error(vm, + "Method SharedArrayBuffer.prototype.byteLength " + "called on incompatible receiver"); + return NJS_ERROR; + } - array = njs_array_buffer(value); - if (njs_slow_path(njs_is_detached_buffer(array))) { - njs_set_number(retval, 0); - return NJS_OK; + } else { + if (value->type != NJS_ARRAY_BUFFER) { + njs_type_error(vm, "Method ArrayBuffer.prototype.byteLength " + "called on incompatible receiver"); + return NJS_ERROR; + } + + array = njs_array_buffer(value); + if (njs_slow_path(njs_is_detached_buffer(array))) { + njs_set_number(retval, 0); + return NJS_OK; + } } + array = njs_array_buffer(value); njs_set_number(retval, array->size); return NJS_OK; @@ -191,7 +277,7 @@ njs_array_buffer_prototype_byte_length(njs_vm_t *vm, njs_value_t *args, static njs_int_t njs_array_buffer_prototype_slice(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) + njs_uint_t nargs, njs_index_t shared, njs_value_t *retval) { int64_t len, start, end; njs_int_t ret; @@ -200,17 +286,28 @@ njs_array_buffer_prototype_slice(njs_vm_t *vm, njs_value_t *args, value = njs_argument(args, 0); - if (!njs_is_array_buffer(value)) { - njs_type_error(vm, "Method ArrayBuffer.prototype.slice called " - "on incompatible receiver"); - return NJS_ERROR; + if (shared) { + if (value->type != NJS_SHARED_ARRAY_BUFFER) { + njs_type_error(vm, "Method SharedArrayBuffer.prototype.slice " + "called on incompatible receiver"); + return NJS_ERROR; + } + + } else { + if (value->type != NJS_ARRAY_BUFFER) { + njs_type_error(vm, "Method ArrayBuffer.prototype.slice called " + "on incompatible receiver"); + return NJS_ERROR; + } + + this = njs_array_buffer(value); + if (njs_slow_path(njs_is_detached_buffer(this))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } } this = njs_array_buffer(value); - if (njs_slow_path(njs_is_detached_buffer(this))) { - njs_type_error(vm, "detached buffer"); - return NJS_ERROR; - } len = njs_array_buffer_size(this); end = len; @@ -236,7 +333,12 @@ njs_array_buffer_prototype_slice(njs_vm_t *vm, njs_value_t *args, return NJS_ERROR; } - njs_set_array_buffer(retval, buffer); + if (shared) { + njs_set_shared_array_buffer(retval, buffer); + + } else { + njs_set_array_buffer(retval, buffer); + } return NJS_OK; } @@ -250,8 +352,10 @@ njs_array_buffer_detach(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_array_buffer_t *buffer; value = njs_arg(args, nargs, 1); - if (njs_slow_path(!njs_is_array_buffer(value))) { - njs_type_error(vm, "\"this\" is not an ArrayBuffer"); + + if (njs_slow_path(value->type != NJS_ARRAY_BUFFER)) { + njs_type_error(vm, "detaching of %s is not supported", + njs_type_string(value->type)); return NJS_ERROR; } @@ -265,6 +369,171 @@ njs_array_buffer_detach(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } +static njs_int_t +njs_array_buffer_resize(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t shared, njs_value_t *retval) +{ + njs_value_t *value; + + value = njs_argument(args, 0); + + if (shared) { + if (value->type != NJS_SHARED_ARRAY_BUFFER) { + njs_type_error(vm, "Method SharedArrayBuffer.prototype.grow " + "called on incompatible receiver"); + return NJS_ERROR; + } + + njs_type_error(vm, "SharedArrayBuffer is not growable"); + return NJS_ERROR; + + } else { + if (value->type != NJS_ARRAY_BUFFER) { + njs_type_error(vm, "Method ArrayBuffer.prototype.resize " + "called on incompatible receiver"); + return NJS_ERROR; + } + + njs_type_error(vm, "ArrayBuffer is not resizable"); + return NJS_ERROR; + } +} + + +static njs_int_t +njs_array_buffer_get_resizable(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t shared, njs_value_t *retval) +{ + njs_value_t *value; + + value = njs_argument(args, 0); + + if (shared) { + if (value->type != NJS_SHARED_ARRAY_BUFFER) { + njs_type_error(vm, + "Method SharedArrayBuffer.prototype.growable " + "called on incompatible receiver"); + return NJS_ERROR; + } + + } else { + if (value->type != NJS_ARRAY_BUFFER) { + njs_type_error(vm, "Method ArrayBuffer.prototype.resizable " + "called on incompatible receiver"); + return NJS_ERROR; + } + } + + njs_set_boolean(retval, 0); + + return NJS_OK; +} + + +static void +njs_shared_array_buffer_cleanup(void *data) +{ + njs_vm_t *vm; + njs_sab_cleanup_data_t *cleanup_data; + + cleanup_data = data; + vm = cleanup_data->vm; + + if (vm->sab_funcs.sab_free != NULL) { + vm->sab_funcs.sab_free(vm->sab_funcs.sab_opaque, cleanup_data->data); + } +} + + +njs_int_t +njs_shared_array_buffer_destructor_set(njs_vm_t *vm, void *data) +{ + njs_mp_cleanup_t *cleanup; + njs_sab_cleanup_data_t *cleanup_data; + + if (vm->sab_funcs.sab_free == NULL) { + return NJS_OK; + } + + cleanup = njs_mp_cleanup_add(vm->mem_pool, + sizeof(njs_sab_cleanup_data_t)); + if (njs_slow_path(cleanup == NULL)) { + return NJS_ERROR; + } + + cleanup_data = cleanup->data; + cleanup_data->vm = vm; + cleanup_data->data = data; + cleanup->handler = njs_shared_array_buffer_cleanup; + + return NJS_OK; +} + + +static const njs_object_prop_init_t +njs_shared_array_buffer_constructor_properties[] = +{ + NJS_DECLARE_PROP_LENGTH(1), + + NJS_DECLARE_PROP_NAME("SharedArrayBuffer"), + + NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create, + 0, 0), + + NJS_DECLARE_PROP_GETTER(SYMBOL_species, + njs_array_buffer_get_this, + 0), +}; + + +const njs_object_init_t njs_shared_array_buffer_constructor_init = { + njs_shared_array_buffer_constructor_properties, + njs_nitems(njs_shared_array_buffer_constructor_properties), +}; + + +static const njs_object_prop_init_t +njs_shared_array_buffer_prototype_properties[] = +{ + NJS_DECLARE_PROP_HANDLER(STRING_constructor, + njs_object_prototype_create_constructor, 0, + NJS_OBJECT_PROP_VALUE_CW), + + NJS_DECLARE_PROP_GETTER(STRING_byteLength, + njs_array_buffer_prototype_byte_length, 1), + + NJS_DECLARE_PROP_NATIVE(STRING_grow, njs_array_buffer_resize, 1, 1), + + NJS_DECLARE_PROP_GETTER(STRING_growable, + njs_array_buffer_get_resizable, 1), + + NJS_DECLARE_PROP_GETTER(STRING_maxByteLength, + njs_array_buffer_prototype_byte_length, 1), + + NJS_DECLARE_PROP_NATIVE(STRING_slice, + njs_array_buffer_prototype_slice, + 2, 1), + + NJS_DECLARE_PROP_VALUE(SYMBOL_toStringTag, + njs_ascii_strval("SharedArrayBuffer"), + NJS_OBJECT_PROP_VALUE_C), +}; + + +const njs_object_init_t njs_shared_array_buffer_prototype_init = { + njs_shared_array_buffer_prototype_properties, + njs_nitems(njs_shared_array_buffer_prototype_properties), +}; + + +const njs_object_type_init_t njs_shared_array_buffer_type_init = { + .constructor = njs_native_ctor(njs_array_buffer_constructor, 1, 1), + .prototype_props = &njs_shared_array_buffer_prototype_init, + .constructor_props = &njs_shared_array_buffer_constructor_init, + .prototype_value = { .object = { .type = NJS_OBJECT } }, +}; + + static const njs_object_prop_init_t njs_array_buffer_prototype_properties[] = @@ -276,6 +545,14 @@ static const njs_object_prop_init_t njs_array_buffer_prototype_properties[] = NJS_DECLARE_PROP_GETTER(STRING_byteLength, njs_array_buffer_prototype_byte_length, 0), + NJS_DECLARE_PROP_GETTER(STRING_maxByteLength, + njs_array_buffer_prototype_byte_length, 0), + + NJS_DECLARE_PROP_GETTER(STRING_resizable, + njs_array_buffer_get_resizable, 0), + + NJS_DECLARE_PROP_NATIVE(STRING_resize, njs_array_buffer_resize, 1, 0), + NJS_DECLARE_PROP_NATIVE(STRING_slice, njs_array_buffer_prototype_slice, 2, 0), diff --git a/src/njs_array_buffer.h b/src/njs_array_buffer.h index 850db9a57..f9c44a9b2 100644 --- a/src/njs_array_buffer.h +++ b/src/njs_array_buffer.h @@ -13,37 +13,39 @@ njs_array_buffer_t *njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size, - njs_bool_t zeroing); + njs_bool_t zeroing, njs_bool_t shared); +njs_int_t njs_shared_array_buffer_destructor_set(njs_vm_t *vm, void *data); njs_int_t njs_array_buffer_writable(njs_vm_t *vm, njs_array_buffer_t *buffer); NJS_EXPORT njs_int_t njs_array_buffer_detach(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); njs_inline njs_array_buffer_t * -njs_array_buffer_slice(njs_vm_t *vm, njs_array_buffer_t *this, int64_t start, - int64_t end) +njs_array_buffer_slice(njs_vm_t *vm, njs_array_buffer_t *this_value, + int64_t start, int64_t end) { int64_t len, new_len, first, final; njs_array_buffer_t *new_buffer; - len = njs_array_buffer_size(this); + len = njs_array_buffer_size(this_value); first = (start < 0) ? njs_max(len + start, 0) : njs_min(start, len); final = (end < 0) ? njs_max(len + end, 0) : njs_min(end, len); new_len = njs_max(final - first, 0); - new_buffer = njs_array_buffer_alloc(vm, new_len, 1); + new_buffer = njs_array_buffer_alloc(vm, new_len, 1, this_value->shared); if (new_buffer == NULL) { return NULL; } - memcpy(new_buffer->u.u8, &this->u.u8[first], new_len); + memcpy(new_buffer->u.u8, &this_value->u.u8[first], new_len); return new_buffer; } extern const njs_object_type_init_t njs_array_buffer_type_init; +extern const njs_object_type_init_t njs_shared_array_buffer_type_init; #endif /* _NJS_ARRAY_BUFFER_H_INCLUDED_ */ diff --git a/src/njs_atom_defs.h b/src/njs_atom_defs.h index 35dcbe124..546c90d1a 100644 --- a/src/njs_atom_defs.h +++ b/src/njs_atom_defs.h @@ -135,6 +135,7 @@ NJS_DEF_STRING(Promise, "Promise", 0, 0) NJS_DEF_STRING(RangeError, "RangeError", 0, 0) NJS_DEF_STRING(ReferenceError, "ReferenceError", 0, 0) NJS_DEF_STRING(RegExp, "RegExp", 0, 0) +NJS_DEF_STRING(SharedArrayBuffer, "SharedArrayBuffer", 0, 0) NJS_DEF_STRING(SQRT1_2, "SQRT1_2", 0, 0) NJS_DEF_STRING(SQRT2, "SQRT2", 0, 0) NJS_DEF_STRING(String, "String", 0, 0) @@ -283,6 +284,8 @@ NJS_DEF_STRING(getUint16, "getUint16", 0, 0) NJS_DEF_STRING(getUint32, "getUint32", 0, 0) NJS_DEF_STRING(getUint8, "getUint8", 0, 0) NJS_DEF_STRING(getMonth, "getMonth", 0, 0) +NJS_DEF_STRING(grow, "grow", 0, 0) +NJS_DEF_STRING(growable, "growable", 0, 0) NJS_DEF_STRING(global, "global", 0, 0) NJS_DEF_STRING(globalThis, "globalThis", 0, 0) NJS_DEF_STRING(groups, "groups", 0, 0) @@ -328,6 +331,7 @@ NJS_DEF_STRING(map, "map", 0, 0) NJS_DEF_STRING(matchAll, "matchAll", 0, 0) NJS_DEF_STRING(match, "match", 0, 0) NJS_DEF_STRING(max, "max", 0, 0) +NJS_DEF_STRING(maxByteLength, "maxByteLength", 0, 0) NJS_DEF_STRING(min, "min", 0, 0) NJS_DEF_STRING(memoryStats, "memoryStats", 0, 0) NJS_DEF_STRING(message, "message", 0, 0) @@ -386,6 +390,8 @@ NJS_DEF_STRING(replace, "replace", 0, 0) NJS_DEF_STRING(replaceAll, "replaceAll", 0, 0) NJS_DEF_STRING(require, "require", 0, 0) NJS_DEF_STRING(resolve, "resolve", 0, 0) +NJS_DEF_STRING(resizable, "resizable", 0, 0) +NJS_DEF_STRING(resize, "resize", 0, 0) NJS_DEF_STRING(reverse, "reverse", 0, 0) NJS_DEF_STRING(round, "round", 0, 0) NJS_DEF_STRING(seal, "seal", 0, 0) diff --git a/src/njs_buffer.c b/src/njs_buffer.c index bd987c5f2..3c3fbd892 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -167,6 +167,7 @@ njs_buffer_set(njs_vm_t *vm, njs_value_t *value, const u_char *start, buffer->object.fast_array = 0; buffer->u.data = (void *) start; buffer->size = size; + buffer->shared = 0; proto = njs_vm_proto(vm, NJS_OBJ_TYPE_BUFFER); @@ -297,6 +298,7 @@ njs_buffer_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return njs_buffer_from_typed_array(vm, value, retval); case NJS_ARRAY_BUFFER: + case NJS_SHARED_ARRAY_BUFFER: return njs_buffer_from_array_buffer(vm, value, njs_arg(args, nargs, 2), njs_arg(args, nargs, 3), retval); @@ -586,6 +588,7 @@ njs_buffer_byte_length(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_OK; case NJS_ARRAY_BUFFER: + case NJS_SHARED_ARRAY_BUFFER: njs_set_number(retval, njs_array_buffer(value)->size); return NJS_OK; @@ -647,28 +650,44 @@ njs_buffer_slot(njs_vm_t *vm, njs_value_t *value, const char *name) njs_int_t njs_value_buffer_get(njs_vm_t *vm, njs_value_t *value, njs_str_t *dst) { + size_t offset, length; njs_typed_array_t *array; njs_array_buffer_t *array_buffer; if (njs_slow_path(!(njs_is_typed_array(value) + || njs_is_array_buffer(value) || njs_is_data_view(value)))) { njs_type_error(vm, "first argument must be a Buffer or DataView"); return NJS_ERROR; } - array = njs_typed_array(value); - if (njs_slow_path(array == NULL)) { - return NJS_ERROR; - } + if (njs_is_typed_array(value) || njs_is_data_view(value)) { + array = njs_typed_array(value); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } - array_buffer = njs_typed_array_writable(vm, array); - if (njs_slow_path(array_buffer == NULL)) { - return NJS_ERROR; + array_buffer = njs_typed_array_writable(vm, array); + if (njs_slow_path(array_buffer == NULL)) { + return NJS_ERROR; + } + + offset = array->offset; + length = array->byte_length; + + } else { + array_buffer = njs_array_buffer(value); + if (njs_array_buffer_writable(vm, array_buffer) != NJS_OK) { + return NJS_ERROR; + } + + offset = 0; + length = array_buffer->size; } - dst->length = array->byte_length; - dst->start = &array_buffer->u.u8[array->offset]; + dst->length = length; + dst->start = &array_buffer->u.u8[offset]; return NJS_OK; } diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 1b486ba90..51af26606 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -69,6 +69,7 @@ static const njs_object_type_init_t *const &njs_date_type_init, &njs_promise_type_init, &njs_array_buffer_type_init, + &njs_shared_array_buffer_type_init, &njs_data_view_type_init, &njs_text_decoder_type_init, &njs_text_encoder_type_init, @@ -1028,6 +1029,11 @@ static const njs_object_prop_init_t njs_global_this_object_properties[] = NJS_OBJ_TYPE_ARRAY_BUFFER, NJS_OBJECT_PROP_VALUE_CW), + NJS_DECLARE_PROP_HANDLER(STRING_SharedArrayBuffer, + njs_top_level_constructor, + NJS_OBJ_TYPE_SHARED_ARRAY_BUFFER, + NJS_OBJECT_PROP_VALUE_CW), + NJS_DECLARE_PROP_HANDLER(STRING_DataView, njs_top_level_constructor, NJS_OBJ_TYPE_DATA_VIEW, NJS_OBJECT_PROP_VALUE_CW), diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c index f74f175fa..e797eb8b8 100644 --- a/src/njs_typed_array.c +++ b/src/njs_typed_array.c @@ -130,7 +130,7 @@ njs_typed_array_alloc(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } if (buffer == NULL) { - buffer = njs_array_buffer_alloc(vm, size, zeroing); + buffer = njs_array_buffer_alloc(vm, size, zeroing, 0); if (njs_slow_path(buffer == NULL)) { return NULL; } diff --git a/src/njs_value.c b/src/njs_value.c index 7d4c0a39d..629130757 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -350,6 +350,9 @@ njs_type_string(njs_value_type_t type) case NJS_ARRAY_BUFFER: return "array buffer"; + case NJS_SHARED_ARRAY_BUFFER: + return "shared array buffer"; + case NJS_TYPED_ARRAY: return "typed array"; @@ -576,6 +579,20 @@ njs_value_is_buffer(const njs_value_t *value) } +njs_int_t +njs_value_is_array_buffer(const njs_value_t *value) +{ + return njs_is_array_buffer(value); +} + + +njs_int_t +njs_value_is_shared_array_buffer(const njs_value_t *value) +{ + return njs_is_shared_array_buffer(value); +} + + njs_int_t njs_value_is_data_view(const njs_value_t *value) { @@ -638,6 +655,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, case NJS_ARRAY: case NJS_FUNCTION: case NJS_ARRAY_BUFFER: + case NJS_SHARED_ARRAY_BUFFER: case NJS_DATA_VIEW: case NJS_TYPED_ARRAY: case NJS_REGEXP: diff --git a/src/njs_value.h b/src/njs_value.h index 1422f02cc..fea00043a 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -59,6 +59,7 @@ typedef enum { NJS_PROMISE, NJS_OBJECT_VALUE, NJS_ARRAY_BUFFER, + NJS_SHARED_ARRAY_BUFFER, NJS_DATA_VIEW, NJS_VALUE_TYPE_MAX } njs_value_type_t; @@ -195,6 +196,7 @@ struct njs_array_s { struct njs_array_buffer_s { njs_object_t object; size_t size; + uint8_t shared; union { uint8_t *u8; uint16_t *u16; @@ -604,7 +606,12 @@ typedef struct { #define njs_is_array_buffer(value) \ - ((value)->type == NJS_ARRAY_BUFFER) + (((value)->type == NJS_ARRAY_BUFFER) \ + || ((value)->type == NJS_SHARED_ARRAY_BUFFER)) + + +#define njs_is_shared_array_buffer(value) \ + ((value)->type == NJS_SHARED_ARRAY_BUFFER) #define njs_is_typed_array(value) \ @@ -875,6 +882,15 @@ njs_set_array_buffer(njs_value_t *value, njs_array_buffer_t *array) } +njs_inline void +njs_set_shared_array_buffer(njs_value_t *value, njs_array_buffer_t *array) +{ + value->data.u.array_buffer = array; + value->type = NJS_SHARED_ARRAY_BUFFER; + value->data.truth = 1; +} + + njs_inline void njs_set_typed_array(njs_value_t *value, njs_typed_array_t *array) { diff --git a/src/njs_vm.c b/src/njs_vm.c index 348316a14..8514155be 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -28,6 +28,13 @@ njs_vm_opt_init(njs_vm_opt_t *options) } +void +njs_vm_set_sab_functions(njs_vm_t *vm, const njs_sab_functions_t *functions) +{ + vm->sab_funcs = *functions; +} + + njs_vm_t * njs_vm_create(njs_vm_opt_t *options) { @@ -986,26 +993,6 @@ njs_value_string_get(njs_vm_t *vm, njs_value_t *value, njs_str_t *dst) } -njs_int_t -njs_vm_value_array_buffer_set(njs_vm_t *vm, njs_value_t *value, - const u_char *start, uint32_t size) -{ - njs_array_buffer_t *array; - - array = njs_array_buffer_alloc(vm, 0, 0); - if (njs_slow_path(array == NULL)) { - return NJS_ERROR; - } - - array->u.data = (u_char *) start; - array->size = size; - - njs_set_array_buffer(value, array); - - return NJS_OK; -} - - njs_int_t njs_vm_value_buffer_set(njs_vm_t *vm, njs_value_t *value, const u_char *start, uint32_t size) @@ -1575,8 +1562,11 @@ njs_vm_value_to_bytes(njs_vm_t *vm, njs_str_t *dst, njs_value_t *src) case NJS_TYPED_ARRAY: case NJS_DATA_VIEW: case NJS_ARRAY_BUFFER: + case NJS_SHARED_ARRAY_BUFFER: - if (value.type != NJS_ARRAY_BUFFER) { + if (value.type != NJS_ARRAY_BUFFER + && value.type != NJS_SHARED_ARRAY_BUFFER) + { array = njs_typed_array(&value); buffer = njs_typed_array_buffer(array); offset = array->offset; diff --git a/src/njs_vm.h b/src/njs_vm.h index 46f566e3c..252e4f793 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -39,6 +39,7 @@ typedef enum { NJS_OBJ_TYPE_DATE, NJS_OBJ_TYPE_PROMISE, NJS_OBJ_TYPE_ARRAY_BUFFER, + NJS_OBJ_TYPE_SHARED_ARRAY_BUFFER, NJS_OBJ_TYPE_DATA_VIEW, NJS_OBJ_TYPE_TEXT_DECODER, NJS_OBJ_TYPE_TEXT_ENCODER, @@ -125,6 +126,8 @@ struct njs_vm_s { njs_external_ptr_t external; + njs_sab_functions_t sab_funcs; + njs_native_frame_t *top_frame; njs_frame_t *active_frame; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 626f85cff..e23eaf508 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -2362,6 +2362,7 @@ njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value, njs_value_t *retval) NJS_ATOM_STRING_object, NJS_ATOM_STRING_object, NJS_ATOM_STRING_object, + NJS_ATOM_STRING_object, }; njs_atom_to_value(vm, retval, types[value->type]); diff --git a/src/test/njs_externals_test.c b/src/test/njs_externals_test.c index d34ec1f20..f064e99a0 100644 --- a/src/test/njs_externals_test.c +++ b/src/test/njs_externals_test.c @@ -553,6 +553,30 @@ njs_unit_test_r_bind(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } +static njs_int_t +njs_unit_test_r_wrap_sab(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + njs_str_t buffer; + njs_value_t *sab; + + sab = njs_arg(args, nargs, 1); + + if (!njs_value_is_shared_array_buffer(sab)) { + njs_vm_type_error(vm, "argument is not a SharedArrayBuffer"); + return NJS_ERROR; + } + + if (njs_value_buffer_get(vm, sab, &buffer) != NJS_OK) { + njs_vm_type_error(vm, "cannot get SharedArrayBuffer data"); + return NJS_ERROR; + } + + return njs_vm_value_array_buffer_set2(vm, retval, buffer.start, + buffer.length, 1); +} + + static njs_int_t njs_unit_test_null_get(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) @@ -783,6 +807,17 @@ static njs_external_t njs_unit_test_r_external[] = { } }, + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("wrapSAB"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_unit_test_r_wrap_sab, + } + }, + { .flags = NJS_EXTERN_OBJECT, .name.string = njs_str("consts"), diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index c39523ac0..12b341b85 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -7,10 +7,12 @@ #include #include #include +#include #include #include #include +#include #ifndef NJS_HAVE_PCRE2 #include @@ -6290,6 +6292,41 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = new ArrayBuffer(10); a.slice(0,-1).byteLength"), njs_str("9") }, + { njs_str("var ab = new ArrayBuffer(16); ab.resizable"), + njs_str("false") }, + + { njs_str("var ab = new ArrayBuffer(16); ab.maxByteLength"), + njs_str("16") }, + + { njs_str("var ab = new ArrayBuffer(16); ab.maxByteLength === ab.byteLength"), + njs_str("true") }, + + { njs_str("var ab = new ArrayBuffer(0); ab.resizable"), + njs_str("false") }, + + { njs_str("var ab = new ArrayBuffer(0); ab.maxByteLength"), + njs_str("0") }, + + { njs_str("var ab = new ArrayBuffer(16); var msg; " + "try { ab.resize(32); } catch(e) { msg = e.message } msg"), + njs_str("ArrayBuffer is not resizable") }, + + { njs_str("var ab = new ArrayBuffer(16); var msg; " + "try { ab.resize(8); } catch(e) { msg = e.message } msg"), + njs_str("ArrayBuffer is not resizable") }, + + { njs_str("var ab = new ArrayBuffer(16); var sab = new SharedArrayBuffer(16); var msg; " + "try { ArrayBuffer.prototype.resize.call(sab, 32); } catch(e) { msg = e.message } msg"), + njs_str("Method ArrayBuffer.prototype.resize called on incompatible receiver") }, + + { njs_str("var get = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'resizable').get; " + "get.call([])"), + njs_str("TypeError: Method ArrayBuffer.prototype.resizable called on incompatible receiver") }, + + { njs_str("var get = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'maxByteLength').get; " + "get.call([])"), + njs_str("TypeError: Method ArrayBuffer.prototype.byteLength called on incompatible receiver") }, + { njs_str(NJS_TYPED_ARRAY_LIST ".map(v=>{var buffer = new ArrayBuffer(8); var view = new v(buffer);" " view[0] = 511; return new Uint8Array(buffer.slice(0,4))})"), @@ -21721,6 +21758,140 @@ static njs_unit_test_t njs_backtraces_test[] = }; +static njs_unit_test_t njs_shared_array_buffer_test[] = +{ + { njs_str("SharedArrayBuffer()"), + njs_str("TypeError: Constructor SharedArrayBuffer requires 'new'") }, + + { njs_str("new SharedArrayBuffer()"), + njs_str("[object SharedArrayBuffer]") }, + + { njs_str("SharedArrayBuffer.prototype.constructor.name === 'SharedArrayBuffer'"), + njs_str("true") }, + + { njs_str("SharedArrayBuffer.name"), + njs_str("SharedArrayBuffer") }, + + { njs_str("SharedArrayBuffer.prototype[Symbol.toStringTag]"), + njs_str("SharedArrayBuffer") }, + + { njs_str("var sab = new SharedArrayBuffer(); sab.byteLength"), + njs_str("0") }, + + { njs_str("var sab = new SharedArrayBuffer(16); sab.byteLength"), + njs_str("16") }, + + { njs_str("var sab = new SharedArrayBuffer(100); sab.byteLength"), + njs_str("100") }, + + { njs_str("var sab = new SharedArrayBuffer(16); " + "var view = new Uint8Array(sab); " + "view[0] = 42; view[0]"), + njs_str("42") }, + + { njs_str("var sab = new SharedArrayBuffer(16); " + "var view = new Uint8Array(sab, 4, 8); " + "view.byteLength"), + njs_str("8") }, + + { njs_str("var sab = new SharedArrayBuffer(100); " + "var slice = sab.slice(10, 20); " + "slice.byteLength"), + njs_str("10") }, + + { njs_str("var sab = new SharedArrayBuffer(100); " + "var slice = sab.slice(10, 20); " + "slice.constructor.name"), + njs_str("SharedArrayBuffer") }, + + { njs_str("var sab = new SharedArrayBuffer(100); " + "var dv = new DataView(sab); " + "var sab2 = sab.slice(0, 16); " + "dv.setUint8(10, 99); " + "var dv2 = new DataView(sab2); " + "dv2.getUint8(10)"), + njs_str("0") }, + + { njs_str("var sab = new SharedArrayBuffer(100); " + "var dv = new DataView(sab); " + "dv.setUint8(10, 99); " + "var sab2 = sab.slice(0, 16); " + "var dv2 = new DataView(sab2); " + "dv2.getUint8(10)"), + njs_str("99") }, + + { njs_str("var sab = new SharedArrayBuffer(16); " + "var dv = new DataView(sab); " + "dv.setUint8(0, 99); " + "dv.getUint8(0)"), + njs_str("99") }, + + { njs_str("var sab = new SharedArrayBuffer(16); " + "var view1 = new Uint8Array(sab); " + "var view2 = new Uint8Array(sab); " + "view1[0] = 123; " + "view2[0]"), + njs_str("123") }, + + { njs_str("var sab = new SharedArrayBuffer(16); sab.growable"), + njs_str("false") }, + + { njs_str("var sab = new SharedArrayBuffer(16); sab.maxByteLength"), + njs_str("16") }, + + { njs_str("var sab = new SharedArrayBuffer(16); sab.maxByteLength === sab.byteLength"), + njs_str("true") }, + + { njs_str("var sab = new SharedArrayBuffer(0); sab.growable"), + njs_str("false") }, + + { njs_str("var sab = new SharedArrayBuffer(0); sab.maxByteLength"), + njs_str("0") }, + + { njs_str("var sab = new SharedArrayBuffer(16); var msg; " + "try { sab.grow(32); } catch(e) { msg = e.message } msg"), + njs_str("SharedArrayBuffer is not growable") }, + + { njs_str("var sab = new SharedArrayBuffer(16); var msg; " + "try { sab.grow(8); } catch(e) { msg = e.message } msg"), + njs_str("SharedArrayBuffer is not growable") }, + + { njs_str("var ab = new ArrayBuffer(16); var sab = new SharedArrayBuffer(16); var msg; " + "try { SharedArrayBuffer.prototype.grow.call(ab, 32); } catch(e) { msg = e.message } msg"), + njs_str("Method SharedArrayBuffer.prototype.grow called on incompatible receiver") }, + + { njs_str("var get = Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, 'growable').get; " + "get.call([])"), + njs_str("TypeError: Method SharedArrayBuffer.prototype.growable called on incompatible receiver") }, + + { njs_str("var get = Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, 'maxByteLength').get; " + "get.call([])"), + njs_str("TypeError: Method SharedArrayBuffer.prototype.byteLength called on incompatible receiver") }, + + { njs_str("typeof $262"), + njs_str("object") }, + + { njs_str("var sab1 = new SharedArrayBuffer(16); " + "var view1 = new Uint8Array(sab1); " + "view1[0] = 42; " + "var r = $r.create('/test'); " + "var sab2 = r.wrapSAB(sab1); " + "var view2 = new Uint8Array(sab2); " + "view2[0]"), + njs_str("42") }, + + { njs_str("var sab1 = new SharedArrayBuffer(16); " + "var view1 = new Uint8Array(sab1); " + "var r = $r.create('/test'); " + "var sab2 = r.wrapSAB(sab1); " + "var view2 = new Uint8Array(sab2); " + "view2[5] = 99; " + "view1[5]"), + njs_str("99") }, + +}; + + typedef struct { njs_bool_t disassemble; njs_str_t filter; @@ -21734,6 +21905,7 @@ typedef struct { njs_bool_t handler; njs_bool_t async; njs_bool_t preload; + njs_bool_t sab_funcs; unsigned seed; } njs_opts_t; @@ -21764,9 +21936,18 @@ typedef struct { njs_external_state_t *states; njs_uint_t size; njs_uint_t current; + njs_queue_t sab_allocs; } njs_runtime_t; +typedef struct { + njs_queue_link_t link; + void *ptr; + size_t size; + uint32_t refcount; +} njs_sab_alloc_entry_t; + + static void njs_unit_test_report(njs_str_t *name, njs_stat_t *prev, njs_stat_t *current) { @@ -21835,16 +22016,12 @@ njs_external_retval(njs_external_state_t *state, njs_int_t ret, njs_str_t *s) static njs_runtime_t * -njs_runtime_init(njs_vm_t *vm, njs_opts_t *opts) +njs_runtime_init(njs_runtime_t *rt, njs_vm_t *vm, njs_opts_t *opts) { - njs_int_t ret; - njs_uint_t i; - njs_runtime_t *rt; + njs_int_t ret; + njs_uint_t i; - rt = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_runtime_t)); - if (rt == NULL) { - return NULL; - } + njs_queue_init(&rt->sab_allocs); rt->size = opts->repeat; rt->states = njs_mp_alloc(njs_vm_memory_pool(vm), @@ -21896,6 +22073,8 @@ njs_runtime_destroy(njs_runtime_t *rt) { njs_uint_t i; + njs_assert(njs_queue_is_empty(&rt->sab_allocs)); + for (i = 0; i < rt->size; i++) { if (rt->states[i].vm != NULL) { njs_vm_destroy(rt->states[i].vm); @@ -22024,6 +22203,98 @@ njs_module_t *njs_unit_test_addon_external_modules[] = { }; +static void * +njs_sab_mmap_alloc(void *opaque, size_t size) +{ + void *ptr; + njs_runtime_t *rt; + njs_sab_alloc_entry_t *entry; + + rt = opaque; + + if (size == 0) { + size = 1; + } + + ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, + -1, 0); + if (ptr == MAP_FAILED) { + return NULL; + } + + entry = malloc(sizeof(njs_sab_alloc_entry_t)); + if (entry == NULL) { + munmap(ptr, size); + return NULL; + } + + entry->ptr = ptr; + entry->size = size; + entry->refcount = 1; + + njs_queue_insert_tail(&rt->sab_allocs, &entry->link); + + return ptr; +} + + +static void +njs_sab_mmap_free(void *opaque, void *ptr) +{ + njs_runtime_t *rt; + njs_queue_link_t *lnk; + njs_sab_alloc_entry_t *entry; + + rt = opaque; + + for (lnk = njs_queue_first(&rt->sab_allocs); + lnk != njs_queue_tail(&rt->sab_allocs); + lnk = njs_queue_next(lnk)) + { + entry = njs_queue_link_data(lnk, njs_sab_alloc_entry_t, link); + + if (entry->ptr == ptr) { + entry->refcount--; + + if (entry->refcount == 0) { + munmap(entry->ptr, entry->size); + njs_queue_remove(&entry->link); + free(entry); + } + + return; + } + } + + njs_assert_msg(0, "SharedArrayBuffer free of unknown pointer"); +} + + +static void +njs_sab_mmap_dup(void *opaque, void *ptr) +{ + njs_runtime_t *rt; + njs_queue_link_t *lnk; + njs_sab_alloc_entry_t *entry; + + rt = opaque; + + for (lnk = njs_queue_first(&rt->sab_allocs); + lnk != njs_queue_tail(&rt->sab_allocs); + lnk = njs_queue_next(lnk)) + { + entry = njs_queue_link_data(lnk, njs_sab_alloc_entry_t, link); + + if (entry->ptr == ptr) { + entry->refcount++; + return; + } + } + + njs_assert_msg(0, "SharedArrayBuffer dup of unknown pointer"); +} + + static njs_int_t njs_unit_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, njs_opts_t *opts, njs_stat_t *stat) @@ -22039,6 +22310,7 @@ njs_unit_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, njs_runtime_t *rt; njs_opaque_value_t retval; njs_external_state_t *state; + njs_sab_functions_t sab_funcs; njs_str_t preload = njs_str( "globalThis.preload = JSON.parse(" @@ -22114,7 +22386,22 @@ njs_unit_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, njs_disassembler(vm); } - rt = njs_runtime_init(vm, opts); + rt = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_runtime_t)); + if (rt == NULL) { + njs_stderror("njs_mp_alloc() failed\n"); + goto done; + } + + if (opts->sab_funcs) { + sab_funcs.sab_alloc = njs_sab_mmap_alloc; + sab_funcs.sab_free = njs_sab_mmap_free; + sab_funcs.sab_dup = njs_sab_mmap_dup; + sab_funcs.sab_opaque = rt; + + njs_vm_set_sab_functions(vm, &sab_funcs); + } + + rt = njs_runtime_init(rt, vm, opts); if (rt == NULL) { njs_stderror("njs_runtime_init() failed\n"); goto done; @@ -23371,6 +23658,18 @@ static njs_test_suite_t njs_suites[] = njs_nitems(njs_backtraces_test), njs_unit_test }, + { njs_str("sab_funcs"), + { .repeat = 1, .unsafe = 1, .externals = 1, .sab_funcs = 1 }, + njs_shared_array_buffer_test, + njs_nitems(njs_shared_array_buffer_test), + njs_unit_test }, + + { njs_str("no sab_funcs"), + { .repeat = 1, .unsafe = 1, .externals = 1 }, + njs_shared_array_buffer_test, + njs_nitems(njs_shared_array_buffer_test), + njs_unit_test }, + { njs_str("timezone"), { .repeat = 1, .unsafe = 1 }, njs_tz_test,