From: Alexander Borisov Date: Wed, 9 Oct 2019 15:54:54 +0000 (+0300) Subject: Fixed Array functions according to specification. X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=50c33dee36bb475f0b0187426db4fd553bdc4083;p=njs.git 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. --- diff --git a/src/njs_array.c b/src/njs_array.c index adab009d..37459eea 100644 --- a/src/njs_array.c +++ b/src/njs_array.c @@ -2014,6 +2014,41 @@ njs_array_iterator_call(njs_vm_t *vm, njs_array_iterator_args_t *args, } +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 *vm, njs_value_t *args, njs_uint_t nargs, 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, njs_value_t *args, njs_uint_t nargs, 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_value_t *args, njs_uint_t nargs, 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_value_t *args, njs_uint_t nargs, 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, njs_value_t *args, njs_uint_t nargs, 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 *vm, njs_value_t *args, 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, njs_value_t *args, njs_uint_t nargs, 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, njs_value_t *args, njs_uint_t nargs, 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, njs_value_t *args, njs_uint_t nargs, 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 *args, njs_uint_t nargs, 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_t *vm, njs_value_t *args, 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); } @@ -2622,6 +2578,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 --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 644d106e..9d62b020 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -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") },