[njs] Fixed Array prototype functions according to the specification.

Alexander Borisov alexander.borisov at nginx.com
Thu Sep 19 07:20:13 UTC 2019


details:   https://hg.nginx.org/njs/rev/893fc436a363
branches:  
changeset: 1164:893fc436a363
user:      Alexander Borisov <alexander.borisov at nginx.com>
date:      Thu Sep 19 10:19:02 2019 +0300
description:
Fixed Array prototype functions according to the specification.

The following fuctions were fixed:
pop, push, shift, unshift.

diffstat:

 src/njs_array.c          |  229 ++++++++++++++++++++++++++++++++++++++++++----
 src/njs_object.h         |   14 ++
 src/test/njs_unit_test.c |   51 ++++++++++
 3 files changed, 273 insertions(+), 21 deletions(-)

diffs (407 lines):

diff -r a07722caaee3 -r 893fc436a363 src/njs_array.c
--- a/src/njs_array.c	Thu Sep 19 10:19:01 2019 +0300
+++ b/src/njs_array.c	Thu Sep 19 10:19:02 2019 +0300
@@ -589,9 +589,19 @@ static njs_int_t
 njs_array_prototype_push(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
+    uint32_t     length;
     njs_int_t    ret;
     njs_uint_t   i;
     njs_array_t  *array;
+    njs_value_t  *value, index;
+
+    value = njs_arg(args, nargs, 0);
+    length = 0;
+
+    if (njs_slow_path(njs_is_null_or_undefined(value))) {
+        njs_type_error(vm, "Cannot convert undefined or null to object");
+        return NJS_ERROR;
+    }
 
     if (njs_is_array(&args[0])) {
         array = njs_array(&args[0]);
@@ -609,8 +619,31 @@ njs_array_prototype_push(njs_vm_t *vm, n
         }
 
         njs_set_number(&vm->retval, array->length);
+
+        return NJS_OK;
     }
 
+    ret = njs_object_length(vm, value, &length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    for (i = 1; i < nargs; i++) {
+        njs_uint32_to_string(&index, length++);
+
+        ret = njs_value_property_set(vm, value, &index, &args[i]);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return ret;
+        }
+    }
+
+    ret = njs_object_length_set(vm, value, length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    njs_set_number(&vm->retval, length);
+
     return NJS_OK;
 }
 
@@ -619,25 +652,53 @@ static njs_int_t
 njs_array_prototype_pop(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    njs_array_t        *array;
-    const njs_value_t  *retval, *value;
-
-    retval = &njs_value_undefined;
+    uint32_t     length;
+    njs_int_t    ret;
+    njs_array_t  *array;
+    njs_value_t  *value, *entry, index;
+
+    value = njs_arg(args, nargs, 0);
+
+    if (njs_slow_path(njs_is_null_or_undefined(value))) {
+        njs_type_error(vm, "Cannot convert undefined or null to object");
+        return NJS_ERROR;
+    }
+
+    njs_set_undefined(&vm->retval);
 
     if (njs_is_array(&args[0])) {
         array = njs_array(&args[0]);
 
         if (array->length != 0) {
             array->length--;
-            value = &array->start[array->length];
-
-            if (njs_is_valid(value)) {
-                retval = value;
+            entry = &array->start[array->length];
+
+            if (njs_is_valid(entry)) {
+                vm->retval = *entry;
             }
         }
+
+        return NJS_OK;
     }
 
-    vm->retval = *retval;
+    ret = njs_object_length(vm, value, &length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    if (length != 0) {
+        njs_uint32_to_string(&index, --length);
+
+        ret = njs_value_property_delete(vm, value, &index, &vm->retval);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+    }
+
+    ret = njs_object_length_set(vm, value, length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
 
     return NJS_OK;
 }
@@ -647,13 +708,28 @@ static njs_int_t
 njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
+    uint32_t     from, to, length;
     njs_int_t    ret;
     njs_uint_t   n;
     njs_array_t  *array;
-
-    if (njs_is_array(&args[0])) {
-        array = njs_array(&args[0]);
-        n = nargs - 1;
+    njs_value_t  *value, entry, index;
+
+    value = njs_arg(args, nargs, 0);
+    length = 0;
+    n = nargs - 1;
+
+    if (njs_slow_path(njs_is_null_or_undefined(value))) {
+        njs_type_error(vm, "Cannot convert undefined or null to object");
+        return NJS_ERROR;
+    }
+
+    if (njs_is_array(value)) {
+        array = njs_array(value);
+
+        if (array->length > (UINT32_MAX - n)) {
+            njs_type_error(vm, "Invalid length");
+            return NJS_ERROR;
+        }
 
         if (n != 0) {
             ret = njs_array_expand(vm, array, n, 0);
@@ -673,8 +749,66 @@ njs_array_prototype_unshift(njs_vm_t *vm
         }
 
         njs_set_number(&vm->retval, array->length);
+
+        return NJS_OK;
     }
 
+    ret = njs_object_length(vm, value, &length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    if (n == 0) {
+        goto done;
+    }
+
+    if (length > (UINT32_MAX - n)) {
+        njs_type_error(vm, "Invalid length");
+        return NJS_ERROR;
+    }
+
+    from = length;
+    length += n;
+    to = length;
+
+    while (from > 0) {
+        njs_uint32_to_string(&index, --from);
+
+        ret = njs_value_property_delete(vm, value, &index, &entry);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return ret;
+        }
+
+        to--;
+
+        if (ret == NJS_OK) {
+            njs_uint32_to_string(&index, to);
+
+            ret = njs_value_property_set(vm, value, &index, &entry);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                return ret;
+            }
+        }
+    }
+
+    for (n = 1; n < nargs; n++) {
+        njs_uint32_to_string(&index, n - 1);
+
+        ret = njs_value_property_set(vm, value, &index, &args[n]);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return ret;
+        }
+    }
+
+done:
+
+    ret = njs_object_length_set(vm, value, length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    njs_set_number(&vm->retval, length);
+
     return NJS_OK;
 }
 
@@ -683,10 +817,20 @@ static njs_int_t
 njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    njs_array_t        *array;
-    const njs_value_t  *retval, *value;
-
-    retval = &njs_value_undefined;
+    uint32_t     i, length;
+    njs_int_t    ret;
+    njs_array_t  *array;
+    njs_value_t  *value, *item, entry, index;
+
+    value = njs_arg(args, nargs, 0);
+    length = 0;
+
+    if (njs_slow_path(njs_is_null_or_undefined(value))) {
+        njs_type_error(vm, "Cannot convert undefined or null to object");
+        return NJS_ERROR;
+    }
+
+    njs_set_undefined(&vm->retval);
 
     if (njs_is_array(&args[0])) {
         array = njs_array(&args[0]);
@@ -694,16 +838,59 @@ njs_array_prototype_shift(njs_vm_t *vm, 
         if (array->length != 0) {
             array->length--;
 
-            value = &array->start[0];
+            item = &array->start[0];
             array->start++;
 
-            if (njs_is_valid(value)) {
-                retval = value;
+            if (njs_is_valid(item)) {
+                vm->retval = *item;
+            }
+        }
+
+        return NJS_OK;
+    }
+
+    ret = njs_object_length(vm, value, &length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
+
+    if (length == 0) {
+        goto done;
+    }
+
+    njs_uint32_to_string(&index, 0);
+
+    ret = njs_value_property_delete(vm, value, &index, &vm->retval);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    for (i = 1; i < length; i++) {
+        njs_uint32_to_string(&index, i);
+
+        ret = njs_value_property_delete(vm, value, &index, &entry);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return ret;
+        }
+
+        if (ret == NJS_OK) {
+            njs_uint32_to_string(&index, i - 1);
+
+            ret = njs_value_property_set(vm, value, &index, &entry);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                return ret;
             }
         }
     }
 
-    vm->retval = *retval;
+    length--;
+
+done:
+
+    ret = njs_object_length_set(vm, value, length);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return ret;
+    }
 
     return NJS_OK;
 }
diff -r a07722caaee3 -r 893fc436a363 src/njs_object.h
--- a/src/njs_object.h	Thu Sep 19 10:19:01 2019 +0300
+++ b/src/njs_object.h	Thu Sep 19 10:19:02 2019 +0300
@@ -86,4 +86,18 @@ extern const njs_object_init_t  njs_obje
 extern const njs_object_init_t  njs_object_prototype_init;
 
 
+njs_inline njs_int_t
+njs_object_length_set(njs_vm_t *vm, njs_value_t *value, uint32_t length)
+{
+    njs_value_t  index;
+
+    static const njs_value_t  string_length = njs_string("length");
+
+    njs_value_number_set(&index, length);
+
+    return njs_value_property_set(vm, value, njs_value_arg(&string_length),
+                                  &index);
+}
+
+
 #endif /* _NJS_OBJECT_H_INCLUDED_ */
diff -r a07722caaee3 -r 893fc436a363 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Thu Sep 19 10:19:01 2019 +0300
+++ b/src/test/njs_unit_test.c	Thu Sep 19 10:19:02 2019 +0300
@@ -3907,6 +3907,17 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("Array.prototype.pop()"),
       njs_str("undefined") },
 
+    { njs_str("var o = {0: 'a', 1: 'b', 2: 'c', 'length': 3};"
+              "Array.prototype.pop.call(o); var res = o.length;"
+              "Array.prototype.forEach.call(o, (v) => {res += ', ' + v}); res"),
+      njs_str("2, a, b") },
+
+    { njs_str("var obj = {}; obj.pop = Array.prototype.pop; obj.pop(); obj.length === 0"),
+      njs_str("true") },
+
+    { njs_str("var o = Object.freeze({0: 0, 1: 1, length: 2}); Array.prototype.pop.call(o)"),
+      njs_str("TypeError: Cannot delete property \"1\" of object") },
+
     { njs_str("Array.prototype.shift()"),
       njs_str("undefined") },
 
@@ -3939,9 +3950,22 @@ static njs_unit_test_t  njs_test[] =
                  "len +' '+ a.pop() +' '+ a"),
       njs_str("5 5 1,2,3,4") },
 
