Implement and add support for RegExp.prototype[@@search] (#3436)

JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai@inf.u-szeged.hu
This commit is contained in:
Dániel Bátyai 2019-12-12 15:59:28 +01:00 committed by Zoltan Herczeg
parent c525b1f10a
commit d3b8bed2c1
8 changed files with 405 additions and 88 deletions

View File

@ -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
*

View File

@ -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 */

View File

@ -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, &regexp_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) */

View File

@ -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
*

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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);