[njs] Fixed Array functions according to specification.

Alexander Borisov alexander.borisov at nginx.com
Wed Oct 9 15:56:20 UTC 2019


details:   https://hg.nginx.org/njs/rev/6ed84cda7426
branches:  
changeset: 1177:6ed84cda7426
user:      Alexander Borisov <alexander.borisov at nginx.com>
date:      Wed Oct 09 18:54:54 2019 +0300
description:
Fixed Array functions according to specification.

Length property should be queried before checking arguments for validity.

?ffected functions:
every, filter, find, findIndex, forEach, map, reduce, reduceRight, some.

This closes #228 issue on GitHub.

diffstat:

 src/njs_array.c          |  194 ++++++++++++++++++----------------------------
 src/test/njs_unit_test.c |   81 +++++++++++++++++++
 2 files changed, 159 insertions(+), 116 deletions(-)

diffs (518 lines):

diff -r 9f1ba171d81e -r 6ed84cda7426 src/njs_array.c
--- a/src/njs_array.c	Tue Oct 08 15:56:58 2019 +0300
+++ b/src/njs_array.c	Wed Oct 09 18:54:54 2019 +0300
@@ -2014,6 +2014,41 @@ njs_array_iterator_call(njs_vm_t *vm, nj
 }
 
 
+njs_inline njs_int_t
+njs_array_validate_args(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_array_iterator_args_t *iargs)
+{
+    njs_int_t  ret;
+
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))) {
+        goto failed;
+    }
+
+    iargs->value = njs_argument(args, 0);
+
+    ret = njs_value_length(vm, iargs->value, &iargs->to);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    if (njs_slow_path(!njs_is_function(njs_arg(args, nargs, 1)))) {
+        goto failed;
+    }
+
+    iargs->from = 0;
+    iargs->function = njs_function(njs_argument(args, 1));
+    iargs->argument = njs_arg(args, nargs, 2);
+
+    return NJS_OK;
+
+failed:
+
+    njs_type_error(vm, "unexpected iterator arguments");
+
+    return NJS_ERROR;
+}
+
+
 static njs_int_t
 njs_array_handler_for_each(njs_vm_t *vm, njs_array_iterator_args_t *args,
     njs_value_t *entry, uint32_t n)
@@ -2033,20 +2068,7 @@ njs_array_prototype_for_each(njs_vm_t *v
     njs_int_t                  ret;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
-    }
-
-    iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
-    iargs.argument = njs_arg(args, nargs, 2);
-
-    iargs.from = 0;
-
-    ret = njs_value_length(vm, iargs.value, &iargs.to);
+    ret = njs_array_validate_args(vm, args, nargs, &iargs);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -2092,20 +2114,7 @@ njs_array_prototype_some(njs_vm_t *vm, n
     njs_int_t                  ret;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
-    }
-
-    iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
-    iargs.argument = njs_arg(args, nargs, 2);
-
-    iargs.from = 0;
-
-    ret = njs_value_length(vm, iargs.value, &iargs.to);
+    ret = njs_array_validate_args(vm, args, nargs, &iargs);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -2153,20 +2162,7 @@ njs_array_prototype_every(njs_vm_t *vm, 
     njs_int_t                  ret;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
-    }
-
-    iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
-    iargs.argument = njs_arg(args, nargs, 2);
-
-    iargs.from = 0;
-
-    ret = njs_value_length(vm, iargs.value, &iargs.to);
+    ret = njs_array_validate_args(vm, args, nargs, &iargs);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -2219,20 +2215,7 @@ njs_array_prototype_filter(njs_vm_t *vm,
     njs_int_t                  ret;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
-    }
-
-    iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
-    iargs.argument = njs_arg(args, nargs, 2);
-
-    iargs.from = 0;
-
-    ret = njs_value_length(vm, iargs.value, &iargs.to);
+    ret = njs_array_validate_args(vm, args, nargs, &iargs);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -2289,20 +2272,7 @@ njs_array_prototype_find(njs_vm_t *vm, n
     njs_int_t                  ret;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
-    }
-
-    iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
-    iargs.argument = njs_arg(args, nargs, 2);
-
-    iargs.from = 0;
-
-    ret = njs_value_length(vm, iargs.value, &iargs.to);
+    ret = njs_array_validate_args(vm, args, nargs, &iargs);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -2356,20 +2326,7 @@ njs_array_prototype_find_index(njs_vm_t 
     njs_int_t                  ret;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
-    }
-
-    iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
-    iargs.argument = njs_arg(args, nargs, 2);
-
-    iargs.from = 0;
-
-    ret = njs_value_length(vm, iargs.value, &iargs.to);
+    ret = njs_array_validate_args(vm, args, nargs, &iargs);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
@@ -2422,22 +2379,21 @@ njs_array_prototype_map(njs_vm_t *vm, nj
     njs_array_t                *array;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))) {
+        goto unexpected_args;
     }
 
     iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
-    iargs.argument = njs_arg(args, nargs, 2);
 
     ret = njs_value_length(vm, iargs.value, &length);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
 
