[njs] Added String.prototype.replaceAll().

Vadim Zhestikov v.zhestikov at f5.com
Fri Feb 3 14:42:10 UTC 2023


details:   https://hg.nginx.org/njs/rev/69547623da14
branches:  
changeset: 2038:69547623da14
user:      Vadim Zhestikov <v.zhestikov at f5.com>
date:      Fri Feb 03 06:41:01 2023 -0800
description:
Added String.prototype.replaceAll().

diffstat:

 src/njs_regexp.c         |    2 +-
 src/njs_regexp.h         |    2 +
 src/njs_string.c         |  218 +++++++++++++++++++++++--------
 src/test/njs_unit_test.c |  319 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 484 insertions(+), 57 deletions(-)

diffs (927 lines):

diff -r 286675dcfbc5 -r 69547623da14 src/njs_regexp.c
--- a/src/njs_regexp.c	Thu Feb 02 10:01:26 2023 -0800
+++ b/src/njs_regexp.c	Fri Feb 03 06:41:01 2023 -0800
@@ -1193,7 +1193,7 @@ njs_regexp_string_create(njs_vm_t *vm, n
 }
 
 
-static njs_int_t
+njs_int_t
 njs_regexp_prototype_symbol_replace(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused)
 {
diff -r 286675dcfbc5 -r 69547623da14 src/njs_regexp.h
--- a/src/njs_regexp.h	Thu Feb 02 10:01:26 2023 -0800
+++ b/src/njs_regexp.h	Fri Feb 03 06:41:01 2023 -0800
@@ -21,6 +21,8 @@ njs_int_t njs_regexp_exec(njs_vm_t *vm, 
     njs_value_t *retval);
 njs_int_t njs_regexp_prototype_exec(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused);
+njs_int_t njs_regexp_prototype_symbol_replace(njs_vm_t *vm,
+    njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
 
 njs_int_t njs_regexp_to_string(njs_vm_t *vm, njs_value_t *retval,
     const njs_value_t *regexp);
diff -r 286675dcfbc5 -r 69547623da14 src/njs_string.c
--- a/src/njs_string.c	Thu Feb 02 10:01:26 2023 -0800
+++ b/src/njs_string.c	Fri Feb 03 06:41:01 2023 -0800
@@ -3667,21 +3667,25 @@ exception:
 
 static njs_int_t
 njs_string_prototype_replace(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
+    njs_index_t replaceAll)
 {
     u_char             *r;
-    size_t             length, size;
+    size_t             length, size, increment, end_of_last_match;
     int64_t            pos;
     njs_int_t          ret;
+    njs_str_t          str;
+    njs_chb_t          chain;
+    njs_bool_t         is_byte_or_ascii_string;
     njs_value_t        *this, *search, *replace;
     njs_value_t        search_lvalue, replace_lvalue, replacer, retval,
                        arguments[3];
-    const u_char       *p;
+    const u_char       *p, *p_start;
     njs_function_t     *func_replace;
     njs_string_prop_t  string, s, ret_string;
 
     static const njs_value_t  replace_key =
                                       njs_wellknown_symbol(NJS_SYMBOL_REPLACE);
+    static const njs_value_t  string_flags = njs_string("flags");
 
     this = njs_argument(args, 0);
 
@@ -3702,8 +3706,35 @@ njs_string_prototype_replace(njs_vm_t *v
         }
 
         if (njs_is_defined(&replacer)) {
-            arguments[0] = *this;
-            arguments[1] = *replace;
+            njs_value_assign(&arguments[0], this);
+            njs_value_assign(&arguments[1], replace);
+
+            if (replaceAll && njs_function(&replacer)->native
+                && njs_function(&replacer)->u.native ==
+                njs_regexp_prototype_symbol_replace)
+            {
+                ret = njs_value_property(vm, search,
+                                         njs_value_arg(&string_flags), &retval);
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    return NJS_ERROR;
+                }
+
+                ret = njs_value_to_string(vm, &retval, &retval);
+                if (njs_slow_path(ret != NJS_OK)) {
+                    return NJS_ERROR;
+                }
+
+                njs_string_get(&retval, &str);
+
+                if (njs_strlchr(str.start, str.start + str.length, 'g')
+                    == NULL)
+                {
+                    njs_type_error(vm, "String.prototype.replaceAll"
+                                  " called with a non-global RegExp argument",
+                                   njs_type_string(this->type));
+                    return NJS_ERROR;
+                }
+            }
 
             return njs_function_call(vm, njs_function(&replacer), search,
                                      arguments, 2, &vm->retval);
@@ -3727,6 +3758,10 @@ njs_string_prototype_replace(njs_vm_t *v
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
         }
+
+    } else {
+        njs_value_assign(&arguments[0], search);
+        njs_value_assign(&arguments[2], this);
     }
 
     (void) njs_string_prop(&string, this);
@@ -3734,69 +3769,138 @@ njs_string_prototype_replace(njs_vm_t *v
 
     pos = njs_string_index_of(&string, &s, 0);
     if (pos < 0) {
-        vm->retval = *this;
+        njs_value_assign(&vm->retval, this);
         return NJS_OK;
     }
 
-    if (func_replace == NULL) {
-        ret = njs_string_get_substitution(vm, search, this, pos, NULL, 0, NULL,
-                                          replace, &retval);
-        if (njs_slow_path(ret != NJS_OK)) {
-            return ret;
+    if (!replaceAll) {
+
+        if (func_replace == NULL) {
+            ret = njs_string_get_substitution(vm, search, this, pos, NULL, 0,
+                                              NULL, replace, &retval);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+
+        } else {
+            njs_set_number(&arguments[1], pos);
+
+            ret = njs_function_call(vm, func_replace,
+                                    njs_value_arg(&njs_value_undefined),
+                                    arguments, 3, &retval);
+
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+
+            ret = njs_value_to_string(vm, &retval, &retval);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return NJS_ERROR;
+            }
         }
 
-    } else {
-        arguments[0] = *search;
-        njs_set_number(&arguments[1], pos);
-        arguments[2] = *this;
-
-        ret = njs_function_call(vm, func_replace,
-                                njs_value_arg(&njs_value_undefined),
-                                arguments, 3, &retval);
-
-        if (njs_slow_path(ret != NJS_OK)) {
-            return ret;
+        if (njs_is_byte_or_ascii_string(&string)) {
+            p = string.start + pos;
+
+        } else {
+            /* UTF-8 string. */
+            p = njs_string_offset(string.start, string.start + string.size,
+                                  pos);
         }
 
-        ret = njs_value_to_string(vm, &retval, &retval);
-        if (njs_slow_path(ret != NJS_OK)) {
+        (void) njs_string_prop(&ret_string, &retval);
+
+        size = string.size + ret_string.size - s.size;
+        length = string.length + ret_string.length - s.length;
+
+        if (njs_is_byte_string(&string)
+            || njs_is_byte_string(&s)
+            || njs_is_byte_string(&ret_string))
+        {
+            length = 0;
+        }
+
+        r = njs_string_alloc(vm, &vm->retval, size, length);
+        if (njs_slow_path(r == NULL)) {
             return NJS_ERROR;
         }
-    }
-
-    if (njs_is_byte_or_ascii_string(&string)) {
-        p = string.start + pos;
-
-    } else {
-        /* UTF-8 string. */
-        p = njs_string_offset(string.start, string.start + string.size, pos);
-    }
-
-    (void) njs_string_prop(&ret_string, &retval);
-
-    size = string.size + ret_string.size - s.size;
-    length = string.length + ret_string.length - s.length;
-
-    if (njs_is_byte_string(&string)
-        || njs_is_byte_string(&s)
-        || njs_is_byte_string(&ret_string))
-    {
-        length = 0;
-    }
-
-    r = njs_string_alloc(vm, &vm->retval, size, length);
-    if (njs_slow_path(r == NULL)) {
-        return NJS_ERROR;
-    }
-
-    r = njs_cpymem(r, string.start, p - string.start);
-    r = njs_cpymem(r, ret_string.start, ret_string.size);
-    memcpy(r, p + s.size, string.size - s.size - (p - string.start));
-
-    return NJS_OK;
+
+        r = njs_cpymem(r, string.start, p - string.start);
+        r = njs_cpymem(r, ret_string.start, ret_string.size);
+        memcpy(r, p + s.size, string.size - s.size - (p - string.start));
+
+        return NJS_OK;
+    }
+
+    njs_chb_init(&chain, vm->mem_pool);
+
+    p_start = string.start;
+    increment = s.length != 0 ? s.length : 1;
+    is_byte_or_ascii_string = njs_is_byte_or_ascii_string(&string);
+
+    do {
+        if (func_replace == NULL) {
+            ret = njs_string_get_substitution(vm, search, this, pos, NULL, 0,
+                                              NULL, replace, &retval);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+
+        } else {
+            njs_set_number(&arguments[1], pos);
+
+            ret = njs_function_call(vm, func_replace,
+                                    njs_value_arg(&njs_value_undefined),
+                                    arguments, 3, &retval);
+
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+
+            ret = njs_value_to_string(vm, &retval, &retval);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return NJS_ERROR;
+            }
+        }
+
+        if (is_byte_or_ascii_string) {
+            p = string.start + pos;
+
+        } else {
+            /* UTF-8 string. */
+            p = njs_string_offset(string.start, string.start + string.size,
+                                  pos);
+        }
+
+        (void) njs_string_prop(&ret_string, &retval);
+
+        njs_chb_append(&chain, p_start, p - p_start);
+        njs_chb_append(&chain, ret_string.start, ret_string.size);
+
+        p_start = p + s.size;
+        end_of_last_match = pos + increment;
+
+        pos = njs_string_index_of(&string, &s, end_of_last_match);
+
+    } while (pos >= 0 && end_of_last_match <= string.length);
+
+    njs_chb_append(&chain, p_start, string.start + string.size - p_start);
+
+    ret = njs_string_create_chb(vm, &vm->retval, &chain);
+    if (njs_slow_path(ret != NJS_OK)) {
+        ret = NJS_ERROR;
+        goto exception;
+    }
+
+exception:
+
+    njs_chb_destroy(&chain);
+
+    return ret;
 }
 
 
+
 static njs_int_t
 njs_string_prototype_iterator_obj(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t kind)
@@ -4090,6 +4194,8 @@ static const njs_object_prop_t  njs_stri
 
     NJS_DECLARE_PROP_NATIVE("replace", njs_string_prototype_replace, 2, 0),
 
+    NJS_DECLARE_PROP_NATIVE("replaceAll", njs_string_prototype_replace, 2, 1),
+
     {
         .type = NJS_PROPERTY,
         .name = njs_wellknown_symbol(NJS_SYMBOL_ITERATOR),
diff -r 286675dcfbc5 -r 69547623da14 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Thu Feb 02 10:01:26 2023 -0800
+++ b/src/test/njs_unit_test.c	Fri Feb 03 06:41:01 2023 -0800
@@ -8750,198 +8750,397 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("'abc'.replace()"),
       njs_str("abc") },
 
+    { njs_str("'abc'.replaceAll()"),
+      njs_str("abc") },
+
     { njs_str("'ABC'.replace('B')"),
       njs_str("AundefinedC") },
 
+    { njs_str("'ABCB'.replaceAll('B')"),
+      njs_str("AundefinedCundefined") },
+
     { njs_str("'ABC'.replace('B', undefined)"),
       njs_str("AundefinedC") },
 
+    { njs_str("'ABBC'.replaceAll('B', undefined)"),
+      njs_str("AundefinedundefinedC") },
+
     { njs_str("'a'.repeat(16).replace('a'.repeat(17)) === 'a'.repeat(16)"),
       njs_str("true") },
 
+    { njs_str("'a'.repeat(16).replaceAll('a'.repeat(17)) === 'a'.repeat(16)"),
+      njs_str("true") },
+
     { njs_str("'α'.repeat(16).replace('α'.repeat(17)) === 'α'.repeat(16)"),
       njs_str("true") },
 
+    { njs_str("'α'.repeat(16).replaceAll('α'.repeat(17)) === 'α'.repeat(16)"),
+      njs_str("true") },
+
     { njs_str("'ABC'.replace('B', null)"),
       njs_str("AnullC") },
 
+    { njs_str("'BABCB'.replaceAll('B', null)"),
+      njs_str("nullAnullCnull") },
+
     { njs_str("'abc'.replace('c', 1)"),
       njs_str("ab1") },
 
+    { njs_str("'cacbc'.replaceAll('c', 1)"),
+      njs_str("1a1b1") },
+
     { njs_str("'abc'.replace('a', 'X')"),
       njs_str("Xbc") },
 
+    { njs_str("'cabac'.replaceAll('a', 'X')"),
+      njs_str("cXbXc") },
+
     { njs_str("'abc'.replace('b', 'X')"),
       njs_str("aXc") },
 
+    { njs_str("'abc'.replaceAll('b', 'X')"),
+      njs_str("aXc") },
+
     { njs_str("('a'.repeat(33) + 'bb').replace('bb', 'CC').slice(31)"),
       njs_str("aaCC") },
 
+    { njs_str("('a'.repeat(33) + 'bb').replaceAll('b', 'C').slice(31)"),
+      njs_str("aaCC") },
+
     { njs_str("var r = 'abc'.replace('c', 'X'); [r, r.length]"),
       njs_str("abX,3") },
 
+    { njs_str("var r = 'acbc'.replaceAll('c', 'X'); [r, r.length]"),
+      njs_str("aXbX,4") },
+
     { njs_str("var r = 'αβγ'.replace('α', 'X'); [r, r.length]"),
       njs_str("Xβγ,3") },
 
+    { njs_str("var r = 'αβαγα'.replaceAll('α', 'X'); [r, r.length]"),
+      njs_str("XβXγX,5") },
+
     { njs_str("var r = 'αβγ'.replace('β', 'X'); [r, r.length]"),
       njs_str("αXγ,3") },
 
+    { njs_str("var r = 'αβγ'.replaceAll('β', 'X'); [r, r.length]"),
+      njs_str("αXγ,3") },
+
     { njs_str("var r = 'αβγ'.replace('γ', 'X'); [r, r.length]"),
       njs_str("αβX,3") },
 
+    { njs_str("var r = 'αβγγ'.replaceAll('γ', 'X'); [r, r.length]"),
+      njs_str("αβXX,4") },
+
     { njs_str("var r = 'αβγ'.replace('', 'X'); [r, r.length]"),
       njs_str("Xαβγ,4") },
 
+    { njs_str("var r = 'αβγ'.replaceAll('', 'X'); [r, r.length]"),
+      njs_str("XαXβXγX,7") },
+
     { njs_str("var s = 'αz'.toUTF8();"
               "var r = s.replace('z', 'β');"
               "r.length"),
       njs_str("4") },
 
+    { njs_str("var s = 'αzz'.toUTF8();"
+              "var r = s.replaceAll('z', 'β');"
+              "r.length"),
+      njs_str("3") },
+
     { njs_str("'abc'.replace('b', (m, o, s) => `|${s}|${o}|${m}|`)"),
       njs_str("a|abc|1|b|c") },
 
+    { njs_str("'abcb'.replaceAll('b', (m, o, s) => `|${s}|${o}|${m}|`)"),
+      njs_str("a|abcb|1|b|c|abcb|3|b|") },
+
     { njs_str("'abcdbe'.replace('b', '|$`X$\\'|')"),
       njs_str("a|aXcdbe|cdbe") },
 
+    { njs_str("'abcdbe'.replaceAll('b', '|$`X$\\'|')"),
+      njs_str("a|aXcdbe|cd|abcdXe|e") },
+
+
     { njs_str("'ABC'.replace('B', '$<g>')"),
       njs_str("A$<g>C") },
 
+    { njs_str("'ABCB'.replaceAll('B', '$<g>')"),
+      njs_str("A$<g>C$<g>") },
+
     { njs_str("'ABC'.replace('B', '$23')"),
       njs_str("A$23C") },
 
+    { njs_str("'ABBC'.replaceAll('B', '$23')"),
+      njs_str("A$23$23C") },
+
     { njs_str("'undefined'.replace(void 0, 'x')"),
       njs_str("x") },
 
+    { njs_str("'undefinedundefined'.replaceAll(void 0, 'x')"),
+      njs_str("xx") },
+
     { njs_str("'12345'.replace(3, () => 0)"),
       njs_str("12045") },
 
+    { njs_str("'123435'.replaceAll(3, () => 0)"),
+      njs_str("120405") },
+
     { njs_str("var r = new String('undefined').replace(x, Function('return arguments[1]+42;')); var x; r"),
       njs_str("42") },
 
+    { njs_str("var r = new String('undefined undefined').replaceAll(x, Function('return arguments[1]+42;')); var x; r"),
+      njs_str("42 52") },
+
     { njs_str("'123'.replace(3, function() { return {toString: ()=>({})}; })"),
       njs_str("TypeError: Cannot convert object to primitive value") },
 
+    { njs_str("'123'.replaceAll(3, function() { return {toString: ()=>({})}; })"),
+      njs_str("TypeError: Cannot convert object to primitive value") },
+
     { njs_str("'12345'.replace(3, () => ({toString: () => 'aaaa'}))"),
       njs_str("12aaaa45") },
 
+    { njs_str("'123435'.replaceAll(3, () => ({toString: () => 'aaaa'}))"),
+      njs_str("12aaaa4aaaa5") },
+
     { njs_str("'ABC'.replace('B', () => {throw 'OOps'})"),
       njs_str("OOps") },
 
+    { njs_str("'ABCB'.replaceAll('B', () => {throw 'OOps'})"),
+      njs_str("OOps") },
+
     { njs_str("'abc'.replace(/a/, 'X')"),
       njs_str("Xbc") },
 
+    { njs_str("'abc'.replaceAll(/a/, 'X')"),
+      njs_str("TypeError: String.prototype.replaceAll called with a non-global RegExp argument") },
+
+    { njs_str("'abac'.replaceAll(/a/g, 'X')"),
+      njs_str("XbXc") },
+
     { njs_str("'abccd'.replace(/c/, 'X')"),
       njs_str("abXcd") },
 
+    { njs_str("'abccd'.replaceAll(/c/g, 'X')"),
+      njs_str("abXXd") },
+
     { njs_str("'abc'.replace(/c/, 'X')"),
       njs_str("abX") },
 
+    { njs_str("'abc'.replaceAll(/c/g, 'X')"),
+      njs_str("abX") },
+
     { njs_str("'abccd'.replace(/c+/, 'X')"),
       njs_str("abXd") },
 
+    { njs_str("'acccbccd'.replaceAll(/c+/g, 'X')"),
+      njs_str("aXbXd") },
+
     { njs_str("'abc'.replace(/f/, 'X')"),
       njs_str("abc") },
 
+    { njs_str("'abc'.replaceAll(/f/g, 'X')"),
+      njs_str("abc") },
+
     { njs_str("'AB=C==='.replace(/=*$/, '')"),
       njs_str("AB=C") },
 
+    { njs_str("'AB=C==='.replaceAll(/=*$/g, '')"),
+      njs_str("AB=C") },
+
     { njs_str("('a'.repeat(33) + 'bb').replace(/bb/, 'CC').slice(31)"),
       njs_str("aaCC") },
 
+    { njs_str("('a'.repeat(33) + 'bb').replaceAll(/b/g, 'C').slice(31)"),
+      njs_str("aaCC") },
+
     { njs_str("'abccd'.replace(/c/g, 'X')"),
       njs_str("abXXd") },
 
+    { njs_str("'abccd'.replaceAll(/c/g, 'X')"),
+      njs_str("abXXd") },
+
     { njs_str("('a'.repeat(33) + 'bb').replace(/bb/g, 'CC').slice(31)"),
       njs_str("aaCC") },
 
+    { njs_str("('a'.repeat(33) + 'bb').replaceAll(/bb/g, 'CC').slice(31)"),
+      njs_str("aaCC") },
+
     { njs_str("'abccd'.replace(/[ac]/g, 'X')"),
       njs_str("XbXXd") },
 
+    { njs_str("'abccd'.replaceAll(/[ac]/g, 'X')"),
+      njs_str("XbXXd") },
+
     { njs_str("'ab'.replace(/q*/g, 'X')"),
       njs_str("XaXbX") },
 
+    { njs_str("'ab'.replaceAll(/q*/g, 'X')"),
+      njs_str("XaXbX") },
+
     { njs_str("'αβ'.replace(/q*/g, 'X')"),
       njs_str("XαXβX") },
 
+    { njs_str("'αβ'.replaceAll(/q*/g, 'X')"),
+      njs_str("XαXβX") },
+
     { njs_str("'αβ'.replace(/(q)*/g, 'X')"),
       njs_str("XαXβX") },
 
+    { njs_str("'αβ'.replaceAll(/(q)*/g, 'X')"),
+      njs_str("XαXβX") },
+
     { njs_str("'αβ'.replace(/q*/g, 'γ')"),
       njs_str("γαγβγ") },
 
+    { njs_str("'αβ'.replaceAll(/q*/g, 'γ')"),
+      njs_str("γαγβγ") },
+
     { njs_str("':α:β:γ:'.replace(/:/g, '')"),
       njs_str("αβγ") },
 
+    { njs_str("':α:β:γ:'.replaceAll(/:/g, '')"),
+      njs_str("αβγ") },
+
     { njs_str("':α:β:γ:'.replace(/[αβγ]/g, '')"),
       njs_str("::::") },
 
+    { njs_str("':α:β:γ:'.replaceAll(/[αβγ]/g, '')"),
+      njs_str("::::") },
+
     { njs_str("'aabbccaa'.replace(/a*/g, '')"),
       njs_str("bbcc") },
 
+    { njs_str("'aabbccaa'.replaceAll(/a*/g, '')"),
+      njs_str("bbcc") },
+
     { njs_str("'aabbccaab'.replace(/z*/g, '')"),
       njs_str("aabbccaab") },
 
+    { njs_str("'aabbccaab'.replaceAll(/z*/g, '')"),
+      njs_str("aabbccaab") },
+
     { njs_str("''.replace(/a*/g, '')"),
       njs_str("") },
 
+    { njs_str("''.replaceAll(/a*/g, '')"),
+      njs_str("") },
+
     { njs_str("'abcde'.replace(/d/, (m, o, s) => `|${s}|${o}|${m}|`)"),
       njs_str("abc|abcde|3|d|e") },
 
+    { njs_str("'abcdde'.replaceAll(/d/g, (m, o, s) => `|${s}|${o}|${m}|`)"),
+      njs_str("abc|abcdde|3|d||abcdde|4|d|e") },
+
     { njs_str("'abcde'.replace(/(d)/, (m, p, o, s) => `|${s}|${o}|${m}|${p}|`)"),
       njs_str("abc|abcde|3|d|d|e") },
 
+    { njs_str("'abcded'.replaceAll(/(d)/g, (m, p, o, s) => `|${s}|${o}|${m}|${p}|`)"),
+      njs_str("abc|abcded|3|d|d|e|abcded|5|d|d|") },
+
     { njs_str("'abc'.replace(/b/, () => 1)"),
       njs_str("a1c") },
 
+    { njs_str("'abcb'.replaceAll(/b/g, () => 1)"),
+      njs_str("a1c1") },
+
     { njs_str("var n = 0; 'abbbc'.replace(/b/g, () => ++n)"),
       njs_str("a123c") },
 
+    { njs_str("var n = 0; 'abbbc'.replaceAll(/b/g, () => ++n)"),
+      njs_str("a123c") },
+
     { njs_str("'abc'.replace(/x/, (m, o, s) => `|${s}|${o}|${m}|`)"),
       njs_str("abc") },
 
+    { njs_str("'abc'.replaceAll(/x/g, (m, o, s) => `|${s}|${o}|${m}|`)"),
+      njs_str("abc") },
+
     { njs_str("'abc12345#$*%'.replace(/([^\\d]*)(\\d*)([^\\w]*)/,"
               "                       (_, p1, p2, p3) => [p1, p2, p3].join('-'))"),
       njs_str("abc-12345-#$*%") },
 
+    { njs_str("'abc12345#$*%'.replaceAll(/([^\\d]*)(\\d*)([^\\w]*)/g,"
+              "                       (_, p1, p2, p3) => [p1, p2, p3].join('-'))"),
+      njs_str("abc-12345-#$*%--") },
+
     { njs_str("'abc'.replace(/(?<named>b)/, (m, p, o, s, gr) => `|${gr.named}|`)"),
       njs_str("a|b|c") },
 
+    { njs_str("'abcb'.replaceAll(/(?<named>b)/g, (m, p, o, s, gr) => `|${gr.named}|`)"),
+      njs_str("a|b|c|b|") },
+
     { njs_str("'ABC'.replace(/[A-Z]/g, m => '-' + m.toLowerCase())"),
       njs_str("-a-b-c") },
 
+    { njs_str("'ABC'.replaceAll(/[A-Z]/g, m => '-' + m.toLowerCase())"),
+      njs_str("-a-b-c") },
+
     { njs_str("'abc'.replace(/(b)c/g, '|$01|')"),
       njs_str("a|b|") },
 
+    { njs_str("'abc'.replaceAll(/(b)c/g, '|$01|')"),
+      njs_str("a|b|") },
+
     { njs_str("'abc'.replace(/(b)c/g, '@$0|$01|$00@')"),
       njs_str("a@$0|b|$00@") },
 
+    { njs_str("'abc'.replaceAll(/(b)c/g, '@$0|$01|$00@')"),
+      njs_str("a@$0|b|$00@") },
+
     { njs_str("'abcdeFGHIJ'.replace(/(a)(b)(c)(d)(e)(F)(G)(H)(I)(J)/, '$9|$10|$11|$01')"),
       njs_str("I|J|a1|a") },
 
+    { njs_str("'abcdeFGHIJ abcdeFGHIJ'.replaceAll(/(a)(b)(c)(d)(e)(F)(G)(H)(I)(J)/g, '$9|$10|$11|$01')"),
+      njs_str("I|J|a1|a I|J|a1|a") },
+
     { njs_str("'abcdbe'.replace(/(b)/g, '$2$23')"),
       njs_str("a$2$23cd$2$23e") },
 
+    { njs_str("'abcdbe'.replaceAll(/(b)/g, '$2$23')"),
+      njs_str("a$2$23cd$2$23e") },
+
     { njs_str("'abcdbe'.replace(/(b)/g, '$2$23X$$Y')"),
       njs_str("a$2$23X$Ycd$2$23X$Ye") },
 
+    { njs_str("'abcdbe'.replaceAll(/(b)/g, '$2$23X$$Y')"),
+      njs_str("a$2$23X$Ycd$2$23X$Ye") },
+
     { njs_str("'abcdbe'.replace(/b/, '|$`X$\\'|')"),
       njs_str("a|aXcdbe|cdbe") },
 
+    { njs_str("'abcdbe'.replaceAll(/b/g, '|$`X$\\'|')"),
+      njs_str("a|aXcdbe|cd|abcdXe|e") },
+
     { njs_str("'abcdbefbgh'.replace(/b/g, '|$`X$\\'|')"),
       njs_str("a|aXcdbefbgh|cd|abcdXefbgh|ef|abcdbefXgh|gh") },
 
+    { njs_str("'abcdbefbgh'.replaceAll(/b/g, '|$`X$\\'|')"),
+      njs_str("a|aXcdbefbgh|cd|abcdXefbgh|ef|abcdbefXgh|gh") },
+
     { njs_str("'abc12345#$*%'.replace(/([^\\d]*)(\\d*)([^\\w]*)/, '$1-$2-$3')"),
       njs_str("abc-12345-#$*%") },
 
+    { njs_str("'abc12345#$*%'.replaceAll(/([^\\d]*)(\\d*)([^\\w]*)/g, '$1-$2-$3')"),
+      njs_str("abc-12345-#$*%--") },
+
     { njs_str("'$1,$2'.replace(/(\\$(\\d))/g, '$$1-$1$2')"),
       njs_str("$1-$11,$1-$22") },
 
+    { njs_str("'$1,$2'.replaceAll(/(\\$(\\d))/g, '$$1-$1$2')"),
+      njs_str("$1-$11,$1-$22") },
+
     { njs_str("'ABC'.replace(/(h*)(z*)(g*)/g, '$1@$2α$3')"),
       njs_str("@αA@αB@αC@α") },
 
+    { njs_str("'ABC'.replaceAll(/(h*)(z*)(g*)/g, '$1@$2α$3')"),
+      njs_str("@αA@αB@αC@α") },
+
     { njs_str("'abc'.replace(/(h*)(z*)/g, '$1@$2#$3:')"),
       njs_str("@#$3:a@#$3:b@#$3:c@#$3:") },
 
+    { njs_str("'abc'.replaceAll(/(h*)(z*)/g, '$1@$2#$3:')"),
+      njs_str("@#$3:a@#$3:b@#$3:c@#$3:") },
+
     { njs_str("/b(c)(z)?(.)/[Symbol.replace]('abcde', '[$1$2$3]')"),
       njs_str("a[cd]e") },
 
@@ -8963,60 +9162,117 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("'α'.replace(/(h*)/g, '$1βγ')"),
       njs_str("βγαβγ") },
 
+    { njs_str("'α'.replaceAll(/(h*)/g, '$1βγ')"),
+      njs_str("βγαβγ") },
+
     { njs_str("'αg'.replace(/(h*)/g, '$1βγ')"),
       njs_str("βγαβγgβγ") },
 
+    { njs_str("'αg'.replaceAll(/(h*)/g, '$1βγ')"),
+      njs_str("βγαβγgβγ") },
+
     { njs_str("'αg'.replace(/(α*)/g, '$1βγ')"),
       njs_str("αβγβγgβγ") },
 
+    { njs_str("'αg'.replaceAll(/(α*)/g, '$1βγ')"),
+      njs_str("αβγβγgβγ") },
+
     { njs_str("'αg'.replace(/(h*)/g, 'fg$1βγ')"),
       njs_str("fgβγαfgβγgfgβγ") },
 
+    { njs_str("'αg'.replaceAll(/(h*)/g, 'fg$1βγ')"),
+      njs_str("fgβγαfgβγgfgβγ") },
+
     { njs_str("'αgβfγ'.replace(/(gβ)/g, 'n$1i')"),
       njs_str("αngβifγ") },
 
+    { njs_str("'αgβfγ'.replaceAll(/(gβ)/g, 'n$1i')"),
+      njs_str("αngβifγ") },
+
     { njs_str("'abc'.replace(/b/g, '|$&|')"),
       njs_str("a|b|c") },
 
+    { njs_str("'abc'.replaceAll(/b/g, '|$&|')"),
+      njs_str("a|b|c") },
+
     { njs_str("'ABC'.replace(/((A)B)/g, '($1|$&|$2)')"),
       njs_str("(AB|AB|A)C") },
 
+    { njs_str("'ABC'.replaceAll(/((A)B)/g, '($1|$&|$2)')"),
+      njs_str("(AB|AB|A)C") },
+
     { njs_str("'abc'.replace(/b/g, '$0')"),
       njs_str("a$0c") },
 
+    { njs_str("'abc'.replaceAll(/b/g, '$0')"),
+      njs_str("a$0c") },
+
     { njs_str("'abc'.replace(/^/g, '|$&|')"),
       njs_str("||abc") },
 
+    { njs_str("'abc'.replaceAll(/^/g, '|$&|')"),
+      njs_str("||abc") },
+
     { njs_str("var uri ='/u/v1/Aa/bB?type=m3u8&mt=42';"
               "uri.replace(/^\\/u\\/v1\\/[^/]*\\/([^\?]*)\\?.*(mt=[^&]*).*$/, '$1|$2')"),
       njs_str("bB|mt=42") },
 
+    { njs_str("var uri ='/u/v1/Aa/bB?type=m3u8&mt=42 /u/v1/Aa/bB?type=m3u8&mt=43';"
+              "uri.replaceAll(/^\\/u\\/v1\\/[^/]*\\/([^\?]*)\\?.*(mt=[^&]*).*$/g, '$1|$2')"),
+      njs_str("bB|mt=43") },
+
     { njs_str("'ABC'.replace(/B/, '$<g>')"),
       njs_str("A$<g>C") },
 
+    { njs_str("'ABBC'.replaceAll(/B/g, '$<g>')"),
+      njs_str("A$<g>$<g>C") },
+
     { njs_str("'ABC'.replace(/(?<b>B)/, '|$<b>|@$<a>@')"),
       njs_str("A|B|@@C") },
 
+    { njs_str("'ABBC'.replaceAll(/(?<b>B)/g, '|$<b>|@$<a>@')"),
+      njs_str("A|B|@@|B|@@C") },
+
     { njs_str("'ABC'.replace(/(?<b>B)/, '|$<BB|')"),
       njs_str("A|$<BB|C") },
 
+    { njs_str("'ABCB'.replaceAll(/(?<b>B)/g, '|$<BB|')"),
+      njs_str("A|$<BB|C|$<BB|") },
+
     { njs_str("'ABC'.replace(/(?<b>B)/, '|$<BB$$|>@')"),
       njs_str("A|@C") },
 
+    { njs_str("'ABCB'.replaceAll(/(?<b>B)/g, '|$<BB$$|>@')"),
+      njs_str("A|@C|@") },
+
     { njs_str("('β' + 'α'.repeat(33)+'β').replace(/(α+)(β+)/, (m, p1) => p1[32])"),
       njs_str("βα") },
 
+    { njs_str("('β' + 'α'.repeat(33)+'β').replaceAll(/(α+)(β+)/g, (m, p1) => p1[32])"),
+      njs_str("βα") },
+
     { njs_str("'abc'.replace(/(z*)/g, () => '@')"),
       njs_str("@a at b@c@") },
 
+    { njs_str("'abc'.replaceAll(/(z*)/g, () => '@')"),
+      njs_str("@a at b@c@") },
+
     { njs_str("'abc'.replace(/(a*)/g, () => '@')"),
       njs_str("@@b at c@") },
 
+    { njs_str("'abc'.replaceAll(/(a*)/g, () => '@')"),
+      njs_str("@@b at c@") },
+
     { njs_str("var O = RegExp.prototype[Symbol.replace];"
               "RegExp.prototype[Symbol.replace] = function (s, rep) { return O.call(this, s, `|${rep}|`); };"
               "'ABC'.replace(/B/, '+')"),
       njs_str("A|+|C") },
 
+    { njs_str("var O = RegExp.prototype[Symbol.replace];"
+              "RegExp.prototype[Symbol.replace] = function (s, rep) { return O.call(this, s, `|${rep}|`); };"
+              "'ABC'.replaceAll(/B/g, '+')"),
+      njs_str("A|+|C") },
+
     { njs_str("var O = RegExp.prototype.exec;"
               "function mangled(s) { var r = O.call(this, s);"
               "                      Object.defineProperty(r, '0', {enumerable:false}); "
@@ -9027,6 +9283,14 @@ static njs_unit_test_t  njs_test[] =
 
     { njs_str("var O = RegExp.prototype.exec;"
               "function mangled(s) { var r = O.call(this, s);"
+              "                      Object.defineProperty(r, '0', {enumerable:false}); "
+              "                      return r; };"
+              "RegExp.prototype.exec = mangled;"
+              "'ABC'.replaceAll(/(B)/g, (m, p1, off, s) => `@${m}|${p1}|${off}|${s}@`)"),
+      njs_str("TypeError: Object.defineProperty is called on non-object") },
+
+    { njs_str("var O = RegExp.prototype.exec;"
+              "function mangled(s) { var r = O.call(this, s);"
               "                      Object.defineProperty(r, 'groups', {value: {g:1}}); "
               "                      return r; };"
               "RegExp.prototype.exec = mangled;"
@@ -9035,12 +9299,28 @@ static njs_unit_test_t  njs_test[] =
 
     { njs_str("var O = RegExp.prototype.exec;"
               "function mangled(s) { var r = O.call(this, s);"
+              "                      Object.defineProperty(r, 'groups', {value: {g:1}}); "
+              "                      return r; };"
+              "RegExp.prototype.exec = mangled;"
+              "'ABC'.replaceAll(/(B)/g, '$<g>')"),
+      njs_str("TypeError: Object.defineProperty is called on non-object") },
+
+    { njs_str("var O = RegExp.prototype.exec;"
+              "function mangled(s) { var r = O.call(this, s);"
               "                      Object.defineProperty(r, 'groups', {value: {get g() {throw 'OOps'}}}); "
               "                      return r; };"
               "RegExp.prototype.exec = mangled;"
               "'ABC'.replace(/(B)/, '$<g>')"),
       njs_str("OOps") },
 
+    { njs_str("var O = RegExp.prototype.exec;"
+              "function mangled(s) { var r = O.call(this, s);"
+              "                      Object.defineProperty(r, 'groups', {value: {get g() {throw 'OOps'}}}); "
+              "                      return r; };"
+              "RegExp.prototype.exec = mangled;"
+              "'ABC'.replaceAll(/(B)/g, '$<g>')"),
+      njs_str("TypeError: Object.defineProperty is called on non-object") },
+
     { njs_str("var name = /a/g[Symbol.replace].name; [name, typeof name]"),
       njs_str("[Symbol.replace],string") },
 
@@ -9077,6 +9357,17 @@ static njs_unit_test_t  njs_test[] =
               "'any_string'.replace(re)"),
       njs_str("undefinedg") },
 
+    { njs_str("var cnt = 0;"
+              "var a = [];"
+              "a[2] = '';"
+              "var re = /any_regexp/g;"
+              "re.exec = function () {"
+              "    if (cnt++ > 1) return null;"
+              "    return a;"
+              "};"
+              "'any_string'.replaceAll(re)"),
+      njs_str("undefinedg") },
+
     { njs_str("var a = [];"
               "a[2] = {toString() {a[2**20] = 1; return 'X';}}; "
               "a[4] = 'Y';"
@@ -9100,6 +9391,18 @@ static njs_unit_test_t  njs_test[] =
               "'abc'.replace(re, '@$1|$2|$3|$4|$99|$100|@')"),
       njs_str("@|X||Y|Z|0|@") },
 
+    { njs_str("var cnt = 0;"
+              "var a = [];"
+              "a[2] = {toString() {a[2**20] = 1; return 'X';}}; "
+              "a[4] = 'Y';"
+              "a[99] = 'Z';"
+              "a[100] = '*';"
+              "a[200] = '!';"
+              "var re = /b/g;"
+              "re.exec = () => {if (cnt++ > 1) return null; return a};"
+              "'abc'.replaceAll(re, '@$1|$2|$3|$4|$99|$100|@')"),
+      njs_str("@|X||Y|Z|0|@") },
+
     { njs_str("var a = [];"
               "Object.defineProperty(a, 32768, {});"
               "var re = /any_regexp/;"
@@ -9120,6 +9423,22 @@ static njs_unit_test_t  njs_test[] =
               "'any_string'.replace(re)"),
       njs_str("undefinedg") },
 
+    { njs_str("var cnt = 0;"
+              "var a = [];"
+              "Object.defineProperty(a, 32768, {});"
+              "var re = /any_regexp/g;"
+              "re.exec = function () {"
+              "    if (cnt++ > 1) return null;"
+              "    return a;"
+              "};"
+              "'any_string'.replace(re)"),
+      njs_str("undefinedg") },
+
+    { njs_str("var r = /h/g;"
+              "Object.defineProperty(r,'flags',{value: ''});"
+              "''.replaceAll(r,'');"),
+      njs_str("TypeError: String.prototype.replaceAll called with a non-global RegExp argument") },
+
     { njs_str("/=/"),
       njs_str("/=/") },
 


More information about the nginx-devel mailing list