1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 18:37:34 +00:00

LibJS: Extract RegExp.prototype.exec to RegExpExec and RegExpBuiltinExec

The RegExp specification dictates that the internal implementation of
RegExp.prototype.exec must go through the RegExpBuiltinExec abstraction.

Note there is currently no functional difference in this commit. However
this now allows other RegExp.prototype methods to use RegExpExec rather
than calling RegExp.prototype.exec. Then, if JavaScript in the wild has
overwritten exec, RegExpExec has some protections to defer to
RegExpBuiltinExec.
This commit is contained in:
Timothy Flynn 2021-07-07 12:47:19 -04:00 committed by Linus Groh
parent 0dc4e722e6
commit a90d5cb622
3 changed files with 108 additions and 70 deletions

View file

@ -55,6 +55,7 @@
M(NotAFunctionNoParam, "Not a function") \
M(NotAn, "Not an {} object") \
M(NotAnObject, "{} is not an object") \
M(NotAnObjectOrNull, "{} is neither an object nor null") \
M(NotASymbol, "{} is not a symbol") \
M(NotIterable, "{} is not iterable") \
M(NotObjectCoercible, "{} cannot be converted to an object") \

View file

@ -82,6 +82,110 @@ static String escape_regexp_pattern(const RegExpObject& regexp_object)
return pattern;
}
static RegexResult do_match(const Regex<ECMA262>& re, const StringView& subject)
{
auto result = re.match(subject);
// The 'lastIndex' property is reset on failing tests (if 'global')
if (!result.success && re.options().has_flag_set(ECMAScriptFlags::Global))
re.start_offset = 0;
return result;
}
// 22.2.5.2.2 RegExpBuiltinExec ( R, S ), https://tc39.es/ecma262/#sec-regexpbuiltinexec
static Value regexp_builtin_exec(GlobalObject& global_object, RegExpObject& regexp_object, String const& string)
{
// FIXME: This should try using dynamic properties for 'lastIndex',
// and internal slots [[RegExpMatcher]], [[OriginalFlags]], etc.
auto& vm = global_object.vm();
auto str = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
StringView str_to_match = string;
// RegExps without "global" and "sticky" always start at offset 0.
if (!regexp_object.regex().options().has_flag_set((ECMAScriptFlags)regex::AllFlags::Internal_Stateful)) {
regexp_object.set(vm.names.lastIndex, Value(0), true);
if (vm.exception())
return {};
}
auto last_index = regexp_object.get(vm.names.lastIndex);
if (vm.exception())
return {};
regexp_object.regex().start_offset = last_index.to_length(global_object);
if (vm.exception())
return {};
auto result = do_match(regexp_object.regex(), str_to_match);
regexp_object.set(vm.names.lastIndex, Value(regexp_object.regex().start_offset), true);
if (vm.exception())
return {};
if (!result.success)
return js_null();
auto& match = result.matches[0];
// FIXME: Do code point index correction if the Unicode flag is set.
auto* array = Array::create(global_object, result.n_capture_groups + 1);
if (vm.exception())
return {};
array->create_data_property_or_throw(vm.names.index, Value((i32)match.global_offset));
array->create_data_property_or_throw(vm.names.input, js_string(vm, string));
array->create_data_property_or_throw(0, js_string(vm, match.view.to_string()));
for (size_t i = 0; i < result.n_capture_groups; ++i) {
auto capture_value = js_undefined();
auto& capture = result.capture_group_matches[0][i + 1];
if (!capture.view.is_null())
capture_value = js_string(vm, capture.view.to_string());
array->create_data_property_or_throw(i + 1, capture_value);
}
Value groups = js_undefined();
if (result.n_named_capture_groups > 0) {
auto groups_object = Object::create(global_object, nullptr);
for (auto& entry : result.named_capture_group_matches[0])
groups_object->create_data_property_or_throw(entry.key, js_string(vm, entry.value.view.to_string()));
groups = move(groups_object);
}
array->create_data_property_or_throw(vm.names.groups, groups);
return array;
}
// 22.2.5.2.1 RegExpExec ( R, S ), https://tc39.es/ecma262/#sec-regexpexec
static Value regexp_exec(GlobalObject& global_object, Object& rx, String const& string)
{
auto& vm = global_object.vm();
auto exec = rx.get(vm.names.exec);
if (vm.exception())
return {};
if (exec.is_function()) {
auto result = vm.call(exec.as_function(), &rx, js_string(vm, string));
if (vm.exception())
return {};
if (!result.is_object() && !result.is_null())
vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObjectOrNull, result.to_string_without_side_effects());
return result;
}
auto regexp_object = regexp_object_from(vm, global_object);
if (!regexp_object)
return {};
return regexp_builtin_exec(global_object, *regexp_object, string);
}
// 22.2.5.3 get RegExp.prototype.dotAll, https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll
// 22.2.5.5 get RegExp.prototype.global, https://tc39.es/ecma262/#sec-get-regexp.prototype.global
// 22.2.5.6 get RegExp.prototype.ignoreCase, https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase
@ -139,83 +243,18 @@ JS_DEFINE_NATIVE_GETTER(RegExpPrototype::source)
return js_string(vm, escape_regexp_pattern(*regexp_object));
}
RegexResult RegExpPrototype::do_match(const Regex<ECMA262>& re, const StringView& subject)
{
auto result = re.match(subject);
// The 'lastIndex' property is reset on failing tests (if 'global')
if (!result.success && re.options().has_flag_set(ECMAScriptFlags::Global))
re.start_offset = 0;
return result;
}
// 22.2.5.2 RegExp.prototype.exec ( string ), https://tc39.es/ecma262/#sec-regexp.prototype.exec
JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::exec)
{
// FIXME: This should try using dynamic properties for 'lastIndex',
// and internal slots [[RegExpMatcher]], [[OriginalFlags]], etc.
auto regexp_object = regexp_object_from(vm, global_object);
auto* regexp_object = regexp_object_from(vm, global_object);
if (!regexp_object)
return {};
auto str = vm.argument(0).to_string(global_object);
auto string = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
StringView str_to_match = str;
// RegExps without "global" and "sticky" always start at offset 0.
if (!regexp_object->regex().options().has_flag_set((ECMAScriptFlags)regex::AllFlags::Internal_Stateful)) {
regexp_object->set(vm.names.lastIndex, Value(0), true);
if (vm.exception())
return {};
}
auto last_index = regexp_object->get(vm.names.lastIndex);
if (vm.exception())
return {};
regexp_object->regex().start_offset = last_index.to_length(global_object);
if (vm.exception())
return {};
auto result = do_match(regexp_object->regex(), str_to_match);
regexp_object->set(vm.names.lastIndex, Value(regexp_object->regex().start_offset), true);
if (vm.exception())
return {};
if (!result.success)
return js_null();
auto& match = result.matches[0];
// FIXME: Do code point index correction if the Unicode flag is set.
auto* array = Array::create(global_object, result.n_capture_groups + 1);
if (vm.exception())
return {};
array->create_data_property_or_throw(vm.names.index, Value((i32)match.global_offset));
array->create_data_property_or_throw(vm.names.input, js_string(vm, str));
array->create_data_property_or_throw(0, js_string(vm, match.view.to_string()));
for (size_t i = 0; i < result.n_capture_groups; ++i) {
auto capture_value = js_undefined();
auto& capture = result.capture_group_matches[0][i + 1];
if (!capture.view.is_null())
capture_value = js_string(vm, capture.view.to_string());
array->create_data_property_or_throw(i + 1, capture_value);
}
Value groups = js_undefined();
if (result.n_named_capture_groups > 0) {
auto groups_object = Object::create(global_object, nullptr);
for (auto& entry : result.named_capture_group_matches[0])
groups_object->create_data_property_or_throw(entry.key, js_string(vm, entry.value.view.to_string()));
groups = move(groups_object);
}
array->create_data_property_or_throw(vm.names.groups, groups);
return array;
return regexp_builtin_exec(global_object, *regexp_object, string);
}
// 22.2.5.15 RegExp.prototype.test ( S ), https://tc39.es/ecma262/#sec-regexp.prototype.test

View file

@ -19,8 +19,6 @@ public:
virtual ~RegExpPrototype() override;
private:
static RegexResult do_match(const Regex<ECMA262>&, const StringView&);
JS_DECLARE_NATIVE_GETTER(flags);
JS_DECLARE_NATIVE_GETTER(source);