diff options
77 files changed, 3216 insertions, 1267 deletions
@@ -1,3 +1,58 @@ +Changes with njs 0.9.1 10 Jul 2025 + + nginx modules: + + *) Feature: added Fetch API for QuickJS engine. + + *) Feature: added state file for a shared dictionary. + + *) Bugfix: fixed handling of Content-Length header when + a body is provided for Fetch API. + + *) Bugfix: fixed qjs engine after bellard/quickjs@458c34d2. + + *) Bugfix: fixed NULL pointer dereference when processing + If-* headers. + + Core: + + *) Feature: added ECDH support for WebCrypto. + + *) Improvement: reduced memory consumption by the object hash. + The new hash uses 42% less memory per element. + + *) Improvement: reduced memory consumption for concatenation of + numbers and strings. + + *) Improvement: reduced memory consumption of + String.prototype.concat() with scalar values. + + *) Bugfix: fixed segfault in njs_property_query(). + The issue was introduced in b28e50b1 (0.9.0). + + *) Bugfix: fixed Function constructor template injection. + + *) Bugfix: fixed GCC compilation with O3 optimization level. + + *) Bugfix: fixed constant is too large for 'long' warning + on MIPS -mabi=n32. + + *) Bugfix: fixed compilation with GCC 4.1. + + *) Bugfix: fixed %TypedArray%.from() with the buffer is detached + by the mapper. + + *) Bugfix: fixed %TypedArray%.prototype.slice() with overlapping + buffers. + + *) Bugfix: fixed handling of detached buffers for typed arrays. + + *) Bugfix: fixed frame saving for async functions with + closures. + + *) Bugfix: fixed RegExp compilation of patterns with + escaped '[' characters. + Changes with njs 0.9.0 06 May 2025 Core: @@ -17,7 +17,7 @@ NGINX JavaScript, also known as [NJS](https://nginx.org/en/docs/njs/), is a dyna - [Enabling the NGINX JavaScript modules](#enabling-the-nginx-javascipt-modules) - [Basics of writing .js script files](#basics-of-writing-js-script-files) - [Reference of custom objects, methods, and properties](#reference-of-custom-objects-methods-and-properties) - - [Example: Hello World](#hello-world) + - [Example: Hello World](#example-hello-world) - [The NJS command line interface (CLI)](#the-njs-command-line-interface-cli) - [Building from source](#building-from-source) - [Installing dependencies](#installing-dependencies) @@ -310,20 +310,30 @@ https://github.com/nginx/nginx.git ## Building NGINX JavaScript as a module of NGINX To build NGINX JavaScript as a dynamic module, execute the following commands from the NGINX source code repository's root directory: +> [!NOTE] +> Replace `<NJS_SRC_ROOT_DIR>` with the actual path to your NJS source directory. + ```bash auto/configure --add-dynamic-module=<NJS_SRC_ROOT_DIR>/nginx ``` To build with [QuickJS](https://nginx.org/en/docs/njs/engine.html) support, provide include and library path using `--with-cc-opt=` and `--with-ld-opt=` options: + +> [!NOTE] +> Replace `<NJS_SRC_ROOT_DIR>` with the actual path to your NJS source directory and `<QUICKJS_SRC_ROOT_DIR>` with the actual path to your QuickJS source directory. + ```bash auto/configure --add-dynamic-module=<NJS_SRC_ROOT_DIR>/nginx \ - --with-cc-opt="-I<QUICKJS_SRC_ROOT_DIR>" --with-ld-opt="-L<QUICKJS_SRC_ROOT_DIR>" + --with-cc-opt="-I<QUICKJS_SRC_ROOT_DIR>" \ + --with-ld-opt="-L<QUICKJS_SRC_ROOT_DIR>" ``` > [!WARNING] > By default, this method will only build the `ngx_http_js_module` module. To use NJS with the NGINX Stream module, you'll need to enable it during the `configure` step so it builds with the NGINX binary. Doing so will automatically compile the `ngx_stream_js_module` module when NJS is added to the build. One way of accomplishing this is to alter the `configure` step to: +> > ```bash -> auto/configure --with-stream --add-dynamic-module=<NJS_SRC_ROOT_DIR>/nginx +> auto/configure --with-stream \ +> --add-dynamic-module=<NJS_SRC_ROOT_DIR>/nginx > ``` Compile the module diff --git a/external/njs_regex.c b/external/njs_regex.c index a118666b..cd45afc0 100644 --- a/external/njs_regex.c +++ b/external/njs_regex.c @@ -114,6 +114,11 @@ njs_regex_escape(njs_mp_t *mp, njs_str_t *text) for (p = start; p < end; p++) { switch (*p) { + case '\\': + p += 1; + + break; + case '[': if (p + 1 < end && p[1] == ']') { p += 1; @@ -122,6 +127,11 @@ njs_regex_escape(njs_mp_t *mp, njs_str_t *text) } else if (p + 2 < end && p[1] == '^' && p[2] == ']') { p += 2; anychars += 1; + + } else { + while (p < end && *p != ']') { + p += 1; + } } break; @@ -146,6 +156,15 @@ njs_regex_escape(njs_mp_t *mp, njs_str_t *text) for (p = start; p < end; p++) { switch (*p) { + case '\\': + *dst++ = *p; + if (p + 1 < end) { + p += 1; + *dst++ = *p; + } + + continue; + case '[': if (p + 1 < end && p[1] == ']') { p += 1; @@ -156,12 +175,27 @@ njs_regex_escape(njs_mp_t *mp, njs_str_t *text) p += 2; dst = njs_cpymem(dst, "[\\s\\S]", 6); continue; + + } else { + *dst++ = *p++; /* Copy '['. */ + + while (p < end && *p != ']') { + *dst++ = *p++; + } + + if (p < end) { + *dst++ = *p; /* Copy ']'. */ + } + + continue; } } *dst++ = *p; } + njs_assert(dst == text->start + text->length); + return NJS_OK; #else diff --git a/external/njs_webcrypto_module.c b/external/njs_webcrypto_module.c index d9b05d09..b9a74353 100644 --- a/external/njs_webcrypto_module.c +++ b/external/njs_webcrypto_module.c @@ -2593,7 +2593,6 @@ static njs_int_t njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - int nid; unsigned usage; njs_int_t ret; njs_bool_t extractable; @@ -2730,8 +2729,7 @@ njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, case NJS_ALGORITHM_ECDSA: case NJS_ALGORITHM_ECDH: - nid = 0; - ret = njs_algorithm_curve(vm, aobject, &nid); + ret = njs_algorithm_curve(vm, aobject, &key->u.a.curve); if (njs_slow_path(ret == NJS_ERROR)) { goto fail; } @@ -2747,7 +2745,7 @@ njs_ext_generate_key(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, goto fail; } - if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) <= 0) { + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, key->u.a.curve) <= 0) { njs_webcrypto_error(vm, "EVP_PKEY_CTX_set_ec_paramgen_curve_nid() " "failed"); goto fail; @@ -4854,7 +4852,8 @@ njs_algorithm_hash(njs_vm_t *vm, njs_value_t *options, if (njs_value_is_object(options)) { val = njs_vm_object_prop(vm, options, &string_hash, &value); if (val == NULL) { - return NJS_HASH_SHA256; + *hash = NJS_HASH_SHA256; + return NJS_OK; } } else { diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 40bb83a5..45ddf17e 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -433,6 +433,13 @@ static ngx_command_t ngx_http_js_commands[] = { offsetof(ngx_http_js_loc_conf_t, reuse), NULL }, + { ngx_string("js_context_reuse_max_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, reuse_max_size), + NULL }, + { ngx_string("js_import"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE13, ngx_js_import, @@ -2448,6 +2455,8 @@ ngx_http_js_ext_send_header(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } + r->disable_not_modified = 1; + if (ngx_http_send_header(r) == NGX_ERROR) { return NJS_ERROR; } @@ -2731,6 +2740,8 @@ ngx_http_js_ext_return(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, cv.value.data = text.start; cv.value.len = text.length; + r->disable_not_modified = 1; + ctx->status = ngx_http_send_response(r, status, NULL, &cv); if (ctx->status == NGX_ERROR) { @@ -5438,6 +5449,8 @@ ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val, cv.value.data = body.data; cv.value.len = body.len; + r->disable_not_modified = 1; + ctx->status = ngx_http_send_response(r, status, NULL, &cv); if (ctx->status == NGX_ERROR) { @@ -5663,6 +5676,8 @@ ngx_http_qjs_ext_send_header(JSContext *cx, JSValueConst this_val, return JS_ThrowInternalError(cx, "failed to set content type"); } + r->disable_not_modified = 1; + if (ngx_http_send_header(r) == NGX_ERROR) { return JS_ThrowInternalError(cx, "failed to send header"); } @@ -7684,21 +7699,12 @@ ngx_http_js_init(ngx_conf_t *cf) static ngx_int_t -ngx_http_js_init_worker(ngx_cycle_t *cycle) +ngx_http_js_init_worker_periodics(ngx_js_main_conf_t *jmcf) { ngx_uint_t i; ngx_js_periodic_t *periodics; - ngx_js_main_conf_t *jmcf; - - if ((ngx_process != NGX_PROCESS_WORKER) - && ngx_process != NGX_PROCESS_SINGLE) - { - return NGX_OK; - } - - jmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_js_module); - if (jmcf == NULL || jmcf->periodics == NULL) { + if (jmcf->periodics == NULL) { return NGX_OK; } @@ -7726,6 +7732,35 @@ ngx_http_js_init_worker(ngx_cycle_t *cycle) } +static ngx_int_t +ngx_http_js_init_worker(ngx_cycle_t *cycle) +{ + ngx_js_main_conf_t *jmcf; + + if ((ngx_process != NGX_PROCESS_WORKER) + && ngx_process != NGX_PROCESS_SINGLE) + { + return NGX_OK; + } + + jmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_js_module); + + if (jmcf == NULL) { + return NGX_OK; + } + + if (ngx_http_js_init_worker_periodics(jmcf) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_js_dict_init_worker(jmcf) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + static char * ngx_http_js_periodic(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index e4bae32a..01d4bb2a 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -31,12 +31,6 @@ typedef struct { } ngx_js_rejected_promise_t; -#if defined(PATH_MAX) -#define NGX_MAX_PATH PATH_MAX -#else -#define NGX_MAX_PATH 4096 -#endif - typedef struct { int fd; njs_str_t name; @@ -984,6 +978,11 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external) "js load module exception: %V", &exception); goto destroy; } + + if (i != length - 1) { + /* JS_EvalFunction() does JS_FreeValue(cx, rv) for the last rv. */ + JS_FreeValue(cx, rv); + } } if (JS_ResolveModule(cx, rv) < 0) { @@ -1133,6 +1132,7 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, JSRuntime *rt; JSContext *cx; JSClassID class_id; + JSMemoryUsage stats; ngx_qjs_event_t *event; ngx_js_opaque_t *opaque; njs_rbtree_node_t *node; @@ -1198,6 +1198,28 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, cln->data = conf->reuse_queue; } + /* + * After the request object is freed, the runtime's memory usage should + * be low. It can only remain high if the global scope was + * modified. + * + * To prevent unlimited memory consumption growth, check whether memory + * usage exceeds the configured limit. The check is performed rarely to + * avoid performance impact of JS_ComputeMemoryUsage() which is slow. + */ + + if ((ngx_random() & 0xff) == 1) { + JS_ComputeMemoryUsage(JS_GetRuntime(cx), &stats); + + if ((size_t) stats.malloc_size > conf->reuse_max_size) { + ngx_log_error(NGX_LOG_WARN, ctx->log, 0, + "js remaining memory usage of the context " + "exceeds \"js_context_reuse_max_size\" limit: %L" + ", not reusing it", stats.malloc_size); + goto free_ctx; + } + } + if (ngx_js_queue_push(conf->reuse_queue, cx) != NGX_OK) { goto free_ctx; } @@ -3950,6 +3972,7 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size) conf->preload_objects = NGX_CONF_UNSET_PTR; conf->reuse = NGX_CONF_UNSET_SIZE; + conf->reuse_max_size = NGX_CONF_UNSET_SIZE; conf->buffer_size = NGX_CONF_UNSET_SIZE; conf->max_response_body_size = NGX_CONF_UNSET_SIZE; conf->timeout = NGX_CONF_UNSET_MSEC; @@ -4059,6 +4082,8 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); ngx_conf_merge_size_value(conf->reuse, prev->reuse, 128); + ngx_conf_merge_size_value(conf->reuse_max_size, prev->reuse_max_size, + 4 * 1024 * 1024); ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 16384); ngx_conf_merge_size_value(conf->max_response_body_size, prev->max_response_body_size, 1048576); diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index bb7c1d26..99330f88 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -16,8 +16,6 @@ #include <njs.h> #include <njs_rbtree.h> #include <njs_arr.h> -#include "ngx_js_fetch.h" -#include "ngx_js_shared_dict.h" #if (NJS_HAVE_QUICKJS) #include <qjs.h> @@ -124,6 +122,7 @@ typedef struct { ngx_uint_t type; \ ngx_engine_t *engine; \ ngx_uint_t reuse; \ + size_t reuse_max_size; \ ngx_js_queue_t *reuse_queue; \ ngx_str_t cwd; \ ngx_array_t *imports; \ @@ -317,6 +316,9 @@ ngx_int_t ngx_js_exception(njs_vm_t *vm, ngx_str_t *s); ngx_engine_t *ngx_njs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external); +#define NGX_CHB_CTX_INIT(chain, pool) \ + njs_chb_init(chain, pool, (njs_chb_alloc_t) ngx_palloc, NULL) + #if (NJS_HAVE_QUICKJS) typedef struct ngx_qjs_event_s ngx_qjs_event_t; @@ -437,4 +439,7 @@ extern njs_module_t njs_xml_module; extern njs_module_t njs_zlib_module; +#include "ngx_js_fetch.h" +#include "ngx_js_shared_dict.h" + #endif /* _NGX_JS_H_INCLUDED_ */ diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 45f2dc10..faa38aab 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -514,6 +514,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ngx_url_t u; ngx_uint_t i; njs_bool_t has_host; + ngx_str_t method; ngx_pool_t *pool; njs_value_t *init, *value; ngx_js_http_t *http; @@ -674,6 +675,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, continue; } + if (h[i].key.len == 14 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Content-Length", 14) + == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); @@ -693,7 +701,18 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_chb_append(&http->chain, request.body.data, request.body.len); } else { - njs_chb_append_literal(&http->chain, CRLF); + method = request.method; + + if ((method.len == 4 + && (ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0)) + || (method.len == 3 + && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0)) + { + njs_chb_append_literal(&http->chain, "Content-Length: 0" CRLF CRLF); + + } else { + njs_chb_append_literal(&http->chain, CRLF); + } } if (u.addrs == NULL) { diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c index ccca530d..5445b4f2 100644 --- a/nginx/ngx_js_shared_dict.c +++ b/nginx/ngx_js_shared_dict.c @@ -18,17 +18,10 @@ typedef struct { ngx_rbtree_t rbtree_expire; ngx_rbtree_node_t sentinel_expire; -} ngx_js_dict_sh_t; - -typedef struct { - ngx_str_node_t sn; - ngx_rbtree_node_t expire; - union { - ngx_str_t value; - double number; - } u; -} ngx_js_dict_node_t; + unsigned dirty:1; + unsigned writing:1; +} ngx_js_dict_sh_t; struct ngx_js_dict_s { @@ -36,16 +29,47 @@ struct ngx_js_dict_s { ngx_js_dict_sh_t *sh; ngx_slab_pool_t *shpool; + /** + * in order for ngx_js_dict_t to be used as a ngx_event_t data, + * fd is used for event debug and should be at the same position + * as in ngx_connection_t. see ngx_event_ident() for details. + */ + ngx_socket_t fd; + ngx_msec_t timeout; ngx_flag_t evict; #define NGX_JS_DICT_TYPE_STRING 0 #define NGX_JS_DICT_TYPE_NUMBER 1 ngx_uint_t type; + ngx_event_t save_event; + ngx_str_t state_file; + ngx_str_t state_temp_file; + ngx_js_dict_t *next; }; +typedef union { + ngx_str_t str; /* NGX_JS_DICT_TYPE_STRING */ + double number; /* NGX_JS_DICT_TYPE_NUMBER */ +} ngx_js_dict_value_t; + + +typedef struct { + ngx_str_node_t sn; + ngx_rbtree_node_t expire; + ngx_js_dict_value_t value; +} ngx_js_dict_node_t; + + +typedef struct { + ngx_str_t key; + ngx_js_dict_value_t value; + ngx_msec_t expire; +} ngx_js_dict_entry_t; + + static njs_int_t njs_js_ext_shared_dict_capacity(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); @@ -79,25 +103,25 @@ static njs_int_t njs_js_ext_shared_dict_type(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); static ngx_js_dict_node_t *ngx_js_dict_lookup(ngx_js_dict_t *dict, - njs_str_t *key); + ngx_str_t *key); #define NGX_JS_DICT_FLAG_MUST_EXIST 1 #define NGX_JS_DICT_FLAG_MUST_NOT_EXIST 2 static ngx_int_t ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, - njs_str_t *key, njs_value_t *value, ngx_msec_t timeout, unsigned flags); + ngx_str_t *key, njs_value_t *value, ngx_msec_t timeout, unsigned flags); static ngx_int_t ngx_js_dict_add(njs_vm_t *vm, ngx_js_dict_t *dict, - njs_str_t *key, njs_value_t *value, ngx_msec_t timeout, ngx_msec_t now); + ngx_str_t *key, njs_value_t *value, ngx_msec_t timeout, ngx_msec_t now); static ngx_int_t ngx_js_dict_update(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_js_dict_node_t *node, njs_value_t *value, ngx_msec_t timeout, ngx_msec_t now); static ngx_int_t ngx_js_dict_get(njs_vm_t *vm, ngx_js_dict_t *dict, - njs_str_t *key, njs_value_t *retval); + ngx_str_t *key, njs_value_t *retval); static ngx_int_t ngx_js_dict_incr(njs_vm_t *vm, ngx_js_dict_t *dict, - njs_str_t *key, njs_value_t *delta, njs_value_t *init, double *value, + ngx_str_t *key, njs_value_t *delta, njs_value_t *init, double *value, ngx_msec_t timeout); static ngx_int_t ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict, - njs_str_t *key, njs_value_t *retval); + ngx_str_t *key, njs_value_t *retval); static ngx_int_t ngx_js_dict_copy_value_locked(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_js_dict_node_t *node, njs_value_t *retval); @@ -622,8 +646,14 @@ njs_js_ext_shared_dict_clear(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, done: + dict->sh->dirty = 1; + ngx_rwlock_unlock(&dict->sh->rwlock); + if (dict->state_file.data && !dict->save_event.timer_set) { + ngx_add_timer(&dict->save_event, 1000); + } + njs_value_undefined_set(retval); return NJS_OK; @@ -662,7 +692,7 @@ njs_js_ext_shared_dict_delete(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { ngx_int_t rc; - njs_str_t key; + ngx_str_t key; ngx_shm_zone_t *shm_zone; shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, @@ -672,7 +702,7 @@ njs_js_ext_shared_dict_delete(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + if (ngx_js_ngx_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { return NJS_ERROR; } @@ -689,7 +719,7 @@ njs_js_ext_shared_dict_get(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { ngx_int_t rc; - njs_str_t key; + ngx_str_t key; ngx_shm_zone_t *shm_zone; shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, @@ -699,7 +729,7 @@ njs_js_ext_shared_dict_get(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + if (ngx_js_ngx_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { return NJS_ERROR; } @@ -717,7 +747,7 @@ static njs_int_t njs_js_ext_shared_dict_has(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - njs_str_t key; + ngx_str_t key; ngx_msec_t now; ngx_time_t *tp; ngx_js_dict_t *dict; @@ -731,7 +761,7 @@ njs_js_ext_shared_dict_has(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + if (ngx_js_ngx_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { return NJS_ERROR; } @@ -851,7 +881,7 @@ njs_js_ext_shared_dict_incr(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, { double value; ngx_int_t rc; - njs_str_t key; + ngx_str_t key; ngx_msec_t timeout; njs_value_t *delta, *init, *timeo; ngx_js_dict_t *dict; @@ -872,7 +902,7 @@ njs_js_ext_shared_dict_incr(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + if (ngx_js_ngx_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { return NJS_ERROR; } @@ -1058,7 +1088,7 @@ njs_js_ext_shared_dict_pop(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { ngx_int_t rc; - njs_str_t key; + ngx_str_t key; ngx_shm_zone_t *shm_zone; shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, @@ -1068,7 +1098,7 @@ njs_js_ext_shared_dict_pop(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + if (ngx_js_ngx_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { return NJS_ERROR; } @@ -1086,7 +1116,7 @@ static njs_int_t njs_js_ext_shared_dict_set(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t flags, njs_value_t *retval) { - njs_str_t key; + ngx_str_t key; ngx_int_t rc; ngx_msec_t timeout; njs_value_t *value, *timeo; @@ -1100,7 +1130,7 @@ njs_js_ext_shared_dict_set(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + if (ngx_js_ngx_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { return NJS_ERROR; } @@ -1218,7 +1248,7 @@ njs_js_ext_shared_dict_type(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_str_t type; + ngx_str_t type; ngx_js_dict_t *dict; ngx_shm_zone_t *shm_zone; @@ -1229,36 +1259,41 @@ njs_js_ext_shared_dict_type(njs_vm_t *vm, njs_object_prop_t *prop, } dict = shm_zone->data; - switch (dict->type) { case NGX_JS_DICT_TYPE_STRING: - type = njs_str_value("string"); + ngx_str_set(&type, "string"); break; default: - type = njs_str_value("number"); + ngx_str_set(&type, "number"); break; } - return njs_vm_value_string_create(vm, retval, type.start, type.length); + return njs_vm_value_string_create(vm, retval, type.data, type.len); +} + + +static njs_int_t +ngx_js_dict_shared_error_name(njs_vm_t *vm, njs_object_prop_t *prop, + uint32_t unused, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval) +{ + return njs_vm_value_string_create(vm, retval, + (u_char *) "SharedMemoryError", 17); } static ngx_js_dict_node_t * -ngx_js_dict_lookup(ngx_js_dict_t *dict, njs_str_t *key) +ngx_js_dict_lookup(ngx_js_dict_t *dict, ngx_str_t *key) { uint32_t hash; - ngx_str_t k; ngx_rbtree_t *rbtree; rbtree = &dict->sh->rbtree; - hash = ngx_crc32_long(key->start, key->length); - - k.data = key->start; - k.len = key->length; + hash = ngx_crc32_long(key->data, key->len); - return (ngx_js_dict_node_t *) ngx_str_rbtree_lookup(rbtree, &k, hash); + return (ngx_js_dict_node_t *) ngx_str_rbtree_lookup(rbtree, key, hash); } @@ -1286,7 +1321,7 @@ ngx_js_dict_node_free(ngx_js_dict_t *dict, ngx_js_dict_node_t *node) shpool = dict->shpool; if (dict->type == NGX_JS_DICT_TYPE_STRING) { - ngx_slab_free_locked(shpool, node->u.value.data); + ngx_slab_free_locked(shpool, node->value.str.data); } ngx_slab_free_locked(shpool, node); @@ -1294,7 +1329,7 @@ ngx_js_dict_node_free(ngx_js_dict_t *dict, ngx_js_dict_node_t *node) static ngx_int_t -ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, +ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_str_t *key, njs_value_t *value, ngx_msec_t timeout, unsigned flags) { ngx_msec_t now; @@ -1331,8 +1366,14 @@ ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, } } + dict->sh->dirty = 1; + ngx_rwlock_unlock(&dict->sh->rwlock); + if (dict->state_file.data && !dict->save_event.timer_set) { + ngx_add_timer(&dict->save_event, 1000); + } + return NGX_OK; memory_error: @@ -1346,20 +1387,19 @@ memory_error: static ngx_int_t -ngx_js_dict_add(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, - njs_value_t *value, ngx_msec_t timeout, ngx_msec_t now) +ngx_js_dict_add_value(ngx_js_dict_t *dict, ngx_str_t *key, + ngx_js_dict_value_t *value, ngx_msec_t timeout, ngx_msec_t now) { size_t n; uint32_t hash; - njs_str_t string; ngx_js_dict_node_t *node; if (dict->timeout) { ngx_js_dict_expire(dict, now); } - n = sizeof(ngx_js_dict_node_t) + key->length; - hash = ngx_crc32_long(key->start, key->length); + n = sizeof(ngx_js_dict_node_t) + key->len; + hash = ngx_crc32_long(key->data, key->len); node = ngx_js_dict_alloc(dict, n); if (node == NULL) { @@ -1369,24 +1409,23 @@ ngx_js_dict_add(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, node->sn.str.data = (u_char *) node + sizeof(ngx_js_dict_node_t); if (dict->type == NGX_JS_DICT_TYPE_STRING) { - njs_value_string_get(vm, value, &string); - node->u.value.data = ngx_js_dict_alloc(dict, string.length); - if (node->u.value.data == NULL) { + node->value.str.data = ngx_js_dict_alloc(dict, value->str.len); + if (node->value.str.data == NULL) { ngx_slab_free_locked(dict->shpool, node); return NGX_ERROR; } - ngx_memcpy(node->u.value.data, string.start, string.length); - node->u.value.len = string.length; + ngx_memcpy(node->value.str.data, value->str.data, value->str.len); + node->value.str.len = value->str.len; } else { - node->u.number = njs_value_number(value); + node->value.number = value->number; } node->sn.node.key = hash; - ngx_memcpy(node->sn.str.data, key->start, key->length); - node->sn.str.len = key->length; + ngx_memcpy(node->sn.str.data, key->data, key->len); + node->sn.str.len = key->len; ngx_rbtree_insert(&dict->sh->rbtree, &node->sn.node); @@ -1400,6 +1439,29 @@ ngx_js_dict_add(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, static ngx_int_t +ngx_js_dict_add(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_str_t *key, + njs_value_t *value, ngx_msec_t timeout, ngx_msec_t now) +{ + njs_str_t string; + ngx_js_dict_value_t entry; + + if (dict->type == NGX_JS_DICT_TYPE_STRING) { + njs_value_string_get(vm, value, &string); + + entry.str.data = string.start; + entry.str.len = string.length; + + } else { + /* GCC complains about uninitialized entry.str.data. */ + entry.str.data = NULL; + entry.number = njs_value_number(value); + } + + return ngx_js_dict_add_value(dict, key, &entry, timeout, now); +} + + +static ngx_int_t ngx_js_dict_update(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_js_dict_node_t *node, njs_value_t *value, ngx_msec_t timeout, ngx_msec_t now) { @@ -1414,14 +1476,14 @@ ngx_js_dict_update(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_js_dict_node_t *node, return NGX_ERROR; } - ngx_slab_free_locked(dict->shpool, node->u.value.data); + ngx_slab_free_locked(dict->shpool, node->value.str.data); ngx_memcpy(p, string.start, string.length); - node->u.value.data = p; - node->u.value.len = string.length; + node->value.str.data = p; + node->value.str.len = string.length; } else { - node->u.number = njs_value_number(value); + node->value.number = njs_value_number(value); } if (dict->timeout) { @@ -1435,7 +1497,7 @@ ngx_js_dict_update(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_js_dict_node_t *node, static ngx_int_t -ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, +ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_str_t *key, njs_value_t *retval) { ngx_int_t rc; @@ -1475,14 +1537,20 @@ ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, ngx_js_dict_node_free(dict, node); + dict->sh->dirty = 1; + ngx_rwlock_unlock(&dict->sh->rwlock); + if (dict->state_file.data && !dict->save_event.timer_set) { + ngx_add_timer(&dict->save_event, 1000); + } + return rc; } static ngx_int_t -ngx_js_dict_incr(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, +ngx_js_dict_incr(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_str_t *key, njs_value_t *delta, njs_value_t *init, double *value, ngx_msec_t timeout) { ngx_msec_t now; @@ -1507,8 +1575,8 @@ ngx_js_dict_incr(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, *value = njs_value_number(init); } else { - node->u.number += njs_value_number(delta); - *value = node->u.number; + node->value.number += njs_value_number(delta); + *value = node->value.number; if (dict->timeout) { ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire); @@ -1517,14 +1585,20 @@ ngx_js_dict_incr(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, } } + dict->sh->dirty = 1; + ngx_rwlock_unlock(&dict->sh->rwlock); + if (dict->state_file.data && !dict->save_event.timer_set) { + ngx_add_timer(&dict->save_event, 1000); + } + return NGX_OK; } static ngx_int_t -ngx_js_dict_get(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, +ngx_js_dict_get(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_str_t *key, njs_value_t *retval) { ngx_int_t rc; @@ -1573,14 +1647,14 @@ ngx_js_dict_copy_value_locked(njs_vm_t *vm, ngx_js_dict_t *dict, type = dict->type; if (type == NGX_JS_DICT_TYPE_STRING) { - ret = njs_vm_value_string_create(vm, retval, node->u.value.data, - node->u.value.len); + ret = njs_vm_value_string_create(vm, retval, node->value.str.data, + node->value.str.len); if (ret != NJS_OK) { return NGX_ERROR; } } else { - njs_value_number_set(retval, node->u.number); + njs_value_number_set(retval, node->value.number); } return NGX_OK; @@ -1657,13 +1731,1013 @@ ngx_js_dict_evict(ngx_js_dict_t *dict, ngx_int_t count) } -static njs_int_t -ngx_js_dict_shared_error_name(njs_vm_t *vm, njs_object_prop_t *prop, - uint32_t unused, njs_value_t *value, njs_value_t *setval, - njs_value_t *retval) +static ngx_int_t +ngx_js_render_string(njs_chb_t *chain, ngx_str_t *str) { - return njs_vm_value_string_create(vm, retval, - (u_char *) "SharedMemoryError", 17); + size_t size; + u_char c, *dst, *dst_end; + const u_char *p, *end; + + static char hex2char[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + p = str->data; + end = p + str->len; + size = str->len + 2; + + dst = njs_chb_reserve(chain, size); + if (dst == NULL) { + return NGX_ERROR; + } + + dst_end = dst + size; + + *dst++ = '\"'; + njs_chb_written(chain, 1); + + while (p < end) { + if (dst_end <= dst + sizeof("\\uXXXX")) { + size = ngx_max(end - p + 1, 6); + dst = njs_chb_reserve(chain, size); + if (dst == NULL) { + return NGX_ERROR; + } + + dst_end = dst + size; + } + + if (*p < ' ' || *p == '\\' || *p == '\"') { + c = (u_char) *p++; + *dst++ = '\\'; + njs_chb_written(chain, 2); + + switch (c) { + case '\\': + *dst++ = '\\'; + break; + case '"': + *dst++ = '\"'; + break; + case '\r': + *dst++ = 'r'; + break; + case '\n': + *dst++ = 'n'; + break; + case '\t': + *dst++ = 't'; + break; + case '\b': + *dst++ = 'b'; + break; + case '\f': + *dst++ = 'f'; + break; + default: + *dst++ = 'u'; + *dst++ = '0'; + *dst++ = '0'; + *dst++ = hex2char[(c & 0xf0) >> 4]; + *dst++ = hex2char[c & 0x0f]; + njs_chb_written(chain, 4); + } + + continue; + } + + dst = njs_utf8_copy(dst, &p, end); + + njs_chb_written(chain, dst - chain->last->pos); + } + + njs_chb_append_literal(chain, "\""); + + return NGX_OK; +} + + +static ngx_int_t +ngx_js_dict_render_json(ngx_js_dict_t *dict, njs_chb_t *chain) +{ + u_char *p, *dst; + size_t len; + ngx_msec_t now; + ngx_time_t *tp; + ngx_rbtree_t *rbtree; + ngx_rbtree_node_t *rn, *next; + ngx_js_dict_node_t *node; + + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + rbtree = &dict->sh->rbtree; + + njs_chb_append_literal(chain,"{"); + + if (rbtree->root == rbtree->sentinel) { + njs_chb_append_literal(chain, "}"); + return NGX_OK; + } + + for (rn = ngx_rbtree_min(rbtree->root, rbtree->sentinel); + rn != NULL; + rn = next) + { + node = (ngx_js_dict_node_t *) rn; + + next = ngx_rbtree_next(rbtree, rn); + + if (dict->timeout && now >= node->expire.key) { + continue; + } + + if (ngx_js_render_string(chain, &node->sn.str) != NGX_OK) { + return NGX_ERROR; + } + + njs_chb_append_literal(chain,":{"); + + if (dict->type == NGX_JS_DICT_TYPE_STRING) { + njs_chb_append_literal(chain,"\"value\":"); + + if (ngx_js_render_string(chain, &node->value.str) != NGX_OK) { + return NGX_ERROR; + } + + } else { + len = sizeof("\"value\":.") + 18 + 6; + dst = njs_chb_reserve(chain, len); + if (dst == NULL) { + return NGX_ERROR; + } + + p = njs_sprintf(dst, dst + len, "\"value\":%.6f", + node->value.number); + njs_chb_written(chain, p - dst); + } + + if (dict->timeout) { + len = sizeof(",\"expire\":1000000000"); + dst = njs_chb_reserve(chain, len); + if (dst == NULL) { + return NGX_ERROR; + } + + p = njs_sprintf(dst, dst + len, ",\"expire\":%ui", + node->expire.key); + njs_chb_written(chain, p - dst); + } + + njs_chb_append_literal(chain, "}"); + + if (next != NULL) { + njs_chb_append_literal(chain, ","); + } + } + + njs_chb_append_literal(chain, "}"); + + return NGX_OK; +} + + +static u_char * +ngx_js_skip_space(u_char *start, u_char *end) +{ + u_char *p; + + for (p = start; p != end; p++) { + + switch (*p) { + case ' ': + case '\t': + case '\r': + case '\n': + continue; + } + + break; + } + + return p; +} + + +static uint32_t +ngx_js_unicode(const u_char *p) +{ + u_char c; + uint32_t utf; + njs_uint_t i; + + utf = 0; + + for (i = 0; i < 4; i++) { + utf <<= 4; + c = p[i] | 0x20; + c -= '0'; + if (c > 9) { + c += '0' - 'a' + 10; + } + + utf |= c; + } + + return utf; +} + + +static u_char * +ngx_js_dict_parse_string(ngx_pool_t *pool, u_char *p, u_char *end, + ngx_str_t *str, const char **err, u_char **at) +{ + u_char ch, *s, *dst, *start, *last; + size_t size, surplus; + uint32_t utf, utf_low; + + enum { + sw_usual = 0, + sw_escape, + sw_encoded1, + sw_encoded2, + sw_encoded3, + sw_encoded4, + } state; + + if (*p != '"') { + *err = "unexpected character, expected '\"'"; + goto error; + } + + start = p + 1; + + dst = NULL; + state = 0; + surplus = 0; + + for (p = start; p < end; p++) { + ch = *p; + + switch (state) { + + case sw_usual: + + if (ch == '"') { + break; + } + + if (ch == '\\') { + state = sw_escape; + continue; + } + + if (ch >= ' ') { + continue; + } + + *err = "Invalid source char"; + goto error; + + case sw_escape: + + switch (ch) { + case '"': + case '\\': + case '/': + case 'n': + case 'r': + case 't': + case 'b': + case 'f': + surplus++; + state = sw_usual; + continue; + + case 'u': + /* + * Basic unicode 6 bytes "\uXXXX" in JSON + * and up to 3 bytes in UTF-8. + * + * Surrogate pair: 12 bytes "\uXXXX\uXXXX" in JSON + * and 3 or 4 bytes in UTF-8. + */ + surplus += 3; + state = sw_encoded1; + continue; + } + + *err = "Invalid escape char"; + goto error; + + case sw_encoded1: + case sw_encoded2: + case sw_encoded3: + case sw_encoded4: + + if ((ch >= '0' && ch <= '9') + || (ch >= 'A' && ch <= 'F') + || (ch >= 'a' && ch <= 'f')) + { + state = (state == sw_encoded4) ? sw_usual : state + 1; + continue; + } + + *err = "Invalid Unicode escape sequence"; + goto error; + } + + break; + } + + if (p == end) { + *err = "unexpected end of input"; + goto error; + } + + /* Points to the ending quote mark. */ + last = p; + + size = last - start - surplus; + + if (surplus != 0) { + p = start; + + dst = ngx_palloc(pool, size); + if (dst == NULL) { + *err = "out of memory"; + goto error; + } + + s = dst; + + do { + ch = *p++; + + if (ch != '\\') { + *s++ = ch; + continue; + } + + ch = *p++; + + switch (ch) { + case '"': + case '\\': + case '/': + *s++ = ch; + continue; + + case 'n': + *s++ = '\n'; + continue; + + case 'r': + *s++ = '\r'; + continue; + + case 't': + *s++ = '\t'; + continue; + + case 'b': + *s++ = '\b'; + continue; + + case 'f': + *s++ = '\f'; + continue; + } + + /* "\uXXXX": Unicode escape sequence. */ + + utf = ngx_js_unicode(p); + p += 4; + + if (njs_surrogate_any(utf)) { + + if (utf > 0xdbff || p[0] != '\\' || p[1] != 'u') { + s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT); + continue; + } + + p += 2; + + utf_low = ngx_js_unicode(p); + p += 4; + + if (njs_fast_path(njs_surrogate_trailing(utf_low))) { + utf = njs_surrogate_pair(utf, utf_low); + + } else if (njs_surrogate_leading(utf_low)) { + utf = NJS_UNICODE_REPLACEMENT; + s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT); + + } else { + utf = utf_low; + s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT); + } + } + + s = njs_utf8_encode(s, utf); + + } while (p != last); + + size = s - dst; + start = dst; + } + + str->data = start; + str->len = size; + + return p + 1; + +error: + + *at = p; + + return NULL; +} + + +static u_char * +ngx_js_dict_parse_entry(ngx_js_dict_t *dict, ngx_pool_t *pool, + ngx_js_dict_entry_t *entry, u_char *buf, u_char *end, const char **err, + u_char **at) +{ + int see_value; + u_char *p, *pp; + double number; + ngx_str_t key, str; + + p = buf; + + if (*p++ != '{') { + *err = "unexpected character, expected '{'"; + goto error; + } + + see_value = 0; + + while (1) { + p = ngx_js_skip_space(p, end); + if (p == end) { + *err = "unexpected end of json"; + goto error; + } + + if (*p == '}') { + break; + } + + p = ngx_js_dict_parse_string(pool, p, end, &key, err, at); + if (p == NULL) { + return NULL; + } + + p = ngx_js_skip_space(p, end); + if (p == end) { + *err = "unexpected end of json"; + goto error; + } + + if (*p++ != ':') { + *err = "unexpected character, expected ':'"; + goto error; + } + + p = ngx_js_skip_space(p, end); + if (p == end) { + *err = "unexpected end of json"; + goto error; + } + + if (*p == '\"') { + p = ngx_js_dict_parse_string(pool, p, end, &str, err, at); + if (p == NULL) { + return NULL; + } + + if (key.len == 5 && ngx_strncmp(key.data, "value", 5) == 0) { + if (dict->type != NGX_JS_DICT_TYPE_STRING) { + *err = "expected string value"; + goto error; + } + + entry->value.str = str; + see_value = 1; + } + + } else { + pp = p; + number = strtod((char *) p, (char **) &p); + if (pp == p) { + *err = "invalid number value"; + goto error; + } + + if (key.len == 5 && ngx_strncmp(key.data, "value", 5) == 0) { + if (dict->type == NGX_JS_DICT_TYPE_STRING) { + *err = "expected number value"; + goto error; + } + + entry->value.number = number; + see_value = 1; + + } else if (key.len == 6 + && ngx_strncmp(key.data, "expire", 6) == 0) + { + entry->expire = number; + } + } + + p = ngx_js_skip_space(p, end); + if (p == end) { + *err = "unexpected end of json"; + goto error; + } + + if (*p == ',') { + p++; + } + } + + if (!see_value) { + *err = "missing value"; + goto error; + } + + return p + 1; + +error: + + *at = p; + + return NULL; +} + + +static ngx_int_t +ngx_js_dict_parse_state(ngx_js_dict_t *dict, ngx_pool_t *pool, + ngx_array_t *entries, u_char *buf, u_char *end) +{ + u_char *p, *at; + const char *err; + ngx_js_dict_entry_t *e; + + /* GCC complains about uninitialized err, at. */ + + err = ""; + at = NULL; + + p = ngx_js_skip_space(buf, end); + if (p == end) { + err = "empty json"; + goto error; + } + + if (*p++ != '{') { + err = "json must start with '{'"; + goto error; + } + + while (1) { + p = ngx_js_skip_space(p, end); + if (p == end) { + err = "unexpected end of json"; + goto error; + } + + if (*p == '}') { + p++; + break; + } + + e = ngx_array_push(entries); + if (e == NULL) { + return NGX_ERROR; + } + + p = ngx_js_dict_parse_string(pool, p, end, &e->key, &err, &at); + if (p == NULL) { + p = at; + goto error; + } + + p = ngx_js_skip_space(p, end); + if (p == end) { + err = "unexpected end of json"; + goto error; + } + + if (*p++ != ':') { + err = "unexpected character, expected ':'"; + goto error; + } + + p = ngx_js_skip_space(p, end); + if (p == end) { + err = "unexpected end of json"; + goto error; + } + + p = ngx_js_dict_parse_entry(dict, pool, e, p, end, &err, &at); + if (p == NULL) { + p = at; + goto error; + } + + p = ngx_js_skip_space(p, end); + if (p == end) { + err = "unexpected end of json"; + goto error; + } + + if (*p == ',') { + p++; + } + } + + p = ngx_js_skip_space(p, end); + + if (p != end) { + err = "unexpected character, expected end of json"; + goto error; + } + + return NGX_OK; + +error: + + ngx_log_error(NGX_LOG_EMERG, dict->shm_zone->shm.log, 0, + "invalid format while loading js_shared_dict_zone \"%V\"" + " from state file \"%s\": %s at offset %z", + &dict->shm_zone->shm.name, dict->state_file.data, err, + p - buf); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_js_dict_save(ngx_js_dict_t *dict) +{ + + u_char *name; + ngx_int_t rc; + ngx_log_t *log; + njs_chb_t chain; + ngx_file_t file; + ngx_pool_t *pool; + ngx_chain_t *out, *cl, **ll; + njs_chb_node_t *node; + ngx_ext_rename_file_t ext; + + log = dict->shm_zone->shm.log; + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, log); + if (pool == NULL) { + return NGX_ERROR; + } + + ngx_rwlock_wlock(&dict->sh->rwlock); + + if (!dict->sh->dirty) { + ngx_rwlock_unlock(&dict->sh->rwlock); + ngx_destroy_pool(pool); + return NGX_OK; + } + + if (dict->sh->writing) { + ngx_rwlock_unlock(&dict->sh->rwlock); + ngx_destroy_pool(pool); + return NGX_AGAIN; + } + + ngx_rwlock_downgrade(&dict->sh->rwlock); + + NGX_CHB_CTX_INIT(&chain, pool); + + rc = ngx_js_dict_render_json(dict, &chain); + + if (rc != NGX_OK) { + ngx_rwlock_unlock(&dict->sh->rwlock); + ngx_destroy_pool(pool); + return rc; + } + + dict->sh->writing = 1; + dict->sh->dirty = 0; + + ngx_rwlock_unlock(&dict->sh->rwlock); + + name = dict->state_temp_file.data; + + out = NULL; + ll = &out; + + for (node = chain.nodes; node != NULL; node = node->next) { + cl = ngx_alloc_chain_link(pool); + if (cl == NULL) { + goto error; + } + + cl->buf = ngx_calloc_buf(pool); + if (cl->buf == NULL) { + goto error; + } + + cl->buf->pos = node->start; + cl->buf->last = node->pos; + cl->buf->memory = 1; + cl->buf->last_buf = (node->next == NULL) ? 1 : 0; + + *ll = cl; + ll = &cl->next; + } + + *ll = NULL; + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = dict->state_temp_file; + file.log = log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (file.fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_open_file_n " \"%s\" failed", name); + goto error; + } + + rc = ngx_write_chain_to_file(&file, out, 0, pool); + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_write_fd_n " \"%s\" failed", file.name.data); + goto error; + } + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_close_file_n " \"%s\" failed", file.name.data); + } + + file.fd = NGX_INVALID_FILE; + + ext.access = 0; + ext.time = -1; + ext.create_path = 0; + ext.delete_file = 0; + ext.log = log; + + if (ngx_ext_rename_file(&dict->state_temp_file, &dict->state_file, &ext) + != NGX_OK) + { + goto error; + } + + /* no lock required */ + dict->sh->writing = 0; + ngx_destroy_pool(pool); + + return NGX_OK; + +error: + + if (file.fd != NGX_INVALID_FILE + && ngx_close_file(file.fd) == NGX_FILE_ERROR) + { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_close_file_n " \"%s\" failed", name); + } + + ngx_destroy_pool(pool); + + /* no lock required */ + dict->sh->writing = 0; + dict->sh->dirty = 1; + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_js_dict_load(ngx_js_dict_t *dict) +{ + off_t size; + u_char *name, *buf; + size_t len; + ssize_t n; + ngx_fd_t fd; + ngx_err_t err; + ngx_int_t rc; + ngx_log_t *log; + ngx_uint_t i; + ngx_msec_t now, expire; + ngx_time_t *tp; + ngx_pool_t *pool; + ngx_array_t data; + ngx_file_info_t fi; + ngx_js_dict_entry_t *entries; + + if (dict->state_file.data == NULL) { + return NGX_OK; + } + + log = dict->shm_zone->shm.log; + + name = dict->state_file.data; + + fd = ngx_open_file(name, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + if (fd == NGX_INVALID_FILE) { + err = ngx_errno; + + if (err == NGX_ENOENT || err == NGX_ENOPATH) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_EMERG, log, err, + ngx_open_file_n " \"%s\" failed", name); + return NGX_ERROR; + } + + if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + ngx_fd_info_n " \"%s\" failed", name); + pool = NULL; + goto failed; + } + + size = ngx_file_size(&fi); + + if (size == 0) { + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + ngx_close_file_n " \"%s\" failed", name); + } + + return NGX_OK; + } + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, log); + if (pool == NULL) { + goto failed; + } + + len = size; + + buf = ngx_pnalloc(pool, len); + if (buf == NULL) { + goto failed; + } + + n = ngx_read_fd(fd, buf, len); + + if (n == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + ngx_read_fd_n " \"%s\" failed", name); + goto failed; + } + + if ((size_t) n != len) { + ngx_log_error(NGX_LOG_EMERG, log, 0, + ngx_read_fd_n " has read only %z of %uz from %s", + n, len, name); + goto failed; + } + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + ngx_close_file_n " \"%s\" failed", name); + fd = NGX_INVALID_FILE; + goto failed; + } + + fd = NGX_INVALID_FILE; + + if (ngx_array_init(&data, pool, 4, sizeof(ngx_js_dict_entry_t)) + != NGX_OK) + { + goto failed; + } + + rc = ngx_js_dict_parse_state(dict, pool, &data, buf, buf + len); + + if (rc != NGX_OK) { + goto failed; + } + + entries = data.elts; + + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + for (i = 0; i < data.nelts; i++) { + + if (dict->timeout) { + expire = entries[i].expire; + + if (expire && now >= expire) { + dict->sh->dirty = 1; + continue; + } + + if (expire == 0) { + /* treat state without expire as new */ + expire = now + dict->timeout; + dict->sh->dirty = 1; + } + + } else { + expire = 0; + } + + if (ngx_js_dict_lookup(dict, &entries[i].key) != NULL) { + goto failed; + } + + if (ngx_js_dict_add_value(dict, &entries[i].key, &entries[i].value, + expire, 1) + != NGX_OK) + { + goto failed; + } + } + + ngx_destroy_pool(pool); + + return NGX_OK; + +failed: + + if (fd != NGX_INVALID_FILE && ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + ngx_close_file_n " \"%s\" failed", name); + } + + if (pool) { + ngx_destroy_pool(pool); + } + + return NGX_ERROR; +} + + +static void +ngx_js_dict_save_handler(ngx_event_t *ev) +{ + ngx_int_t rc; + ngx_js_dict_t *dict; + + dict = ev->data; + + rc = ngx_js_dict_save(dict); + + if (rc == NGX_OK) { + return; + } + + if (rc == NGX_ERROR && (ngx_terminate || ngx_exiting)) { + ngx_log_error(NGX_LOG_ALERT, ev->log, 0, + "failed to save the state of shared dict zone \"%V\"", + &dict->shm_zone->shm.name); + return; + } + + /* NGX_ERROR, NGX_AGAIN */ + + ngx_add_timer(ev, 1000); +} + + +ngx_int_t +ngx_js_dict_init_worker(ngx_js_main_conf_t *jmcf) +{ + ngx_js_dict_t *dict; + + if ((ngx_process != NGX_PROCESS_WORKER || ngx_worker != 0) + && ngx_process != NGX_PROCESS_SINGLE) + { + return NGX_OK; + } + + if (jmcf->dicts == NULL) { + return NGX_OK; + } + + for (dict = jmcf->dicts; dict != NULL; dict = dict->next) { + + if (!dict->sh->dirty || !dict->state_file.data) { + continue; + } + + ngx_add_timer(&dict->save_event, 1000); + } + + return NGX_OK; } @@ -1733,6 +2807,10 @@ ngx_js_dict_init_zone(ngx_shm_zone_t *shm_zone, void *data) ngx_sprintf(dict->shpool->log_ctx, " in js shared zone \"%V\"%Z", &shm_zone->shm.name); + if (ngx_js_dict_load(dict) != NGX_OK) { + return NGX_ERROR; + } + return NGX_OK; } @@ -1745,7 +2823,7 @@ ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, u_char *p; ssize_t size; - ngx_str_t *value, name, s; + ngx_str_t *value, name, file, s; ngx_flag_t evict; ngx_msec_t timeout; ngx_uint_t i, type; @@ -1756,6 +2834,7 @@ ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, evict = 0; timeout = 0; name.len = 0; + ngx_str_null(&file); type = NGX_JS_DICT_TYPE_STRING; value = cf->args->elts; @@ -1807,6 +2886,17 @@ ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, continue; } + if (ngx_strncmp(value[i].data, "state=", 6) == 0) { + file.data = value[i].data + 6; + file.len = value[i].len - 6; + + if (ngx_conf_full_name(cf->cycle, &file, 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + continue; + } + if (ngx_strncmp(value[i].data, "timeout=", 8) == 0) { s.data = value[i].data + 8; @@ -1880,6 +2970,23 @@ ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, dict->timeout = timeout; dict->type = type; + dict->save_event.handler = ngx_js_dict_save_handler; + dict->save_event.data = dict; + dict->save_event.log = &cf->cycle->new_log; + dict->fd = -1; + + if (file.data) { + dict->state_file = file; + + p = ngx_pnalloc(cf->pool, file.len + sizeof(".tmp")); + if (p == NULL) { + return NGX_CONF_ERROR; + } + + dict->state_temp_file.data = p; + dict->state_temp_file.len = ngx_sprintf(p, "%V.tmp%Z", &file) - p - 1; + } + return NGX_CONF_OK; } @@ -2079,8 +3186,14 @@ ngx_qjs_ext_shared_dict_clear(JSContext *cx, JSValueConst this_val, done: + dict->sh->dirty = 1; + ngx_rwlock_unlock(&dict->sh->rwlock); + if (dict->state_file.data && !dict->save_event.timer_set) { + ngx_add_timer(&dict->save_event, 1000); + } + return JS_UNDEFINED; } @@ -2630,13 +3743,13 @@ ngx_qjs_dict_copy_value_locked(JSContext *cx, ngx_js_dict_t *dict, ngx_js_dict_node_t *node) { if (dict->type == NGX_JS_DICT_TYPE_STRING) { - return JS_NewStringLen(cx, (const char *) node->u.value.data, - node->u.value.len); + return JS_NewStringLen(cx, (const char *) node->value.str.data, + node->value.str.len); } /* NGX_JS_DICT_TYPE_NUMBER */ - return JS_NewFloat64(cx, node->u.number); + return JS_NewFloat64(cx, node->value.number); } @@ -2658,64 +3771,31 @@ static ngx_int_t ngx_qjs_dict_add(JSContext *cx, ngx_js_dict_t *dict, ngx_str_t *key, JSValue value, ngx_msec_t timeout, ngx_msec_t now) { - size_t n; - uint32_t hash; - ngx_str_t string; - ngx_js_dict_node_t *node; - - if (dict->timeout) { - ngx_js_dict_expire(dict, now); - } - - n = sizeof(ngx_js_dict_node_t) + key->len; - hash = ngx_crc32_long(key->data, key->len); - - node = ngx_js_dict_alloc(dict, n); - if (node == NULL) { - return NGX_ERROR; - } - - node->sn.str.data = (u_char *) node + sizeof(ngx_js_dict_node_t); + ngx_int_t rc; + ngx_js_dict_value_t entry; if (dict->type == NGX_JS_DICT_TYPE_STRING) { - string.data = (u_char *) JS_ToCStringLen(cx, &string.len, value); - if (string.data == NULL) { - ngx_slab_free_locked(dict->shpool, node); + entry.str.data = (u_char *) JS_ToCStringLen(cx, &entry.str.len, value); + if (entry.str.data == NULL) { return NGX_ERROR; } - node->u.value.data = ngx_js_dict_alloc(dict, string.len); - if (node->u.value.data == NULL) { - ngx_slab_free_locked(dict->shpool, node); - JS_FreeCString(cx, (char *) string.data); - return NGX_ERROR; - } - - ngx_memcpy(node->u.value.data, string.data, string.len); - node->u.value.len = string.len; - - JS_FreeCString(cx, (char *) string.data); - } else { - if (JS_ToFloat64(cx, &node->u.number, value) < 0) { - ngx_slab_free_locked(dict->shpool, node); + /* GCC complains about uninitialized entry.str.data. */ + entry.str.data = NULL; + + if (JS_ToFloat64(cx, &entry.number, value) < 0) { return NGX_ERROR; } } - node->sn.node.key = hash; - - ngx_memcpy(node->sn.str.data, key->data, key->len); - node->sn.str.len = key->len; + rc = ngx_js_dict_add_value(dict, key, &entry, timeout, now); - ngx_rbtree_insert(&dict->sh->rbtree, &node->sn.node); - - if (dict->timeout) { - node->expire.key = now + timeout; - ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire); + if (dict->type == NGX_JS_DICT_TYPE_STRING) { + JS_FreeCString(cx, (char *) entry.str.data); } - return NGX_OK; + return rc; } @@ -2760,8 +3840,14 @@ ngx_qjs_dict_delete(JSContext *cx, ngx_js_dict_t *dict, ngx_str_t *key, ngx_js_dict_node_free(dict, node); + dict->sh->dirty = 1; + ngx_rwlock_unlock(&dict->sh->rwlock); + if (dict->state_file.data && !dict->save_event.timer_set) { + ngx_add_timer(&dict->save_event, 1000); + } + return ret; } @@ -2829,8 +3915,8 @@ ngx_qjs_dict_incr(JSContext *cx, ngx_js_dict_t *dict, ngx_str_t *key, } } else { - node->u.number += delta; - value = JS_NewFloat64(cx, node->u.number); + node->value.number += delta; + value = JS_NewFloat64(cx, node->value.number); if (dict->timeout) { ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire); @@ -2839,8 +3925,14 @@ ngx_qjs_dict_incr(JSContext *cx, ngx_js_dict_t *dict, ngx_str_t *key, } } + dict->sh->dirty = 1; + ngx_rwlock_unlock(&dict->sh->rwlock); + if (dict->state_file.data && !dict->save_event.timer_set) { + ngx_add_timer(&dict->save_event, 1000); + } + return value; } @@ -2870,24 +3962,30 @@ ngx_qjs_dict_set(JSContext *cx, ngx_js_dict_t *dict, ngx_str_t *key, goto memory_error; } - ngx_rwlock_unlock(&dict->sh->rwlock); + } else { - return JS_TRUE; - } + if (flags & NGX_JS_DICT_FLAG_MUST_NOT_EXIST) { + if (!dict->timeout || now < node->expire.key) { + ngx_rwlock_unlock(&dict->sh->rwlock); + return JS_FALSE; + } + } - if (flags & NGX_JS_DICT_FLAG_MUST_NOT_EXIST) { - if (!dict->timeout || now < node->expire.key) { - ngx_rwlock_unlock(&dict->sh->rwlock); - return JS_FALSE; + if (ngx_qjs_dict_update(cx, dict, node, value, timeout, now) + != NGX_OK) + { + goto memory_error; } } - if (ngx_qjs_dict_update(cx, dict, node, value, timeout, now) != NGX_OK) { - goto memory_error; - } + dict->sh->dirty = 1; ngx_rwlock_unlock(&dict->sh->rwlock); + if (dict->state_file.data && !dict->save_event.timer_set) { + ngx_add_timer(&dict->save_event, 1000); + } + return JS_TRUE; memory_error: @@ -2917,16 +4015,16 @@ ngx_qjs_dict_update(JSContext *cx, ngx_js_dict_t *dict, return NGX_ERROR; } - ngx_slab_free_locked(dict->shpool, node->u.value.data); + ngx_slab_free_locked(dict->shpool, node->value.str.data); ngx_memcpy(p, string.data, string.len); - node->u.value.data = p; - node->u.value.len = string.len; + node->value.str.data = p; + node->value.str.len = string.len; JS_FreeCString(cx, (char *) string.data); } else { - if (JS_ToFloat64(cx, &node->u.number, value) < 0) { + if (JS_ToFloat64(cx, &node->value.number, value) < 0) { return NGX_ERROR; } } diff --git a/nginx/ngx_js_shared_dict.h b/nginx/ngx_js_shared_dict.h index b9c7f967..b082962c 100644 --- a/nginx/ngx_js_shared_dict.h +++ b/nginx/ngx_js_shared_dict.h @@ -13,6 +13,7 @@ njs_int_t njs_js_ext_global_shared_prop(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *retval); njs_int_t njs_js_ext_global_shared_keys(njs_vm_t *vm, njs_value_t *value, njs_value_t *keys); +ngx_int_t ngx_js_dict_init_worker(ngx_js_main_conf_t *jmcf); extern njs_module_t ngx_js_shared_dict_module; diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 084162ba..5ed8fc30 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -241,6 +241,7 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, JSValue init, value, promise; ngx_int_t rc; ngx_url_t u; + ngx_str_t method; ngx_uint_t i; ngx_pool_t *pool; ngx_js_ctx_t *ctx; @@ -410,6 +411,13 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, continue; } + if (h[i].key.len == 14 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Content-Length", 14) + == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); @@ -429,7 +437,18 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, njs_chb_append(&http->chain, request.body.data, request.body.len); } else { - njs_chb_append_literal(&http->chain, CRLF); + method = request.method; + + if ((method.len == 4 + && (ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0)) + || (method.len == 3 + && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0)) + { + njs_chb_append_literal(&http->chain, "Content-Length: 0" CRLF CRLF); + + } else { + njs_chb_append_literal(&http->chain, CRLF); + } } if (u.addrs == NULL) { diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 0e022eb0..328ce581 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -264,6 +264,13 @@ static ngx_command_t ngx_stream_js_commands[] = { offsetof(ngx_stream_js_srv_conf_t, reuse), NULL }, + { ngx_string("js_context_reuse_max_size"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, reuse_max_size), + NULL }, + { ngx_string("js_import"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE13, ngx_js_import, @@ -3192,21 +3199,12 @@ ngx_stream_js_periodic_init(ngx_js_periodic_t *periodic) static ngx_int_t -ngx_stream_js_init_worker(ngx_cycle_t *cycle) +ngx_stream_js_init_worker_periodics(ngx_js_main_conf_t *jmcf) { - ngx_uint_t i; - ngx_js_periodic_t *periodics; - ngx_js_main_conf_t *jmcf; - - if ((ngx_process != NGX_PROCESS_WORKER) - && ngx_process != NGX_PROCESS_SINGLE) - { - return NGX_OK; - } + ngx_uint_t i; + ngx_js_periodic_t *periodics; - jmcf = ngx_stream_cycle_get_module_main_conf(cycle, ngx_stream_js_module); - - if (jmcf == NULL || jmcf->periodics == NULL) { + if (jmcf->periodics == NULL) { return NGX_OK; } @@ -3234,6 +3232,35 @@ ngx_stream_js_init_worker(ngx_cycle_t *cycle) } +static ngx_int_t +ngx_stream_js_init_worker(ngx_cycle_t *cycle) +{ + ngx_js_main_conf_t *jmcf; + + if ((ngx_process != NGX_PROCESS_WORKER) + && ngx_process != NGX_PROCESS_SINGLE) + { + return NGX_OK; + } + + jmcf = ngx_stream_cycle_get_module_main_conf(cycle, ngx_stream_js_module); + + if (jmcf == NULL) { + return NGX_OK; + } + + if (ngx_stream_js_init_worker_periodics(jmcf) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_js_dict_init_worker(jmcf) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + static char * ngx_stream_js_periodic(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/nginx/t/js_fetch.t b/nginx/t/js_fetch.t index 7ee1a602..76d9238d 100644 --- a/nginx/t/js_fetch.t +++ b/nginx/t/js_fetch.t @@ -52,10 +52,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /broken { js_content test.broken; } @@ -68,6 +64,10 @@ http { js_content test.body; } + location /body_content_length { + js_content test.body_content_length; + } + location /body_special { js_content test.body_special; } @@ -138,10 +138,6 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - function body(r) { var loc = r.args.loc; var getter = r.args.getter; @@ -164,6 +160,13 @@ $t->write_file('test.js', <<EOF); .catch(e => r.return(501, e.message)) } + async function body_content_length(r) { + let resp = await ngx.fetch(`http://127.0.0.1:$p0/loc`, + {headers: {'Content-Length': '100'}, + body: "CONTENT-BODY"}); + r.return(resp.status); + } + function property(r) { var opts = {headers:{}}; @@ -408,12 +411,12 @@ $t->write_file('test.js', <<EOF); export default {njs: test_njs, body, broken, broken_response, body_special, chain, chunked_ok, chunked_fail, header, header_iter, - host_header, multi, loc, property, engine}; + host_header, multi, loc, property, body_content_length }; EOF $t->try_run('no njs.fetch'); -$t->plan(37); +$t->plan(38); $t->run_daemon(\&http_daemon, port(8082)); $t->waitforsocket('127.0.0.1:' . port(8082)); @@ -516,6 +519,14 @@ like(http_get('/body_special?loc=head/large&method=HEAD'), qr/200 OK.*<empty>$/s, 'fetch head method large content-length'); } +TODO: { +local $TODO = 'not yet' unless has_version('0.9.1'); + +like(http_get('/body_content_length'), qr/200 OK/s, + 'fetch body content-length'); + +} + ############################################################################### sub has_version { diff --git a/nginx/t/js_fetch_https.t b/nginx/t/js_fetch_https.t index 8ede1048..42b5acbb 100644 --- a/nginx/t/js_fetch_https.t +++ b/nginx/t/js_fetch_https.t @@ -48,10 +48,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /https { js_content test.https; } @@ -106,10 +102,6 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - function https(r) { var url = `https://\${r.args.domain}:$p1/loc`; var opt = {}; @@ -124,7 +116,7 @@ $t->write_file('test.js', <<EOF); .catch(e => r.return(501, e.message)) } - export default {njs: test_njs, https, engine}; + export default {njs: test_njs, https}; EOF my $d = $t->testdir(); diff --git a/nginx/t/js_fetch_objects.t b/nginx/t/js_fetch_objects.t index bc5cc7ed..c9d04c49 100644 --- a/nginx/t/js_fetch_objects.t +++ b/nginx/t/js_fetch_objects.t @@ -45,10 +45,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /headers { js_content test.headers; } @@ -92,10 +88,6 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - function header(r) { r.return(200, r.headersIn.a); } @@ -528,7 +520,7 @@ $t->write_file('test.js', <<EOF); run(r, tests); } - export default {njs: test_njs, engine, body, headers, request, response, + export default {njs: test_njs, body, headers, request, response, fetch, fetch_multi_header}; EOF diff --git a/nginx/t/js_fetch_resolver.t b/nginx/t/js_fetch_resolver.t index 031ff43c..67680283 100644 --- a/nginx/t/js_fetch_resolver.t +++ b/nginx/t/js_fetch_resolver.t @@ -50,10 +50,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /dns { js_content test.dns; @@ -108,10 +104,6 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - const p0 = $p0; const p1 = $p1; @@ -141,7 +133,7 @@ $t->write_file('test.js', <<EOF); r.return(c, `\${v.host}:\${v.request_method}:\${foo}:\${bar}:\${body}`); } - export default {njs: test_njs, dns, loc, engine}; + export default {njs: test_njs, dns, loc}; EOF $t->try_run('no njs.fetch'); diff --git a/nginx/t/js_fetch_timeout.t b/nginx/t/js_fetch_timeout.t index ab1ba24a..2ca1510f 100644 --- a/nginx/t/js_fetch_timeout.t +++ b/nginx/t/js_fetch_timeout.t @@ -47,10 +47,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /normal_timeout { js_content test.timeout_test; } @@ -84,10 +80,6 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - async function timeout_test(r) { let rs = await Promise.allSettled([ 'http://127.0.0.1:$p1/normal_reply', @@ -110,7 +102,7 @@ $t->write_file('test.js', <<EOF); setTimeout((r) => { r.return(200); }, 250, r, 0); } - export default {njs: test_njs, engine, timeout_test, normal_reply, + export default {njs: test_njs, timeout_test, normal_reply, delayed_reply}; EOF diff --git a/nginx/t/js_fetch_verify.t b/nginx/t/js_fetch_verify.t index f98b4d8c..8b691a74 100644 --- a/nginx/t/js_fetch_verify.t +++ b/nginx/t/js_fetch_verify.t @@ -48,10 +48,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /https { js_content test.https; } @@ -80,10 +76,6 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - function https(r) { ngx.fetch(`https://example.com:$p1/loc`) .then(reply => reply.text()) @@ -91,7 +83,7 @@ $t->write_file('test.js', <<EOF); .catch(e => r.return(501, e.message)); } - export default {njs: test_njs, engine, https}; + export default {njs: test_njs, https}; EOF $t->write_file('openssl.conf', <<EOF); diff --git a/nginx/t/js_internal_redirect.t b/nginx/t/js_internal_redirect.t index abfe79f9..721113bb 100644 --- a/nginx/t/js_internal_redirect.t +++ b/nginx/t/js_internal_redirect.t @@ -11,6 +11,7 @@ use warnings; use strict; use Test::More; +use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -54,6 +55,10 @@ http { return 200 redirect$arg_b; } + location /destroyed_ctx { + js_content test.destroyed_ctx; + } + location @named { return 200 named; } @@ -87,7 +92,16 @@ $t->write_file('test.js', <<EOF); } } - export default {njs:test_njs, redirect}; + function destroyed_ctx(r) { + try { + r.return(200); + + } catch (e) { + r.internalRedirect("\@sub"); + } + } + + export default {njs:test_njs, redirect, destroyed_ctx}; EOF @@ -103,5 +117,18 @@ like(http_get('/test?unsafe=1'), qr/500 Internal Server/s, 'unsafe redirect'); like(http_get('/test?quoted=1'), qr/200 .*redirect/s, 'quoted redirect'); +get('/destroyed_ctx', 'If-Match: tt'); ############################################################################### + +sub get { + my ($url, @headers) = @_; + return http( + "GET $url HTTP/1.1" . CRLF . + 'Host: localhost' . CRLF . + 'Connection: close' . CRLF . + join(CRLF, @headers) . CRLF . CRLF + ); +} + +################################################################################ diff --git a/nginx/t/js_periodic.t b/nginx/t/js_periodic.t index d6868935..7e134588 100644 --- a/nginx/t/js_periodic.t +++ b/nginx/t/js_periodic.t @@ -56,7 +56,7 @@ http { server_name localhost; location @periodic { - js_periodic test.tick interval=30ms jitter=1ms; + js_periodic test.tick interval=20ms jitter=1ms; js_periodic test.timer interval=1s worker_affinity=all; js_periodic test.overrun interval=30ms; js_periodic test.affinity interval=50ms worker_affinity=0101; diff --git a/nginx/t/js_periodic_fetch.t b/nginx/t/js_periodic_fetch.t index 0231b662..39385132 100644 --- a/nginx/t/js_periodic_fetch.t +++ b/nginx/t/js_periodic_fetch.t @@ -54,10 +54,6 @@ http { js_periodic test.fetch_exception interval=1s; } - location /engine { - js_content test.engine; - } - location /fetch_ok { return 200 'ok'; } @@ -81,10 +77,6 @@ EOF my $p0 = port(8080); $t->write_file('test.js', <<EOF); - function engine(r) { - r.return(200, njs.engine); - } - async function fetch() { let reply = await ngx.fetch('http://127.0.0.1:$p0/fetch_ok'); let body = await reply.text(); @@ -107,16 +99,15 @@ $t->write_file('test.js', <<EOF); } function test_fetch(r) { - r.return(200, ngx.shared.strings.get('fetch').startsWith('okok')); + r.return(200, ngx.shared.strings.get('fetch')); } function test_multiple_fetches(r) { - r.return(200, ngx.shared.strings.get('multiple_fetches') - .startsWith('ok\@foo')); + r.return(200, ngx.shared.strings.get('multiple_fetches')); } export default { fetch, fetch_exception, multiple_fetches, test_fetch, - test_multiple_fetches, engine }; + test_multiple_fetches }; EOF $t->try_run('no js_periodic with fetch'); @@ -127,8 +118,8 @@ $t->plan(3); select undef, undef, undef, 0.1; -like(http_get('/test_fetch'), qr/true/, 'periodic fetch test'); -like(http_get('/test_multiple_fetches'), qr/true/, 'multiple fetch test'); +like(http_get('/test_fetch'), qr/(ok)+/, 'periodic fetch test'); +like(http_get('/test_multiple_fetches'), qr/ok\@foo/, 'multiple fetch test'); $t->stop(); diff --git a/nginx/t/js_shared_dict.t b/nginx/t/js_shared_dict.t index 8be2831f..b27a33ef 100644 --- a/nginx/t/js_shared_dict.t +++ b/nginx/t/js_shared_dict.t @@ -52,10 +52,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /add { js_content test.add; } @@ -141,10 +137,6 @@ $t->write_file('test.js', <<'EOF'); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - function convertToValue(dict, v) { if (dict.type == 'number') { return parseInt(v); @@ -337,7 +329,7 @@ $t->write_file('test.js', <<'EOF'); export default { add, capacity, chain, clear, del, free_space, get, has, incr, items, keys, name, njs: test_njs, pop, replace, set, - set_clear, size, zones, engine, overflow }; + set_clear, size, zones, overflow }; EOF $t->try_run('no js_shared_dict_zone'); diff --git a/nginx/t/js_shared_dict_state.t b/nginx/t/js_shared_dict_state.t new file mode 100644 index 00000000..32eef948 --- /dev/null +++ b/nginx/t/js_shared_dict_state.t @@ -0,0 +1,256 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for js_shared_dict_zone directive, state= parameter. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require JSON::PP; }; +plan(skip_all => "JSON::PP not installed") if $@; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + js_shared_dict_zone zone=bar:64k type=string state=bar.json; + js_shared_dict_zone zone=waka:32k timeout=1000s type=number state=waka.json; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /add { + js_content test.add; + } + + location /clear { + js_content test.clear; + } + + location /delete { + js_content test.del; + } + + location /get { + js_content test.get; + } + + location /incr { + js_content test.incr; + } + + location /pop { + js_content test.pop; + } + + location /set { + js_content test.set; + } + } +} + +EOF + +$t->write_file('bar.json', <<EOF); +{"waka":{"value":"foo","expire":0}, + "bar": { "value" :"\\u0061\\u0062\\u0063"}, +"FOO \\n": { "value" : "BAZ", "unexpected_str": "u\\r" }, + "X": { "valu\\u0065" : "\\n" , "unexpected_num": 23.1 } , + "\\u0061\\u0062\\u0063": { "value" : "def" } , +} +EOF + +$t->write_file('test.js', <<'EOF'); + function convertToValue(dict, v) { + if (dict.type == 'number') { + return parseInt(v); + + } else if (v == 'empty') { + v = ''; + } + + return v; + } + + function add(r) { + var dict = ngx.shared[r.args.dict]; + var value = convertToValue(dict, r.args.value); + + if (r.args.timeout) { + var timeout = Number(r.args.timeout); + r.return(200, dict.add(r.args.key, value, timeout)); + + } else { + r.return(200, dict.add(r.args.key, value)); + } + } + + function clear(r) { + var dict = ngx.shared[r.args.dict]; + var result = dict.clear(); + r.return(200, result === undefined ? 'undefined' : result); + } + + function del(r) { + var dict = ngx.shared[r.args.dict]; + r.return(200, dict.delete(r.args.key)); + } + + function get(r) { + var dict = ngx.shared[r.args.dict]; + var val = dict.get(r.args.key); + + if (val == '') { + val = 'empty'; + + } else if (val === undefined) { + val = 'undefined'; + } + + r.return(200, val); + } + + function incr(r) { + var dict = ngx.shared[r.args.dict]; + var def = r.args.def ? parseInt(r.args.def) : 0; + + if (r.args.timeout) { + var timeout = Number(r.args.timeout); + var val = dict.incr(r.args.key, parseInt(r.args.by), def, timeout); + r.return(200, val); + + } else { + var val = dict.incr(r.args.key, parseInt(r.args.by), def); + r.return(200, val); + } + } + + function pop(r) { + var dict = ngx.shared[r.args.dict]; + var val = dict.pop(r.args.key); + if (val == '') { + val = 'empty'; + + } else if (val === undefined) { + val = 'undefined'; + } + + r.return(200, val); + } + + function set(r) { + var dict = ngx.shared[r.args.dict]; + var value = convertToValue(dict, r.args.value); + + if (r.args.timeout) { + var timeout = Number(r.args.timeout); + r.return(200, dict.set(r.args.key, value, timeout) === dict); + + } else { + r.return(200, dict.set(r.args.key, value) === dict); + } + } + + export default { add, clear, del, get, incr, pop, set }; +EOF + +$t->try_run('no js_shared_dict_zone with state=')->plan(11); + +############################################################################### + +like(http_get('/get?dict=bar&key=waka'), qr/foo/, 'get bar.waka'); +like(http_get('/get?dict=bar&key=bar'), qr/abc/, 'get bar.bar'); +like(http_get('/get?dict=bar&key=FOO%20%0A'), qr/BAZ/, 'get bar["FOO \\n"]'); +like(http_get('/get?dict=bar&key=abc'), qr/def/, 'get bar.abc'); + +http_get('/set?dict=bar&key=waka&value=foo2'); +http_get('/delete?dict=bar&key=bar'); + +http_get('/set?dict=waka&key=foo&value=42'); + +select undef, undef, undef, 1.1; + +$t->reload(); + +my $bar_state = read_state($t, 'bar.json'); +my $waka_state = read_state($t, 'waka.json'); + +is($bar_state->{waka}->{value}, 'foo2', 'get bar.waka from state'); +is($bar_state->{bar}, undef, 'no bar.bar in state'); +is($waka_state->{foo}->{value}, '42', 'get waka.foo from state'); +like($waka_state->{foo}->{expire}, qr/^\d+$/, 'waka.foo expire'); + +http_get('/pop?dict=bar&key=FOO%20%0A'); + +http_get('/incr?dict=waka&key=foo&by=1'); + +select undef, undef, undef, 1.1; + +$bar_state = read_state($t, 'bar.json'); +$waka_state = read_state($t, 'waka.json'); + +is($bar_state->{'FOO \\n'}, undef, 'no bar.FOO \\n in state'); +is($waka_state->{foo}->{value}, '43', 'get waka.foo from state'); + +http_get('/clear?dict=bar'); + +select undef, undef, undef, 1.1; + +$bar_state = read_state($t, 'bar.json'); + +is($bar_state->{waka}, undef, 'no bar.waka in state'); + +############################################################################### + +sub decode_json { + my $json; + eval { $json = JSON::PP::decode_json(shift) }; + + if ($@) { + return "<failed to parse JSON>"; + } + + return $json; +} + +sub read_state { + my ($self, $file) = @_; + my $json = $self->read_file($file); + + if ($json) { + $json = decode_json($json); + } + + return $json; +} + +############################################################################### diff --git a/nginx/t/stream_js_fetch.t b/nginx/t/stream_js_fetch.t index 9a42ae29..cb87eec7 100644 --- a/nginx/t/stream_js_fetch.t +++ b/nginx/t/stream_js_fetch.t @@ -46,10 +46,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /validate { js_content test.validate; } @@ -103,10 +99,6 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - function validate(r) { r.return((r.requestText == 'QZ') ? 200 : 403); } @@ -166,7 +158,7 @@ $t->write_file('test.js', <<EOF); } export default {njs: test_njs, validate, preread_verify, filter_verify, - access_ok, access_nok, engine}; + access_ok, access_nok}; EOF $t->try_run('no stream njs available'); diff --git a/nginx/t/stream_js_fetch_https.t b/nginx/t/stream_js_fetch_https.t index 987a896a..f397ea70 100644 --- a/nginx/t/stream_js_fetch_https.t +++ b/nginx/t/stream_js_fetch_https.t @@ -47,10 +47,6 @@ http { location /njs { js_content test.njs; } - - location /engine { - js_content test.engine; - } } server { @@ -163,10 +159,6 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - function preread(s) { s.on('upload', function (data, flags) { if (data.startsWith('GO')) { @@ -201,7 +193,7 @@ $t->write_file('test.js', <<EOF); (r.status == 200) ? s.allow(): s.deny(); } - export default {njs: test_njs, engine, preread, access_ok, access_nok}; + export default {njs: test_njs, preread, access_ok, access_nok}; EOF my $d = $t->testdir(); diff --git a/nginx/t/stream_js_fetch_init.t b/nginx/t/stream_js_fetch_init.t index f48b9d5e..3980a9ee 100644 --- a/nginx/t/stream_js_fetch_init.t +++ b/nginx/t/stream_js_fetch_init.t @@ -58,10 +58,6 @@ http { js_content test.njs; } - location /engine { - js_content test.engine; - } - location /success { return 200; } @@ -77,17 +73,13 @@ $t->write_file('test.js', <<EOF); r.return(200, njs.version); } - function engine(r) { - r.return(200, njs.engine); - } - async function access_ok(s) { let reply = await ngx.fetch('http://127.0.0.1:$p/success'); (reply.status == 200) ? s.allow(): s.deny(); } - export default {njs: test_njs, engine, access_ok}; + export default {njs: test_njs, access_ok}; EOF $t->try_run('no stream njs available'); diff --git a/nginx/t/stream_js_periodic_fetch.t b/nginx/t/stream_js_periodic_fetch.t index 60599423..4ebec96e 100644 --- a/nginx/t/stream_js_periodic_fetch.t +++ b/nginx/t/stream_js_periodic_fetch.t @@ -67,10 +67,6 @@ http { listen 127.0.0.1:8080; server_name localhost; - location /engine { - js_content test.engine; - } - location /fetch_ok { return 200 'ok'; } @@ -86,10 +82,6 @@ EOF my $p1 = port(8080); $t->write_file('test.js', <<EOF); - function engine(r) { - r.return(200, njs.engine); - } - async function fetch() { let reply = await ngx.fetch('http://127.0.0.1:$p1/fetch_ok'); let body = await reply.text(); @@ -142,7 +134,7 @@ $t->write_file('test.js', <<EOF); }); } - export default { engine, fetch, fetch_exception, test, multiple_fetches }; + export default { fetch, fetch_exception, test, multiple_fetches }; EOF $t->run_daemon(\&stream_daemon, port(8090)); diff --git a/nginx/t/stream_js_shared_dict.t b/nginx/t/stream_js_shared_dict.t index 915cc40b..0435033d 100644 --- a/nginx/t/stream_js_shared_dict.t +++ b/nginx/t/stream_js_shared_dict.t @@ -43,10 +43,6 @@ http { location / { return 200; } - - location /engine { - js_content test.engine; - } } } @@ -75,10 +71,6 @@ EOF $t->write_file('test.js', <<EOF); import qs from 'querystring'; - function engine(r) { - r.return(200, 'engine'); - } - function preread_verify(s) { var collect = Buffer.from([]); @@ -121,7 +113,7 @@ $t->write_file('test.js', <<EOF); }); } - export default { engine, preread_verify, control_access }; + export default { preread_verify, control_access }; EOF diff --git a/nginx/t/stream_js_shared_dict_state.t b/nginx/t/stream_js_shared_dict_state.t new file mode 100644 index 00000000..c2edb63e --- /dev/null +++ b/nginx/t/stream_js_shared_dict_state.t @@ -0,0 +1,137 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for js_shared_dict_zone directive, state= parameter. + +############################################################################### + +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 $t = Test::Nginx->new()->has(qw/stream/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + js_shared_dict_zone zone=foo:32k state=foo.json; + + server { + listen 127.0.0.1:8081; + js_preread test.preread_verify; + proxy_pass 127.0.0.1:8090; + } +} + +EOF + +$t->write_file('foo.json', <<EOF); +{"QZ":{"value":"1"},"QQ":{"value":"1"}} +EOF + +$t->write_file('test.js', <<EOF); + function preread_verify(s) { + var collect = Buffer.from([]); + + s.on('upstream', function (data, flags) { + collect = Buffer.concat([collect, data]); + + if (collect.length >= 4 && collect.readUInt16BE(0) == 0xabcd) { + let id = collect.slice(2,4); + + ngx.shared.foo.get(id) ? s.done(): s.deny(); + + } else if (collect.length) { + s.deny(); + } + }); + } + + export default { preread_verify }; + +EOF + +$t->try_run('no js_shared_dict_zone with state='); + +$t->plan(2); + +$t->run_daemon(\&stream_daemon, port(8090)); +$t->waitforsocket('127.0.0.1:' . port(8090)); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQY##"), "", + 'access failed, QY is not in the shared dict'); +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQZ##"), "\xAB\xCDQZ##", + 'access granted'); + +############################################################################### + +sub stream_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8090), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + log2c("(new connection $client)"); + + $client->sysread(my $buffer, 65536) or next; + + log2i("$client $buffer"); + + log2o("$client $buffer"); + + $client->syswrite($buffer); + + close $client; + } +} + +sub log2i { Test::Nginx::log_core('|| <<', @_); } +sub log2o { Test::Nginx::log_core('|| >>', @_); } +sub log2c { Test::Nginx::log_core('||', @_); } + +sub get { + my ($url, %extra) = @_; + + my $s = IO::Socket::INET->new( + Proto => 'tcp', + PeerAddr => '127.0.0.1:' . port(8082) + ) or die "Can't connect to nginx: $!\n"; + + return http_get($url, socket => $s); +} + +############################################################################### diff --git a/src/njs_array.c b/src/njs_array.c index e6f8ed83..3f424bc4 100644 --- a/src/njs_array.c +++ b/src/njs_array.c @@ -702,8 +702,10 @@ njs_array_length(njs_vm_t *vm,njs_object_prop_t *prop, uint32_t unused, } } - prop->type = NJS_PROPERTY; - njs_set_number(njs_prop_value(prop), length); + ret = njs_array_length_redefine(vm, value, length, 1); + if (ret != NJS_OK) { + return ret; + } njs_value_assign(retval, setval); diff --git a/src/njs_atom.c b/src/njs_atom.c index 24e6dc17..dc66f886 100644 --- a/src/njs_atom.c +++ b/src/njs_atom.c @@ -82,7 +82,7 @@ njs_atom_find_or_add(njs_vm_t *vm, u_char *key, size_t size, size_t length, uint32_t hash) { njs_int_t ret; - njs_value_t *entry; + njs_object_prop_t *prop; njs_lvlhsh_query_t lhq; lhq.key.start = key; @@ -92,33 +92,65 @@ njs_atom_find_or_add(njs_vm_t *vm, u_char *key, size_t size, size_t length, ret = njs_lvlhsh_find(vm->atom_hash_current, &lhq); if (ret == NJS_OK) { - return lhq.value; + return njs_prop_value(lhq.value); } ret = njs_lvlhsh_find(&vm->atom_hash_shared, &lhq); if (ret == NJS_OK) { - return lhq.value; + return njs_prop_value(lhq.value); } - entry = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); - if (njs_slow_path(entry == NULL)) { + lhq.pool = vm->mem_pool; + + ret = njs_lvlhsh_insert(vm->atom_hash_current, &lhq); + if (njs_slow_path(ret != NJS_OK)) { return NULL; } - ret = njs_string_create(vm, entry, key, size); + prop = lhq.value; + + ret = njs_string_create(vm, &prop->u.value, key, size); if (njs_slow_path(ret != NJS_OK)) { return NULL; } - entry->string.atom_id = vm->atom_id_generator++; - if (njs_atom_is_number(entry->string.atom_id)) { + prop->u.value.string.atom_id = vm->atom_id_generator++; + if (njs_atom_is_number(prop->u.value.string.atom_id)) { njs_internal_error(vm, "too many atoms"); return NULL; } - entry->string.token_type = NJS_KEYWORD_TYPE_UNDEF; + prop->u.value.string.token_type = NJS_KEYWORD_TYPE_UNDEF; + + return &prop->u.value; +} + + +static njs_value_t * +njs_atom_find_or_add_string(njs_vm_t *vm, njs_value_t *value, + uint32_t hash) +{ + njs_int_t ret; + njs_object_prop_t *prop; + njs_lvlhsh_query_t lhq; + + njs_assert(njs_is_string(value)); + + lhq.key.start = value->string.data->start; + lhq.key.length = value->string.data->size; + lhq.key_hash = hash; + lhq.proto = &njs_lexer_hash_proto; + + ret = njs_lvlhsh_find(vm->atom_hash_current, &lhq); + if (ret == NJS_OK) { + return njs_prop_value(lhq.value); + } + + ret = njs_lvlhsh_find(&vm->atom_hash_shared, &lhq); + if (ret == NJS_OK) { + return njs_prop_value(lhq.value); + } - lhq.value = entry; lhq.pool = vm->mem_pool; ret = njs_lvlhsh_insert(vm->atom_hash_current, &lhq); @@ -126,7 +158,19 @@ njs_atom_find_or_add(njs_vm_t *vm, u_char *key, size_t size, size_t length, return NULL; } - return entry; + prop = lhq.value; + + prop->u.value = *value; + + prop->u.value.string.atom_id = vm->atom_id_generator++; + if (njs_atom_is_number(prop->u.value.string.atom_id)) { + njs_internal_error(vm, "too many atoms"); + return NULL; + } + + prop->u.value.string.token_type = NJS_KEYWORD_TYPE_UNDEF; + + return &prop->u.value; } @@ -190,7 +234,6 @@ njs_atom_hash_init(njs_vm_t *vm) if (value->type == NJS_SYMBOL) { lhq.key_hash = value->string.atom_id; - lhq.value = (void *) value; ret = njs_flathsh_insert(&vm->atom_hash_shared, &lhq); if (njs_slow_path(ret != NJS_OK)) { @@ -206,7 +249,6 @@ njs_atom_hash_init(njs_vm_t *vm) lhq.key_hash = njs_djb_hash(start, len); lhq.key.length = len; lhq.key.start = start; - lhq.value = (void *) value; ret = njs_flathsh_insert(&vm->atom_hash_shared, &lhq); if (njs_slow_path(ret != NJS_OK)) { @@ -214,6 +256,8 @@ njs_atom_hash_init(njs_vm_t *vm) return 0xffffffff; } } + + *njs_prop_value(lhq.value) = *value; } vm->atom_hash_current = &vm->atom_hash_shared; @@ -247,10 +291,7 @@ njs_atom_atomize_key(njs_vm_t *vm, njs_value_t *value) hash_id = njs_djb_hash(value->string.data->start, value->string.data->size); - entry = njs_atom_find_or_add(vm, value->string.data->start, - value->string.data->size, - value->string.data->length, - hash_id); + entry = njs_atom_find_or_add_string(vm, value, hash_id); if (njs_slow_path(entry == NULL)) { return NJS_ERROR; } @@ -320,13 +361,14 @@ njs_atom_symbol_add(njs_vm_t *vm, njs_value_t *value) if (value->type == NJS_SYMBOL) { lhq.key_hash = value->atom_id; - lhq.value = (void *) value; ret = njs_flathsh_insert(vm->atom_hash_current, &lhq); if (njs_slow_path(ret != NJS_OK)) { njs_internal_error(vm, "flathsh insert/replace failed"); return NJS_ERROR; } + + *njs_prop_value(lhq.value) = *value; } return NJS_OK; diff --git a/src/njs_buffer.c b/src/njs_buffer.c index 3f0469e7..dbe9447a 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -2732,8 +2732,8 @@ static njs_int_t njs_buffer(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value, njs_value_t *unused, njs_value_t *retval) { - return njs_object_prop_init(vm, &njs_buffer_constructor_init, prop, atom_id, - value, retval); + return njs_object_props_init(vm, &njs_buffer_constructor_init, prop, + atom_id, value, retval); } @@ -2741,8 +2741,8 @@ static njs_int_t njs_buffer_constants(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t atom_id, njs_value_t *value, njs_value_t *unused, njs_value_t *retval) { - return njs_object_prop_init(vm, &njs_buffer_constants_init, prop, atom_id, - value, retval); + return njs_object_props_init(vm, &njs_buffer_constants_init, prop, atom_id, + value, retval); } diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 230b4c1c..5ce2ec7e 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -433,17 +433,11 @@ njs_builtin_traverse(njs_vm_t *vm, njs_traverse_t *traverse, void *data) /* NJS_BUILTIN_TRAVERSE_KEYS. */ - prop = njs_object_prop_alloc(vm, &njs_value_null, 0); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - ret = njs_atom_string_create(vm, &prop_name, buf, p - buf); if (njs_slow_path(ret != NJS_OK)) { return ret; } - lhq.value = prop; lhq.key_hash = prop_name.atom_id; lhq.replace = 1; lhq.pool = vm->mem_pool; @@ -455,6 +449,14 @@ njs_builtin_traverse(njs_vm_t *vm, njs_traverse_t *traverse, void *data) return NJS_ERROR; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 0; + prop->configurable = 0; + prop->writable = 0; + prop->u.value = njs_value_null; + return NJS_OK; } @@ -477,6 +479,7 @@ njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function, njs_uint_t i, n; njs_value_t value, tag; njs_object_t object; + njs_object_prop_t *prop; njs_lvlhsh_each_t lhe; njs_exotic_slots_t *slots; njs_function_name_t *fn; @@ -538,12 +541,13 @@ njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function, njs_lvlhsh_each_init(&lhe, &njs_modules_hash_proto); for ( ;; ) { - module = njs_lvlhsh_each(&vm->modules_hash, &lhe); - - if (module == NULL) { + prop = (njs_object_prop_t *) njs_flathsh_each(&vm->modules_hash, &lhe); + if (prop == NULL) { break; } + module = prop->u.mod; + if (njs_is_object(&module->value) && !njs_object(&module->value)->shared) { @@ -759,7 +763,6 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop, { njs_value_t *value; njs_variable_t *var; - njs_function_t *function; njs_rbtree_node_t *rb_node; njs_variable_node_t *node, var_node; @@ -788,15 +791,6 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop, value = njs_scope_valid_value(vm, var->index); - if (var->type == NJS_VARIABLE_FUNCTION && njs_is_undefined(value)) { - njs_value_assign(value, &var->value); - - function = njs_function_value_copy(vm, value); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - } - if (setval != NULL) { njs_value_assign(value, setval); } @@ -825,15 +819,6 @@ njs_global_this_object(njs_vm_t *vm, njs_object_prop_t *self, uint32_t atom_id, njs_value_assign(retval, setval); } - prop = njs_object_prop_alloc(vm, retval, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - - njs_value_assign(njs_prop_value(prop), retval); - prop->enumerable = self->enumerable; - - lhq.value = prop; lhq.key_hash = atom_id; lhq.replace = 1; lhq.pool = vm->mem_pool; @@ -845,6 +830,14 @@ njs_global_this_object(njs_vm_t *vm, njs_object_prop_t *self, uint32_t atom_id, return NJS_ERROR; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = self->enumerable; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = *retval; + return NJS_OK; } @@ -876,15 +869,6 @@ njs_top_level_object(njs_vm_t *vm, njs_object_prop_t *self, uint32_t atom_id, object->__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_OBJECT); } - prop = njs_object_prop_alloc(vm, retval, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - - njs_value_assign(njs_prop_value(prop), retval); - prop->enumerable = self->enumerable; - - lhq.value = prop; lhq.key_hash = atom_id; lhq.replace = 1; lhq.pool = vm->mem_pool; @@ -896,6 +880,14 @@ njs_top_level_object(njs_vm_t *vm, njs_object_prop_t *self, uint32_t atom_id, return NJS_ERROR; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = self->enumerable; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = *retval; + return NJS_OK; } @@ -925,15 +917,6 @@ njs_top_level_constructor(njs_vm_t *vm, njs_object_prop_t *self, return NJS_OK; } - prop = njs_object_prop_alloc(vm, retval, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - - njs_value_assign(njs_prop_value(prop), retval); - prop->enumerable = 0; - - lhq.value = prop; lhq.key_hash = atom_id; lhq.replace = 1; lhq.pool = vm->mem_pool; @@ -945,6 +928,14 @@ njs_top_level_constructor(njs_vm_t *vm, njs_object_prop_t *self, return NJS_ERROR; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 0; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = *retval; + return NJS_OK; } @@ -1215,28 +1206,29 @@ njs_process_object_argv(njs_vm_t *vm, njs_object_prop_t *pr, uint32_t unused, } } - prop = njs_object_prop_alloc(vm, &njs_value_undefined, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - - njs_set_array(njs_prop_value(prop), argv); - - lhq.value = prop; lhq.key_hash = NJS_ATOM_STRING_argv; lhq.replace = 1; lhq.pool = vm->mem_pool; lhq.proto = &njs_object_hash_proto; ret = njs_flathsh_unique_insert(njs_object_hash(process), &lhq); - if (njs_fast_path(ret == NJS_OK)) { - njs_value_assign(retval, njs_prop_value(prop)); - return NJS_OK; + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NJS_ERROR; } - njs_internal_error(vm, "lvlhsh insert failed"); + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + + njs_set_array(njs_prop_value(prop), argv); + + njs_value_assign(retval, njs_prop_value(prop)); + return NJS_OK; - return NJS_ERROR; } @@ -1261,10 +1253,6 @@ njs_env_hash_init(njs_vm_t *vm, njs_flathsh_t *hash, char **environment) ep = environment; while (*ep != NULL) { - prop = njs_object_prop_alloc(vm, &njs_value_undefined, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } entry = (u_char *) *ep++; @@ -1293,17 +1281,11 @@ njs_env_hash_init(njs_vm_t *vm, njs_flathsh_t *hash, char **environment) val++; - ret = njs_string_create(vm, njs_prop_value(prop), val, njs_strlen(val)); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } - ret = njs_atom_atomize_key(vm, &prop_name); if (ret != NJS_OK) { return ret; } - lhq.value = prop; lhq.key_hash = prop_name.atom_id; ret = njs_flathsh_unique_insert(hash, &lhq); @@ -1319,6 +1301,19 @@ njs_env_hash_init(njs_vm_t *vm, njs_flathsh_t *hash, char **environment) * Always using the first element among the duplicates * and ignoring the rest. */ + continue; + } + + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + + ret = njs_string_create(vm, njs_prop_value(prop), val, njs_strlen(val)); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; } } @@ -1342,28 +1337,29 @@ njs_process_object_env(njs_vm_t *vm, njs_object_prop_t *pr, uint32_t unused, env->shared_hash = vm->shared->env_hash; - prop = njs_object_prop_alloc(vm, &njs_value_undefined, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - - njs_set_object(njs_prop_value(prop), env); - lhq.replace = 1; lhq.pool = vm->mem_pool; lhq.proto = &njs_object_hash_proto; - lhq.value = prop; lhq.key_hash = NJS_ATOM_STRING_env; ret = njs_flathsh_unique_insert(njs_object_hash(process), &lhq); - if (njs_fast_path(ret == NJS_OK)) { - njs_value_assign(retval, njs_prop_value(prop)); - return NJS_OK; + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NJS_ERROR; } - njs_internal_error(vm, "lvlhsh insert failed"); + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; - return NJS_ERROR; + njs_set_object(njs_prop_value(prop), env); + + njs_value_assign(retval, njs_prop_value(prop)); + + return NJS_OK; } diff --git a/src/njs_chb.c b/src/njs_chb.c index 3ee28009..ac88c0bd 100644 --- a/src/njs_chb.c +++ b/src/njs_chb.c @@ -251,7 +251,11 @@ njs_chb_destroy(njs_chb_t *chain) while (n != NULL) { next = n->next; - chain->free(chain->pool, n); + + if (chain->free != NULL) { + chain->free(chain->pool, n); + } + n = next; } } diff --git a/src/njs_clang.h b/src/njs_clang.h index 2bd45a74..af9f3350 100644 --- a/src/njs_clang.h +++ b/src/njs_clang.h @@ -111,7 +111,7 @@ njs_leading_zeros64(uint64_t x) n = 0; - while ((x & 0x8000000000000000) == 0) { + while ((x & 0x8000000000000000ULL) == 0) { n++; x <<= 1; } diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c index c7927bf5..72ea63b9 100644 --- a/src/njs_disassembler.c +++ b/src/njs_disassembler.c @@ -30,9 +30,6 @@ static njs_code_name_t code_names[] = { { NJS_VMCODE_TEMPLATE_LITERAL, sizeof(njs_vmcode_template_literal_t), njs_str("TEMPLATE LITERAL") }, - { NJS_VMCODE_FUNCTION_COPY, sizeof(njs_vmcode_function_copy_t), - njs_str("FUNCTION COPY ") }, - { NJS_VMCODE_PROPERTY_GET, sizeof(njs_vmcode_prop_get_t), njs_str("PROP GET ") }, { NJS_VMCODE_PROPERTY_ATOM_GET, sizeof(njs_vmcode_prop_get_t), diff --git a/src/njs_dtoa.h b/src/njs_dtoa.h index 35ff09a5..4dcfc098 100644 --- a/src/njs_dtoa.h +++ b/src/njs_dtoa.h @@ -7,6 +7,8 @@ #ifndef _NJS_DTOA_H_INCLUDED_ #define _NJS_DTOA_H_INCLUDED_ +#define NJS_DTOA_MAX_LEN njs_length("-1.7976931348623157e+308") + NJS_EXPORT size_t njs_dtoa(double value, char *start); NJS_EXPORT size_t njs_dtoa_precision(double value, char *start, size_t prec); NJS_EXPORT size_t njs_dtoa_exponential(double value, char *start, diff --git a/src/njs_error.c b/src/njs_error.c index 6e12aa61..d19e0e22 100644 --- a/src/njs_error.c +++ b/src/njs_error.c @@ -212,12 +212,6 @@ njs_error_alloc(njs_vm_t *vm, njs_object_t *proto, const njs_value_t *name, lhq.proto = &njs_object_hash_proto; if (name != NULL) { - prop = njs_object_prop_alloc(vm, name, 1); - if (njs_slow_path(prop == NULL)) { - goto memory_error; - } - - lhq.value = prop; lhq.key_hash = NJS_ATOM_STRING_name; ret = njs_flathsh_unique_insert(&error->hash, &lhq); @@ -225,17 +219,18 @@ njs_error_alloc(njs_vm_t *vm, njs_object_t *proto, const njs_value_t *name, njs_internal_error(vm, "lvlhsh insert failed"); return NULL; } - } - if (message!= NULL) { - prop = njs_object_prop_alloc(vm, message, 1); - if (njs_slow_path(prop == NULL)) { - goto memory_error; - } + prop = lhq.value; - prop->enumerable = 0; + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + + prop->u.value = *name; + } - lhq.value = prop; + if (message!= NULL) { lhq.key_hash = NJS_ATOM_STRING_message; ret = njs_flathsh_unique_insert(&error->hash, &lhq); @@ -243,17 +238,18 @@ njs_error_alloc(njs_vm_t *vm, njs_object_t *proto, const njs_value_t *name, njs_internal_error(vm, "lvlhsh insert failed"); return NULL; } - } - if (errors != NULL) { - prop = njs_object_prop_alloc(vm, errors, 1); - if (njs_slow_path(prop == NULL)) { - goto memory_error; - } + prop = lhq.value; + prop->type = NJS_PROPERTY; prop->enumerable = 0; + prop->configurable = 1; + prop->writable = 1; - lhq.value = prop; + prop->u.value = *message; + } + + if (errors != NULL) { lhq.key_hash = NJS_ATOM_STRING_errors; ret = njs_flathsh_unique_insert(&error->hash, &lhq); @@ -261,6 +257,15 @@ njs_error_alloc(njs_vm_t *vm, njs_object_t *proto, const njs_value_t *name, njs_internal_error(vm, "lvlhsh insert failed"); return NULL; } + + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 0; + prop->configurable = 1; + prop->writable = 1; + + prop->u.value = *errors; } return error; diff --git a/src/njs_extern.c b/src/njs_extern.c index 077d2a5f..adc8cd67 100644 --- a/src/njs_extern.c +++ b/src/njs_extern.c @@ -58,15 +58,6 @@ njs_external_add(njs_vm_t *vm, njs_arr_t *protos, continue; } - prop = njs_object_prop_alloc(vm, &njs_value_invalid, 1); - if (njs_slow_path(prop == NULL)) { - goto memory_error; - } - - prop->writable = external->writable; - prop->configurable = external->configurable; - prop->enumerable = external->enumerable; - if (external->flags & NJS_EXTERN_SYMBOL) { lhq.key_hash = external->name.symbol; @@ -81,7 +72,20 @@ njs_external_add(njs_vm_t *vm, njs_arr_t *protos, lhq.key_hash = prop_name.atom_id; } - lhq.value = prop; + ret = njs_flathsh_unique_insert(hash, &lhq); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NJS_ERROR; + } + + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = external->enumerable; + prop->configurable = external->configurable; + prop->writable = external->writable; + prop->u.value = njs_value_invalid; + switch (external->flags & NJS_EXTERN_TYPE_MASK) { case NJS_EXTERN_METHOD: @@ -167,12 +171,6 @@ njs_external_add(njs_vm_t *vm, njs_arr_t *protos, break; } - ret = njs_flathsh_unique_insert(hash, &lhq); - if (njs_slow_path(ret != NJS_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NJS_ERROR; - } - external++; } @@ -224,16 +222,6 @@ njs_external_prop_handler(njs_vm_t *vm, njs_object_prop_t *self, njs_set_object_value(retval, ov); } - prop = njs_object_prop_alloc(vm, retval, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - - prop->writable = self->writable; - prop->configurable = self->configurable; - prop->enumerable = self->enumerable; - - lhq.value = prop; lhq.key_hash = atom_id; lhq.replace = 1; lhq.pool = vm->mem_pool; @@ -245,6 +233,15 @@ njs_external_prop_handler(njs_vm_t *vm, njs_object_prop_t *self, return NJS_ERROR; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = self->enumerable; + prop->configurable = self->configurable; + prop->writable = self->writable; + + prop->u.value = *retval; + return NJS_OK; } diff --git a/src/njs_flathsh.c b/src/njs_flathsh.c index a9ef6e69..7e374c62 100644 --- a/src/njs_flathsh.c +++ b/src/njs_flathsh.c @@ -210,13 +210,14 @@ njs_flathsh_add_elt(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) elts = njs_hash_elts(h); elt = &elts[h->elts_count++]; - elt->value = fhq->value; elt->key_hash = fhq->key_hash; cell_num = fhq->key_hash & h->hash_mask; elt->next_elt = njs_hash_cells_end(h)[-cell_num - 1]; njs_hash_cells_end(h)[-cell_num - 1] = h->elts_count; + elt->type = NJS_PROPERTY; + return elt; } @@ -269,7 +270,7 @@ njs_expand_elts(njs_flathsh_query_t *fhq, njs_flathsh_descr_t *h) njs_memzero(chunk, sizeof(uint32_t) * new_hash_size); for (i = 0, elt = njs_hash_elts(h); i < h->elts_count; i++, elt++) { - if (elt->value != NULL) { + if (elt->type != NJS_FREE_FLATHSH_ELEMENT) { cell_num = elt->key_hash & new_hash_mask; elt->next_elt = njs_hash_cells_end(h)[-cell_num - 1]; njs_hash_cells_end(h)[-cell_num - 1] = i + 1; @@ -331,7 +332,7 @@ njs_flathsh_find(const njs_flathsh_t *fh, njs_flathsh_query_t *fhq) if (e->key_hash == fhq->key_hash && fhq->proto->test(fhq, e->value) == NJS_OK) { - fhq->value = e->value; + fhq->value = e; return NJS_OK; } @@ -362,7 +363,7 @@ njs_flathsh_unique_find(const njs_flathsh_t *fh, njs_flathsh_query_t *fhq) e = &elts[elt_num - 1]; if (e->key_hash == fhq->key_hash) { - fhq->value = e->value; + fhq->value = e; return NJS_OK; } @@ -376,7 +377,6 @@ njs_flathsh_unique_find(const njs_flathsh_t *fh, njs_flathsh_query_t *fhq) njs_int_t njs_flathsh_insert(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) { - void *tmp; njs_int_t cell_num, elt_num; njs_flathsh_elt_t *elt, *elts; njs_flathsh_descr_t *h; @@ -403,15 +403,10 @@ njs_flathsh_insert(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) fhq->proto->test(fhq, elt->value) == NJS_OK) { if (fhq->replace) { - tmp = fhq->value; - fhq->value = elt->value; - elt->value = tmp; - + fhq->value = elt; return NJS_OK; } else { - fhq->value = elt->value; - return NJS_DECLINED; } } @@ -424,7 +419,7 @@ njs_flathsh_insert(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) return NJS_ERROR; } - elt->value = fhq->value; + fhq->value = elt; return NJS_OK; } @@ -433,7 +428,6 @@ njs_flathsh_insert(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) njs_int_t njs_flathsh_unique_insert(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) { - void *tmp; njs_int_t cell_num, elt_num; njs_flathsh_elt_t *elt, *elts; njs_flathsh_descr_t *h; @@ -458,15 +452,10 @@ njs_flathsh_unique_insert(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) if (elt->key_hash == fhq->key_hash) { if (fhq->replace) { - tmp = fhq->value; - fhq->value = elt->value; - elt->value = tmp; - + fhq->value = elt; return NJS_OK; } else { - fhq->value = elt->value; - return NJS_DECLINED; } } @@ -479,7 +468,7 @@ njs_flathsh_unique_insert(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) return NJS_ERROR; } - elt->value = fhq->value; + fhq->value = elt; return NJS_OK; } @@ -521,8 +510,8 @@ njs_shrink_elts(njs_flathsh_query_t *fhq, njs_flathsh_descr_t *h) elt_src = njs_hash_elts(h_src); for (i = 0, j = 0, elt = njs_hash_elts(h); i < h->elts_count; i++) { - if (elt_src->value != NULL) { - elt->value = elt_src->value; + if (elt_src->type != NJS_FREE_FLATHSH_ELEMENT) { + *elt = *elt_src; elt->key_hash = elt_src->key_hash; cell_num = elt_src->key_hash & new_hash_mask; @@ -572,7 +561,7 @@ njs_flathsh_delete(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) if (elt->key_hash == fhq->key_hash && fhq->proto->test(fhq, elt->value) == NJS_OK) { - fhq->value = elt->value; + fhq->value = elt; if (elt_prev != NULL) { elt_prev->next_elt = elt->next_elt; @@ -583,7 +572,7 @@ njs_flathsh_delete(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) h->elts_deleted_count++; - elt->value = NULL; + elt->type = NJS_FREE_FLATHSH_ELEMENT; /* Shrink elts if elts_deleted_count is eligible. */ @@ -637,7 +626,7 @@ njs_flathsh_unique_delete(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) elt = &elts[elt_num - 1]; if (elt->key_hash == fhq->key_hash) { - fhq->value = elt->value; + fhq->value = elt; if (elt_prev != NULL) { elt_prev->next_elt = elt->next_elt; @@ -648,7 +637,7 @@ njs_flathsh_unique_delete(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) h->elts_deleted_count++; - elt->value = NULL; + elt->type = NJS_FREE_FLATHSH_ELEMENT; /* Shrink elts if elts_deleted_count is eligible. */ @@ -695,7 +684,7 @@ njs_flathsh_each(const njs_flathsh_t *fh, njs_flathsh_each_t *fhe) while (fhe->cp < h->elts_count) { e = &elt[fhe->cp++]; - if (e->value != NULL) { + if (e->type != NJS_FREE_FLATHSH_ELEMENT) { return e; } } diff --git a/src/njs_flathsh.h b/src/njs_flathsh.h index 985bdab1..06fd00c0 100644 --- a/src/njs_flathsh.h +++ b/src/njs_flathsh.h @@ -13,9 +13,18 @@ typedef struct { typedef struct { - uint32_t next_elt; + /* next_elt + property descriptor : 32 bits */ + + uint32_t next_elt:26; + + uint32_t type:3; + uint32_t writable:1; + uint32_t enumerable:1; + uint32_t configurable:1; + uint32_t key_hash; - void *value; + + void *value[16 / sizeof(void *)]; } njs_flathsh_elt_t; @@ -72,7 +81,7 @@ struct njs_flathsh_query_s { #define njs_hash_elts(h) \ - ((njs_flathsh_elt_t *) ((char *) (h) + 16 /* njs_flathsh_descr_t size */)) + ((njs_flathsh_elt_t *) ((char *) (h) + sizeof(njs_flathsh_descr_t))) /* @@ -91,14 +100,13 @@ NJS_EXPORT njs_int_t njs_flathsh_unique_find(const njs_flathsh_t *fh, njs_flathsh_query_t *fhq); /* - * njs_flathsh_insert() adds a hash element. If the element is already - * present in flathsh and the fhq->replace flag is zero, then fhq->value - * is updated with the old element and NJS_DECLINED is returned. - * If the element is already present in flathsh and the fhq->replace flag - * is non-zero, then the old element is replaced with the new element. - * fhq->value is updated with the old element, and NJS_OK is returned. + * njs_flathsh_insert() adds a hash element. If the element is already present + * in flathsh and the fhq->replace flag is zero, then NJS_DECLINED is returned. + * If the element is already present in flathsh and the fhq->replace flag is + * non-zero, then the old element is replaced with the new element and NJS_OK is + * returned. * If the element is not present in flathsh, then it is inserted and - * NJS_OK is returned. The fhq->value is not changed. + * NJS_OK is returned. * On memory allocation failure NJS_ERROR is returned. * * The required njs_flathsh_query_t fields: key_hash, key, proto, replace, @@ -175,7 +183,7 @@ typedef struct njs_flathsh_proto_s njs_lvlhsh_proto_t; #define njs_lvlhsh_delete(lh, lhq) njs_flathsh_delete(lh, lhq) #define njs_lvlhsh_each_init(lhe, _proto) njs_flathsh_each_init(lhe, _proto) -njs_inline void * +njs_inline njs_flathsh_elt_t * njs_lvlhsh_each(const njs_flathsh_t *lh, njs_flathsh_each_t *lhe) { njs_flathsh_elt_t *e; @@ -185,7 +193,7 @@ njs_lvlhsh_each(const njs_flathsh_t *lh, njs_flathsh_each_t *lhe) return NULL; } - return e->value; + return e; } diff --git a/src/njs_function.c b/src/njs_function.c index 0840d437..09bc2ebb 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -126,11 +126,25 @@ njs_function_name_set(njs_vm_t *vm, njs_function_t *function, njs_object_prop_t *prop; njs_flathsh_query_t lhq; - prop = njs_object_prop_alloc(vm, name, 0); - if (njs_slow_path(prop == NULL)) { + lhq.key_hash = NJS_ATOM_STRING_name; + lhq.replace = 0; + lhq.pool = vm->mem_pool; + lhq.proto = &njs_object_hash_proto; + + ret = njs_flathsh_unique_insert(&function->object.hash, &lhq); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); return NJS_ERROR; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 0; + prop->configurable = 1; + prop->writable = 0; + prop->u.value = *name; + symbol = 0; if (njs_is_symbol(njs_prop_value(prop))) { @@ -172,20 +186,6 @@ njs_function_name_set(njs_vm_t *vm, njs_function_t *function, } } - prop->configurable = 1; - - lhq.value = prop; - lhq.key_hash = NJS_ATOM_STRING_name; - lhq.replace = 0; - lhq.pool = vm->mem_pool; - lhq.proto = &njs_object_hash_proto; - - ret = njs_flathsh_unique_insert(&function->object.hash, &lhq); - if (njs_slow_path(ret != NJS_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NJS_ERROR; - } - return NJS_OK; } @@ -327,12 +327,12 @@ njs_function_prototype_thrower(njs_vm_t *vm, njs_value_t *args, const njs_object_prop_init_t njs_arguments_object_instance_properties[] = { { - .atom_id = NJS_ATOM_STRING_callee, .desc = { + .atom_id = NJS_ATOM_STRING_callee, .type = NJS_ACCESSOR, .u.accessor = njs_accessor(njs_function_prototype_thrower, 0, njs_function_prototype_thrower, 0), - .writable = NJS_ATTRIBUTE_UNSET, + .writable = 0, }, }, }; @@ -394,6 +394,19 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function, lambda = function->u.lambda; + /* + * Lambda frame has the following layout: + * njs_frame_t | p0 , p2, ..., pn | v0, v1, ..., vn + * where: + * p0, p1, ..., pn - pointers to arguments and locals, + * v0, v1, ..., vn - values of arguments and locals. + * n - number of arguments + locals. + * + * Normally, the pointers point to the values directly after them, + * but if a value was captured as a closure by an inner function, + * pn points to a value allocated from the heap. + */ + args_count = njs_max(nargs, lambda->nargs); value_count = args_count + lambda->nlocal; @@ -523,7 +536,6 @@ njs_function_lambda_call(njs_vm_t *vm, njs_value_t *retval, void *promise_cap) njs_value_t *args, **local, *value; njs_value_t **cur_local, **cur_closures; njs_function_t *function; - njs_declaration_t *declr; njs_function_lambda_t *lambda; frame = (njs_frame_t *) vm->top_frame; @@ -582,29 +594,6 @@ njs_function_lambda_call(njs_vm_t *vm, njs_value_t *retval, void *promise_cap) vm->active_frame = frame; - /* Closures */ - - n = lambda->ndeclarations; - - while (n != 0) { - n--; - - declr = &lambda->declarations[n]; - value = njs_scope_value(vm, declr->index); - - *value = *declr->value; - - function = njs_function_value_copy(vm, value); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - - ret = njs_function_capture_closure(vm, function, function->u.lambda); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - } - ret = njs_vmcode_interpreter(vm, lambda->start, retval, promise_cap, NULL); /* Restore current level. */ @@ -699,11 +688,11 @@ njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *native) njs_int_t njs_function_frame_save(njs_vm_t *vm, njs_frame_t *frame, u_char *pc) { - size_t args_count, value_count, n; - njs_value_t *start, *end, *p, **new, *value, **local; - njs_function_t *function; + size_t args_count, value_count, n; + njs_value_t **map, *value, **current_map; + njs_function_t *function; + njs_native_frame_t *active, *native; njs_function_lambda_t *lambda; - njs_native_frame_t *active, *native; *frame = *vm->active_frame; @@ -721,34 +710,37 @@ njs_function_frame_save(njs_vm_t *vm, njs_frame_t *frame, u_char *pc) args_count = njs_max(native->nargs, lambda->nargs); value_count = args_count + lambda->nlocal; - new = (njs_value_t **) ((u_char *) native + NJS_FRAME_SIZE); - value = (njs_value_t *) (new + value_count); - - native->arguments = value; - native->local = new + njs_function_frame_args_count(active); - native->pc = pc; + /* + * We need to save the current frame state because it will be freed + * when the function returns. + * + * To detect whether a value is captured as a closure, + * we check whether the pointer is within the frame. In this case + * the pointer is copied as is because the value it points to + * is already allocated in the heap and will not be freed. + * See njs_function_capture_closure() and njs_function_lambda_frame() + * for details. + */ - start = njs_function_frame_values(active, &end); - p = native->arguments; + map = (njs_value_t **) ((u_char *) native + NJS_FRAME_SIZE); + value = (njs_value_t *) (map + value_count); - while (start < end) { - njs_value_assign(p, start++); - *new++ = p++; - } + current_map = (njs_value_t **) ((u_char *) active + NJS_FRAME_SIZE); - /* Move all arguments. */ + for (n = 0; n < value_count; n++) { + if (njs_is_value_allocated_on_frame(active, current_map[n])) { + map[n] = &value[n]; + njs_value_assign(&value[n], current_map[n]); - p = native->arguments; - local = native->local + 1 /* this */; - - for (n = 0; n < function->args_count; n++) { - if (!njs_is_valid(p)) { - njs_set_undefined(p); + } else { + map[n] = current_map[n]; } - - *local++ = p++; } + native->arguments = value; + native->local = map + args_count; + native->pc = pc; + return NJS_OK; } @@ -770,7 +762,6 @@ njs_int_t njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, njs_function_lambda_t *lambda) { - void *start, *end; uint32_t n; njs_value_t *value, **closure; njs_native_frame_t *frame; @@ -785,9 +776,6 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, frame = frame->previous; } - start = frame; - end = frame->free; - closure = njs_function_closures(function); n = lambda->nclosures; @@ -796,7 +784,7 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, value = njs_scope_value(vm, lambda->closures[n]); - if (start <= (void *) value && (void *) value < end) { + if (njs_is_value_allocated_on_frame(frame, value)) { value = njs_scope_value_clone(vm, lambda->closures[n], value); if (njs_slow_path(value == NULL)) { return NJS_ERROR; @@ -812,14 +800,14 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, njs_inline njs_value_t * -njs_function_closure_value(njs_vm_t *vm, njs_value_t **scope, njs_index_t index, - void *start, void *end) +njs_function_closure_value(njs_vm_t *vm, njs_native_frame_t *frame, + njs_value_t **scope, njs_index_t index) { njs_value_t *value, *newval; value = scope[njs_scope_index_value(index)]; - if (start <= (void *) value && end > (void *) value) { + if (njs_is_value_allocated_on_frame(frame, value)) { newval = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); if (njs_slow_path(newval == NULL)) { njs_memory_error(vm); @@ -839,7 +827,6 @@ njs_function_closure_value(njs_vm_t *vm, njs_value_t **scope, njs_index_t index, njs_int_t njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function) { - void *start, *end; uint32_t n; njs_value_t *value, **refs, **global; njs_index_t *indexes, index; @@ -858,9 +845,6 @@ njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function) native = native->previous; } - start = native; - end = native->free; - indexes = lambda->closures; refs = njs_function_closures(function); @@ -875,12 +859,12 @@ njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function) switch (njs_scope_index_type(index)) { case NJS_LEVEL_LOCAL: - value = njs_function_closure_value(vm, native->local, index, - start, end); + value = njs_function_closure_value(vm, native, native->local, + index); break; case NJS_LEVEL_GLOBAL: - value = njs_function_closure_value(vm, global, index, start, end); + value = njs_function_closure_value(vm, native, global, index); break; default: @@ -910,14 +894,6 @@ njs_function_property_prototype_set(njs_vm_t *vm, njs_flathsh_t *hash, njs_object_prop_t *prop; njs_flathsh_query_t lhq; - prop = njs_object_prop_alloc(vm, prototype, 0); - if (njs_slow_path(prop == NULL)) { - return NULL; - } - - prop->writable = 1; - - lhq.value = prop; lhq.key_hash = NJS_ATOM_STRING_prototype; lhq.replace = 1; lhq.pool = vm->mem_pool; @@ -925,13 +901,21 @@ njs_function_property_prototype_set(njs_vm_t *vm, njs_flathsh_t *hash, ret = njs_flathsh_unique_insert(hash, &lhq); - if (njs_fast_path(ret == NJS_OK)) { - return njs_prop_value(prop); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NULL; } - njs_internal_error(vm, "lvlhsh insert failed"); + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 0; + prop->configurable = 0; + prop->writable = 1; + prop->u.value = *prototype; + + return njs_prop_value(prop); - return NULL; } @@ -950,9 +934,8 @@ njs_function_prototype_create(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_value_t *proto, proto_value, *cons; - njs_object_t *prototype; - njs_function_t *function; + njs_value_t *proto, proto_value, *cons; + njs_object_t *prototype; if (setval == NULL) { prototype = njs_object_alloc(vm); @@ -965,11 +948,6 @@ njs_function_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop, setval = &proto_value; } - function = njs_function_value_copy(vm, value); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - proto = njs_function_property_prototype_set(vm, njs_object_hash(value), setval); if (njs_slow_path(proto == NULL)) { @@ -1049,7 +1027,7 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } - njs_chb_append_literal(&chain, "){"); + njs_chb_append_literal(&chain, "\n){\n"); if (nargs > 1) { ret = njs_value_to_chain(vm, &chain, njs_argument(args, nargs - 1)); @@ -1058,7 +1036,7 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } - njs_chb_append_literal(&chain, "})"); + njs_chb_append_literal(&chain, "\n})"); ret = njs_chb_join(&chain, &str); if (njs_slow_path(ret != NJS_OK)) { @@ -1125,7 +1103,15 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_chb_destroy(&chain); - lambda = ((njs_vmcode_function_t *) generator.code_start)->lambda; + if ((code->end - code->start) + != (sizeof(njs_vmcode_function_t) + sizeof(njs_vmcode_return_t)) + || ((njs_vmcode_generic_t *) code->start)->code != NJS_VMCODE_FUNCTION) + { + njs_syntax_error(vm, "single function literal required"); + return NJS_ERROR; + } + + lambda = ((njs_vmcode_function_t *) code->start)->lambda; function = njs_function_alloc(vm, lambda, (njs_bool_t) async); if (njs_slow_path(function == NULL)) { @@ -1446,23 +1432,23 @@ static const njs_object_prop_init_t njs_function_prototype_properties[] = NJS_DECLARE_PROP_NATIVE(STRING_bind, njs_function_prototype_bind, 1, 0), { - .atom_id = NJS_ATOM_STRING_caller, .desc = { + .atom_id = NJS_ATOM_STRING_caller, .type = NJS_ACCESSOR, .u.accessor = njs_accessor(njs_function_prototype_thrower, 0, njs_function_prototype_thrower, 0), - .writable = NJS_ATTRIBUTE_UNSET, + .writable = 0, .configurable = 1, }, }, { - .atom_id = NJS_ATOM_STRING_arguments, .desc = { + .atom_id = NJS_ATOM_STRING_arguments, .type = NJS_ACCESSOR, .u.accessor = njs_accessor(njs_function_prototype_thrower, 0, njs_function_prototype_thrower, 0), - .writable = NJS_ATTRIBUTE_UNSET, + .writable = 0, .configurable = 1, }, }, diff --git a/src/njs_function.h b/src/njs_function.h index 6516ff78..1b5de544 100644 --- a/src/njs_function.h +++ b/src/njs_function.h @@ -13,9 +13,6 @@ struct njs_function_lambda_s { uint32_t nclosures; uint32_t nlocal; - njs_declaration_t *declarations; - uint32_t ndeclarations; - njs_index_t self; uint32_t nargs; @@ -205,29 +202,15 @@ njs_function_frame_size(njs_native_frame_t *frame) } -njs_inline size_t -njs_function_frame_args_count(njs_native_frame_t *frame) -{ - uintptr_t start; - - start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE); - - return ((uintptr_t) frame->local - start) / sizeof(njs_value_t *); -} - - -njs_inline njs_value_t * -njs_function_frame_values(njs_native_frame_t *frame, njs_value_t **end) +njs_inline njs_bool_t +njs_is_value_allocated_on_frame(njs_native_frame_t *frame, njs_value_t *value) { - size_t count; - uintptr_t start; - - start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE); - count = ((uintptr_t) frame->arguments - start) / sizeof(njs_value_t *); + void *start, *end; - *end = frame->arguments + count; + start = frame; + end = frame->free; - return frame->arguments; + return start <= (void *) value && (void *) value < end; } diff --git a/src/njs_generator.c b/src/njs_generator.c index a6209cce..bb443017 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -890,11 +890,10 @@ static njs_int_t njs_generate_name(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; - njs_variable_t *var; - njs_parser_scope_t *scope; - njs_vmcode_variable_t *variable; - njs_vmcode_function_copy_t *copy; + njs_int_t ret; + njs_variable_t *var; + njs_parser_scope_t *scope; + njs_vmcode_variable_t *variable; var = njs_variable_reference(vm, node); if (njs_slow_path(var == NULL)) { @@ -906,13 +905,6 @@ njs_generate_name(njs_vm_t *vm, njs_generator_t *generator, return njs_generator_stack_pop(vm, generator, NULL); } - if (var->function && var->type == NJS_VARIABLE_FUNCTION) { - njs_generate_code(generator, njs_vmcode_function_copy_t, copy, - NJS_VMCODE_FUNCTION_COPY, node); - copy->function = &var->value; - copy->retval = node->index; - } - if (var->init) { return njs_generator_stack_pop(vm, generator, NULL); } @@ -935,10 +927,9 @@ static njs_int_t njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar) { - njs_variable_t *var; - njs_parser_scope_t *scope; - njs_vmcode_variable_t *variable; - njs_vmcode_function_copy_t *copy; + njs_variable_t *var; + njs_parser_scope_t *scope; + njs_vmcode_variable_t *variable; var = njs_variable_reference(vm, node); @@ -958,13 +949,6 @@ njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, } } - if (var->function && var->type == NJS_VARIABLE_FUNCTION) { - njs_generate_code(generator, njs_vmcode_function_copy_t, copy, - NJS_VMCODE_FUNCTION_COPY, node); - copy->function = &var->value; - copy->retval = node->index; - } - if (var->init) { return NJS_OK; } @@ -4293,7 +4277,6 @@ njs_generate_function_scope(njs_vm_t *vm, njs_generator_t *prev, const njs_str_t *name) { njs_int_t ret; - njs_arr_t *arr; njs_uint_t depth; njs_vm_code_t *code; njs_generator_t generator; @@ -4327,10 +4310,6 @@ njs_generate_function_scope(njs_vm_t *vm, njs_generator_t *prev, lambda->nclosures = generator.closures->items; lambda->nlocal = node->scope->items; - arr = node->scope->declarations; - lambda->declarations = (arr != NULL) ? arr->start : NULL; - lambda->ndeclarations = (arr != NULL) ? arr->items : 0; - return NJS_OK; } @@ -4339,11 +4318,15 @@ njs_vm_code_t * njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator, njs_parser_scope_t *scope, const njs_str_t *name) { - u_char *p; - int64_t nargs; - njs_int_t ret; - njs_uint_t index; - njs_vm_code_t *code; + u_char *p, *code_start; + size_t code_size, prelude; + int64_t nargs; + njs_int_t ret; + njs_arr_t *arr; + njs_uint_t index, n; + njs_vm_code_t *code; + njs_declaration_t *declr; + njs_vmcode_function_t *fun; generator->code_size = 128; @@ -4414,6 +4397,45 @@ njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator, } while (generator->state != NULL); + arr = scope->declarations; + scope->declarations = NULL; + + if (arr != NULL && arr->items > 0) { + declr = arr->start; + prelude = sizeof(njs_vmcode_function_t) * arr->items; + code_size = generator->code_end - generator->code_start; + + for (n = 0; n < arr->items; n++) { + fun = (njs_vmcode_function_t *) njs_generate_reserve(vm, + generator, sizeof(njs_vmcode_function_t)); + if (njs_slow_path(fun == NULL)) { + njs_memory_error(vm); + return NULL; + } + + generator->code_end += sizeof(njs_vmcode_function_t); + fun->code = NJS_VMCODE_FUNCTION; + + fun->lambda = declr[n].lambda; + fun->async = declr[n].async; + fun->retval = declr[n].index; + } + + code_start = njs_mp_alloc(vm->mem_pool, code_size + prelude); + if (njs_slow_path(code_start == NULL)) { + njs_memory_error(vm); + return NULL; + } + + memcpy(code_start, &generator->code_start[code_size], prelude); + memcpy(&code_start[prelude], generator->code_start, code_size); + + njs_mp_free(vm->mem_pool, generator->code_start); + + generator->code_start = code_start; + generator->code_end = code_start + code_size + prelude; + } + code = njs_arr_item(vm->codes, index); code->start = generator->code_start; code->end = generator->code_end; @@ -5491,7 +5513,7 @@ njs_generate_reference_error(njs_vm_t *vm, njs_generator_t *generator, ref_err->type = NJS_OBJ_TYPE_REF_ERROR; - njs_lexer_entry(vm, node->u.reference.atom_id, &entry); + njs_atom_string_get(vm, node->u.reference.atom_id, &entry); return njs_name_copy(vm, &ref_err->u.name, &entry); } diff --git a/src/njs_json.c b/src/njs_json.c index e79b1eb4..49c2b7e7 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -394,12 +394,6 @@ njs_json_parse_object(njs_json_parse_ctx_t *ctx, njs_value_t *value, return NULL; } - prop = njs_object_prop_alloc(ctx->vm, &prop_value, 1); - if (njs_slow_path(prop == NULL)) { - goto memory_error; - } - - lhq.value = prop; lhq.key_hash = prop_name.atom_id; lhq.replace = 1; lhq.pool = ctx->pool; @@ -411,6 +405,14 @@ njs_json_parse_object(njs_json_parse_ctx_t *ctx, njs_value_t *value, return NULL; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = prop_value; + p = njs_json_skip_space(p, ctx->end); if (njs_slow_path(p == ctx->end)) { goto error_end; @@ -1609,12 +1611,6 @@ njs_json_wrap_value(njs_vm_t *vm, njs_value_t *wrapper, wrapper->type = NJS_OBJECT; wrapper->data.truth = 1; - prop = njs_object_prop_alloc(vm, value, 1); - if (njs_slow_path(prop == NULL)) { - return NULL; - } - - lhq.value = prop; lhq.key_hash = NJS_ATOM_STRING_empty; lhq.replace = 0; lhq.pool = vm->mem_pool; @@ -1625,6 +1621,14 @@ njs_json_wrap_value(njs_vm_t *vm, njs_value_t *wrapper, return NULL; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = *value; + return wrapper->data.u.object; } diff --git a/src/njs_lexer.h b/src/njs_lexer.h index 42f612a6..3728e253 100644 --- a/src/njs_lexer.h +++ b/src/njs_lexer.h @@ -288,16 +288,6 @@ const njs_lexer_keyword_entry_t *njs_lexer_keyword(const u_char *key, njs_int_t njs_lexer_keywords(njs_arr_t *array); -njs_inline void -njs_lexer_entry(njs_vm_t *vm, uintptr_t atom_id, njs_str_t *entry) -{ - njs_value_t value; - - njs_atom_to_value(vm, &value, atom_id); - njs_string_get(vm, &value, entry); -} - - njs_inline njs_bool_t njs_lexer_token_is_keyword(njs_lexer_token_t *token) { diff --git a/src/njs_module.c b/src/njs_module.c index 5af174f3..7d58972c 100644 --- a/src/njs_module.c +++ b/src/njs_module.c @@ -13,7 +13,7 @@ njs_module_hash_test(njs_lvlhsh_query_t *lhq, void *data) { njs_mod_t *module; - module = data; + module = *(njs_mod_t **) data; if (njs_strstr_eq(&lhq->key, &module->name)) { return NJS_OK; @@ -39,6 +39,7 @@ njs_module_find(njs_vm_t *vm, njs_str_t *name, njs_bool_t shared) njs_int_t ret; njs_mod_t *shrd, *module; njs_object_t *object; + njs_object_prop_t *prop; njs_lvlhsh_query_t lhq; lhq.key = *name; @@ -46,11 +47,11 @@ njs_module_find(njs_vm_t *vm, njs_str_t *name, njs_bool_t shared) lhq.proto = &njs_modules_hash_proto; if (njs_lvlhsh_find(&vm->modules_hash, &lhq) == NJS_OK) { - return lhq.value; + return njs_prop_module(lhq.value); } if (njs_lvlhsh_find(&vm->shared->modules_hash, &lhq) == NJS_OK) { - shrd = lhq.value; + shrd = njs_prop_module(lhq.value); if (shared) { return shrd; @@ -70,13 +71,19 @@ njs_module_find(njs_vm_t *vm, njs_str_t *name, njs_bool_t shared) } lhq.replace = 0; - lhq.value = module; lhq.pool = vm->mem_pool; ret = njs_lvlhsh_insert(&vm->modules_hash, &lhq); - if (njs_fast_path(ret == NJS_OK)) { - return module; + if (njs_slow_path(ret != NJS_OK)) { + return NULL; } + + prop = lhq.value; + + prop->u.mod = module; + + return module; + } return NULL; @@ -88,6 +95,7 @@ njs_module_add(njs_vm_t *vm, njs_str_t *name, njs_value_t *value) { njs_int_t ret; njs_mod_t *module; + njs_object_prop_t *prop; njs_lvlhsh_query_t lhq; module = njs_mp_zalloc(vm->mem_pool, sizeof(njs_mod_t)); @@ -105,7 +113,6 @@ njs_module_add(njs_vm_t *vm, njs_str_t *name, njs_value_t *value) lhq.replace = 0; lhq.key = *name; lhq.key_hash = njs_djb_hash(name->start, name->length); - lhq.value = module; lhq.pool = vm->mem_pool; lhq.proto = &njs_modules_hash_proto; @@ -115,6 +122,10 @@ njs_module_add(njs_vm_t *vm, njs_str_t *name, njs_value_t *value) return NULL; } + prop = lhq.value; + + prop->u.mod = module; + if (value != NULL) { njs_value_assign(&module->value, value); module->function.native = 1; diff --git a/src/njs_object.c b/src/njs_object.c index 63273628..0c592176 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -157,6 +157,7 @@ njs_object_hash_create(njs_vm_t *vm, njs_flathsh_t *hash, const njs_object_prop_init_t *prop, njs_uint_t n) { njs_int_t ret; + njs_object_prop_t *obj_prop; njs_flathsh_query_t lhq; lhq.replace = 0; @@ -164,7 +165,7 @@ njs_object_hash_create(njs_vm_t *vm, njs_flathsh_t *hash, lhq.pool = vm->mem_pool; while (n != 0) { - lhq.key_hash = prop->atom_id; + lhq.key_hash = prop->desc.atom_id; lhq.value = (void *) prop; ret = njs_flathsh_unique_insert(hash, &lhq); @@ -173,6 +174,14 @@ njs_object_hash_create(njs_vm_t *vm, njs_flathsh_t *hash, return NJS_ERROR; } + obj_prop = lhq.value; + + obj_prop->type = prop->desc.type; + obj_prop->enumerable = prop->desc.enumerable; + obj_prop->configurable = prop->desc.configurable; + obj_prop->writable = prop->desc.writable; + obj_prop->u.value = prop->desc.u.value; + prop++; n--; } @@ -939,7 +948,7 @@ njs_get_own_ordered_keys(njs_vm_t *vm, const njs_object_t *object, break; } - prop = elt->value; + prop = (njs_object_prop_t *) elt; ret = njs_atom_to_value(vm, &prop_name, elt->key_hash); if (ret != NJS_OK) { @@ -980,7 +989,7 @@ njs_get_own_ordered_keys(njs_vm_t *vm, const njs_object_t *object, } else { - if (!(((njs_object_prop_t *)(lhq.value))->enumerable + if (!(((njs_object_prop_t *) (lhq.value))->enumerable || !(flags & NJS_ENUM_ENUMERABLE_ONLY))) { continue; @@ -993,13 +1002,7 @@ njs_get_own_ordered_keys(njs_vm_t *vm, const njs_object_t *object, njs_object_prop_t *hash_prop = lhq.value; - /* select names of prop which are not deleted and - * not deleted and created again i.e., - * they are replaced shared hash props - */ - if (hash_prop->type != NJS_WHITEOUT && - !(hash_prop->enum_in_object_hash)) - { + if (hash_prop->type != NJS_WHITEOUT) { njs_process_prop(vm, &prop_name, flags, items_string, items_symbol); } @@ -1018,7 +1021,7 @@ local_hash: break; } - prop = elt->value; + prop = (njs_object_prop_t *) elt; ret = njs_atom_to_value(vm, &prop_name, elt->key_hash); if (ret != NJS_OK) { @@ -1060,8 +1063,7 @@ local_hash: } else { /* prop is: in_hash && in_shared_hash */ - /* select names of not deleted and created again */ - if (prop->enum_in_object_hash) { + if (prop->type == NJS_WHITEOUT) { njs_process_prop(vm, &prop_name, flags, items_string, items_symbol); } @@ -1222,7 +1224,7 @@ njs_object_copy_shared_hash(njs_vm_t *vm, njs_object_t *object) njs_int_t ret; njs_value_t prop_name; njs_flathsh_t new_hash, *shared_hash; - njs_object_prop_t *prop; + njs_object_prop_t *prop, *obj_prop; njs_flathsh_elt_t *elt; njs_flathsh_each_t fhe; njs_flathsh_query_t fhq; @@ -1242,7 +1244,7 @@ njs_object_copy_shared_hash(njs_vm_t *vm, njs_object_t *object) break; } - prop = elt->value; + prop = (njs_object_prop_t *) elt; ret = njs_atom_to_value(vm, &prop_name, elt->key_hash); if (ret != NJS_OK) { @@ -1258,13 +1260,19 @@ njs_object_copy_shared_hash(njs_vm_t *vm, njs_object_t *object) fhq.key_hash = elt->key_hash; } - fhq.value = prop; - ret = njs_flathsh_unique_insert(&new_hash, &fhq); if (njs_slow_path(ret != NJS_OK)) { njs_internal_error(vm, "flathsh insert failed"); return NJS_ERROR; } + + obj_prop = fhq.value; + + obj_prop->type = prop->type; + obj_prop->enumerable = prop->enumerable; + obj_prop->configurable = prop->configurable; + obj_prop->writable = prop->writable; + obj_prop->u.value = prop->u.value; } object->shared_hash = new_hash; @@ -1281,7 +1289,7 @@ njs_object_make_shared(njs_vm_t *vm, njs_object_t *object) njs_object_t **start; njs_value_t value, *key; njs_traverse_t *s; - njs_object_prop_t *prop; + njs_object_prop_t *prop, *obj_prop; njs_property_query_t pq; njs_traverse_t state[NJS_TRAVERSE_MAX_DEPTH]; @@ -1360,6 +1368,14 @@ njs_object_make_shared(njs_vm_t *vm, njs_object_t *object) return NJS_ERROR; } + obj_prop = pq.lhq.value; + + obj_prop->type = prop->type; + obj_prop->enumerable = prop->enumerable; + obj_prop->configurable = prop->configurable; + obj_prop->writable = prop->writable; + obj_prop->u.value = prop->u.value; + njs_value_assign(&value, njs_prop_value(prop)); if (njs_is_object(&value) @@ -1659,7 +1675,7 @@ njs_object_get_own_property_descriptors(njs_vm_t *vm, njs_value_t *args, njs_array_t *names; njs_value_t descriptor, *value, *key; njs_object_t *descriptors; - njs_object_prop_t *pr; + njs_object_prop_t *prop; njs_flathsh_query_t lhq; value = njs_arg(args, nargs, 1); @@ -1697,20 +1713,22 @@ njs_object_get_own_property_descriptors(njs_vm_t *vm, njs_value_t *args, goto done; } - pr = njs_object_prop_alloc(vm, &descriptor, 1); - if (njs_slow_path(pr == NULL)) { - ret = NJS_ERROR; - goto done; - } - lhq.key_hash = key->atom_id; - lhq.value = pr; ret = njs_flathsh_unique_insert(&descriptors->hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { njs_internal_error(vm, "lvlhsh insert failed"); goto done; } + + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + + prop->u.value = descriptor; } ret = NJS_OK; @@ -1889,7 +1907,7 @@ njs_object_set_integrity_level(njs_vm_t *vm, njs_value_t *args, break; } - prop = elt->value; + prop = (njs_object_prop_t *) elt; if (level == NJS_OBJECT_INTEGRITY_FROZEN && !njs_is_accessor_descriptor(prop)) @@ -1949,7 +1967,7 @@ njs_object_test_integrity_level(njs_vm_t *vm, njs_value_t *args, break; } - prop = elt->value; + prop = (njs_object_prop_t *) elt; if (prop->configurable) { goto done; @@ -2172,15 +2190,6 @@ njs_property_prototype_create(njs_vm_t *vm, njs_flathsh_t *hash, njs_object_prop_t *prop; njs_flathsh_query_t lhq; - prop = njs_object_prop_alloc(vm, &njs_value_undefined, 0); - if (njs_slow_path(prop == NULL)) { - return NULL; - } - - lhq.value = prop; - - njs_set_type_object(njs_prop_value(prop), prototype, prototype->type); - lhq.key_hash = NJS_ATOM_STRING_prototype; lhq.replace = 1; @@ -2189,13 +2198,21 @@ njs_property_prototype_create(njs_vm_t *vm, njs_flathsh_t *hash, ret = njs_flathsh_unique_insert(hash, &lhq); - if (njs_fast_path(ret == NJS_OK)) { - return njs_prop_value(prop); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NULL; } - njs_internal_error(vm, "lvlhsh insert failed"); + prop = lhq.value; - return NULL; + prop->type = NJS_PROPERTY; + prop->enumerable = 0; + prop->configurable = 0; + prop->writable = 0; + + njs_set_type_object(njs_prop_value(prop), prototype, prototype->type); + + return njs_prop_value(prop); } @@ -2433,16 +2450,6 @@ njs_property_constructor_set(njs_vm_t *vm, njs_flathsh_t *hash, njs_object_prop_t *prop; njs_flathsh_query_t lhq; - prop = njs_object_prop_alloc(vm, constructor, 1); - if (njs_slow_path(prop == NULL)) { - return NULL; - } - - njs_value_assign(njs_prop_value(prop), constructor); - prop->enumerable = 0; - - lhq.value = prop; - lhq.key_hash = NJS_ATOM_STRING_constructor; lhq.replace = 1; @@ -2450,13 +2457,21 @@ njs_property_constructor_set(njs_vm_t *vm, njs_flathsh_t *hash, lhq.proto = &njs_object_hash_proto; ret = njs_flathsh_unique_insert(hash, &lhq); - if (njs_fast_path(ret == NJS_OK)) { - return njs_prop_value(prop); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert/replace failed"); + return NULL; } - njs_internal_error(vm, "lvlhsh insert/replace failed"); + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 0; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = *constructor; + + return njs_prop_value(prop); - return NULL; } diff --git a/src/njs_object.h b/src/njs_object.h index 6b2438df..dd5334d2 100644 --- a/src/njs_object.h +++ b/src/njs_object.h @@ -101,8 +101,8 @@ njs_int_t njs_object_length(njs_vm_t *vm, njs_value_t *value, int64_t *dst); njs_int_t njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq, njs_object_t *proto); -njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, - const njs_value_t *value, uint8_t attributes); +void njs_object_prop_init(njs_object_prop_t *prop, njs_object_prop_type_t type, + uint8_t attributes); njs_int_t njs_object_property(njs_vm_t *vm, njs_object_t *object, njs_flathsh_query_t *lhq, njs_value_t *retval); njs_object_prop_t *njs_object_property_add(njs_vm_t *vm, njs_value_t *object, @@ -114,7 +114,7 @@ njs_int_t njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest, njs_int_t njs_object_get_prototype_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); const char *njs_prop_type_string(njs_object_prop_type_t type); -njs_int_t njs_object_prop_init(njs_vm_t *vm, const njs_object_init_t* init, +njs_int_t njs_object_props_init(njs_vm_t *vm, const njs_object_init_t* init, njs_object_prop_t *base, uint32_t atom_id, njs_value_t *value, njs_value_t *retval); @@ -122,10 +122,11 @@ njs_int_t njs_object_prop_init(njs_vm_t *vm, const njs_object_init_t* init, njs_inline njs_bool_t njs_is_data_descriptor(njs_object_prop_t *prop) { - return prop->writable != NJS_ATTRIBUTE_UNSET - || (prop->type != NJS_ACCESSOR && njs_is_valid(njs_prop_value(prop))) - || prop->type == NJS_PROPERTY_HANDLER; - + return (prop->type == NJS_PROPERTY && njs_is_valid(njs_prop_value(prop))) + || prop->type == NJS_PROPERTY_HANDLER + || prop->type == NJS_PROPERTY_REF + || prop->type == NJS_PROPERTY_PLACE_REF + || prop->type == NJS_PROPERTY_TYPED_ARRAY_REF; } @@ -136,13 +137,6 @@ njs_is_accessor_descriptor(njs_object_prop_t *prop) } -njs_inline njs_bool_t -njs_is_generic_descriptor(njs_object_prop_t *prop) -{ - return !njs_is_data_descriptor(prop) && !njs_is_accessor_descriptor(prop); -} - - njs_inline njs_int_t njs_primitive_value_to_key(njs_vm_t *vm, njs_value_t *dst, const njs_value_t *src) @@ -241,18 +235,17 @@ njs_key_string_get(njs_vm_t *vm, njs_value_t *key, njs_str_t *str) } -njs_inline njs_int_t +njs_inline void njs_atom_string_get(njs_vm_t *vm, uint32_t atom_id, njs_str_t *str) { njs_value_t value; if (njs_atom_to_value(vm, &value, atom_id) != NJS_OK) { - return NJS_ERROR; + str->start = (u_char *) "unknown"; + str->length = njs_length("unknown"); } njs_key_string_get(vm, &value, str); - - return NJS_OK; } diff --git a/src/njs_object_prop.c b/src/njs_object_prop.c index d7c55d3a..6b93960c 100644 --- a/src/njs_object_prop.c +++ b/src/njs_object_prop.c @@ -8,57 +8,20 @@ #include <njs_main.h> -static njs_object_prop_t *njs_object_prop_alloc2(njs_vm_t *vm, - njs_object_prop_type_t type, unsigned flags); static njs_object_prop_t *njs_descriptor_prop(njs_vm_t *vm, - const njs_value_t *desc); + const njs_value_t *desc, njs_object_prop_t *prop, + uint32_t *unset_enumerable, uint32_t *unset_configuarble, + uint32_t *enum_writable); -njs_object_prop_t * -njs_object_prop_alloc(njs_vm_t *vm, - const njs_value_t *value, uint8_t attributes) -{ - unsigned flags; - njs_object_prop_t *prop; - - switch (attributes) { - case NJS_ATTRIBUTE_FALSE: - case NJS_ATTRIBUTE_TRUE: - flags = attributes ? NJS_OBJECT_PROP_VALUE_ECW : 0; - break; - - case NJS_ATTRIBUTE_UNSET: - default: - flags = NJS_OBJECT_PROP_UNSET; - break; - } - - prop = njs_object_prop_alloc2(vm, NJS_PROPERTY, flags); - if (njs_slow_path(prop == NULL)) { - return NULL; - } - - njs_value_assign(njs_prop_value(prop), value); - - return prop; -} - - -static njs_object_prop_t * -njs_object_prop_alloc2(njs_vm_t *vm, - njs_object_prop_type_t type, unsigned flags) +void +njs_object_prop_init(njs_object_prop_t *prop, njs_object_prop_type_t type, + uint8_t flags) { - njs_object_prop_t *prop; - - prop = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), - sizeof(njs_object_prop_t)); - if (njs_slow_path(prop == NULL)) { - njs_memory_error(vm); - return NULL; - } + prop->next_elt = 0; + prop->atom_id = 0; prop->type = type; - prop->enum_in_object_hash = 0; if (flags != NJS_OBJECT_PROP_UNSET) { prop->enumerable = !!(flags & NJS_OBJECT_PROP_ENUMERABLE); @@ -68,16 +31,14 @@ njs_object_prop_alloc2(njs_vm_t *vm, prop->writable = !!(flags & NJS_OBJECT_PROP_WRITABLE); } else { - prop->writable = NJS_ATTRIBUTE_UNSET; + prop->writable = 0; } } else { - prop->enumerable = NJS_ATTRIBUTE_UNSET; - prop->configurable = NJS_ATTRIBUTE_UNSET; - prop->writable = NJS_ATTRIBUTE_UNSET; + prop->enumerable = 0; + prop->configurable = 0; + prop->writable = 0; } - - return prop; } @@ -142,12 +103,6 @@ njs_object_property_add(njs_vm_t *vm, njs_value_t *object, unsigned atom_id, njs_object_prop_t *prop; njs_flathsh_query_t lhq; - prop = njs_object_prop_alloc(vm, &njs_value_invalid, 1); - if (njs_slow_path(prop == NULL)) { - return NULL; - } - - lhq.value = prop; lhq.key_hash = atom_id; lhq.replace = replace; lhq.pool = vm->mem_pool; @@ -159,6 +114,14 @@ njs_object_property_add(njs_vm_t *vm, njs_value_t *object, unsigned atom_id, return NULL; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = njs_value_invalid; + return prop; } @@ -170,15 +133,21 @@ njs_int_t njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, unsigned atom_id, njs_value_t *value, unsigned flags) { - uint32_t length, index; + uint32_t length, index, set_enumerable, set_configurable, + set_writable; njs_int_t ret; njs_array_t *array; njs_value_t key, retval; - njs_object_prop_t *prop, *prev; + njs_object_prop_t _prop; + njs_object_prop_t *prop = &_prop, *prev, *obj_prop; njs_property_query_t pq; again: + set_enumerable = 1; + set_configurable = 1; + set_writable = 1; + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 1); ret = (flags & NJS_OBJECT_PROP_CREATE) @@ -191,7 +160,8 @@ again: switch (njs_prop_type(flags)) { case NJS_OBJECT_PROP_DESCRIPTOR: - prop = njs_descriptor_prop(vm, value); + prop = njs_descriptor_prop(vm, value, prop, &set_enumerable, + &set_configurable, &set_writable); if (njs_slow_path(prop == NULL)) { return NJS_ERROR; } @@ -212,12 +182,8 @@ again: } } - prop = njs_object_prop_alloc2(vm, NJS_PROPERTY, - flags & NJS_OBJECT_PROP_VALUE_ECW); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - + njs_object_prop_init(prop, NJS_PROPERTY, + flags & NJS_OBJECT_PROP_VALUE_ECW); njs_value_assign(njs_prop_value(prop), value); break; @@ -226,11 +192,9 @@ again: default: njs_assert(njs_is_function(value)); - prop = njs_object_prop_alloc2(vm, NJS_ACCESSOR, - NJS_OBJECT_PROP_VALUE_EC); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } + njs_object_prop_init(prop, NJS_ACCESSOR, NJS_OBJECT_PROP_VALUE_EC); + + set_writable = 0; if (njs_prop_type(flags) == NJS_OBJECT_PROP_GETTER) { njs_prop_getter(prop) = njs_function(value); @@ -283,7 +247,7 @@ set_prop: } } else { - if (prop->writable == NJS_ATTRIBUTE_UNSET) { + if (!set_writable) { prop->writable = 0; } @@ -292,11 +256,11 @@ set_prop: } } - if (prop->enumerable == NJS_ATTRIBUTE_UNSET) { + if (!set_enumerable) { prop->enumerable = 0; } - if (prop->configurable == NJS_ATTRIBUTE_UNSET) { + if (!set_configurable) { prop->configurable = 0; } @@ -305,6 +269,8 @@ set_prop: if (njs_slow_path(prev->type == NJS_WHITEOUT)) { /* Previously deleted property. */ + prop->atom_id = prev->atom_id; + prop->next_elt = prev->next_elt; *prev = *prop; } @@ -312,7 +278,6 @@ set_prop: pq.lhq.key_hash = atom_id; pq.lhq.proto = &njs_object_hash_proto; - pq.lhq.value = prop; pq.lhq.replace = 0; pq.lhq.pool = vm->mem_pool; @@ -321,6 +286,13 @@ set_prop: njs_internal_error(vm, "lvlhsh insert failed"); return NJS_ERROR; } + + obj_prop = pq.lhq.value; + obj_prop->enumerable = prop->enumerable; + obj_prop->configurable = prop->configurable; + obj_prop->writable = prop->writable; + obj_prop->type = prop->type; + obj_prop->u.value = prop->u.value; } return NJS_OK; @@ -340,9 +312,9 @@ set_prop: case NJS_PROPERTY_PLACE_REF: if (prev->type == NJS_PROPERTY_REF && !njs_is_accessor_descriptor(prop) - && prop->configurable != NJS_ATTRIBUTE_FALSE - && prop->enumerable != NJS_ATTRIBUTE_FALSE - && prop->writable != NJS_ATTRIBUTE_FALSE) + && (!set_configurable || prop->configurable) + && (!set_enumerable || prop->enumerable) + && (!set_writable || prop->writable)) { if (njs_is_valid(njs_prop_value(prop))) { njs_value_assign(njs_prop_ref(prev), njs_prop_value(prop)); @@ -373,9 +345,9 @@ set_prop: goto exception; } - if (prop->configurable == NJS_ATTRIBUTE_TRUE || - prop->enumerable == NJS_ATTRIBUTE_FALSE || - prop->writable == NJS_ATTRIBUTE_FALSE) + if ((set_configurable && prop->configurable) + || (set_enumerable && !prop->enumerable) + || (set_writable && !prop->writable)) { goto exception; } @@ -401,22 +373,24 @@ set_prop: if (!prev->configurable) { - if (prop->configurable == NJS_ATTRIBUTE_TRUE) { + if (prop->configurable) { goto exception; } - if (prop->enumerable != NJS_ATTRIBUTE_UNSET - && prev->enumerable != prop->enumerable) - { + if (set_enumerable && prev->enumerable != prop->enumerable) { goto exception; } } - if (njs_is_generic_descriptor(prop)) { + if (!(set_writable || njs_is_data_descriptor(prop)) + && !njs_is_accessor_descriptor(prop)) + { goto done; } - if (njs_is_data_descriptor(prev) != njs_is_data_descriptor(prop)) { + if (njs_is_data_descriptor(prev) + != (set_writable || njs_is_data_descriptor(prop))) + { if (!prev->configurable) { goto exception; } @@ -435,12 +409,12 @@ set_prop: } if (njs_is_data_descriptor(prev)) { - prev->writable = NJS_ATTRIBUTE_UNSET; + set_writable = 0; njs_prop_getter(prev) = NULL; njs_prop_setter(prev) = NULL; } else { - prev->writable = NJS_ATTRIBUTE_FALSE; + prev->writable = 0; njs_set_undefined(njs_prop_value(prev)); } @@ -448,10 +422,10 @@ set_prop: prev->type = prop->type; } else if (njs_is_data_descriptor(prev) - && njs_is_data_descriptor(prop)) + && (set_writable || njs_is_data_descriptor(prop))) { if (!prev->configurable && !prev->writable) { - if (prop->writable == NJS_ATTRIBUTE_TRUE) { + if (prop->writable) { goto exception; } @@ -484,7 +458,7 @@ done: if (njs_slow_path(njs_is_fast_array(object) && pq.lhq.key_hash == NJS_ATOM_STRING_length) - && prop->writable == NJS_ATTRIBUTE_FALSE) + && (set_writable && !prop->writable)) { array = njs_array(object); length = array->length; @@ -539,8 +513,8 @@ done: if (njs_slow_path(njs_is_array(object) && pq.lhq.key_hash == NJS_ATOM_STRING_length)) { - if (prev->configurable != NJS_ATTRIBUTE_TRUE - && prev->writable != NJS_ATTRIBUTE_TRUE + if (!prev->configurable + && !prev->writable && !njs_values_strict_equal(vm, njs_prop_value(prev), njs_prop_value(prop))) { @@ -548,7 +522,7 @@ done: return NJS_ERROR; } - if (prop->writable != NJS_ATTRIBUTE_UNSET) { + if (set_writable) { prev->writable = prop->writable; } @@ -565,15 +539,15 @@ done: * attribute of the property named P of object O to the value of the field. */ - if (prop->writable != NJS_ATTRIBUTE_UNSET) { + if (set_writable) { prev->writable = prop->writable; } - if (prop->enumerable != NJS_ATTRIBUTE_UNSET) { + if (set_enumerable) { prev->enumerable = prop->enumerable; } - if (prop->configurable != NJS_ATTRIBUTE_UNSET) { + if (set_configurable) { prev->configurable = prop->configurable; } @@ -598,18 +572,9 @@ njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq, njs_function_t *function; njs_object_prop_t *prop, *shared; - prop = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), - sizeof(njs_object_prop_t)); - if (njs_slow_path(prop == NULL)) { - njs_memory_error(vm); - return NJS_ERROR; - } - shared = pq->lhq.value; - *prop = *shared; pq->lhq.replace = 0; - pq->lhq.value = prop; pq->lhq.pool = vm->mem_pool; ret = njs_flathsh_unique_insert(&proto->hash, &pq->lhq); @@ -618,6 +583,13 @@ njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq, return NJS_ERROR; } + prop = pq->lhq.value; + prop->enumerable = shared->enumerable; + prop->configurable = shared->configurable; + prop->writable = shared->writable; + prop->type = shared->type; + prop->u.value = shared->u.value; + if (njs_is_accessor_descriptor(prop)) { if (njs_prop_getter(prop) != NULL) { function = njs_function_copy(vm, njs_prop_getter(prop)); @@ -684,14 +656,15 @@ njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq, static njs_object_prop_t * -njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *desc) +njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *desc, + njs_object_prop_t *prop, uint32_t *set_enumerable, + uint32_t *set_configurable, uint32_t *set_writable) { njs_int_t ret; njs_bool_t data, accessor; njs_value_t value; njs_object_t *desc_object; njs_function_t *getter, *setter; - njs_object_prop_t *prop; njs_flathsh_query_t lhq; if (!njs_is_object(desc)) { @@ -699,11 +672,12 @@ njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *desc) return NULL; } - prop = njs_object_prop_alloc(vm, &njs_value_invalid, - NJS_ATTRIBUTE_UNSET); - if (njs_slow_path(prop == NULL)) { - return NULL; - } + njs_object_prop_init(prop, NJS_PROPERTY, NJS_OBJECT_PROP_UNSET); + *njs_prop_value(prop) = njs_value_invalid; + + *set_enumerable = 0; + *set_configurable = 0; + *set_writable = 0; data = 0; accessor = 0; @@ -768,6 +742,7 @@ njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *desc) if (ret == NJS_OK) { data = 1; prop->writable = njs_is_true(&value); + *set_writable = 1; } if (accessor && data) { @@ -785,6 +760,7 @@ njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *desc) if (ret == NJS_OK) { prop->enumerable = njs_is_true(&value); + *set_enumerable = 1; } lhq.key_hash = NJS_ATOM_STRING_configurable; @@ -796,6 +772,7 @@ njs_descriptor_prop(njs_vm_t *vm, const njs_value_t *desc) if (ret == NJS_OK) { prop->configurable = njs_is_true(&value); + *set_configurable = 1; } if (accessor) { @@ -879,29 +856,23 @@ njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest, if (njs_is_data_descriptor(prop)) { lhq.key_hash = NJS_ATOM_STRING_value; - pr = njs_object_prop_alloc(vm, njs_prop_value(prop), 1); - if (njs_slow_path(pr == NULL)) { - return NJS_ERROR; - } - - lhq.value = pr; - ret = njs_flathsh_unique_insert(&desc->hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { njs_internal_error(vm, "lvlhsh insert failed"); return NJS_ERROR; } - lhq.key_hash = NJS_ATOM_STRING_writable; + pr = lhq.value; - setval = (prop->writable == 1) ? &njs_value_true : &njs_value_false; + pr->type = NJS_PROPERTY; + pr->enumerable = 1; + pr->configurable = 1; + pr->writable = 1; + pr->u.value = *(njs_prop_value(prop)); - pr = njs_object_prop_alloc(vm, setval, 1); - if (njs_slow_path(pr == NULL)) { - return NJS_ERROR; - } + lhq.key_hash = NJS_ATOM_STRING_writable; - lhq.value = pr; + setval = (prop->writable == 1) ? &njs_value_true : &njs_value_false; ret = njs_flathsh_unique_insert(&desc->hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { @@ -909,20 +880,37 @@ njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest, return NJS_ERROR; } + pr = lhq.value; + + pr->type = NJS_PROPERTY; + pr->enumerable = 1; + pr->configurable = 1; + pr->writable = 1; + pr->u.value = *setval; + } else { lhq.key_hash = NJS_ATOM_STRING_get; - pr = njs_object_prop_alloc(vm, &njs_value_undefined, 1); - if (njs_slow_path(pr == NULL)) { + ret = njs_flathsh_unique_insert(&desc->hash, &lhq); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); return NJS_ERROR; } + pr = lhq.value; + + pr->type = NJS_PROPERTY; + pr->enumerable = 1; + pr->configurable = 1; + pr->writable = 1; + pr->u.value = njs_value_undefined; + if (njs_prop_getter(prop) != NULL) { njs_set_function(njs_prop_value(pr), njs_prop_getter(prop)); } - lhq.value = pr; + lhq.key_hash = NJS_ATOM_STRING_set; ret = njs_flathsh_unique_insert(&desc->hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { @@ -930,53 +918,40 @@ njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest, return NJS_ERROR; } - lhq.key_hash = NJS_ATOM_STRING_set; + pr = lhq.value; - pr = njs_object_prop_alloc(vm, &njs_value_undefined, 1); - if (njs_slow_path(pr == NULL)) { - return NJS_ERROR; - } + pr->type = NJS_PROPERTY; + pr->enumerable = 1; + pr->configurable = 1; + pr->writable = 1; + pr->u.value = njs_value_undefined; if (njs_prop_setter(prop) != NULL) { njs_set_function(njs_prop_value(pr), njs_prop_setter(prop)); } - - lhq.value = pr; - - ret = njs_flathsh_unique_insert(&desc->hash, &lhq); - if (njs_slow_path(ret != NJS_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NJS_ERROR; - } } lhq.key_hash = NJS_ATOM_STRING_enumerable; setval = (prop->enumerable == 1) ? &njs_value_true : &njs_value_false; - pr = njs_object_prop_alloc(vm, setval, 1); - if (njs_slow_path(pr == NULL)) { - return NJS_ERROR; - } - - lhq.value = pr; - ret = njs_flathsh_unique_insert(&desc->hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { njs_internal_error(vm, "lvlhsh insert failed"); return NJS_ERROR; } - lhq.key_hash = NJS_ATOM_STRING_configurable; + pr = lhq.value; - setval = (prop->configurable == 1) ? &njs_value_true : &njs_value_false; + pr->type = NJS_PROPERTY; + pr->enumerable = 1; + pr->configurable = 1; + pr->writable = 1; + pr->u.value = *setval; - pr = njs_object_prop_alloc(vm, setval, 1); - if (njs_slow_path(pr == NULL)) { - return NJS_ERROR; - } + lhq.key_hash = NJS_ATOM_STRING_configurable; - lhq.value = pr; + setval = (prop->configurable == 1) ? &njs_value_true : &njs_value_false; ret = njs_flathsh_unique_insert(&desc->hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { @@ -984,6 +959,14 @@ njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest, return NJS_ERROR; } + pr = lhq.value; + + pr->type = NJS_PROPERTY; + pr->enumerable = 1; + pr->configurable = 1; + pr->writable = 1; + pr->u.value = *setval; + njs_set_object(dest, desc); return NJS_OK; @@ -1007,6 +990,9 @@ njs_prop_type_string(njs_object_prop_type_t type) case NJS_PROPERTY: return "property"; + case NJS_FREE_FLATHSH_ELEMENT: + return "free hash element"; + default: return "unknown"; } @@ -1014,7 +1000,7 @@ njs_prop_type_string(njs_object_prop_type_t type) njs_int_t -njs_object_prop_init(njs_vm_t *vm, const njs_object_init_t* init, +njs_object_props_init(njs_vm_t *vm, const njs_object_init_t* init, njs_object_prop_t *base, uint32_t atom_id, njs_value_t *value, njs_value_t *retval) { @@ -1034,31 +1020,26 @@ njs_object_prop_init(njs_vm_t *vm, const njs_object_init_t* init, return NJS_ERROR; } - prop = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), - sizeof(njs_object_prop_t)); - if (njs_slow_path(prop == NULL)) { - njs_memory_error(vm); - return NJS_ERROR; - } - - *prop = *base; - - prop->type = NJS_PROPERTY; - njs_set_object(njs_prop_value(prop), object); - - lhq.value = prop; lhq.key_hash = atom_id; lhq.replace = 1; lhq.pool = vm->mem_pool; lhq.proto = &njs_object_hash_proto; ret = njs_flathsh_unique_insert(njs_object_hash(value), &lhq); - if (njs_fast_path(ret == NJS_OK)) { - njs_value_assign(retval, njs_prop_value(prop)); - return NJS_OK; + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NJS_ERROR; } - njs_internal_error(vm, "lvlhsh insert failed"); + prop = lhq.value; - return NJS_ERROR; + prop->enumerable = base->enumerable; + prop->configurable = base->configurable; + prop->writable = base->writable; + prop->type = NJS_PROPERTY; + njs_set_object(njs_prop_value(prop), object); + + njs_value_assign(retval, njs_prop_value(prop)); + + return NJS_OK; } diff --git a/src/njs_object_prop_declare.h b/src/njs_object_prop_declare.h index f2f7a5eb..3c991c50 100644 --- a/src/njs_object_prop_declare.h +++ b/src/njs_object_prop_declare.h @@ -9,8 +9,8 @@ #define NJS_DECLARE_PROP_VALUE(_name, _v, _fl) \ { \ - .atom_id = NJS_ATOM_ ## _name, \ .desc = { \ + .atom_id = NJS_ATOM_ ## _name, \ .type = NJS_PROPERTY, \ .u.value = _v, \ .enumerable = !!(_fl & NJS_OBJECT_PROP_ENUMERABLE), \ @@ -28,8 +28,8 @@ #define NJS_DECLARE_PROP_HANDLER(_name, _native, _m16, _fl) \ { \ - .atom_id = NJS_ATOM_ ## _name, \ .desc = { \ + .atom_id = NJS_ATOM_ ## _name, \ .type = NJS_PROPERTY_HANDLER, \ .u.value = njs_prop_handler2(_native, _m16), \ .enumerable = !!(_fl & NJS_OBJECT_PROP_ENUMERABLE), \ @@ -41,11 +41,11 @@ #define NJS_DECLARE_PROP_GETTER(_name, _native, _magic) \ { \ - .atom_id = NJS_ATOM_ ## _name, \ .desc = { \ + .atom_id = NJS_ATOM_ ## _name, \ .type = NJS_ACCESSOR, \ .u.accessor = njs_getter(_native, _magic), \ - .writable = NJS_ATTRIBUTE_UNSET, \ + .writable = 0, \ .configurable = 1, \ }, \ } diff --git a/src/njs_parser.c b/src/njs_parser.c index 50cee672..cfaad740 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -839,10 +839,9 @@ njs_inline njs_int_t njs_parser_expect_semicolon(njs_parser_t *parser, njs_lexer_token_t *token) { if (token->type != NJS_TOKEN_SEMICOLON) { - if (parser->strict_semicolon - || (token->type != NJS_TOKEN_END - && token->type != NJS_TOKEN_CLOSE_BRACE - && parser->lexer->prev_type != NJS_TOKEN_LINE_END)) + if (token->type != NJS_TOKEN_END + && token->type != NJS_TOKEN_CLOSE_BRACE + && parser->lexer->prev_type != NJS_TOKEN_LINE_END) { return NJS_DECLINED; } @@ -5408,10 +5407,6 @@ static njs_int_t njs_parser_do_while_semicolon(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - if (parser->strict_semicolon) { - return njs_parser_failed(parser); - } - parser->target->right = parser->node; parser->node = parser->target; @@ -6257,10 +6252,9 @@ njs_parser_break_continue(njs_parser_t *parser, njs_lexer_token_t *token, break; } - if (parser->strict_semicolon - || (token->type != NJS_TOKEN_END - && token->type != NJS_TOKEN_CLOSE_BRACE - && parser->lexer->prev_type != NJS_TOKEN_LINE_END)) + if (token->type != NJS_TOKEN_END + && token->type != NJS_TOKEN_CLOSE_BRACE + && parser->lexer->prev_type != NJS_TOKEN_LINE_END) { return njs_parser_failed(parser); } @@ -6314,9 +6308,7 @@ njs_parser_return_statement(njs_parser_t *parser, njs_lexer_token_t *token, return njs_parser_failed(parser); default: - if (!parser->strict_semicolon - && parser->lexer->prev_type == NJS_TOKEN_LINE_END) - { + if (parser->lexer->prev_type == NJS_TOKEN_LINE_END) { break; } @@ -6702,7 +6694,6 @@ njs_parser_labelled_statement_after(njs_parser_t *parser, njs_int_t ret; njs_str_t str; uintptr_t atom_id; - njs_value_t entry; njs_parser_node_t *node; node = parser->node; @@ -6719,8 +6710,7 @@ njs_parser_labelled_statement_after(njs_parser_t *parser, atom_id = (uint32_t) (uintptr_t) parser->target; - njs_atom_to_value(parser->vm, &entry, atom_id); - njs_string_get(parser->vm, &entry, &str); + njs_atom_string_get(parser->vm, atom_id, &str); ret = njs_name_copy(parser->vm, &parser->node->name, &str); if (ret != NJS_OK) { @@ -7093,8 +7083,7 @@ njs_parser_function_declaration(njs_parser_t *parser, njs_lexer_token_t *token, njs_lexer_consume_token(parser->lexer, 1); - var = njs_variable_function_add(parser, parser->scope, atom_id, - NJS_VARIABLE_FUNCTION); + var = njs_variable_function_add(parser, parser->scope, atom_id); if (var == NULL) { return NJS_ERROR; } diff --git a/src/njs_parser.h b/src/njs_parser.h index 2f7cff79..8a67bdab 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -84,7 +84,6 @@ struct njs_parser_s { uint8_t use_lhs; uint8_t module; - njs_bool_t strict_semicolon; njs_str_t file; uint32_t line; @@ -109,8 +108,9 @@ typedef struct { typedef struct { - njs_value_t *value; + njs_function_lambda_t *lambda; njs_index_t index; + njs_bool_t async; } njs_declaration_t; diff --git a/src/njs_regexp.c b/src/njs_regexp.c index ed560b18..d958e2af 100644 --- a/src/njs_regexp.c +++ b/src/njs_regexp.c @@ -1038,25 +1038,9 @@ njs_regexp_exec_result(njs_vm_t *vm, njs_value_t *r, njs_utf8_t utf8, } /* FIXME: implement fast CreateDataPropertyOrThrow(). */ - prop = njs_object_prop_alloc(vm, &njs_value_undefined, 1); - if (njs_slow_path(prop == NULL)) { - goto fail; - } - - c = njs_regex_capture(match_data, 0); - - if (utf8 == NJS_STRING_UTF8) { - index = njs_string_index(string, c); - - } else { - index = c; - } - - njs_set_number(&prop->u.value, index); lhq.key_hash = NJS_ATOM_STRING_index; lhq.replace = 0; - lhq.value = prop; lhq.pool = vm->mem_pool; lhq.proto = &njs_object_hash_proto; @@ -1065,32 +1049,54 @@ njs_regexp_exec_result(njs_vm_t *vm, njs_value_t *r, njs_utf8_t utf8, goto insert_fail; } - prop = njs_object_prop_alloc(vm, ®exp->string, 1); - if (njs_slow_path(prop == NULL)) { - goto fail; + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + + c = njs_regex_capture(match_data, 0); + + if (utf8 == NJS_STRING_UTF8) { + index = njs_string_index(string, c); + + } else { + index = c; } + njs_set_number(&prop->u.value, index); + lhq.key_hash = NJS_ATOM_STRING_input; - lhq.value = prop; ret = njs_flathsh_unique_insert(&array->object.hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { goto insert_fail; } - prop = njs_object_prop_alloc(vm, &njs_value_undefined, 1); - if (njs_slow_path(prop == NULL)) { - goto fail; - } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = regexp->string; lhq.key_hash = NJS_ATOM_STRING_groups; - lhq.value = prop; ret = njs_flathsh_unique_insert(&array->object.hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { goto insert_fail; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = njs_value_undefined; + if (pattern->ngroups != 0) { groups = njs_object_alloc(vm); if (njs_slow_path(groups == NULL)) { @@ -1110,19 +1116,21 @@ njs_regexp_exec_result(njs_vm_t *vm, njs_value_t *r, njs_utf8_t utf8, goto fail; } - prop = njs_object_prop_alloc(vm, &array->start[group->capture], 1); - if (njs_slow_path(prop == NULL)) { - goto fail; - } - lhq.key_hash = name.atom_id; - lhq.value = prop; ret = njs_flathsh_unique_insert(&groups->hash, &lhq); if (njs_slow_path(ret != NJS_OK)) { goto insert_fail; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = array->start[group->capture]; + i++; } while (i < pattern->ngroups); @@ -1149,26 +1157,10 @@ static void njs_regexp_exec_result_free(njs_vm_t *vm, njs_array_t *result) { njs_flathsh_t *hash; - njs_object_prop_t *prop; - njs_flathsh_elt_t *elt; - njs_flathsh_each_t lhe; njs_flathsh_query_t lhq; - njs_flathsh_each_init(&lhe, &njs_object_hash_proto); - hash = &result->object.hash; - for ( ;; ) { - elt = njs_flathsh_each(hash, &lhe); - if (elt == NULL) { - break; - } - - prop = elt->value; - - njs_mp_free(vm->mem_pool, prop); - } - lhq.pool = vm->mem_pool; lhq.proto = &njs_object_hash_proto; diff --git a/src/njs_scope.c b/src/njs_scope.c index 304e4e33..7959849a 100644 --- a/src/njs_scope.c +++ b/src/njs_scope.c @@ -25,22 +25,6 @@ njs_scope_temp_index(njs_parser_scope_t *scope) } -njs_value_t * -njs_scope_create_index_value(njs_vm_t *vm, njs_index_t index) -{ - njs_value_t *value; - - value = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); - if (njs_slow_path(value == NULL)) { - return NULL; - } - - njs_scope_value_set(vm, index, value); - - return value; -} - - njs_value_t ** njs_scope_make(njs_vm_t *vm, uint32_t count) { @@ -124,7 +108,7 @@ njs_scope_values_hash_test(njs_lvlhsh_query_t *lhq, void *data) njs_str_t string; njs_value_t *value; - value = data; + value = *(njs_value_t **) data; if (njs_is_string(value)) { /* parser strings are always initialized. */ @@ -172,6 +156,7 @@ njs_scope_value_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime, njs_value_t *value; njs_string_t *string; njs_lvlhsh_t *values_hash; + njs_object_prop_t *pr; njs_lvlhsh_query_t lhq; is_string = 0; @@ -197,12 +182,12 @@ njs_scope_value_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime, lhq.proto = &njs_values_hash_proto; if (njs_lvlhsh_find(&vm->shared->values_hash, &lhq) == NJS_OK) { - value = lhq.value; + value = ((njs_object_prop_t *) lhq.value)->u.val; *index = (njs_index_t *) ((u_char *) value + sizeof(njs_value_t)); } else if (runtime && njs_lvlhsh_find(&vm->values_hash, &lhq) == NJS_OK) { - value = lhq.value; + value = ((njs_object_prop_t *) lhq.value)->u.val; *index = (njs_index_t *) ((u_char *) value + sizeof(njs_value_t)); @@ -244,7 +229,6 @@ njs_scope_value_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime, **index = NJS_INDEX_ERROR; lhq.replace = 0; - lhq.value = value; lhq.pool = vm->mem_pool; values_hash = runtime ? &vm->values_hash : &vm->shared->values_hash; @@ -253,6 +237,9 @@ njs_scope_value_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime, if (njs_slow_path(ret != NJS_OK)) { return NULL; } + + pr = lhq.value; + pr->u.val = value; } if (start != (u_char *) src) { diff --git a/src/njs_scope.h b/src/njs_scope.h index 16a47d98..32e44c6c 100644 --- a/src/njs_scope.h +++ b/src/njs_scope.h @@ -27,7 +27,6 @@ njs_index_t njs_scope_temp_index(njs_parser_scope_t *scope); -njs_value_t *njs_scope_create_index_value(njs_vm_t *vm, njs_index_t index); njs_value_t **njs_scope_make(njs_vm_t *vm, uint32_t count); njs_index_t njs_scope_global_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime); diff --git a/src/njs_string.c b/src/njs_string.c index 6d7c464d..1a2b2333 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -601,56 +601,152 @@ njs_int_t njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - u_char *p, *start; - uint64_t size, length, mask; + char *np, *np_end; + double num; + u_char *p; + uint64_t sz, size, length; njs_int_t ret; njs_uint_t i; njs_string_prop_t string; + char buf[512]; + +#define NJS_SZ_LAST 64 if (njs_is_null_or_undefined(&args[0])) { njs_type_error(vm, "\"this\" argument is null or undefined"); return NJS_ERROR; } + size = 0; + length = 0; + + np = buf; + np_end = buf + sizeof(buf); + *np = 0; + for (i = 0; i < nargs; i++) { - if (!njs_is_string(&args[i])) { - ret = njs_value_to_string(vm, &args[i], &args[i]); - if (ret != NJS_OK) { - return ret; + if (njs_is_number(&args[i])) { + num = njs_number(&args[i]); + + if (isnan(num)) { + size += njs_length("NaN"); + length += njs_length("NaN"); + + } else if (isinf(num)) { + if (num < 0) { + size += njs_length("-Infinity"); + length += njs_length("-Infinity"); + + } else { + size += njs_length("Infinity"); + length += njs_length("Infinity"); + } + + } else { + sz = njs_dtoa(num, np + sizeof(uint8_t)); + + if (*np == 0) { + if (np + sizeof(uint8_t) + sz + < np_end - NJS_DTOA_MAX_LEN - sizeof(uint8_t)) + { + *np = (uint8_t) sz; + np += sizeof(uint8_t) + sz; + *np = 0; + + } else { + *np = NJS_SZ_LAST; + } + } + + size += sz; + length += sz; + } + + } else if (njs_is_boolean(&args[i])) { + if (njs_is_true(&args[i])) { + size += njs_length("true"); + length += njs_length("true"); + } else { + size += njs_length("false"); + length += njs_length("false"); + } + + } else if (njs_is_null(&args[i])) { + size += njs_length("null"); + length += njs_length("null"); + + } else if (njs_is_undefined(&args[i])) { + size += njs_length("undefined"); + length += njs_length("undefined"); + + } else { + if (!njs_is_string(&args[i])) { + ret = njs_value_to_string(vm, &args[i], &args[i]); + if (ret != NJS_OK) { + return ret; + } + } + + njs_string_prop(vm, &string, &args[i]); + + size += string.size; + length += string.length; } } - if (nargs == 1) { - njs_string_copy(retval, &args[0]); - return NJS_OK; + p = njs_string_alloc(vm, retval, size, length); + if (njs_slow_path(p == NULL)) { + return NJS_ERROR; } - size = 0; - length = 0; - mask = -1; + np = buf; for (i = 0; i < nargs; i++) { - (void) njs_string_prop(vm, &string, &args[i]); + if (njs_is_number(&args[i])) { + num = njs_number(&args[i]); - size += string.size; - length += string.length; - } + if (isnan(num)) { + p = njs_cpymem(p, "NaN", njs_length("NaN")); - length &= mask; + } else if (isinf(num)) { + if (num < 0) { + p = njs_cpymem(p, "-Infinity", njs_length("-Infinity")); - start = njs_string_alloc(vm, retval, size, length); - if (njs_slow_path(start == NULL)) { - return NJS_ERROR; - } + } else { + p = njs_cpymem(p, "Infinity", njs_length("Infinity")); + } - p = start; + } else { + if (*np != NJS_SZ_LAST) { + sz = *np++; + p = njs_cpymem(p, np, sz); + np += sz; - for (i = 0; i < nargs; i++) { - (void) njs_string_prop(vm, &string, &args[i]); + } else { + sz = njs_dtoa(num, (char *) p); + p += sz; + } + } - p = memcpy(p, string.start, string.size); - p += string.size; + } else if (njs_is_boolean(&args[i])) { + if (njs_is_true(&args[i])) { + p = njs_cpymem(p, "true", njs_length("true")); + } else { + p = njs_cpymem(p, "false", njs_length("false")); + } + + } else if (njs_is_null(&args[i])) { + p = njs_cpymem(p, "null", njs_length("null")); + + } else if (njs_is_undefined(&args[i])) { + p = njs_cpymem(p, "undefined", njs_length("undefined")); + + } else { + njs_string_prop(vm, &string, &args[i]); + + p = njs_cpymem(p, string.start, string.size); + } } return NJS_OK; diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c index 19399e3b..d7ca2e83 100644 --- a/src/njs_typed_array.c +++ b/src/njs_typed_array.c @@ -324,13 +324,14 @@ static njs_int_t njs_typed_array_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - double num; - int64_t length, i; - njs_int_t ret; - njs_value_t *this, *source, *mapfn; - njs_value_t arguments[3], value; - njs_function_t *function; - njs_typed_array_t *array; + double num; + int64_t length, i; + njs_int_t ret; + njs_value_t *this, *source, *mapfn; + njs_value_t arguments[3], value; + njs_function_t *function; + njs_typed_array_t *array; + njs_array_buffer_t *buffer; this = njs_argument(args, 0); @@ -371,6 +372,7 @@ njs_typed_array_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } array = njs_typed_array(retval); + buffer = njs_typed_array_buffer(array); arguments[0] = *njs_arg(args, nargs, 3); for (i = 0; i < length; i++) { @@ -393,7 +395,9 @@ njs_typed_array_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - njs_typed_array_prop_set(vm, array, i, num); + if (!njs_is_detached_buffer(buffer)) { + njs_typed_array_prop_set(vm, array, i, num); + } } njs_set_typed_array(retval, array); @@ -651,6 +655,11 @@ njs_typed_array_set_value(njs_vm_t *vm, njs_typed_array_t *array, return ret; } + buffer = njs_typed_array_buffer(array); + if (njs_slow_path(njs_is_detached_buffer(buffer))) { + return NJS_OK; + } + buffer = njs_typed_array_writable(vm, array); if (njs_slow_path(buffer == NULL)) { return NJS_ERROR; @@ -908,6 +917,20 @@ njs_typed_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, } +static void +njs_slice_memcpy(uint8_t *dst, const uint8_t *src, size_t len) +{ + if (dst + len <= src || dst >= src + len) { + /* no overlap: can use memcpy */ + memcpy(dst, src, len); + + } else { + while (len-- != 0) + *dst++ = *src++; + } +} + + njs_int_t njs_typed_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t copy, njs_value_t *retval) @@ -986,7 +1009,7 @@ njs_typed_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, start = start * element_size; count = count * element_size; - memcpy(&new_buffer->u.u8[0], &buffer->u.u8[start], count); + njs_slice_memcpy(&new_buffer->u.u8[0], &buffer->u.u8[start], count); } else { for (i = 0; i < count; i++) { diff --git a/src/njs_value.c b/src/njs_value.c index dcdab5a2..78f03821 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -8,8 +8,9 @@ #include <njs_main.h> -static njs_int_t njs_object_property_query(njs_vm_t *vm, - njs_property_query_t *pq, njs_object_t *object, uint32_t atom_id); +njs_inline njs_int_t +njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, + njs_object_t *object, uint32_t atom_id); static njs_int_t njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_array_t *array, uint32_t index, uint32_t atom_id); @@ -560,9 +561,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, { uint32_t index; njs_int_t ret; - njs_value_t key; njs_object_t *obj; - njs_function_t *function; njs_assert(atom_id != NJS_ATOM_STRING_unknown); @@ -585,6 +584,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, case NJS_OBJECT: case NJS_ARRAY: + case NJS_FUNCTION: case NJS_ARRAY_BUFFER: case NJS_DATA_VIEW: case NJS_TYPED_ARRAY: @@ -595,31 +595,12 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, obj = njs_object(value); break; - case NJS_FUNCTION: - function = njs_function_value_copy(vm, value); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - - obj = &function->object; - break; - case NJS_UNDEFINED: case NJS_NULL: default: - ret = njs_atom_to_value(vm, &key, atom_id); - - if (njs_fast_path(ret == NJS_OK)) { - njs_string_get(vm, &key, &pq->lhq.key); - njs_type_error(vm, "cannot get property \"%V\" of %s", - &pq->lhq.key, njs_is_null(value) ? "null" - : "undefined"); - return NJS_ERROR; - } - - njs_type_error(vm, "cannot get property \"unknown\" of %s", - njs_is_null(value) ? "null" : "undefined"); - + njs_atom_string_get(vm, atom_id, &pq->lhq.key); + njs_type_error(vm, "cannot get property \"%V\" of %s", &pq->lhq.key, + njs_type_string(value->type)); return NJS_ERROR; } @@ -979,6 +960,7 @@ njs_external_property_query(njs_vm_t *vm, njs_property_query_t *pq, pq->lhq.value = prop; + prop->type = NJS_PROPERTY; prop->writable = slots->writable; prop->configurable = slots->configurable; prop->enumerable = slots->enumerable; @@ -1029,8 +1011,7 @@ njs_value_property(njs_vm_t *vm, njs_value_t *value, uint32_t atom_id, tarray = njs_typed_array(value); if (njs_slow_path(njs_is_detached_buffer(tarray->buffer))) { - njs_type_error(vm, "detached buffer"); - return NJS_ERROR; + goto not_found; } if (njs_slow_path(index >= njs_typed_array_length(tarray))) { @@ -1119,6 +1100,7 @@ slow_path: break; case NJS_DECLINED: +not_found: njs_set_undefined(retval); return NJS_DECLINED; @@ -1298,10 +1280,7 @@ slow_path: return NJS_ERROR; } - elt->value = (&pq.lhq)->value; - - prop = (njs_object_prop_t *) elt->value; - + prop = (njs_object_prop_t *) elt; prop->type = NJS_PROPERTY; prop->enumerable = 1; prop->configurable = 1; @@ -1337,13 +1316,8 @@ slow_path: goto fail; } - prop = njs_object_prop_alloc(vm, &njs_value_undefined, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } pq.lhq.replace = 0; - pq.lhq.value = prop; pq.lhq.key_hash = atom_id; pq.lhq.pool = vm->mem_pool; @@ -1353,6 +1327,12 @@ slow_path: return NJS_ERROR; } + prop = pq.lhq.value; + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + found: njs_value_assign(njs_prop_value(prop), setval); @@ -1471,7 +1451,6 @@ slow_path: } prop->type = NJS_WHITEOUT; - prop->enum_in_object_hash = 1; return NJS_OK; } diff --git a/src/njs_value.h b/src/njs_value.h index 7bc9eff3..3fba344b 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -287,7 +287,8 @@ struct njs_object_type_init_s { typedef enum { - NJS_PROPERTY = 0, + NJS_FREE_FLATHSH_ELEMENT = 0, + NJS_PROPERTY, NJS_ACCESSOR, NJS_PROPERTY_HANDLER, @@ -305,22 +306,23 @@ typedef enum { } njs_prop_query_t; -/* - * Attributes are generally used as Boolean values. - * The UNSET value is can be seen: - * for newly created property descriptors in njs_define_property(), - * for writable attribute of accessor descriptors (desc->writable - * cannot be used as a boolean value). - */ -typedef enum { - NJS_ATTRIBUTE_FALSE = 0, - NJS_ATTRIBUTE_TRUE = 1, - NJS_ATTRIBUTE_UNSET, -} njs_object_attribute_t; - +/* njs_object_prop_s: same structure and length as njs_flathsh_elt_t. */ struct njs_object_prop_s { + /* next_elt + property descriptor : 32 bits */ + + uint32_t next_elt:26; + + uint32_t type:3; + uint32_t writable:1; + uint32_t enumerable:1; + uint32_t configurable:1; + + uint32_t atom_id; + union { + njs_value_t *val; + njs_mod_t *mod; njs_value_t value; struct { njs_function_t *getter; @@ -328,7 +330,8 @@ struct njs_object_prop_s { } accessor; } u; -#define njs_prop_value(_p) (&(_p)->u.value) +#define njs_prop_value(_p) (&((njs_object_prop_t *) (_p))->u.value) +#define njs_prop_module(_p) (((njs_object_prop_t *) (_p))->u.mod) #define njs_prop_handler(_p) (_p)->u.value.data.u.prop_handler #define njs_prop_ref(_p) (_p)->u.value.data.u.value #define njs_prop_typed_ref(_p) (_p)->u.value.data.u.typed_array @@ -338,18 +341,11 @@ struct njs_object_prop_s { #define njs_prop_getter(_p) (_p)->u.accessor.getter #define njs_prop_setter(_p) (_p)->u.accessor.setter - njs_object_prop_type_t type:8; /* 3 bits */ - njs_object_prop_type_t enum_in_object_hash:8; /* 3 bits */ - - njs_object_attribute_t writable:8; /* 2 bits */ - njs_object_attribute_t enumerable:8; /* 2 bits */ - njs_object_attribute_t configurable:8; /* 2 bits */ }; struct njs_object_prop_init_s { struct njs_object_prop_s desc; - uint32_t atom_id; }; diff --git a/src/njs_variable.c b/src/njs_variable.c index 78bd2afb..b65e5934 100644 --- a/src/njs_variable.c +++ b/src/njs_variable.c @@ -36,7 +36,7 @@ njs_variable_add(njs_parser_t *parser, njs_parser_scope_t *scope, njs_variable_t * njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, - uintptr_t atom_id, njs_variable_type_t type) + uintptr_t atom_id) { njs_bool_t ctor; njs_variable_t *var; @@ -44,14 +44,15 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, njs_parser_scope_t *root; njs_function_lambda_t *lambda; - root = njs_variable_scope_find(parser, scope, atom_id, type); + root = njs_variable_scope_find(parser, scope, atom_id, + NJS_VARIABLE_FUNCTION); if (njs_slow_path(root == NULL)) { njs_parser_ref_error(parser, "scope not found"); return NULL; } - var = njs_variable_scope_add(parser, root, scope, atom_id, type, - NJS_INDEX_ERROR); + var = njs_variable_scope_add(parser, root, scope, atom_id, + NJS_VARIABLE_FUNCTION, NJS_INDEX_ERROR); if (njs_slow_path(var == NULL)) { return NULL; } @@ -77,15 +78,15 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, } var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL, - type); + NJS_VARIABLE_FUNCTION); - declr->value = &var->value; + declr->lambda = lambda; + declr->async = !ctor; declr->index = var->index; root->items++; var->type = NJS_VARIABLE_FUNCTION; - var->function = 1; return var; } @@ -173,7 +174,6 @@ njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope, if (var != NULL && var->scope == root) { if (var->self) { - var->function = 0; return scope; } @@ -239,7 +239,7 @@ njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope, failed: - njs_lexer_entry(parser->vm, atom_id, &entry); + njs_atom_string_get(parser->vm, atom_id, &entry); njs_parser_syntax_error(parser, "\"%V\" has already been declared", &entry); return NULL; diff --git a/src/njs_variable.h b/src/njs_variable.h index 2ed5220a..db40de75 100644 --- a/src/njs_variable.h +++ b/src/njs_variable.h @@ -26,7 +26,6 @@ typedef struct { njs_bool_t self; njs_bool_t init; njs_bool_t closure; - njs_bool_t function; njs_parser_scope_t *scope; njs_parser_scope_t *original; @@ -62,7 +61,7 @@ typedef struct { njs_variable_t *njs_variable_add(njs_parser_t *parser, njs_parser_scope_t *scope, uintptr_t atom_id, njs_variable_type_t type); njs_variable_t *njs_variable_function_add(njs_parser_t *parser, - njs_parser_scope_t *scope, uintptr_t atom_id, njs_variable_type_t type); + njs_parser_scope_t *scope, uintptr_t atom_id); njs_variable_t * njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t atom_id); njs_variable_t *njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope, diff --git a/src/njs_vm.c b/src/njs_vm.c index 008f9d04..e6aac927 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -310,7 +310,6 @@ njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name, u_char **start, u_char *end) { njs_int_t ret; - njs_arr_t *arr; njs_mod_t *module; njs_parser_t parser; njs_vm_code_t *code; @@ -366,10 +365,6 @@ njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name, u_char **start, lambda->start = generator.code_start; lambda->nlocal = scope->items; - arr = scope->declarations; - lambda->declarations = (arr != NULL) ? arr->start : NULL; - lambda->ndeclarations = (arr != NULL) ? arr->items : 0; - module->function.u.lambda = lambda; return module; @@ -908,6 +903,7 @@ njs_vm_bind2(njs_vm_t *vm, const njs_str_t *var_name, njs_object_prop_t *prop, njs_value_t prop_name; njs_object_t *global; njs_flathsh_t *hash; + njs_object_prop_t *obj_prop; njs_flathsh_query_t lhq; ret = njs_atom_string_create(vm, &prop_name, var_name->start, @@ -916,7 +912,6 @@ njs_vm_bind2(njs_vm_t *vm, const njs_str_t *var_name, njs_object_prop_t *prop, return NJS_ERROR; } - lhq.value = prop; lhq.key_hash = prop_name.atom_id; lhq.replace = 1; lhq.pool = vm->mem_pool; @@ -931,6 +926,14 @@ njs_vm_bind2(njs_vm_t *vm, const njs_str_t *var_name, njs_object_prop_t *prop, return ret; } + obj_prop = lhq.value; + + obj_prop->type = prop->type; + obj_prop->enumerable = prop->enumerable; + obj_prop->configurable = prop->configurable; + obj_prop->writable = prop->writable; + obj_prop->u.value = prop->u.value; + return NJS_OK; } @@ -939,14 +942,13 @@ njs_int_t njs_vm_bind(njs_vm_t *vm, const njs_str_t *var_name, const njs_value_t *value, njs_bool_t shared) { - njs_object_prop_t *prop; + njs_object_prop_t prop; - prop = njs_object_prop_alloc(vm, value, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } + njs_object_prop_init(&prop, NJS_PROPERTY, + NJS_OBJECT_PROP_VALUE_ECW); + *njs_prop_value(&prop) = *value; - return njs_vm_bind2(vm, var_name, prop, shared); + return njs_vm_bind2(vm, var_name, &prop, shared); } @@ -955,21 +957,18 @@ njs_vm_bind_handler(njs_vm_t *vm, const njs_str_t *var_name, njs_prop_handler_t handler, uint16_t magic16, uint32_t magic32, njs_bool_t shared) { - njs_object_prop_t *prop; + njs_object_prop_t prop; - prop = njs_object_prop_alloc(vm, &njs_value_invalid, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } + njs_object_prop_init(&prop, NJS_PROPERTY_HANDLER, + NJS_OBJECT_PROP_VALUE_ECW); - prop->type = NJS_PROPERTY_HANDLER; - prop->u.value.type = NJS_INVALID; - prop->u.value.data.truth = 1; - njs_prop_magic16(prop) = magic16; - njs_prop_magic32(prop) = magic32; - njs_prop_handler(prop) = handler; + prop.u.value.type = NJS_INVALID; + prop.u.value.data.truth = 1; + njs_prop_magic16(&prop) = magic16; + njs_prop_magic32(&prop) = magic32; + njs_prop_handler(&prop) = handler; - return njs_vm_bind2(vm, var_name, prop, shared); + return njs_vm_bind2(vm, var_name, &prop, shared); } @@ -1247,11 +1246,6 @@ njs_vm_object_alloc(njs_vm_t *vm, njs_value_t *retval, ...) goto done; } - prop = njs_object_prop_alloc(vm, value, 1); - if (njs_slow_path(prop == NULL)) { - goto done; - } - if (name->atom_id == NJS_ATOM_STRING_unknown) { ret = njs_atom_atomize_key(vm, name); if (ret != NJS_OK) { @@ -1259,7 +1253,6 @@ njs_vm_object_alloc(njs_vm_t *vm, njs_value_t *retval, ...) } } - lhq.value = prop; lhq.key_hash = name->atom_id; lhq.replace = 0; lhq.pool = vm->mem_pool; @@ -1270,6 +1263,14 @@ njs_vm_object_alloc(njs_vm_t *vm, njs_value_t *retval, ...) njs_internal_error(vm, NULL); goto done; } + + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = *value; } ret = NJS_OK; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 3826f9eb..727dc6d0 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -16,15 +16,12 @@ struct njs_property_next_s { static njs_jump_off_t njs_vmcode_object(njs_vm_t *vm, njs_value_t *retval); static njs_jump_off_t njs_vmcode_array(njs_vm_t *vm, u_char *pc, njs_value_t *retval); -static njs_jump_off_t njs_vmcode_function(njs_vm_t *vm, u_char *pc, - njs_value_t *retval); +static njs_jump_off_t njs_vmcode_function(njs_vm_t *vm, u_char *pc); static njs_jump_off_t njs_vmcode_arguments(njs_vm_t *vm, u_char *pc); static njs_jump_off_t njs_vmcode_regexp(njs_vm_t *vm, u_char *pc, njs_value_t *retval); static njs_jump_off_t njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *retval); -static njs_jump_off_t njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, - njs_index_t retval); static njs_jump_off_t njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, njs_value_t *retval); @@ -113,7 +110,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval, njs_vmcode_equal_jump_t *equal; njs_vmcode_try_return_t *try_return; njs_vmcode_method_frame_t *method_frame; - njs_vmcode_function_copy_t *fcopy; njs_vmcode_prop_accessor_t *accessor; njs_vmcode_try_trampoline_t *try_trampoline; njs_vmcode_function_frame_t *function_frame; @@ -157,7 +153,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval, NJS_GOTO_ROW(NJS_VMCODE_IF_EQUAL_JUMP), NJS_GOTO_ROW(NJS_VMCODE_PROPERTY_INIT), NJS_GOTO_ROW(NJS_VMCODE_RETURN), - NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_COPY), NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_FRAME), NJS_GOTO_ROW(NJS_VMCODE_METHOD_FRAME), NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_CALL), @@ -672,14 +667,52 @@ NEXT_LBL; src = value1; } - ret = njs_primitive_value_to_string(vm, &dst, src); - if (njs_slow_path(ret != NJS_OK)) { - goto error; - } + if (njs_is_number(src)) { + size_t size; + njs_string_t sp; + char buf[64]; - ret = njs_string_concat(vm, s1, s2, &name); - if (njs_slow_path(ret == NJS_ERROR)) { - goto error; + /* Alloc free path for "str" + int or int + "str" concatenation. */ + + num = njs_number(src); + + if (isnan(num)) { + njs_atom_to_value(vm, &dst, NJS_ATOM_STRING_NaN); + + } else if (isinf(num)) { + + if (num < 0) { + njs_atom_to_value(vm, &dst, NJS_ATOM_STRING__Infinity); + + } else { + njs_atom_to_value(vm, &dst, NJS_ATOM_STRING_Infinity); + } + + } else { + size = njs_dtoa(num, buf); + + sp.start = (u_char *) buf; + sp.size = size; + sp.length = size; + + dst.string.data = &sp; + } + + ret = njs_string_concat(vm, s1, s2, &name); + if (njs_slow_path(ret == NJS_ERROR)) { + goto error; + } + + } else { + ret = njs_primitive_value_to_string(vm, &dst, src); + if (njs_slow_path(ret != NJS_OK)) { + goto error; + } + + ret = njs_string_concat(vm, s1, s2, &name); + if (njs_slow_path(ret == NJS_ERROR)) { + goto error; + } } njs_value_assign(retval, &name); @@ -1155,9 +1188,7 @@ NEXT_LBL; CASE (NJS_VMCODE_FUNCTION): njs_vmcode_debug_opcode(); - njs_vmcode_operand(vm, vmcode->operand1, retval); - - ret = njs_vmcode_function(vm, pc, retval); + ret = njs_vmcode_function(vm, pc); if (njs_slow_path(ret < 0 && ret >= NJS_PREEMPT)) { goto error; } @@ -1422,17 +1453,6 @@ NEXT_LBL; return NJS_OK; - CASE (NJS_VMCODE_FUNCTION_COPY): - njs_vmcode_debug_opcode(); - - fcopy = (njs_vmcode_function_copy_t *) pc; - ret = njs_vmcode_function_copy(vm, fcopy->function, fcopy->retval); - if (njs_slow_path(ret == NJS_ERROR)) { - goto error; - } - - BREAK; - CASE (NJS_VMCODE_FUNCTION_FRAME): njs_vmcode_debug_opcode(); @@ -1928,7 +1948,7 @@ njs_vmcode_array(njs_vm_t *vm, u_char *pc, njs_value_t *retval) static njs_jump_off_t -njs_vmcode_function(njs_vm_t *vm, u_char *pc, njs_value_t *retval) +njs_vmcode_function(njs_vm_t *vm, u_char *pc) { njs_function_t *function; njs_vmcode_function_t *code; @@ -1948,7 +1968,7 @@ njs_vmcode_function(njs_vm_t *vm, u_char *pc, njs_value_t *retval) function->args_count = lambda->nargs - lambda->rest_parameters; - njs_set_function(retval, function); + njs_set_function(njs_scope_value(vm, code->retval), function); return sizeof(njs_vmcode_function_t); } @@ -2028,28 +2048,9 @@ njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *retval) return ret; } - return sizeof(njs_vmcode_template_literal_t); -} - - -static njs_jump_off_t -njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, njs_index_t retidx) -{ - njs_value_t *retval; - njs_function_t *function; - - retval = njs_scope_value(vm, retidx); - - if (!njs_is_valid(retval)) { - *retval = *value; - - function = njs_function_value_copy(vm, retval); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - } + njs_array_destroy(vm, array); - return sizeof(njs_vmcode_function_copy_t); + return sizeof(njs_vmcode_template_literal_t); } @@ -2122,12 +2123,6 @@ njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, } } - prop = njs_object_prop_alloc(vm, init, 1); - if (njs_slow_path(prop == NULL)) { - return NJS_ERROR; - } - - lhq.value = prop; lhq.key_hash = name.atom_id; lhq.replace = 1; lhq.pool = vm->mem_pool; @@ -2139,6 +2134,14 @@ njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, return NJS_ERROR; } + prop = lhq.value; + + prop->type = NJS_PROPERTY; + prop->enumerable = 1; + prop->configurable = 1; + prop->writable = 1; + prop->u.value = *init; + break; default: @@ -2591,7 +2594,7 @@ njs_function_frame_create(njs_vm_t *vm, njs_value_t *value, } -inline njs_object_t * +njs_object_t * njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor) { njs_value_t proto, bound; diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h index 83507f63..5170f29f 100644 --- a/src/njs_vmcode.h +++ b/src/njs_vmcode.h @@ -38,7 +38,6 @@ enum { NJS_VMCODE_IF_EQUAL_JUMP, NJS_VMCODE_PROPERTY_INIT, NJS_VMCODE_RETURN, - NJS_VMCODE_FUNCTION_COPY, NJS_VMCODE_FUNCTION_FRAME, NJS_VMCODE_METHOD_FRAME, NJS_VMCODE_FUNCTION_CALL, @@ -383,13 +382,6 @@ typedef struct { typedef struct { njs_vmcode_t code; - njs_value_t *function; - njs_index_t retval; -} njs_vmcode_function_copy_t; - - -typedef struct { - njs_vmcode_t code; njs_index_t retval; njs_mod_t *module; } njs_vmcode_import_t; @@ -911,6 +911,11 @@ qjs_to_bytes(JSContext *ctx, qjs_bytes_t *bytes, JSValueConst value) goto string; } + /* GCC complains about uninitialized variables. */ + + byte_offset = 0; + byte_length = 0; + val = JS_GetTypedArrayBuffer(ctx, value, &byte_offset, &byte_length, NULL); if (!JS_IsException(val)) { bytes->start = JS_GetArrayBuffer(ctx, &bytes->length, val); @@ -967,6 +972,11 @@ qjs_typed_array_data(JSContext *ctx, JSValueConst value, njs_str_t *data) size_t byte_offset, byte_length; JSValue ab; + /* GCC complains about uninitialized variables. */ + + byte_offset = 0; + byte_length = 0; + /* TODO: DataView. */ ab = JS_GetTypedArrayBuffer(ctx, value, &byte_offset, &byte_length, diff --git a/src/test/lvlhsh_unit_test.c b/src/test/lvlhsh_unit_test.c index 6172c1c7..0e334c5d 100644 --- a/src/test/lvlhsh_unit_test.c +++ b/src/test/lvlhsh_unit_test.c @@ -11,7 +11,7 @@ static njs_int_t lvlhsh_unit_test_key_test(njs_lvlhsh_query_t *lhq, void *data) { - if (*(uintptr_t *) lhq->key.start == (uintptr_t) data) { + if (*(uintptr_t *) lhq->key.start == *(uintptr_t *) data) { return NJS_OK; } @@ -51,13 +51,13 @@ lvlhsh_unit_test_add(njs_lvlhsh_t *lh, const njs_lvlhsh_proto_t *proto, lhq.replace = 0; lhq.key.length = sizeof(uintptr_t); lhq.key.start = (u_char *) &key; - lhq.value = (void *) key; lhq.proto = proto; lhq.pool = pool; switch (njs_lvlhsh_insert(lh, &lhq)) { case NJS_OK: + ((njs_flathsh_elt_t *) lhq.value)->value[0] = (void *) key; return NJS_OK; case NJS_DECLINED: @@ -84,7 +84,7 @@ lvlhsh_unit_test_get(njs_lvlhsh_t *lh, const njs_lvlhsh_proto_t *proto, if (njs_lvlhsh_find(lh, &lhq) == NJS_OK) { - if (key == (uintptr_t) lhq.value) { + if (key == (uintptr_t) ((njs_flathsh_elt_t *) lhq.value)->value[0]) { return NJS_OK; } } diff --git a/src/test/njs_externals_test.c b/src/test/njs_externals_test.c index 8d71aae7..d3d3f1c1 100644 --- a/src/test/njs_externals_test.c +++ b/src/test/njs_externals_test.c @@ -61,7 +61,7 @@ lvlhsh_unit_test_key_test(njs_lvlhsh_query_t *lhq, void *data) njs_str_t name; njs_unit_test_prop_t *prop; - prop = data; + prop = *(njs_unit_test_prop_t **) data; name = prop->name; if (name.length != lhq->key.length) { @@ -129,13 +129,13 @@ lvlhsh_unit_test_add(njs_mp_t *pool, njs_unit_test_req_t *r, lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length); lhq.replace = 1; - lhq.value = (void *) prop; lhq.proto = &lvlhsh_proto; lhq.pool = pool; switch (njs_lvlhsh_insert(&r->hash, &lhq)) { case NJS_OK: + ((njs_flathsh_elt_t *) lhq.value)->value[0] = (void *) prop; return NJS_OK; case NJS_DECLINED: @@ -291,9 +291,9 @@ njs_unit_test_r_vars(njs_vm_t *vm, njs_object_prop_t *self, uint32_t atom_id, ret = njs_lvlhsh_find(&r->hash, &lhq); - prop = lhq.value; - if (ret == NJS_OK) { + prop = ((njs_flathsh_elt_t *) lhq.value)->value[0]; + if (retval == NULL) { njs_value_invalid_set(njs_value_arg(&prop->value)); return NJS_OK; diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 2227c0a6..78e12197 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -3923,6 +3923,9 @@ static njs_unit_test_t njs_test[] = { njs_str("delete this !== true"), njs_str("false") }, + { njs_str("undefined[Symbol()]"), + njs_str("TypeError: cannot get property \"Symbol()\" of undefined") }, + /* Object shorthand methods. */ { njs_str("var o = {m(){}}; new o.m();"), @@ -5906,6 +5909,12 @@ static njs_unit_test_t njs_test[] = njs_str("true") }, { njs_str(NJS_TYPED_ARRAY_LIST + ".every(v=>{Object.defineProperty(v.prototype, '0', {set(){ throw 'Oops' }});" + " var t = new v([0]); var r = Object.create(t);" + " r[0] = 1; return true})"), + njs_str("true") }, + + { njs_str(NJS_TYPED_ARRAY_LIST ".every(v=>{try {var a = new v([1,1]); Object.defineProperty(a, '1', {get(){return 22}})} " " catch (e) { return e.message == 'Cannot redefine property: \"1\"'}})"), njs_str("true") }, @@ -9553,6 +9562,9 @@ static njs_unit_test_t njs_test[] = { njs_str("/["), njs_str("SyntaxError: Unterminated RegExp \"/[\" in 1") }, + { njs_str("/[][a"), + njs_str("SyntaxError: Unterminated RegExp \"/[][a\" in 1") }, + { njs_str("/[\\"), njs_str("SyntaxError: Unterminated RegExp \"/[\\\" in 1") }, @@ -9588,11 +9600,24 @@ static njs_unit_test_t njs_test[] = njs_str("/\\]cd/") }, #endif + { njs_str("RegExp('[][a')"), + njs_str("SyntaxError: " + njs_pcre_var("pcre_compile2(\"(?!)[a\") failed: missing terminating ] for character class at \"\"", + "pcre_compile(\"[][a\") failed: missing terminating ] for character class")) }, + + { njs_str("RegExp('[][a][a')"), + njs_str("SyntaxError: " + njs_pcre_var("pcre_compile2(\"(?!)[a][a\") failed: missing terminating ] for character class at \"\"", + "pcre_compile(\"[][a][a\") failed: missing terminating ] for character class")) }, + { njs_str("RegExp('[\\\\')"), njs_str("SyntaxError: " njs_pcre_var("pcre_compile2(\"[\\\") failed: \\ at end of pattern at \"\"", "pcre_compile(\"[\\\") failed: \\ at end of pattern")) }, + { njs_str("RegExp('[][a]')"), + njs_str(njs_pcre_var("/(?!)[a]/", "/[][a]/")) }, + { njs_str("RegExp('\\\\0').source[1]"), njs_str("0") }, @@ -10984,6 +11009,30 @@ static njs_unit_test_t njs_test[] = "f.apply(123, {})"), njs_str("123") }, + { njs_str("'Hello'.concat(' ', 'World')"), + njs_str("Hello World") }, + + { njs_str("'Value: '.concat(42, ' and ', 3.14)"), + njs_str("Value: 42 and 3.14") }, + + { njs_str("'Flags: '.concat(true, ' and ', false)"), + njs_str("Flags: true and false") }, + + { njs_str("'Values: '.concat(null, ' and ', undefined)"), + njs_str("Values: null and undefined") }, + + { njs_str("'Mixed: '.concat(123, ' ', true, ' ', null, ' ', undefined)"), + njs_str("Mixed: 123 true null undefined") }, + + { njs_str("'Special: '.concat(NaN, ' ', Infinity, ' ', -Infinity)"), + njs_str("Special: NaN Infinity -Infinity") }, + + { njs_str("'Numbers: '.concat(1234567890, ' ', 0.123456789, ' ', 1.23e-10)"), + njs_str("Numbers: 1234567890 0.123456789 1.23e-10") }, + + { njs_str("'Zero: '.concat(0, ' ', -0)"), + njs_str("Zero: 0 0") }, + { njs_str("(function(index, ...rest){ return rest[index];})" ".apply({}, [1022].concat(Array(1023).fill(1).map((v,i)=>i.toString(16))))"), njs_str("3fe") }, @@ -11033,6 +11082,15 @@ static njs_unit_test_t njs_test[] = { njs_str("''.concat.apply('a', [ 'b', 'c' ], 'd')"), njs_str("abc") }, + { njs_str("''.concat.apply('', Array(128).fill(1.23456789123e14)) == '123456789123000'.repeat(128)"), + njs_str("true") }, + + { njs_str("''.concat.apply('', Array(128).fill(0).map((v,i)=>Math.log2(i))).startsWith('-Infinity')"), + njs_str("true") }, + + { njs_str("''.concat.apply('', Array(256).fill(0).map((v,i)=> !(i % 2) ? Math.exp(i) : 'α'.repeat(Math.log2(i)))).endsWith('110ααααααα')"), + njs_str("true") }, + { njs_str("[].join.call([1,2,3])"), njs_str("1,2,3") }, @@ -11966,6 +12024,48 @@ static njs_unit_test_t njs_test[] = { njs_str("/[]a/.test('a')"), njs_str("false") }, + { njs_str("/[#[]/.test('[')"), + njs_str("true") }, + + { njs_str("/[\\s[]/.test('[')"), + njs_str("true") }, + + { njs_str("/[#[^]/.test('[')"), + njs_str("true") }, + + { njs_str("/[#\\[]/.test('[')"), + njs_str("true") }, + + { njs_str("/[\\[^]/.test('[')"), + njs_str("true") }, + + { njs_str("/[^]abc]/.test('#abc]')"), + njs_str("true") }, + + { njs_str("/[[^]abc]/.test('[abc]')"), + njs_str("true") }, + + { njs_str("/[[^]abc]/.test('^abc]')"), + njs_str("true") }, + + { njs_str("/[]/.test('[]')"), + njs_str("false") }, + + { njs_str("/[[]/.test('[')"), + njs_str("true") }, + + { njs_str("/\\[]/.test('[]')"), + njs_str("true") }, + + { njs_str("/[]abc]/.test('abc]')"), + njs_str("false") }, + + { njs_str("/abc]/.test('abc]')"), + njs_str("true") }, + + { njs_str("/\\\\\\[]/.test('\\\\[]')"), + njs_str("true") }, + #ifdef NJS_HAVE_PCRE2 { njs_str("/[]*a/.test('a')"), njs_str("true") }, @@ -14186,22 +14286,22 @@ static njs_unit_test_t njs_test[] = njs_str("true") }, { njs_str("new Function('('.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, { njs_str("new Function('{'.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \")\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \")\" in runtime") }, { njs_str("new Function('['.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, { njs_str("new Function('`'.repeat(2**13));"), njs_str("[object Function]") }, { njs_str("new Function('{['.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, { njs_str("new Function('{;'.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \")\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \")\" in runtime") }, { njs_str("(new Function('1;'.repeat(2**13) + 'return 2'))()"), njs_str("2") }, @@ -14213,7 +14313,7 @@ static njs_unit_test_t njs_test[] = njs_str("-4") }, { njs_str("new Function('new '.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, { njs_str("(new Function('return ' + 'typeof '.repeat(2**13) + 'x'))()"), njs_str("string") }, @@ -14279,7 +14379,13 @@ static njs_unit_test_t njs_test[] = njs_str("ReferenceError: \"foo\" is not defined") }, { njs_str("this.NN = {}; var f = Function('eval = 42;'); f()"), - njs_str("SyntaxError: Identifier \"eval\" is forbidden as left-hand in assignment in runtime:1") }, + njs_str("SyntaxError: Identifier \"eval\" is forbidden as left-hand in assignment in runtime") }, + + { njs_str("new Function('}); let a; a; function o(){}; //')"), + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, + + { njs_str("new Function('}); let a; a; function o(){}; ({')"), + njs_str("SyntaxError: single function literal required") }, { njs_str("RegExp()"), njs_str("/(?:)/") }, @@ -19808,7 +19914,7 @@ static njs_unit_test_t njs_test[] = njs_str("[object AsyncFunction]") }, { njs_str("let f = new Function('x', 'await 1; return x'); f(1)"), - njs_str("SyntaxError: await is only valid in async functions in runtime:1") }, + njs_str("SyntaxError: await is only valid in async functions in runtime") }, { njs_str("new AsyncFunction()"), njs_str("ReferenceError: \"AsyncFunction\" is not defined") }, @@ -21673,7 +21779,9 @@ done: return NJS_ERROR; } - success = njs_strstr_eq(&expected->ret, &s); + success = expected->ret.length <= s.length + && (memcmp(expected->ret.start, s.start, expected->ret.length) + == 0); if (!success) { njs_stderror("njs(\"%V\")\nexpected: \"%V\"\n got: \"%V\"\n", &expected->script, &expected->ret, &s); diff --git a/test/js/async_closure.t.js b/test/js/async_closure.t.js new file mode 100644 index 00000000..9a387ff2 --- /dev/null +++ b/test/js/async_closure.t.js @@ -0,0 +1,24 @@ +/*--- +includes: [] +flags: [async] +---*/ + +async function f() { + await 1; + var v = 2; + + function g() { + return v + 1; + } + + function s() { + g + 1; + } + + return g(); +} + +f().then(v => { + assert.sameValue(v, 3) +}) +.then($DONE, $DONE); diff --git a/test/js/async_closure_arg.t.js b/test/js/async_closure_arg.t.js new file mode 100644 index 00000000..d6aa8ab6 --- /dev/null +++ b/test/js/async_closure_arg.t.js @@ -0,0 +1,24 @@ +/*--- +includes: [] +flags: [async] +---*/ + +async function f(v) { + await 1; + v = 2; + + function g() { + return v + 1; + } + + function s() { + g + 1; + } + + return g(); +} + +f(42).then(v => { + assert.sameValue(v, 3) +}) +.then($DONE, $DONE); diff --git a/test/js/async_closure_share.t.js b/test/js/async_closure_share.t.js new file mode 100644 index 00000000..d78f92c5 --- /dev/null +++ b/test/js/async_closure_share.t.js @@ -0,0 +1,28 @@ +/*--- +includes: [] +flags: [async] +---*/ + +async function f() { + await 1; + var v = 'f'; + + function g() { + v += ':g'; + return v; + } + + function s() { + v += ':s'; + return v; + } + + return [g, s]; +} + +f().then(pair => { + pair[0](); + var v = pair[1](); + assert.sameValue(v, 'f:g:s'); +}) +.then($DONE, $DONE); diff --git a/ts/ngx_core.d.ts b/ts/ngx_core.d.ts index b459c929..d35cb40f 100644 --- a/ts/ngx_core.d.ts +++ b/ts/ngx_core.d.ts @@ -276,6 +276,7 @@ interface NgxSharedDict<V extends string | number = string | number> { * * @param key The key of the item to add. * @param value The value of the item to add. + * @param timeout Overrides the default timeout for this item in milliseconds. * @returns `true` if the value has been added successfully, `false` * if the `key` already exists in this dictionary. * @throws {SharedMemoryError} if there's not enough free space in this @@ -283,7 +284,7 @@ interface NgxSharedDict<V extends string | number = string | number> { * @throws {TypeError} if the `value` is of a different type than expected * by this dictionary. */ - add(key: string, value: V): boolean; + add(key: string, value: V, timeout?: number): boolean; /** * Removes all items from this dictionary. */ @@ -307,13 +308,14 @@ interface NgxSharedDict<V extends string | number = string | number> { * @param delta The number to increment/decrement the value by. * @param init The number to initialize the item with if it didn't exist * (default is `0`). + * @param timeout Overrides the default timeout for this item in milliseconds. * @returns The new value. * @throws {SharedMemoryError} if there's not enough free space in this * dictionary. * @throws {TypeError} if this dictionary does not expect numbers. */ incr: V extends number - ? (key: string, delta: V, init?: number) => number + ? (key: string, delta: V, init?: number, timeout?: number) => number : never; /** * @param maxCount The maximum number of pairs to retrieve (default is 1024). @@ -371,13 +373,14 @@ interface NgxSharedDict<V extends string | number = string | number> { * * @param key The key of the item to set. * @param value The value of the item to set. + * @param timeout Overrides the default timeout for this item in milliseconds. * @returns This dictionary (for method chaining). * @throws {SharedMemoryError} if there's not enough free space in this * dictionary. * @throws {TypeError} if the `value` is of a different type than expected * by this dictionary. */ - set(key: string, value: V): this; + set(key: string, value: V, timeout?: number): this; /** * @returns The number of items in this shared dictionary. */ diff --git a/ts/ngx_http_js_module.d.ts b/ts/ngx_http_js_module.d.ts index 37932893..d7dd1c9e 100644 --- a/ts/ngx_http_js_module.d.ts +++ b/ts/ngx_http_js_module.d.ts @@ -468,7 +468,7 @@ interface NginxHTTPRequest { /** * nginx variables as strings. * - * **Warning:** Bytes invalid in UTF-8 encoding may be converted into the replacement character. + * After 0.8.5 bytes invalid in UTF-8 encoding are converted into the replacement characters. * * @see rawVariables */ diff --git a/ts/ngx_stream_js_module.d.ts b/ts/ngx_stream_js_module.d.ts index 58c4d908..c78d008b 100644 --- a/ts/ngx_stream_js_module.d.ts +++ b/ts/ngx_stream_js_module.d.ts @@ -200,7 +200,7 @@ interface NginxStreamRequest { /** * nginx variables as strings. * - * **Warning:** Bytes invalid in UTF-8 encoding may be converted into the replacement character. + * After 0.8.5 bytes invalid in UTF-8 encoding are converted into the replacement characters. * * @see rawVariables */ diff --git a/ts/njs_core.d.ts b/ts/njs_core.d.ts index 2f1d45d0..f64c9576 100644 --- a/ts/njs_core.d.ts +++ b/ts/njs_core.d.ts @@ -581,6 +581,7 @@ interface NjsProcess { readonly env: NjsEnv; /** + * Send signal to a process by its PID. * @since 0.8.8 */ kill(pid: number, signal?: string | number): true; diff --git a/ts/njs_webcrypto.d.ts b/ts/njs_webcrypto.d.ts index 058359cb..4e6f2057 100644 --- a/ts/njs_webcrypto.d.ts +++ b/ts/njs_webcrypto.d.ts @@ -41,12 +41,12 @@ interface RsaHashedKeyGenParams { } interface EcKeyImportParams { - name: "ECDSA"; + name: "ECDSA" | "ECDH"; namedCurve: "P-256" | "P-384" | "P-521"; } interface EcKeyGenParams { - name: "ECDSA"; + name: "ECDSA" | "ECDH"; namedCurve: "P-256" | "P-384" | "P-521"; } @@ -68,7 +68,8 @@ type ImportAlgorithm = | AesImportParams | AesVariants | "PBKDF2" - | "HKDF"; + | "HKDF" + | "ECDH"; type GenerateAlgorithm = | RsaHashedKeyGenParams @@ -99,9 +100,15 @@ interface Pbkdf2Params { interations: number; } +interface EcdhParams { + name: "ECDH"; + public: CryptoKey; +} + type DeriveAlgorithm = | HkdfParams - | Pbkdf2Params; + | Pbkdf2Params + | EcdhParams; interface HmacKeyGenParams { name: "HMAC"; |