[njs] Allow to execute some code before cloning.
noreply at nginx.com
noreply at nginx.com
Fri Nov 22 18:54:03 UTC 2024
details: https://github.com/nginx/njs/commit/16f42c87dead030a26675b4c8eeea0b15b1e15f5
branches: master
commit: 16f42c87dead030a26675b4c8eeea0b15b1e15f5
user: Dmitry Volyntsev <xeioex at 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 },
More information about the nginx-devel
mailing list