[njs] Linking global variables and global object properties.

Dmitry Volyntsev xeioex at nginx.com
Wed Apr 8 13:15:57 UTC 2020


details:   https://hg.nginx.org/njs/rev/7ccb8b32cc02
branches:  
changeset: 1367:7ccb8b32cc02
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Wed Apr 08 13:15:02 2020 +0000
description:
Linking global variables and global object properties.

diffstat:

 src/njs.h                |    3 +-
 src/njs_array.c          |    4 +
 src/njs_builtin.c        |   65 +++++++++++++++
 src/njs_object_prop.c    |   37 +++++---
 src/njs_variable.c       |   47 -----------
 src/njs_vm.c             |   59 ++++++++++++++
 src/njs_vm.h             |    2 +
 src/test/njs_unit_test.c |  193 ++++++++++++++++++++++++++++++++++++++++++++++-
 8 files changed, 345 insertions(+), 65 deletions(-)

diffs (578 lines):

diff -r 9812e7d99e7c -r 7ccb8b32cc02 src/njs.h
--- a/src/njs.h	Fri Apr 03 16:10:00 2020 +0000
+++ b/src/njs.h	Wed Apr 08 13:15:02 2020 +0000
@@ -294,7 +294,8 @@ NJS_EXPORT void njs_disassemble(u_char *
 
 NJS_EXPORT 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_EXPORT const njs_value_t *njs_vm_value(njs_vm_t *vm, const njs_str_t *name);
+NJS_EXPORT njs_int_t njs_vm_value(njs_vm_t *vm, const njs_str_t *path,
+    njs_value_t *retval);
 NJS_EXPORT njs_function_t *njs_vm_function(njs_vm_t *vm, const njs_str_t *name);
 
 NJS_EXPORT njs_value_t *njs_vm_retval(njs_vm_t *vm);
diff -r 9812e7d99e7c -r 7ccb8b32cc02 src/njs_array.c
--- a/src/njs_array.c	Fri Apr 03 16:10:00 2020 +0000
+++ b/src/njs_array.c	Wed Apr 08 13:15:02 2020 +0000
@@ -519,6 +519,10 @@ njs_array_length(njs_vm_t *vm,njs_object
         return NJS_DECLINED;
     }
 
+    if (njs_slow_path(!njs_is_valid(setval))) {
+        return NJS_DECLINED;
+    }
+
     ret = njs_value_to_number(vm, setval, &num);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
diff -r 9812e7d99e7c -r 7ccb8b32cc02 src/njs_builtin.c
--- a/src/njs_builtin.c	Fri Apr 03 16:10:00 2020 +0000
+++ b/src/njs_builtin.c	Wed Apr 08 13:15:02 2020 +0000
@@ -22,6 +22,9 @@ typedef struct {
 } njs_builtin_traverse_t;
 
 
+static njs_int_t njs_global_this_prop_handler(njs_vm_t *vm,
+    njs_object_prop_t *self, njs_value_t *global, njs_value_t *setval,
+    njs_value_t *retval);
 static njs_arr_t *njs_vm_expression_completions(njs_vm_t *vm,
     njs_str_t *expression);
 static njs_arr_t *njs_object_completions(njs_vm_t *vm, njs_value_t *object);
@@ -288,6 +291,13 @@ njs_builtin_objects_create(njs_vm_t *vm)
         }
     }
 
+    shared->global_slots.prop_handler = njs_global_this_prop_handler;
+    shared->global_slots.writable = 1;
+    shared->global_slots.configurable = 1;
+    shared->global_slots.enumerable = 1;
+
+    shared->objects[0].slots = &shared->global_slots;
+
     vm->global_object = shared->objects[0];
     vm->global_object.shared = 0;
 
