]> git.kaiwu.me - njs.git/commitdiff
Ensuring Array.prototype.sort() stability.
authorDmitry Volyntsev <xeioex@nginx.com>
Tue, 2 Jun 2020 12:08:04 +0000 (12:08 +0000)
committerDmitry Volyntsev <xeioex@nginx.com>
Tue, 2 Jun 2020 12:08:04 +0000 (12:08 +0000)
For fast arrays with empty or undefined elements.

This closes #312 issue on Github.

src/njs_array.c
src/test/njs_unit_test.c

index bb820a80793f3a548f5c42d013b20b9238c18040..d4ac54921c4c330186d215a4d62cc33d9145ad38 100644 (file)
@@ -3216,8 +3216,8 @@ static njs_int_t
 njs_array_prototype_sort(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    int64_t                i, und, inv, len, nlen, length;
-    njs_int_t              ret;
+    int64_t                i, und, len, nlen, length;
+    njs_int_t              ret, fast_path;
     njs_array_t            *array;
     njs_value_t            *this, *comparefn, *start, *strings;
     njs_array_sort_ctx_t   ctx;
@@ -3260,50 +3260,46 @@ njs_array_prototype_sort(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     ctx.strings.pointer = 0;
     ctx.exception = 0;
 
-    if (njs_fast_path(njs_is_fast_array(this))) {
+    fast_path = njs_is_fast_array(this);
+
+    if (njs_fast_path(fast_path)) {
         array = njs_array(this);
         start = array->start;
 
-        /**
-         * Moving undefined and invalid elements to the end.
-         * x x x | undefineds | invalids
-         */
+        slots = njs_mp_alloc(vm->mem_pool,
+                             sizeof(njs_array_sort_slot_t) * length);
+        if (njs_slow_path(slots == NULL)) {
+                return NJS_ERROR;
+        }
 
         und = 0;
-        inv = length;
+        p = slots;
 
-        for (i = length - 1; i >= 0; i--) {
-            if (njs_is_undefined(&start[i])) {
-                start[i] = start[inv - und - 1];
-                start[inv - und - 1] = njs_value_undefined;
-                und++;
-                continue;
+        for (i = 0; i < length; i++) {
+            if (njs_slow_path(!njs_is_valid(&start[i]))) {
+                fast_path = 0;
+                njs_mp_free(vm->mem_pool, slots);
+                slots = NULL;
+                goto slow_path;
             }
 
-            if (!njs_is_valid(&start[i])) {
-                start[i] = start[inv - und - 1];
-                start[inv - und - 1] = njs_value_undefined;
-                start[inv - 1] = njs_value_invalid;
-                inv--;
+            if (njs_slow_path(njs_is_undefined(&start[i]))) {
+                und++;
                 continue;
             }
-        }
 
-        len = inv - und;
-
-        slots = njs_mp_alloc(vm->mem_pool,
-                             sizeof(njs_array_sort_slot_t) * len);
-        if (njs_slow_path(slots == NULL)) {
-                return NJS_ERROR;
+            p->value = start[i];
+            p->pos = i;
+            p->str = NULL;
+            p++;
         }
 
-        for (i = 0; i < len; i++) {
-            slots[i].value = start[i];
-            slots[i].pos = i;
-            slots[i].str = NULL;
-        }
+        len = p - slots;
 
     } else {
+
+slow_path:
+
         und = 0;
         p = NULL;
         end = NULL;
@@ -3369,13 +3365,18 @@ njs_array_prototype_sort(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         goto exception;
     }
 
-    if (njs_fast_path(njs_is_fast_array(this))) {
+    if (njs_fast_path(fast_path)) {
         array = njs_array(this);
         start = array->start;
+
         for (i = 0; i < len; i++) {
             start[i] = slots[i].value;
         }
 
+        for (i = len; und-- > 0; i++) {
+            start[i] = njs_value_undefined;
+        }
+
     } else {
         for (i = 0; i < len; i++) {
             if (slots[i].pos != i) {
index 7f89a1c513ecdd0c8b61325d1a20a7b11e7e429d..6b4042f71e8f63b31d718ae4e95f4d9b92cea587 100644 (file)
@@ -6104,6 +6104,12 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("[1,2,3].sort(()=>-1)"),
       njs_str("3,2,1") },
 
+    { njs_str("njs.dump([undefined,1,2,3].sort(()=>0))"),
+      njs_str("[1,2,3,undefined]") },
+
+    { njs_str("njs.dump([1,,2,3].sort(()=>0))"),
+      njs_str("[1,2,3,<empty>]") },
+
     { njs_str("var count = 0;"
               "[4,3,2,1].sort(function(x, y) { if (count++ == 2) {throw Error('Oops'); }; return x - y })"),
       njs_str("Error: Oops") },