From: Dmitry Volyntsev Date: Thu, 2 Jul 2020 14:00:16 +0000 (+0000) Subject: Fixed String.prototype.replace() according to the specification. X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=04fea5ccd24a0b1db19ffc5d255d7ddc6ea7c944;p=njs.git Fixed String.prototype.replace() according to the specification. This closes #308 issue on Github. --- diff --git a/src/njs_regexp.c b/src/njs_regexp.c index fbed0fd5..374610e4 100644 --- a/src/njs_regexp.c +++ b/src/njs_regexp.c @@ -1161,6 +1161,314 @@ njs_regexp_string_create(njs_vm_t *vm, njs_value_t *value, u_char *start, } +static njs_int_t +njs_regexp_prototype_symbol_replace(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + u_char *p; + int64_t n, last_index, ncaptures, pos, next_pos, size, length; + njs_str_t rep, m; + njs_int_t ret; + njs_arr_t results; + njs_chb_t chain; + njs_uint_t i; + njs_bool_t global; + njs_array_t *array; + njs_value_t *arguments, *r, *rx, *string, *replace; + njs_value_t s_lvalue, r_lvalue, value, matched, groups, retval; + njs_function_t *func_replace; + njs_string_prop_t s; + + static const njs_value_t string_global = njs_string("global"); + static const njs_value_t string_groups = njs_string("groups"); + static const njs_value_t string_index = njs_string("index"); + static const njs_value_t string_lindex = njs_string("lastIndex"); + + rx = njs_argument(args, 0); + + if (njs_slow_path(!njs_is_object(rx))) { + njs_type_error(vm, "\"this\" is not object"); + return NJS_ERROR; + } + + string = njs_lvalue_arg(&s_lvalue, args, nargs, 1); + + ret = njs_value_to_string(vm, string, string); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + length = njs_string_prop(&s, string); + + rep.start = NULL; + rep.length = 0; + + replace = njs_lvalue_arg(&r_lvalue, args, nargs, 2); + func_replace = njs_is_function(replace) ? njs_function(replace) : NULL; + + if (!func_replace) { + ret = njs_value_to_string(vm, replace, replace); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + ret = njs_value_property(vm, rx, njs_value_arg(&string_global), &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + global = njs_bool(&value); + + if (global) { + njs_set_number(&value, 0); + ret = njs_value_property_set(vm, rx, njs_value_arg(&string_lindex), + &value); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } + + njs_chb_init(&chain, vm->mem_pool); + + results.separate = 0; + results.pointer = 0; + + r = njs_arr_init(vm->mem_pool, &results, NULL, 4, sizeof(njs_value_t)); + if (njs_slow_path(r == NULL)) { + return NJS_ERROR; + } + + for ( ;; ) { + r = njs_arr_add(&results); + if (njs_slow_path(r == NULL)) { + ret = NJS_ERROR; + goto exception; + } + + ret = njs_regexp_exec(vm, rx, string, r); + if (njs_slow_path(ret != NJS_OK)) { + goto exception; + } + + if (njs_is_null(r) || !global) { + break; + } + + if (njs_fast_path(njs_is_fast_array(r) && njs_array_len(r) != 0)) { + value = njs_array_start(r)[0]; + + } else { + ret = njs_value_property_i64(vm, r, 0, &value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + } + + ret = njs_value_to_string(vm, &value, &value); + if (njs_slow_path(ret != NJS_OK)) { + goto exception; + } + + if (njs_string_length(&value) != 0) { + continue; + } + + ret = njs_value_property(vm, rx, njs_value_arg(&string_lindex), &value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + + ret = njs_value_to_length(vm, &value, &last_index); + if (njs_slow_path(ret != NJS_OK)) { + goto exception; + } + + njs_set_number(&value, last_index + 1); + ret = njs_value_property_set(vm, rx, njs_value_arg(&string_lindex), + &value); + if (njs_slow_path(ret != NJS_OK)) { + goto exception; + } + } + + i = 0; + next_pos = 0; + + while (i < results.items) { + r = njs_arr_item(&results, i++); + + if (njs_slow_path(njs_is_null(r))) { + break; + } + + ret = njs_value_property_i64(vm, r, 0, &matched); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + + ret = njs_value_to_string(vm, &matched, &matched); + if (njs_slow_path(ret != NJS_OK)) { + goto exception; + } + + ret = njs_value_property(vm, r, njs_value_arg(&string_index), &value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + + ret = njs_value_to_integer(vm, &value, &pos); + if (njs_slow_path(ret != NJS_OK)) { + goto exception; + } + + if ((size_t) length != s.size) { + /* UTF-8 string. */ + pos = njs_string_offset(s.start, s.start + s.size, pos) - s.start; + } + + pos = njs_max(njs_min(pos, (int64_t) s.size), 0); + + if (njs_fast_path(njs_is_fast_array(r))) { + array = njs_array(r); + + arguments = array->start; + ncaptures = array->length; + + for (n = 1; n < ncaptures; n++) { + if (njs_is_undefined(&arguments[n])) { + continue; + } + + ret = njs_value_to_string(vm, &arguments[n], &arguments[n]); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + } + + } else { + ret = njs_object_length(vm, r, &ncaptures); + if (njs_slow_path(ret != NJS_OK)) { + goto exception; + } + + array = njs_array_alloc(vm, 0, ncaptures, 0); + if (njs_slow_path(array == NULL)) { + goto exception; + } + + arguments = array->start; + arguments[0] = matched; + + for (n = 1; n < ncaptures; n++) { + ret = njs_value_property_i64(vm, r, n, &arguments[n]); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + + if (njs_is_undefined(&arguments[n])) { + continue; + } + + ret = njs_value_to_string(vm, &arguments[n], &arguments[n]); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + } + } + + ret = njs_value_property(vm, r, njs_value_arg(&string_groups), &groups); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } + + if (!func_replace) { + if (njs_is_defined(&groups)) { + ret = njs_value_to_object(vm, &groups); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + ret = njs_string_get_substitution(vm, &matched, string, pos, + arguments, ncaptures - 1, &groups, + replace, &retval); + + } else { + ret = njs_array_expand(vm, array, 0, + njs_is_defined(&groups) ? 3 : 2); + if (njs_slow_path(ret != NJS_OK)) { + goto exception; + } + + arguments = array->start; + njs_set_number(&arguments[n++], pos); + arguments[n++] = *string; + + if (njs_is_defined(&groups)) { + arguments[n++] = groups; + } + + ret = njs_function_call(vm, func_replace, + njs_value_arg(&njs_value_undefined), + arguments, n, &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)) { + goto exception; + } + + if (pos >= next_pos) { + njs_chb_append(&chain, &s.start[next_pos], pos - next_pos); + + njs_string_get(&retval, &rep); + njs_chb_append_str(&chain, &rep); + + njs_string_get(&matched, &m); + + next_pos = pos + (int64_t) m.length; + } + } + + if (next_pos < (int64_t) s.size) { + njs_chb_append(&chain, &s.start[next_pos], s.size - next_pos); + } + + size = njs_chb_size(&chain); + if (njs_slow_path(size < 0)) { + njs_memory_error(vm); + ret = NJS_ERROR; + goto exception; + } + + length = njs_chb_utf8_length(&chain); + + p = njs_string_alloc(vm, &vm->retval, size, length); + if (njs_slow_path(p == NULL)) { + ret = NJS_ERROR; + goto exception; + } + + njs_chb_join_to(&chain, p); + + ret = NJS_OK; + +exception: + + njs_chb_destroy(&chain); + njs_arr_destroy(&results); + + return ret; +} + + + + static const njs_object_prop_t njs_regexp_constructor_properties[] = { { @@ -1252,6 +1560,14 @@ static const njs_object_prop_t njs_regexp_prototype_properties[] = .writable = 1, .configurable = 1, }, + + { + .type = NJS_PROPERTY, + .name = njs_wellknown_symbol(NJS_SYMBOL_REPLACE), + .value = njs_native_function(njs_regexp_prototype_symbol_replace, 2), + .writable = 1, + .configurable = 1, + }, }; diff --git a/src/njs_string.c b/src/njs_string.c index ba7e5869..482bfb4e 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -12,44 +12,6 @@ #define NJS_TRIM_END 2 -typedef struct { - u_char *start; - size_t size; - njs_value_t value; -} njs_string_replace_part_t; - - -#define NJS_SUBST_COPY 255 -#define NJS_SUBST_PRECEDING 254 -#define NJS_SUBST_FOLLOWING 253 - - -typedef struct { - uint32_t type; - uint32_t size; - u_char *start; -} njs_string_subst_t; - - -typedef struct { - njs_value_t retval; - - njs_arr_t parts; - njs_string_replace_part_t array[3]; - njs_string_replace_part_t *part; - - njs_arr_t *substitutions; - njs_function_t *function; - - njs_regex_match_data_t *match_data; - - njs_bool_t empty; - - njs_utf8_t utf8:8; - njs_regexp_utf8_t type:8; -} njs_string_replace_t; - - static void njs_encode_base64_core(njs_str_t *dst, const njs_str_t *src, const u_char *basis, njs_uint_t padding); static njs_int_t njs_decode_base64_core(njs_vm_t *vm, @@ -72,25 +34,6 @@ static njs_int_t njs_string_match_multiple(njs_vm_t *vm, njs_value_t *args, njs_regexp_pattern_t *pattern); static njs_int_t njs_string_split_part_add(njs_vm_t *vm, njs_array_t *array, njs_utf8_t utf8, const u_char *start, size_t size); -static njs_int_t njs_string_replace_regexp(njs_vm_t *vm, njs_value_t *this, - njs_value_t *regex, njs_string_replace_t *r); -static njs_int_t njs_string_replace_regexp_function(njs_vm_t *vm, - njs_value_t *this, njs_value_t *regex, njs_string_replace_t *r, - int *captures, njs_uint_t n); -static njs_int_t njs_string_replace_regexp_join(njs_vm_t *vm, - njs_string_replace_t *r); -static njs_int_t njs_string_replace_search(njs_vm_t *vm, njs_value_t *this, - njs_value_t *search, njs_string_replace_t *r); -static njs_int_t njs_string_replace_search_function(njs_vm_t *vm, - njs_value_t *this, njs_value_t *search, njs_string_replace_t *r); -static njs_int_t njs_string_replace_parse(njs_vm_t *vm, - njs_string_replace_t *r, u_char *p, u_char *end, size_t size, - njs_uint_t ncaptures); -static njs_int_t njs_string_replace_substitute(njs_vm_t *vm, - njs_string_replace_t *r, int *captures); -static njs_int_t njs_string_replace_join(njs_vm_t *vm, njs_string_replace_t *r); -static void njs_string_replacement_copy(njs_string_replace_part_t *string, - const njs_value_t *value); #define njs_base64_encoded_length(len) (((len + 2) / 3) * 4) @@ -3406,806 +3349,294 @@ njs_string_split_part_add(njs_vm_t *vm, njs_array_t *array, njs_utf8_t utf8, } -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_int_t +njs_string_get_substitution(njs_vm_t *vm, njs_value_t *matched, + njs_value_t *string, int64_t pos, njs_value_t *captures, int64_t ncaptures, + njs_value_t *groups, njs_value_t *replacement, njs_value_t *retval) { - u_char *p, *start, *end; - njs_int_t ret; - njs_uint_t ncaptures; - njs_value_t *this, *search, *replace; - njs_value_t search_lvalue, replace_lvalue; - njs_regex_t *regex; - njs_string_prop_t string; - njs_string_replace_t *r, string_replace; - - ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0)); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - this = njs_argument(args, 0); - - if (nargs == 1) { - goto original; - } - - search = njs_lvalue_arg(&search_lvalue, args, nargs, 1); - replace = njs_lvalue_arg(&replace_lvalue, args, nargs, 2); - - (void) njs_string_prop(&string, this); - - if (string.size == 0) { - goto original; - } - - r = &string_replace; - - r->utf8 = NJS_STRING_BYTE; - r->type = NJS_REGEXP_BYTE; - - if (string.length != 0) { - r->utf8 = NJS_STRING_ASCII; - r->type = NJS_REGEXP_UTF8; - - if (string.length != string.size) { - r->utf8 = NJS_STRING_UTF8; - } - } - - if (njs_is_regexp(search)) { - regex = &njs_regexp_pattern(search)->regex[r->type]; - - if (!njs_regex_is_valid(regex)) { - goto original; - } + int64_t tail, size, length, n; + u_char c, c2, *p, *r, *end; + njs_str_t rep, m, str, cap; + njs_int_t ret; + njs_chb_t chain; + njs_value_t name, value; - ncaptures = njs_regex_ncaptures(regex); + njs_string_get(replacement, &rep); + p = rep.start; + end = rep.start + rep.length; - } else { - regex = NULL; - ncaptures = 1; + njs_chb_init(&chain, vm->mem_pool); - if (!njs_is_string(search)) { - ret = njs_value_to_string(vm, search, search); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + while (p < end) { + r = njs_strlchr(p, end, '$'); + if (r == NULL || r == &end[-1]) { + if (njs_fast_path(p == rep.start)) { + *retval = *replacement; + return NJS_OK; } - } - } - - /* This cannot fail. */ - r->part = njs_arr_init(vm->mem_pool, &r->parts, &r->array, - 3, sizeof(njs_string_replace_part_t)); - - r->substitutions = NULL; - r->function = NULL; - - /* A literal replacement is stored in the second part. */ - if (nargs == 2) { - njs_string_replacement_copy(&r->part[1], &njs_string_undefined); - - } else if (njs_is_function(replace)) { - r->function = njs_function(replace); - - } else { - if (njs_slow_path(!njs_is_string(replace))) { - ret = njs_value_to_string(vm, replace, replace); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } + njs_chb_append(&chain, p, end - p); + goto done; } - njs_string_replacement_copy(&r->part[1], replace); + njs_chb_append(&chain, p, r - p); + p = r; - start = r->part[1].start; + c = r[1]; - if (start == NULL) { - start = r->part[1].value.short_string.start; - } + switch (c) { + case '$': + njs_chb_append_literal(&chain, "$"); + p += 2; + break; - end = start + r->part[1].size; + case '&': + njs_string_get(matched, &m); + njs_chb_append_str(&chain, &m); + p += 2; + break; - for (p = start; p < end; p++) { - if (*p == '$') { - ret = njs_string_replace_parse(vm, r, p, end, p - start, - ncaptures); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } + case '`': + njs_string_get(string, &str); + njs_chb_append(&chain, str.start, pos); + p += 2; + break; + + case '\'': + njs_string_get(matched, &m); + tail = pos + m.length; - /* Reset parts array to the subject string only. */ - r->parts.items = 1; + njs_string_get(string, &str); + njs_chb_append(&chain, &str.start[tail], + njs_max((int64_t) str.length - tail, 0)); + p += 2; + break; + case '<': + r = njs_strlchr(p, end, '>'); + if (r == NULL) { + njs_chb_append(&chain, p, 2); + p += 2; break; } - } - } - r->part[0].start = string.start; - r->part[0].size = string.size; - njs_set_invalid(&r->part[0].value); + p += 2; - if (regex != NULL) { - r->match_data = njs_regex_match_data(regex, vm->regex_context); - if (njs_slow_path(r->match_data == NULL)) { - return NJS_ERROR; - } - - return njs_string_replace_regexp(vm, this, search, r); - } - - return njs_string_replace_search(vm, this, search, r); - -original: - - njs_string_copy(&vm->retval, this); - - return NJS_OK; -} - - -static njs_int_t -njs_string_replace_regexp(njs_vm_t *vm, njs_value_t *this, njs_value_t *regex, - njs_string_replace_t *r) -{ - int *captures; - u_char *p, *start; - njs_int_t ret; - const u_char *end; - njs_regexp_pattern_t *pattern; - njs_string_replace_part_t replace; - - pattern = njs_regexp_pattern(regex); - end = r->part[0].start + r->part[0].size; - - replace = r->part[1]; - - do { - ret = njs_regexp_match(vm, &pattern->regex[r->type], - r->part[0].start, 0, r->part[0].size, - r->match_data); - - if (ret < 0) { - if (njs_slow_path(ret != NJS_REGEX_NOMATCH)) { - return NJS_ERROR; + if (groups == NULL) { + break; } - break; - } - - captures = njs_regex_captures(r->match_data); - - if (r->substitutions != NULL) { - ret = njs_string_replace_substitute(vm, r, captures); + ret = njs_vm_value_string_set(vm, &name, p, r - p); if (njs_slow_path(ret != NJS_OK)) { - return ret; + goto exception; } - if (!pattern->global) { - return njs_string_replace_regexp_join(vm, r); - } - - continue; - } - - if (r->part != r->parts.start) { - r->part = njs_arr_add(&r->parts); - if (njs_slow_path(r->part == NULL)) { - return NJS_ERROR; - } + p = r + 1; - r->part = njs_arr_add(&r->parts); - if (njs_slow_path(r->part == NULL)) { - return NJS_ERROR; + ret = njs_value_property(vm, groups, &name, &value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; } - r->part -= 2; - } - - if (captures[1] == 0) { - - /* Empty match. */ + if (njs_is_defined(&value)) { + ret = njs_value_to_string(vm, &value, &value); + if (njs_slow_path(ret == NJS_ERROR)) { + goto exception; + } - start = r->part[0].start; + njs_string_get(&value, &str); + njs_chb_append_str(&chain, &str); + } - if (start < end) { - p = (r->utf8 != NJS_STRING_BYTE) - ? (u_char *) njs_utf8_next(start, end) : start + 1; + break; - r->part[1].start = start; - r->part[1].size = p - start; + default: + if (c >= '0' && c <= '9') { + n = c - '0'; - r->part[2].start = p; - r->part[2].size = end - p; + c2 = (&r[2] < end) ? r[2] : 0; - } else { - r->part[1].size = 0; - r->part[2].size = 0; + if (c2 >= '0' && c2 <= '9' + && (n * 10 + (c2 - '0')) <= ncaptures) + { + n = n * 10 + (c2 - '0'); - /* To exit the loop. */ - r->part[2].start = start + 1; - } + } else { + c2 = 0; + } - if (r->function != NULL) { - return njs_string_replace_regexp_function(vm, this, regex, r, - captures, ret); - } + if (n == 0 || n > ncaptures) { + njs_chb_append(&chain, p, (c2 != 0) ? 3 : 2); + p += (c2 != 0) ? 3 : 2; + break; + } - r->part[0] = replace; + p += (c2 != 0) ? 3 : 2; - } else { - r->part[2].start = r->part[0].start + captures[1]; - r->part[2].size = r->part[0].size - captures[1]; - njs_set_invalid(&r->part[2].value); + if (njs_is_defined(&captures[n])) { + njs_string_get(&captures[n], &cap); + njs_chb_append_str(&chain, &cap); + } - if (r->function != NULL) { - return njs_string_replace_regexp_function(vm, this, regex, r, - captures, ret); + break; } - r->part[0].size = captures[0]; - - r->part[1] = replace; - } - - if (!pattern->global) { - return njs_string_replace_regexp_join(vm, r); + njs_chb_append_literal(&chain, "$"); + p += 1; + break; } - - r->part += 2; - - } while (r->part[0].start <= end); - - if (r->part != r->parts.start) { - return njs_string_replace_regexp_join(vm, r); } - njs_regex_match_data_free(r->match_data, vm->regex_context); - - njs_arr_destroy(&r->parts); - - njs_string_copy(&vm->retval, this); - - return NJS_OK; -} - - -static njs_int_t -njs_string_replace_regexp_function(njs_vm_t *vm, njs_value_t *this, - njs_value_t *regex, njs_string_replace_t *r, int *captures, njs_uint_t n) -{ - u_char *start; - size_t size, length; - njs_int_t ret; - njs_uint_t i, k; - njs_value_t *arguments; - njs_string_prop_t string; - - if (njs_slow_path((n + 3) >= UINT32_MAX / sizeof(njs_value_t))) { - njs_memory_error(vm); - return NJS_ERROR; - } - - njs_set_invalid(&r->retval); +done: - arguments = njs_mp_alloc(vm->mem_pool, (n + 3) * sizeof(njs_value_t)); - if (njs_slow_path(arguments == NULL)) { + size = njs_chb_size(&chain); + if (njs_slow_path(size < 0)) { njs_memory_error(vm); - return NJS_ERROR; - } - - njs_set_undefined(&arguments[0]); - - /* Matched substring and parenthesized submatch strings. */ - for (k = 0, i = 1; i <= n; i++) { - - start = r->part[0].start + captures[k]; - size = captures[k + 1] - captures[k]; - k += 2; - - length = njs_string_calc_length(r->utf8, start, size); - - ret = njs_string_new(vm, &arguments[i], start, size, length); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } - } - - r->empty = (captures[0] == captures[1]); - - /* The offset of the matched substring. */ - njs_set_number(&arguments[n + 1], captures[0]); - - /* The whole string being examined. */ - length = njs_string_calc_length(r->utf8, r->part[0].start, r->part[0].size); - - (void) njs_string_prop(&string, this); - - ret = njs_string_new(vm, &arguments[n + 2], string.start, string.size, - length); - - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; + ret = NJS_ERROR; + goto exception; } - r->part[0].size = captures[0]; + length = njs_chb_utf8_length(&chain); - ret = njs_function_apply(vm, r->function, arguments, n + 3, &r->retval); - if (njs_slow_path(ret != NJS_OK)) { + p = njs_string_alloc(vm, retval, size, length); + if (njs_slow_path(p == NULL)) { + ret = NJS_ERROR; goto exception; } - if (njs_slow_path(!njs_is_string(&r->retval))) { - ret = njs_value_to_string(vm, &r->retval, &r->retval); - if (njs_slow_path(ret != NJS_OK)) { - goto exception; - } - } - - njs_string_replacement_copy(&r->part[r->empty ? 0 : 1], &r->retval); - - if (njs_regexp_pattern(regex)->global) { - r->part += 2; + njs_chb_join_to(&chain, p); - if (r->part[0].start > (string.start + string.size)) { - return njs_string_replace_regexp_join(vm, r); - } - - return njs_string_replace_regexp(vm, this, regex, r); - } - - return njs_string_replace_regexp_join(vm, r); + ret = NJS_OK; exception: - njs_regex_match_data_free(r->match_data, vm->regex_context); - - return NJS_ERROR; -} - - -static njs_int_t -njs_string_replace_regexp_join(njs_vm_t *vm, njs_string_replace_t *r) -{ - njs_regex_match_data_free(r->match_data, vm->regex_context); + njs_chb_destroy(&chain); - return njs_string_replace_join(vm, r); + return NJS_OK; } static njs_int_t -njs_string_replace_search(njs_vm_t *vm, njs_value_t *this, njs_value_t *search, - njs_string_replace_t *r) +njs_string_prototype_replace(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) { - int captures[2]; - u_char *p, *end; - size_t size; - njs_int_t ret; - njs_str_t string; - - njs_string_get(search, &string); - - p = r->part[0].start; - end = (p + r->part[0].size) - (string.length - 1); + u_char *r; + size_t length, search_length, ret_length, size; + int64_t pos; + njs_int_t ret; + njs_value_t *this, *search, *replace; + njs_value_t search_lvalue, replace_lvalue, replacer, retval, + arguments[3]; + const u_char *p; + njs_function_t *func_replace; + njs_string_prop_t string, s, ret_string; - while (p < end) { - if (memcmp(p, string.start, string.length) == 0) { + static const njs_value_t replace_key = + njs_wellknown_symbol(NJS_SYMBOL_REPLACE); - if (r->substitutions != NULL) { - captures[0] = p - r->part[0].start; - captures[1] = captures[0] + string.length; + this = njs_argument(args, 0); - ret = njs_string_replace_substitute(vm, r, captures); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } + if (njs_slow_path(njs_is_null_or_undefined(this))) { + njs_type_error(vm, "cannot convert \"%s\"to object", + njs_type_string(this->type)); + return NJS_ERROR; + } - } else { - r->part[2].start = p + string.length; - size = p - r->part[0].start; - r->part[2].size = r->part[0].size - size - string.length; - r->part[0].size = size; - njs_set_invalid(&r->part[2].value); - - if (r->function != NULL) { - return njs_string_replace_search_function(vm, this, search, - r); - } - } + search = njs_lvalue_arg(&search_lvalue, args, nargs, 1); + replace = njs_lvalue_arg(&replace_lvalue, args, nargs, 2); - return njs_string_replace_join(vm, r); + if (!njs_is_null_or_undefined(search)) { + ret = njs_value_method(vm, search, njs_value_arg(&replace_key), + &replacer); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } - if (r->utf8 < 2) { - p++; + if (njs_is_defined(&replacer)) { + arguments[0] = *this; + arguments[1] = *replace; - } else { - p = (u_char *) njs_utf8_next(p, end); + return njs_function_call(vm, njs_function(&replacer), search, + arguments, 2, &vm->retval); } } - njs_string_copy(&vm->retval, this); - - return NJS_OK; -} - - -static njs_int_t -njs_string_replace_search_function(njs_vm_t *vm, njs_value_t *this, - njs_value_t *search, njs_string_replace_t *r) -{ - njs_int_t ret; - njs_value_t string; - njs_value_t arguments[4]; - - njs_set_undefined(&arguments[0]); - - /* GC, args[0], args[1] */ - - /* Matched substring, it is the same as the args[1]. */ - arguments[1] = *search; - - /* The offset of the matched substring. */ - njs_set_number(&arguments[2], r->part[0].size); - - /* The whole string being examined. */ - arguments[3] = *this; - - ret = njs_function_apply(vm, r->function, arguments, 4, &r->retval); - + ret = njs_value_to_string(vm, this, this); if (njs_slow_path(ret != NJS_OK)) { return ret; } - if (!njs_is_primitive(&r->retval)) { - ret = njs_value_to_string(vm, &r->retval, &r->retval); - if (ret != NJS_OK) { - return ret; - } - } - - ret = njs_primitive_value_to_string(vm, &string, &r->retval); + ret = njs_value_to_string(vm, search, search); if (njs_slow_path(ret != NJS_OK)) { - njs_type_error(vm, "cannot convert primitive value to string: %s", - njs_type_string(r->retval.type)); - - return NJS_ERROR; + return ret; } - njs_string_replacement_copy(&r->part[1], &string); - - return njs_string_replace_join(vm, r); -} - + func_replace = njs_is_function(replace) ? njs_function(replace) : NULL; -static njs_int_t -njs_string_replace_parse(njs_vm_t *vm, njs_string_replace_t *r, u_char *p, - u_char *end, size_t size, njs_uint_t ncaptures) -{ - u_char c; - uint32_t type; - njs_string_subst_t *s; - - r->substitutions = njs_arr_create(vm->mem_pool, 4, - sizeof(njs_string_subst_t)); - - if (njs_slow_path(r->substitutions == NULL)) { - return NJS_ERROR; + if (func_replace == NULL) { + ret = njs_value_to_string(vm, replace, replace); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } } - s = NULL; + length = njs_string_prop(&string, this); + search_length = njs_string_prop(&s, search); - if (size == 0) { - goto skip; + pos = njs_string_index_of(&string, &s, 0); + if (pos < 0) { + vm->retval = *this; + return NJS_OK; } -copy: - - if (s == NULL) { - s = njs_arr_add(r->substitutions); - if (njs_slow_path(s == NULL)) { - return NJS_ERROR; + 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; } - s->type = NJS_SUBST_COPY; - s->size = size; - s->start = p - size; - } else { - s->size += size; - } - -skip: - - while (p < end) { - size = 1; - c = *p++; + arguments[0] = *search; + njs_set_number(&arguments[1], pos); + arguments[2] = *this; - if (c != '$' || p == end) { - goto copy; - } - - c = *p++; - - if (c == '$') { - s = NULL; - goto copy; - } - - size = 2; - - if (c >= '1' && c <= '9') { - type = c - '0'; - - if (p < end) { - c = *p; - - if (c >= '0' && c <= '9') { - type = type * 10 + (c - '0'); - p++; - size = 3; - } - } - - if (type >= ncaptures) { - goto copy; - } + ret = njs_function_call(vm, func_replace, + njs_value_arg(&njs_value_undefined), + arguments, 3, &retval); - type *= 2; - - } else if (c == '`') { - type = NJS_SUBST_PRECEDING; - - } else if (c == '&') { - type = 0; - - } else if (c == '\'') { - type = NJS_SUBST_FOLLOWING; - - } else { - goto copy; - } - - s = njs_arr_add(r->substitutions); - if (njs_slow_path(s == NULL)) { + ret = njs_value_to_string(vm, &retval, &retval); + if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - - s->type = type; - s = NULL; - } - - return NJS_OK; -} - - -static njs_int_t -njs_string_replace_substitute(njs_vm_t *vm, njs_string_replace_t *r, - int *captures) -{ - int *capture; - uint32_t i, n, last; - const u_char *end; - njs_string_subst_t *s; - njs_string_replace_part_t *part, *subject; - - capture = NULL; - - last = r->substitutions->items; - end = r->part[0].start + r->part[0].size; - - part = njs_arr_add_multiple(&r->parts, last + 1); - if (njs_slow_path(part == NULL)) { - return NJS_ERROR; - } - - r->part = &part[-1]; - - part[last].start = r->part[0].start + captures[1]; - - if (captures[1] == 0) { - - /* Empty match. */ - - if (r->part[0].start < end) { - captures[1] = njs_utf8_next(r->part[0].start, end) - - r->part[0].start; - part[last].start = r->part[0].start + captures[1]; - - } else { - /* To exit the loop. */ - part[last].start = r->part[0].start + 1; - } } - part[last].size = r->part[0].size - captures[1]; - njs_set_invalid(&part[last].value); - - r->part[0].size = captures[0]; - - s = r->substitutions->start; - - for (i = 0; i < last; i++) { - n = s[i].type; - - switch (n) { - - /* Literal text, "$$", and out of range "$n" substitutions. */ - case NJS_SUBST_COPY: - part->start = s[i].start; - part->size = s[i].size; - break; - - /* "$`" substitution. */ - case NJS_SUBST_PRECEDING: - subject = r->parts.start; - part->start = subject->start; - part->size = (r->part[0].start - subject->start) + r->part[0].size; - break; - - /* "$'" substitution. */ - case NJS_SUBST_FOLLOWING: - part->start = r->part[last + 1].start; - part->size = r->part[last + 1].size; - break; - - /* - * "$n" and "$&" substitutions. - */ - default: - if (captures[n] == captures[n + 1]) { - - /* Empty match. */ - - if (n > 0 && captures[n - 1] == captures[n]) { - - /* - * Consecutive empty matches as in - * 'ab'.replace(/(z*)(h*)/g, 'x') - */ - - part->size = 0; - break; - } - - capture = &captures[n]; - continue; - } - - if (capture != NULL) { - - /* - * Inserting a single character after a series of - * (possibly several) empty matches. - */ - - if (part->start < end) { - part->start = r->part[0].start + *capture; - part->size = njs_utf8_next(part->start, end) - part->start; - - } else { - part->size = 0; - } - - capture = NULL; - break; - } - - part->start = r->part[0].start + captures[n]; - part->size = captures[n + 1] - captures[n]; - break; - } - - njs_set_invalid(&part->value); - part++; - } - - if (capture != NULL) { - part->start = r->part[0].start + *capture; - - if (part->start < end) { - part->size = njs_utf8_next(part->start, end) - part->start; - - } else { - part->size = 0; - } + if (length == string.size) { + p = string.start + pos; - njs_set_invalid(&part->value); - part++; + } else { + /* UTF-8 string. */ + p = njs_string_offset(string.start, string.start + string.size, pos); } - r->part = part; - - return NJS_OK; -} - - -static njs_int_t -njs_string_replace_join(njs_vm_t *vm, njs_string_replace_t *r) -{ - u_char *p, *string; - size_t size, length, mask; - ssize_t len; - njs_uint_t i, n; - njs_string_replace_part_t *part; - - size = 0; - length = 0; - mask = -1; - - part = r->parts.start; - n = r->parts.items; + ret_length = njs_string_prop(&ret_string, &retval); - for (i = 0; i < n; i++) { - if (part[i].size == 0) { - continue; - } - - size += part[i].size; - - if (part[i].start == NULL) { - part[i].start = part[i].value.short_string.start; - } - - len = njs_utf8_length(part[i].start, part[i].size); - - if (len >= 0) { - length += len; + size = string.size + ret_string.size - s.size; + length += ret_length - search_length; - } else { - mask = 0; - } - } - - length &= mask; - - string = njs_string_alloc(vm, &vm->retval, size, length); - if (njs_slow_path(string == NULL)) { + r = njs_string_alloc(vm, &vm->retval, size, length); + if (njs_slow_path(r == NULL)) { return NJS_ERROR; } - p = string; - - for (i = 0; i < n; i++) { - size = part[i].size; - - if (size != 0) { - p = njs_cpymem(p, part[i].start, size); - } - - /* GC: release valid values. */ - } - - njs_arr_destroy(&r->parts); + r = njs_cpymem(r, string.start, p - string.start); + r = njs_cpymem(r, ret_string.start, ret_string.size); + r = njs_cpymem(r, p + s.size, string.size - s.size - (p - string.start)); return NJS_OK; } -static void -njs_string_replacement_copy(njs_string_replace_part_t *string, - const njs_value_t *value) -{ - size_t size; - - string->value = *value; - - size = value->short_string.size; - - if (size != NJS_STRING_LONG) { - string->start = NULL; - - } else { - string->start = value->long_string.data->start; - size = value->long_string.size; - } - - string->size = size; -} - - double njs_string_to_number(const njs_value_t *value, njs_bool_t parse_float) { diff --git a/src/njs_string.h b/src/njs_string.h index c78510ca..da300a49 100644 --- a/src/njs_string.h +++ b/src/njs_string.h @@ -199,6 +199,9 @@ njs_index_t njs_value_index(njs_vm_t *vm, const njs_value_t *src, njs_int_t njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); +njs_int_t njs_string_get_substitution(njs_vm_t *vm, njs_value_t *matched, + njs_value_t *string, int64_t pos, njs_value_t *captures, int64_t ncaptures, + njs_value_t *groups, njs_value_t *replacement, njs_value_t *retval); extern const njs_object_init_t njs_string_instance_init; diff --git a/src/njs_value.c b/src/njs_value.c index 89b64ff8..4d5aba6d 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -1519,3 +1519,28 @@ default_constructor: return NJS_OK; } + + +njs_int_t +njs_value_method(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, + njs_value_t *retval) +{ + njs_int_t ret; + + ret = njs_value_to_object(vm, value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_value_property(vm, value, key, retval); + if (njs_slow_path(ret != NJS_OK)) { + return (ret == NJS_DECLINED) ? NJS_OK : ret; + } + + if (njs_slow_path(!njs_is_function(retval))) { + njs_type_error(vm, "method is not callable"); + return NJS_ERROR; + } + + return NJS_OK; +} diff --git a/src/njs_value.h b/src/njs_value.h index 726ce717..609eadc4 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -1030,6 +1030,9 @@ void njs_symbol_conversion_failed(njs_vm_t *vm, njs_bool_t to_string); njs_int_t njs_value_species_constructor(njs_vm_t *vm, njs_value_t *object, njs_value_t *default_constructor, njs_value_t *dst); +njs_int_t njs_value_method(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, + njs_value_t *retval); + njs_inline njs_int_t njs_value_property_i64(njs_vm_t *vm, njs_value_t *value, int64_t index, diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 82079ed2..54518871 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -7171,6 +7171,9 @@ static njs_unit_test_t njs_test[] = { njs_str("''.indexOf.call(12345, 45, '0')"), njs_str("3") }, + { njs_str("var r = new String('undefined').indexOf(x); var x; r"), + njs_str("0") }, + { njs_str("'abc'.lastIndexOf('abcdef')"), njs_str("-1") }, @@ -7448,82 +7451,159 @@ static njs_unit_test_t njs_test[] = "'123456'.search(r)"), njs_str("3") }, - { njs_str("'abcdefgh'.replace()"), - njs_str("abcdefgh") }, + { njs_str("'abc'.replace()"), + njs_str("abc") }, - { njs_str("'abcdefgh'.replace('d')"), - njs_str("abcundefinedefgh") }, + { njs_str("'ABC'.replace('B')"), + njs_str("AundefinedC") }, - { njs_str("'abcdefgh'.replace('d', undefined)"), - njs_str("abcundefinedefgh") }, + { njs_str("'ABC'.replace('B', undefined)"), + njs_str("AundefinedC") }, { njs_str("'a'.repeat(16).replace('a'.repeat(17)) === 'a'.repeat(16)"), njs_str("true") }, - { njs_str("'abcdefgh'.replace('d', null)"), - njs_str("abcnullefgh") }, + { njs_str("'α'.repeat(16).replace('α'.repeat(17)) === 'α'.repeat(16)"), + njs_str("true") }, - { njs_str("'abcdefgh'.replace('d', 1)"), - njs_str("abc1efgh") }, + { njs_str("'ABC'.replace('B', null)"), + njs_str("AnullC") }, - { njs_str("'abcdefghdijklm'.replace('d', 'X')"), - njs_str("abcXefghdijklm") }, + { njs_str("'abc'.replace('c', 1)"), + njs_str("ab1") }, - { njs_str("'абвгдежгийклм'.replace('г', 'Г')"), - njs_str("абвГдежгийклм") }, + { njs_str("'abc'.replace('a', 'X')"), + njs_str("Xbc") }, - { njs_str("'abcdefghdijklm'.replace('d'," - " function(m, o, s) { return '|'+s+'|'+o+'|'+m+'|' })"), - njs_str("abc|abcdefghdijklm|3|d|efghdijklm") }, + { njs_str("'abc'.replace('b', 'X')"), + njs_str("aXc") }, - { njs_str("'abcdefgh'.replace('', 'X')"), - njs_str("Xabcdefgh") }, + { njs_str("('a'.repeat(33) + 'bb').replace('bb', 'CC').slice(31)"), + njs_str("aaCC") }, - { njs_str("'abcdefghdijklm'.replace(/d/, 'X')"), - njs_str("abcXefghdijklm") }, + { njs_str("var r = 'abc'.replace('c', 'X'); [r, r.length]"), + njs_str("abX,3") }, - { njs_str("'abcdefghdijklm'.replace(/d/," - " function(m, o, s) { return '|'+s+'|'+o+'|'+m+'|' })"), - njs_str("abc|abcdefghdijklm|3|d|efghdijklm") }, + { njs_str("var r = 'αβγ'.replace('α', 'X'); [r, r.length]"), + njs_str("Xβγ,3") }, - { njs_str("'abcdefghdijklm'.replace(/(d)/," - " function(m, p, o, s)" - "{ return '|'+s+'|'+o+'|'+m+'|'+p+'|' })"), - njs_str("abc|abcdefghdijklm|3|d|d|efghdijklm") }, + { njs_str("var r = 'αβγ'.replace('β', 'X'); [r, r.length]"), + njs_str("αXγ,3") }, - { njs_str("'abc'.replace(/b/, ()=>1)"), - njs_str("a1c") }, + { njs_str("var r = 'αβγ'.replace('γ', 'X'); [r, r.length]"), + njs_str("αβX,3") }, - { njs_str("var n = 0; 'abbbc'.replace(/b/g, function() {return ++n;})"), - njs_str("a123c") }, + { njs_str("var r = 'αβγ'.replace('', 'X'); [r, r.length]"), + njs_str("Xαβγ,4") }, + + { njs_str("'abc'.replace('b', (m, o, s) => `|${s}|${o}|${m}|`)"), + njs_str("a|abc|1|b|c") }, + + { njs_str("'abcdbe'.replace('b', '|$`X$\\'|')"), + njs_str("a|aXcdbe|cdbe") }, + + { njs_str("'undefined'.replace(void 0, 'x')"), + njs_str("x") }, + + { njs_str("'12345'.replace(3, () => 0)"), + njs_str("12045") }, + + { njs_str("var r = new String('undefined').replace(x, Function('return arguments[1]+42;')); var x; r"), + njs_str("42") }, + + { njs_str("'123'.replace(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("'abc'.replace(/a/, 'X')"), + njs_str("Xbc") }, + + { njs_str("'abccd'.replace(/c/, 'X')"), + njs_str("abXcd") }, - { njs_str("'abcdefghdijklm'.replace(/x/, 'X')"), - njs_str("abcdefghdijklm") }, + { njs_str("'abc'.replace(/c/, 'X')"), + njs_str("abX") }, - { njs_str("'abcdefghdijklm'.replace(/x/," - " function(m, o, s) { return '|'+s+'|'+o+'|'+m+'|' })"), - njs_str("abcdefghdijklm") }, + { njs_str("'abccd'.replace(/c+/, 'X')"), + njs_str("abXd") }, - { njs_str("'абвгдежгийклм'.replace(/г/, 'Г')"), - njs_str("абвГдежгийклм") }, + { njs_str("'abc'.replace(/f/, 'X')"), + njs_str("abc") }, + + { njs_str("('a'.repeat(33) + 'bb').replace(/bb/, 'CC').slice(31)"), + njs_str("aaCC") }, + + { njs_str("'abccd'.replace(/c/g, 'X')"), + njs_str("abXXd") }, + + { njs_str("('a'.repeat(33) + 'bb').replace(/bb/g, 'CC').slice(31)"), + njs_str("aaCC") }, + + { njs_str("'abccd'.replace(/[ac]/g, 'X')"), + njs_str("XbXXd") }, + + { njs_str("'ab'.replace(/q*/g, 'X')"), + njs_str("XaXbX") }, + + { njs_str("'αβ'.replace(/q*/g, 'X')"), + njs_str("XαXβX") }, - { njs_str("'abcdefghdijklm'.replace(/d/g, 'X')"), - njs_str("abcXefghXijklm") }, + { njs_str("'αβ'.replace(/(q)*/g, 'X')"), + njs_str("XαXβX") }, + + { njs_str("'αβ'.replace(/q*/g, 'γ')"), + njs_str("γαγβγ") }, + + { njs_str("':α:β:γ:'.replace(/:/g, '')"), + njs_str("αβγ") }, + + { njs_str("':α:β:γ:'.replace(/[αβγ]/g, '')"), + njs_str("::::") }, + + { njs_str("'aabbccaa'.replace(/a*/g, '')"), + njs_str("bbcc") }, - { njs_str("'абвгдежгийклм'.replace(/г/g, 'Г')"), - njs_str("абвГдежГийклм") }, + { njs_str("'aabbccaab'.replace(/z*/g, '')"), + njs_str("aabbccaab") }, + + { njs_str("''.replace(/a*/g, '')"), + njs_str("") }, + + { njs_str("'abcde'.replace(/d/, (m, o, s) => `|${s}|${o}|${m}|`)"), + njs_str("abc|abcde|3|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("'abc'.replace(/b/, () => 1)"), + njs_str("a1c") }, + + { njs_str("var n = 0; 'abbbc'.replace(/b/g, () => ++n)"), + njs_str("a123c") }, + + { njs_str("'abc'.replace(/x/, (m, o, s) => `|${s}|${o}|${m}|`)"), + njs_str("abc") }, { njs_str("'abc12345#$*%'.replace(/([^\\d]*)(\\d*)([^\\w]*)/," - " function(match, p1, p2, p3) {" - " return [p1, p2, p3].join('-')})"), + " (_, p1, p2, p3) => [p1, p2, p3].join('-'))"), njs_str("abc-12345-#$*%") }, - { njs_str("'ABCDEFGHDIJKLM'.replace(/[A-Z]/g," - " function(match) { return '-' + match.toLowerCase() })"), - njs_str("-a-b-c-d-e-f-g-h-d-i-j-k-l-m") }, + { njs_str("'abc'.replace(/(?b)/, (m, p, o, s, gr) => `|${gr.named}|`)"), + njs_str("a|b|c") }, + + { njs_str("'ABC'.replace(/[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'.replace(/(b)c/g, '@$0|$01|$00@')"), + njs_str("a@$0|b|$00@") }, - { njs_str("'abcdbe'.replace(/(b)/g, '$')"), - njs_str("a$cd$e") }, + { 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("'abcdbe'.replace(/(b)/g, '$2$23')"), njs_str("a$2$23cd$2$23e") }, @@ -7531,72 +7611,50 @@ static njs_unit_test_t njs_test[] = { njs_str("'abcdbe'.replace(/(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'.replace(/b/, '|$`X$\\'|')"), njs_str("a|aXcdbe|cdbe") }, { njs_str("'abcdbefbgh'.replace(/b/g, '|$`X$\\'|')"), njs_str("a|aXcdbefbgh|cd|abcdXefbgh|ef|abcdbefXgh|gh") }, - { njs_str("'abc12345#$*%'.replace(/([^\\d]*)(\\d*)([^\\w]*)/," - " '$1-$2-$3')"), + { njs_str("'abc12345#$*%'.replace(/([^\\d]*)(\\d*)([^\\w]*)/, '$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("('β' + 'α'.repeat(33)+'β').replace(/(α+)(β+)/, function(m, p1) { return p1[32]; })"), - njs_str("βα") }, + { njs_str("'ABC'.replace(/(h*)(z*)(g*)/g, '$1@$2α$3')"), + njs_str("@αA@αB@αC@α") }, - { njs_str("'abc'.replace(/(h*)(z*)(g*)/g, '$1nn$2zz$3')"), - njs_str("nnzzannzzbnnzzcnnzz") }, + { njs_str("'abc'.replace(/(h*)(z*)/g, '$1@$2#$3:')"), + njs_str("@#$3:a@#$3:b@#$3:c@#$3:") }, - { njs_str("'abc'.replace(/(h*)(z*)/g, '$1nn$2zz$3yy')"), - njs_str("nnzz$3yyannzz$3yybnnzz$3yycnnzz$3yy") }, + { njs_str("/b(c)(z)?(.)/[Symbol.replace]('abcde', '[$1$2$3]')"), + njs_str("a[cd]e") }, - { njs_str("'ъ'.replace(/(h*)/g, '$1ЮЙ')"), - njs_str("ЮЙъЮЙ") }, + { njs_str("/b(c)(z)?(.)/[Symbol.replace]('abcde', '[$01$02$03$04$00]')"), + njs_str("a[cd$04$00]e") }, - { njs_str("'ъg'.replace(/(h*)/g, '$1ЮЙ')"), - njs_str("ЮЙъЮЙgЮЙ") }, + { njs_str("'α'.replace(/(h*)/g, '$1βγ')"), + njs_str("βγαβγ") }, - { njs_str("'ъg'.replace(/(ъ*)/g, '$1ЮЙ')"), - njs_str("ъЮЙЮЙgЮЙ") }, + { njs_str("'αg'.replace(/(h*)/g, '$1βγ')"), + njs_str("βγαβγgβγ") }, - { njs_str("'ъg'.replace(/(h*)/g, 'fg$1ЮЙ')"), - njs_str("fgЮЙъfgЮЙgfgЮЙ") }, + { njs_str("'αg'.replace(/(α*)/g, '$1βγ')"), + njs_str("αβγβγgβγ") }, - { njs_str("'юgёfя'.replace(/(gё)/g, 'n$1i')"), - njs_str("юngёifя") }, + { njs_str("'αg'.replace(/(h*)/g, 'fg$1βγ')"), + njs_str("fgβγαfgβγgfgβγ") }, - { njs_str("'aabbccaa'.replace(/a*/g, '')"), - njs_str("bbcc") }, - - { njs_str("'aabbccaab'.replace(/z*/g, '')"), - njs_str("aabbccaab") }, - - { njs_str("'αβγ'.replace(/z*/g, '|')"), - njs_str("|α|β|γ|") }, - - { njs_str("''.replace(/a*/g, '')"), - njs_str("") }, - - { njs_str("'12345'.replace(3, () => 0)"), - njs_str("12045") }, - - { njs_str("'123'.replace(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("'αgβfγ'.replace(/(gβ)/g, 'n$1i')"), + njs_str("αngβifγ") }, - { njs_str("'abc'.replace(/(z*)/g, function v0() {return '124'})"), - njs_str("124a124b124c124") }, + { njs_str("'abc'.replace(/b/g, '|$&|')"), + njs_str("a|b|c") }, - { njs_str("'abc'.replace(/(a*)/g, function v0() {return '124'})"), - njs_str("124124b124c124") }, + { njs_str("'ABC'.replace(/((A)B)/g, '($1|$&|$2)')"), + njs_str("(AB|AB|A)C") }, { njs_str("'abc'.replace(/b/g, '$0')"), njs_str("a$0c") }, @@ -7604,19 +7662,51 @@ static njs_unit_test_t njs_test[] = { njs_str("typeof String.bytesFrom(Array(15).fill(0xE3)).replace(/^/g, 1)"), njs_str("string") }, -#if 0 /* FIXME: PCRE limitation */ { njs_str("'abc'.replace(/^/g, '|$&|')"), njs_str("||abc") }, -#endif - { njs_str("'abc'.replace(/b/g, '|$&|')"), - njs_str("a|b|c") }, + { 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("'ABC'.replace(/((A)B)/g, '($1|$&|$2)')"), - njs_str("(AB|AB|A)C") }, + { njs_str("'ABC'.replace(/(?B)/, '|$|@$@')"), + njs_str("A|B|@@C") }, - { njs_str("'undefined'.replace(void 0, 'x')"), - njs_str("x") }, + { njs_str("'ABC'.replace(/(?B)/, '|$B)/, '|$@')"), + njs_str("A|@C") }, + + { njs_str("('β' + 'α'.repeat(33)+'β').replace(/(α+)(β+)/, (m, p1) => p1[32])"), + njs_str("βα") }, + + { njs_str("'abc'.replace(/(z*)/g, () => '@')"), + njs_str("@a@b@c@") }, + + { njs_str("'abc'.replace(/(a*)/g, () => '@')"), + njs_str("@@b@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.exec;" + "function mangled(s) { var r = O.call(this, s); Object.defineProperty(r, '0', {enumerable:false}); " + " return r; };" + "RegExp.prototype.exec = mangled;" + "'ABC'.replace(/(B)/, (m, p1, off, s) => `@${m}|${p1}|${off}|${s}@`)"), + njs_str("A@B|B|1|ABC@C") }, + + { njs_str("RegExp.prototype[Symbol.replace].call()"), + njs_str("TypeError: \"this\" is not object") }, + + { njs_str("RegExp.prototype[Symbol.replace].call(1)"), + njs_str("TypeError: \"this\" is not object") }, + + { njs_str("RegExp.prototype[Symbol.replace].call(/b/, 'abc','B')"), + njs_str("aBc") }, { njs_str("/]/"), njs_str("/\\]/") }, @@ -9534,6 +9624,9 @@ static njs_unit_test_t njs_test[] = { njs_str("/./['exec'] === RegExp.prototype.exec"), njs_str("true") }, + { njs_str("/./[Symbol.replace] === RegExp.prototype[Symbol.replace]"), + njs_str("true") }, + { njs_str("/^[A-Za-z0-9+/]{4}$/.test('////')"), njs_str("true") }, @@ -9697,14 +9790,15 @@ static njs_unit_test_t njs_test[] = { njs_str("var r = /a/.exec('a'); ['groups' in r, typeof r.groups]"), njs_str("true,undefined") }, -#if (!NJS_HAVE_MEMORY_SANITIZER) /* PCRE bug in groups code */ { njs_str("var r = /(?[0-9]{2})\\/(?[0-9]{2})\\/(?[0-9]{4})/;" - "var g = r.exec('12/31/1986').groups;" - "g.d + '.' + g.m + '.' + g.y"), + "var g = r.exec('12/31/1986').groups;" + "g.d + '.' + g.m + '.' + g.y"), njs_str("31.12.1986") }, +#if (!NJS_HAVE_MEMORY_SANITIZER) /* PCRE bug in groups code */ + { njs_str("var g = /(?(?no)?(?yes)?)/.exec('yes').groups;" - "[Object.keys(g).length,'no' in g, typeof g.no, g.yes, g.r]"), + "[Object.keys(g).length,'no' in g, typeof g.no, g.yes, g.r]"), njs_str("3,true,undefined,yes,yes") }, #endif @@ -17792,6 +17886,7 @@ static njs_unit_test_t njs_shell_test[] = " function(m) {return m.a.a})" ENTER), njs_str("TypeError: cannot get property \"a\" of undefined\n" " at anonymous (:1)\n" + " at RegExp.prototype[Symbol.replace] (native)\n" " at String.prototype.replace (native)\n" " at main (:1)\n") },