]> git.kaiwu.me - njs.git/commitdiff
Fixed Array.prototype.join().
authorDmitry Volyntsev <xeioex@nginx.com>
Mon, 16 Dec 2019 12:18:51 +0000 (15:18 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Mon, 16 Dec 2019 12:18:51 +0000 (15:18 +0300)
Resulting string length of Array.prototype.join() was invalid
when joined values are not strings.

The issue was introduced in 5bf71dfc052f.

src/njs_array.c
src/njs_chb.h
src/njs_number.c
src/njs_number.h
src/njs_string.h
src/njs_value.c
src/njs_value_conversion.h
src/test/njs_unit_test.c

index cd2bad3bbdaef77fc64bddd8182fefe51a66a531..e6016e8cf966b78e7a515223575d5b5010977609 100644 (file)
@@ -1122,11 +1122,12 @@ static njs_int_t
 njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    u_char             *p;
+    u_char             *p, *last;
     size_t             size;
     ssize_t            length;
     njs_int_t          ret;
     njs_chb_t          chain;
+    njs_utf8_t         utf8;
     njs_uint_t         i;
     njs_array_t        *array;
     njs_value_t        *value;
@@ -1163,20 +1164,37 @@ njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     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 < array->length; i++) {
         value = &array->start[i];
+
         if (njs_is_valid(value) && !njs_is_null_or_undefined(value)) {
             if (!njs_is_string(value)) {
+                last = njs_chb_current(&chain);
+
                 ret = njs_value_to_chain(vm, &chain, value);
-                if (njs_slow_path(ret != NJS_OK)) {
+                if (njs_slow_path(ret < NJS_OK)) {
                     return ret;
                 }
 
+                if (last != njs_chb_current(&chain) && ret == 0) {
+                    /*
+                     * Appended values was a byte string.
+                     */
+                    utf8 = NJS_STRING_BYTE;
+                }
+
+                length += ret;
+
             } else {
                 (void) njs_string_prop(&string, value);
-                length += string.length;
 
+                if (njs_is_byte_string(&string)) {
+                    utf8 = NJS_STRING_BYTE;
+                }
+
+                length += string.length;
                 njs_chb_append(&chain, string.start, string.size);
             }
         }
@@ -1188,12 +1206,9 @@ njs_array_prototype_join(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_chb_drop(&chain, separator.size);
 
     size = njs_chb_size(&chain);
+    length -= separator.length;
 
-    if (length != 0) {
-        length -= separator.length;
-    }
-
-    p = njs_string_alloc(vm, &vm->retval, size, length);
+    p = njs_string_alloc(vm, &vm->retval, size, utf8 ? length : 0);
     if (njs_slow_path(p == NULL)) {
         return NJS_ERROR;
     }
index 065f62804eeca22023a4018147010a30529ce98a..3a8a24f798bf5a89e1637c843d53e3f1a56e0f7d 100644 (file)
@@ -104,6 +104,13 @@ njs_chb_utf8_length(njs_chb_t *chain)
 }
 
 
+njs_inline u_char *
+njs_chb_current(njs_chb_t *chain)
+{
+    return (chain->last != NULL) ? chain->last->pos : NULL;
+}
+
+
 njs_inline void
 njs_chb_written(njs_chb_t *chain, size_t bytes)
 {
index 82fbcfdd2813f79d1e21fea67857c354ea847fc4..8b4476c182a52873c79582e951d03e26738b4025 100644 (file)
@@ -236,37 +236,39 @@ njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
 }
 
 
-void
-njs_number_to_chain(njs_vm_t *vm, njs_chb_t *chain, const njs_value_t *number)
+njs_int_t
+njs_number_to_chain(njs_vm_t *vm, njs_chb_t *chain, double num)
 {
-    double             num;
-    size_t             size;
-    u_char   *p;
-
-    num = njs_number(number);
+    size_t  size;
+    u_char  *p;
 
     if (isnan(num)) {
         njs_chb_append_literal(chain, "NaN");
+        return njs_length("NaN");
 
-    } else if (isinf(num)) {
+    }
 
+    if (isinf(num)) {
         if (num < 0) {
             njs_chb_append_literal(chain, "-Infinity");
+            return njs_length("-Infinity");
 
         } else {
             njs_chb_append_literal(chain, "Infinity");
+            return njs_length("Infinity");
         }
+    }
 
-    } else {
-        p = njs_chb_reserve(chain, 64);
-        if (njs_slow_path(p == NULL)) {
-            return;
-        }
+    p = njs_chb_reserve(chain, 64);
+    if (njs_slow_path(p == NULL)) {
+        return NJS_ERROR;
+    }
 
-        size = njs_dtoa(num, (char *) p);
+    size = njs_dtoa(num, (char *) p);
 
-        njs_chb_written(chain, size);
-    }
+    njs_chb_written(chain, size);
+
+    return size;
 }
 
 
index 22d9140e26f2706498fbd7e67fcf220bb0753e30..f73bf7c0bfdc2291456794458727ffedb46462aa 100644 (file)
@@ -17,8 +17,8 @@ int64_t njs_number_radix_parse(const u_char **start, const u_char *end,
     uint8_t radix);
 njs_int_t njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
     const njs_value_t *number);
