Index: src/runtime/runtime-regexp.cc |
diff --git a/src/runtime/runtime-regexp.cc b/src/runtime/runtime-regexp.cc |
index b10ee3230b1671d11422f3569963c79105663181..824835401dd4fafe776c7539a185d5375a32b855 100644 |
--- a/src/runtime/runtime-regexp.cc |
+++ b/src/runtime/runtime-regexp.cc |
@@ -4,6 +4,8 @@ |
#include "src/runtime/runtime-utils.h" |
+#include <functional> |
+ |
#include "src/arguments.h" |
#include "src/conversions-inl.h" |
#include "src/isolate-inl.h" |
@@ -885,7 +887,7 @@ class VectorBackedMatch : public String::Match { |
public: |
VectorBackedMatch(Isolate* isolate, Handle<String> subject, |
Handle<String> match, int match_position, |
- ZoneVector<Handle<Object>>* captures) |
+ std::vector<Handle<Object>>* captures) |
: isolate_(isolate), |
match_(match), |
match_position_(match_position), |
@@ -924,9 +926,34 @@ class VectorBackedMatch : public String::Match { |
Handle<String> subject_; |
Handle<String> match_; |
const int match_position_; |
- ZoneVector<Handle<Object>>* captures_; |
+ std::vector<Handle<Object>>* captures_; |
}; |
+// Create the groups object (see also the RegExp result creation in |
+// RegExpBuiltinsAssembler::ConstructNewResultFromMatchInfo). |
+Handle<JSObject> ConstructNamedCaptureGroupsObject( |
+ Isolate* isolate, Handle<FixedArray> capture_map, |
+ std::function<Object*(int)> f_get_capture) { |
+ Handle<JSObject> groups = isolate->factory()->NewJSObjectWithNullProto(); |
+ |
+ const int capture_count = capture_map->length() >> 1; |
+ for (int i = 0; i < capture_count; i++) { |
+ const int name_ix = i * 2; |
+ const int index_ix = i * 2 + 1; |
+ |
+ Handle<String> capture_name(String::cast(capture_map->get(name_ix))); |
+ const int capture_ix = Smi::cast(capture_map->get(index_ix))->value(); |
+ DCHECK(1 <= capture_ix && capture_ix <= capture_count); |
+ |
+ Handle<Object> capture_value(f_get_capture(capture_ix), isolate); |
+ DCHECK(capture_value->IsString()); |
+ |
+ JSObject::AddProperty(groups, capture_name, capture_value, NONE); |
+ } |
+ |
+ return groups; |
+} |
+ |
// Only called from Runtime_RegExpExecMultiple so it doesn't need to maintain |
// separate last match info. See comment on that function. |
template <bool has_capture> |
@@ -934,8 +961,9 @@ static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject, |
Handle<JSRegExp> regexp, |
Handle<RegExpMatchInfo> last_match_array, |
Handle<JSArray> result_array) { |
- DCHECK(subject->IsFlat()); |
+ DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp)); |
DCHECK_NE(has_capture, regexp->CaptureCount() == 0); |
+ DCHECK(subject->IsFlat()); |
int capture_count = regexp->CaptureCount(); |
int subject_length = subject->length(); |
@@ -1013,11 +1041,19 @@ static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject, |
if (has_capture) { |
// Arguments array to replace function is match, captures, index and |
- // subject, i.e., 3 + capture count in total. |
- Handle<FixedArray> elements = |
- isolate->factory()->NewFixedArray(3 + capture_count); |
+ // subject, i.e., 3 + capture count in total. If the RegExp contains |
+ // named captures, they are also passed as the last argument. |
+ |
+ Handle<Object> maybe_capture_map(regexp->CaptureNameMap(), isolate); |
+ const bool has_named_captures = maybe_capture_map->IsFixedArray(); |
+ |
+ const int argc = |
+ has_named_captures ? 4 + capture_count : 3 + capture_count; |
+ |
+ Handle<FixedArray> elements = isolate->factory()->NewFixedArray(argc); |
+ int cursor = 0; |
- elements->set(0, *match); |
+ elements->set(cursor++, *match); |
for (int i = 1; i <= capture_count; i++) { |
int start = current_match[i * 2]; |
if (start >= 0) { |
@@ -1025,14 +1061,25 @@ static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject, |
DCHECK(start <= end); |
Handle<String> substring = |
isolate->factory()->NewSubString(subject, start, end); |
- elements->set(i, *substring); |
+ elements->set(cursor++, *substring); |
} else { |
DCHECK(current_match[i * 2 + 1] < 0); |
- elements->set(i, isolate->heap()->undefined_value()); |
+ elements->set(cursor++, isolate->heap()->undefined_value()); |
} |
} |
- elements->set(capture_count + 1, Smi::FromInt(match_start)); |
- elements->set(capture_count + 2, *subject); |
+ |
+ elements->set(cursor++, Smi::FromInt(match_start)); |
+ elements->set(cursor++, *subject); |
+ |
+ if (has_named_captures) { |
+ Handle<FixedArray> capture_map = |
+ Handle<FixedArray>::cast(maybe_capture_map); |
+ Handle<JSObject> groups = ConstructNamedCaptureGroupsObject( |
+ isolate, capture_map, [=](int ix) { return elements->get(ix); }); |
+ elements->set(cursor++, *groups); |
+ } |
+ |
+ DCHECK_EQ(cursor, argc); |
builder.Add(*isolate->factory()->NewJSArrayWithElements(elements)); |
} else { |
builder.Add(*match); |
@@ -1084,15 +1131,16 @@ MUST_USE_RESULT MaybeHandle<String> RegExpReplace(Isolate* isolate, |
Handle<JSRegExp> regexp, |
Handle<String> string, |
Handle<Object> replace_obj) { |
+ // Functional fast-paths are dispatched directly by replace builtin. |
+ DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp)); |
+ DCHECK(!replace_obj->IsCallable()); |
+ |
Factory* factory = isolate->factory(); |
const int flags = regexp->GetFlags(); |
const bool global = (flags & JSRegExp::kGlobal) != 0; |
const bool sticky = (flags & JSRegExp::kSticky) != 0; |
- // Functional fast-paths are dispatched directly by replace builtin. |
- DCHECK(!replace_obj->IsCallable()); |
- |
Handle<String> replace; |
ASSIGN_RETURN_ON_EXCEPTION(isolate, replace, |
Object::ToString(isolate, replace_obj), String); |
@@ -1254,26 +1302,49 @@ RUNTIME_FUNCTION(Runtime_StringReplaceNonGlobalRegExpWithFunction) { |
builder.AppendString(factory->NewSubString(subject, 0, index)); |
// Compute the parameter list consisting of the match, captures, index, |
- // and subject for the replace function invocation. |
+ // and subject for the replace function invocation. If the RegExp contains |
+ // named captures, they are also passed as the last argument. |
+ |
// The number of captures plus one for the match. |
const int m = match_indices->NumberOfCaptureRegisters() / 2; |
- const int argc = m + 2; |
+ bool has_named_captures = false; |
+ Handle<FixedArray> capture_map; |
+ if (m > 1) { |
+ // The existence of capture groups implies IRREGEXP kind. |
+ DCHECK_EQ(regexp->TypeTag(), JSRegExp::IRREGEXP); |
+ |
+ Object* maybe_capture_map = regexp->CaptureNameMap(); |
+ if (maybe_capture_map->IsFixedArray()) { |
+ has_named_captures = true; |
+ capture_map = handle(FixedArray::cast(maybe_capture_map)); |
+ } |
+ } |
+ |
+ const int argc = has_named_captures ? m + 3 : m + 2; |
ScopedVector<Handle<Object>> argv(argc); |
+ int cursor = 0; |
for (int j = 0; j < m; j++) { |
bool ok; |
Handle<String> capture = |
RegExpUtils::GenericCaptureGetter(isolate, match_indices, j, &ok); |
if (ok) { |
- argv[j] = capture; |
+ argv[cursor++] = capture; |
} else { |
- argv[j] = factory->undefined_value(); |
+ argv[cursor++] = factory->undefined_value(); |
} |
} |
- argv[argc - 2] = handle(Smi::FromInt(index), isolate); |
- argv[argc - 1] = subject; |
+ argv[cursor++] = handle(Smi::FromInt(index), isolate); |
+ argv[cursor++] = subject; |
+ |
+ if (has_named_captures) { |
+ argv[cursor++] = ConstructNamedCaptureGroupsObject( |
+ isolate, capture_map, [&argv](int ix) { return *argv[ix]; }); |
+ } |
+ |
+ DCHECK_EQ(cursor, argc); |
Handle<Object> replacement_obj; |
ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
@@ -1578,14 +1649,14 @@ RUNTIME_FUNCTION(Runtime_RegExpReplace) { |
isolate, position_obj, |
Object::GetProperty(result, factory->index_string())); |
- // TODO(jgruber): Extract and correct error handling. Since we can go up to |
- // 2^53 - 1 (at least for ToLength), we might actually need uint64_t here? |
ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
isolate, position_obj, Object::ToInteger(isolate, position_obj)); |
const uint32_t position = |
std::min(PositiveNumberToUint32(*position_obj), length); |
- ZoneVector<Handle<Object>> captures(&zone); |
+ std::vector<Handle<Object>> captures; |
+ captures.reserve(captures_length); |
+ |
for (int n = 0; n < captures_length; n++) { |
Handle<Object> capture; |
ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
@@ -1598,17 +1669,29 @@ RUNTIME_FUNCTION(Runtime_RegExpReplace) { |
captures.push_back(capture); |
} |
+ Handle<Object> groups_obj; |
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
+ isolate, groups_obj, |
+ Object::GetProperty(result, factory->groups_string())); |
+ |
+ const bool has_named_captures = !groups_obj->IsUndefined(isolate); |
+ |
Handle<String> replacement; |
if (functional_replace) { |
- const int argc = captures_length + 2; |
+ const int argc = |
+ has_named_captures ? captures_length + 3 : captures_length + 2; |
ScopedVector<Handle<Object>> argv(argc); |
+ int cursor = 0; |
for (int j = 0; j < captures_length; j++) { |
- argv[j] = captures[j]; |
+ argv[cursor++] = captures[j]; |
} |
- argv[captures_length] = handle(Smi::FromInt(position), isolate); |
- argv[captures_length + 1] = string; |
+ argv[cursor++] = handle(Smi::FromInt(position), isolate); |
+ argv[cursor++] = string; |
+ if (has_named_captures) argv[cursor++] = groups_obj; |
+ |
+ DCHECK_EQ(cursor, argc); |
Handle<Object> replacement_obj; |
ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
@@ -1619,6 +1702,7 @@ RUNTIME_FUNCTION(Runtime_RegExpReplace) { |
ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
isolate, replacement, Object::ToString(isolate, replacement_obj)); |
} else { |
+ DCHECK(!functional_replace); |
VectorBackedMatch m(isolate, string, match, position, &captures); |
ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
isolate, replacement, String::GetSubstitution(isolate, &m, replace)); |