From d5465143c23e04a860412c1dff877b3d17d2ab2a Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Tue, 23 Oct 2018 20:40:02 +0300 Subject: [PATCH] Handling non-array values in Array.prototype.slice. --- njs/njs_array.c | 193 ++++++++++++++++++++++++++++++--------- njs/njs_object_hash.h | 10 ++ njs/test/njs_unit_test.c | 84 +++++++++++++++++ 3 files changed, 244 insertions(+), 43 deletions(-) diff --git a/njs/njs_array.c b/njs/njs_array.c index c11ca05a..13a0047c 100644 --- a/njs/njs_array.c +++ b/njs/njs_array.c @@ -8,6 +8,19 @@ #include +typedef struct { + union { + njs_continuation_t cont; + u_char padding[NJS_CONTINUATION_SIZE]; + } u; + /* + * This retval value must be aligned so the continuation is padded + * to aligned size. + */ + njs_value_t length; +} njs_array_slice_t; + + typedef struct { njs_continuation_t cont; njs_value_t *values; @@ -67,6 +80,10 @@ typedef struct { } njs_array_sort_t; +static njs_ret_t njs_array_prototype_slice_continuation(njs_vm_t *vm, + njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_array_prototype_slice_copy(njs_vm_t *vm, + njs_value_t *this, int64_t start, int64_t length); static njs_ret_t njs_array_prototype_to_string_continuation(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t retval); static njs_ret_t njs_array_prototype_join_continuation(njs_vm_t *vm, @@ -434,58 +451,95 @@ static njs_ret_t njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) { - int32_t start, end, length; - uint32_t n; - njs_array_t *array; - njs_value_t *value; + njs_ret_t ret; + njs_array_slice_t *slice; - start = 0; - length = 0; + static const njs_value_t njs_string_length = njs_string("length"); - if (njs_is_array(&args[0])) { - length = args[0].data.u.array->length; + slice = njs_vm_continuation(vm); + slice->u.cont.function = njs_array_prototype_slice_continuation; - if (nargs > 1) { - start = args[1].data.u.number; + ret = njs_value_property(vm, &args[0], &njs_string_length, &slice->length); + if (nxt_slow_path(ret == NXT_ERROR || ret == NJS_TRAP)) { + return ret; + } - if (start < 0) { - start += length; + return njs_array_prototype_slice_continuation(vm, args, nargs, unused); +} - if (start < 0) { - start = 0; - } - } - if (start >= length) { - start = 0; - length = 0; +static njs_ret_t +njs_array_prototype_slice_continuation(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) +{ + int64_t start, end, length; + njs_array_slice_t *slice; - } else { - end = length; + slice = njs_vm_continuation(vm); - if (nargs > 2) { - end = args[2].data.u.number; + if (nxt_slow_path(!njs_is_primitive(&slice->length))) { + njs_vm_trap_value(vm, &slice->length); + return njs_trap(vm, NJS_TRAP_NUMBER_ARG); + } - if (end < 0) { - end += length; - } - } + start = (int32_t) njs_primitive_value_to_integer(njs_arg(args, nargs, 1)); + length = njs_primitive_value_to_integer(&slice->length); - if (length >= end) { - length = end - start; + if (start < 0) { + start += length; - if (length < 0) { - start = 0; - length = 0; - } + if (start < 0) { + start = 0; + } + } - } else { - length -= start; - } + if (start >= length) { + start = 0; + length = 0; + + } else { + if (!njs_is_void(njs_arg(args, nargs, 2))) { + end = (int32_t) njs_primitive_value_to_integer(&args[2]); + + } else { + end = length; + } + + if (end < 0) { + end += length; + } + + if (length >= end) { + length = end - start; + + if (length < 0) { + start = 0; + length = 0; } + + } else { + length -= start; } } + return njs_array_prototype_slice_copy(vm, &args[0], start, length); +} + + +static njs_ret_t +njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, + int64_t start, int64_t length) +{ + size_t size; + u_char *dst; + uint32_t n, len; + njs_ret_t ret; + njs_array_t *array; + njs_value_t *value, name; + const u_char *src, *end; + njs_slice_prop_t string_slice; + njs_string_prop_t string; + array = njs_array_alloc(vm, length, NJS_ARRAY_SPARE); if (nxt_slow_path(array == NULL)) { return NXT_ERROR; @@ -496,14 +550,66 @@ njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, vm->retval.data.truth = 1; if (length != 0) { - value = args[0].data.u.array->start; n = 0; - do { - /* GC: retain long string and object in values[start]. */ - array->start[n++] = value[start++]; - length--; - } while (length != 0); + if (nxt_fast_path(njs_is_array(this))) { + value = this->data.u.array->start; + + do { + /* GC: retain long string and object in values[start]. */ + array->start[n++] = value[start++]; + length--; + } while (length != 0); + + } else if (njs_is_string(this) || this->type == NJS_OBJECT_STRING) { + + if (this->type == NJS_OBJECT_STRING) { + this = &this->data.u.object_value->value; + } + + string_slice.start = start; + string_slice.length = length; + string_slice.string_length = njs_string_prop(&string, this); + + njs_string_slice_string_prop(&string, &string, &string_slice); + + src = string.start; + end = src + string.size; + + if (string.length == 0) { + /* Byte string. */ + len = 0; + + } else { + /* UTF-8 or ASCII string. */ + len = 1; + } + + do { + value = &array->start[n++]; + dst = njs_string_short_start(value); + dst = nxt_utf8_copy(dst, &src, end); + size = dst - njs_string_short_start(value); + njs_string_short_set(value, size, len); + + length--; + } while (length != 0); + + } else if (njs_is_object(this)) { + + do { + njs_uint32_to_string(&name, start++); + + value = &array->start[n++]; + ret = njs_value_property(vm, this, &name, value); + + if (ret != NXT_OK) { + *value = njs_value_invalid; + } + + length--; + } while (length != 0); + } } return NXT_OK; @@ -2101,7 +2207,8 @@ static const njs_object_prop_t njs_array_prototype_properties[] = { .type = NJS_METHOD, .name = njs_string("slice"), - .value = njs_native_function(njs_array_prototype_slice, 0, + .value = njs_native_function(njs_array_prototype_slice, + njs_continuation_size(njs_array_slice_t), NJS_OBJECT_ARG, NJS_INTEGER_ARG, NJS_INTEGER_ARG), }, diff --git a/njs/njs_object_hash.h b/njs/njs_object_hash.h index 8b7ce3ac..25df960e 100644 --- a/njs/njs_object_hash.h +++ b/njs/njs_object_hash.h @@ -108,6 +108,16 @@ 'j'), 'o'), 'i'), 'n') +#define NJS_LENGTH_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 'l'), 'e'), 'n'), 'g'), 't'), 'h') + + #define NJS_NAME_HASH \ nxt_djb_hash_add( \ nxt_djb_hash_add( \ diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index a08443ff..0dd97f96 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -3052,12 +3052,93 @@ static njs_unit_test_t njs_test[] = { nxt_string("Array.prototype.slice(1,2)"), nxt_string("") }, + { nxt_string("Array.prototype.slice.call(undefined)"), + nxt_string("TypeError: cannot convert void to object") }, + + { nxt_string("Array.prototype.slice.call(1)"), + nxt_string("") }, + + { nxt_string("Array.prototype.slice.call(false)"), + nxt_string("") }, + + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:1})"), + nxt_string("a") }, + + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2})"), + nxt_string("a,b") }, + + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:4})"), + nxt_string("a,b,,") }, + + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2}, 1)"), + nxt_string("b") }, + + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2}, 1, 2)"), + nxt_string("b") }, + + { nxt_string("Array.prototype.slice.call({length:'2'})"), + nxt_string(",") }, + + { nxt_string("njs.dump(Array.prototype.slice.call({length: 3, 1: undefined }))"), + nxt_string("[,undefined,]") }, + + { nxt_string("Array.prototype.slice.call({length:new Number(3)})"), + nxt_string(",,") }, + + { nxt_string("Array.prototype.slice.call({length: { valueOf: function() { return 2; } }})"), + nxt_string(",") }, + + { nxt_string("Array.prototype.slice.call({ length: Object.create(null) })"), + nxt_string("TypeError: Cannot convert object to primitive value") }, + + { nxt_string("Array.prototype.slice.call({length:-1})"), + nxt_string("MemoryError") }, + + { nxt_string("Array.prototype.slice.call('αβZγ')"), + nxt_string("α,β,Z,γ") }, + + { nxt_string("Array.prototype.slice.call('αβZγ', 1)"), + nxt_string("β,Z,γ") }, + + { nxt_string("Array.prototype.slice.call('αβZγ', 2)"), + nxt_string("Z,γ") }, + + { nxt_string("Array.prototype.slice.call('αβZγ', 3)"), + nxt_string("γ") }, + + { nxt_string("Array.prototype.slice.call('αβZγ', 4)"), + nxt_string("") }, + + { nxt_string("Array.prototype.slice.call('αβZγ', 0, 1)"), + nxt_string("α") }, + + { nxt_string("Array.prototype.slice.call('αβZγ', 1, 2)"), + nxt_string("β") }, + + { nxt_string("Array.prototype.slice.call('αβZγ').length"), + nxt_string("4") }, + + { nxt_string("Array.prototype.slice.call('αβZγ')[1].length"), + nxt_string("1") }, + + { nxt_string("Array.prototype.slice.call(new String('αβZγ'))"), + nxt_string("α,β,Z,γ") }, + { nxt_string("Array.prototype.pop()"), nxt_string("undefined") }, { nxt_string("Array.prototype.shift()"), nxt_string("undefined") }, + { nxt_string("[0,1].slice()"), + nxt_string("0,1") }, + + { nxt_string("[0,1].slice(undefined)"), + nxt_string("0,1") }, + + { nxt_string("[0,1].slice(undefined, undefined)"), + nxt_string("0,1") }, + { nxt_string("[0,1,2,3,4].slice(1,4)"), nxt_string("1,2,3") }, @@ -9667,6 +9748,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("njs.dump({a:1, b:[1,,2,{c:new Boolean(1)}]})"), nxt_string("{a:1,b:[1,,2,{c:[Boolean: true]}]}") }, + { nxt_string("njs.dump(Array.prototype.slice.call({'1':'b', length:2}))"), + nxt_string("[,'b']") }, + { nxt_string("njs.dump($r.props)"), nxt_string("{a:{type:\"property\",props:[\"getter\"]},b:{type:\"property\",props:[\"getter\"]}}") }, -- 2.47.3