details: https://hg.nginx.org/njs/rev/2488363b220b branches: changeset: 1239:2488363b220b user: Dmitry Volyntsev <xei...@nginx.com> date: Thu Nov 14 21:06:48 2019 +0300 description: Reimplemented "bound" functions according to specification.
This closes #202 and closes #204 issues on Github. diffstat: src/njs_builtin.c | 4 +- src/njs_function.c | 151 +++++++++++++++++++++++++++++++++++++++++----- src/njs_function.h | 2 + src/njs_object_prop.c | 29 +------- src/njs_value.h | 1 + src/njs_vm.c | 7 +- src/njs_vm.h | 2 +- src/njs_vmcode.c | 25 ++++++- src/test/njs_unit_test.c | 60 ++++++++++++++++++ test/njs_expect_test.exp | 2 + 10 files changed, 233 insertions(+), 50 deletions(-) diffs (495 lines): diff -r 4d414fccd53d -r 2488363b220b src/njs_builtin.c --- a/src/njs_builtin.c Thu Nov 14 21:01:28 2019 +0300 +++ b/src/njs_builtin.c Thu Nov 14 21:06:48 2019 +0300 @@ -761,7 +761,7 @@ njs_object_completions(njs_vm_t *vm, njs njs_int_t -njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function, +njs_builtin_match_native_function(njs_vm_t *vm, njs_function_native_t func, njs_str_t *name) { njs_int_t ret; @@ -772,7 +772,7 @@ njs_builtin_match_native_function(njs_vm njs_builtin_traverse_t ctx; ctx.type = NJS_BUILTIN_TRAVERSE_MATCH; - ctx.native = function->u.native; + ctx.native = func; /* Global object. */ diff -r 4d414fccd53d -r 2488363b220b src/njs_function.c --- a/src/njs_function.c Thu Nov 14 21:01:28 2019 +0300 +++ b/src/njs_function.c Thu Nov 14 21:06:48 2019 +0300 @@ -111,6 +111,53 @@ njs_function_closures(njs_vm_t *vm, njs_ } +njs_int_t +njs_function_name_set(njs_vm_t *vm, njs_function_t *function, + njs_value_t *name, njs_bool_t bound) +{ + u_char *start; + njs_int_t ret; + njs_string_prop_t string; + njs_object_prop_t *prop; + njs_lvlhsh_query_t lhq; + + prop = njs_object_prop_alloc(vm, &njs_string_name, name, 0); + if (njs_slow_path(name == NULL)) { + return NJS_ERROR; + } + + if (bound) { + (void) njs_string_prop(&string, name); + + start = njs_string_alloc(vm, &prop->value, string.size + 6, + string.length + 6); + if (njs_slow_path(start == NULL)) { + return NJS_ERROR; + } + + start = njs_cpymem(start, "bound ", 6); + memcpy(start, string.start, string.size); + } + + prop->configurable = 1; + + lhq.key_hash = NJS_NAME_HASH; + lhq.key = njs_str_value("name"); + lhq.replace = 0; + lhq.value = prop; + lhq.proto = &njs_object_hash_proto; + lhq.pool = vm->mem_pool; + + ret = njs_lvlhsh_insert(&function->object.hash, &lhq); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NJS_ERROR; + } + + return NJS_OK; +} + + static njs_function_t * njs_function_copy(njs_vm_t *vm, njs_function_t *function) { @@ -347,10 +394,32 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_uint_t n, max_args, closures; njs_value_t *value, *bound; njs_frame_t *frame; + njs_function_t *target; njs_native_frame_t *native_frame; njs_function_lambda_t *lambda; - lambda = function->u.lambda; + bound = function->bound; + + if (njs_fast_path(bound == NULL)) { + lambda = function->u.lambda; + target = function; + + } else { + target = function->u.bound_target; + + if (njs_slow_path(target->bound != NULL)) { + + /* + * FIXME: bound functions should call target function with + * bound "this" and bound args. + */ + + njs_internal_error(vm, "chain of bound function are not supported"); + return NJS_ERROR; + } + + lambda = target->u.lambda; + } max_args = njs_max(nargs, lambda->nargs); @@ -365,7 +434,7 @@ njs_function_lambda_frame(njs_vm_t *vm, return NJS_ERROR; } - native_frame->function = function; + native_frame->function = target; native_frame->nargs = nargs; native_frame->ctor = ctor; @@ -375,8 +444,6 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_frame_size(closures)); native_frame->arguments = value; - bound = function->bound; - if (bound == NULL) { *value++ = *this; @@ -384,10 +451,16 @@ njs_function_lambda_frame(njs_vm_t *vm, n = function->args_offset; native_frame->nargs += n - 1; - do { + if (ctor) { + *value++ = *this; + bound++; + n--; + } + + while (n != 0) { *value++ = *bound++; n--; - } while (n != 0); + }; } vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = value; @@ -580,18 +653,32 @@ njs_function_lambda_call(njs_vm_t *vm) njs_int_t njs_function_native_call(njs_vm_t *vm) { - njs_int_t ret; - njs_value_t *value; - njs_frame_t *frame; - njs_function_t *function; - njs_native_frame_t *native, *previous; + njs_int_t ret; + njs_value_t *value; + njs_frame_t *frame; + njs_function_t *function, *target; + njs_native_frame_t *native, *previous; + njs_function_native_t call; native = vm->top_frame; frame = (njs_frame_t *) native; function = native->function; - ret = function->u.native(vm, native->arguments, native->nargs, - function->magic); + if (njs_fast_path(function->bound == NULL)) { + call = function->u.native; + + } else { + target = function->u.bound_target; + + if (njs_slow_path(target->bound != NULL)) { + njs_internal_error(vm, "chain of bound function are not supported"); + return NJS_ERROR; + } + + call = target->u.native; + } + + ret = call(vm, native->arguments, native->nargs, function->magic); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -1044,21 +1131,51 @@ static njs_int_t njs_function_prototype_bind(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { - size_t size; - njs_value_t *values; - njs_function_t *function; + size_t size; + njs_int_t ret; + njs_value_t *values, name; + njs_function_t *function; + njs_lvlhsh_query_t lhq; if (!njs_is_function(&args[0])) { njs_type_error(vm, "\"this\" argument is not a function"); return NJS_ERROR; } - function = njs_function_copy(vm, njs_function(&args[0])); + function = njs_mp_alloc(vm->mem_pool, sizeof(njs_function_t)); if (njs_slow_path(function == NULL)) { njs_memory_error(vm); return NJS_ERROR; } + *function = *njs_function(&args[0]); + + njs_lvlhsh_init(&function->object.hash); + + /* Bound functions have no "prototype" property. */ + function->object.shared_hash = vm->shared->arrow_instance_hash; + + function->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; + function->object.shared = 0; + + function->u.bound_target = njs_function(&args[0]); + + njs_object_property_init(&lhq, "name", NJS_NAME_HASH); + + ret = njs_object_property(vm, &args[0], &lhq, &name); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (!njs_is_string(&name)) { + name = njs_string_empty; + } + + ret = njs_function_name_set(vm, function, &name, 1); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + if (nargs == 1) { args = njs_value_arg(&njs_value_undefined); diff -r 4d414fccd53d -r 2488363b220b src/njs_function.h --- a/src/njs_function.h Thu Nov 14 21:01:28 2019 +0300 +++ b/src/njs_function.h Thu Nov 14 21:06:48 2019 +0300 @@ -101,6 +101,8 @@ struct njs_frame_s { njs_function_t *njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda, njs_closure_t *closures[], njs_bool_t shared); njs_function_t *njs_function_value_copy(njs_vm_t *vm, njs_value_t *value); +njs_int_t njs_function_name_set(njs_vm_t *vm, njs_function_t *function, + njs_value_t *name, njs_bool_t bound); njs_int_t njs_function_arguments_object_init(njs_vm_t *vm, njs_native_frame_t *frame); njs_int_t njs_function_rest_parameters_init(njs_vm_t *vm, diff -r 4d414fccd53d -r 2488363b220b src/njs_object_prop.c --- a/src/njs_object_prop.c Thu Nov 14 21:01:28 2019 +0300 +++ b/src/njs_object_prop.c Thu Nov 14 21:06:48 2019 +0300 @@ -376,10 +376,9 @@ exception: njs_int_t njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq) { - njs_int_t ret; - njs_function_t *function; - njs_object_prop_t *prop, *shared, *name; - njs_lvlhsh_query_t lhq; + njs_int_t ret; + njs_function_t *function; + njs_object_prop_t *prop, *shared; prop = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), sizeof(njs_object_prop_t)); @@ -436,27 +435,7 @@ njs_prop_private_copy(njs_vm_t *vm, njs_ return NJS_ERROR; } - name = njs_object_prop_alloc(vm, &njs_string_name, &prop->name, 0); - if (njs_slow_path(name == NULL)) { - return NJS_ERROR; - } - - name->configurable = 1; - - lhq.key_hash = NJS_NAME_HASH; - lhq.key = njs_str_value("name"); - lhq.replace = 0; - lhq.value = name; - lhq.proto = &njs_object_hash_proto; - lhq.pool = vm->mem_pool; - - ret = njs_lvlhsh_insert(&function->object.hash, &lhq); - if (njs_slow_path(ret != NJS_OK)) { - njs_internal_error(vm, "lvlhsh insert failed"); - return NJS_ERROR; - } - - return NJS_OK; + return njs_function_name_set(vm, function, &prop->name, 0); } diff -r 4d414fccd53d -r 2488363b220b src/njs_value.h --- a/src/njs_value.h Thu Nov 14 21:01:28 2019 +0300 +++ b/src/njs_value.h Thu Nov 14 21:06:48 2019 +0300 @@ -245,6 +245,7 @@ struct njs_function_s { union { njs_function_lambda_t *lambda; njs_function_native_t native; + njs_function_t *bound_target; } u; njs_value_t *bound; diff -r 4d414fccd53d -r 2488363b220b src/njs_vm.c --- a/src/njs_vm.c Thu Nov 14 21:01:28 2019 +0300 +++ b/src/njs_vm.c Thu Nov 14 21:06:48 2019 +0300 @@ -1056,7 +1056,12 @@ njs_vm_add_backtrace_entry(njs_vm_t *vm, } if (function->native) { - ret = njs_builtin_match_native_function(vm, function, &be->name); + while (function->bound != NULL) { + function = function->u.bound_target; + } + + ret = njs_builtin_match_native_function(vm, function->u.native, + &be->name); if (ret == NJS_OK) { return NJS_OK; } diff -r 4d414fccd53d -r 2488363b220b src/njs_vm.h --- a/src/njs_vm.h Thu Nov 14 21:01:28 2019 +0300 +++ b/src/njs_vm.h Thu Nov 14 21:06:48 2019 +0300 @@ -275,7 +275,7 @@ njs_int_t njs_vm_add_backtrace_entry(njs njs_int_t njs_builtin_objects_create(njs_vm_t *vm); njs_int_t njs_builtin_objects_clone(njs_vm_t *vm, njs_value_t *global); njs_int_t njs_builtin_match_native_function(njs_vm_t *vm, - njs_function_t *function, njs_str_t *name); + njs_function_native_t func, njs_str_t *name); njs_arr_t *njs_vm_backtrace(njs_vm_t *vm); njs_arr_t *njs_vm_completions(njs_vm_t *vm, njs_str_t *expression); diff -r 4d414fccd53d -r 2488363b220b src/njs_vmcode.c --- a/src/njs_vmcode.c Thu Nov 14 21:01:28 2019 +0300 +++ b/src/njs_vmcode.c Thu Nov 14 21:06:48 2019 +0300 @@ -1328,22 +1328,30 @@ static njs_jump_off_t njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object, njs_value_t *constructor) { - njs_value_t value; + njs_value_t value, bound; njs_object_t *prototype, *proto; + njs_function_t *function; njs_jump_off_t ret; const njs_value_t *retval; - const njs_value_t prototype_string = njs_string("prototype"); + static const njs_value_t prototype_string = njs_string("prototype"); if (!njs_is_function(constructor)) { njs_type_error(vm, "right argument is not callable"); return NJS_ERROR; } + function = njs_function(constructor); + + if (function->bound != NULL) { + function = function->u.bound_target; + njs_set_function(&bound, function); + constructor = &bound; + } + retval = &njs_value_false; if (njs_is_object(object)) { - njs_set_undefined(&value); ret = njs_value_property(vm, constructor, njs_value_arg(&prototype_string), &value); @@ -1607,8 +1615,9 @@ njs_function_frame_create(njs_vm_t *vm, static njs_object_t * njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor) { - njs_value_t proto; + njs_value_t proto, bound; njs_object_t *object; + njs_function_t *function; njs_jump_off_t ret; const njs_value_t prototype_string = njs_string("prototype"); @@ -1618,6 +1627,14 @@ njs_function_new_object(njs_vm_t *vm, nj return NULL; } + function = njs_function(constructor); + + if (function->bound != NULL) { + function = function->u.bound_target; + njs_set_function(&bound, function); + constructor = &bound; + } + ret = njs_value_property(vm, constructor, njs_value_arg(&prototype_string), &proto); diff -r 4d414fccd53d -r 2488363b220b src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Thu Nov 14 21:01:28 2019 +0300 +++ b/src/test/njs_unit_test.c Thu Nov 14 21:06:48 2019 +0300 @@ -5528,6 +5528,62 @@ static njs_unit_test_t njs_test[] = "var o = { toString: f }; o"), njs_str("0,1,2") }, + { njs_str("var f = Object.defineProperty(function() {}, 'name', {value: 'F'});" + "[f.name, f.bind().name, f.bind().bind().name]"), + njs_str("F,bound F,bound bound F") }, + + { njs_str("var f = Object.defineProperty(function() {}, 'name', {value: undefined});" + "[f.name, f.bind().name, f.bind().bind().name]"), + njs_str(",bound ,bound bound ") }, + + { njs_str("var f = Object.defineProperty(function() {}, 'name', {get:()=>'F'});" + "[f.name, f.bind().name]"), + njs_str("F,bound F") }, + + { njs_str("var f = Object.defineProperty(function() {}, 'name', {get:()=>{throw Error('Oops')}});" + "f.bind().name"), + njs_str("Error: Oops") }, + + { njs_str("var f = function() {}; f.a = 'a'; [f.bind().a, f.a]"), + njs_str(",a") }, + + { njs_str("var f = function() {}; var bf = f.bind(); bf.b = 'b'; " + "[f.b, bf.b]"), + njs_str(",b") }, + + { njs_str("function f(x,y) {return {args:arguments,length:arguments.length}};" + "var nf = Function.prototype.bind.call(f, {}, 'a', 'b');" + "var o = new nf();[o.length, o.args[0]]"), + njs_str("2,a") }, + + { njs_str("function f(x,y) {return {args:arguments,length:arguments.length}};" + "var nf = Function.prototype.bind.call(f, {});" + "var o = new nf('a', 'b');[o.length, o.args[0]]"), + njs_str("2,a") }, + + { njs_str("var f = function(a,b) { this.a = a; this.b = b; };" + "f.prototype.X = 'X';" + "var bf = f.bind(null, 1,2);" + "var o = new bf(); " + "[Object.keys(o), o.X,(o instanceof f) && (o instanceof bf),bf.prototype]"), + njs_str("a,b,X,true,") }, + + { njs_str("var bArray = Array.bind(null, 10,16); bArray()"), + njs_str("10,16") }, + + { njs_str("var bArray = Array.bind(null, 10,16); new bArray()"), + njs_str("10,16") }, + + { njs_str("var bArray = Array.bind(null, 10); new bArray(16)"), + njs_str("10,16") }, + +#if 0 /* FIXME: refactor Bound calls (9.4.1.1[[Call]]). */ + { njs_str("function f(x,y) {return {args:arguments,length:arguments.length}};" + "var bf = f.bind({}, 'a'); var bbf = bf.bind({},'b'); var o = bbf('c');"), + "[o.args[0], o.args[2], o.length]" + njs_str("a,c,3") }, +#endif + { njs_str("var s = { toString: function() { return '123' } };" "var a = 'abc'; a.concat('абв', s)"), njs_str("abcабв123") }, @@ -7907,6 +7963,10 @@ static njs_unit_test_t njs_test[] = { njs_str("(function(){ var a = 1; return (function() { return a; })})().bind()()"), njs_str("1") }, + { njs_str("var r = (function(){ var a = 1; return (function() { return {a,args:arguments}; })})().bind()('b');" + "njs.dump(r)"), + njs_str("{a:1,args:{0:'b'}}") }, + { njs_str("function f() { var a = 1; function baz() { return a; } return baz; } f().bind()()"), njs_str("1") }, diff -r 4d414fccd53d -r 2488363b220b test/njs_expect_test.exp --- a/test/njs_expect_test.exp Thu Nov 14 21:01:28 2019 +0300 +++ b/test/njs_expect_test.exp Thu Nov 14 21:06:48 2019 +0300 @@ -266,6 +266,8 @@ njs_test { "1 a \\\[1,2]\r\nundefined\r\n>> "} {"var print = console.dump.bind(console); print(1, 'a', [1, 2])\r\n" "1 a \\\[\r\n 1,\r\n 2\r\n]\r\nundefined\r\n>> "} + {"var print = console.log.bind(console); print(console.a.a)\r\n" + "TypeError: cannot get property \"a\" of undefined*at console.log"} } # Backtraces for external objects _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org http://mailman.nginx.org/mailman/listinfo/nginx-devel