[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