+    if (njs_slow_path(!njs_is_function(njs_arg(args, nargs, 1)))) {
+        goto unexpected_args;
+    }
+
     iargs.array = njs_array_alloc(vm, length, 0);
     if (njs_slow_path(iargs.array == NULL)) {
         return NJS_ERROR;
@@ -2453,6 +2409,9 @@ njs_array_prototype_map(njs_vm_t *vm, nj
         iargs.from = 0;
         iargs.to = length;
 
+        iargs.function = njs_function(njs_argument(args, 1));
+        iargs.argument = njs_arg(args, nargs, 2);
+
         ret = njs_array_iterator(vm, &iargs, njs_array_handler_map);
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
@@ -2470,6 +2429,12 @@ njs_array_prototype_map(njs_vm_t *vm, nj
     njs_set_array(&vm->retval, iargs.array);
 
     return NJS_OK;
+
+unexpected_args:
+
+    njs_type_error(vm, "unexpected iterator arguments");
+
+    return NJS_ERROR;
 }
 
 
@@ -2522,30 +2487,19 @@ njs_array_prototype_reduce(njs_vm_t *vm,
     njs_value_t                accumulator;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
+    ret = njs_array_validate_args(vm, args, nargs, &iargs);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
     }
 
     njs_set_invalid(&accumulator);
 
     if (nargs > 2) {
-        accumulator = *njs_argument(args, 2);
+        accumulator = *iargs.argument;
     }
 
-    iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
     iargs.argument = &accumulator;
 
-    iargs.from = 0;
-
-    ret = njs_value_length(vm, iargs.value, &iargs.to);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return ret;
-    }
-
     ret = njs_array_iterator(vm, &iargs, njs_array_handler_reduce);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
@@ -2570,25 +2524,27 @@ njs_array_prototype_reduce_right(njs_vm_
     njs_value_t                accumulator;
     njs_array_iterator_args_t  iargs;
 
-    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))
-        || !njs_is_function(njs_arg(args, nargs, 1)))
-    {
-        njs_type_error(vm, "unexpected iterator arguments");
-        return NJS_ERROR;
+    if (njs_is_null_or_undefined(njs_arg(args, nargs, 0))) {
+        goto unexpected_args;
     }
 
-    njs_set_invalid(&accumulator);
-
     iargs.value = njs_argument(args, 0);
-    iargs.function = njs_function(&args[1]);
-    iargs.argument = &accumulator;
-    iargs.to = 0;
 
     ret = njs_value_length(vm, iargs.value, &iargs.from);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
     }
 
+    if (njs_slow_path(!njs_is_function(njs_arg(args, nargs, 1)))) {
+        goto unexpected_args;
+    }
+
+    njs_set_invalid(&accumulator);
+
+    iargs.to = 0;
+    iargs.function = njs_function(njs_argument(args, 1));
+    iargs.argument = &accumulator;
+
     if (nargs > 2) {
         accumulator = *njs_argument(args, 2);
     }
@@ -2623,6 +2579,12 @@ failed:
     njs_type_error(vm, "Reduce of empty object with no initial value");
 
     return NJS_ERROR;
+
+unexpected_args:
+
+    njs_type_error(vm, "unexpected iterator arguments");
+
+    return NJS_ERROR;
 }
 
 
diff -r 9f1ba171d81e -r 6ed84cda7426 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Tue Oct 08 15:56:58 2019 +0300
+++ b/src/test/njs_unit_test.c	Wed Oct 09 18:54:54 2019 +0300
@@ -4088,6 +4088,11 @@ static njs_unit_test_t  njs_test[] =
     { 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("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.pop.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: Cannot set property \"length\" of object which has only a getter") },
+
     { njs_str("Array.prototype.shift()"),
       njs_str("undefined") },
 
@@ -4129,6 +4134,11 @@ static njs_unit_test_t  njs_test[] =
               "Array.prototype.forEach.call(x, (v) => a.push(v)); a"),
       njs_str("a,b,c,x,y,z,123") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.push.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: Cannot set property \"length\" of object which has only a getter") },
+
     { njs_str("var a = [1,2,3]; a.shift() +' '+ a[0] +' '+ a.length"),
       njs_str("1 2 2") },
 
@@ -4136,6 +4146,11 @@ static njs_unit_test_t  njs_test[] =
               "Array.prototype.shift.call(x) +' '+ x[0] +' '+ x.length"),
       njs_str("x y 2") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.shift.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: Cannot set property \"length\" of object which has only a getter") },
+
     { njs_str("var a = [1,2], len = a.unshift(3);"
                  "len +' '+ a +' '+ a.shift()"),
       njs_str("3 3,1,2 3") },
@@ -4177,6 +4192,11 @@ static njs_unit_test_t  njs_test[] =
               "Array.prototype.forEach.call(obj, (v) => a.push(v)); a"),
       njs_str("a,b,c,x,y,z")},
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.unshift.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: Cannot set property \"length\" of object which has only a getter") },
+
     { njs_str("var a=[0], n = 64; while(--n) {a.push(n); a.shift()}; a"),
       njs_str("1") },
 
