[njs] Reimplemented "bound" functions according to specification.
Dmitry Volyntsev
xeioex at nginx.com
Thu Nov 14 18:08:31 UTC 2019
details: https://hg.nginx.org/njs/rev/2488363b220b
branches:
changeset: 1239:2488363b220b
user: Dmitry Volyntsev <xeioex at 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
More information about the nginx-devel
mailing list