+    { njs_str("var x = {'0': 'a', '1': 'b', '2': 'c', 'length': 3};"
+              "Array.prototype.push.call(x, 'x', 'y', 'z', 123) +' '+ x[0] +' '+ x.length"),
+      njs_str("7 a 7") },
+
+    { njs_str("var x = {'0': 'a', '1': 'b', '2': 'c', 'length': 3}; var a = [];"
+              "Array.prototype.push.call(x, 'x', 'y', 'z', 123);"
+              "Array.prototype.forEach.call(x, (v) => a.push(v)); a"),
+      njs_str("a,b,c,x,y,z,123") },
+
     { njs_str("var a = [1,2,3]; a.shift() +' '+ a[0] +' '+ a.length"),
       njs_str("1 2 2") },
 
+    { njs_str("var x = {'0': 'x', '1': 'y', '2': 'z', 'length': 3};"
+              "Array.prototype.shift.call(x) +' '+ x[0] +' '+ x.length"),
+      njs_str("x y 2") },
+
     { njs_str("var a = [1,2], len = a.unshift(3);"
                  "len +' '+ a +' '+ a.shift()"),
       njs_str("3 3,1,2 3") },
@@ -3950,6 +3974,33 @@ static njs_unit_test_t  njs_test[] =
                  "len +' '+ a +' '+ a.shift()"),
       njs_str("5 3,4,5,1,2 3") },
 
