From: Dmitry Volyntsev Date: Tue, 4 Feb 2020 17:35:02 +0000 (+0300) Subject: Introduced memory-efficient arrays. X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/static/gitweb.js?a=commitdiff_plain;h=58ebb38a063a3be7167da944b800f6c44cf90972;p=njs.git Introduced memory-efficient arrays. 1) If "length" of ordinary array exceeds 32768 it is converted to to array-like object. 2) Array.prototype.concat() is rewritten according to the spec. 3) Array.prototype.slice() is rewritten according to the spec. --- diff --git a/src/njs_array.c b/src/njs_array.c index 3378115a..d72b72fd 100644 --- a/src/njs_array.c +++ b/src/njs_array.c @@ -8,7 +8,7 @@ #include -#define NJS_ARRAY_LARGE_OBJECT_LENGTH 4096 +#define njs_fast_object(_sz) ((_sz) <= NJS_ARRAY_FAST_OBJECT_LENGTH) typedef struct { @@ -29,35 +29,36 @@ typedef njs_int_t (*njs_array_iterator_handler_t)(njs_vm_t *vm, static njs_int_t njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, int64_t start, int64_t length); -static njs_value_t *njs_array_copy(njs_value_t *dst, njs_value_t *src); -static njs_array_t *njs_object_indexes(njs_vm_t *vm, njs_value_t *object); njs_array_t * njs_array_alloc(njs_vm_t *vm, uint64_t length, uint32_t spare) { uint64_t size; + njs_int_t ret; njs_array_t *array; + njs_value_t value; if (njs_slow_path(length > UINT32_MAX)) { goto overflow; } - size = length + spare; - - if (njs_slow_path(size > NJS_ARRAY_MAX_LENGTH)) { - goto memory_error; - } - array = njs_mp_alloc(vm->mem_pool, sizeof(njs_array_t)); if (njs_slow_path(array == NULL)) { goto memory_error; } - array->data = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), - size * sizeof(njs_value_t)); - if (njs_slow_path(array->data == NULL)) { - goto memory_error; + size = length + spare; + + if (size <= NJS_ARRAY_LARGE_OBJECT_LENGTH) { + array->data = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), + size * sizeof(njs_value_t)); + if (njs_slow_path(array->data == NULL)) { + goto memory_error; + } + + } else { + array->data = NULL; } array->start = array->data; @@ -67,9 +68,23 @@ njs_array_alloc(njs_vm_t *vm, uint64_t length, uint32_t spare) array->object.type = NJS_ARRAY; array->object.shared = 0; array->object.extensible = 1; - array->object.fast_array = 1; - array->size = size; - array->length = length; + array->object.error_data = 0; + array->object.fast_array = (array->data != NULL); + + if (njs_fast_path(array->object.fast_array)) { + array->size = size; + array->length = length; + + } else { + array->size = 0; + array->length = 0; + + njs_set_array(&value, array); + ret = njs_array_length_redefine(vm, &value, length); + if (njs_slow_path(ret != NJS_OK)) { + return NULL; + } + } return array; @@ -87,6 +102,130 @@ overflow: } +njs_int_t +njs_array_convert_to_slow_array(njs_vm_t *vm, njs_array_t *array) +{ + uint32_t i, length; + njs_value_t index, value; + njs_object_prop_t *prop; + + njs_set_array(&value, array); + array->object.fast_array = 0; + + length = array->length; + + for (i = 0; i < length; i++) { + njs_uint32_to_string(&index, i); + + if (njs_is_valid(&array->start[i])) { + prop = njs_object_property_add(vm, &value, &index, 0); + if (njs_slow_path(prop == NULL)) { + return NJS_ERROR; + } + + prop->value = array->start[i]; + } + } + + /* GC: release value. */ + + njs_mp_free(vm->mem_pool, array->start); + array->start = NULL; + + return NJS_OK; +} + + +njs_int_t +njs_array_length_redefine(njs_vm_t *vm, njs_value_t *value, uint32_t length) +{ + njs_object_prop_t *prop; + + static const njs_value_t string_length = njs_string("length"); + + if (njs_slow_path(!njs_is_array(value))) { + njs_internal_error(vm, "njs_array_length_redefine() " + "applied to non-array"); + return NJS_ERROR; + } + + prop = njs_object_property_add(vm, value, njs_value_arg(&string_length), 1); + if (njs_slow_path(prop == NULL)) { + njs_internal_error(vm, "njs_array_length_redefine() " + "cannot redefine \"length\""); + return NJS_ERROR; + } + + prop->enumerable = 0; + prop->configurable = 0; + + njs_value_number_set(&prop->value, length); + + return NJS_OK; +} + + +njs_int_t +njs_array_length_set(njs_vm_t *vm, njs_value_t *value, + njs_object_prop_t *prev, njs_value_t *setval) +{ + double num, idx; + uint32_t i, length, prev_length; + njs_int_t ret; + njs_array_t *array, *keys; + + array = njs_object_proto_lookup(njs_object(value), NJS_ARRAY, njs_array_t); + if (njs_slow_path(array == NULL)) { + return NJS_DECLINED; + } + + ret = njs_value_to_number(vm, setval, &num); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + length = njs_number_to_length(num); + if ((double) length != num) { + njs_range_error(vm, "Invalid array length"); + return NJS_ERROR; + } + + ret = njs_value_to_length(vm, &prev->value, &prev_length); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (length < prev_length) { + keys = njs_array_indices(vm, value); + if (njs_slow_path(keys == NULL)) { + return NJS_ERROR; + } + + if (keys->length != 0) { + i = keys->length - 1; + + do { + idx = njs_string_to_index(&keys->start[i]); + if (idx >= length) { + ret = njs_value_property_delete(vm, value, &keys->start[i], + NULL); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + } while (i-- != 0); + } + } + + ret = njs_array_length_redefine(vm, value, length); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return NJS_OK; +} + + njs_int_t njs_array_add(njs_vm_t *vm, njs_array_t *array, njs_value_t *value) { @@ -206,24 +345,25 @@ njs_array_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, if (njs_fast_path(array != NULL)) { - value = array->start; + if (array->object.fast_array) { + value = array->start; - if (args == NULL) { - while (size != 0) { - njs_set_invalid(value); - value++; - size--; - } + if (args == NULL) { + while (size != 0) { + njs_set_invalid(value); + value++; + size--; + } - } else { - while (size != 0) { - njs_retain(args); - *value++ = *args++; - size--; + } else { + while (size != 0) { + njs_retain(args); + *value++ = *args++; + size--; + } } } - njs_set_array(&vm->retval, array); return NJS_OK; @@ -253,26 +393,28 @@ njs_array_is_array(njs_vm_t *vm, njs_value_t *args, static njs_int_t -njs_array_of(njs_vm_t *vm, njs_value_t *args, - njs_uint_t nargs, njs_index_t unused) +njs_array_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) { - uint32_t length, i; - njs_array_t *array; + uint32_t length, i; + njs_array_t *array; - length = nargs > 1 ? nargs - 1 : 0; + length = nargs > 1 ? nargs - 1 : 0; - array = njs_array_alloc(vm, length, NJS_ARRAY_SPARE); - if (njs_slow_path(array == NULL)) { - return NJS_ERROR; - } + array = njs_array_alloc(vm, length, NJS_ARRAY_SPARE); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } - njs_set_array(&vm->retval, array); + njs_set_array(&vm->retval, array); + if (array->object.fast_array) { for (i = 0; i < length; i++) { array->start[i] = args[i + 1]; } + } - return NJS_OK; + return NJS_OK; } @@ -358,18 +500,12 @@ njs_array_length(njs_vm_t *vm,njs_object_prop_t *prop, njs_value_t *value, return NJS_DECLINED; } - if (njs_slow_path(!njs_is_number(setval))) { - ret = njs_value_to_number(vm, setval, &num); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - } else { - num = njs_number(setval); + ret = njs_value_to_number(vm, setval, &num); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } length = njs_number_to_length(num); - if ((double) length != num) { njs_range_error(vm, "Invalid array length"); return NJS_ERROR; @@ -377,35 +513,46 @@ njs_array_length(njs_vm_t *vm,njs_object_prop_t *prop, njs_value_t *value, array = (njs_array_t *) proto; - size = (int64_t) length - array->length; + if (njs_fast_path(array->object.fast_array)) { + if (njs_fast_path(length <= NJS_ARRAY_LARGE_OBJECT_LENGTH)) { + size = (int64_t) length - array->length; - if (size > 0) { - ret = njs_array_expand(vm, array, 0, size); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } + if (size > 0) { + ret = njs_array_expand(vm, array, 0, size); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } - val = &array->start[array->length]; + val = &array->start[array->length]; - do { - njs_set_invalid(val); - val++; - size--; - } while (size != 0); + do { + njs_set_invalid(val); + val++; + size--; + } while (size != 0); + } + + array->length = length; + + *retval = *setval; + return NJS_OK; + } + + ret = njs_array_convert_to_slow_array(vm, array); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } } - array->length = length; + prop->type = NJS_PROPERTY; + njs_set_number(&prop->value, length); *retval = *setval; + return NJS_OK; } -/* - * Array.slice(start[, end]). - * JavaScript 1.2, ECMAScript 3. - */ - static njs_int_t njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) @@ -413,16 +560,16 @@ njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, int64_t start, end, length; uint32_t object_length; njs_int_t ret; - njs_value_t *value; + njs_value_t *this; - value = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); - ret = njs_value_to_object(vm, value); + ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } - ret = njs_object_length(vm, value, &object_length); + ret = njs_object_length(vm, this, &object_length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -474,7 +621,7 @@ njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } - return njs_array_prototype_slice_copy(vm, value, start, length); + return njs_array_prototype_slice_copy(vm, this, start, length); } @@ -486,8 +633,8 @@ njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, u_char *dst; uint32_t n; njs_int_t ret; - njs_array_t *array; - njs_value_t *value, name; + njs_array_t *array, *keys; + njs_value_t *value, index, retval, array_value; const u_char *src, *end; njs_slice_prop_t string_slice; njs_string_prop_t string; @@ -497,17 +644,37 @@ njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, return NJS_ERROR; } - njs_set_array(&vm->retval, array); + if (njs_slow_path(length == 0)) { + goto done; + } - if (length != 0) { - n = 0; + n = 0; - if (njs_fast_path(njs_is_array(this))) { + if (njs_fast_path(array->object.fast_array)) { + if (njs_fast_path(njs_is_fast_array(this))) { value = njs_array_start(this); do { - /* GC: retain long string and object in values[start]. */ - array->start[n++] = value[start++]; + if (njs_fast_path(njs_is_valid(&value[start]))) { + array->start[n++] = value[start++]; + + } else { + + /* src value may be in Array.prototype object. */ + + njs_uint32_to_string(&index, start++); + + value = &array->start[n++]; + ret = njs_value_property(vm, this, &index, value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + if (ret != NJS_OK) { + njs_set_invalid(value); + } + } + length--; } while (length != 0); @@ -553,10 +720,10 @@ njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, } else if (njs_is_object(this)) { do { - njs_uint32_to_string(&name, start++); + njs_uint32_to_string(&index, start++); value = &array->start[n++]; - ret = njs_value_property(vm, this, &name, value); + ret = njs_value_property(vm, this, &index, value); if (ret != NJS_OK) { njs_set_invalid(value); @@ -576,8 +743,56 @@ njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, length--; } while (length != 0); } + + goto done; + } + + njs_set_array(&array_value, array); + + if (njs_fast_object(length)) { + do { + njs_uint32_to_string(&index, start++); + + ret = njs_value_property(vm, this, &index, &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + if (ret == NJS_OK) { + ret = njs_value_property_set(vm, &array_value, &index, &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + + length--; + } while (length != 0); + + goto done; + } + + keys = njs_array_indices(vm, this); + if (njs_slow_path(keys == NULL)) { + return NJS_ERROR; + } + + for (n = 0; n < keys->length; n++) { + ret = njs_value_property(vm, this, &keys->start[n], &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + ret = njs_value_property_set(vm, &array_value, &keys->start[n], + &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } } +done: + + njs_set_array(&vm->retval, array); + return NJS_OK; } @@ -590,18 +805,18 @@ njs_array_prototype_push(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_int_t ret; njs_uint_t i; njs_array_t *array; - njs_value_t *value, index; + njs_value_t *this, index; - value = njs_arg(args, nargs, 0); length = 0; + this = njs_argument(args, 0); - ret = njs_value_to_object(vm, value); + ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } - if (njs_is_array(&args[0])) { - array = njs_array(&args[0]); + if (njs_is_fast_array(this)) { + array = njs_array(this); if (nargs != 0) { ret = njs_array_expand(vm, array, 0, nargs); @@ -620,7 +835,7 @@ njs_array_prototype_push(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_OK; } - ret = njs_object_length(vm, value, &length); + ret = njs_object_length(vm, this, &length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -628,13 +843,13 @@ njs_array_prototype_push(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, for (i = 1; i < nargs; i++) { njs_uint32_to_string(&index, length++); - ret = njs_value_property_set(vm, value, &index, &args[i]); + ret = njs_value_property_set(vm, this, &index, &args[i]); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } } - ret = njs_object_length_set(vm, value, length); + ret = njs_object_length_set(vm, this, length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -652,19 +867,19 @@ njs_array_prototype_pop(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, uint32_t length; njs_int_t ret; njs_array_t *array; - njs_value_t *value, *entry, index; + njs_value_t *this, *entry, index; - value = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); - ret = njs_value_to_object(vm, value); + ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_set_undefined(&vm->retval); - if (njs_is_array(&args[0])) { - array = njs_array(&args[0]); + if (njs_is_fast_array(this)) { + array = njs_array(this); if (array->length != 0) { array->length--; @@ -678,7 +893,7 @@ njs_array_prototype_pop(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_OK; } - ret = njs_object_length(vm, value, &length); + ret = njs_object_length(vm, this, &length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -686,13 +901,13 @@ njs_array_prototype_pop(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, if (length != 0) { njs_uint32_to_string(&index, --length); - ret = njs_value_property_delete(vm, value, &index, &vm->retval); + ret = njs_value_property_delete(vm, this, &index, &vm->retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } } - ret = njs_object_length_set(vm, value, length); + ret = njs_object_length_set(vm, this, length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -710,19 +925,19 @@ njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_int_t ret; njs_uint_t n; njs_array_t *array, *keys; - njs_value_t *value, entry, index; + njs_value_t *this, entry, index; - value = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); length = 0; n = nargs - 1; - ret = njs_value_to_object(vm, value); + ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } - if (njs_is_array(value)) { - array = njs_array(value); + if (njs_is_fast_array(this)) { + array = njs_array(this); if (array->length > (UINT32_MAX - n)) { njs_type_error(vm, "Invalid length"); @@ -751,7 +966,7 @@ njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_OK; } - ret = njs_object_length(vm, value, &length); + ret = njs_object_length(vm, this, &length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -765,8 +980,8 @@ njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (length > NJS_ARRAY_LARGE_OBJECT_LENGTH) { - keys = njs_object_indexes(vm, value); + if (!njs_fast_object(length)) { + keys = njs_array_indices(vm, this); if (njs_slow_path(keys == NULL)) { return NJS_ERROR; } @@ -774,7 +989,7 @@ njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, from = keys->length; while (from > 0) { - ret = njs_value_property_delete(vm, value, &keys->start[--from], + ret = njs_value_property_delete(vm, this, &keys->start[--from], &entry); if (njs_slow_path(ret == NJS_ERROR)) { return ret; @@ -785,7 +1000,7 @@ njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_uint32_to_string(&index, (uint32_t) idx + nargs - 1); - ret = njs_value_property_set(vm, value, &index, &entry); + ret = njs_value_property_set(vm, this, &index, &entry); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -804,7 +1019,7 @@ njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, while (from > 0) { njs_uint32_to_string(&index, --from); - ret = njs_value_property_delete(vm, value, &index, &entry); + ret = njs_value_property_delete(vm, this, &index, &entry); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -814,7 +1029,7 @@ njs_array_prototype_unshift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, if (ret == NJS_OK) { njs_uint32_to_string(&index, to); - ret = njs_value_property_set(vm, value, &index, &entry); + ret = njs_value_property_set(vm, this, &index, &entry); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -826,7 +1041,7 @@ copy: for (n = 1; n < nargs; n++) { njs_uint32_to_string(&index, n - 1); - ret = njs_value_property_set(vm, value, &index, &args[n]); + ret = njs_value_property_set(vm, this, &index, &args[n]); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -834,7 +1049,7 @@ copy: done: - ret = njs_object_length_set(vm, value, length); + ret = njs_object_length_set(vm, this, length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -852,20 +1067,20 @@ njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, uint32_t i, length; njs_int_t ret; njs_array_t *array; - njs_value_t *value, *item, entry, index; + njs_value_t *this, *item, entry, index; - value = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); length = 0; - ret = njs_value_to_object(vm, value); + ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_set_undefined(&vm->retval); - if (njs_is_array(&args[0])) { - array = njs_array(&args[0]); + if (njs_is_fast_array(this)) { + array = njs_array(this); if (array->length != 0) { array->length--; @@ -881,7 +1096,7 @@ njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_OK; } - ret = njs_object_length(vm, value, &length); + ret = njs_object_length(vm, this, &length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -892,7 +1107,7 @@ njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_uint32_to_string(&index, 0); - ret = njs_value_property_delete(vm, value, &index, &vm->retval); + ret = njs_value_property_delete(vm, this, &index, &vm->retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -900,7 +1115,7 @@ njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, for (i = 1; i < length; i++) { njs_uint32_to_string(&index, i); - ret = njs_value_property_delete(vm, value, &index, &entry); + ret = njs_value_property_delete(vm, this, &index, &entry); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -908,7 +1123,7 @@ njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, if (ret == NJS_OK) { njs_uint32_to_string(&index, i - 1); - ret = njs_value_property_set(vm, value, &index, &entry); + ret = njs_value_property_set(vm, this, &index, &entry); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -919,7 +1134,7 @@ njs_array_prototype_shift(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, done: - ret = njs_object_length_set(vm, value, length); + ret = njs_object_length_set(vm, this, length); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -935,35 +1150,33 @@ njs_array_prototype_splice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, int64_t n, start, length, items, delta, delete; njs_int_t ret; njs_uint_t i; - njs_value_t *value; + njs_value_t *this; njs_array_t *array, *deleted; - value = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); - ret = njs_value_to_object(vm, value); + ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } + if (njs_slow_path(!njs_is_fast_array(this))) { + njs_internal_error(vm, "splice() is not implemented yet for objects"); + return NJS_ERROR; + } + array = NULL; start = 0; delete = 0; - if (njs_is_array(value)) { - array = njs_array(value); + if (njs_is_fast_array(this)) { + array = njs_array(this); length = array->length; if (nargs > 1) { - value = njs_argument(args, 1); - - if (njs_slow_path(!njs_is_number(value))) { - ret = njs_value_to_integer(vm, value, &start); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - } else { - start = njs_number_to_integer(njs_number(value)); + ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } if (start < 0) { @@ -980,16 +1193,9 @@ njs_array_prototype_splice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, delete = length - start; if (nargs > 2) { - value = njs_argument(args, 2); - - if (njs_slow_path(!njs_is_number(value))) { - ret = njs_value_to_integer(vm, value, &n); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - } else { - n = njs_number_to_integer(njs_number(value)); + ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &n); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } if (n < 0) { @@ -1007,6 +1213,11 @@ njs_array_prototype_splice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } + if (njs_slow_path(!deleted->object.fast_array)) { + njs_internal_error(vm, "deleted is not a fast_array"); + return NJS_ERROR; + } + if (array != NULL && (delete >= 0 || nargs > 3)) { /* Move deleted items to a new array to return. */ @@ -1055,19 +1266,35 @@ static njs_int_t njs_array_prototype_reverse(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, n, length; + njs_uint_t i, n; njs_value_t value, *this; njs_array_t *array; - this = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } - if (njs_is_array(this)) { + ret = njs_object_length(vm, this, &length); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (njs_slow_path(length == 0)) { + vm->retval = *this; + return NJS_OK; + } + + if (njs_slow_path(!njs_is_fast_array(this))) { + njs_internal_error(vm, "reverse() is not implemented yet for objects"); + return NJS_ERROR; + } + + if (njs_is_fast_array(this)) { array = njs_array(this); length = array->length; @@ -1080,10 +1307,6 @@ njs_array_prototype_reverse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } njs_set_array(&vm->retval, array); - - } else { - /* STUB */ - vm->retval = *this; } return NJS_OK; @@ -1126,15 +1349,18 @@ njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, u_char *p, *last; size_t size; ssize_t length; + uint32_t len; njs_int_t ret; njs_chb_t chain; njs_utf8_t utf8; njs_uint_t i; njs_array_t *array; - njs_value_t *value; + njs_value_t *value, *this, index, entry; njs_string_prop_t separator, string; - ret = njs_value_to_object(vm, &args[0]); + this = njs_argument(args, 0); + + ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -1155,20 +1381,47 @@ njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, (void) njs_string_prop(&separator, value); - if (!njs_is_array(&args[0]) || njs_array_len(&args[0]) == 0) { + if (njs_slow_path(!njs_is_object(this))) { vm->retval = njs_string_empty; return NJS_OK; } - array = njs_array(&args[0]); + length = 0; + array = NULL; + utf8 = njs_is_byte_string(&separator) ? NJS_STRING_BYTE : NJS_STRING_UTF8; + + if (njs_is_fast_array(this)) { + array = njs_array(this); + len = array->length; + + } else { + ret = njs_object_length(vm, this, &len); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + + if (njs_slow_path(len == 0)) { + vm->retval = njs_string_empty; + return NJS_OK; + } njs_chb_init(&chain, vm->mem_pool); - length = 0; - utf8 = njs_is_byte_string(&separator) ? NJS_STRING_BYTE : NJS_STRING_UTF8; + for (i = 0; i < len; i++) { + if (njs_fast_path(array != NULL)) { + value = &array->start[i]; - for (i = 0; i < array->length; i++) { - value = &array->start[i]; + } else { + njs_uint32_to_string(&index, i); + + ret = njs_value_property(vm, this, &index, &entry); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + value = &entry; + } if (njs_is_valid(value) && !njs_is_null_or_undefined(value)) { if (!njs_is_string(value)) { @@ -1222,9 +1475,10 @@ njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, static int -njs_object_indexes_handler(const void *first, const void *second) +njs_array_indices_handler(const void *first, const void *second) { double num1, num2; + int64_t diff; njs_str_t str1, str2; const njs_value_t *val1, *val2; @@ -1243,7 +1497,13 @@ njs_object_indexes_handler(const void *first, const void *second) return -1; } - return (int) (num1 - num2); + diff = (int64_t) (num1 - num2); + + if (diff < 0) { + return -1; + } + + return diff != 0; } njs_string_get(val1, &str1); @@ -1254,21 +1514,35 @@ njs_object_indexes_handler(const void *first, const void *second) } -static njs_array_t * -njs_object_indexes(njs_vm_t *vm, njs_value_t *object) +njs_array_t * +njs_array_keys(njs_vm_t *vm, const njs_value_t *object, njs_bool_t all) { - double idx; - uint32_t i; njs_array_t *keys; keys = njs_value_own_enumerate(vm, object, NJS_ENUM_KEYS, NJS_ENUM_STRING, - 0); + all); if (njs_slow_path(keys == NULL)) { return NULL; } qsort(keys->start, keys->length, sizeof(njs_value_t), - njs_object_indexes_handler); + njs_array_indices_handler); + + return keys; +} + + +njs_array_t * +njs_array_indices(njs_vm_t *vm, const njs_value_t *object) +{ + double idx; + uint32_t i; + njs_array_t *keys; + + keys = njs_array_keys(vm, object, 1); + if (njs_slow_path(keys == NULL)) { + return NULL; + } for (i = 0; i < keys->length; i++) { idx = njs_string_to_index(&keys->start[i]); @@ -1298,7 +1572,6 @@ njs_array_object_handler(njs_vm_t *vm, njs_array_iterator_handler_t handler, entry = (ret == NJS_OK) ? &prop : njs_value_arg(&njs_value_invalid); ret = handler(vm, args, entry, i); - if (njs_slow_path(ret != NJS_OK)) { if (ret > 0) { return NJS_DECLINED; @@ -1329,7 +1602,7 @@ njs_array_iterator(njs_vm_t *vm, njs_array_iterator_args_t *args, to = args->to; if (njs_is_array(value)) { - if (njs_slow_path(!njs_object_hash_is_empty(value))) { + if (njs_slow_path(!njs_is_fast_array(value))) { goto process_object; } @@ -1422,8 +1695,8 @@ njs_array_iterator(njs_vm_t *vm, njs_array_iterator_args_t *args, process_object: - if ((to - from) > NJS_ARRAY_LARGE_OBJECT_LENGTH) { - keys = njs_object_indexes(vm, value); + if (!njs_fast_object(to - from)) { + keys = njs_array_indices(vm, value); if (njs_slow_path(keys == NULL)) { return NJS_ERROR; } @@ -1431,12 +1704,12 @@ process_object: for (i = 0; i < keys->length; i++) { idx = njs_string_to_index(&keys->start[i]); - if (idx < from || idx > to) { + if (idx < from || idx >= to) { continue; } ret = njs_array_object_handler(vm, handler, args, &keys->start[i], - i); + idx); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -1476,7 +1749,7 @@ njs_array_reverse_iterator(njs_vm_t *vm, njs_array_iterator_args_t *args, to = args->to; if (njs_is_array(value)) { - if (njs_slow_path(!njs_object_hash_is_empty(value))) { + if (njs_slow_path(!njs_is_fast_array(value))) { goto process_object; } @@ -1576,8 +1849,8 @@ njs_array_reverse_iterator(njs_vm_t *vm, njs_array_iterator_args_t *args, process_object: - if ((from - to) > NJS_ARRAY_LARGE_OBJECT_LENGTH) { - keys = njs_object_indexes(vm, value); + if (!njs_fast_object(from - to)) { + keys = njs_array_indices(vm, value); if (njs_slow_path(keys == NULL)) { return NJS_ERROR; } @@ -1616,68 +1889,193 @@ process_object: } +njs_inline njs_int_t +njs_is_concat_spreadable(njs_vm_t *vm, njs_value_t *value) +{ + njs_int_t ret; + njs_value_t retval; + + static const njs_value_t key = + njs_wellknown_symbol(NJS_SYMBOL_IS_CONCAT_SPREADABLE); + + if (njs_slow_path(!njs_is_object(value))) { + return NJS_DECLINED; + } + + ret = njs_value_property(vm, value, njs_value_arg(&key), &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + if (njs_is_defined(&retval)) { + return njs_bool(&retval) ? NJS_OK : NJS_DECLINED; + } + + return njs_is_array(value) ? NJS_OK : NJS_DECLINED; +} + + static njs_int_t njs_array_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { + double idx; + uint32_t len; uint64_t length; njs_int_t ret; - njs_uint_t i; - njs_value_t *value; - njs_array_t *array; + njs_uint_t i, k; + njs_value_t this, index, retval, *value, *e; + njs_array_t *array, *keys; ret = njs_value_to_object(vm, &args[0]); if (njs_slow_path(ret != NJS_OK)) { return ret; } + /* TODO: ArraySpeciesCreate(). */ + + array = njs_array_alloc(vm, 0, NJS_ARRAY_SPARE); + if (njs_slow_path(array == NULL)) { + return NJS_ERROR; + } + + njs_set_array(&this, array); + + len = 0; length = 0; for (i = 0; i < nargs; i++) { - if (njs_is_array(&args[i])) { - length += njs_array_len(&args[i]); + e = njs_argument(args, i); - } else { - length++; + ret = njs_is_concat_spreadable(vm, e); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; } - } - array = njs_array_alloc(vm, length, NJS_ARRAY_SPARE); - if (njs_slow_path(array == NULL)) { - return NJS_ERROR; - } + if (ret == NJS_OK) { + ret = njs_object_length(vm, e, &len); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } - njs_set_array(&vm->retval, array); + if (njs_slow_path((length + len) >= NJS_ARRAY_MAX_LENGTH53)) { + njs_type_error(vm, "Invalid length"); + return NJS_ERROR; + } - value = array->start; + if (njs_is_fast_array(&this) && njs_is_fast_array(e) + && (length + len) <= NJS_ARRAY_LARGE_OBJECT_LENGTH) + { + for (k = 0; k < len; k++, length++) { + value = &njs_array_start(e)[k]; - for (i = 0; i < nargs; i++) { - value = njs_array_copy(value, &args[i]); - } + if (njs_slow_path(!njs_is_valid(value))) { + njs_uint32_to_string(&index, k); + ret = njs_value_property(vm, e, &index, + &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } - return NJS_OK; -} + if (ret == NJS_DECLINED) { + njs_set_invalid(&retval); + } + value = &retval; + } -static njs_value_t * -njs_array_copy(njs_value_t *dst, njs_value_t *src) -{ - njs_uint_t n; + ret = njs_array_add(vm, array, value); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } + + continue; + } + + if (njs_fast_object(len)) { + for (k = 0; k < len; k++, length++) { + njs_uint32_to_string(&index, k); + + ret = njs_value_property(vm, e, &index, &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + if (ret != NJS_OK) { + continue; + } + + njs_uint32_to_string(&index, length); + + ret = njs_value_property_set(vm, &this, &index, &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + + continue; + } + + keys = njs_array_indices(vm, e); + if (njs_slow_path(keys == NULL)) { + return NJS_ERROR; + } + + for (k = 0; k < keys->length; k++) { + ret = njs_value_property(vm, e, &keys->start[k], &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + + idx = njs_string_to_index(&keys->start[k]); + + if (ret == NJS_OK) { + njs_uint32_to_string(&index, length + idx); + + ret = njs_value_property_set(vm, &this, &index, &retval); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + } - n = 1; + length += len; - if (njs_is_array(src)) { - n = njs_array_len(src); - src = njs_array_start(src); + continue; + } + + if (njs_slow_path((length + len) >= NJS_ARRAY_MAX_LENGTH53)) { + njs_type_error(vm, "Invalid length"); + return NJS_ERROR; + } + + if (njs_is_fast_array(&this)) { + ret = njs_array_add(vm, array, e); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + } else { + njs_uint32_to_string(&index, length); + + ret = njs_value_property_set(vm, &this, &index, e); + if (njs_slow_path(ret == NJS_ERROR)) { + return ret; + } + } + + length++; } - while (n != 0) { - /* GC: njs_retain src */ - *dst++ = *src++; - n--; + ret = njs_object_length_set(vm, &this, length); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; } - return dst; + vm->retval = this; + + return NJS_OK; } @@ -1704,7 +2102,7 @@ njs_array_prototype_index_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_int_t ret; njs_array_iterator_args_t iargs; - iargs.value = njs_arg(args, nargs, 0); + iargs.value = njs_argument(args, 0); ret = njs_value_to_object(vm, iargs.value); if (njs_slow_path(ret != NJS_OK)) { @@ -1760,7 +2158,7 @@ njs_array_prototype_last_index_of(njs_vm_t *vm, njs_value_t *args, njs_int_t ret; njs_array_iterator_args_t iargs; - iargs.value = njs_arg(args, nargs, 0); + iargs.value = njs_argument(args, 0); ret = njs_value_to_object(vm, iargs.value); if (njs_slow_path(ret != NJS_OK)) { @@ -1842,7 +2240,7 @@ njs_array_prototype_includes(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_int_t ret; njs_array_iterator_args_t iargs; - iargs.value = njs_arg(args, nargs, 0); + iargs.value = njs_argument(args, 0); ret = njs_value_to_object(vm, iargs.value); if (njs_slow_path(ret != NJS_OK)) { @@ -1899,7 +2297,7 @@ njs_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_array_t *array; njs_value_t name, *this, *value; - this = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { @@ -1908,7 +2306,7 @@ njs_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, array = NULL; - if (njs_is_array(this)) { + if (njs_is_fast_array(this)) { array = njs_array(this); length = array->length; @@ -1990,7 +2388,7 @@ njs_array_validate_args(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, { njs_int_t ret; - iargs->value = njs_arg(args, nargs, 0); + iargs->value = njs_argument(args, 0); ret = njs_value_to_object(vm, iargs->value); if (njs_slow_path(ret != NJS_OK)) { @@ -2321,10 +2719,13 @@ njs_array_handler_map(njs_vm_t *vm, njs_array_iterator_args_t *args, { njs_int_t ret; njs_array_t *retval; + njs_value_t this, key; retval = args->array; - njs_set_invalid(&retval->start[n]); + if (retval->object.fast_array) { + njs_set_invalid(&retval->start[n]); + } if (njs_is_valid(entry)) { ret = njs_array_iterator_call(vm, args, entry, n); @@ -2333,7 +2734,18 @@ njs_array_handler_map(njs_vm_t *vm, njs_array_iterator_args_t *args, } if (njs_is_valid(&vm->retval)) { - retval->start[n] = vm->retval; + if (retval->object.fast_array) { + retval->start[n] = vm->retval; + + } else { + njs_set_array(&this, retval); + njs_uint32_to_string(&key, n); + + ret = njs_value_property_set(vm, &this, &key, &vm->retval); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } } } @@ -2348,16 +2760,17 @@ njs_array_prototype_map(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, uint32_t length, i; njs_int_t ret; njs_array_t *array; + njs_value_t *this; njs_array_iterator_args_t iargs; - iargs.value = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); - ret = njs_value_to_object(vm, iargs.value); + ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } - ret = njs_value_length(vm, iargs.value, &length); + ret = njs_value_length(vm, this, &length); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -2371,33 +2784,31 @@ njs_array_prototype_map(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - if (length > NJS_ARRAY_LARGE_OBJECT_LENGTH) { - for (i = 0; i < length; i++) { - njs_set_invalid(&iargs.array->start[i]); - } + if (njs_slow_path(length == 0)) { + goto done; } - if (length > 0) { - iargs.from = 0; - iargs.to = length; + iargs.from = 0; + iargs.to = length; + iargs.value = this; + iargs.function = njs_function(njs_argument(args, 1)); + iargs.argument = njs_arg(args, nargs, 2); - iargs.function = njs_function(njs_argument(args, 1)); - iargs.argument = njs_arg(args, nargs, 2); + if (iargs.array->object.fast_array) { + array = iargs.array; - ret = njs_array_iterator(vm, &iargs, njs_array_handler_map); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + for (i = 0; i < length; i++) { + njs_set_invalid(&array->start[i]); } + } - if (njs_is_array(&args[0]) && njs_object_hash_is_empty(&args[0])) { - array = iargs.array; - - for (i = njs_array_len(&args[0]); i < length; i++) { - njs_set_invalid(&array->start[i]); - } - } + ret = njs_array_iterator(vm, &iargs, njs_array_handler_map); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } +done: + njs_set_array(&vm->retval, iargs.array); return NJS_OK; @@ -2435,7 +2846,6 @@ njs_array_handler_reduce(njs_vm_t *vm, njs_array_iterator_args_t *args, njs_int_t ret; if (njs_is_valid(entry)) { - if (!njs_is_valid(args->argument)) { *(args->argument) = *entry; return NJS_OK; @@ -2496,7 +2906,7 @@ njs_array_prototype_reduce_right(njs_vm_t *vm, njs_value_t *args, njs_value_t accumulator; njs_array_iterator_args_t iargs; - iargs.value = njs_arg(args, nargs, 0); + iargs.value = njs_argument(args, 0); ret = njs_value_to_object(vm, iargs.value); if (njs_slow_path(ret != NJS_OK)) { @@ -2597,14 +3007,26 @@ static njs_int_t njs_array_prototype_sort(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { - uint32_t n, index, current; + uint32_t n, index, length, current; njs_int_t ret; njs_array_t *array; - njs_value_t retval, value, *start, arguments[3];; + njs_value_t retval, value, *this, *start, arguments[3]; njs_function_t *function; - if (!njs_is_array(&args[0]) || njs_array_len(&args[0]) == 0) { - vm->retval = args[0]; + this = njs_argument(args, 0); + + ret = njs_value_to_object(vm, this); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_value_length(vm, this, &length); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (njs_slow_path(length == 0)) { + vm->retval = *this; return NJS_OK; } @@ -2615,6 +3037,11 @@ njs_array_prototype_sort(njs_vm_t *vm, njs_value_t *args, function = (njs_function_t *) &njs_array_string_sort_function; } + if (njs_slow_path(!njs_is_fast_array(this))) { + njs_internal_error(vm, "sort() is not implemented yet for objects"); + return NJS_ERROR; + } + index = 0; current = 0; retval = njs_value_zero; @@ -2699,7 +3126,7 @@ njs_array_prototype_copy_within(njs_vm_t *vm, njs_value_t *args, njs_array_t *array; njs_value_t *this, *value, from_key, to_key, prop; - this = njs_arg(args, nargs, 0); + this = njs_argument(args, 0); ret = njs_value_to_object(vm, this); if (njs_slow_path(ret != NJS_OK)) { @@ -2752,11 +3179,7 @@ njs_array_prototype_copy_within(njs_vm_t *vm, njs_value_t *args, njs_vm_retval_set(vm, this); - if (njs_is_array(this)) { - if (njs_slow_path(!njs_object_hash_is_empty(this))) { - goto process_object; - } - + if (njs_fast_path(njs_is_fast_array(this))) { array = njs_array(this); while (count-- > 0) { @@ -2769,8 +3192,6 @@ njs_array_prototype_copy_within(njs_vm_t *vm, njs_value_t *args, return NJS_OK; } -process_object: - while (count-- > 0) { /* FIXME: largest index is 2**53-1. */ @@ -3052,5 +3473,5 @@ const njs_object_type_init_t njs_array_type_init = { .constructor = njs_native_ctor(njs_array_constructor, 1, 0), .constructor_props = &njs_array_constructor_init, .prototype_props = &njs_array_prototype_init, - .prototype_value = { .object = { .type = NJS_ARRAY } }, + .prototype_value = { .object = { .type = NJS_ARRAY, .fast_array = 1 } }, }; diff --git a/src/njs_array.h b/src/njs_array.h index 92f5f5b4..56bb8113 100644 --- a/src/njs_array.h +++ b/src/njs_array.h @@ -8,15 +8,25 @@ #define _NJS_ARRAY_H_INCLUDED_ -#define NJS_ARRAY_MAX_INDEX 0xffffffff -#define NJS_ARRAY_INVALID_INDEX NJS_ARRAY_MAX_INDEX - -#define NJS_ARRAY_SPARE 8 -#define NJS_ARRAY_MAX_LENGTH (UINT32_MAX/ sizeof(njs_value_t)) +#define NJS_ARRAY_MAX_INDEX 0xffffffff +#define NJS_ARRAY_INVALID_INDEX NJS_ARRAY_MAX_INDEX +#define NJS_ARRAY_SPARE 8 +#define NJS_ARRAY_MAX_LENGTH (UINT32_MAX/ sizeof(njs_value_t)) +#define NJS_ARRAY_MAX_LENGTH53 (0x1fffffffffffff) +#define NJS_ARRAY_FAST_OBJECT_LENGTH (128) +#define NJS_ARRAY_LARGE_OBJECT_LENGTH (32768) njs_array_t *njs_array_alloc(njs_vm_t *vm, uint64_t length, uint32_t spare); njs_int_t njs_array_add(njs_vm_t *vm, njs_array_t *array, njs_value_t *value); +njs_int_t njs_array_convert_to_slow_array(njs_vm_t *vm, njs_array_t *array); +njs_int_t njs_array_length_redefine(njs_vm_t *vm, njs_value_t *value, + uint32_t length); +njs_int_t njs_array_length_set(njs_vm_t *vm, njs_value_t *value, + njs_object_prop_t *prev, njs_value_t *setval); +njs_array_t *njs_array_keys(njs_vm_t *vm, const njs_value_t *array, + njs_bool_t all); +njs_array_t *njs_array_indices(njs_vm_t *vm, const njs_value_t *object); njs_int_t njs_array_string_add(njs_vm_t *vm, njs_array_t *array, const u_char *start, size_t size, size_t length); njs_int_t njs_array_expand(njs_vm_t *vm, njs_array_t *array, uint32_t prepend, diff --git a/src/njs_array_buffer.c b/src/njs_array_buffer.c index c04bc6f9..bf85a968 100644 --- a/src/njs_array_buffer.c +++ b/src/njs_array_buffer.c @@ -37,6 +37,8 @@ njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size) array->object.type = NJS_ARRAY_BUFFER; array->object.shared = 0; array->object.extensible = 1; + array->object.error_data = 0; + array->object.fast_array = 0; array->size = size; return array; diff --git a/src/njs_chb.h b/src/njs_chb.h index 3a8a24f7..2cbf509d 100644 --- a/src/njs_chb.h +++ b/src/njs_chb.h @@ -38,7 +38,7 @@ void njs_chb_destroy(njs_chb_t *chain); #define njs_chb_append(chain, msg, len) \ - njs_chb_append0(chain, (const char *) msg, len) + njs_chb_append0(chain, (const char *) (msg), len) #define njs_chb_append_literal(chain, literal) \ njs_chb_append0(chain, literal, njs_length(literal)) diff --git a/src/njs_clang.h b/src/njs_clang.h index 967c9ad6..a2d60e9e 100644 --- a/src/njs_clang.h +++ b/src/njs_clang.h @@ -155,6 +155,9 @@ njs_leading_zeros64(uint64_t x) #endif +#define njs_stringify(v) #v + + #if (NJS_HAVE_MEMORY_SANITIZER) #include diff --git a/src/njs_crypto.c b/src/njs_crypto.c index e6d66595..434251fd 100644 --- a/src/njs_crypto.c +++ b/src/njs_crypto.c @@ -134,6 +134,8 @@ njs_crypto_object_value_alloc(njs_vm_t *vm, njs_object_type_t type) ov->object.type = NJS_OBJECT_VALUE; ov->object.shared = 0; ov->object.extensible = 1; + ov->object.error_data = 0; + ov->object.fast_array = 0; ov->object.__proto__ = &vm->prototypes[type].object; return ov; diff --git a/src/njs_date.c b/src/njs_date.c index 41c84bd7..93244b91 100644 --- a/src/njs_date.c +++ b/src/njs_date.c @@ -385,6 +385,8 @@ done: date->object.type = NJS_DATE; date->object.shared = 0; date->object.extensible = 1; + date->object.error_data = 0; + date->object.fast_array = 0; date->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_DATE].object; date->time = time; diff --git a/src/njs_error.c b/src/njs_error.c index 433da72c..b005ac58 100644 --- a/src/njs_error.c +++ b/src/njs_error.c @@ -200,6 +200,7 @@ njs_error_alloc(njs_vm_t *vm, njs_object_type_t type, const njs_value_t *name, error->type = NJS_OBJECT; error->shared = 0; error->extensible = 1; + error->fast_array = 0; error->error_data = 1; error->__proto__ = &vm->prototypes[type].object; @@ -634,6 +635,7 @@ njs_memory_error_set(njs_vm_t *vm, njs_value_t *value) * it from ordinary internal errors. */ object->extensible = 0; + object->fast_array = 0; object->error_data = 1; njs_set_object(value, object); diff --git a/src/njs_json.c b/src/njs_json.c index 6fba9c6e..fcd6777b 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -21,6 +21,7 @@ typedef struct { njs_value_t value; uint8_t written; /* 1 bit */ + uint8_t array; /* 1 bit */ enum { NJS_JSON_OBJECT, @@ -28,6 +29,7 @@ typedef struct { } type:8; uint32_t index; + uint32_t length; njs_array_t *keys; njs_object_prop_t *prop; } njs_json_state_t; @@ -156,7 +158,7 @@ njs_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, reviver = njs_arg(args, nargs, 2); - if (njs_is_function(reviver) && njs_is_object(&value)) { + if (njs_slow_path(njs_is_function(reviver) && njs_is_object(&value))) { parse->function = njs_function(reviver); parse->depth = 0; @@ -828,18 +830,11 @@ njs_json_push_parse_state(njs_vm_t *vm, njs_json_parse_t *parse, state = &parse->states[parse->depth++]; state->value = *value; state->index = 0; - - if (njs_is_array(value)) { - state->type = NJS_JSON_ARRAY; - - } else { - state->type = NJS_JSON_OBJECT; - state->prop = NULL; - state->keys = njs_value_own_enumerate(vm, value, NJS_ENUM_KEYS, - NJS_ENUM_STRING, 0); - if (state->keys == NULL) { - return NULL; - } + state->prop = NULL; + state->keys = njs_value_own_enumerate(vm, value, NJS_ENUM_KEYS, + NJS_ENUM_STRING, 0); + if (state->keys == NULL) { + return NULL; } return state; @@ -858,111 +853,88 @@ njs_json_pop_parse_state(njs_json_parse_t *parse) } -#define njs_json_is_non_empty(_value) \ - ((njs_is_object(_value) && !njs_object_hash_is_empty(_value)) \ - || (njs_is_array(_value) && njs_array_len(_value) != 0)) - - static njs_int_t njs_json_parse_iterator(njs_vm_t *vm, njs_json_parse_t *parse, njs_value_t *object) { - njs_int_t ret; - njs_value_t *key, *value, wrapper; - njs_object_t *obj; - njs_json_state_t *state; - njs_object_prop_t *prop; - njs_lvlhsh_query_t lhq; + njs_int_t ret; + njs_value_t *key, wrapper; + njs_object_t *obj; + njs_json_state_t *state; + njs_object_prop_t *prop; + njs_property_query_t pq; obj = njs_json_wrap_value(vm, &wrapper, object); if (njs_slow_path(obj == NULL)) { - goto memory_error; + return NJS_ERROR; } state = njs_json_push_parse_state(vm, parse, &wrapper); if (njs_slow_path(state == NULL)) { - goto memory_error; + return NJS_ERROR; } - lhq.proto = &njs_object_hash_proto; - for ( ;; ) { + if (state->index < state->keys->length) { + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0); - switch (state->type) { - case NJS_JSON_OBJECT: - if (state->index < state->keys->length) { - key = &state->keys->start[state->index]; - njs_string_get(key, &lhq.key); - lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length); + key = &state->keys->start[state->index]; - ret = njs_lvlhsh_find(njs_object_hash(&state->value), &lhq); - if (njs_slow_path(ret == NJS_DECLINED)) { + ret = njs_property_query(vm, &pq, &state->value, key); + if (njs_slow_path(ret != NJS_OK)) { + if (ret == NJS_DECLINED) { state->index++; - break; + continue; } - prop = lhq.value; - - if (prop->type == NJS_WHITEOUT) { - state->index++; - break; - } + return NJS_ERROR; + } - state->prop = prop; + prop = pq.lhq.value; - if (njs_json_is_non_empty(&prop->value)) { - state = njs_json_push_parse_state(vm, parse, &prop->value); - if (state == NULL) { - goto memory_error; - } + if (prop->type == NJS_WHITEOUT) { + state->index++; + continue; + } - break; - } + state->prop = prop; - } else { - state = njs_json_pop_parse_state(parse); + if (prop->type == NJS_PROPERTY && njs_is_object(&prop->value)) { + state = njs_json_push_parse_state(vm, parse, &prop->value); if (state == NULL) { - vm->retval = parse->retval; - return NJS_OK; + return NJS_ERROR; } - } - ret = njs_json_parse_iterator_call(vm, parse, state); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + continue; } - break; - - case NJS_JSON_ARRAY: - if (state->index < njs_array_len(&state->value)) { - value = &njs_array_start(&state->value)[state->index]; - - if (njs_json_is_non_empty(value)) { - state = njs_json_push_parse_state(vm, parse, value); - if (state == NULL) { - goto memory_error; - } - - break; + if (prop->type == NJS_PROPERTY_REF + && njs_is_object(prop->value.data.u.value)) + { + state = njs_json_push_parse_state(vm, parse, + prop->value.data.u.value); + if (state == NULL) { + return NJS_ERROR; } - } else { - state = njs_json_pop_parse_state(parse); + continue; } - ret = njs_json_parse_iterator_call(vm, parse, state); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + } else { + state = njs_json_pop_parse_state(parse); + if (state == NULL) { + vm->retval = parse->retval; + return NJS_OK; } + } - break; + ret = njs_json_parse_iterator_call(vm, parse, state); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } } -memory_error: - - njs_memory_error(vm); + njs_internal_error(vm, "njs_json_parse_iterator() unexpected exit"); return NJS_ERROR; } @@ -972,15 +944,18 @@ static njs_int_t njs_json_parse_iterator_call(njs_vm_t *vm, njs_json_parse_t *parse, njs_json_state_t *state) { - njs_int_t ret; - njs_value_t arguments[3], *value; + njs_int_t ret; + njs_value_t arguments[3], *value; + njs_object_prop_t *prop; + + prop = state->prop; arguments[0] = state->value; + arguments[1] = state->keys->start[state->index++]; - switch (state->type) { - case NJS_JSON_OBJECT: - arguments[1] = state->keys->start[state->index++]; - arguments[2] = state->prop->value; + switch (prop->type) { + case NJS_PROPERTY: + arguments[2] = prop->value; ret = njs_function_apply(vm, parse->function, arguments, 3, &parse->retval); @@ -989,17 +964,16 @@ njs_json_parse_iterator_call(njs_vm_t *vm, njs_json_parse_t *parse, } if (njs_is_undefined(&parse->retval)) { - state->prop->type = NJS_WHITEOUT; + prop->type = NJS_WHITEOUT; } else { - state->prop->value = parse->retval; + prop->value = parse->retval; } break; - case NJS_JSON_ARRAY: - njs_uint32_to_string(&arguments[1], state->index); - value = &njs_array_start(&state->value)[state->index++]; + case NJS_PROPERTY_REF: + value = prop->value.data.u.value; arguments[2] = *value; ret = njs_function_apply(vm, parse->function, arguments, 3, @@ -1008,9 +982,18 @@ njs_json_parse_iterator_call(njs_vm_t *vm, njs_json_parse_t *parse, return ret; } - *value = parse->retval; + if (njs_is_undefined(&parse->retval)) { + njs_set_invalid(value); + + } else { + *value = parse->retval; + } break; + + default: + njs_internal_error(vm, "njs_json_parse_iterator_call() unexpected " + "property type:%s", njs_prop_type_string(prop->type)); } return NJS_OK; @@ -1036,6 +1019,7 @@ static njs_json_state_t * njs_json_push_stringify_state(njs_vm_t *vm, njs_json_stringify_t *stringify, const njs_value_t *value) { + njs_int_t ret; njs_json_state_t *state; if (njs_slow_path(stringify->depth >= NJS_JSON_MAX_DEPTH)) { @@ -1048,15 +1032,28 @@ njs_json_push_stringify_state(njs_vm_t *vm, njs_json_stringify_t *stringify, state->index = 0; state->written = 0; - if (njs_is_array(value)) { + if (njs_is_fast_array(value)) { state->type = NJS_JSON_ARRAY; + state->array = 1; } else { state->type = NJS_JSON_OBJECT; + state->array = njs_is_array(value); if (njs_is_array(&stringify->replacer)) { state->keys = njs_array(&stringify->replacer); + } else if (njs_is_array(value)) { + state->keys = njs_array_keys(vm, value, 0); + if (njs_slow_path(state->keys == NULL)) { + return NULL; + } + + ret = njs_object_length(vm, &state->value, &state->length); + if (njs_slow_path(ret == NJS_ERROR)) { + return NULL; + } + } else { state->keys = njs_value_own_enumerate(vm, value, NJS_ENUM_KEYS, stringify->keys_type, 0); @@ -1098,13 +1095,20 @@ njs_json_is_object(const njs_value_t *value) } -#define njs_json_stringify_indent(times) \ - if (stringify->space.length != 0) { \ - njs_chb_append(&chain,"\n", 1); \ - for (i = 0; i < (njs_int_t) (times) - 1; i++) { \ - njs_chb_append_str(&chain, &stringify->space); \ - } \ +njs_inline void +njs_json_stringify_indent(njs_json_stringify_t *stringify, njs_chb_t *chain, + njs_int_t times) +{ + njs_int_t i; + + if (stringify->space.length != 0) { + times += stringify->depth; + njs_chb_append(chain,"\n", 1); + for (i = 0; i < (times - 1); i++) { + njs_chb_append_str(chain, &stringify->space); + } } +} static njs_int_t njs_json_stringify_iterator(njs_vm_t *vm, njs_json_stringify_t *stringify, @@ -1112,11 +1116,10 @@ njs_json_stringify_iterator(njs_vm_t *vm, njs_json_stringify_t *stringify, { u_char *p; size_t size; - njs_chb_t chain; ssize_t length; - njs_int_t i; njs_int_t ret; - njs_value_t *key, *value, wrapper; + njs_chb_t chain; + njs_value_t *key, *value, index, wrapper; njs_object_t *obj; njs_json_state_t *state; @@ -1136,13 +1139,15 @@ njs_json_stringify_iterator(njs_vm_t *vm, njs_json_stringify_t *stringify, switch (state->type) { case NJS_JSON_OBJECT: if (state->index == 0) { - njs_chb_append_literal(&chain,"{"); - njs_json_stringify_indent(stringify->depth); + njs_chb_append(&chain, state->array ? "[" : "{", 1); + njs_json_stringify_indent(stringify, &chain, 0); } - if (state->index >= state->keys->length) { - njs_json_stringify_indent(stringify->depth - 1); - njs_chb_append_literal(&chain,"}"); + if ((state->array && state->index >= state->length) + || (!state->array && state->index >= state->keys->length)) + { + njs_json_stringify_indent(stringify, &chain, -1); + njs_chb_append(&chain, state->array ? "]" : "}", 1); state = njs_json_pop_stringify_state(stringify); if (state == NULL) { @@ -1153,12 +1158,24 @@ njs_json_stringify_iterator(njs_vm_t *vm, njs_json_stringify_t *stringify, } value = &stringify->retval; - key = &state->keys->start[state->index++]; + + if (state->array) { + njs_uint32_to_string(&index, state->index++); + key = &index; + + } else { + key = &state->keys->start[state->index++]; + } + ret = njs_value_property(vm, &state->value, key, value); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } + if (state->array && ret == NJS_DECLINED) { + njs_set_null(value); + } + ret = njs_json_stringify_to_json(stringify, state, key, value); if (njs_slow_path(ret != NJS_OK)) { return ret; @@ -1179,14 +1196,17 @@ njs_json_stringify_iterator(njs_vm_t *vm, njs_json_stringify_t *stringify, if (state->written) { njs_chb_append_literal(&chain,","); - njs_json_stringify_indent(stringify->depth); + njs_json_stringify_indent(stringify, &chain, 0); } state->written = 1; - njs_json_append_string(&chain, key, '\"'); - njs_chb_append_literal(&chain,":"); - if (stringify->space.length != 0) { - njs_chb_append_literal(&chain," "); + + if (!state->array) { + njs_json_append_string(&chain, key, '\"'); + njs_chb_append_literal(&chain,":"); + if (stringify->space.length != 0) { + njs_chb_append_literal(&chain," "); + } } if (njs_json_is_object(value)) { @@ -1205,11 +1225,11 @@ njs_json_stringify_iterator(njs_vm_t *vm, njs_json_stringify_t *stringify, case NJS_JSON_ARRAY: if (state->index == 0) { njs_chb_append_literal(&chain,"["); - njs_json_stringify_indent(stringify->depth); + njs_json_stringify_indent(stringify, &chain, 0); } if (state->index >= njs_array_len(&state->value)) { - njs_json_stringify_indent(stringify->depth - 1); + njs_json_stringify_indent(stringify, &chain, -1); njs_chb_append_literal(&chain,"]"); state = njs_json_pop_stringify_state(stringify); @@ -1222,7 +1242,7 @@ njs_json_stringify_iterator(njs_vm_t *vm, njs_json_stringify_t *stringify, if (state->written) { njs_chb_append_literal(&chain,","); - njs_json_stringify_indent(stringify->depth); + njs_json_stringify_indent(stringify, &chain, 0); } stringify->retval = njs_array_start(&state->value)[state->index++]; @@ -1977,6 +1997,41 @@ njs_dump_visited(njs_arr_t *list, const njs_value_t *value) } +njs_inline njs_bool_t +njs_dump_empty(njs_json_stringify_t *stringify, njs_json_state_t *state, + njs_chb_t *chain, double key, double prev, njs_bool_t sep_position) +{ + int64_t diff; + + if (!state->array || isnan(prev)) { + return 0; + } + + if (isnan(key)) { + key = state->length; + } + + diff = key - prev; + + if (diff > 1) { + if (sep_position == 0 && state->keys->length) { + njs_chb_append_literal(chain, ","); + njs_json_stringify_indent(stringify, chain, 1); + } + + njs_chb_sprintf(chain, 64, "<%L empty items>", diff - 1); + state->written = 1; + + if (sep_position == 1 && state->keys->length) { + njs_chb_append_literal(chain, ","); + njs_json_stringify_indent(stringify, chain, 1); + } + } + + return 1; +} + + static const njs_value_t string_get = njs_string("[Getter]"); static const njs_value_t string_set = njs_string("[Setter]"); static const njs_value_t string_get_set = njs_long_string("[Getter/Setter]"); @@ -1986,7 +2041,7 @@ njs_int_t njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, njs_uint_t console, njs_uint_t indent) { - njs_int_t i, ret; + njs_int_t ret; njs_chb_t chain; njs_str_t str; njs_arr_t visited; @@ -2035,6 +2090,7 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, goto memory_error; } + key = NULL; (void) njs_dump_visit(&visited, value); for ( ;; ) { @@ -2052,13 +2108,17 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, njs_chb_append_literal(&chain, " "); } - njs_chb_append_literal(&chain, "{"); - njs_json_stringify_indent(stringify->depth + 1); + njs_chb_append(&chain, state->array ? "[" : "{", 1); + njs_json_stringify_indent(stringify, &chain, 1); + } if (state->index >= state->keys->length) { - njs_json_stringify_indent(stringify->depth); - njs_chb_append_literal(&chain, "}"); + njs_dump_empty(stringify, state, &chain, state->length, + (state->index > 0) ? njs_key_to_index(key) : -1, 0); + + njs_json_stringify_indent(stringify, &chain, 0); + njs_chb_append(&chain, state->array ? "]" : "}", 1); state = njs_json_pop_stringify_state(stringify); if (state == NULL) { @@ -2087,6 +2147,25 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, break; } + if (state->written) { + njs_chb_append_literal(&chain, ","); + njs_json_stringify_indent(stringify, &chain, 1); + } + + state->written = 1; + + njs_dump_empty(stringify, state, &chain, njs_key_to_index(key), + (state->index > 1) ? njs_key_to_index(&key[-1]) : -1, 1); + + if (!state->array || isnan(njs_key_to_index(key))) { + njs_key_string_get(vm, key, &pq.lhq.key); + njs_chb_append(&chain, pq.lhq.key.start, pq.lhq.key.length); + njs_chb_append_literal(&chain, ":"); + if (stringify->space.length != 0) { + njs_chb_append_literal(&chain, " "); + } + } + val = &prop->value; if (prop->type == NJS_PROPERTY_HANDLER) { @@ -2116,19 +2195,6 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, } } - if (state->written) { - njs_chb_append_literal(&chain, ","); - njs_json_stringify_indent(stringify->depth + 1); - } - - state->written = 1; - njs_key_string_get(vm, key, &pq.lhq.key); - njs_chb_append(&chain, pq.lhq.key.start, pq.lhq.key.length); - njs_chb_append_literal(&chain, ":"); - if (stringify->space.length != 0) { - njs_chb_append_literal(&chain, " "); - } - if (njs_dump_is_recursive(val)) { if (njs_slow_path(njs_dump_visited(&visited, val))) { njs_chb_append_literal(&chain, "[Circular]"); @@ -2173,11 +2239,11 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, } njs_chb_append_literal(&chain, "["); - njs_json_stringify_indent(stringify->depth + 1); + njs_json_stringify_indent(stringify, &chain, 1); } if (state->index >= njs_array_len(&state->value)) { - njs_json_stringify_indent(stringify->depth); + njs_json_stringify_indent(stringify, &chain, 0); njs_chb_append_literal(&chain, "]"); state = njs_json_pop_stringify_state(stringify); @@ -2190,7 +2256,7 @@ njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, if (state->written) { njs_chb_append_literal(&chain, ","); - njs_json_stringify_indent(stringify->depth + 1); + njs_json_stringify_indent(stringify, &chain, 1); } val = &njs_array_start(&state->value)[state->index++]; diff --git a/src/njs_module.c b/src/njs_module.c index b0aa0482..f527dfec 100644 --- a/src/njs_module.c +++ b/src/njs_module.c @@ -540,7 +540,9 @@ njs_module_require(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, *object = module->object; object->__proto__ = &vm->prototypes[NJS_OBJ_TYPE_OBJECT].object; object->shared = 0; + object->extensible = 0; object->error_data = 0; + object->fast_array = 0; njs_set_object(&vm->retval, object); diff --git a/src/njs_object.c b/src/njs_object.c index 34a8cc28..67d2ba65 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -110,6 +110,7 @@ njs_object_value_alloc(njs_vm_t *vm, const njs_value_t *value, njs_uint_t type) ov->object.type = njs_object_value_type(type); ov->object.shared = 0; ov->object.extensible = 1; + ov->object.error_data = 0; ov->object.fast_array = 0; index = njs_primitive_prototype_index(type); @@ -588,6 +589,11 @@ njs_object_enumerate(njs_vm_t *vm, const njs_object_t *object, return NULL; } + if (njs_slow_path(!items->object.fast_array)) { + njs_internal_error(vm, "njs_object_enumerate() too many keys"); + return NULL; + } + ret = njs_object_enumerate_value(vm, object, items, kind, type, all); if (njs_slow_path(ret != NJS_OK)) { return NULL; @@ -614,6 +620,11 @@ njs_object_own_enumerate(njs_vm_t *vm, const njs_object_t *object, return NULL; } + if (njs_slow_path(!items->object.fast_array)) { + njs_internal_error(vm, "njs_object_own_enumerate() too many keys"); + return NULL; + } + ret = njs_object_own_enumerate_value(vm, object, object, items, kind, type, all); if (njs_slow_path(ret != NJS_OK)) { @@ -635,9 +646,11 @@ njs_object_enumerate_array_length(const njs_object_t *object) length = 0; array = (njs_array_t *) object; - for (i = 0; i < array->length; i++) { - if (njs_is_valid(&array->start[i])) { - length++; + if (object->fast_array) { + for (i = 0; i < array->length; i++) { + if (njs_is_valid(&array->start[i])) { + length++; + } } } @@ -761,6 +774,10 @@ njs_object_enumerate_array(njs_vm_t *vm, const njs_array_t *array, njs_value_t *item; njs_array_t *entry; + if (!array->object.fast_array) { + return NJS_OK; + } + item = items->start; switch (kind) { diff --git a/src/njs_object.h b/src/njs_object.h index 1d704a8c..73333c98 100644 --- a/src/njs_object.h +++ b/src/njs_object.h @@ -74,6 +74,8 @@ njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name, const njs_value_t *value, uint8_t attributes); njs_int_t njs_object_property(njs_vm_t *vm, const njs_value_t *value, njs_lvlhsh_query_t *lhq, njs_value_t *retval); +njs_object_prop_t *njs_object_property_add(njs_vm_t *vm, njs_value_t *object, + njs_value_t *key, njs_bool_t replace); njs_int_t njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, njs_value_t *name, njs_value_t *value, njs_object_prop_define_t type); njs_int_t njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest, diff --git a/src/njs_object_prop.c b/src/njs_object_prop.c index c486ece1..45d6e2db 100644 --- a/src/njs_object_prop.c +++ b/src/njs_object_prop.c @@ -95,20 +95,50 @@ found: } +njs_object_prop_t * +njs_object_property_add(njs_vm_t *vm, njs_value_t *object, njs_value_t *key, + njs_bool_t replace) +{ + njs_int_t ret; + njs_object_prop_t *prop; + njs_lvlhsh_query_t lhq; + + prop = njs_object_prop_alloc(vm, key, &njs_value_invalid, 1); + if (njs_slow_path(prop == NULL)) { + return NULL; + } + + lhq.proto = &njs_object_hash_proto; + njs_key_string_get(vm, key, &lhq.key); + lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length); + lhq.value = prop; + lhq.replace = replace; + lhq.pool = vm->mem_pool; + + ret = njs_lvlhsh_insert(njs_object_hash(object), &lhq); + if (njs_slow_path(ret != NJS_OK)) { + njs_internal_error(vm, "lvlhsh insert failed"); + return NULL; + } + + return prop; +} + + /* * ES5.1, 8.12.9: [[DefineOwnProperty]] - * Limited support of special descriptors like length and array index - * (values can be set, but without property flags support). */ njs_int_t njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, njs_value_t *name, njs_value_t *value, njs_object_prop_define_t type) { + uint32_t length; njs_int_t ret; + njs_array_t *array; njs_object_prop_t *prop, *prev; njs_property_query_t pq; - njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 1); + static const njs_str_t length_key = njs_str("length"); if (njs_slow_path(!njs_is_key(name))) { ret = njs_value_to_key(vm, name, name); @@ -117,8 +147,11 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, } } - ret = njs_property_query(vm, &pq, object, name); +again: + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 1); + + ret = njs_property_query(vm, &pq, object, name); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } @@ -236,6 +269,27 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, break; case NJS_PROPERTY_REF: + if (njs_is_accessor_descriptor(prop) + || prop->configurable == NJS_ATTRIBUTE_FALSE + || prop->enumerable == NJS_ATTRIBUTE_FALSE + || prop->writable == NJS_ATTRIBUTE_FALSE) + { + array = njs_array(object); + length = array->length; + + ret = njs_array_convert_to_slow_array(vm, array); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_array_length_redefine(vm, object, length); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + goto again; + } + if (njs_is_valid(&prop->value)) { *prev->value.data.u.value = prop->value; } else { @@ -285,20 +339,6 @@ njs_object_prop_define(njs_vm_t *vm, njs_value_t *object, { goto exception; } - - if (pq.shared) { - /* - * shared non-configurable NJS_PROPERTY_HANDLER are not copied - * by njs_object_property_query(). - */ - - ret = njs_prop_private_copy(vm, &pq); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - prev = pq.lhq.value; - } } if (njs_is_generic_descriptor(prop)) { @@ -391,6 +431,17 @@ done: } } else { + if (njs_slow_path(pq.lhq.key_hash == NJS_LENGTH_HASH)) { + njs_key_string_get(vm, &pq.key, &pq.lhq.key); + + if (njs_strstr_eq(&pq.lhq.key, &length_key)) { + ret = njs_array_length_set(vm, object, prev, &prop->value); + if (ret != NJS_DECLINED) { + return ret; + } + } + } + prev->value = prop->value; } } diff --git a/src/njs_promise.c b/src/njs_promise.c index a9a2a718..6eb4b02b 100644 --- a/src/njs_promise.c +++ b/src/njs_promise.c @@ -87,6 +87,8 @@ njs_promise_alloc(njs_vm_t *vm) promise->object.type = NJS_PROMISE; promise->object.shared = 0; promise->object.extensible = 1; + promise->object.error_data = 0; + promise->object.fast_array = 0; promise->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_PROMISE].object; data = (njs_promise_data_t *) ((uint8_t *) promise + sizeof(njs_promise_t)); @@ -217,7 +219,6 @@ njs_promise_create_function(njs_vm_t *vm) function->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; function->object.shared_hash = vm->shared->arrow_instance_hash; function->object.type = NJS_FUNCTION; - function->object.shared = 0; function->object.extensible = 1; function->args_offset = 1; function->native = 1; diff --git a/src/njs_regexp.c b/src/njs_regexp.c index acec4422..c16588a1 100644 --- a/src/njs_regexp.c +++ b/src/njs_regexp.c @@ -713,6 +713,8 @@ njs_regexp_alloc(njs_vm_t *vm, njs_regexp_pattern_t *pattern) regexp->object.type = NJS_REGEXP; regexp->object.shared = 0; regexp->object.extensible = 1; + regexp->object.fast_array = 0; + regexp->object.error_data = 0; njs_set_number(®exp->last_index, 0); regexp->pattern = pattern; njs_string_short_set(®exp->string, 0, 0); diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c index 8751c62c..0cd89b35 100644 --- a/src/njs_typed_array.c +++ b/src/njs_typed_array.c @@ -177,7 +177,6 @@ njs_typed_array_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_lvlhsh_init(&array->object.shared_hash); array->object.__proto__ = &vm->prototypes[type].object; array->object.type = NJS_TYPED_ARRAY; - array->object.shared = 0; array->object.extensible = 1; array->object.fast_array = 1; diff --git a/src/njs_value.c b/src/njs_value.c index eb95b822..926769ec 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -295,7 +295,7 @@ njs_value_length(njs_vm_t *vm, njs_value_t *value, uint32_t *length) } else if (njs_is_primitive(value)) { *length = 0; - } else if (njs_is_array(value)) { + } else if (njs_is_fast_array(value)) { *length = njs_array_len(value); } else { @@ -653,10 +653,14 @@ njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, if (!njs_is_null_or_undefined_or_boolean(key)) { switch (proto->type) { case NJS_ARRAY: + array = (njs_array_t *) proto; num = njs_key_to_index(key); + if (njs_fast_path(njs_key_is_integer_index(num, key))) { - array = (njs_array_t *) proto; - return njs_array_property_query(vm, pq, array, num); + ret = njs_array_property_query(vm, pq, array, num); + if (njs_fast_path(ret != NJS_DECLINED)) { + return (ret == NJS_DONE) ? NJS_DECLINED : ret; + } } break; @@ -679,7 +683,6 @@ njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, if (njs_fast_path(njs_key_is_integer_index(num, key))) { ov = (njs_object_value_t *) proto; ret = njs_string_property_query(vm, pq, &ov->value, num); - if (njs_fast_path(ret != NJS_DECLINED)) { return ret; } @@ -709,14 +712,6 @@ njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, if (ret == NJS_OK) { prop = pq->lhq.value; - if (!prop->configurable - && prop->type == NJS_PROPERTY_HANDLER) - { - /* Postpone making a mutable NJS_PROPERTY_HANDLER copy. */ - pq->shared = 1; - return ret; - } - return njs_prop_private_copy(vm, pq); } } @@ -738,38 +733,88 @@ static njs_int_t njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_array_t *array, uint32_t index) { - uint32_t size; + uint32_t size, length; njs_int_t ret; - njs_value_t *value; + njs_value_t *setval, value; njs_object_prop_t *prop; - if (index >= array->length) { - if (pq->query != NJS_PROPERTY_QUERY_SET) { + if (pq->query == NJS_PROPERTY_QUERY_SET) { + if (!array->object.extensible) { return NJS_DECLINED; } - if (!array->object.extensible) { - return NJS_DECLINED; + if (njs_fast_path(array->object.fast_array)) { + if (njs_fast_path(index < NJS_ARRAY_LARGE_OBJECT_LENGTH)) { + if (index >= array->length) { + size = index - array->length + 1; + + ret = njs_array_expand(vm, array, 0, size); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + setval = &array->start[array->length]; + + while (size != 0) { + njs_set_invalid(setval); + setval++; + size--; + } + + array->length = index + 1; + } + + goto prop; + } + + ret = njs_array_convert_to_slow_array(vm, array); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } } - size = index - array->length; + njs_set_array(&value, array); - ret = njs_array_expand(vm, array, 0, size + 1); + ret = njs_object_length(vm, &value, &length); if (njs_slow_path(ret != NJS_OK)) { return ret; } - value = &array->start[array->length]; + if ((index + 1) > length) { + ret = njs_array_length_redefine(vm, &value, index + 1); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } - while (size != 0) { - njs_set_invalid(value); - value++; - size--; + ret = njs_lvlhsh_find(&array->object.hash, &pq->lhq); + if (ret == NJS_OK) { + prop = pq->lhq.value; + + if (prop->type != NJS_WHITEOUT) { + return NJS_OK; + } + + if (pq->own) { + pq->own_whiteout = prop; + } + + return NJS_DECLINED; } - array->length = index + 1; + return NJS_DONE; } + if (njs_slow_path(!array->object.fast_array)) { + return NJS_DECLINED; + } + + if (index >= array->length) { + return NJS_DECLINED; + } + +prop: + prop = &pq->scratch; if (pq->query == NJS_PROPERTY_QUERY_GET) { @@ -1056,20 +1101,18 @@ njs_value_property(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, goto slow_path; } - /* NJS_ARRAY */ + /* njs_is_fast_array() */ array = njs_array(value); - if (njs_slow_path(index >= array->length)) { + if (njs_slow_path(index >= array->length + || !njs_is_valid(&array->start[index]))) + { goto slow_path; } *retval = array->start[index]; - if (njs_slow_path(!njs_is_valid(retval))) { - njs_set_undefined(retval); - } - return NJS_OK; } @@ -1151,6 +1194,8 @@ njs_value_property_set(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, njs_typed_array_t *tarray; njs_property_query_t pq; + static const njs_str_t length_key = njs_str("length"); + if (njs_fast_path(njs_is_number(key))) { num = njs_number(key); @@ -1202,7 +1247,6 @@ slow_path: return NJS_ERROR; } - njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0); ret = njs_property_query(vm, &pq, value, key); @@ -1245,6 +1289,17 @@ slow_path: if (pq.own) { switch (prop->type) { case NJS_PROPERTY: + if (njs_slow_path(pq.lhq.key_hash == NJS_LENGTH_HASH)) { + njs_key_string_get(vm, &pq.key, &pq.lhq.key); + + if (njs_strstr_eq(&pq.lhq.key, &length_key)) { + ret = njs_array_length_set(vm, value, prop, setval); + if (ret != NJS_DECLINED) { + return ret; + } + } + } + goto found; case NJS_PROPERTY_REF: diff --git a/src/njs_value.h b/src/njs_value.h index e49d1621..2256f862 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -647,6 +647,10 @@ typedef struct { ((value)->type == NJS_ARRAY) +#define njs_is_fast_array(value) \ + (njs_is_array(value) && njs_array(value)->object.fast_array) + + #define njs_is_array_buffer(value) \ ((value)->type == NJS_ARRAY_BUFFER) @@ -767,6 +771,10 @@ typedef struct { *(value) = njs_value_undefined +#define njs_set_null(value) \ + *(value) = njs_value_null + + #define njs_set_true(value) \ *(value) = njs_value_true diff --git a/src/test/njs_benchmark.c b/src/test/njs_benchmark.c index b5e1f23c..3cb05bfc 100644 --- a/src/test/njs_benchmark.c +++ b/src/test/njs_benchmark.c @@ -212,13 +212,13 @@ static njs_benchmark_test_t njs_test[] = njs_str("3524578"), 1 }, - { "array 10M", - njs_str("var arr = new Array(10000000);" + { "array 1M", + njs_str("var arr = new Array(1000000);" "var count = 0, length = arr.length;" "arr.fill(2);" "for (var i = 0; i < length; i++) { count += arr[i]; }" "count"), - njs_str("20000000"), + njs_str("2000000"), 1 }, { "typed array 10M", diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index bd9727dd..1f42cf8a 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -1,4 +1,3 @@ - /* * Copyright (C) Igor Sysoev * Copyright (C) NGINX, Inc. @@ -9,6 +8,9 @@ #define NJS_HAVE_LARGE_STACK (!NJS_HAVE_ADDRESS_SANITIZER && !NJS_HAVE_MEMORY_SANITIZER) +#define _NJS_ARRAY(sz) "Array(" njs_stringify(sz) ")" +#define NJS_LARGE_ARRAY _NJS_ARRAY(NJS_ARRAY_LARGE_OBJECT_LENGTH + 1) +#define NJS_LARGE_ARRAY_LEN "32769" typedef struct { @@ -3800,7 +3802,7 @@ static njs_unit_test_t njs_test[] = "true:true,Infinity:Infinity,-Infinity:-Infinity,NaN:NaN,") }, { njs_str("--[][3e9]"), - njs_str("MemoryError") }, + njs_str("NaN") }, { njs_str("[].length"), njs_str("0") }, @@ -3839,13 +3841,16 @@ static njs_unit_test_t njs_test[] = njs_str("RangeError: Invalid array length") }, { njs_str("[].length = 2**32 - 1"), - njs_str("MemoryError") }, + njs_str("4294967295") }, + + { njs_str("var a = []; a.length = 2**32 - 1; a.length"), + njs_str("4294967295") }, { njs_str("[].length = 3e9"), - njs_str("MemoryError") }, + njs_str("3000000000") }, - { njs_str("Object.defineProperty([], 'length',{value: 2**32 - 1})"), - njs_str("MemoryError") }, + { njs_str("var a = []; Object.defineProperty(a, 'length',{value: 2**32 - 1}); a.length"), + njs_str("4294967295") }, { njs_str("[].length = 2**32"), njs_str("RangeError: Invalid array length") }, @@ -3906,6 +3911,14 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = [1,2,3]; a.join(':')"), njs_str("1:2:3") }, + { njs_str("[" + " []," + " ['β', 'γ']," + "]" + ".map(v=>{v.length = 2**14+1; var out = v.join('α'); return [out[0], out[out.length - 1],out.length]})" + ".map(v=>njs.dump(v))"), + njs_str("['α','α',16384],['β','α',16386]") }, + { njs_str("[" " 'α'.repeat(33)," " String.bytesFrom(Array(16).fill(0x9d))," @@ -3949,8 +3962,45 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = []; a[5] = 5; a"), njs_str(",,,,,5") }, - { njs_str("var a = []; a.concat([])"), - njs_str("") }, + { njs_str("var a = []; a.concat([],[1],[])"), + njs_str("1") }, + + { njs_str("var a = []; a[Symbol.isConcatSpreadable] = undefined; [].concat(a).length"), + njs_str("0") }, + + { njs_str("var a = []; Object.defineProperty(a, Symbol.isConcatSpreadable, {get:()=>{throw 'Oops'}}); " + "[].concat(a)"), + njs_str("Oops") }, + + { njs_str("var a = [].concat([1,2,3], {length:3, 1:4, 2:5, [Symbol.isConcatSpreadable]:1});" + "njs.dump([a, a.length])"), + njs_str("[[1,2,3,,4,5],6]") }, + + { njs_str("njs.dump([].concat([1,2,3], {length:3, 1:4, 2:5}))"), + njs_str("[1,2,3,{length:3,1:4,2:5}]") }, + + { njs_str("Array.prototype[1] = 1; var x = [0]; x.length = 2; " + "x.concat().hasOwnProperty('1') === true"), + njs_str("true") }, + + { njs_str("var a = " NJS_LARGE_ARRAY ";" + "a[32] = 1; a = a.concat([1]);" + "njs.dump([a[0], a[32],a.length])"), + njs_str("[undefined,1,32770]") }, + + { njs_str("var a = " NJS_LARGE_ARRAY ";" + "a[32] = 1; a = [1].concat(a);" + "njs.dump([a[0], a[33],a.length])"), + njs_str("[1,1,32770]") }, + + { njs_str("var re = /abc/; re[Symbol.isConcatSpreadable] = true;" + "re[0] = 1, re[1] = 2, re[2] = 3, re.length = 3;" + "[].concat(re)"), + njs_str("1,2,3") }, + + { njs_str("var s = new String('yuck\\uD83D\\uDCA9'); s[Symbol.isConcatSpreadable] = true;" + "[].concat(s)"), + njs_str("y,u,c,k,💩") }, { njs_str("var s = { toString: function() { return 'S' } };" "var v = { toString: 8, valueOf: function() { return 'V' } };" @@ -3976,6 +4026,10 @@ static njs_unit_test_t njs_test[] = { njs_str("Array.prototype.toString.call('abc')"), njs_str("[object String]") }, + { njs_str("var a = " NJS_LARGE_ARRAY "; var s = a.toString();" + "[s.length]"), + njs_str("32768") }, + /* Empty array elements. */ { njs_str("[,,]"), @@ -4163,6 +4217,10 @@ static njs_unit_test_t njs_test[] = { njs_str("Array.prototype.slice.call({length:-1})"), njs_str("") }, + { njs_str("Array.prototype[1] = 1; var x = [0]; x.length = 2;" + "var a = x.slice(); a.hasOwnProperty('1')"), + njs_str("true") }, + { njs_str("Array.prototype.slice.call('αβZγ')"), njs_str("α,β,Z,γ") }, @@ -4225,6 +4283,10 @@ static njs_unit_test_t njs_test[] = { njs_str("var o = { length: 3 }; Array.prototype.pop.call(o); o.length"), njs_str("2") }, + { njs_str("var a = " NJS_LARGE_ARRAY "; a[a.length - 1] = 'z'; a[a.length -2] = 'y';" + "Array.prototype.pop.call(a); [a.length, a[a.length - 1]]"), + njs_str("32768,y") }, + { njs_str("Array.prototype.shift()"), njs_str("undefined") }, @@ -4338,6 +4400,11 @@ static njs_unit_test_t njs_test[] = { njs_str("var o = { length: 3 }; Array.prototype.shift.call(o); o.length"), njs_str("2") }, + { njs_str("var a = [1,2,3];" + "Object.defineProperty(a, '1', {enumerable:false});" + "a.shift(); a"), + njs_str("2,3") }, + { njs_str("var a = []; a.splice()"), njs_str("") }, @@ -4381,6 +4448,9 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = [1,2,3,4]; a.reverse()"), njs_str("4,3,2,1") }, + { njs_str("var o = {1:true, 2:'', length:-2}; Array.prototype.reverse.call(o) === o"), + njs_str("true") }, + { njs_str("var a = [1,2,3,4]; a.indexOf()"), njs_str("-1") }, @@ -4419,6 +4489,25 @@ static njs_unit_test_t njs_test[] = "Array.prototype.indexOf.call(o); i"), njs_str("1") }, +#if (!NJS_HAVE_MEMORY_SANITIZER) /* False-positive in MSAN? */ + { njs_str("var a = new Array(); a[100] =1; a[99999] = ''; a[10] = new Object(); " + "a[5555] = 5.5; a[123456] = 'str'; a[5] = 1E+309; " + "[1, '', 'str', 1E+309, 5.5, true, 5, 'str1', null, new Object()].map(v=>a.indexOf(v))"), + njs_str("100,99999,123456,5,5555,-1,-1,-1,-1,-1") }, +#endif + + { njs_str("Array.prototype.indexOf.call({199:true, 200:'200.59', length:200}, '200.59')"), + njs_str("-1") }, + + { njs_str("Array.prototype.indexOf.call({199:true, 200:'200.59', length:201}, '200.59')"), + njs_str("200") }, + + { njs_str("Array.prototype.indexOf.call({1:true, 2:'200.59', length:2}, '200.59')"), + njs_str("-1") }, + + { njs_str("Array.prototype.indexOf.call({1:true, 2:'200.59', length:3}, '200.59')"), + njs_str("2") }, + { njs_str("[].lastIndexOf(1, -1)"), njs_str("-1") }, @@ -4494,6 +4583,13 @@ static njs_unit_test_t njs_test[] = "Array.prototype.lastIndexOf.call(o, 'd')"), njs_str("3") }, +#if (!NJS_HAVE_MEMORY_SANITIZER) /* False-positive in MSAN? */ + { njs_str("var a = new Array(); a[100] =1; a[99999] = ''; a[10] = new Object(); " + "a[5555] = 5.5; a[123456] = 'str'; a[5] = 1E+309; " + "[1,'', 'str', 1E+309, 5.5, true, 5, 'str1', null, new Object()].map(v=>a.lastIndexOf(v))"), + njs_str("100,99999,123456,5,5555,-1,-1,-1,-1,-1") }, +#endif + { njs_str("var obj = {'10000000': 'x', '10000001': 'y', '10000002': 'z'}; var a = [];" "obj.length = 90000000;" "Array.prototype.lastIndexOf.call(obj, 'y');"), @@ -4503,6 +4599,9 @@ static njs_unit_test_t njs_test[] = "Array.prototype.lastIndexOf.call(o); i"), njs_str("1") }, + { njs_str("Array.prototype.lastIndexOf.call({199:true, 200:'200.59', length:200}, '200.59')"), + njs_str("-1") }, + { njs_str("[''].lastIndexOf.call('00000000000000000000000000000а00')"), njs_str("-1") }, @@ -4683,6 +4782,16 @@ static njs_unit_test_t njs_test[] = "catch (e) {i += '; ' + e} i"), njs_str("1; TypeError: unexpected iterator arguments") }, + { njs_str("var obj = new Date(); obj.length = 1; obj[0] = 1;" + "Array.prototype.every.call(obj, (val,idx,obj)=>!(obj instanceof Date))"), + njs_str("false") }, + + { njs_str("var vis = false; var a = []; " + "Object.defineProperty(a, '0', {get:()=>{vis = true; return 11;}, configurable:true});" + "Object.defineProperty(a, '1', {get:()=>{if (vis) {return 9;} else {return 11}}, configurable:true});" + "a.every(val=>val > 10)"), + njs_str("false") }, + { njs_str("var o = {0: 'x', 1: 'y', 2: 'z'};" "Object.defineProperty(o, 'length', {get: () => 4});" "Object.defineProperty(o, '3', {get: () => 'a'});" @@ -5633,6 +5742,13 @@ static njs_unit_test_t njs_test[] = "catch (e) {i += '; ' + e} i"), njs_str("1; TypeError: unexpected iterator arguments") }, + { njs_str("var a = [1,2,3]; Object.defineProperty(a, '1', {enumerable:false});" + "a.map(v=>v)"), + njs_str("1,2,3") }, + + { njs_str("Array.prototype.map.call({0:9, length:2**16}, val=>val<10).length"), + njs_str("65536") }, + { 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") }, @@ -5756,6 +5872,12 @@ static njs_unit_test_t njs_test[] = "var a = [6,o,4,3,2,1]; a.sort()"), njs_str("1,2,3,4,5,6") }, + { njs_str("var a = {0:3,1:2,2:1}; Array.prototype.sort.call(a) === a"), + njs_str("true") }, + + { njs_str("var a = {0:3,1:2,2:1,length:0}; Array.prototype.sort.call(a) === a"), + njs_str("true") }, + { njs_str("var a = [1,2,3,4,5,6];" "a.sort(function(x, y) { return x - y })"), njs_str("1,2,3,4,5,6") }, @@ -10379,18 +10501,46 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = Array(Infinity)"), njs_str("RangeError: Invalid array length") }, - { njs_str("var a = Array(1111111111)"), - njs_str("MemoryError") }, + { njs_str(NJS_LARGE_ARRAY ".length"), + njs_str(NJS_LARGE_ARRAY_LEN) }, + + { njs_str("var a = Array(1111111111); a[1111111112] = 1; a.length"), + njs_str("1111111113") }, + + { njs_str("var a = Array(1111111111); a[1111111112] = 1; a[1111111112]"), + njs_str("1") }, + + { njs_str("var a = Array(1111111111); a[1] = 2; a[1111111112] = 1; Object.keys(a)"), + njs_str("1,1111111112") }, + + { njs_str("var a = Array(1111111111); a[1] = 2; a[1111111112] = 1; Object.entries(a)"), + njs_str("1,2,1111111112,1") }, { njs_str("var x = Array(2**32)"), njs_str("RangeError: Invalid array length") }, { njs_str("var x = Array(2**28)"), - njs_str("MemoryError") }, + njs_str("undefined") }, + + { njs_str("Array.prototype[2] = -1; var x = [0,1,3]; x.length = 2; x[2]"), + njs_str("-1") }, + { njs_str("Array.prototype[1] = 1; var x = [0]; x.length = 2; x[1]"), + njs_str("1") }, + + { njs_str("var a = new Array(4); Object.defineProperty(a, '0', {enumerable:false}); a[2] = 's';" + "Array.prototype.length"), + njs_str("0") }, + + { njs_str("var x = [0, 1, 2]; x[4294967294] = 4294967294; x.length = 2;" + "njs.dump([x,x.length,Array.prototype,Array.prototype.length])"), + njs_str("[[0,1],2,[],0]") }, + +#if 0 /* TODO: length 2**53-1. */ { njs_str("var x = Array(2**20), y = Array(2**12).fill(x);" "Array.prototype.concat.apply(y[0], y.slice(1))"), njs_str("RangeError: Invalid array length") }, +#endif { njs_str("var a = new Array(3); a"), njs_str(",,") }, @@ -12174,6 +12324,65 @@ static njs_unit_test_t njs_test[] = "JSON.stringify(Object.getOwnPropertyDescriptor(o, 'a')).set"), njs_str("undefined") }, + { njs_str("var a = []; Object.defineProperty(a, 4294967294, {value:100}); " + "[a.hasOwnProperty('4294967294'), a.length, a[4294967294]]"), + njs_str("true,4294967295,100") }, + + { njs_str("var a = []; Object.defineProperty(a, 'length', {value:4294967294}); " + "a.length"), + njs_str("4294967294") }, + + { njs_str("var a = []; Object.defineProperty(a, 'length', {value:4294967295}); " + "a.length"), + njs_str("4294967295") }, + + { njs_str("njs.dump(Object.defineProperty([], 'length', {value:4294967295}))"), + njs_str("[<4294967295 empty items>]") }, + + { njs_str("var a = [1]; Object.defineProperty(a, '1', {get:()=>2}); " + "[a[0], a[1], a.length]"), + njs_str("1,2,2") }, + + { njs_str("var a = [1,2,3]; Object.defineProperty(a, '1', {configurable:false}); " + "[a[0], a[1], a.length]"), + njs_str("1,2,3") }, + + { njs_str("var a = [1,2,3]; Object.defineProperty(a, '1', {configurable:false}); " + "delete a[1]"), + njs_str("TypeError: Cannot delete property \"1\" of array") }, + + { njs_str("var a = [1,2,3]; Object.defineProperty(a, '1', {configurable:false}); " + "a.length = 1"), + njs_str("TypeError: Cannot delete property \"1\" of array") }, + + { njs_str("var a = [1,2,3]; Object.defineProperty(a, '1', {writable:false}); " + "a[1]=4"), + njs_str("TypeError: Cannot assign to read-only property \"1\" of array") }, + + { njs_str("var a = [1,2,3]; Object.defineProperty(a, '1', {enumerable:false}); " + "njs.dump([Object.keys(a), Object.values(a), Object.entries(a)])"), + njs_str("[['0','2'],[1,3],[['0',1],['2',3]]]") }, + + { njs_str("var a = [1]; Object.defineProperty(a, '1', {get:()=>2});" + "Object.defineProperty(a, 'length', {value:1})"), + njs_str("TypeError: Cannot delete property \"1\" of array") }, + + { njs_str("var a = [1]; Object.defineProperty(a, '1', {get:()=>2});" + "Object.defineProperty(a, 'length', {enumerable:true})"), + njs_str("TypeError: Cannot redefine property: \"length\"") }, + + { njs_str("var a = [1]; Object.defineProperty(a, '1', {get:()=>2, configurable:true});" + "Object.defineProperty(a, 'length', {value:1}); a[1]"), + njs_str("undefined") }, + + { njs_str("var a = [1,2,3]; Object.defineProperty(a, '1', {enumerable:false});" + "Object.getOwnPropertyDescriptor(a, '1').enumerable"), + njs_str("false") }, + + { njs_str("var args = (function(a, b, c) {return arguments;})(1,2,3); " + "Object.defineProperty(args, \"length\", {value:6}); args.length"), + njs_str("6") }, + { njs_str("var get = 'get'; var o = { get }; o.get"), njs_str("get") }, @@ -15154,6 +15363,22 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = [1]; a[2] = 'x'; JSON.stringify(a)"), njs_str("[1,null,\"x\"]") }, + { njs_str("var a = " NJS_LARGE_ARRAY ";" + "a[32] = 'a'; a[64] = 'b';" + "var s = JSON.stringify(a); " + "[s.length,s.substring(162,163),s.match(/null/g).length]"), + njs_str("163844,a,32767") }, + + { njs_str("var a = " NJS_LARGE_ARRAY ";" + "a[2] = 'a'; a[4] = 'b'; a.length = 3;" + "JSON.stringify(a)"), + njs_str("[null,null,\"a\"]") }, + + { njs_str("var a = [1,2,3];" + "Object.defineProperty(a, '1', {enumerable:false});" + "JSON.stringify(a)"), + njs_str("[1,2,3]") }, + { njs_str("JSON.stringify({a:\"b\",c:19,e:null,t:true,f:false})"), njs_str("{\"a\":\"b\",\"c\":19,\"e\":null,\"t\":true,\"f\":false}") }, @@ -15486,6 +15711,35 @@ static njs_unit_test_t njs_test[] = { njs_str("var a = [], b = [a]; a[0] = b; njs.dump(a)"), njs_str("[[[Circular]]]") }, + { njs_str("var a = []; a.length = 2**31;" + "njs.dump(a)"), + njs_str("[<2147483648 empty items>]") }, + + { njs_str("var a = ['a',,'c']; a.length = 2**31;" + "njs.dump(a)"), + njs_str("['a',<1 empty items>,'c',<2147483645 empty items>]") }, + + { njs_str("var a = [,'b','c']; a.length = 2**31;" + "njs.dump(a)"), + njs_str("[<1 empty items>,'b','c',<2147483645 empty items>]") }, + +#if (!NJS_HAVE_MEMORY_SANITIZER) /* False-positive in MSAN? */ + { njs_str("var a = []; a[2**31] = 'Z'; a[0] = 'A'; njs.dump(a)"), + njs_str("['A',<2147483647 empty items>,'Z']") }, + + { njs_str("var a = []; a[2**31] = 'Z'; a[0] = 'A'; a.b = 'X'; njs.dump(a)"), + njs_str("['A',<2147483647 empty items>,'Z',b:'X']") }, + + { njs_str("var a = []; a[2**31] = 'Z'; a[0] = 'A'; a.b = 'X'; a.length = 3; njs.dump(a)"), + njs_str("['A',<2 empty items>,b:'X']") }, +#endif + + { njs_str("var a = [1,2,3];Object.defineProperty(a, '1', {get:()=>2});njs.dump(a)"), + njs_str("[1,'[Getter]',3]") }, + + { njs_str("var a = [1,2,3];Object.defineProperty(a, '1', {enumerable:false});njs.dump(a)"), + njs_str("[1,<1 empty items>,3]") }, + { njs_str("njs.dump(-0)"), njs_str("-0") },