[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