[njs] Fixed Array.prototype.splice() according to the specification.
Dmitry Volyntsev
xeioex at nginx.com
Wed Jun 3 18:41:14 UTC 2020
details: https://hg.nginx.org/njs/rev/c1e3fd0d24fd
branches:
changeset: 1422:c1e3fd0d24fd
user: Dmitry Volyntsev <xeioex at nginx.com>
date: Wed Jun 03 18:40:10 2020 +0000
description:
Fixed Array.prototype.splice() according to the specification.
diffstat:
src/njs_array.c | 171 ++++++++++++++++++++++++++++------------------
src/test/njs_unit_test.c | 96 ++++++++++++++++++++++++++
2 files changed, 201 insertions(+), 66 deletions(-)
diffs (319 lines):
diff -r a23651137ccf -r c1e3fd0d24fd src/njs_array.c
--- a/src/njs_array.c Wed Jun 03 18:40:09 2020 +0000
+++ b/src/njs_array.c Wed Jun 03 18:40:10 2020 +0000
@@ -1241,10 +1241,9 @@ static njs_int_t
njs_array_prototype_splice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
- int64_t n, start, length, items, delta, delete;
+ int64_t i, n, start, length, items, delta, delete;
njs_int_t ret;
- njs_uint_t i;
- njs_value_t *this;
+ njs_value_t *this, value, del_object;
njs_array_t *array, *deleted;
this = njs_argument(args, 0);
@@ -1254,74 +1253,81 @@ njs_array_prototype_splice(njs_vm_t *vm,
return ret;
}
- if (njs_slow_path(!njs_is_fast_array(this))) {
- njs_internal_error(vm, "splice() is not implemented yet for objects");
+ ret = njs_object_length(vm, this, &length);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return ret;
+ }
+
+ ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ start = (start < 0) ? njs_max(length + start, 0) : njs_min(start, length);
+
+ items = 0;
+ delete = 0;
+
+ if (nargs == 2) {
+ delete = length - start;
+
+ } else if (nargs > 2) {
+ items = nargs - 3;
+
+ ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &delete);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ delete = njs_min(njs_max(delete, 0), length - start);
+ }
+
+ delta = items - delete;
+
+ if (njs_slow_path((length + delta) > NJS_MAX_LENGTH)) {
+ njs_type_error(vm, "Invalid length");
return NJS_ERROR;
}
- array = NULL;
- start = 0;
- delete = 0;
-
- if (njs_is_fast_array(this)) {
- array = njs_array(this);
- length = array->length;
-
- if (nargs > 1) {
- ret = njs_value_to_integer(vm, njs_arg(args, nargs, 1), &start);
- if (njs_slow_path(ret != NJS_OK)) {
- return ret;
- }
-
- if (start < 0) {
- start += length;
-
- if (start < 0) {
- start = 0;
- }
-
- } else if (start > length) {
- start = length;
- }
-
- delete = length - start;
-
- if (nargs > 2) {
- ret = njs_value_to_integer(vm, njs_arg(args, nargs, 2), &n);
- if (njs_slow_path(ret != NJS_OK)) {
- return ret;
- }
-
- if (n < 0) {
- delete = 0;
-
- } else if (n < delete) {
- delete = n;
- }
- }
- }
- }
+ /* TODO: ArraySpeciesCreate(). */
deleted = njs_array_alloc(vm, 0, delete, 0);
if (njs_slow_path(deleted == NULL)) {
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. */
- for (i = 0, n = start; i < (njs_uint_t) delete; i++, n++) {
- /* No retention required. */
+ if (njs_fast_path(njs_is_fast_array(this) && deleted->object.fast_array)) {
+ array = njs_array(this);
+ for (i = 0, n = start; i < delete; i++, n++) {
deleted->start[i] = array->start[n];
}
- items = (nargs > 3) ? nargs - 3: 0;
- delta = items - delete;
+ } else {
+ njs_set_array(&del_object, deleted);
+
+ for (i = 0, n = start; i < delete; i++, n++) {
+ ret = njs_value_property_i64(vm, this, n, &value);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+
+ if (ret == NJS_OK) {
+ /* TODO: CreateDataPropertyOrThrow(). */
+ ret = njs_value_property_i64_set(vm, &del_object, i, &value);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return ret;
+ }
+ }
+ }
+
+ ret = njs_object_length_set(vm, &del_object, delete);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+
+ if (njs_fast_path(njs_is_fast_array(this))) {
+ array = njs_array(this);
if (delta != 0) {
/*
@@ -1335,18 +1341,51 @@ njs_array_prototype_splice(njs_vm_t *vm,
}
}
- memmove(&array->start[start + items], &array->start[n],
- (array->length - n) * sizeof(njs_value_t));
+ ret = njs_array_copy_within(vm, this, start + items, start + delete,
+ array->length - (start + delete), 0);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
array->length += delta;
}
/* Copy new items. */
- n = start;
-
- for (i = 3; i < nargs; i++) {
- /* GC: njs_retain(&args[i]); */
- array->start[n++] = args[i];
+
+ if (items > 0) {
+ memcpy(&array->start[start], &args[3],
+ items * sizeof(njs_value_t));
+ }
+
+ } else {
+
+ if (delta != 0) {
+ ret = njs_array_copy_within(vm, this, start + items, start + delete,
+ length - (start + delete), delta < 0);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return ret;
+ }
+
+ for (i = length - 1; i >= length + delta; i--) {
+ ret = njs_value_property_i64_delete(vm, this, i, NULL);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+ }
+ }
+
+ /* Copy new items. */
+
+ for (i = 3, n = start; items-- > 0; i++, n++) {
+ ret = njs_value_property_i64_set(vm, this, n, &args[i]);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+ }
+
+ ret = njs_object_length_set(vm, this, length + delta);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
}
}
diff -r a23651137ccf -r c1e3fd0d24fd src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c Wed Jun 03 18:40:09 2020 +0000
+++ b/src/test/njs_unit_test.c Wed Jun 03 18:40:10 2020 +0000
@@ -4499,6 +4499,102 @@ static njs_unit_test_t njs_test[] =
"a.splice(3, 2, 8, 9, 10, 11 ).join(':') + '|' + a"),
njs_str("3:4|0,1,2,8,9,10,11,5,6,7") },
+ { njs_str("["
+ " [],"
+ " [1],"
+ " [1, 2],"
+ " [1, 2, 'a'],"
+ " [1, 2, 'a', 'b'],"
+ " [1, 2, 'a', 'b', 'c'],"
+ "]"
+ ".map(args=>{var a = [0,1,3,4,5]; a.splice.apply(a, args); return a})"
+ ".map(v=>v.join(''))"),
+ njs_str("01345,0,045,0a45,0ab45,0abc45") },
+
+ { njs_str("["
+ " [],"
+ " [1],"
+ " [1, 1, 'a'],"
+ " [1, 2, 'a'],"
+ " [1, 2, 'a', 'b'],"
+ " [1, 2, 'a', 'b', 'c'],"
+ "]"
+ ".map(args=>{var a = [0,1,3,4,5]; return a.splice.apply(a, args);})"
+ ".map(v=>v.join(''))"),
+ njs_str(",1345,1,13,13,13") },
+
+ { njs_str("Object.prototype.splice = Array.prototype.splice;"
+ "Object.prototype.join = Array.prototype.join;"
+ "["
+ " [],"
+ " [1],"
+ " [1, 2],"
+ " [1, 1, 'a'],"
+ " [1, 2, 'a'],"
+ " [1, 2, 'a', 'b'],"
+ " [1, 2, 'a', 'b', 'c'],"
+ "]"
+ ".map(args=>{var a = {0:0, 1:1, 2:3, 3:4, 4:5, length:5};"
+ " a.splice.apply(a, args); return a})"
+ ".map(v=>v.join(''))"),
+ njs_str("01345,0,045,0a345,0a45,0ab45,0abc45") },
+
+ { njs_str("Object.prototype.splice = Array.prototype.splice;"
+ "Object.prototype.join = Array.prototype.join;"
+ "["
+ " [],"
+ " [1],"
+ " [1, 0, 'a'],"
+ " [1, 1, 'a'],"
+ " [1, 2, 'a'],"
+ " [1, 2, 'a', 'b'],"
+ " [1, 2, 'a', 'b', 'c'],"
+ "]"
+ ".map(args=>{var a = {0:0, 1:1, 2:3, 3:4, 4:5, length:5};"
+ " return a.splice.apply(a, args);})"
+ ".map(v=>v.join(''))"),
+ njs_str(",1345,,1,13,13,13") },
+
+ { njs_str("Array.prototype.splice.call({0:0,1:1,2:2,3:3,length:4},0,3,4,5)"),
+ njs_str("0,1,2") },
+
+ { njs_str("var obj = {0:0,1:1,2:2,3:3,length:4};"
+ "Array.prototype.splice.call(obj,0,3,4,5); obj[3]"),
+ njs_str("undefined") },
+
+ { njs_str("var obj = {4294967294: 'x', length:-1};"
+ "Array.prototype.splice.call(obj, 4294967294, 1); obj.length"),
+ njs_str("0") },
+
+ { njs_str("var obj = {0:0, 1:1, 2:2};"
+ "Object.defineProperty(obj, 'length', {value:3, writable:false});"
+ "Array.prototype.splice.call(obj, 1, 2, 4)"),
+ njs_str("TypeError: Cannot assign to read-only property \"length\" of object") },
+
+ { njs_str("var obj = {'9007199254740988': 'A', '9007199254740989': 'B',"
+ " '9007199254740990': 'C', '9007199254740991': 'D', "
+ " length: 2 ** 53 + 2};"
+ "Array.prototype.splice.call(obj, 2**53-3, 2 ** 53 + 4)"),
+ njs_str("B,C") },
+
+ { njs_str("var obj = {'9007199254740988': 'A', '9007199254740989': 'B',"
+ " '9007199254740990': 'C', '9007199254740991': 'D', "
+ " length: 2 ** 53 + 2};"
+ "Array.prototype.splice.call(obj, 2**53-3, 2 ** 53 + 4);"
+ "obj['9007199254740988'] == 'A' && obj['9007199254740991'] == 'D'"),
+ njs_str("true") },
+
+ { njs_str("var obj = {'9007199254740990': 'A', '9007199254740991': 'B',"
+ " length: 2 ** 53 - 1};"
+ "Array.prototype.splice.call(obj, 2**53-2, 1, 'C');"
+ "obj['9007199254740990'] == 'C' && obj['9007199254740991'] == 'B'"),
+ njs_str("true") },
+
+ { njs_str("var obj = {'9007199254740990': 'A', '9007199254740991': 'B',"
+ " length: 2 ** 53 - 1};"
+ "Array.prototype.splice.call(obj, 2**53-2, 0, 'C');"),
+ njs_str("TypeError: Invalid length") },
+
{ njs_str("var a = []; a.reverse()"),
njs_str("") },
More information about the nginx-devel
mailing list