-void njs_number_to_chain(njs_vm_t *vm, njs_chb_t *chain,
-    const njs_value_t *number);
+njs_int_t njs_number_to_chain(njs_vm_t *vm, njs_chb_t *chain,
+    double number);
 njs_int_t njs_number_global_is_nan(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused);
 njs_int_t njs_number_global_is_finite(njs_vm_t *vm, njs_value_t *args,
index edf78f4f4ed2df88854303b2d20db2520d8d8471..32204009078f4efd6e4b712a9df5f76030f7f89f 100644 (file)
@@ -102,6 +102,13 @@ typedef enum {
 } njs_utf8_t;
 
 
+njs_inline njs_bool_t
+njs_is_byte_string(njs_string_prop_t *string)
+{
+    return (string->length == 0 && string->size != 0);
+}
+
+
 njs_inline uint32_t
 njs_string_calc_length(njs_utf8_t utf8, const u_char *start, size_t size)
 {
index 9f3d273680ca2a67abfcf89eeedf1190531d3243..a861889d7fc0e26fc4842a9fb8a9d00557551c8e 100644 (file)
@@ -1270,46 +1270,43 @@ njs_int_t
 njs_primitive_value_to_chain(njs_vm_t *vm, njs_chb_t *chain,
     const njs_value_t *src)
 {
-    njs_str_t  string;
+    njs_string_prop_t  string;
 
     switch (src->type) {
 
     case NJS_NULL:
         njs_chb_append_literal(chain, "null");
-        break;
+        return njs_length("null");
 
     case NJS_UNDEFINED:
         njs_chb_append_literal(chain, "undefined");
-        break;
+        return njs_length("undefined");
 
     case NJS_BOOLEAN:
         if (njs_is_true(src)) {
             njs_chb_append_literal(chain, "true");
+            return njs_length("true");
 
         } else {
             njs_chb_append_literal(chain, "false");
+            return njs_length("false");
         }
 
-        break;
-
     case NJS_NUMBER:
-        njs_number_to_chain(vm, chain, src);
-        break;
+        return njs_number_to_chain(vm, chain, njs_number(src));
 
     case NJS_SYMBOL:
         njs_symbol_conversion_failed(vm, 1);
         return NJS_ERROR;
 
     case NJS_STRING:
-        njs_string_get(src, &string);
-        njs_chb_append_str(chain, &string);
-        break;
+        (void) njs_string_prop(&string, src);
+        njs_chb_append(chain, string.start, string.size);
+        return string.length;
 
     default:
         return NJS_ERROR;
     }
-
-    return NJS_OK;
 }
 
 
index 7c706b151b21bffd18777ee32a93eff7940529ac..581a29c6542a2c2bc02aa33f551d5c5ad9f0c15e 100644 (file)
@@ -199,6 +199,9 @@ njs_value_to_string(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value)
 }
 
 
+/*
+ * retval >= 0 is length (UTF8 characters) value of appended string.
+ */
 njs_inline njs_int_t
 njs_value_to_chain(njs_vm_t *vm, njs_chb_t *chain, njs_value_t *value)
 {
index 9d0ae40b78f03b8755b974f1b1de44d850c45a11..a7fc052e1d298211a8b76b0565250b2a54143412 100644 (file)
@@ -3893,11 +3893,31 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var a = [1,2,3]; a.join(':')"),
       njs_str("1:2:3") },
 
-    { njs_str("var a = ['#', '@']; var out= a.join('α'.repeat(33)); [out, out.length]"),
-      njs_str("#ααααααααααααααααααααααααααααααααα@,35") },
+    { njs_str("["
+              "  'α'.repeat(33),"
+              "  String.bytesFrom(Array(16).fill(0x9d)),"
+              "]"
+              ".map(v=>{var out = ['β', 'γ'].join(v); return out.length})"),
+      njs_str("35,20") },
+
+    { njs_str("["
+              "  [],"
+              "  ['β', 'γ'],"
+              "  [NaN, Math.pow(2,123.2), Infinity, -1],"
+              "  [new String('β'),{toString(){return 'γ'}}],"
+              "]"
+              ".map(v=>{var out = v.join('α'); return [out, out[out.length - 1],out.length]})"
+              ".map(v=>njs.dump(v))"),
+      njs_str("['',undefined,0],"
+              "['βαγ','γ',3],"
+              "['NaNα1.2215056097393134e+37αInfinityα-1','1',38],"
+              "['βαγ','γ',3]") },
 
-    { njs_str("var a = ['β', 'γ']; var out= a.join('α'); [out, out.length]"),
-      njs_str("βαγ,3") },
+    { njs_str("var a = ['β','γ']; a.join('').length"),
+      njs_str("2") },
+
+    { njs_str("var a = ['β', String.bytesFrom([0x9d]),'γ']; a.join('').length"),
+      njs_str("5") },
 
     { njs_str("var a = []; a[5] = 5; a.join()"),
       njs_str(",,,,,5") },