+    { njs_str("var x = {'0': 'x', '1': 'y', '2': 'z', 'length': 3}; var a = [];"
+              "Array.prototype.unshift.call(x, 'a', 'b', 'c');"
+              "Array.prototype.forEach.call(x, (v) => a.push(v)); a + ', ' + x.length"),
+      njs_str("a,b,c,x,y,z, 6") },
+
+    { njs_str("var x = {}; x.length = 1; var a = [];"
+              "Array.prototype.unshift.call(x, 'a', 'b', 1025, 'c', 'oh my');"
+              "Array.prototype.forEach.call(x, (v) => a.push(v)); a + ', ' + x.length"),
+      njs_str("a,b,1025,c,oh my, 6") },
+
+    { njs_str("var x = {5: 1, 6: 2, length: 7}; var a = [];"
+              "Array.prototype.unshift.call(x, '0');"
+              "Array.prototype.forEach.call(x, (v) => a.push(v)); a + ', ' + x.length"),
+      njs_str("0,1,2, 8") },
+
+    { njs_str("var x = {5: 2, 10: 3, 11: 4, 12: 5, 20: 6, length: 21}; var a = [];"
+              "Array.prototype.unshift.call(x, '0', '1');"
+              "Array.prototype.forEach.call(x, (v, k) => a.push(k + ':' + v)); a + ', ' + x.length"),
+      njs_str("0:0,1:1,7:2,12:3,13:4,14:5,22:6, 23") },
+
+    { njs_str("var x = {0: 0, length: 4294967294};"
+              "Array.prototype.unshift.call(x, '0', '1');"),
+      njs_str("TypeError: Invalid length") },
+
+    { njs_str("var x = {0: 0}; Array.prototype.unshift.call(x); x.length"),
+      njs_str("0") },
+
     { njs_str("var a=[0], n = 64; while(--n) {a.push(n); a.shift()}; a"),
       njs_str("1") },
 


More information about the nginx-devel mailing list