diff options
-rw-r--r-- | src/njs.h | 3 | ||||
-rw-r--r-- | src/njs_array.c | 4 | ||||
-rw-r--r-- | src/njs_builtin.c | 65 | ||||
-rw-r--r-- | src/njs_object_prop.c | 37 | ||||
-rw-r--r-- | src/njs_variable.c | 47 | ||||
-rw-r--r-- | src/njs_vm.c | 59 | ||||
-rw-r--r-- | src/njs_vm.h | 2 | ||||
-rw-r--r-- | src/test/njs_unit_test.c | 193 |
8 files changed, 345 insertions, 65 deletions
@@ -294,7 +294,8 @@ NJS_EXPORT void njs_disassemble(u_char *start, u_char *end); 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 --git a/src/njs_array.c b/src/njs_array.c index 9c983b83..13be922f 100644 --- a/src/njs_array.c +++ b/src/njs_array.c @@ -519,6 +519,10 @@ njs_array_length(njs_vm_t *vm,njs_object_prop_t *prop, njs_value_t *value, 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 --git a/src/njs_builtin.c b/src/njs_builtin.c index d948c02c..b1a1cce7 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -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 *args, njs_uint_t nargs, 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_object_prop_t *self, 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_object_prop_t *self, *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, njs_object_prop_t *self, *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 --git a/src/njs_object_prop.c b/src/njs_object_prop.c index 4ea537db..6c6340a4 100644 --- a/src/njs_object_prop.c +++ b/src/njs_object_prop.c @@ -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 --git a/src/njs_variable.c b/src/njs_variable.c index b42b26fa..a52b0f5e 100644 --- a/src/njs_variable.c +++ b/src/njs_variable.c @@ -587,50 +587,3 @@ njs_name_copy(njs_vm_t *vm, njs_str_t *dst, const njs_str_t *src) 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 --git a/src/njs_vm.c b/src/njs_vm.c index 2827ad46..4d98e402 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -580,6 +580,50 @@ njs_vm_retval_set(njs_vm_t *vm, const njs_value_t *value) 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_value_t *value, uint32_t size) } +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 --git a/src/njs_vm.h b/src/njs_vm.h index f97f863a..05afebbe 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -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 --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 526ea84a..37dd63a7 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -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_stat_t *stat) } 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; |