]> git.kaiwu.me - njs.git/commitdiff
Introduced memory-efficient arrays.
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 4 Feb 2020 17:35:02 +0000 (20:35 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Tue, 4 Feb 2020 17:35:02 +0000 (20:35 +0300)
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.

20 files changed:
src/njs_array.c
src/njs_array.h
src/njs_array_buffer.c
src/njs_chb.h
src/njs_clang.h
src/njs_crypto.c
src/njs_date.c
src/njs_error.c
src/njs_json.c
src/njs_module.c
src/njs_object.c
src/njs_object.h
src/njs_object_prop.c
src/njs_promise.c
src/njs_regexp.c
src/njs_typed_array.c
src/njs_value.c
src/njs_value.h
src/test/njs_benchmark.c
src/test/njs_unit_test.c

index 3378115aef2a77eda34955b41d9b778d29a528cc..d72b72fd9ce2a658d471ff78785d745c35a11c1e 100644 (file)
@@ -8,7 +8,7 @@
 #include <njs_main.h>
 
 
-#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 } },
 };
index 92f5f5b4cde07634b7db55916f71802e14f8fbb6..56bb8113b6245843a1375c36faf7372ab71d121c 100644 (file)
@@ -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,
index c04bc6f98c4601b8fd7619f500efe89c7705a7f6..bf85a9688b116585dc373e1d05fe1755a06dd95c 100644 (file)
@@ -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;
index 3a8a24f798bf5a89e1637c843d53e3f1a56e0f7d..2cbf509dfe1fe8a235e2cf68d691a406507d2e85 100644 (file)
@@ -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))
index 967c9ad63bf7601cdd35911ac573ded015c0e749..a2d60e9efb98d71410a752f6913a8f3094b4de4c 100644 (file)
@@ -155,6 +155,9 @@ njs_leading_zeros64(uint64_t x)
 #endif
 
 
+#define njs_stringify(v)    #v
+
+
 #if (NJS_HAVE_MEMORY_SANITIZER)
 #include <sanitizer/msan_interface.h>
 
index e6d665950133da58efd79cf2c721cc2537c899d3..434251fd8f1ba3eae484f6c7a6a79241b8b56c55 100644 (file)
@@ -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;
index 41c84bd7ebd3482c0ea7ef9c8699d86557f13719..93244b91085a0d8e09c71d2af0fb41ea0dfdf8f9 100644 (file)
@@ -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;
index 433da72c2515aacf275751c61c879d73d5a3fe81..b005ac58387f42c5dd91c6e7ff01f9c6b2ff1ab2 100644 (file)
@@ -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);
index 6fba9c6e575238eb264de7a71ab697bb1ce18ecd..fcd6777bc158d12912a52417358b50c4b54a3fce 100644 (file)
@@ -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++];
index b0aa04827af15166abf732f0d1e59587f654d5d1..f527dfec97d0b9f67ef8bbc97bf4f8ec8e95fd38 100644 (file)
@@ -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);
 
index 34a8cc28ac4cb4610c5090bede445a315db802c3..67d2ba657ed03b18b6a7171def7790bd5ba619cb 100644 (file)
@@ -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) {
index 1d704a8cb5792d6b2ab055ec7e4056cbe4e585b4..73333c987fe23c4b8689db3745d790ff0eac0ec1 100644 (file)
@@ -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,
index c486ece17d275f7dbdaf4f9e6d1544c88ccfd65d..45d6e2dbcc9acad915ccddaaaea252e7baa5d32a 100644 (file)
@@ -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;
         }
     }
index a9a2a71845a9a3379ff4c39565f809d7f95f7319..6eb4b02bfe867562bfc202b01eddcc588a3c30c3 100644 (file)
@@ -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;
index acec442294d6d91a1b78bdc343ea79815f9d09cf..c16588a1d197b8cc91b6efc75a206cd0df91a68c 100644 (file)
@@ -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(&regexp->last_index, 0);
         regexp->pattern = pattern;
         njs_string_short_set(&regexp->string, 0, 0);
index 8751c62c00b5a4d58ed47707ee72b1630bb7190f..0cd89b35f69c2887717302f2cd6eb4a4dd2e7222 100644 (file)
@@ -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;
 
index eb95b822e71303432200ed276de033d82be565da..926769ec7276c09245d31bb2f18a316a973b526d 100644 (file)
@@ -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:
index e49d1621fab79705fade7232150620fe75dfbcb7..2256f8620110c7e70902753c5d3ed4b320412f3d 100644 (file)
@@ -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
 
index b5e1f23cb6b535308bcb28907ef78c397fd90a64..3cb05bfc15da6fc7169f7c107a2116f586a47a87 100644 (file)
@@ -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",
index bd9727dd8c8641d6877334d7059efa3dee13ef0c..1f42cf8ad7dd8639aa1dc3810e5436f95a3411f7 100644 (file)
@@ -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,<empty>,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") },