#include <functional>
#include <type_traits>
#include "hwy/highway.h"
#include "src/base/small-vector.h"
#include "src/base/strings.h"
#include "src/common/message-template.h"
#include "src/execution/arguments-inl.h"
#include "src/execution/isolate-inl.h"
#include "src/heap/heap-inl.h"
#include "src/logging/counters.h"
#include "src/numbers/conversions-inl.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/js-regexp-inl.h"
#include "src/regexp/regexp-utils.h"
#include "src/regexp/regexp.h"
#include "src/strings/string-builder-inl.h"
#include "src/strings/string-search.h"
namespace v8 {
namespace internal {
namespace {
constexpr int kStaticVectorSlots = 8;
uint32_t GetArgcForReplaceCallable(uint32_t num_captures,
bool has_named_captures) {
const uint32_t kAdditionalArgsWithoutNamedCaptures = 2;
const uint32_t kAdditionalArgsWithNamedCaptures = 3;
if (num_captures > Code::kMaxArguments) return -1;
uint32_t argc = has_named_captures
? num_captures + kAdditionalArgsWithNamedCaptures
: num_captures + kAdditionalArgsWithoutNamedCaptures;
static_assert(Code::kMaxArguments < std::numeric_limits<uint32_t>::max() -
kAdditionalArgsWithNamedCaptures);
return (argc > Code::kMaxArguments) ? -1 : argc;
}
template <typename Matcher, typename = std::enable_if<std::is_invocable_r_v<
bool, Matcher, Tagged<String>>>>
int LookupNamedCapture(Matcher name_matches,
Tagged<FixedArray> capture_name_map, int* index_in_out) {
DCHECK_GE(*index_in_out, 0);
int maybe_capture_index = -1;
const int named_capture_count = capture_name_map->length() >> 1;
DCHECK_LE(*index_in_out, named_capture_count);
for (int j = *index_in_out; j < named_capture_count; j++) {
const int name_ix = j * 2;
const int index_ix = j * 2 + 1;
Tagged<String> capture_name = Cast<String>(capture_name_map->get(name_ix));
if (!name_matches(capture_name)) continue;
maybe_capture_index = Smi::ToInt(capture_name_map->get(index_ix));
*index_in_out = j + 1;
break;
}
return maybe_capture_index;
}
}
class CompiledReplacement {
public:
explicit CompiledReplacement(Isolate* isolate)
: replacement_substrings_(isolate) {}
bool Compile(Isolate* isolate, DirectHandle<JSRegExp> regexp,
DirectHandle<RegExpData> regexp_data,
DirectHandle<String> replacement, int capture_count,
int subject_length);
void Apply(ReplacementStringBuilder* builder, int match_from, int match_to,
int32_t* match);
int parts() { return static_cast<int>(parts_.size()); }
private:
enum PartType {
SUBJECT_PREFIX = 1,
SUBJECT_SUFFIX,
SUBJECT_CAPTURE,
REPLACEMENT_SUBSTRING,
REPLACEMENT_STRING,
EMPTY_REPLACEMENT,
NUMBER_OF_PART_TYPES
};
struct ReplacementPart {
static inline ReplacementPart SubjectMatch() {
return ReplacementPart(SUBJECT_CAPTURE, 0);
}
static inline ReplacementPart SubjectCapture(int capture_index) {
return ReplacementPart(SUBJECT_CAPTURE, capture_index);
}
static inline ReplacementPart SubjectPrefix() {
return ReplacementPart(SUBJECT_PREFIX, 0);
}
static inline ReplacementPart SubjectSuffix(int subject_length) {
return ReplacementPart(SUBJECT_SUFFIX, subject_length);
}
static inline ReplacementPart ReplacementString() {
return ReplacementPart(REPLACEMENT_STRING, 0);
}
static inline ReplacementPart EmptyReplacement() {
return ReplacementPart(EMPTY_REPLACEMENT, 0);
}
static inline ReplacementPart ReplacementSubString(int from, int to) {
DCHECK_LE(0, from);
DCHECK_GT(to, from);
return ReplacementPart(-from, to);
}
ReplacementPart(int tag, int data) : tag(tag), data(data) {
DCHECK(tag < NUMBER_OF_PART_TYPES);
}
int tag;
int data;
};
template <typename Char>
bool ParseReplacementPattern(base::Vector<Char> characters,
Tagged<FixedArray> capture_name_map,
int capture_count, int subject_length) {
int length = characters.length();
int last = 0;
for (int i = 0; i < length; i++) {
Char c = characters[i];
if (c == '$') {
int next_index = i + 1;
if (next_index == length) {
break;
}
Char c2 = characters[next_index];
switch (c2) {
case '$':
if (i > last) {
parts_.emplace_back(
ReplacementPart::ReplacementSubString(last, next_index));
last = next_index + 1;
} else {
last = next_index;
}
i = next_index;
break;
case '`':
if (i > last) {
parts_.emplace_back(
ReplacementPart::ReplacementSubString(last, i));
}
parts_.emplace_back(ReplacementPart::SubjectPrefix());
i = next_index;
last = i + 1;
break;
case '\'':
if (i > last) {
parts_.emplace_back(
ReplacementPart::ReplacementSubString(last, i));
}
parts_.emplace_back(ReplacementPart::SubjectSuffix(subject_length));
i = next_index;
last = i + 1;
break;
case '&':
if (i > last) {
parts_.emplace_back(
ReplacementPart::ReplacementSubString(last, i));
}
parts_.emplace_back(ReplacementPart::SubjectMatch());
i = next_index;
last = i + 1;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
int capture_ref = c2 - '0';
if (capture_ref > capture_count) {
i = next_index;
continue;
}
int second_digit_index = next_index + 1;
if (second_digit_index < length) {
Char c3 = characters[second_digit_index];
if ('0' <= c3 && c3 <= '9') {
int double_digit_ref = capture_ref * 10 + c3 - '0';
if (double_digit_ref <= capture_count) {
next_index = second_digit_index;
capture_ref = double_digit_ref;
}
}
}
if (capture_ref > 0) {
if (i > last) {
parts_.emplace_back(
ReplacementPart::ReplacementSubString(last, i));
}
DCHECK(capture_ref <= capture_count);
parts_.emplace_back(ReplacementPart::SubjectCapture(capture_ref));
last = next_index + 1;
}
i = next_index;
break;
}
case '<': {
if (capture_name_map.is_null()) {
i = next_index;
break;
}
const int name_start_index = next_index + 1;
int closing_bracket_index = -1;
for (int j = name_start_index; j < length; j++) {
if (characters[j] == '>') {
closing_bracket_index = j;
break;
}
}
if (closing_bracket_index == -1) {
i = next_index;
break;
}
if (i > last) {
parts_.emplace_back(
ReplacementPart::ReplacementSubString(last, i));
}
base::Vector<Char> requested_name =
characters.SubVector(name_start_index, closing_bracket_index);
int capture_index = 0;
int capture_name_map_index = 0;
while (capture_index != -1) {
capture_index = LookupNamedCapture(
[=](Tagged<String> capture_name) {
return capture_name->IsEqualTo(requested_name);
},
capture_name_map, &capture_name_map_index);
DCHECK(capture_index == -1 ||
(1 <= capture_index && capture_index <= capture_count));
parts_.emplace_back(
capture_index == -1
? ReplacementPart::EmptyReplacement()
: ReplacementPart::SubjectCapture(capture_index));
}
last = closing_bracket_index + 1;
i = closing_bracket_index;
break;
}
default:
i = next_index;
break;
}
}
}
if (length > last) {
if (last == 0) {
return true;
} else {
parts_.emplace_back(
ReplacementPart::ReplacementSubString(last, length));
}
}
return false;
}
base::SmallVector<ReplacementPart, kStaticVectorSlots> parts_;
DirectHandleSmallVector<String, kStaticVectorSlots> replacement_substrings_;
};
bool CompiledReplacement::Compile(Isolate* isolate,
DirectHandle<JSRegExp> regexp,
DirectHandle<RegExpData> regexp_data,
DirectHandle<String> replacement,
int capture_count, int subject_length) {
{
DisallowGarbageCollection no_gc;
String::FlatContent content = replacement->GetFlatContent(no_gc);
DCHECK(content.IsFlat());
Tagged<FixedArray> capture_name_map;
if (capture_count > 0) {
Tagged<IrRegExpData> re_data = TrustedCast<IrRegExpData>(*regexp_data);
Tagged<Object> maybe_capture_name_map = re_data->capture_name_map();
if (IsFixedArray(maybe_capture_name_map)) {
capture_name_map = Cast<FixedArray>(maybe_capture_name_map);
}
}
bool simple;
if (content.IsOneByte()) {
simple =
ParseReplacementPattern(content.ToOneByteVector(), capture_name_map,
capture_count, subject_length);
} else {
DCHECK(content.IsTwoByte());
simple = ParseReplacementPattern(content.ToUC16Vector(), capture_name_map,
capture_count, subject_length);
}
if (simple) return true;
}
replacement_substrings_.reserve(parts());
int substring_index = 0;
for (ReplacementPart& part : parts_) {
int tag = part.tag;
if (tag <= 0) {
int from = -tag;
int to = part.data;
replacement_substrings_.emplace_back(
isolate->factory()->NewSubString(replacement, from, to));
part.tag = REPLACEMENT_SUBSTRING;
part.data = substring_index;
substring_index++;
} else if (tag == REPLACEMENT_STRING) {
replacement_substrings_.emplace_back(replacement);
part.data = substring_index;
substring_index++;
}
}
return false;
}
void CompiledReplacement::Apply(ReplacementStringBuilder* builder,
int match_from, int match_to, int32_t* match) {
DCHECK_LT(0, parts_.size());
for (ReplacementPart& part : parts_) {
switch (part.tag) {
case SUBJECT_PREFIX:
if (match_from > 0) builder->AddSubjectSlice(0, match_from);
break;
case SUBJECT_SUFFIX: {
int subject_length = part.data;
if (match_to < subject_length) {
builder->AddSubjectSlice(match_to, subject_length);
}
break;
}
case SUBJECT_CAPTURE: {
int capture = part.data;
int from = match[capture * 2];
int to = match[capture * 2 + 1];
if (from >= 0 && to > from) {
builder->AddSubjectSlice(from, to);
}
break;
}
case REPLACEMENT_SUBSTRING:
case REPLACEMENT_STRING:
builder->AddString(replacement_substrings_[part.data]);
break;
case EMPTY_REPLACEMENT:
break;
default:
UNREACHABLE();
}
}
}
void FindOneByteStringIndices(base::Vector<const uint8_t> subject,
uint8_t pattern, std::vector<int>* indices,
unsigned int limit) {
DCHECK_LT(0, limit);
const uint8_t* subject_start = subject.begin();
const uint8_t* subject_end = subject_start + subject.length();
const uint8_t* pos = subject_start;
while (limit > 0) {
pos = reinterpret_cast<const uint8_t*>(
memchr(pos, pattern, subject_end - pos));
if (pos == nullptr) return;
indices->push_back(static_cast<int>(pos - subject_start));
pos++;
limit--;
}
}
void FindTwoByteStringIndices(const base::Vector<const base::uc16> subject,
base::uc16 pattern, std::vector<int>* indices,
unsigned int limit) {
DCHECK_LT(0, limit);
const base::uc16* subject_start = subject.begin();
const base::uc16* subject_end = subject_start + subject.length();
for (const base::uc16* pos = subject_start; pos < subject_end && limit > 0;
pos++) {
if (*pos == pattern) {
indices->push_back(static_cast<int>(pos - subject_start));
limit--;
}
}
}
template <typename SubjectChar, typename PatternChar>
void FindStringIndices(Isolate* isolate,
base::Vector<const SubjectChar> subject,
base::Vector<const PatternChar> pattern,
std::vector<int>* indices, unsigned int limit) {
DCHECK_LT(0, limit);
int pattern_length = pattern.length();
int index = 0;
StringSearch<PatternChar, SubjectChar> search(isolate, pattern);
while (limit > 0) {
index = search.Search(subject, index);
if (index < 0) return;
indices->push_back(index);
index += pattern_length;
limit--;
}
}
void FindStringIndicesDispatch(Isolate* isolate, Tagged<String> subject,
Tagged<String> pattern,
std::vector<int>* indices, unsigned int limit) {
{
DisallowGarbageCollection no_gc;
String::FlatContent subject_content = subject->GetFlatContent(no_gc);
String::FlatContent pattern_content = pattern->GetFlatContent(no_gc);
DCHECK(subject_content.IsFlat());
DCHECK(pattern_content.IsFlat());
if (subject_content.IsOneByte()) {
base::Vector<const uint8_t> subject_vector =
subject_content.ToOneByteVector();
if (pattern_content.IsOneByte()) {
base::Vector<const uint8_t> pattern_vector =
pattern_content.ToOneByteVector();
if (pattern_vector.length() == 1) {
FindOneByteStringIndices(subject_vector, pattern_vector[0], indices,
limit);
} else {
FindStringIndices(isolate, subject_vector, pattern_vector, indices,
limit);
}
} else {
FindStringIndices(isolate, subject_vector,
pattern_content.ToUC16Vector(), indices, limit);
}
} else {
base::Vector<const base::uc16> subject_vector =
subject_content.ToUC16Vector();
if (pattern_content.IsOneByte()) {
base::Vector<const uint8_t> pattern_vector =
pattern_content.ToOneByteVector();
if (pattern_vector.length() == 1) {
FindTwoByteStringIndices(subject_vector, pattern_vector[0], indices,
limit);
} else {
FindStringIndices(isolate, subject_vector, pattern_vector, indices,
limit);
}
} else {
base::Vector<const base::uc16> pattern_vector =
pattern_content.ToUC16Vector();
if (pattern_vector.length() == 1) {
FindTwoByteStringIndices(subject_vector, pattern_vector[0], indices,
limit);
} else {
FindStringIndices(isolate, subject_vector, pattern_vector, indices,
limit);
}
}
}
}
}
namespace {
std::vector<int>* GetRewoundRegexpIndicesList(Isolate* isolate) {
std::vector<int>* list = isolate->regexp_indices();
list->clear();
return list;
}
void TruncateRegexpIndicesList(Isolate* isolate) {
static const int kMaxRegexpIndicesListCapacity = 8 * KB / kIntSize;
std::vector<int>* indices = isolate->regexp_indices();
if (indices->capacity() > kMaxRegexpIndicesListCapacity) {
indices->clear();
indices->shrink_to_fit();
}
}
}
template <typename ResultSeqString>
V8_WARN_UNUSED_RESULT static Tagged<UnionOf<ExceptionHole, String>>
StringReplaceGlobalAtomRegExpWithString(
Isolate* isolate, DirectHandle<String> subject,
DirectHandle<JSRegExp> pattern_regexp, DirectHandle<String> replacement,
DirectHandle<RegExpMatchInfo> last_match_info,
DirectHandle<AtomRegExpData> regexp_data) {
DCHECK(subject->IsFlat());
DCHECK(replacement->IsFlat());
std::vector<int>* indices = GetRewoundRegexpIndicesList(isolate);
Tagged<String> pattern = regexp_data->pattern();
int subject_len = subject->length();
int pattern_len = pattern->length();
int replacement_len = replacement->length();
FindStringIndicesDispatch(isolate, *subject, pattern, indices, 0xFFFFFFFF);
if (indices->empty()) return *subject;
int64_t result_len_64 = (static_cast<int64_t>(replacement_len) -
static_cast<int64_t>(pattern_len)) *
static_cast<int64_t>(indices->size()) +
static_cast<int64_t>(subject_len);
int result_len;
if (result_len_64 > static_cast<int64_t>(String::kMaxLength)) {
static_assert(String::kMaxLength < kMaxInt);
result_len = kMaxInt;
} else {
result_len = static_cast<int>(result_len_64);
}
if (result_len == 0) {
return ReadOnlyRoots(isolate).empty_string();
}
int subject_pos = 0;
int result_pos = 0;
MaybeDirectHandle<SeqString> maybe_res;
if (ResultSeqString::kHasOneByteEncoding) {
maybe_res = isolate->factory()->NewRawOneByteString(result_len);
} else {
maybe_res = isolate->factory()->NewRawTwoByteString(result_len);
}
DirectHandle<SeqString> untyped_res;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, untyped_res, maybe_res);
DirectHandle<ResultSeqString> result = Cast<ResultSeqString>(untyped_res);
DisallowGarbageCollection no_gc;
for (int index : *indices) {
if (subject_pos < index) {
String::WriteToFlat(*subject, result->GetChars(no_gc) + result_pos,
subject_pos, index - subject_pos);
result_pos += index - subject_pos;
}
if (replacement_len > 0) {
String::WriteToFlat(*replacement, result->GetChars(no_gc) + result_pos, 0,
replacement_len);
result_pos += replacement_len;
}
subject_pos = index + pattern_len;
}
if (subject_pos < subject_len) {
String::WriteToFlat(*subject, result->GetChars(no_gc) + result_pos,
subject_pos, subject_len - subject_pos);
}
int32_t match_indices[] = {indices->back(), indices->back() + pattern_len};
RegExp::SetLastMatchInfo(isolate, last_match_info, subject, 0, match_indices);
TruncateRegexpIndicesList(isolate);
return *result;
}
V8_WARN_UNUSED_RESULT static Tagged<UnionOf<ExceptionHole, String>>
StringReplaceGlobalRegExpWithString(
Isolate* isolate, DirectHandle<String> subject,
DirectHandle<JSRegExp> regexp, DirectHandle<RegExpData> regexp_data,
DirectHandle<String> replacement,
DirectHandle<RegExpMatchInfo> last_match_info) {
DCHECK(subject->IsFlat());
DCHECK(replacement->IsFlat());
int capture_count = regexp_data->capture_count();
int subject_length = subject->length();
if (!RegExp::EnsureFullyCompiled(isolate, regexp_data, subject)) {
return ReadOnlyRoots(isolate).exception();
}
CompiledReplacement compiled_replacement(isolate);
const bool simple_replace = compiled_replacement.Compile(
isolate, regexp, regexp_data, replacement, capture_count, subject_length);
if (regexp_data->type_tag() == RegExpData::Type::ATOM && simple_replace) {
if (subject->IsOneByteRepresentation() &&
replacement->IsOneByteRepresentation()) {
return StringReplaceGlobalAtomRegExpWithString<SeqOneByteString>(
isolate, subject, regexp, replacement, last_match_info,
TrustedCast<AtomRegExpData>(regexp_data));
} else {
return StringReplaceGlobalAtomRegExpWithString<SeqTwoByteString>(
isolate, subject, regexp, replacement, last_match_info,
TrustedCast<AtomRegExpData>(regexp_data));
}
}
RegExpGlobalExecRunner runner(direct_handle(*regexp_data, isolate), subject,
isolate);
if (runner.HasException()) return ReadOnlyRoots(isolate).exception();
int32_t* current_match = runner.FetchNext();
if (current_match == nullptr) {
if (runner.HasException()) return ReadOnlyRoots(isolate).exception();
return *subject;
}
int expected_parts = (compiled_replacement.parts() + 1) * 4 + 1;
ReplacementStringBuilder builder(isolate->heap(), subject, expected_parts);
int prev = 0;
do {
int start = current_match[0];
int end = current_match[1];
if (prev < start) {
builder.AddSubjectSlice(prev, start);
}
if (simple_replace) {
builder.AddString(replacement);
} else {
compiled_replacement.Apply(&builder, start, end, current_match);
}
prev = end;
current_match = runner.FetchNext();
} while (current_match != nullptr);
if (runner.HasException()) return ReadOnlyRoots(isolate).exception();
if (prev < subject_length) {
builder.AddSubjectSlice(prev, subject_length);
}
RegExp::SetLastMatchInfo(isolate, last_match_info, subject, capture_count,
runner.LastSuccessfulMatch());
RETURN_RESULT_OR_FAILURE(isolate, builder.ToString());
}
template <typename ResultSeqString>
V8_WARN_UNUSED_RESULT static Tagged<UnionOf<ExceptionHole, String>>
StringReplaceGlobalRegExpWithEmptyString(
Isolate* isolate, DirectHandle<String> subject,
DirectHandle<JSRegExp> regexp, DirectHandle<RegExpData> regexp_data,
DirectHandle<RegExpMatchInfo> last_match_info) {
DCHECK(subject->IsFlat());
if (regexp_data->type_tag() == RegExpData::Type::ATOM) {
DirectHandle<String> empty_string = isolate->factory()->empty_string();
if (subject->IsOneByteRepresentation()) {
return StringReplaceGlobalAtomRegExpWithString<SeqOneByteString>(
isolate, subject, regexp, empty_string, last_match_info,
TrustedCast<AtomRegExpData>(regexp_data));
} else {
return StringReplaceGlobalAtomRegExpWithString<SeqTwoByteString>(
isolate, subject, regexp, empty_string, last_match_info,
TrustedCast<AtomRegExpData>(regexp_data));
}
}
RegExpGlobalExecRunner runner(direct_handle(*regexp_data, isolate), subject,
isolate);
if (runner.HasException()) return ReadOnlyRoots(isolate).exception();
int32_t* current_match = runner.FetchNext();
if (current_match == nullptr) {
if (runner.HasException()) return ReadOnlyRoots(isolate).exception();
return *subject;
}
int start = current_match[0];
int end = current_match[1];
int capture_count = regexp_data->capture_count();
int subject_length = subject->length();
int new_length = subject_length - (end - start);
if (new_length == 0) return ReadOnlyRoots(isolate).empty_string();
DirectHandle<ResultSeqString> answer;
if (ResultSeqString::kHasOneByteEncoding) {
answer = Cast<ResultSeqString>(
isolate->factory()->NewRawOneByteString(new_length).ToHandleChecked());
} else {
answer = Cast<ResultSeqString>(
isolate->factory()->NewRawTwoByteString(new_length).ToHandleChecked());
}
int prev = 0;
int position = 0;
DisallowGarbageCollection no_gc;
do {
start = current_match[0];
end = current_match[1];
if (prev < start) {
String::WriteToFlat(*subject, answer->GetChars(no_gc) + position, prev,
start - prev);
position += start - prev;
}
prev = end;
current_match = runner.FetchNext();
} while (current_match != nullptr);
if (runner.HasException()) return ReadOnlyRoots(isolate).exception();
RegExp::SetLastMatchInfo(isolate, last_match_info, subject, capture_count,
runner.LastSuccessfulMatch());
if (prev < subject_length) {
String::WriteToFlat(*subject, answer->GetChars(no_gc) + position, prev,
subject_length - prev);
position += subject_length - prev;
}
if (position == 0) return ReadOnlyRoots(isolate).empty_string();
int string_size = ResultSeqString::SizeFor(position);
int allocated_string_size = ResultSeqString::SizeFor(new_length);
int delta = allocated_string_size - string_size;
answer->set_length(position);
if (delta == 0) return *answer;
Address end_of_string = answer->address() + string_size;
Heap* heap = isolate->heap();
if (!HeapLayout::InAnyLargeSpace(*answer)) {
heap->CreateFillerObjectAt(end_of_string, delta);
}
return *answer;
}
RUNTIME_FUNCTION(Runtime_StringSplit) {
HandleScope handle_scope(isolate);
DCHECK_EQ(3, args.length());
DirectHandle<String> subject = args.at<String>(0);
DirectHandle<String> pattern = args.at<String>(1);
uint32_t limit = NumberToUint32(args[2]);
CHECK_LT(0, limit);
int subject_length = subject->length();
int pattern_length = pattern->length();
CHECK_LT(0, pattern_length);
if (limit == 0xFFFFFFFFu) {
Tagged<FixedArray> last_match_cache_unused;
DirectHandle<Object> cached_answer(
RegExpResultsCache::Lookup(isolate->heap(), *subject, *pattern,
&last_match_cache_unused,
RegExpResultsCache::STRING_SPLIT_SUBSTRINGS),
isolate);
if (*cached_answer != Smi::zero()) {
DirectHandle<JSArray> result = isolate->factory()->NewJSArrayWithElements(
Cast<FixedArray>(cached_answer));
return *result;
}
}
subject = String::Flatten(isolate, subject);
pattern = String::Flatten(isolate, pattern);
std::vector<int>* indices = GetRewoundRegexpIndicesList(isolate);
FindStringIndicesDispatch(isolate, *subject, *pattern, indices, limit);
if (static_cast<uint32_t>(indices->size()) < limit) {
indices->push_back(subject_length);
}
int part_count = static_cast<int>(indices->size());
DirectHandle<JSArray> result = isolate->factory()->NewJSArray(
PACKED_ELEMENTS, part_count, part_count,
ArrayStorageAllocationMode::INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE);
DCHECK(result->HasObjectElements());
DirectHandle<FixedArray> elements(Cast<FixedArray>(result->elements()),
isolate);
if (part_count == 1 && indices->at(0) == subject_length) {
elements->set(0, *subject);
} else {
int part_start = 0;
FOR_WITH_HANDLE_SCOPE(isolate, int i = 0, i, i < part_count, i++) {
int part_end = indices->at(i);
DirectHandle<String> substring =
isolate->factory()->NewProperSubString(subject, part_start, part_end);
elements->set(i, *substring);
part_start = part_end + pattern_length;
}
}
if (limit == 0xFFFFFFFFu) {
if (result->HasObjectElements()) {
RegExpResultsCache::Enter(isolate, subject, pattern, elements,
isolate->factory()->empty_fixed_array(),
RegExpResultsCache::STRING_SPLIT_SUBSTRINGS);
}
}
TruncateRegexpIndicesList(isolate);
return *result;
}
namespace {
std::optional<int> RegExpExec(Isolate* isolate, DirectHandle<JSRegExp> regexp,
DirectHandle<String> subject, int32_t index,
int32_t* result_offsets_vector,
uint32_t result_offsets_vector_length) {
CHECK_LE(0, index);
CHECK_GE(subject->length(), index);
isolate->counters()->regexp_entry_runtime()->Increment();
return RegExp::Exec(isolate, regexp, subject, index, result_offsets_vector,
result_offsets_vector_length);
}
std::optional<int> ExperimentalOneshotExec(
Isolate* isolate, DirectHandle<JSRegExp> regexp,
DirectHandle<String> subject, int32_t index, int32_t* result_offsets_vector,
uint32_t result_offsets_vector_length) {
CHECK_GE(result_offsets_vector_length,
JSRegExp::RegistersForCaptureCount(
regexp->data(isolate)->capture_count()));
CHECK_LE(0, index);
CHECK_GE(subject->length(), index);
isolate->counters()->regexp_entry_runtime()->Increment();
return RegExp::ExperimentalOneshotExec(isolate, regexp, subject, index,
result_offsets_vector,
result_offsets_vector_length);
}
}
RUNTIME_FUNCTION(Runtime_RegExpExec) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
DirectHandle<JSRegExp> regexp = args.at<JSRegExp>(0);
DirectHandle<String> subject = args.at<String>(1);
int32_t index = 0;
CHECK(Object::ToInt32(args[2], &index));
uint32_t result_offsets_vector_length = 0;
CHECK(Object::ToUint32(args[3], &result_offsets_vector_length));
int32_t* result_offsets_vector = reinterpret_cast<int32_t*>(
isolate->isolate_data()->regexp_exec_vector_argument());
DCHECK_NOT_NULL(result_offsets_vector);
std::optional<int> result =
RegExpExec(isolate, regexp, subject, index, result_offsets_vector,
result_offsets_vector_length);
DCHECK_EQ(!result, isolate->has_exception());
if (!result) return ReadOnlyRoots(isolate).exception();
return Smi::FromInt(result.value());
}
RUNTIME_FUNCTION(Runtime_RegExpGrowRegExpMatchInfo) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
DirectHandle<RegExpMatchInfo> match_info = args.at<RegExpMatchInfo>(0);
int32_t register_count;
CHECK(Object::ToInt32(args[1], ®ister_count));
DCHECK_EQ(*match_info, *isolate->regexp_last_match_info());
DirectHandle<RegExpMatchInfo> result = RegExpMatchInfo::ReserveCaptures(
isolate, match_info, JSRegExp::CaptureCountForRegisters(register_count));
if (*result != *match_info) {
isolate->native_context()->set_regexp_last_match_info(*result);
}
return *result;
}
RUNTIME_FUNCTION(Runtime_RegExpExperimentalOneshotExec) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
DirectHandle<JSRegExp> regexp = args.at<JSRegExp>(0);
DirectHandle<String> subject = args.at<String>(1);
int32_t index = 0;
CHECK(Object::ToInt32(args[2], &index));
uint32_t result_offsets_vector_length = 0;
CHECK(Object::ToUint32(args[3], &result_offsets_vector_length));
int32_t* result_offsets_vector = reinterpret_cast<int32_t*>(
isolate->isolate_data()->regexp_exec_vector_argument());
DCHECK_NOT_NULL(result_offsets_vector);
std::optional<int> result = ExperimentalOneshotExec(
isolate, regexp, subject, index, result_offsets_vector,
result_offsets_vector_length);
DCHECK_EQ(!result, isolate->has_exception());
if (!result) return ReadOnlyRoots(isolate).exception();
return Smi::FromInt(result.value());
}
RUNTIME_FUNCTION(Runtime_RegExpBuildIndices) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
DirectHandle<RegExpMatchInfo> match_info = args.at<RegExpMatchInfo>(1);
DirectHandle<Object> maybe_names = args.at(2);
#ifdef DEBUG
DirectHandle<JSRegExp> regexp = args.at<JSRegExp>(0);
DCHECK(regexp->flags() & JSRegExp::kHasIndices);
#endif
return *JSRegExpResultIndices::BuildIndices(isolate, match_info, maybe_names);
}
namespace {
class MatchInfoBackedMatch : public String::Match {
public:
MatchInfoBackedMatch(Isolate* isolate, DirectHandle<JSRegExp> regexp,
DirectHandle<RegExpData> regexp_data,
DirectHandle<String> subject,
DirectHandle<RegExpMatchInfo> match_info)
: isolate_(isolate), match_info_(match_info) {
subject_ = String::Flatten(isolate, subject);
if (RegExpData::TypeSupportsCaptures(regexp_data->type_tag())) {
DCHECK(Is<IrRegExpData>(*regexp_data));
Tagged<Object> o =
TrustedCast<IrRegExpData>(regexp_data)->capture_name_map();
has_named_captures_ = IsFixedArray(o);
if (has_named_captures_) {
capture_name_map_ = direct_handle(Cast<FixedArray>(o), isolate);
}
} else {
has_named_captures_ = false;
}
}
DirectHandle<String> GetMatch() override {
return RegExpUtils::GenericCaptureGetter(isolate_, match_info_, 0, nullptr);
}
DirectHandle<String> GetPrefix() override {
const int match_start = match_info_->capture(0);
return isolate_->factory()->NewSubString(subject_, 0, match_start);
}
DirectHandle<String> GetSuffix() override {
const int match_end = match_info_->capture(1);
return isolate_->factory()->NewSubString(subject_, match_end,
subject_->length());
}
bool HasNamedCaptures() override { return has_named_captures_; }
int CaptureCount() override {
return match_info_->number_of_capture_registers() / 2;
}
MaybeDirectHandle<String> GetCapture(int i, bool* capture_exists) override {
DirectHandle<Object> capture_obj = RegExpUtils::GenericCaptureGetter(
isolate_, match_info_, i, capture_exists);
return (*capture_exists) ? Object::ToString(isolate_, capture_obj)
: isolate_->factory()->empty_string();
}
MaybeDirectHandle<String> GetNamedCapture(DirectHandle<String> name,
CaptureState* state) override {
DCHECK(has_named_captures_);
int capture_index = 0;
int capture_name_map_index = 0;
while (true) {
capture_index = LookupNamedCapture(
[=](Tagged<String> capture_name) {
return capture_name->Equals(*name);
},
*capture_name_map_, &capture_name_map_index);
if (capture_index == -1) {
*state = UNMATCHED;
return isolate_->factory()->empty_string();
}
if (RegExpUtils::IsMatchedCapture(*match_info_, capture_index)) {
DirectHandle<String> capture_value;
ASSIGN_RETURN_ON_EXCEPTION(
isolate_, capture_value,
Object::ToString(isolate_,
RegExpUtils::GenericCaptureGetter(
isolate_, match_info_, capture_index)));
*state = MATCHED;
return capture_value;
}
}
}
private:
Isolate* isolate_;
DirectHandle<String> subject_;
DirectHandle<RegExpMatchInfo> match_info_;
bool has_named_captures_;
DirectHandle<FixedArray> capture_name_map_;
};
class VectorBackedMatch : public String::Match {
public:
VectorBackedMatch(Isolate* isolate, DirectHandle<String> subject,
DirectHandle<String> match, uint32_t match_position,
base::Vector<DirectHandle<Object>> captures,
DirectHandle<Object> groups_obj)
: isolate_(isolate),
match_(match),
match_position_(match_position),
captures_(captures) {
subject_ = String::Flatten(isolate, subject);
DCHECK(IsUndefined(*groups_obj, isolate) || IsJSReceiver(*groups_obj));
has_named_captures_ = !IsUndefined(*groups_obj, isolate);
if (has_named_captures_) groups_obj_ = Cast<JSReceiver>(groups_obj);
}
DirectHandle<String> GetMatch() override { return match_; }
DirectHandle<String> GetPrefix() override {
uint32_t end = std::min(subject_->length(), match_position_);
return isolate_->factory()->NewSubString(subject_, 0, end);
}
DirectHandle<String> GetSuffix() override {
uint32_t start =
std::min(subject_->length(), match_position_ + match_->length());
return isolate_->factory()->NewSubString(subject_, start,
subject_->length());
}
bool HasNamedCaptures() override { return has_named_captures_; }
int CaptureCount() override { return captures_.length(); }
MaybeDirectHandle<String> GetCapture(int i, bool* capture_exists) override {
DirectHandle<Object> capture_obj = captures_[i];
if (IsUndefined(*capture_obj, isolate_)) {
*capture_exists = false;
return isolate_->factory()->empty_string();
}
*capture_exists = true;
return Object::ToString(isolate_, capture_obj);
}
MaybeDirectHandle<String> GetNamedCapture(DirectHandle<String> name,
CaptureState* state) override {
DCHECK(has_named_captures_);
{
size_t unused;
if (name->AsIntegerIndex(&unused)) {
*state = UNMATCHED;
return isolate_->factory()->empty_string();
}
}
DirectHandle<Object> capture_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate_, capture_obj,
Object::GetProperty(isolate_, groups_obj_, name));
if (IsUndefined(*capture_obj, isolate_)) {
*state = UNMATCHED;
return isolate_->factory()->empty_string();
} else {
*state = MATCHED;
return Object::ToString(isolate_, capture_obj);
}
}
private:
Isolate* isolate_;
DirectHandle<String> subject_;
DirectHandle<String> match_;
const uint32_t match_position_;
base::Vector<DirectHandle<Object>> captures_;
bool has_named_captures_;
DirectHandle<JSReceiver> groups_obj_;
};
template <typename FunctionType,
typename = std::enable_if_t<std::is_function_v<Tagged<Object>(int)>>>
DirectHandle<JSObject> ConstructNamedCaptureGroupsObject(
Isolate* isolate, DirectHandle<FixedArray> capture_map,
const FunctionType& f_get_capture) {
DirectHandle<JSObject> groups =
isolate->factory()->NewJSObjectWithNullProto();
const int named_capture_count = capture_map->length() >> 1;
for (int i = 0; i < named_capture_count; i++) {
const int name_ix = i * 2;
const int index_ix = i * 2 + 1;
DirectHandle<String> capture_name(Cast<String>(capture_map->get(name_ix)),
isolate);
const int capture_ix = Smi::ToInt(capture_map->get(index_ix));
DCHECK_GE(capture_ix, 1);
DirectHandle<Object> capture_value(f_get_capture(capture_ix), isolate);
DCHECK(IsUndefined(*capture_value, isolate) || IsString(*capture_value));
LookupIterator it(isolate, groups, capture_name, groups,
LookupIterator::OWN_SKIP_INTERCEPTOR);
if (it.IsFound()) {
DCHECK(v8_flags.js_regexp_duplicate_named_groups);
if (!IsUndefined(*capture_value, isolate)) {
DCHECK(IsUndefined(*it.GetDataValue(), isolate));
CHECK(Object::SetDataProperty(&it, capture_value).ToChecked());
}
} else {
CHECK(Object::AddDataProperty(&it, capture_value, NONE,
Just(ShouldThrow::kThrowOnError),
StoreOrigin::kNamed)
.IsJust());
}
}
return groups;
}
template <bool has_capture>
static Tagged<UnionOf<ExceptionHole, Null, FixedArray>> SearchRegExpMultiple(
Isolate* isolate, DirectHandle<String> subject,
DirectHandle<JSRegExp> regexp, DirectHandle<RegExpData> regexp_data,
DirectHandle<RegExpMatchInfo> last_match_array) {
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
DCHECK_NE(has_capture, regexp_data->capture_count() == 0);
DCHECK_IMPLIES(has_capture, Is<IrRegExpData>(*regexp_data));
DCHECK(subject->IsFlat());
if (v8_flags.regexp_tier_up &&
regexp_data->type_tag() == RegExpData::Type::IRREGEXP) {
TrustedCast<IrRegExpData>(regexp_data)->MarkTierUpForNextExec();
if (v8_flags.trace_regexp_tier_up) {
PrintF("Forcing tier-up of JSRegExp object %p in SearchRegExpMultiple\n",
reinterpret_cast<void*>(regexp->ptr()));
}
}
int capture_count = regexp_data->capture_count();
int subject_length = subject->length();
static const int kMinLengthToCache = 0x1000;
if (subject_length > kMinLengthToCache) {
Tagged<FixedArray> last_match_cache;
Tagged<Object> cached_answer = RegExpResultsCache::Lookup(
isolate->heap(), *subject, regexp_data->wrapper(), &last_match_cache,
RegExpResultsCache::REGEXP_MULTIPLE_INDICES);
if (IsFixedArray(cached_answer)) {
int capture_registers = JSRegExp::RegistersForCaptureCount(capture_count);
std::unique_ptr<int32_t[]> last_match(new int32_t[capture_registers]);
int32_t* raw_last_match = last_match.get();
for (int i = 0; i < capture_registers; i++) {
raw_last_match[i] = Smi::ToInt(last_match_cache->get(i));
}
DirectHandle<FixedArray> cached_fixed_array(
Cast<FixedArray>(cached_answer), isolate);
DirectHandle<FixedArray> copied_fixed_array =
isolate->factory()->CopyFixedArrayWithMap(
cached_fixed_array, isolate->factory()->fixed_array_map());
RegExp::SetLastMatchInfo(isolate, last_match_array, subject,
capture_count, raw_last_match);
return *copied_fixed_array;
}
}
RegExpGlobalExecRunner runner(direct_handle(*regexp_data, isolate), subject,
isolate);
if (runner.HasException()) return ReadOnlyRoots(isolate).exception();
FixedArrayBuilder builder = FixedArrayBuilder::Lazy(isolate);
int match_start = -1;
int match_end = 0;
bool first = true;
static const int kMaxBuilderEntriesPerRegExpMatch = 5;
while (true) {
int32_t* current_match = runner.FetchNext();
if (current_match == nullptr) break;
match_start = current_match[0];
builder.EnsureCapacity(isolate, kMaxBuilderEntriesPerRegExpMatch);
if (match_end < match_start) {
ReplacementStringBuilder::AddSubjectSlice(&builder, match_end,
match_start);
}
match_end = current_match[1];
{
HandleScope temp_scope(isolate);
DirectHandle<String> match;
if (!first) {
match = isolate->factory()->NewProperSubString(subject, match_start,
match_end);
} else {
match =
isolate->factory()->NewSubString(subject, match_start, match_end);
first = false;
}
if (has_capture) {
Tagged<IrRegExpData> re_data = TrustedCast<IrRegExpData>(*regexp_data);
DirectHandle<Object> maybe_capture_map(re_data->capture_name_map(),
isolate);
const bool has_named_captures = IsFixedArray(*maybe_capture_map);
const int argc =
has_named_captures ? 4 + capture_count : 3 + capture_count;
DirectHandle<FixedArray> elements =
isolate->factory()->NewFixedArray(argc);
int cursor = 0;
elements->set(cursor++, *match);
for (int i = 1; i <= capture_count; i++) {
int start = current_match[i * 2];
if (start >= 0) {
int end = current_match[i * 2 + 1];
DCHECK(start <= end);
DirectHandle<String> substring =
isolate->factory()->NewSubString(subject, start, end);
elements->set(cursor++, *substring);
} else {
DCHECK_GT(0, current_match[i * 2 + 1]);
elements->set(cursor++, ReadOnlyRoots(isolate).undefined_value());
}
}
elements->set(cursor++, Smi::FromInt(match_start));
elements->set(cursor++, *subject);
if (has_named_captures) {
DirectHandle<FixedArray> capture_map =
Cast<FixedArray>(maybe_capture_map);
DirectHandle<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);
}
}
}
if (runner.HasException()) return ReadOnlyRoots(isolate).exception();
if (match_start >= 0) {
if (match_end < subject_length) {
ReplacementStringBuilder::AddSubjectSlice(&builder, match_end,
subject_length);
}
RegExp::SetLastMatchInfo(isolate, last_match_array, subject, capture_count,
runner.LastSuccessfulMatch());
if (subject_length > kMinLengthToCache) {
int capture_registers = JSRegExp::RegistersForCaptureCount(capture_count);
DirectHandle<FixedArray> last_match_cache =
isolate->factory()->NewFixedArray(capture_registers);
int32_t* last_match = runner.LastSuccessfulMatch();
for (int i = 0; i < capture_registers; i++) {
last_match_cache->set(i, Smi::FromInt(last_match[i]));
}
DirectHandle<FixedArray> result_fixed_array =
FixedArray::RightTrimOrEmpty(isolate, builder.array(),
builder.length());
DirectHandle<FixedArray> copied_fixed_array =
isolate->factory()->CopyFixedArrayWithMap(
result_fixed_array, isolate->factory()->fixed_array_map());
RegExpResultsCache::Enter(
isolate, subject,
direct_handle(regexp->data(isolate)->wrapper(), isolate),
copied_fixed_array, last_match_cache,
RegExpResultsCache::REGEXP_MULTIPLE_INDICES);
}
return *builder.array();
} else {
return ReadOnlyRoots(isolate).null_value();
}
}
V8_WARN_UNUSED_RESULT MaybeDirectHandle<String> RegExpReplace(
Isolate* isolate, DirectHandle<JSRegExp> regexp,
DirectHandle<String> string, DirectHandle<String> replace) {
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
Factory* factory = isolate->factory();
const int flags = regexp->flags();
const bool global = (flags & JSRegExp::kGlobal) != 0;
const bool sticky = (flags & JSRegExp::kSticky) != 0;
replace = String::Flatten(isolate, replace);
DirectHandle<RegExpMatchInfo> last_match_info =
isolate->regexp_last_match_info();
DirectHandle<RegExpData> data(regexp->data(isolate), isolate);
if (!global) {
uint32_t last_index = 0;
if (sticky) {
DirectHandle<Object> last_index_obj(regexp->last_index(), isolate);
ASSIGN_RETURN_ON_EXCEPTION(isolate, last_index_obj,
Object::ToLength(isolate, last_index_obj));
last_index = PositiveNumberToUint32(*last_index_obj);
}
DirectHandle<Object> match_indices_obj(ReadOnlyRoots(isolate).null_value(),
isolate);
if (last_index <= static_cast<uint32_t>(string->length())) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, match_indices_obj,
RegExp::Exec_Single(isolate, regexp, string, last_index,
last_match_info));
}
if (IsNull(*match_indices_obj, isolate)) {
if (sticky) regexp->set_last_index(Smi::zero(), SKIP_WRITE_BARRIER);
return string;
}
auto match_indices = Cast<RegExpMatchInfo>(match_indices_obj);
const int start_index = match_indices->capture(0);
const int end_index = match_indices->capture(1);
if (sticky) {
regexp->set_last_index(Smi::FromInt(end_index), SKIP_WRITE_BARRIER);
}
IncrementalStringBuilder builder(isolate);
builder.AppendString(factory->NewSubString(string, 0, start_index));
if (replace->length() > 0) {
MatchInfoBackedMatch m(isolate, regexp, data, string, match_indices);
DirectHandle<String> replacement;
ASSIGN_RETURN_ON_EXCEPTION(isolate, replacement,
String::GetSubstitution(isolate, &m, replace));
builder.AppendString(replacement);
}
builder.AppendString(
factory->NewSubString(string, end_index, string->length()));
return builder.Finish();
} else {
DCHECK(global);
RETURN_ON_EXCEPTION(isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0));
if (v8_flags.regexp_tier_up &&
data->type_tag() == RegExpData::Type::IRREGEXP) {
TrustedCast<IrRegExpData>(data)->MarkTierUpForNextExec();
if (v8_flags.trace_regexp_tier_up) {
PrintF("Forcing tier-up of JSRegExp object %p in RegExpReplace\n",
reinterpret_cast<void*>(regexp->ptr()));
}
}
if (replace->length() == 0) {
if (string->IsOneByteRepresentation()) {
Tagged<UnionOf<ExceptionHole, String>> result =
StringReplaceGlobalRegExpWithEmptyString<SeqOneByteString>(
isolate, string, regexp, data, last_match_info);
return direct_handle(Cast<String>(result), isolate);
} else {
Tagged<UnionOf<ExceptionHole, String>> result =
StringReplaceGlobalRegExpWithEmptyString<SeqTwoByteString>(
isolate, string, regexp, data, last_match_info);
return direct_handle(Cast<String>(result), isolate);
}
}
Tagged<UnionOf<ExceptionHole, String>> result =
StringReplaceGlobalRegExpWithString(isolate, string, regexp, data,
replace, last_match_info);
if (IsExceptionHole(result)) {
return MaybeDirectHandle<String>();
} else {
return direct_handle(Cast<String>(result), isolate);
}
}
UNREACHABLE();
}
}
RUNTIME_FUNCTION(Runtime_RegExpExecMultiple) {
HandleScope handles(isolate);
DCHECK_EQ(3, args.length());
DirectHandle<JSRegExp> regexp = args.at<JSRegExp>(0);
DirectHandle<String> subject = args.at<String>(1);
DirectHandle<RegExpMatchInfo> last_match_info = args.at<RegExpMatchInfo>(2);
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
DirectHandle<RegExpData> regexp_data(regexp->data(isolate), isolate);
subject = String::Flatten(isolate, subject);
CHECK(regexp->flags() & JSRegExp::kGlobal);
Tagged<UnionOf<ExceptionHole, Null, FixedArray>> result;
if (regexp_data->capture_count() == 0) {
result = SearchRegExpMultiple<false>(isolate, subject, regexp, regexp_data,
last_match_info);
} else {
result = SearchRegExpMultiple<true>(isolate, subject, regexp, regexp_data,
last_match_info);
}
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
return result;
}
RUNTIME_FUNCTION(Runtime_StringReplaceNonGlobalRegExpWithFunction) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
DirectHandle<String> subject = args.at<String>(0);
DirectHandle<JSRegExp> regexp = args.at<JSRegExp>(1);
DirectHandle<JSReceiver> replace_obj = args.at<JSReceiver>(2);
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp));
DCHECK(replace_obj->map()->is_callable());
Factory* factory = isolate->factory();
DirectHandle<RegExpMatchInfo> last_match_info =
isolate->regexp_last_match_info();
DirectHandle<RegExpData> data(regexp->data(isolate), isolate);
const int flags = regexp->flags();
DCHECK_EQ(flags & JSRegExp::kGlobal, 0);
const bool sticky = (flags & JSRegExp::kSticky) != 0;
uint32_t last_index = 0;
if (sticky) {
DirectHandle<Object> last_index_obj(regexp->last_index(), isolate);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, last_index_obj, Object::ToLength(isolate, last_index_obj));
last_index = PositiveNumberToUint32(*last_index_obj);
}
DirectHandle<Object> match_indices_obj(ReadOnlyRoots(isolate).null_value(),
isolate);
if (last_index <= static_cast<uint32_t>(subject->length())) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, match_indices_obj,
RegExp::Exec_Single(isolate, regexp, subject, last_index,
last_match_info));
}
if (IsNull(*match_indices_obj, isolate)) {
if (sticky) regexp->set_last_index(Smi::zero(), SKIP_WRITE_BARRIER);
return *subject;
}
auto match_indices = Cast<RegExpMatchInfo>(match_indices_obj);
const int index = match_indices->capture(0);
const int end_of_match = match_indices->capture(1);
if (sticky) {
regexp->set_last_index(Smi::FromInt(end_of_match), SKIP_WRITE_BARRIER);
}
IncrementalStringBuilder builder(isolate);
builder.AppendString(factory->NewSubString(subject, 0, index));
const int m = match_indices->number_of_capture_registers() / 2;
bool has_named_captures = false;
DirectHandle<FixedArray> capture_map;
if (m > 1) {
Tagged<Object> maybe_capture_map =
SbxCast<IrRegExpData>(data)->capture_name_map();
if (IsFixedArray(maybe_capture_map)) {
has_named_captures = true;
capture_map = direct_handle(Cast<FixedArray>(maybe_capture_map), isolate);
}
}
const uint32_t argc = GetArgcForReplaceCallable(m, has_named_captures);
if (argc == static_cast<uint32_t>(-1)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kTooManyArguments));
}
DirectHandleVector<Object> arguments(isolate, argc);
int cursor = 0;
for (int j = 0; j < m; j++) {
bool ok;
DirectHandle<String> capture =
RegExpUtils::GenericCaptureGetter(isolate, match_indices, j, &ok);
if (ok) {
arguments[cursor++] = capture;
} else {
arguments[cursor++] = factory->undefined_value();
}
}
arguments[cursor++] = direct_handle(Smi::FromInt(index), isolate);
arguments[cursor++] = subject;
if (has_named_captures) {
arguments[cursor++] = ConstructNamedCaptureGroupsObject(
isolate, capture_map, [&arguments](int ix) { return *arguments[ix]; });
}
DCHECK_EQ(cursor, argc);
DirectHandle<Object> replacement_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, replacement_obj,
Execution::Call(isolate, replace_obj, factory->undefined_value(),
base::VectorOf(arguments)));
DirectHandle<String> replacement;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, replacement, Object::ToString(isolate, replacement_obj));
builder.AppendString(replacement);
builder.AppendString(
factory->NewSubString(subject, end_of_match, subject->length()));
RETURN_RESULT_OR_FAILURE(isolate, builder.Finish());
}
namespace {
V8_WARN_UNUSED_RESULT MaybeDirectHandle<Object> ToUint32(
Isolate* isolate, DirectHandle<Object> object, uint32_t* out) {
if (IsUndefined(*object, isolate)) {
*out = kMaxUInt32;
return object;
}
DirectHandle<Object> number;
ASSIGN_RETURN_ON_EXCEPTION(isolate, number,
Object::ToNumber(isolate, object));
*out = NumberToUint32(*number);
return object;
}
DirectHandle<JSArray> NewJSArrayWithElements(Isolate* isolate,
DirectHandle<FixedArray> elems,
int num_elems) {
return isolate->factory()->NewJSArrayWithElements(
FixedArray::RightTrimOrEmpty(isolate, elems, num_elems));
}
}
RUNTIME_FUNCTION(Runtime_RegExpSplit) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
DirectHandle<JSReceiver> recv = args.at<JSReceiver>(0);
DirectHandle<String> string = args.at<String>(1);
DirectHandle<Object> limit_obj = args.at(2);
Factory* factory = isolate->factory();
DirectHandle<JSFunction> regexp_fun = isolate->regexp_function();
DirectHandle<Object> ctor;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, ctor, Object::SpeciesConstructor(isolate, recv, regexp_fun));
DirectHandle<Object> flags_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, flags_obj,
JSObject::GetProperty(isolate, recv, factory->flags_string()));
DirectHandle<String> flags;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flags,
Object::ToString(isolate, flags_obj));
DirectHandle<String> u_str =
factory->LookupSingleCharacterStringFromCode('u');
const bool unicode = (String::IndexOf(isolate, flags, u_str, 0) >= 0);
DirectHandle<String> y_str =
factory->LookupSingleCharacterStringFromCode('y');
const bool sticky = (String::IndexOf(isolate, flags, y_str, 0) >= 0);
DirectHandle<String> new_flags = flags;
if (!sticky) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, new_flags,
factory->NewConsString(flags, y_str));
}
DirectHandle<JSReceiver> splitter;
{
constexpr int argc = 2;
std::array<DirectHandle<Object>, argc> ctor_args = {recv, new_flags};
DirectHandle<Object> splitter_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, splitter_obj,
Execution::New(isolate, ctor, base::VectorOf(ctor_args)));
splitter = Cast<JSReceiver>(splitter_obj);
}
uint32_t limit;
RETURN_FAILURE_ON_EXCEPTION(isolate, ToUint32(isolate, limit_obj, &limit));
const uint32_t length = string->length();
if (limit == 0) return *factory->NewJSArray(0);
if (length == 0) {
DirectHandle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, RegExpUtils::RegExpExec(isolate, splitter, string,
factory->undefined_value()));
if (!IsNull(*result, isolate)) return *factory->NewJSArray(0);
DirectHandle<FixedArray> elems = factory->NewFixedArray(1);
elems->set(0, *string);
return *factory->NewJSArrayWithElements(elems);
}
static const int kInitialArraySize = 8;
DirectHandle<FixedArray> elems =
factory->NewFixedArrayWithHoles(kInitialArraySize);
uint32_t num_elems = 0;
uint32_t string_index = 0;
uint32_t prev_string_index = 0;
while (string_index < length) {
RETURN_FAILURE_ON_EXCEPTION(
isolate, RegExpUtils::SetLastIndex(isolate, splitter, string_index));
DirectHandle<JSAny> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, RegExpUtils::RegExpExec(isolate, splitter, string,
factory->undefined_value()));
if (IsNull(*result, isolate)) {
string_index = static_cast<uint32_t>(
RegExpUtils::AdvanceStringIndex(*string, string_index, unicode));
continue;
}
DirectHandle<Object> last_index_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, last_index_obj, RegExpUtils::GetLastIndex(isolate, splitter));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, last_index_obj, Object::ToLength(isolate, last_index_obj));
const uint32_t end =
std::min(PositiveNumberToUint32(*last_index_obj), length);
if (end == prev_string_index) {
string_index = static_cast<uint32_t>(
RegExpUtils::AdvanceStringIndex(*string, string_index, unicode));
continue;
}
{
DirectHandle<String> substr =
factory->NewSubString(string, prev_string_index, string_index);
elems = FixedArray::SetAndGrow(isolate, elems, num_elems++, substr);
if (num_elems == limit) {
return *NewJSArrayWithElements(isolate, elems, num_elems);
}
}
prev_string_index = end;
DirectHandle<Object> num_captures_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, num_captures_obj,
Object::GetProperty(isolate, result,
isolate->factory()->length_string()));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, num_captures_obj, Object::ToLength(isolate, num_captures_obj));
const uint32_t num_captures = PositiveNumberToUint32(*num_captures_obj);
for (uint32_t i = 1; i < num_captures; i++) {
DirectHandle<Object> capture;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, capture, Object::GetElement(isolate, result, i));
elems = FixedArray::SetAndGrow(isolate, elems, num_elems++, capture);
if (num_elems == limit) {
return *NewJSArrayWithElements(isolate, elems, num_elems);
}
}
string_index = prev_string_index;
}
{
DirectHandle<String> substr =
factory->NewSubString(string, prev_string_index, length);
elems = FixedArray::SetAndGrow(isolate, elems, num_elems++, substr);
}
return *NewJSArrayWithElements(isolate, elems, num_elems);
}
namespace {
template <typename Char>
inline bool IsContainFlagImpl(Isolate* isolate, base::Vector<const Char> flags,
const char* target,
DisallowGarbageCollection& no_gc) {
StringSearch<uint8_t, Char> search(isolate, base::OneByteVector(target));
return search.Search(flags, 0) >= 0;
}
inline bool IsContainFlag(Isolate* isolate, String::FlatContent& flags,
const char* target,
DisallowGarbageCollection& no_gc) {
return flags.IsOneByte()
? IsContainFlagImpl<uint8_t>(isolate, flags.ToOneByteVector(),
target, no_gc)
: IsContainFlagImpl<base::uc16>(isolate, flags.ToUC16Vector(),
target, no_gc);
}
}
RUNTIME_FUNCTION(Runtime_RegExpReplaceRT) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
DirectHandle<JSReceiver> recv = args.at<JSReceiver>(0);
DirectHandle<String> string = args.at<String>(1);
DirectHandle<Object> replace_obj = args.at(2);
Factory* factory = isolate->factory();
string = String::Flatten(isolate, string);
const bool functional_replace = IsCallable(*replace_obj);
DirectHandle<String> replace;
if (!functional_replace) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, replace,
Object::ToString(isolate, replace_obj));
}
if (RegExpUtils::IsUnmodifiedRegExp(isolate, recv)) {
CHECK(!functional_replace);
DirectHandle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
RegExpReplace(isolate, Cast<JSRegExp>(recv), string, replace));
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, recv));
return *result;
}
const uint32_t length = string->length();
bool global = false;
bool fullUnicode = false;
DirectHandle<Object> flags_obj;
DirectHandle<String> flag_str;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, flags_obj,
JSReceiver::GetProperty(isolate, recv, factory->flags_string()));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, flag_str,
Object::ToString(isolate, flags_obj));
flag_str = String::Flatten(isolate, flag_str);
{
DisallowGarbageCollection no_gc;
String::FlatContent flat_flag = flag_str->GetFlatContent(no_gc);
global = IsContainFlag(isolate, flat_flag, "g", no_gc);
if (global) {
fullUnicode = IsContainFlag(isolate, flat_flag, "u", no_gc) ||
IsContainFlag(isolate, flat_flag, "v", no_gc);
}
}
if (global) {
RETURN_FAILURE_ON_EXCEPTION(isolate,
RegExpUtils::SetLastIndex(isolate, recv, 0));
}
DirectHandleSmallVector<JSAny, kStaticVectorSlots> results(isolate);
while (true) {
DirectHandle<JSAny> result;
{
HandleScope inner_scope(isolate);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
RegExpUtils::RegExpExec(isolate, recv, string,
factory->undefined_value()));
result = inner_scope.CloseAndEscape(result);
}
if (IsNull(*result, isolate)) break;
results.emplace_back(result);
if (!global) break;
{
HandleScope inner_scope(isolate);
DirectHandle<Object> match_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, match_obj, Object::GetElement(isolate, result, 0));
DirectHandle<String> match;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match,
Object::ToString(isolate, match_obj));
if (match->length() == 0) {
RETURN_FAILURE_ON_EXCEPTION(
isolate, RegExpUtils::SetAdvancedStringIndex(isolate, recv, string,
fullUnicode));
}
}
}
IncrementalStringBuilder builder(isolate);
uint32_t next_source_position = 0;
for (const auto& result : results) {
HandleScope handle_scope(isolate);
DirectHandle<Object> captures_length_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, captures_length_obj,
Object::GetProperty(isolate, result, factory->length_string()));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, captures_length_obj,
Object::ToLength(isolate, captures_length_obj));
const uint32_t captures_length =
PositiveNumberToUint32(*captures_length_obj);
DirectHandle<Object> match_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj,
Object::GetElement(isolate, result, 0));
DirectHandle<String> match;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match,
Object::ToString(isolate, match_obj));
const int match_length = match->length();
DirectHandle<Object> position_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, position_obj,
Object::GetProperty(isolate, result, factory->index_string()));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, position_obj, Object::ToInteger(isolate, position_obj));
const uint32_t position =
std::min(PositiveNumberToUint32(*position_obj), length);
DirectHandleSmallVector<Object, kStaticVectorSlots> captures(isolate);
captures.emplace_back(match);
for (uint32_t n = 1; n < captures_length; n++) {
DirectHandle<Object> capture;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, capture, Object::GetElement(isolate, result, n));
if (!IsUndefined(*capture, isolate)) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, capture,
Object::ToString(isolate, capture));
}
captures.emplace_back(capture);
}
DirectHandle<Object> groups_obj = isolate->factory()->undefined_value();
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, groups_obj,
Object::GetProperty(isolate, result, factory->groups_string()));
const bool has_named_captures = !IsUndefined(*groups_obj, isolate);
DirectHandle<String> replacement;
if (functional_replace) {
const uint32_t argc = GetArgcForReplaceCallable(
static_cast<uint32_t>(captures.size()), has_named_captures);
if (argc == static_cast<uint32_t>(-1)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kTooManyArguments));
}
DirectHandleVector<Object> call_args(isolate, argc);
int cursor = 0;
for (uint32_t j = 0; j < captures.size(); j++) {
call_args[cursor++] = captures[j];
}
call_args[cursor++] = direct_handle(Smi::FromInt(position), isolate);
call_args[cursor++] = string;
if (has_named_captures) call_args[cursor++] = groups_obj;
DCHECK_EQ(cursor, argc);
DirectHandle<Object> replacement_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, replacement_obj,
Execution::Call(isolate, replace_obj, factory->undefined_value(),
base::VectorOf(call_args)));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, replacement, Object::ToString(isolate, replacement_obj));
} else {
DCHECK(!functional_replace);
if (!IsUndefined(*groups_obj, isolate)) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, groups_obj, Object::ToObject(isolate, groups_obj));
}
VectorBackedMatch m(isolate, string, match, position,
base::VectorOf(captures), groups_obj);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, replacement, String::GetSubstitution(isolate, &m, replace));
}
if (position >= next_source_position) {
builder.AppendString(
factory->NewSubString(string, next_source_position, position));
builder.AppendString(replacement);
next_source_position = position + match_length;
}
}
if (next_source_position < length) {
builder.AppendString(
factory->NewSubString(string, next_source_position, length));
}
RETURN_RESULT_OR_FAILURE(isolate, builder.Finish());
}
RUNTIME_FUNCTION(Runtime_RegExpInitializeAndCompile) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
DirectHandle<JSRegExp> regexp = args.at<JSRegExp>(0);
DirectHandle<String> source = args.at<String>(1);
DirectHandle<String> flags = args.at<String>(2);
RETURN_FAILURE_ON_EXCEPTION(
isolate, JSRegExp::Initialize(isolate, regexp, source, flags));
return *regexp;
}
RUNTIME_FUNCTION(Runtime_RegExpStringFromFlags) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
auto regexp = Cast<JSRegExp>(args[0]);
DirectHandle<String> flags =
JSRegExp::StringFromFlags(isolate, regexp->flags());
return *flags;
}
namespace {
template <typename SChar, typename PChar>
inline void RegExpMatchGlobalAtom_OneCharPattern(
Isolate* isolate, base::Vector<const SChar> subject, const PChar pattern,
int start_index, int* number_of_matches, int* last_match_index,
const DisallowGarbageCollection& no_gc) {
static_assert(std::is_unsigned_v<SChar>);
static_assert(std::is_unsigned_v<PChar>);
namespace hw = hwy::HWY_NAMESPACE;
hw::ScalableTag<SChar> tag;
using WidenedTag = hw::RepartitionToWide<decltype(tag)>;
WidenedTag sum_tag;
static const size_t stride = hw::Lanes(tag);
const auto mask = hw::Set(tag, static_cast<const SChar>(pattern));
int matches = 0;
auto submatches = hw::Zero(tag);
const SChar* last_match_block = nullptr;
hw::Mask<decltype(tag)> last_match_vec;
const SChar* block = subject.data() + start_index;
const SChar* end = subject.data() + subject.length();
int max_count = std::numeric_limits<SChar>::max();
while (stride * max_count <= static_cast<size_t>(end - block)) {
for (int i = 0; i < max_count; i++, block += stride) {
const auto input = hw::LoadU(tag, block);
const auto match = hw::Eq(input, mask);
submatches = hw::Sub(submatches, hw::VecFromMask(tag, match));
if (!hw::AllFalse(tag, match)) {
last_match_block = block;
last_match_vec = match;
}
}
auto promoted_submatches = hw::SumsOf2(submatches);
matches += hw::ReduceSum(sum_tag, promoted_submatches);
submatches = hw::Zero(tag);
}
DCHECK_LT(end - block, stride * max_count);
for (; stride <= static_cast<size_t>(end - block); block += stride) {
const auto input = hw::LoadU(tag, block);
const auto match = hw::Eq(input, mask);
submatches = hw::Sub(submatches, hw::VecFromMask(tag, match));
if (!hw::AllFalse(tag, match)) {
last_match_block = block;
last_match_vec = match;
}
}
auto promoted_submatches = hw::SumsOf2(submatches);
matches += hw::ReduceSum(sum_tag, promoted_submatches);
const auto scalar_last_match_vec = hw::FirstN(tag, 1);
for (; block < end; ++block) {
SChar c = *block;
if (c != static_cast<const SChar>(pattern)) continue;
matches++;
last_match_block = block;
last_match_vec = scalar_last_match_vec;
}
*number_of_matches += matches;
if (last_match_block != nullptr) {
DCHECK(!hw::AllFalse(tag, last_match_vec));
*last_match_index = static_cast<int>(
last_match_block + hw::FindKnownLastTrue(tag, last_match_vec) -
subject.data());
}
}
template <>
inline void RegExpMatchGlobalAtom_OneCharPattern(
Isolate* isolate, base::Vector<const uint8_t> subject,
const base::uc16 pattern, int start_index, int* number_of_matches,
int* last_match_index, const DisallowGarbageCollection& no_gc) = delete;
template <typename Char>
inline int AdvanceStringIndex(base::Vector<const Char> subject, int index,
bool is_unicode) {
const int subject_length = subject.length();
if (is_unicode && index < subject_length) {
const uint16_t first = subject[index];
if (first >= 0xD800 && first <= 0xDBFF && index + 1 < subject_length) {
DCHECK_LT(index, std::numeric_limits<int>::max());
const uint16_t second = subject[index + 1];
if (second >= 0xDC00 && second <= 0xDFFF) {
return index + 2;
}
}
}
return index + 1;
}
template <typename SChar, typename PChar>
inline void RegExpMatchGlobalAtom_Generic(
Isolate* isolate, base::Vector<const SChar> subject,
base::Vector<const PChar> pattern, bool is_unicode, int start_index,
int* number_of_matches, int* last_match_index,
const DisallowGarbageCollection& no_gc) {
const int pattern_length = pattern.length();
StringSearch<PChar, SChar> search(isolate, pattern);
int found_at_index;
while (true) {
found_at_index = search.Search(subject, start_index);
if (found_at_index == -1) return;
(*number_of_matches)++;
(*last_match_index) = found_at_index;
start_index = pattern_length > 0
? found_at_index + pattern_length
: AdvanceStringIndex(subject, start_index, is_unicode);
}
}
inline void RegExpMatchGlobalAtom_Dispatch(
Isolate* isolate, const String::FlatContent& subject,
const String::FlatContent& pattern, bool is_unicode, int start_index,
int* number_of_matches, int* last_match_index,
const DisallowGarbageCollection& no_gc) {
#define CALL_Generic() \
RegExpMatchGlobalAtom_Generic(isolate, sv, pv, is_unicode, start_index, \
number_of_matches, last_match_index, no_gc);
#define CALL_OneCharPattern() \
RegExpMatchGlobalAtom_OneCharPattern(isolate, sv, pv[0], start_index, \
number_of_matches, last_match_index, \
no_gc);
DCHECK_NOT_NULL(number_of_matches);
DCHECK_NOT_NULL(last_match_index);
if (pattern.IsOneByte()) {
auto pv = pattern.ToOneByteVector();
if (subject.IsOneByte()) {
auto sv = subject.ToOneByteVector();
if (pattern.length() == 1) {
CALL_OneCharPattern();
} else {
CALL_Generic();
}
} else {
auto sv = subject.ToUC16Vector();
if (pattern.length() == 1) {
CALL_OneCharPattern();
} else {
CALL_Generic();
}
}
} else {
auto pv = pattern.ToUC16Vector();
if (subject.IsOneByte()) {
auto sv = subject.ToOneByteVector();
CALL_Generic();
} else {
auto sv = subject.ToUC16Vector();
if (pattern.length() == 1) {
CALL_OneCharPattern();
} else {
CALL_Generic();
}
}
}
#undef CALL_OneCharPattern
#undef CALL_Generic
}
}
RUNTIME_FUNCTION(Runtime_RegExpMatchGlobalAtom) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
DirectHandle<JSRegExp> regexp_handle = args.at<JSRegExp>(0);
DirectHandle<String> subject_handle =
String::Flatten(isolate, args.at<String>(1));
DirectHandle<AtomRegExpData> data_handle =
SbxCast<AtomRegExpData>(args.at<Object>(2));
DCHECK(RegExpUtils::IsUnmodifiedRegExp(isolate, regexp_handle));
DCHECK(regexp_handle->flags() & JSRegExp::kGlobal);
DCHECK_EQ(data_handle->type_tag(), RegExpData::Type::ATOM);
DirectHandle<String> pattern_handle;
int pattern_length;
int number_of_matches = 0;
int last_match_index = -1;
{
DisallowGarbageCollection no_gc;
Tagged<JSRegExp> regexp = *regexp_handle;
Tagged<String> subject = *subject_handle;
Tagged<String> pattern = data_handle->pattern();
DCHECK(pattern->IsFlat());
pattern_handle = direct_handle(pattern, isolate);
pattern_length = pattern->length();
regexp->set_last_index(Smi::zero(), SKIP_WRITE_BARRIER);
int start_index = 0;
if (RegExpResultsCache_MatchGlobalAtom::TryGet(
isolate, subject, pattern, &number_of_matches, &last_match_index)) {
DCHECK_GT(number_of_matches, 0);
DCHECK_NE(last_match_index, -1);
start_index = last_match_index + pattern_length;
}
const bool is_unicode = (regexp->flags() & JSRegExp::kUnicode) != 0;
String::FlatContent subject_content = subject->GetFlatContent(no_gc);
String::FlatContent pattern_content = pattern->GetFlatContent(no_gc);
RegExpMatchGlobalAtom_Dispatch(isolate, subject_content, pattern_content,
is_unicode, start_index, &number_of_matches,
&last_match_index, no_gc);
if (last_match_index == -1) {
return ReadOnlyRoots(isolate).null_value();
}
DCHECK_GE(last_match_index, 0);
RegExpResultsCache_MatchGlobalAtom::TryInsert(
isolate, subject, pattern, number_of_matches, last_match_index);
}
static constexpr int kNumberOfCaptures = 0;
int32_t match_indices[] = {last_match_index,
last_match_index + pattern_length};
DirectHandle<RegExpMatchInfo> last_match_info =
isolate->regexp_last_match_info();
RegExp::SetLastMatchInfo(isolate, last_match_info, subject_handle,
kNumberOfCaptures, match_indices);
auto elems = isolate->factory()->NewFixedArray(number_of_matches);
ObjectSlot dst_slot = elems->RawFieldOfFirstElement();
MemsetTagged(dst_slot, *pattern_handle, number_of_matches);
if (!HeapLayout::InReadOnlySpace(*pattern_handle)) {
WriteBarrier::ForRange(isolate->heap(), *elems, dst_slot,
dst_slot + number_of_matches);
}
DirectHandle<JSArray> result = isolate->factory()->NewJSArrayWithElements(
elems, TERMINAL_FAST_ELEMENTS_KIND, number_of_matches);
return *result;
}
}
}