[njs] Fixed Array.prototype.splice() according to the specification.

Dmitry Volyntsev xeioex at nginx.com
Wed Jun 3 18:41:14 UTC 2020


details:   https://hg.nginx.org/njs/rev/c1e3fd0d24fd
branches:  
changeset: 1422:c1e3fd0d24fd
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Wed Jun 03 18:40:10 2020 +0000
description:
Fixed Array.prototype.splice() according to the specification.

diffstat:

 src/njs_array.c          |  171 ++++++++++++++++++++++++++++------------------
 src/test/njs_unit_test.c |   96 ++++++++++++++++++++++++++
 2 files changed, 201 insertions(+), 66 deletions(-)

diffs (319 lines):

diff -r a23651137ccf -r c1e3fd0d24fd src/njs_array.c
--- a/src/njs_array.c	Wed Jun 03 18:40:09 2020 +0000
+++ b/src/njs_array.c	Wed Jun 03 18:40:10 2020 +0000
@@ -1241,10 +1241,9 @@ static njs_int_t
 njs_array_prototype_splice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    int64_t      n, start, length, items, delta, delete;
+    int64_t      i, n, start, length, items, delta, delete;
     njs_int_t    ret;
-    njs_uint_t   i;
-    njs_value_t  *this;
+    njs_value_t  *this, value, del_object;
     njs_array_t  *array, *deleted;
 
     this = njs_argument(args, 0);
@@ -1254,74 +1253,81 @@ njs_array_prototype_splice(njs_vm_t *vm,
         return ret;
     }
 
-    if (njs_slow_path(!njs_is_fast_array(this))) {
-        njs_internal_error(vm, "splice() is not implemented yet for objects");
+    ret = njs_object_length(vm, this, &length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length);
+
+    items = 0;
+    delete = 0;
+
+    if (nargs == 2) {
+        delete = length - start;
+
+    } else if (nargs > 2) {
+        items = nargs - 3;
+
+        ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &delete);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        delete = njs_min(njs_max(delete, 0), length - start);
+    }
+
+    delta = items - delete;
+
+    if (njs_slow_path((length + delta) > NJS_MAX_LENGTH)) {
+        njs_type_error(vm, "Invalid length");
         return NJS_ERROR;
     }
 
-    array = NULL;
-    start = 0;
-    delete = 0;
-
-    if (njs_is_fast_array(this)) {
-        array = njs_array(this);
-        length = array->length;
-
-        if (nargs > 1) {
-            ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start);
-            if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
-            }
-
-            if (start < 0) {
-                start += length;
-
-                if (start < 0) {
-                    start = 0;
-                }
-
-            } else if (start > length) {
-                start = length;
-            }
-
-            delete = length - start;
-
-            if (nargs > 2) {
-                ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &n);
-                if (njs_slow_path(ret != NJS_OK)) {
-                    return ret;
-                }
-
-                if (n < 0) {
-                    delete = 0;
-
-                } else if (n < delete) {
-                    delete = n;
-                }
-            }
-        }
-    }
+    /* TODO: ArraySpeciesCreate(). */
 
     deleted = njs_array_alloc(vm, 0, delete, 0);
     if (njs_slow_path(deleted == NULL)) {
         return NJS_ERROR;
     }
 
-    if (njs_slow_path(!deleted->object.fast_array)) {
-        njs_internal_error(vm, "deleted is not a fast_array");
-        return NJS_ERROR;
-    }
-
-    if (array != NULL && (delete >= 0 || nargs > 3)) {
-
-        /* Move deleted items to a new array to return. */
-        for (i = 0, n = start; i < (njs_uint_t) delete; i++, n++) {
-            /* No retention required. */
+    if (njs_fast_path(njs_is_fast_array(this) && deleted->object.fast_array)) {
+        array = njs_array(this);
+        for (i = 0, n = start; i < delete; i++, n++) {
             deleted->start[i] = array->start[n];
         }
 
-        items = (nargs > 3) ? nargs - 3: 0;
-        delta = items - delete;
+    } else {
+        njs_set_array(&del_object, deleted);
+
+        for (i = 0, n = start; i < delete; i++, n++) {
+            ret = njs_value_property_i64(vm, this, n, &value);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                return NJS_ERROR;
+            }
+
+            if (ret == NJS_OK) {
+                /* TODO:  CreateDataPropertyOrThrow(). */
+                ret = njs_value_property_i64_set(vm, &del_object, i, &value);
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    return ret;
+                }
+            }
+        }
+
+        ret = njs_object_length_set(vm, &del_object, delete);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    if (njs_fast_path(njs_is_fast_array(this))) {
+        array = njs_array(this);
 
         if (delta != 0) {
             /*
@@ -1335,18 +1341,51 @@ njs_array_prototype_splice(njs_vm_t *vm,
                 }
             }
 
-            memmove(&array->start[start + items], &array->start[n],
-                    (array->length - n) * sizeof(njs_value_t));
+            ret = njs_array_copy_within(vm, this, start + items, start + delete,
+                                        array->length - (start + delete), 0);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
 
             array->length += delta;
         }
 
         /* Copy new items. */
-        n = start;
-
-        for (i = 3; i < nargs; i++) {
-            /* GC: njs_retain(&args[i]); */
-            array->start[n++] = args[i];
+
+        if (items > 0) {
+            memcpy(&array->start[start], &args[3],
+                   items * sizeof(njs_value_t));
+        }
+
+    } else {
+
+       if (delta != 0) {
+           ret = njs_array_copy_within(vm, this, start + items, start + delete,
+                                       length - (start + delete), delta < 0);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+
+            for (i = length - 1; i >= length + delta; i--) {
+                ret = njs_value_property_i64_delete(vm, this, i, NULL);
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    return NJS_ERROR;
+                }
+            }
+       }
+
+        /* Copy new items. */
+
+        for (i = 3, n = start; items-- > 0; i++, n++) {
+            ret = njs_value_property_i64_set(vm, this, n, &args[i]);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                return NJS_ERROR;
+            }
+        }
+
+        ret = njs_object_length_set(vm, this, length + delta);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
         }
     }
 
