From d3b8bed2c1fd2a019845901cc196a5f1bd19cf1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20B=C3=A1tyai?= Date: Thu, 12 Dec 2019 15:59:28 +0100 Subject: [PATCH] Implement and add support for RegExp.prototype[@@search] (#3436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai@inf.u-szeged.hu --- .../ecma-builtin-regexp-prototype.c | 16 ++ .../ecma-builtin-regexp-prototype.inc.h | 1 + .../ecma-builtin-string-prototype.c | 144 +++++------ .../ecma/operations/ecma-regexp-object.c | 82 +++++++ .../ecma/operations/ecma-regexp-object.h | 1 + tests/jerry/es2015/symbol-search.js | 228 ++++++++++++++++++ ...pe-replace.js => string-regexp-methods.js} | 2 + tests/jerry/string-prototype-search.js | 19 -- 8 files changed, 405 insertions(+), 88 deletions(-) create mode 100644 tests/jerry/es2015/symbol-search.js rename tests/jerry/es5.1/{string-prototype-replace.js => string-regexp-methods.js} (95%) diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-regexp-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-regexp-prototype.c index 6ff749936..722fcddc0 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-regexp-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-regexp-prototype.c @@ -617,6 +617,22 @@ ecma_builtin_regexp_prototype_symbol_replace (ecma_value_t this_arg, /**< this a return ecma_regexp_replace_helper (this_arg, string_arg, replace_arg); } /* ecma_builtin_regexp_prototype_symbol_replace */ +/** + * The RegExp.prototype object's '@@search' routine + * + * See also: + * ECMA-262 v6.0, 21.2.5.9 + * + * @return ecma value + * Returned value must be freed with ecma_free_value. + */ +static ecma_value_t +ecma_builtin_regexp_prototype_symbol_search (ecma_value_t this_arg, /**< this argument */ + ecma_value_t string_arg) /**< string argument */ +{ + return ecma_regexp_search_helper (this_arg, string_arg); +} /* ecma_builtin_regexp_prototype_symbol_search */ + /** * The RegExp.prototype object's '@@match' routine * diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-regexp-prototype.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-regexp-prototype.inc.h index 3411b5a4d..c08c11939 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-regexp-prototype.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-regexp-prototype.inc.h @@ -56,6 +56,7 @@ ACCESSOR_READ_ONLY (LIT_MAGIC_STRING_STICKY, ECMA_PROPERTY_FIXED) ROUTINE (LIT_GLOBAL_SYMBOL_REPLACE, ecma_builtin_regexp_prototype_symbol_replace, 2, 2) +ROUTINE (LIT_GLOBAL_SYMBOL_SEARCH, ecma_builtin_regexp_prototype_symbol_search, 1, 1) ROUTINE (LIT_GLOBAL_SYMBOL_MATCH, ecma_builtin_regexp_prototype_symbol_match, 1, 1) #else /* !ENABLED (JERRY_ES2015) */ /* ECMA-262 v5, 15.10.7.1 */ diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.c index 2bcf72288..10231d4df 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.c @@ -274,37 +274,6 @@ ecma_builtin_string_prototype_object_locale_compare (ecma_string_t *this_string_ } /* ecma_builtin_string_prototype_object_locale_compare */ #if ENABLED (JERRY_BUILTIN_REGEXP) - -/** - * The common preparation code for 'search' and 'match' functions - * of the String prototype. - * - * @return empty value on success, error value otherwise - * Returned value must be freed with ecma_free_value. - */ -static ecma_value_t -ecma_builtin_string_prepare_search (ecma_value_t regexp_arg, /**< regex argument */ - ecma_value_t *regexp_value) /**< [out] ptr to store the regexp object */ -{ - /* 3. */ - if (ecma_object_is_regexp_object (regexp_arg)) - { - *regexp_value = ecma_copy_value (regexp_arg); - return ECMA_VALUE_EMPTY; - } - - /* 4. */ - ecma_value_t regexp_arguments[1] = { regexp_arg }; - ecma_value_t new_regexp_value = ecma_builtin_regexp_dispatch_construct (regexp_arguments, 1); - - if (!ECMA_IS_VALUE_ERROR (new_regexp_value)) - { - *regexp_value = new_regexp_value; - } - - return new_regexp_value; -} /* ecma_builtin_string_prepare_search */ - /** * The String.prototype object's 'match' routine * @@ -600,62 +569,99 @@ cleanup_search: * * See also: * ECMA-262 v5, 15.5.4.12 + * ECMA-262 v6, 21.1.3.15 * * @return ecma value * Returned value must be freed with ecma_free_value. */ static ecma_value_t -ecma_builtin_string_prototype_object_search (ecma_value_t to_string_value, /**< this argument */ - ecma_value_t regexp_arg) /**< routine's argument */ +ecma_builtin_string_prototype_object_search (ecma_value_t this_value, /**< this argument */ + ecma_value_t regexp_value) /**< routine's argument */ { - - ecma_value_t regexp_value = ECMA_VALUE_EMPTY; - - if (ECMA_IS_VALUE_ERROR (ecma_builtin_string_prepare_search (regexp_arg, ®exp_value))) +#if ENABLED (JERRY_ES2015) + if (!(ecma_is_value_undefined (regexp_value) || ecma_is_value_null (regexp_value))) { - return ECMA_VALUE_ERROR; + ecma_object_t *obj_p = ecma_get_object_from_value (ecma_op_to_object (regexp_value)); + ecma_value_t search_symbol = ecma_op_object_get_by_symbol_id (obj_p, LIT_MAGIC_STRING_SEARCH); + ecma_deref_object (obj_p); + + if (ECMA_IS_VALUE_ERROR (search_symbol)) + { + return search_symbol; + } + + if (!ecma_is_value_undefined (search_symbol) && !ecma_is_value_null (search_symbol)) + { + if (!ecma_op_is_callable (search_symbol)) + { + ecma_free_value (search_symbol); + return ecma_raise_type_error (ECMA_ERR_MSG ("@@search is not callable")); + } + + ecma_object_t *search_method = ecma_get_object_from_value (search_symbol); + ecma_value_t search_result = ecma_op_function_call (search_method, regexp_value, &this_value, 1); + + ecma_deref_object (search_method); + return search_result; + } + } +#else /* !ENABLED (JERRY_ES2015) */ + if (ecma_object_is_regexp_object (regexp_value)) + { + return ecma_regexp_search_helper (regexp_value, this_value); + } +#endif /* ENABLED (JERRY_ES2015) */ + + ecma_value_t result = ECMA_VALUE_ERROR; + + ecma_string_t *string_p = ecma_op_to_string (this_value); + if (string_p == NULL) + { + return result; } - /* 5. */ - ecma_string_t *this_string_p = ecma_get_string_from_value (to_string_value); - ecma_ref_ecma_string (this_string_p); - - ecma_value_t match_result = ecma_regexp_exec_helper (regexp_value, to_string_value, true); - - ecma_value_t ret_value = ECMA_VALUE_ERROR; - - if (ECMA_IS_VALUE_ERROR (match_result)) + ecma_string_t *pattern_p = ecma_regexp_read_pattern_str_helper (regexp_value); + if (pattern_p == NULL) { - goto cleanup; + goto cleanup_string; } - ecma_number_t offset = -1; - - if (!ecma_is_value_null (match_result)) + ecma_value_t new_regexp = ecma_op_create_regexp_object (pattern_p, 0); + ecma_deref_ecma_string (pattern_p); + if (ECMA_IS_VALUE_ERROR (new_regexp)) { - JERRY_ASSERT (ecma_is_value_object (match_result)); - - ecma_object_t *match_object_p = ecma_get_object_from_value (match_result); - - ecma_value_t index_value = ecma_op_object_get_by_magic_id (match_object_p, LIT_MAGIC_STRING_INDEX); - - JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (index_value) && ecma_is_value_number (index_value)); - - offset = ecma_get_number_from_value (index_value); - - ecma_free_number (index_value); - ecma_free_value (match_result); + goto cleanup_string; } - ret_value = ecma_make_number_value (offset); +#if !ENABLED (JERRY_ES2015) + result = ecma_regexp_search_helper (new_regexp, ecma_make_string_value (string_p)); + ecma_deref_object (ecma_get_object_from_value (new_regexp)); +#else /* ENABLED (JERRY_ES2015) */ + ecma_object_t *regexp_obj_p = ecma_get_object_from_value (new_regexp); + ecma_value_t search_symbol = ecma_op_object_get_by_symbol_id (regexp_obj_p, LIT_MAGIC_STRING_SEARCH); + if (ECMA_IS_VALUE_ERROR (search_symbol)) + { + goto cleanup_regexp; + } + if (!ecma_op_is_callable (search_symbol)) + { + result = ecma_raise_type_error (ECMA_ERR_MSG ("@@search is not callable")); + goto cleanup_regexp; + } -cleanup: - ecma_free_value (regexp_value); - ecma_deref_ecma_string (this_string_p); + ecma_object_t *search_method_p = ecma_get_object_from_value (search_symbol); + ecma_value_t arguments[] = { ecma_make_string_value (string_p) }; + result = ecma_op_function_call (search_method_p, new_regexp, arguments, 1); + ecma_deref_object (search_method_p); - /* 6. */ - return ret_value; +cleanup_regexp: + ecma_deref_object (regexp_obj_p); +#endif /* !ENABLED (JERRY_ES2015) */ + +cleanup_string: + ecma_deref_ecma_string (string_p); + return result; } /* ecma_builtin_string_prototype_object_search */ #endif /* ENABLED (JERRY_BUILTIN_REGEXP) */ diff --git a/jerry-core/ecma/operations/ecma-regexp-object.c b/jerry-core/ecma/operations/ecma-regexp-object.c index 05729a780..a0b4ba221 100644 --- a/jerry-core/ecma/operations/ecma-regexp-object.c +++ b/jerry-core/ecma/operations/ecma-regexp-object.c @@ -1524,6 +1524,88 @@ ecma_regexp_read_pattern_str_helper (ecma_value_t pattern_arg) /**< the RegExp p return ecma_get_magic_string (LIT_MAGIC_STRING_EMPTY_NON_CAPTURE_GROUP); } /* ecma_regexp_read_pattern_str_helper */ +/** + * Helper function for RegExp based string searches + * + * See also: + * ECMA-262 v6, 21.2.5.9 + * + * @return index of the match + */ +ecma_value_t +ecma_regexp_search_helper (ecma_value_t regexp_arg, /**< regexp argument */ + ecma_value_t string_arg) /**< string argument */ +{ + /* 2. */ + if (!ecma_is_value_object (regexp_arg)) + { + return ecma_raise_type_error (ECMA_ERR_MSG ("'this' is not an object.")); + } + + ecma_value_t result = ECMA_VALUE_ERROR; + + /* 3-4. */ + ecma_string_t *const string_p = ecma_op_to_string (string_arg); + if (string_p == NULL) + { + return result; + } + + ecma_object_t *const regexp_object_p = ecma_get_object_from_value (regexp_arg); + + /* 5-6. */ + ecma_string_t *const last_index_str_p = ecma_get_magic_string (LIT_MAGIC_STRING_LASTINDEX_UL); + const ecma_value_t prev_last_index = ecma_op_object_get (regexp_object_p, last_index_str_p); + if (ECMA_IS_VALUE_ERROR (prev_last_index)) + { + goto cleanup_string; + } + + /* 7-8. */ + const ecma_value_t status = ecma_op_object_put (regexp_object_p, last_index_str_p, ecma_make_uint32_value (0), true); + if (ECMA_IS_VALUE_ERROR (status)) + { + ecma_free_value (prev_last_index); + goto cleanup_string; + } + + JERRY_ASSERT (ecma_is_value_boolean (status)); + + /* 9-10. */ + const ecma_value_t match = ecma_op_regexp_exec (regexp_arg, string_p); + if (ECMA_IS_VALUE_ERROR (match)) + { + ecma_free_value (prev_last_index); + goto cleanup_string; + } + + /* 11-12. */ + result = ecma_op_object_put (regexp_object_p, last_index_str_p, prev_last_index, true); + ecma_free_value (prev_last_index); + + if (ECMA_IS_VALUE_ERROR (result)) + { + ecma_free_value (match); + goto cleanup_string; + } + + /* 13-14. */ + if (ecma_is_value_null (match)) + { + result = ecma_make_int32_value (-1); + } + else + { + ecma_object_t *const match_p = ecma_get_object_from_value (match); + result = ecma_op_object_get_by_magic_id (match_p, LIT_MAGIC_STRING_INDEX); + ecma_deref_object (match_p); + } + +cleanup_string: + ecma_deref_ecma_string (string_p); + return result; +} /* ecma_regexp_search_helper */ + /** * Fast path for RegExp based replace operation * diff --git a/jerry-core/ecma/operations/ecma-regexp-object.h b/jerry-core/ecma/operations/ecma-regexp-object.h index 2fb4cde83..c57e4da16 100644 --- a/jerry-core/ecma/operations/ecma-regexp-object.h +++ b/jerry-core/ecma/operations/ecma-regexp-object.h @@ -106,6 +106,7 @@ lit_code_point_t ecma_regexp_canonicalize_char (lit_code_point_t ch); ecma_value_t ecma_regexp_parse_flags (ecma_string_t *flags_str_p, uint16_t *flags_p); void ecma_regexp_initialize_props (ecma_object_t *re_obj_p, ecma_string_t *source_p, uint16_t flags); +ecma_value_t ecma_regexp_search_helper (ecma_value_t regexp_arg, ecma_value_t string_arg); ecma_value_t ecma_regexp_replace_helper (ecma_value_t this_arg, ecma_value_t string_arg, diff --git a/tests/jerry/es2015/symbol-search.js b/tests/jerry/es2015/symbol-search.js new file mode 100644 index 000000000..5f664d5e8 --- /dev/null +++ b/tests/jerry/es2015/symbol-search.js @@ -0,0 +1,228 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var search = RegExp.prototype[Symbol.search]; + +try { + search.call (0, "string"); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +try { + search.call (new RegExp(), { + toString: () => { + throw "abrupt string" + } + }); + assert (false); +} catch (e) { + assert (e === "abrupt string"); +} + +try { + search.call ({ + get lastIndex() { + throw "abrupt get lastIndex" + } + }, "string"); + assert (false); +} catch (e) { + assert (e === "abrupt get lastIndex"); +} + +try { + search.call ({ + get lastIndex() { + return 3; + }, + set lastIndex(idx) { + throw "abrupt set lastIndex" + } + }, "string"); + assert (false); +} catch (e) { + assert (e === "abrupt set lastIndex"); +} + +try { + search.call ({ + get exec() { + throw "abrupt exec" + } + }, "string"); + assert (false); +} catch (e) { + assert (e === "abrupt exec"); +} + +try { + search.call ({ + exec: RegExp.prototype.exec + }, "string"); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +try { + search.call ({ + exec: 42 + }, "string"); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +try { + search.call ({ + exec: () => { + throw "abrupt exec result" + } + }, "string"); + assert (false); +} catch (e) { + assert (e === "abrupt exec result"); +} + +try { + search.call ({ + exec: () => { + return 1 + } + }, "string"); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +try { + search.call ({ + exec: () => { + return { + get index() { + throw "abrupt index" + } + } + } + }, "string"); + assert (false); +} catch (e) { + assert (e === "abrupt index"); +} + +assert (search.call (/abc/, "abc") === 0); +assert (search.call (/abc/, "strabc") === 3); +assert (search.call (/abc/, "bcd") === -1); + +class Regexplike { + constructor() { + this.index = 0; + this.global = true; + } + + exec() { + if (this.index > 0) { + return null; + } + + this.index = 42; + var result = { + length: 1, + 0: "Duck", + index: this.index + }; + return result; + } +} + +re = new Regexplike(); +assert (search.call (re, "str") === 42); + +/* Object with custom @@search method */ +var o = {} +o[Symbol.search] = function () { + return 4; +}; +assert ("string".search (o) === 4); + +o[Symbol.search] = 42; +try { + "string".search (o); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +Object.defineProperty (o, Symbol.search, { + get: () => { + throw "abrupt @@search get" + }, + set: (v) => {} +}); + +try { + "string".search (o); + assert (false); +} catch (e) { + assert (e === "abrupt @@search get"); +} + +o = {}; +o[Symbol.search] = function () { + throw "abrupt @@search" +}; +try { + "string".search (o); + assert (false); +} catch (e) { + assert (e === "abrupt @@search") +} + +o = { + exec: function () { return {index: "Duck"}; }, +}; +assert ("string".search (o) === 1); + +o[Symbol.search] = RegExp.prototype[Symbol.search]; +assert ("string".search (o) === "Duck"); + +o = { + lastIndex: "Duck", + exec: () => { + return "Duck"; + } +} + +try { + RegExp.prototype[Symbol.search].call (o, "Duck"); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +o = { + exec: () => { + return { 0: "Duck", index: 0 }; + }, + get lastIndex () { + return "Duck"; + }, + set lastIndex (v) { + return "Duck"; + } +} + +assert (RegExp.prototype[Symbol.search].call (o, "str") === 0); diff --git a/tests/jerry/es5.1/string-prototype-replace.js b/tests/jerry/es5.1/string-regexp-methods.js similarity index 95% rename from tests/jerry/es5.1/string-prototype-replace.js rename to tests/jerry/es5.1/string-regexp-methods.js index 5727f2074..6e6c2e172 100644 --- a/tests/jerry/es5.1/string-prototype-replace.js +++ b/tests/jerry/es5.1/string-regexp-methods.js @@ -15,4 +15,6 @@ // Changing exec should not affect replace. Object.getPrototypeOf(/x/).exec = function () { return 1234; } assert (/y/.exec("y") === 1234); + assert ("y".replace (/y/, "x") === "x"); +assert ("ay".search (/y/) === 1); diff --git a/tests/jerry/string-prototype-search.js b/tests/jerry/string-prototype-search.js index cf16c5088..aa74d7d7e 100644 --- a/tests/jerry/string-prototype-search.js +++ b/tests/jerry/string-prototype-search.js @@ -36,24 +36,5 @@ assert ("aaxbb".search (regexp) === 2); assert ("aabb".search (regexp) === -1); assert (regexp.lastIndex === "index"); -Object.defineProperty(regexp, "lastIndex", { - configurable : false, - enumerable : false, - value : "index2", - writable : false -}); - -assert ("axb".search (regexp) === 1); -assert ("aabb".search (regexp) === -1); -assert (regexp.lastIndex === "index2"); - assert ("##\ud801\udc00".search ("\ud801") === 2); assert ("##\ud801\udc00".search ("\udc00") === 3); - -// The real "exec" never returns with a number. -Object.getPrototypeOf(/x/).exec = function () { return "???"; } - -assert (/y/.exec("y") === "???"); - -// Changing exec should not affect search. -assert ("ay".search (/y/) === 1);