]> git.kaiwu.me - njs.git/commitdiff
Reimplemented "bound" functions according to specification.
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 14 Nov 2019 18:06:48 +0000 (21:06 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Thu, 14 Nov 2019 18:06:48 +0000 (21:06 +0300)
This closes #202 and closes #204 issues on Github.

src/njs_builtin.c
src/njs_function.c
src/njs_function.h
src/njs_object_prop.c
src/njs_value.h
src/njs_vm.c
src/njs_vm.h
src/njs_vmcode.c
src/test/njs_unit_test.c
test/njs_expect_test.exp

index 10b08f0fc050e7ee88fdf580b7ff4fc4e356afb1..a07c7f89e8c53ea8bbcd75c0a6c14d944ac34518 100644 (file)
@@ -761,7 +761,7 @@ njs_object_completions(njs_vm_t *vm, njs_object_t *object)
 
 
 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_t *vm, njs_function_t *function,
     njs_builtin_traverse_t  ctx;
 
     ctx.type = NJS_BUILTIN_TRAVERSE_MATCH;
-    ctx.native = function->u.native;
+    ctx.native = func;
 
     /* Global object. */
 
index d6fcccf88e5d9bbe9e9c981d84530d3425a467b6..13f6edd4a5e838ac7fc1807b44090c6e33f671ae 100644 (file)
@@ -111,6 +111,53 @@ njs_function_closures(njs_vm_t *vm, njs_function_t *function)
 }
 
 
+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_function_t *function,
     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, njs_function_t *function,
         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_function_t *function,
                              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, njs_function_t *function,
         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);
 
index 9c80fb5ecee8ec696f7f5ca19234fd6c6b029462..57443015bd51092488d09bfa0980f45938caed20 100644 (file)
@@ -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,
index 6ed864d3a5f394926ce95beb9a85a0aeb37363cd..93501ccc275cc0f8174eb49da5db43daa9ed7edf 100644 (file)
@@ -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_property_query_t *pq)
         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);
 }
 
 
index cf79c12f745ea123db389669eae916b408e593ef..ed909f3fa8ab57cb29bc39d1d6e393f858608baa 100644 (file)
@@ -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;
index b69345d482975c1ad9c9d570cdddbfeedf337354..c2c948a73ad4dd7112caebb45475bd69dc16eb3a 100644 (file)
@@ -1056,7 +1056,12 @@ njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame)
     }
 
     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;
         }
index 63f3059ecadeea75e7590c6697d93d6a15020cbc..5b3398b8891523046a20bf6caa591bd634b1559d 100644 (file)
@@ -275,7 +275,7 @@ njs_int_t njs_vm_add_backtrace_entry(njs_vm_t *vm, njs_frame_t *frame);
 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);
index 4f4238d00d2e999e39d85901495483b8ff312224..af8ef290b48d3c7229295262fc9c790477583105 100644 (file)
@@ -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, njs_value_t *value,
 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, njs_value_t *constructor)
         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);
 
index 9a937f0b27594f90424339179f6e6a7f33407d7d..953c717d1c6e0ed39ed9e1fe3a4510c08872e858 100644 (file)
@@ -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") },
 
index ca227d1fbf64e8dfa70e4272c3b6cd3bb6a07aed..baeea69e7bd9fa5151c40506c7719c1f4d4a9d5f 100644 (file)
@@ -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