From: Alexander Borisov Date: Fri, 5 Apr 2019 14:49:22 +0000 (+0300) Subject: Added support array-like objects for Function.prototype.apply(). X-Git-Tag: 0.3.1~23 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/static/gitweb.js?a=commitdiff_plain;h=c56ea9b99d2c071f12f8b022d5e61c1e6bad0ebb;p=njs.git Added support array-like objects for Function.prototype.apply(). This closes #51 issue on Github. --- diff --git a/njs/njs_function.c b/njs/njs_function.c index c0e8b0bc..0b07d3c5 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -906,44 +906,89 @@ njs_function_prototype_call(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, } +/* + * Non-primitive length values are not supported yet. To handle non-primitive + * values a continuation is needed. Currently, only one continuation per frame + * is supported. apply() is a special function which can add a second + * continuation to the continuation of the underling function. + * + * TODO: + * String.prototype.concat.apply('a', { length:{ valueOf: + * function() { return 2; } }, + * 0:'b', 1:'c'}) + */ static njs_ret_t njs_function_prototype_apply(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t retval) { - njs_ret_t ret; - njs_array_t *array; - njs_value_t *this; - njs_function_t *function; + uint32_t i; + njs_ret_t ret; + njs_value_t length, name; + njs_array_t *arr; + njs_function_t *func; + const njs_value_t *this, *arr_like; - if (!njs_is_function(&args[0])) { + static const njs_value_t njs_string_length = njs_string("length"); + + if (!njs_is_function(njs_arg(args, nargs, 0))) { njs_type_error(vm, "\"this\" argument is not a function"); return NXT_ERROR; } - function = args[0].data.u.function; - this = &args[1]; + func = (njs_argument(args, 0))->data.u.function; + this = njs_arg(args, nargs, 1); + arr_like = njs_arg(args, nargs, 2); - if (nargs > 2) { - if (!njs_is_array(&args[2])) { - njs_type_error(vm, "second argument is not an array"); - return NXT_ERROR; - } + if (njs_is_null_or_undefined(arr_like)) { + nargs = 0; - array = args[2].data.u.array; - args = array->start; - nargs = array->length; + goto activate; - } else { - if (nargs == 1) { - this = (njs_value_t *) &njs_value_undefined; - } + } else if (njs_is_array(arr_like)) { + arr = arr_like->data.u.array; - nargs = 0; + args = arr->start; + nargs = arr->length; + + goto activate; + + } else if (nxt_slow_path(!njs_is_object(arr_like))) { + njs_type_error(vm, "second argument is not an array-like object"); + return NXT_ERROR; } - ret = njs_function_activate(vm, function, this, args, nargs, retval, - sizeof(njs_vmcode_function_call_t)); + ret = njs_value_property(vm, arr_like, &njs_string_length, &length); + if (nxt_slow_path(ret == NXT_ERROR)) { + return ret; + } + + if (!njs_is_primitive(&length)) { + njs_type_error(vm, "non-primitive length values are not supported"); + return NXT_ERROR; + } + + nargs = njs_primitive_value_to_number(&length); + + arr = njs_array_alloc(vm, nargs, NJS_ARRAY_SPARE); + if (nxt_slow_path(arr == NULL)) { + return NXT_ERROR; + } + + args = arr->start; + for (i = 0; i < nargs; i++) { + njs_uint32_to_string(&name, i); + + ret = njs_value_property(vm, arr_like, &name, &args[i]); + if (nxt_slow_path(ret == NXT_ERROR)) { + return ret; + } + } + +activate: + + ret = njs_function_activate(vm, func, this, args, nargs, retval, + sizeof(njs_vmcode_function_call_t)); if (nxt_slow_path(ret == NXT_ERROR)) { return ret; } diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 01b44e5e..859479af 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -6148,7 +6148,7 @@ static njs_unit_test_t njs_test[] = nxt_string("5") }, { nxt_string("var f = function(a) { return this + a }; f.apply(5, 1)"), - nxt_string("TypeError: second argument is not an array") }, + nxt_string("TypeError: second argument is not an array-like object") }, { nxt_string("var f = function(a, b) { return this + a + b };" "f.apply(5, [1, 2])"), @@ -6158,6 +6158,57 @@ static njs_unit_test_t njs_test[] = "f.apply(5, [1, 2], 3)"), nxt_string("8") }, + { nxt_string("var f = function(a, b) { return this + a + b };" + "f.apply(5, {'length':2, '0':1, '1':2})"), + nxt_string("8") }, + + { nxt_string("var f = function(a, b) { return this + a + b };" + "f.apply(5, {'length':2, '0':1, '1':2, '2':3})"), + nxt_string("8") }, + + { nxt_string("var f = function(a, b, c) { return this + a + b + c};" + "f.apply(\"a\", {'length':2, '0':1, '1':2, '2':3})"), + nxt_string("a12undefined") }, + + { nxt_string("var f = function(a, b) { return this + a + b };" + "f.apply(5, {'length':3, '0':1, '1':2, '2':3})"), + nxt_string("8") }, + + { nxt_string("var f = function(a) { return this + a };" + "f.apply(5, {'nolength':3, '0':1, '1':2})"), + nxt_string("NaN") }, + + { nxt_string("var f = function(a) { return this };" + "f.apply(5, {'nolength':3, '0':1, '1':2})"), + nxt_string("5") }, + + { nxt_string("var f = function(a, b, c) { return this + a + b + c };" + "f.apply(\"a\", {'length':3, '0':1, '1':2})"), + nxt_string("a12undefined") }, + + { nxt_string("var f = function(a, b) { return this + a + b };" + "f.apply(\"a\", {'length':2, '0':undefined, '1':null})"), + nxt_string("aundefinednull") }, + + { nxt_string("var f = function() { return this };" + "f.apply(123, {})"), + nxt_string("123") }, + + { nxt_string("String.prototype.concat.apply('a', " + "{length:2, 0:{toString:function() {return 'b'}}, 1:'c'})"), + nxt_string("abc") }, + +#if 0 + /* TODO: non-primitive length values are not supported yet. */ + { nxt_string("String.prototype.concat.apply('a'," + "{length:{valueOf:function() {return 2}}, 0:'b', 1:'c'})"), + nxt_string("abc") }, +#else + { nxt_string("String.prototype.concat.apply('a'," + "{length:{valueOf:function() {return 2}}, 0:'b', 1:'c'})"), + nxt_string("TypeError: non-primitive length values are not supported") }, +#endif + { nxt_string("var a = function() { return 1 } + ''; a"), nxt_string("[object Function]") }, @@ -6183,7 +6234,7 @@ static njs_unit_test_t njs_test[] = nxt_string("a") }, { nxt_string("''.concat.apply('a', 'b')"), - nxt_string("TypeError: second argument is not an array") }, + nxt_string("TypeError: second argument is not an array-like object") }, { nxt_string("''.concat.apply('a', [ 'b', 'c' ])"), nxt_string("abc") },