@@ -778,6 +788,50 @@ njs_dump_value(njs_vm_t *vm, njs_value_t
 
 
 static njs_int_t
+njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *global, njs_value_t *setval, njs_value_t *retval)
+{
+    njs_int_t            ret;
+    njs_value_t          *value;
+    njs_rbtree_node_t    *rb_node;
+    njs_lvlhsh_query_t   lhq;
+    njs_variable_node_t  *node, var_node;
+
+    if (retval == NULL) {
+        return NJS_DECLINED;
+    }
+
+    njs_string_get(&prop->name, &lhq.key);
+    lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length);
+    lhq.proto = &njs_lexer_hash_proto;
+
+    ret = njs_lvlhsh_find(&vm->shared->keywords_hash, &lhq);
+
+    if (njs_slow_path(ret != NJS_OK || lhq.value == NULL)) {
+        return NJS_DECLINED;
+    }
+
+    var_node.key = (uintptr_t) lhq.value;
+
+    rb_node = njs_rbtree_find(vm->variables_hash, &var_node.node);
+    if (rb_node == NULL) {
+        return NJS_DECLINED;
+    }
+
+    node = (njs_variable_node_t *) rb_node;
+    value = njs_vmcode_operand(vm, node->variable->index);
+
+    if (setval != NULL) {
+        *value = *setval;
+    }
+
+    *retval = *value;
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
 njs_global_this_object(njs_vm_t *vm, njs_object_prop_t *self,
     njs_value_t *global, njs_value_t *setval, njs_value_t *retval)
 {
@@ -789,6 +843,9 @@ njs_global_this_object(njs_vm_t *vm, njs
 
     if (njs_slow_path(setval != NULL)) {
         *retval = *setval;
+
+    } else if (njs_slow_path(retval == NULL)) {
+        return NJS_DECLINED;
     }
 
     prop = njs_object_prop_alloc(vm, &self->name, retval, 1);
@@ -831,6 +888,10 @@ njs_top_level_object(njs_vm_t *vm, njs_o
         *retval = *setval;
 
     } else {
+        if (njs_slow_path(retval == NULL)) {
+            return NJS_DECLINED;
+        }
+
         njs_set_object(retval, &vm->shared->objects[self->value.data.magic16]);
 
         object = njs_object_value_copy(vm, retval);
@@ -879,6 +940,10 @@ njs_top_level_constructor(njs_vm_t *vm, 
         *retval = *setval;
 
     } else {
+        if (njs_slow_path(retval == NULL)) {
+            return NJS_DECLINED;
+        }
+
         ctor = &vm->constructors[self->value.data.magic16];
 
         njs_set_function(retval, ctor);
diff -r 9812e7d99e7c -r 7ccb8b32cc02 src/njs_object_prop.c
--- a/src/njs_object_prop.c	Fri Apr 03 16:10:00 2020 +0000
+++ b/src/njs_object_prop.c	Wed Apr 08 13:15:02 2020 +0000
@@ -196,6 +196,8 @@ again:
 
     if (njs_fast_path(ret == NJS_DECLINED)) {
 
+set_prop:
+
         if (!njs_object(object)->extensible) {
             njs_key_string_get(vm, &pq.key,  &pq.lhq.key);
             njs_type_error(vm, "Cannot add property \"%V\", "
@@ -412,28 +414,20 @@ again:
 
 done:
 
-    /*
-     * 9. For each field of Desc that is present, set the corresponding
-     * attribute of the property named P of object O to the value of the field.
-     */
-
-    if (njs_is_valid(&prop->getter)) {
-        prev->getter = prop->getter;
-    }
-
-    if (njs_is_valid(&prop->setter)) {
-        prev->setter = prop->setter;
-    }
-
-    if (njs_is_valid(&prop->value)) {
+    if (njs_is_valid(&prop->value) || njs_is_accessor_descriptor(prop)) {
         if (prev->type == NJS_PROPERTY_HANDLER) {
-            if (njs_is_data_descriptor(prev) && prev->writable) {
+            if (prev->writable) {
                 ret = prev->value.data.u.prop_handler(vm, prev, object,
                                                       &prop->value,
                                                       &vm->retval);
                 if (njs_slow_path(ret == NJS_ERROR)) {
                     return ret;
                 }
+
+                if (ret == NJS_DECLINED) {
+                    pq.lhq.value = NULL;
+                    goto set_prop;
+                }
             }
 
         } else {
@@ -450,6 +444,19 @@ done:
         }
     }
 
+    /*
+     * 9. For each field of Desc that is present, set the corresponding
+     * attribute of the property named P of object O to the value of the field.
+     */
+
+    if (njs_is_valid(&prop->getter)) {
+        prev->getter = prop->getter;
+    }
+
+    if (njs_is_valid(&prop->setter)) {
+        prev->setter = prop->setter;
+    }
+
     if (prop->writable != NJS_ATTRIBUTE_UNSET) {
         prev->writable = prop->writable;
     }
diff -r 9812e7d99e7c -r 7ccb8b32cc02 src/njs_variable.c
--- a/src/njs_variable.c	Fri Apr 03 16:10:00 2020 +0000
+++ b/src/njs_variable.c	Wed Apr 08 13:15:02 2020 +0000
@@ -587,50 +587,3 @@ njs_name_copy(njs_vm_t *vm, njs_str_t *d
 
     return NJS_ERROR;
 }
-
-
-const njs_value_t *
-njs_vm_value(njs_vm_t *vm, const njs_str_t *name)
-{
-    njs_int_t            ret;
-    njs_rbtree_node_t    *rb_node;
-    njs_lvlhsh_query_t   lhq;
-    njs_variable_node_t  *node, var_node;
-
-    lhq.key = *name;
-    lhq.key_hash = njs_djb_hash(name->start, name->length);
-    lhq.proto = &njs_lexer_hash_proto;
-
-    ret = njs_lvlhsh_find(&vm->shared->keywords_hash, &lhq);
-
-    if (njs_slow_path(ret != NJS_OK || lhq.value == NULL)) {
-        return &njs_value_undefined;
-    }
-
-    var_node.key = (uintptr_t) lhq.value;
-
-    rb_node = njs_rbtree_find(vm->variables_hash, &var_node.node);
-
-    if (rb_node != NULL) {
-        node = (njs_variable_node_t *) rb_node;
-
-        return njs_vmcode_operand(vm, node->variable->index);
-    }
-
-    return &njs_value_undefined;
-}
-
-
-njs_function_t *
-njs_vm_function(njs_vm_t *vm, const njs_str_t *name)
-{
-    const njs_value_t  *value;
-
-    value = njs_vm_value(vm, name);
-
-    if (njs_is_function(value)) {
-        return njs_function(value);
-    }
-
-    return NULL;
-}
diff -r 9812e7d99e7c -r 7ccb8b32cc02 src/njs_vm.c
--- a/src/njs_vm.c	Fri Apr 03 16:10:00 2020 +0000
+++ b/src/njs_vm.c	Wed Apr 08 13:15:02 2020 +0000
@@ -580,6 +580,50 @@ njs_vm_retval_set(njs_vm_t *vm, const nj
 
 
 njs_int_t
+njs_vm_value(njs_vm_t *vm, const njs_str_t *path, njs_value_t *retval)
+{
+    u_char       *start, *p, *end;
+    size_t       size;
+    njs_int_t    ret;
+    njs_value_t  value, key;
+
+    start = path->start;
+    end = start + path->length;
+
+    njs_set_object(&value, &vm->global_object);
+
+    for ( ;; ) {
+        p = njs_strchr(start, '.');
+
+        size = ((p != NULL) ? p : end) - start;
+        if (njs_slow_path(size == 0)) {
+            njs_type_error(vm, "empty path element");
+            return NJS_ERROR;
+        }
+
+        ret = njs_string_set(vm, &key, start, size);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_value_property(vm, &value, &key, njs_value_arg(retval));
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return NJS_ERROR;
+        }
+
+        if (p == NULL) {
+            break;
+        }
+
+        start = p + 1;
+        value = *retval;
+    }
+
+    return NJS_OK;
+}
+
+
+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)
 {
@@ -634,6 +678,21 @@ njs_vm_value_string_alloc(njs_vm_t *vm, 
 }
 
 
+njs_function_t *
+njs_vm_function(njs_vm_t *vm, const njs_str_t *path)
+{
+    njs_int_t    ret;
+    njs_value_t  retval;
+
+    ret = njs_vm_value(vm, path, &retval);
+    if (njs_slow_path(ret != NJS_OK || !njs_is_function(&retval))) {
+        return NULL;
+    }
+
+    return njs_function(&retval);
+}
+
+
 uint16_t
 njs_vm_prop_magic16(njs_object_prop_t *prop)
 {
diff -r 9812e7d99e7c -r 7ccb8b32cc02 src/njs_vm.h
--- a/src/njs_vm.h	Fri Apr 03 16:10:00 2020 +0000
+++ b/src/njs_vm.h	Wed Apr 08 13:15:02 2020 +0000
@@ -272,6 +272,8 @@ struct njs_vm_shared_s {
     njs_object_t             string_object;
     njs_object_t             objects[NJS_OBJECT_MAX];
 
+    njs_exotic_slots_t       global_slots;
+
     /*
      * The prototypes and constructors arrays must be togther because they are
      * copied to njs_vm_t by single memcpy() in njs_builtin_objects_clone().
diff -r 9812e7d99e7c -r 7ccb8b32cc02 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Fri Apr 03 16:10:00 2020 +0000
+++ b/src/test/njs_unit_test.c	Wed Apr 08 13:15:02 2020 +0000
@@ -10174,6 +10174,41 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("this.a = ()=>1; a()"),
       njs_str("1") },
 
+    { njs_str("var a = 1; this.a = 42; a"),
+      njs_str("42") },
+
+    { njs_str("var a = 1; global.a = 42; a"),
+      njs_str("42") },
+
+    { njs_str("var a = 1; globalThis.a = 42; a"),
+      njs_str("42") },
+
+    { njs_str("global.a = 1; globalThis.a"),
+      njs_str("1") },
+
+    { njs_str("var a = {}; globalThis.a === a"),
+      njs_str("true") },
+
+    { njs_str("globalThis.a = 1; var a; a"),
+      njs_str("1") },
+
+    { njs_str("var count = 0; function f() {return ++count}; [f(), global.f()]"),
+      njs_str("1,2") },
+
+    { njs_str("Object.defineProperty(global, 'a', {value:1}); a"),
+      njs_str("1") },
+
+    { njs_str("Object.defineProperty(global, 'a', {get:()=>123}); a"),
+      njs_str("123") },
+
+    { njs_str("Object.defineProperties(global, {a:{value:1}, b:{value:2}}); [a,b]"),
+      njs_str("1,2") },
+
+#if 0 /* FIXME: for scope. */
+    { njs_str("var r1 = global.a; for (var a = 1; false;) {}; [r1, global.a]"),
+      njs_str(",") },
+#endif
+
     { njs_str("var global = this;"
               "function isImmutableConstant(v) {"
               "    var d = Object.getOwnPropertyDescriptor(global, v);"
@@ -11455,6 +11490,10 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("(new Function('return this'))() === globalThis"),
       njs_str("true") },
 
+    { njs_str("var f = Function.call(this, 'return this.a');"
+              "var r1 = f(); var a = 1; var r2 = f(); [r1,r2]"),
+      njs_str(",1") },
+
     { njs_str("(new Function('a', 'return a')).length"),
       njs_str("1") },
 
@@ -17373,8 +17412,8 @@ njs_vm_json_test(njs_opts_t *opts, njs_s
         }
 
         args[0] = vm->retval;
-        args[1] = *njs_vm_value(vm, &fname);
-        args[2] = *njs_vm_value(vm, &iname);
+        njs_vm_value(vm, &fname, &args[1]);
+        njs_vm_value(vm, &iname, &args[2]);
 
         ret = njs_vm_json_stringify(vm, args, 3);
         if (ret != NJS_OK) {
@@ -17429,6 +17468,151 @@ done:
 
 
 static njs_int_t
+njs_vm_value_test(njs_opts_t *opts, njs_stat_t *stat)
+{
+    njs_vm_t      *vm;
+    njs_int_t     ret;
+    njs_str_t     s, *script;
+    njs_uint_t    i;
+    njs_bool_t    success;
+    njs_stat_t    prev;
+    njs_vm_opt_t  options;
+
+    static struct {
+        njs_str_t   script;
+        njs_str_t   path;
+        njs_str_t   ret;
+    } tests[] = {
+        {
+          .script = njs_str("var o = {a:1}"),
+          .path = njs_str("o.a"),
+          .ret = njs_str("1"),
+        },
+
+        {
+          .script = njs_str("var aaaaabbbbbcccccddddd = {e:2}"),
+          .path = njs_str("aaaaabbbbbcccccddddd.e"),
+          .ret = njs_str("2"),
+        },
+
+        {
+          .script = njs_str("var o = {a:{b:3}}"),
+          .path = njs_str("o.a.b"),
+          .ret = njs_str("3"),
+        },
+
+        {
+          .script = njs_str("var o = 1"),
+          .path = njs_str("o.a"),
+          .ret = njs_str("undefined"),
+        },
+
+        {
+          .script = njs_str(""),
+          .path = njs_str("o"),
+          .ret = njs_str("undefined"),
+        },
+
+        {
+          .script = njs_str("var o = {'':1}"),
+          .path = njs_str("."),
+          .ret = njs_str("TypeError: empty path element"),
+        },
+
+        {
+          .script = njs_str("var o = {'':1}"),
+          .path = njs_str("o."),
+          .ret = njs_str("TypeError: empty path element"),
+        },
+        {
+          .script = njs_str("var o = {'':1}"),
+          .path = njs_str("o.."),
+          .ret = njs_str("TypeError: empty path element"),
+        },
+    };
+
+    vm = NULL;
+
+    prev = *stat;
+
+    ret = NJS_ERROR;
+
+    for (i = 0; i < njs_nitems(tests); i++) {
+
+        memset(&options, 0, sizeof(njs_vm_opt_t));
+        options.init = 1;
+
+        vm = njs_vm_create(&options);
+        if (vm == NULL) {
+            njs_printf("njs_vm_create() failed\n");
+            goto done;
+        }
+
+        script = &tests[i].script;
+
+        ret = njs_vm_compile(vm, &script->start,
+                             script->start + script->length);
+
+        if (ret != NJS_OK) {
+            njs_printf("njs_vm_compile() failed\n");
+            goto done;
+        }
+
+        ret = njs_vm_start(vm);
+        if (ret != NJS_OK) {
+            njs_printf("njs_vm_run() failed\n");
+            goto done;
+        }
+
+        ret = njs_vm_value(vm, &tests[i].path, &vm->retval);
+
+        if (njs_vm_retval_string(vm, &s) != NJS_OK) {
+            njs_printf("njs_vm_retval_string() failed\n");
+            goto done;
+        }
+
+        success = njs_strstr_eq(&tests[i].ret, &s);
+
+        if (!success) {
+            njs_printf("njs_vm_value_test(\"%V\")\n"
+                       "expected: \"%V\"\n     got: \"%V\"\n", script,
+                       &tests[i].ret, &s);
+
+            stat->failed++;
+
+        } else {
+            stat->passed++;
+        }
+
+        njs_vm_destroy(vm);
+        vm = NULL;
+
+    }
+
+    ret = NJS_OK;
+
+done:
+
+    if (ret != NJS_OK) {
+        if (njs_vm_retval_string(vm, &s) != NJS_OK) {
+            njs_printf("njs_vm_retval_string() failed\n");
+
+        } else {
+            njs_printf("%V\n", &s);
+        }
+    }
+
+    njs_unit_test_report("njs_vm_value() tests", &prev, stat);
+
+    if (vm != NULL) {
+        njs_vm_destroy(vm);
+    }
+
+    return ret;
+}
+
+
+static njs_int_t
 njs_vm_object_alloc_test(njs_vm_t *vm, njs_opts_t *opts, njs_stat_t *stat)
 {
     njs_int_t    ret;
@@ -17931,6 +18115,11 @@ main(int argc, char **argv)
         return ret;
     }
 
+    ret = njs_vm_value_test(&opts, &stat);
+    if (ret != NJS_OK) {
+        return ret;
+    }
+
     ret = njs_api_test(&opts, &stat);
     if (ret != NJS_OK) {
         return ret;


More information about the nginx-devel mailing list