[njs] Handling non-array values in Array.prototype.slice.

Dmitry Volyntsev xeioex at nginx.com
Tue Oct 23 17:40:18 UTC 2018


details:   http://hg.nginx.org/njs/rev/de0974c3e90f
branches:  
changeset: 629:de0974c3e90f
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Tue Oct 23 20:40:02 2018 +0300
description:
Handling non-array values in Array.prototype.slice.

diffstat:

 njs/njs_array.c          |  213 +++++++++++++++++++++++++++++++++++-----------
 njs/njs_object_hash.h    |   10 ++
 njs/test/njs_unit_test.c |   84 ++++++++++++++++++
 3 files changed, 254 insertions(+), 53 deletions(-)

diffs (386 lines):

diff -r 8ab908b0f226 -r de0974c3e90f njs/njs_array.c
--- a/njs/njs_array.c	Fri Oct 19 20:55:38 2018 +0300
+++ b/njs/njs_array.c	Tue Oct 23 20:40:02 2018 +0300
@@ -9,6 +9,19 @@
 
 
 typedef struct {
+    union {
+        njs_continuation_t  cont;
+        u_char              padding[NJS_CONTINUATION_SIZE];
+    } u;
+    /*
+     * This retval value must be aligned so the continuation is padded
+     * to aligned size.
+     */
+    njs_value_t             length;
+} njs_array_slice_t;
+
+
+typedef struct {
     njs_continuation_t      cont;
     njs_value_t             *values;
     uint32_t                max;
@@ -67,6 +80,10 @@ typedef struct {
 } njs_array_sort_t;
 
 
+static njs_ret_t njs_array_prototype_slice_continuation(njs_vm_t *vm,
+    njs_value_t *args, nxt_uint_t nargs, njs_index_t unused);
+static njs_ret_t njs_array_prototype_slice_copy(njs_vm_t *vm,
+    njs_value_t *this, int64_t start, int64_t length);
 static njs_ret_t njs_array_prototype_to_string_continuation(njs_vm_t *vm,
     njs_value_t *args, nxt_uint_t nargs, njs_index_t retval);
 static njs_ret_t njs_array_prototype_join_continuation(njs_vm_t *vm,
@@ -434,58 +451,95 @@ static njs_ret_t
 njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
 {
-    int32_t      start, end, length;
-    uint32_t     n;
-    njs_array_t  *array;
-    njs_value_t  *value;
-
-    start = 0;
-    length = 0;
-
-    if (njs_is_array(&args[0])) {
-        length = args[0].data.u.array->length;
-
-        if (nargs > 1) {
-            start = args[1].data.u.number;
-
-            if (start < 0) {
-                start += length;
-
-                if (start < 0) {
-                    start = 0;
-                }
-            }
-
-            if (start >= length) {
+    njs_ret_t          ret;
+    njs_array_slice_t  *slice;
+
+    static const njs_value_t  njs_string_length = njs_string("length");
+
+    slice = njs_vm_continuation(vm);
+    slice->u.cont.function = njs_array_prototype_slice_continuation;
+
+    ret = njs_value_property(vm, &args[0], &njs_string_length, &slice->length);
+    if (nxt_slow_path(ret == NXT_ERROR || ret == NJS_TRAP)) {
+        return ret;
+    }
+
+    return njs_array_prototype_slice_continuation(vm, args, nargs, unused);
+}
+
+
+static njs_ret_t
+njs_array_prototype_slice_continuation(njs_vm_t *vm, njs_value_t *args,
+    nxt_uint_t nargs, njs_index_t unused)
+{
+    int64_t            start, end, length;
+    njs_array_slice_t  *slice;
+
+    slice = njs_vm_continuation(vm);
+
+    if (nxt_slow_path(!njs_is_primitive(&slice->length))) {
+        njs_vm_trap_value(vm, &slice->length);
+        return njs_trap(vm, NJS_TRAP_NUMBER_ARG);
+    }
+
+    start = (int32_t) njs_primitive_value_to_integer(njs_arg(args, nargs, 1));
+    length = njs_primitive_value_to_integer(&slice->length);
+
+    if (start < 0) {
+        start += length;
+
+        if (start < 0) {
+            start = 0;
+        }
+    }
+
+    if (start >= length) {
+        start = 0;
+        length = 0;
+
+    } else {
+        if (!njs_is_void(njs_arg(args, nargs, 2))) {
+            end = (int32_t) njs_primitive_value_to_integer(&args[2]);
+
+        } else {
+            end = length;
+        }
+
+        if (end < 0) {
+            end += length;
+        }
+
+        if (length >= end) {
+            length = end - start;
+
+            if (length < 0) {
                 start = 0;
                 length = 0;
-
-            } else {
-                end = length;
-
-                if (nargs > 2) {
-                    end = args[2].data.u.number;
-
-                    if (end < 0) {
-                        end += length;
-                    }
-                }
-
-                if (length >= end) {
-                    length = end - start;
-
-                    if (length < 0) {
-                        start = 0;
-                        length = 0;
-                    }
-
-                } else {
-                    length -= start;
-                }
             }
+
+        } else {
+            length -= start;
         }
     }
 
+    return njs_array_prototype_slice_copy(vm, &args[0], start, length);
+}
+
+
+static njs_ret_t
+njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this,
+    int64_t start, int64_t length)
+{
+    size_t             size;
+    u_char             *dst;
+    uint32_t           n, len;
+    njs_ret_t          ret;
+    njs_array_t        *array;
+    njs_value_t        *value, name;
+    const u_char       *src, *end;
+    njs_slice_prop_t   string_slice;
+    njs_string_prop_t  string;
+
     array = njs_array_alloc(vm, length, NJS_ARRAY_SPARE);
     if (nxt_slow_path(array == NULL)) {
         return NXT_ERROR;
@@ -496,14 +550,66 @@ njs_array_prototype_slice(njs_vm_t *vm, 
     vm->retval.data.truth = 1;
 
     if (length != 0) {
-        value = args[0].data.u.array->start;
         n = 0;
 
-        do {
-            /* GC: retain long string and object in values[start]. */
-            array->start[n++] = value[start++];
-            length--;
-        } while (length != 0);
+        if (nxt_fast_path(njs_is_array(this))) {
+            value = this->data.u.array->start;
+
+            do {
+                /* GC: retain long string and object in values[start]. */
+                array->start[n++] = value[start++];
+                length--;
+            } while (length != 0);
+
+        } else if (njs_is_string(this) || this->type == NJS_OBJECT_STRING) {
+
+            if (this->type == NJS_OBJECT_STRING) {
+                this = &this->data.u.object_value->value;
+            }
+
+            string_slice.start = start;
+            string_slice.length = length;
+            string_slice.string_length = njs_string_prop(&string, this);
+
+            njs_string_slice_string_prop(&string, &string, &string_slice);
+
+            src = string.start;
+            end = src + string.size;
+
+            if (string.length == 0) {
+                /* Byte string. */
+                len = 0;
+
+            } else {
+                /* UTF-8 or ASCII string. */
+                len = 1;
+            }
+
+            do {
+                value = &array->start[n++];
+                dst = njs_string_short_start(value);
+                dst = nxt_utf8_copy(dst, &src, end);
+                size = dst - njs_string_short_start(value);
+                njs_string_short_set(value, size, len);
+
+                length--;
+            } while (length != 0);
+
+        } else if (njs_is_object(this)) {
+
+            do {
+                njs_uint32_to_string(&name, start++);
+
+                value = &array->start[n++];
+                ret = njs_value_property(vm, this, &name, value);
+
+                if (ret != NXT_OK) {
+                    *value = njs_value_invalid;
+                }
+
+                length--;
+            } while (length != 0);
+        }
     }
 
     return NXT_OK;
@@ -2101,7 +2207,8 @@ static const njs_object_prop_t  njs_arra
     {
         .type = NJS_METHOD,
         .name = njs_string("slice"),
-        .value = njs_native_function(njs_array_prototype_slice, 0,
+        .value = njs_native_function(njs_array_prototype_slice,
+                     njs_continuation_size(njs_array_slice_t),
                      NJS_OBJECT_ARG, NJS_INTEGER_ARG, NJS_INTEGER_ARG),
     },
 
diff -r 8ab908b0f226 -r de0974c3e90f njs/njs_object_hash.h
--- a/njs/njs_object_hash.h	Fri Oct 19 20:55:38 2018 +0300
+++ b/njs/njs_object_hash.h	Tue Oct 23 20:40:02 2018 +0300
@@ -108,6 +108,16 @@
         'j'), 'o'), 'i'), 'n')
 
 
+#define NJS_LENGTH_HASH                                                       \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        'l'), 'e'), 'n'), 'g'), 't'), 'h')
+
+
 #define NJS_NAME_HASH                                                         \
     nxt_djb_hash_add(                                                         \
     nxt_djb_hash_add(                                                         \
diff -r 8ab908b0f226 -r de0974c3e90f njs/test/njs_unit_test.c
--- a/njs/test/njs_unit_test.c	Fri Oct 19 20:55:38 2018 +0300
+++ b/njs/test/njs_unit_test.c	Tue Oct 23 20:40:02 2018 +0300
@@ -3052,12 +3052,93 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("Array.prototype.slice(1,2)"),
       nxt_string("") },
 
+    { nxt_string("Array.prototype.slice.call(undefined)"),
+      nxt_string("TypeError: cannot convert void to object") },
+
+    { nxt_string("Array.prototype.slice.call(1)"),
+      nxt_string("") },
+
+    { nxt_string("Array.prototype.slice.call(false)"),
+      nxt_string("") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:1})"),
+      nxt_string("a") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2})"),
+      nxt_string("a,b") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:4})"),
+      nxt_string("a,b,,") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2}, 1)"),
+      nxt_string("b") },
+
+    { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2}, 1, 2)"),
+      nxt_string("b") },
+
+    { nxt_string("Array.prototype.slice.call({length:'2'})"),
+      nxt_string(",") },
+
+    { nxt_string("njs.dump(Array.prototype.slice.call({length: 3, 1: undefined }))"),
+      nxt_string("[<empty>,undefined,<empty>]") },
+
+    { nxt_string("Array.prototype.slice.call({length:new Number(3)})"),
+      nxt_string(",,") },
+
+    { nxt_string("Array.prototype.slice.call({length: { valueOf: function() { return 2; } }})"),
+      nxt_string(",") },
+
+    { nxt_string("Array.prototype.slice.call({ length: Object.create(null) })"),
+      nxt_string("TypeError: Cannot convert object to primitive value") },
+
+    { nxt_string("Array.prototype.slice.call({length:-1})"),
+      nxt_string("MemoryError") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ')"),
+      nxt_string("α,β,Z,γ") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ', 1)"),
+      nxt_string("β,Z,γ") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ', 2)"),
+      nxt_string("Z,γ") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ', 3)"),
+      nxt_string("γ") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ', 4)"),
+      nxt_string("") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ', 0, 1)"),
+      nxt_string("α") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ', 1, 2)"),
+      nxt_string("β") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ').length"),
+      nxt_string("4") },
+
+    { nxt_string("Array.prototype.slice.call('αβZγ')[1].length"),
+      nxt_string("1") },
+
+    { nxt_string("Array.prototype.slice.call(new String('αβZγ'))"),
+      nxt_string("α,β,Z,γ") },
+
     { nxt_string("Array.prototype.pop()"),
       nxt_string("undefined") },
 
     { nxt_string("Array.prototype.shift()"),
       nxt_string("undefined") },
 
+    { nxt_string("[0,1].slice()"),
+      nxt_string("0,1") },
+
+    { nxt_string("[0,1].slice(undefined)"),
+      nxt_string("0,1") },
+
+    { nxt_string("[0,1].slice(undefined, undefined)"),
+      nxt_string("0,1") },
+
     { nxt_string("[0,1,2,3,4].slice(1,4)"),
       nxt_string("1,2,3") },
 
@@ -9667,6 +9748,9 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("njs.dump({a:1, b:[1,,2,{c:new Boolean(1)}]})"),
       nxt_string("{a:1,b:[1,<empty>,2,{c:[Boolean: true]}]}") },
 
+    { nxt_string("njs.dump(Array.prototype.slice.call({'1':'b', length:2}))"),
+      nxt_string("[<empty>,'b']") },
+
     { nxt_string("njs.dump($r.props)"),
       nxt_string("{a:{type:\"property\",props:[\"getter\"]},b:{type:\"property\",props:[\"getter\"]}}") },
 


More information about the nginx-devel mailing list