[njs] Fixed Array.prototype.join().

Dmitry Volyntsev xeioex at nginx.com
Mon Dec 23 15:53:26 UTC 2019


details:   https://hg.nginx.org/njs/rev/a0adc224673d
branches:  
changeset: 1285:a0adc224673d
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Mon Dec 16 15:18:51 2019 +0300
description:
Fixed Array.prototype.join().

Resulting string length of Array.prototype.join() was invalid
when joined values are not strings.

The issue was introduced in 5bf71dfc052f.

diffstat:

 src/njs_array.c            |  33 ++++++++++++++++++++++++---------
 src/njs_chb.h              |   7 +++++++
 src/njs_number.c           |  34 ++++++++++++++++++----------------
 src/njs_number.h           |   4 ++--
 src/njs_string.h           |   7 +++++++
 src/njs_value.c            |  21 +++++++++------------
 src/njs_value_conversion.h |   3 +++
 src/test/njs_unit_test.c   |  30 +++++++++++++++++++++++++-----
 8 files changed, 95 insertions(+), 44 deletions(-)

diffs (292 lines):

diff -r 9c1ef5ab7a1f -r a0adc224673d src/njs_array.c
--- a/src/njs_array.c	Thu Dec 12 18:50:27 2019 +0300
+++ b/src/njs_array.c	Mon Dec 16 15:18:51 2019 +0300
@@ -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, n
     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);
+
+                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, n
     njs_chb_drop(&chain, separator.size);
 
     size = njs_chb_size(&chain);
-
-    if (length != 0) {
-        length -= separator.length;
-    }
-
-    p = njs_string_alloc(vm, &vm->retval, size, length);
+    length -= separator.length;
+
+    p = njs_string_alloc(vm, &vm->retval, size, utf8 ? length : 0);
     if (njs_slow_path(p == NULL)) {
         return NJS_ERROR;
     }
diff -r 9c1ef5ab7a1f -r a0adc224673d src/njs_chb.h
--- a/src/njs_chb.h	Thu Dec 12 18:50:27 2019 +0300
+++ b/src/njs_chb.h	Mon Dec 16 15:18:51 2019 +0300
@@ -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)
 {
diff -r 9c1ef5ab7a1f -r a0adc224673d src/njs_number.c
--- a/src/njs_number.c	Thu Dec 12 18:50:27 2019 +0300
+++ b/src/njs_number.c	Mon Dec 16 15:18:51 2019 +0300
@@ -236,37 +236,39 @@ njs_number_to_string(njs_vm_t *vm, njs_v
 }
 
 
-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;
 }
 
 
diff -r 9c1ef5ab7a1f -r a0adc224673d src/njs_number.h
--- a/src/njs_number.h	Thu Dec 12 18:50:27 2019 +0300
+++ b/src/njs_number.h	Mon Dec 16 15:18:51 2019 +0300
@@ -17,8 +17,8 @@ int64_t njs_number_radix_parse(const u_c
     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,
diff -r 9c1ef5ab7a1f -r a0adc224673d src/njs_string.h
--- a/src/njs_string.h	Thu Dec 12 18:50:27 2019 +0300
+++ b/src/njs_string.h	Mon Dec 16 15:18:51 2019 +0300
@@ -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)
 {
diff -r 9c1ef5ab7a1f -r a0adc224673d src/njs_value.c
--- a/src/njs_value.c	Thu Dec 12 18:50:27 2019 +0300
+++ b/src/njs_value.c	Mon Dec 16 15:18:51 2019 +0300
@@ -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;
 }
 
 
diff -r 9c1ef5ab7a1f -r a0adc224673d src/njs_value_conversion.h
--- a/src/njs_value_conversion.h	Thu Dec 12 18:50:27 2019 +0300
+++ b/src/njs_value_conversion.h	Mon Dec 16 15:18:51 2019 +0300
@@ -199,6 +199,9 @@ njs_value_to_string(njs_vm_t *vm, njs_va
 }
 
 
+/*
+ * 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)
 {
diff -r 9c1ef5ab7a1f -r a0adc224673d src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Thu Dec 12 18:50:27 2019 +0300
+++ b/src/test/njs_unit_test.c	Mon Dec 16 15:18:51 2019 +0300
@@ -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("var a = ['β', 'γ']; var out= a.join('α'); [out, out.length]"),
-      njs_str("βαγ,3") },
+    { 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 = ['β','γ']; 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") },


More information about the nginx-devel mailing list