diff -r a23651137ccf -r c1e3fd0d24fd src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Wed Jun 03 18:40:09 2020 +0000
+++ b/src/test/njs_unit_test.c	Wed Jun 03 18:40:10 2020 +0000
@@ -4499,6 +4499,102 @@ static njs_unit_test_t  njs_test[] =
                  "a.splice(3, 2, 8, 9, 10, 11 ).join(':') + '|' + a"),
       njs_str("3:4|0,1,2,8,9,10,11,5,6,7") },
 
+    { njs_str("["
+              " [],"
+              " [1],"
+              " [1, 2],"
+              " [1, 2, 'a'],"
+              " [1, 2, 'a', 'b'],"
+              " [1, 2, 'a', 'b', 'c'],"
+              "]"
+              ".map(args=>{var a = [0,1,3,4,5]; a.splice.apply(a, args); return a})"
+              ".map(v=>v.join(''))"),
+      njs_str("01345,0,045,0a45,0ab45,0abc45") },
+
+    { njs_str("["
+              " [],"
+              " [1],"
+              " [1, 1, 'a'],"
+              " [1, 2, 'a'],"
+              " [1, 2, 'a', 'b'],"
+              " [1, 2, 'a', 'b', 'c'],"
+              "]"
+              ".map(args=>{var a = [0,1,3,4,5]; return a.splice.apply(a, args);})"
+              ".map(v=>v.join(''))"),
+      njs_str(",1345,1,13,13,13") },
+
+    { njs_str("Object.prototype.splice = Array.prototype.splice;"
+              "Object.prototype.join = Array.prototype.join;"
+              "["
+              " [],"
+              " [1],"
+              " [1, 2],"
+              " [1, 1, 'a'],"
+              " [1, 2, 'a'],"
+              " [1, 2, 'a', 'b'],"
+              " [1, 2, 'a', 'b', 'c'],"
+              "]"
+              ".map(args=>{var a = {0:0, 1:1, 2:3, 3:4, 4:5, length:5};"
+              "            a.splice.apply(a, args); return a})"
+              ".map(v=>v.join(''))"),
+      njs_str("01345,0,045,0a345,0a45,0ab45,0abc45") },
+
+    { njs_str("Object.prototype.splice = Array.prototype.splice;"
+              "Object.prototype.join = Array.prototype.join;"
+              "["
+              " [],"
+              " [1],"
+              " [1, 0, 'a'],"
+              " [1, 1, 'a'],"
+              " [1, 2, 'a'],"
+              " [1, 2, 'a', 'b'],"
+              " [1, 2, 'a', 'b', 'c'],"
+              "]"
+              ".map(args=>{var a = {0:0, 1:1, 2:3, 3:4, 4:5, length:5};"
+              "            return a.splice.apply(a, args);})"
+              ".map(v=>v.join(''))"),
+      njs_str(",1345,,1,13,13,13") },
+
+    { njs_str("Array.prototype.splice.call({0:0,1:1,2:2,3:3,length:4},0,3,4,5)"),
+      njs_str("0,1,2") },
+
+    { njs_str("var obj = {0:0,1:1,2:2,3:3,length:4};"
+              "Array.prototype.splice.call(obj,0,3,4,5); obj[3]"),
+      njs_str("undefined") },
+
+    { njs_str("var obj = {4294967294: 'x', length:-1};"
+              "Array.prototype.splice.call(obj, 4294967294, 1); obj.length"),
+      njs_str("0") },
+
+    { njs_str("var obj = {0:0, 1:1, 2:2};"
+              "Object.defineProperty(obj, 'length', {value:3, writable:false});"
+              "Array.prototype.splice.call(obj, 1, 2, 4)"),
+      njs_str("TypeError: Cannot assign to read-only property \"length\" of object") },
+
+    { njs_str("var obj = {'9007199254740988': 'A', '9007199254740989': 'B',"
+              "           '9007199254740990': 'C', '9007199254740991': 'D', "
+              "           length: 2 ** 53 + 2};"
+              "Array.prototype.splice.call(obj, 2**53-3, 2 ** 53 + 4)"),
+      njs_str("B,C") },
+
+    { njs_str("var obj = {'9007199254740988': 'A', '9007199254740989': 'B',"
+              "           '9007199254740990': 'C', '9007199254740991': 'D', "
+              "           length: 2 ** 53 + 2};"
+              "Array.prototype.splice.call(obj, 2**53-3, 2 ** 53 + 4);"
+              "obj['9007199254740988'] == 'A' && obj['9007199254740991'] == 'D'"),
+      njs_str("true") },
+
+    { njs_str("var obj = {'9007199254740990': 'A', '9007199254740991': 'B',"
+              "           length: 2 ** 53 - 1};"
+              "Array.prototype.splice.call(obj, 2**53-2, 1, 'C');"
+              "obj['9007199254740990'] == 'C' && obj['9007199254740991'] == 'B'"),
+      njs_str("true") },
+
+    { njs_str("var obj = {'9007199254740990': 'A', '9007199254740991': 'B',"
+              "           length: 2 ** 53 - 1};"
+              "Array.prototype.splice.call(obj, 2**53-2, 0, 'C');"),
+      njs_str("TypeError: Invalid length") },
+
     { njs_str("var a = []; a.reverse()"),
       njs_str("") },
 


More information about the nginx-devel mailing list