@@ -4257,6 +4277,10 @@ static njs_unit_test_t  njs_test[] =
               "Array.prototype.indexOf.call(o, 'd')"),
       njs_str("3") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "Array.prototype.indexOf.call(o); i"),
+      njs_str("1") },
+
     { njs_str("[].lastIndexOf(1, -1)"),
       njs_str("-1") },
 
@@ -4337,6 +4361,10 @@ static njs_unit_test_t  njs_test[] =
               "Array.prototype.lastIndexOf.call(obj, 'y');"),
       njs_str("10000001")},
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "Array.prototype.lastIndexOf.call(o); i"),
+      njs_str("1") },
+
     { njs_str("[1,2,3,4].includes()"),
       njs_str("false") },
 
@@ -4437,6 +4465,11 @@ static njs_unit_test_t  njs_test[] =
               "Array.prototype.forEach.call(s, function (a, b, c) {t = typeof c;}); [t, typeof s];"),
       njs_str("object,string") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.forEach.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var a = [];"
                  "a.some(function(v, i, a) { return v > 1 })"),
       njs_str("false") },
@@ -4467,6 +4500,11 @@ static njs_unit_test_t  njs_test[] =
               "var r = Array.prototype.some.call(o, function(v, i, a) { return v === 'd' }); r"),
       njs_str("true") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.some.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var a = [];"
                  "a.every(function(v, i, a) { return v > 1 })"),
       njs_str("true") },
@@ -4491,6 +4529,11 @@ static njs_unit_test_t  njs_test[] =
               "var r = Array.prototype.every.call(o, function(el, i, arr) {return el == 'c'}); r"),
       njs_str("true") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.every.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var o = {0: 'x', 1: 'y', 2: 'z'};"
               "Object.defineProperty(o, 'length', {get: () => 4});"
               "Object.defineProperty(o, '3', {get: () => 'a'});"
@@ -4604,6 +4647,10 @@ static njs_unit_test_t  njs_test[] =
       njs_str("RangeError: Maximum call stack size exceeded") },
 #endif
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "Array.prototype.fill.call(o); i"),
+      njs_str("1") },
+
     { njs_str("var a = [];"
                  "a.filter(function(v, i, a) { return v > 1 })"),
       njs_str("") },
@@ -4646,6 +4693,11 @@ static njs_unit_test_t  njs_test[] =
               "var r = Array.prototype.filter.call(o, function(el, i, arr) { return el == 'c' }); r"),
       njs_str("c,c") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.filter.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var a = [];"
                  "a.find(function(v, i, a) { return v > 1 })"),
       njs_str("undefined") },
@@ -4703,6 +4755,11 @@ static njs_unit_test_t  njs_test[] =
               "var r = Array.prototype.find.call(o, function(el, i, arr) { return el == 'd' }); r"),
       njs_str("d") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.find.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var a = [];"
                  "a.findIndex(function(v, i, a) { return v > 1 })"),
       njs_str("-1") },
@@ -4757,6 +4814,11 @@ static njs_unit_test_t  njs_test[] =
               "var r = Array.prototype.findIndex.call(o, function(el, i, arr) { return el == 'd' }); r"),
       njs_str("3") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.findIndex.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var a = [];"
                  "a.map(function(v, i, a) { return v + 1 })"),
       njs_str("") },
@@ -4809,6 +4871,11 @@ static njs_unit_test_t  njs_test[] =
               "var res = Array.prototype.map.call(obj, callbackfn); typeof res[8000]"),
       njs_str("undefined") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.map.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var a = [];"
                  "a.reduce(function(p, v, i, a) { return p + v })"),
       njs_str("TypeError: Reduce of empty object with no initial value") },
@@ -4862,6 +4929,11 @@ static njs_unit_test_t  njs_test[] =
               "var r = Array.prototype.reduce.call(o, (a, b) => a + b); r"),
       njs_str("abcd") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.reduce.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var a = [];"
                  "a.reduceRight(function(p, v, i, a) { return p + v })"),
       njs_str("TypeError: Reduce of empty object with no initial value") },
@@ -4910,6 +4982,11 @@ static njs_unit_test_t  njs_test[] =
               "Array.prototype.reduceRight.call(o, (p, v) => p + v)"),
       njs_str("dcba") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "try {Array.prototype.reduceRight.call(o);}"
+              "catch (e) {i += '; ' + e} i"),
+      njs_str("1; TypeError: unexpected iterator arguments") },
+
     { njs_str("var a = ['1','2','3','4','5','6']; a.sort()"),
       njs_str("1,2,3,4,5,6") },
 
@@ -6053,6 +6130,10 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("'абв абв абвгдежз'.includes('абвгд', 9)"),
       njs_str("false") },
 
+    { njs_str("var i = 0; var o = {get length() {i++}};"
+              "Array.prototype.includes.call(o); i"),
+      njs_str("1") },
+
     { njs_str("''.startsWith('')"),
       njs_str("true") },
 


More information about the nginx-devel mailing list