details: https://github.com/nginx/njs/commit/16f42c87dead030a26675b4c8eeea0b15b1e15f5 branches: master commit: 16f42c87dead030a26675b4c8eeea0b15b1e15f5 user: Dmitry Volyntsev <xei...@nginx.com> date: Wed, 13 Nov 2024 14:50:20 -0800 description: Allow to execute some code before cloning.
--- src/njs.h | 2 + src/njs_builtin.c | 4 + src/njs_object.c | 221 ++++++++++++++++++++++++++++++++++++++++++++++- src/njs_object.h | 1 + src/njs_object_prop.c | 1 + src/njs_vm.c | 31 +++++-- src/njs_vmcode.c | 2 + src/test/njs_unit_test.c | 63 +++++++++++++- 8 files changed, 310 insertions(+), 15 deletions(-) diff --git a/src/njs.h b/src/njs.h index 8809e295..06eb770c 100644 --- a/src/njs.h +++ b/src/njs.h @@ -139,6 +139,7 @@ typedef enum { NJS_ENUM_STRING = 8, NJS_ENUM_SYMBOL = 16, NJS_ENUM_ENUMERABLE_ONLY = 32, + NJS_ENUM_NON_SHARED_ONLY = 64, } njs_object_enum_t; @@ -302,6 +303,7 @@ NJS_EXPORT njs_mod_t *njs_vm_add_module(njs_vm_t *vm, njs_str_t *name, njs_value_t *value); NJS_EXPORT njs_mod_t *njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name, u_char **start, u_char *end); +NJS_EXPORT njs_int_t njs_vm_reuse(njs_vm_t *vm); NJS_EXPORT njs_vm_t *njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t external); NJS_EXPORT njs_int_t njs_vm_enqueue_job(njs_vm_t *vm, njs_function_t *function, diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 812f18d2..279c2158 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -873,6 +873,8 @@ njs_top_level_object(njs_vm_t *vm, njs_object_prop_t *self, if (njs_slow_path(object == NULL)) { return NJS_ERROR; } + + object->__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_OBJECT); } prop = njs_object_prop_alloc(vm, &self->name, retval, 1); @@ -920,6 +922,8 @@ njs_top_level_constructor(njs_vm_t *vm, njs_object_prop_t *self, ctor = &njs_vm_ctor(vm, njs_prop_magic16(self)); njs_set_function(retval, ctor); + + return NJS_OK; } prop = njs_object_prop_alloc(vm, &self->name, retval, 1); diff --git a/src/njs_object.c b/src/njs_object.c index 65e80945..b3eae9a8 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -65,7 +65,7 @@ njs_object_t * njs_object_value_copy(njs_vm_t *vm, njs_value_t *value) { size_t size; - njs_object_t *object; + njs_object_t *object, *proto; object = njs_object(value); @@ -73,13 +73,37 @@ njs_object_value_copy(njs_vm_t *vm, njs_value_t *value) return object; } - size = njs_is_object_value(value) ? sizeof(njs_object_value_t) - : sizeof(njs_object_t); + switch (object->type) { + case NJS_OBJECT: + size = sizeof(njs_object_t); + proto = (object->__proto__ != NULL) + ? njs_vm_proto(vm, NJS_OBJ_TYPE_OBJECT) + : NULL; + break; + case NJS_ARRAY: + size = sizeof(njs_array_t); + njs_assert_msg(!object->fast_array, + "shared fast_array is not supported"); + proto = (object->__proto__ != NULL) + ? njs_vm_proto(vm, NJS_OBJ_TYPE_ARRAY) + : NULL; + break; + case NJS_OBJECT_VALUE: + size = sizeof(njs_object_value_t); + proto = (object->__proto__ != NULL) + ? njs_vm_proto(vm, NJS_OBJ_TYPE_OBJECT) + : NULL; + break; + default: + njs_internal_error(vm, "unexpected object type to copy"); + return NULL; + } + object = njs_mp_alloc(vm->mem_pool, size); if (njs_fast_path(object != NULL)) { memcpy(object, njs_object(value), size); - object->__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_OBJECT); + object->__proto__ = proto; object->shared = 0; value->data.u.object = object; return object; @@ -917,6 +941,10 @@ njs_get_own_ordered_keys(njs_vm_t *vm, const njs_object_t *object, njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto); hash = &object->shared_hash; + if (flags & NJS_ENUM_NON_SHARED_ONLY) { + goto local_hash; + } + for ( ;; ) { prop = njs_lvlhsh_each(hash, &lhe); @@ -984,6 +1012,8 @@ njs_get_own_ordered_keys(njs_vm_t *vm, const njs_object_t *object, } } +local_hash: + njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto); hash = &object->hash; @@ -1187,6 +1217,189 @@ njs_traverse_visited(njs_arr_t *list, const njs_value_t *value) } +static njs_int_t +njs_object_copy_shared_hash(njs_vm_t *vm, njs_object_t *object) +{ + njs_int_t ret; + njs_flathsh_t new_hash, *shared_hash; + njs_object_prop_t *prop; + njs_flathsh_each_t fhe; + njs_flathsh_query_t fhq; + + fhq.replace = 0; + fhq.proto = &njs_object_hash_proto; + fhq.pool = vm->mem_pool; + + njs_flathsh_init(&new_hash); + shared_hash = &object->shared_hash; + + njs_flathsh_each_init(&fhe, &njs_object_hash_proto); + + for ( ;; ) { + prop = njs_flathsh_each(shared_hash, &fhe); + + if (prop == NULL) { + break; + } + + if (njs_is_symbol(&prop->name)) { + fhq.key_hash = njs_symbol_key(&prop->name); + fhq.key.start = NULL; + + } else { + njs_string_get(&prop->name, &fhq.key); + fhq.key_hash = njs_djb_hash(fhq.key.start, fhq.key.length); + } + + fhq.value = prop; + + ret = njs_flathsh_insert(&new_hash, &fhq); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "flathsh insert failed"); + return NJS_ERROR; + } + } + + object->shared_hash = new_hash; + + return NJS_OK; +} + + +njs_int_t +njs_object_make_shared(njs_vm_t *vm, njs_object_t *object) +{ + njs_int_t ret; + njs_arr_t visited; + njs_object_t **start; + njs_value_t value, *key; + njs_traverse_t *s; + njs_object_prop_t *prop; + njs_property_query_t pq; + njs_traverse_t state[NJS_TRAVERSE_MAX_DEPTH]; + + s = &state[0]; + s->parent = NULL; + s->index = 0; + njs_set_object(&s->value, object); + + s->keys = njs_value_own_enumerate(vm, &s->value, NJS_ENUM_KEYS + | NJS_ENUM_STRING + | NJS_ENUM_NON_SHARED_ONLY); + if (njs_slow_path(s->keys == NULL)) { + return NJS_ERROR; + } + + if (s->keys->length != 0 + && !njs_flathsh_is_empty(&object->shared_hash)) + { + /* + * object->shared_hash can be shared with other objects + * and we do not want to modify other objects. + */ + + ret = njs_object_copy_shared_hash(vm, object); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } + + start = njs_arr_init(vm->mem_pool, &visited, NULL, 8, sizeof(void *)); + if (njs_slow_path(start == NULL)) { + return NJS_ERROR; + } + + (void) njs_traverse_visit(&visited, &s->value); + + pq.lhq.replace = 0; + pq.lhq.pool = vm->mem_pool; + + for ( ;; ) { + + if (s->index >= s->keys->length) { + njs_flathsh_init(&njs_object(&s->value)->hash); + njs_object(&s->value)->shared = 1; + njs_array_destroy(vm, s->keys); + s->keys = NULL; + + if (s == &state[0]) { + goto done; + } + + s--; + continue; + } + + + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0, 0); + key = &s->keys->start[s->index++]; + + ret = njs_property_query(vm, &pq, &s->value, key); + if (njs_slow_path(ret != NJS_OK)) { + if (ret == NJS_DECLINED) { + continue; + } + + return NJS_ERROR; + } + + + prop = pq.lhq.value; + + ret = njs_flathsh_insert(&njs_object(&s->value)->shared_hash, &pq.lhq); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "flathsh insert failed"); + return NJS_ERROR; + } + + njs_value_assign(&value, njs_prop_value(prop)); + + if (njs_is_object(&value) + && !njs_object(&value)->shared + && !njs_traverse_visited(&visited, &value)) + { + ret = njs_traverse_visit(&visited, &value); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + if (s == &state[NJS_TRAVERSE_MAX_DEPTH - 1]) { + njs_type_error(vm, "njs_object_traverse() recursion limit:%d", + NJS_TRAVERSE_MAX_DEPTH); + return NJS_ERROR; + } + + s++; + s->prop = NULL; + s->parent = &s[-1]; + s->index = 0; + njs_value_assign(&s->value, &value); + s->keys = njs_value_own_enumerate(vm, &s->value, NJS_ENUM_KEYS + | NJS_ENUM_STRING + | NJS_ENUM_NON_SHARED_ONLY); + if (njs_slow_path(s->keys == NULL)) { + return NJS_ERROR; + } + + if (s->keys->length != 0 + && !njs_flathsh_is_empty(&njs_object(&s->value)->shared_hash)) + { + ret = njs_object_copy_shared_hash(vm, njs_object(&s->value)); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } + } + } + +done: + + njs_arr_destroy(&visited); + + return NJS_OK; +} + + njs_int_t njs_object_traverse(njs_vm_t *vm, njs_object_t *object, void *ctx, njs_object_traverse_cb_t cb) diff --git a/src/njs_object.h b/src/njs_object.h index c6ae14b8..e3f58fdd 100644 --- a/src/njs_object.h +++ b/src/njs_object.h @@ -72,6 +72,7 @@ njs_array_t *njs_object_own_enumerate(njs_vm_t *vm, const njs_object_t *object, uint32_t flags); njs_int_t njs_object_traverse(njs_vm_t *vm, njs_object_t *object, void *ctx, njs_object_traverse_cb_t cb); +njs_int_t njs_object_make_shared(njs_vm_t *vm, njs_object_t *object); njs_int_t njs_object_hash_create(njs_vm_t *vm, njs_lvlhsh_t *hash, const njs_object_prop_t *prop, njs_uint_t n); njs_int_t njs_primitive_prototype_get_proto(njs_vm_t *vm, diff --git a/src/njs_object_prop.c b/src/njs_object_prop.c index c9147bba..a24af753 100644 --- a/src/njs_object_prop.c +++ b/src/njs_object_prop.c @@ -694,6 +694,7 @@ njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq, switch (value->type) { case NJS_OBJECT: + case NJS_ARRAY: case NJS_OBJECT_VALUE: object = njs_object_value_copy(vm, value); if (njs_slow_path(object == NULL)) { diff --git a/src/njs_vm.c b/src/njs_vm.c index 90428cb4..dbeffa51 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -378,6 +378,17 @@ njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name, u_char **start, } +njs_int_t +njs_vm_reuse(njs_vm_t *vm) +{ + vm->active_frame = NULL; + vm->top_frame = NULL; + vm->modules = NULL; + + return njs_object_make_shared(vm, njs_object(&vm->global_value)); +} + + njs_vm_t * njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t external) { @@ -464,17 +475,19 @@ njs_vm_runtime_init(njs_vm_t *vm) njs_int_t ret; njs_frame_t *frame; - frame = (njs_frame_t *) njs_function_frame_alloc(vm, NJS_FRAME_SIZE); - if (njs_slow_path(frame == NULL)) { - njs_memory_error(vm); - return NJS_ERROR; - } + if (vm->active_frame == NULL) { + frame = (njs_frame_t *) njs_function_frame_alloc(vm, NJS_FRAME_SIZE); + if (njs_slow_path(frame == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } - frame->exception.catch = NULL; - frame->exception.next = NULL; - frame->previous_active_frame = NULL; + frame->exception.catch = NULL; + frame->exception.next = NULL; + frame->previous_active_frame = NULL; - vm->active_frame = frame; + vm->active_frame = frame; + } ret = njs_regexp_init(vm); if (njs_slow_path(ret != NJS_OK)) { diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 838e2821..dbf7b116 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -2600,6 +2600,8 @@ njs_vmcode_import(njs_vm_t *vm, njs_mod_t *module, njs_value_t *retval) njs_memzero(m->start, m->items * sizeof(njs_value_t)); } + njs_assert(module->index < vm->modules->items); + value = njs_arr_item(vm->modules, module->index); if (!njs_is_null(value)) { diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index c4dc6dde..d6ae4ed8 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -21863,6 +21863,27 @@ static njs_unit_test_t njs_shared_test[] = "cr.abc = 1; cr.abc"), njs_str("1") }, #endif + + { njs_str("JSON.stringify(preload)"), + njs_str("{\"a\":[1,{\"b\":2,\"c\":3}]}") }, + + { njs_str("preload.a.prop = 1"), + njs_str("TypeError: Cannot add property \"prop\", object is not extensible\n" + " at main (:1)\n") }, + + { njs_str("preload.a[0] = 2"), + njs_str("TypeError: Cannot assign to read-only property \"0\" of array\n" + " at main (:1)\n") }, + + { njs_str("preload.a.push(2)"), + njs_str("TypeError: (intermediate value)[\"push\"] is not a function\n" + " at main (:1)\n") }, + + { njs_str("Array.prototype.push.call(preload.a, 'waka')"), + njs_str("TypeError: Cannot add property \"2\", object is not extensible\n" + " at Array.prototype.push (native)\n" + " at Function.prototype.call (native)\n" + " at main (:1)\n") }, }; @@ -22400,6 +22421,7 @@ typedef struct { njs_bool_t backtrace; njs_bool_t handler; njs_bool_t async; + njs_bool_t preload; unsigned seed; } njs_opts_t; @@ -22701,8 +22723,21 @@ njs_unit_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, njs_stat_t prev; njs_vm_opt_t options; njs_runtime_t *rt; + njs_opaque_value_t retval; njs_external_state_t *state; + njs_str_t preload = njs_str( + "globalThis.preload = JSON.parse(" + "'{\"a\": [1, {\"b\": 2, \"c\": 3}]}'," + "function (k, v) {" + "if (v instanceof Object) {" + "Object.freeze(Object.setPrototypeOf(v, null));" + "}" + "return v;" + "}" + ");" + ); + vm = NULL; rt = NULL; @@ -22718,6 +22753,7 @@ njs_unit_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, njs_vm_opt_init(&options); + options.init = opts->preload; options.module = opts->module; options.unsafe = opts->unsafe; options.backtrace = opts->backtrace; @@ -22730,6 +22766,29 @@ njs_unit_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, goto done; } + if (opts->preload) { + start = preload.start; + end = start + preload.length; + + ret = njs_vm_compile(vm, &start, end); + if (ret != NJS_OK) { + njs_printf("njs_vm_compile() preload failed\n"); + goto done; + } + + ret = njs_vm_start(vm, njs_value_arg(&retval)); + if (ret != NJS_OK) { + njs_printf("njs_vm_start() preload failed\n"); + goto done; + } + + ret = njs_vm_reuse(vm); + if (ret != NJS_OK) { + njs_printf("njs_vm_reuse() failed\n"); + goto done; + } + } + start = tests[i].script.start; end = start + tests[i].script.length; @@ -23953,7 +24012,7 @@ njs_disabled_denormals_tests(njs_unit_test_t tests[], size_t num, static njs_test_suite_t njs_suites[] = { { njs_str("script"), - { .repeat = 1, .unsafe = 1 }, + { .repeat = 1, .unsafe = 1, .preload = 1 }, njs_test, njs_nitems(njs_test), njs_unit_test }, @@ -24040,7 +24099,7 @@ static njs_test_suite_t njs_suites[] = njs_unit_test }, { njs_str("shared"), - { .externals = 1, .repeat = 128, .seed = 42, .unsafe = 1, .backtrace = 1 }, + { .externals = 1, .repeat = 128, .seed = 42, .unsafe = 1, .preload = 1, .backtrace = 1 }, njs_shared_test, njs_nitems(njs_shared_test), njs_unit_test }, _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel