]> git.kaiwu.me - njs.git/commitdiff
Added support array-like objects for Function.prototype.apply().
authorAlexander Borisov <alexander.borisov@nginx.com>
Fri, 5 Apr 2019 14:49:22 +0000 (17:49 +0300)
committerAlexander Borisov <alexander.borisov@nginx.com>
Fri, 5 Apr 2019 14:49:22 +0000 (17:49 +0300)
This closes #51 issue on Github.

njs/njs_function.c
njs/test/njs_unit_test.c

index c0e8b0bc577456169ec37d6767ce89e1bb4829b6..0b07d3c5a7305647bd71bf5ddc2eb529b41fd8c7 100644 (file)
@@ -906,44 +906,89 @@ njs_function_prototype_call(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
 }
 
 
+/*
+ * Non-primitive length values are not supported yet. To handle non-primitive
+ * values a continuation is needed. Currently, only one continuation per frame
+ * is supported. apply() is a special function which can add a second
+ * continuation to the continuation of the underling function.
+ *
+ * TODO:
+ *   String.prototype.concat.apply('a', { length:{ valueOf:
+ *                                        function() { return 2; } },
+ *                                        0:'b', 1:'c'})
+ */
 static njs_ret_t
 njs_function_prototype_apply(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t retval)
 {
-    njs_ret_t       ret;
-    njs_array_t     *array;
-    njs_value_t     *this;
-    njs_function_t  *function;
+    uint32_t           i;
+    njs_ret_t          ret;
+    njs_value_t        length, name;
+    njs_array_t        *arr;
+    njs_function_t     *func;
+    const njs_value_t  *this, *arr_like;
 
-    if (!njs_is_function(&args[0])) {
+    static const njs_value_t  njs_string_length = njs_string("length");
+
+    if (!njs_is_function(njs_arg(args, nargs, 0))) {
         njs_type_error(vm, "\"this\" argument is not a function");
         return NXT_ERROR;
     }
 
-    function = args[0].data.u.function;
-    this = &args[1];
+    func = (njs_argument(args, 0))->data.u.function;
+    this = njs_arg(args, nargs, 1);
+    arr_like = njs_arg(args, nargs, 2);
 
-    if (nargs > 2) {
-        if (!njs_is_array(&args[2])) {
-            njs_type_error(vm, "second argument is not an array");
-            return NXT_ERROR;
-        }
+    if (njs_is_null_or_undefined(arr_like)) {
+        nargs = 0;
 
-        array = args[2].data.u.array;
-        args = array->start;
-        nargs = array->length;
+        goto activate;
 
-    } else {
-        if (nargs == 1) {
-            this = (njs_value_t *) &njs_value_undefined;
-        }
+    } else if (njs_is_array(arr_like)) {
+        arr = arr_like->data.u.array;
 
-        nargs = 0;
+        args = arr->start;
+        nargs = arr->length;
+
+        goto activate;
+
+    } else if (nxt_slow_path(!njs_is_object(arr_like))) {
+        njs_type_error(vm, "second argument is not an array-like object");
+        return NXT_ERROR;
     }
 
-    ret = njs_function_activate(vm, function, this, args, nargs, retval,
-                                sizeof(njs_vmcode_function_call_t));
+    ret = njs_value_property(vm, arr_like, &njs_string_length, &length);
+    if (nxt_slow_path(ret == NXT_ERROR)) {
+        return ret;
+    }
+
+    if (!njs_is_primitive(&length)) {
+        njs_type_error(vm, "non-primitive length values are not supported");
+        return NXT_ERROR;
+    }
+
+    nargs = njs_primitive_value_to_number(&length);
+
+    arr = njs_array_alloc(vm, nargs, NJS_ARRAY_SPARE);
+    if (nxt_slow_path(arr == NULL)) {
+        return NXT_ERROR;
+    }
+
+    args = arr->start;
 
+    for (i = 0; i < nargs; i++) {
+        njs_uint32_to_string(&name, i);
+
+        ret = njs_value_property(vm, arr_like, &name, &args[i]);
+        if (nxt_slow_path(ret == NXT_ERROR)) {
+            return ret;
+        }
+    }
+
+activate:
+
+    ret = njs_function_activate(vm, func, this, args, nargs, retval,
+                                sizeof(njs_vmcode_function_call_t));
     if (nxt_slow_path(ret == NXT_ERROR)) {
         return ret;
     }
index 01b44e5ef67b6cbe9f9b85a25f9d77e85836526b..859479af12b671a894677066b3aa8136aba48c06 100644 (file)
@@ -6148,7 +6148,7 @@ static njs_unit_test_t  njs_test[] =
       nxt_string("5") },
 
     { nxt_string("var f = function(a) { return this + a }; f.apply(5, 1)"),
-      nxt_string("TypeError: second argument is not an array") },
+      nxt_string("TypeError: second argument is not an array-like object") },
 
     { nxt_string("var f = function(a, b) { return this + a + b };"
                  "f.apply(5, [1, 2])"),
@@ -6158,6 +6158,57 @@ static njs_unit_test_t  njs_test[] =
                  "f.apply(5, [1, 2], 3)"),
       nxt_string("8") },
 
+    { nxt_string("var f = function(a, b) { return this + a + b };"
+                 "f.apply(5, {'length':2, '0':1, '1':2})"),
+      nxt_string("8") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b };"
+                 "f.apply(5, {'length':2, '0':1, '1':2, '2':3})"),
+      nxt_string("8") },
+
+    { nxt_string("var f = function(a, b, c) { return this + a + b + c};"
+                 "f.apply(\"a\", {'length':2, '0':1, '1':2, '2':3})"),
+      nxt_string("a12undefined") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b };"
+                 "f.apply(5, {'length':3, '0':1, '1':2, '2':3})"),
+      nxt_string("8") },
+
+    { nxt_string("var f = function(a) { return this + a };"
+                 "f.apply(5, {'nolength':3, '0':1, '1':2})"),
+      nxt_string("NaN") },
+
+    { nxt_string("var f = function(a) { return this };"
+                 "f.apply(5, {'nolength':3, '0':1, '1':2})"),
+      nxt_string("5") },
+
+    { nxt_string("var f = function(a, b, c) { return this + a + b + c };"
+                 "f.apply(\"a\", {'length':3, '0':1, '1':2})"),
+      nxt_string("a12undefined") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b };"
+                 "f.apply(\"a\", {'length':2, '0':undefined, '1':null})"),
+      nxt_string("aundefinednull") },
+
+    { nxt_string("var f = function() { return this };"
+                 "f.apply(123, {})"),
+      nxt_string("123") },
+
+    { nxt_string("String.prototype.concat.apply('a', "
+                 "{length:2, 0:{toString:function() {return 'b'}}, 1:'c'})"),
+      nxt_string("abc") },
+
+#if 0
+    /* TODO: non-primitive length values are not supported yet. */
+    { nxt_string("String.prototype.concat.apply('a',"
+                 "{length:{valueOf:function() {return 2}},  0:'b', 1:'c'})"),
+      nxt_string("abc") },
+#else
+    { nxt_string("String.prototype.concat.apply('a',"
+                 "{length:{valueOf:function() {return 2}},  0:'b', 1:'c'})"),
+      nxt_string("TypeError: non-primitive length values are not supported") },
+#endif
+
     { nxt_string("var a = function() { return 1 } + ''; a"),
       nxt_string("[object Function]") },
 
@@ -6183,7 +6234,7 @@ static njs_unit_test_t  njs_test[] =
       nxt_string("a") },
 
     { nxt_string("''.concat.apply('a', 'b')"),
-      nxt_string("TypeError: second argument is not an array") },
+      nxt_string("TypeError: second argument is not an array-like object") },
 
     { nxt_string("''.concat.apply('a', [ 'b', 'c' ])"),
       nxt_string("abc") },