// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/runtime/runtime-utils.h"
#include "src/arguments.h"
#include "src/conversions-inl.h"
#include "src/isolate-inl.h"
#include "src/messages.h"
#include "src/regexp/jsregexp-inl.h"
#include "src/regexp/jsregexp.h"
#include "src/regexp/regexp-utils.h"
#include "src/string-builder.h"
#include "src/string-search.h"
namespace v8 {
namespace internal {
class CompiledReplacement {
public:
explicit CompiledReplacement(Zone* zone)
: parts_(1, zone), replacement_substrings_(0, zone), zone_(zone) {}
// Return whether the replacement is simple.
bool Compile(Handle<String> replacement, int capture_count,
int subject_length);
// Use Apply only if Compile returned false.
void Apply(ReplacementStringBuilder* builder, int match_from, int match_to,
int32_t* match);
// Number of distinct parts of the replacement pattern.
int parts() { return parts_.length(); }
Zone* zone() const { return zone_; }
private:
enum PartType {
SUBJECT_PREFIX = 1,
SUBJECT_SUFFIX,
SUBJECT_CAPTURE,
REPLACEMENT_SUBSTRING,
REPLACEMENT_STRING,
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 ReplacementSubString(int from, int to) {
DCHECK(from >= 0);
DCHECK(to > from);
return ReplacementPart(-from, to);
}
// If tag <= 0 then it is the negation of a start index of a substring of
// the replacement pattern, otherwise it's a value from PartType.
ReplacementPart(int tag, int data) : tag(tag), data(data) {
// Must be non-positive or a PartType value.
DCHECK(tag < NUMBER_OF_PART_TYPES);
}
// Either a value of PartType or a non-positive number that is
// the negation of an index into the replacement string.
int tag;
// The data value's interpretation depends on the value of tag:
// tag == SUBJECT_PREFIX ||
// tag == SUBJECT_SUFFIX: data is unused.
// tag == SUBJECT_CAPTURE: data is the number of the capture.
// tag == REPLACEMENT_SUBSTRING ||
// tag == REPLACEMENT_STRING: data is index into array of substrings
// of the replacement string.
// tag <= 0: Temporary representation of the substring of the replacement
// string ranging over -tag .. data.
// Is replaced by REPLACEMENT_{SUB,}STRING when we create the
// substring objects.
int data;
};
template <typename Char>
bool ParseReplacementPattern(ZoneList<ReplacementPart>* parts,
Vector<Char> characters, int capture_count,
int subject_length, Zone* zone) {
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) { // No next character!
break;
}
Char c2 = characters[next_index];
switch (c2) {
case '$':
if (i > last) {
// There is a substring before. Include the first "$".
parts->Add(
ReplacementPart::ReplacementSubString(last, next_index),
zone);
last = next_index + 1; // Continue after the second "$".
} else {
// Let the next substring start with the second "$".
last = next_index;
}
i = next_index;
break;
case '`':
if (i > last) {
parts->Add(ReplacementPart::ReplacementSubString(last, i), zone);
}
parts->Add(ReplacementPart::SubjectPrefix(), zone);
i = next_index;
last = i + 1;
break;
case '\'':
if (i > last) {
parts->Add(ReplacementPart::ReplacementSubString(last, i), zone);
}
parts->Add(ReplacementPart::SubjectSuffix(subject_length), zone);
i = next_index;
last = i + 1;
break;
case '&':
if (i > last) {
parts->Add(ReplacementPart::ReplacementSubString(last, i), zone);
}
parts->Add(ReplacementPart::SubjectMatch(), zone);
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) {
// Peek ahead to see if we have two digits.
Char c3 = characters[second_digit_index];
if ('0' <= c3 && c3 <= '9') { // Double digits.
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->Add(ReplacementPart::ReplacementSubString(last, i),
zone);
}
DCHECK(capture_ref <= capture_count);
parts->Add(ReplacementPart::SubjectCapture(capture_ref), zone);
last = next_index + 1;
}
i = next_index;
break;
}
default:
i = next_index;
break;
}
}
}
if (length > last) {
if (last == 0) {
// Replacement is simple. Do not use Apply to do the replacement.
return true;
} else {
parts->Add(ReplacementPart::ReplacementSubString(last, length), zone);
}
}
return false;
}
ZoneList<ReplacementPart> parts_;
ZoneList<Handle<String> > replacement_substrings_;
Zone* zone_;
};
bool CompiledReplacement::Compile(Handle<String> replacement, int capture_count,
int subject_length) {
{
DisallowHeapAllocation no_gc;
String::FlatContent content = replacement->GetFlatContent();
DCHECK(content.IsFlat());
bool simple = false;
if (content.IsOneByte()) {
simple = ParseReplacementPattern(&parts_, content.ToOneByteVector(),
capture_count, subject_length, zone());
} else {
DCHECK(content.IsTwoByte());
simple = ParseReplacementPattern(&parts_, content.ToUC16Vector(),
capture_count, subject_length, zone());
}
if (simple) return true;
}
Isolate* isolate = replacement->GetIsolate();
// Find substrings of replacement string and create them as String objects.
int substring_index = 0;
for (int i = 0, n = parts_.length(); i < n; i++) {
int tag = parts_[i].tag;
if (tag <= 0) { // A replacement string slice.
int from = -tag;
int to = parts_[i].data;
replacement_substrings_.Add(
isolate->factory()->NewSubString(replacement, from, to), zone());
parts_[i].tag = REPLACEMENT_SUBSTRING;
parts_[i].data = substring_index;
substring_index++;
} else if (tag == REPLACEMENT_STRING) {
replacement_substrings_.Add(replacement, zone());
parts_[i].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_.length());
for (int i = 0, n = parts_.length(); i < n; i++) {
ReplacementPart part = parts_[i];
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;
default:
UNREACHABLE();
}
}
}
void FindOneByteStringIndices(Vector<const uint8_t> subject, uint8_t pattern,
List<int>* indices, unsigned int limit) {
DCHECK(limit > 0);
// Collect indices of pattern in subject using memchr.
// Stop after finding at most limit values.
const uint8_t* subject_start = subject.start();
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 == NULL) return;
indices->Add(static_cast<int>(pos - subject_start));
pos++;
limit--;
}
}
void FindTwoByteStringIndices(const Vector<const uc16> subject, uc16 pattern,
List<int>* indices, unsigned int limit) {
DCHECK(limit > 0);
const uc16* subject_start = subject.start();
const uc16* subject_end = subject_start + subject.length();
for (const uc16* pos = subject_start; pos < subject_end && limit > 0; pos++) {
if (*pos == pattern) {
indices->Add(static_cast<int>(pos - subject_start));
limit--;
}
}
}
template <typename SubjectChar, typename PatternChar>
void FindStringIndices(Isolate* isolate, Vector<const SubjectChar> subject,
Vector<const PatternChar> pattern, List<int>* indices,
unsigned int limit) {
DCHECK(limit > 0);
// Collect indices of pattern in subject.
// Stop after finding at most limit values.
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->Add(index);
index += pattern_length;
limit--;
}
}
void FindStringIndicesDispatch(Isolate* isolate, String* subject,
String* pattern, List<int>* indices,
unsigned int limit) {
{
DisallowHeapAllocation no_gc;
String::FlatContent subject_content = subject->GetFlatContent();
String::FlatContent pattern_content = pattern->GetFlatContent();
DCHECK(subject_content.IsFlat());
DCHECK(pattern_content.IsFlat());
if (subject_content.IsOneByte()) {
Vector<const uint8_t> subject_vector = subject_content.ToOneByteVector();
if (pattern_content.IsOneByte()) {
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 {
Vector<const uc16> subject_vector = subject_content.ToUC16Vector();
if (pattern_content.IsOneByte()) {
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 {
Vector<const 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 {
List<int>* GetRewoundRegexpIndicesList(Isolate* isolate) {
List<int>* list = isolate->regexp_indices();
list->Rewind(0);
return list;
}
void TruncateRegexpIndicesList(Isolate* isolate) {
// Same size as smallest zone segment, preserving behavior from the
// runtime zone.
static const int kMaxRegexpIndicesListCapacity = 8 * KB;
if (isolate->regexp_indices()->capacity() > kMaxRegexpIndicesListCapacity) {
isolate->regexp_indices()->Clear(); // Throw away backing storage
}
}
} // namespace
template <typename ResultSeqString>
MUST_USE_RESULT static Object* StringReplaceGlobalAtomRegExpWithString(
Isolate* isolate, Handle<String> subject, Handle<JSRegExp> pattern_regexp,
Handle<String> replacement, Handle<RegExpMatchInfo> last_match_info) {
DCHECK(subject->IsFlat());
DCHECK(replacement->IsFlat());
List<int>* indices = GetRewoundRegexpIndicesList(isolate);
DCHECK_EQ(JSRegExp::ATOM, pattern_regexp->TypeTag());
String* pattern =
String::cast(pattern_regexp->DataAt(JSRegExp::kAtomPatternIndex));
int subject_len = subject->length();
int pattern_len = pattern->length();
int replacement_len = replacement->length();
FindStringIndicesDispatch(isolate, *subject, pattern, indices, 0xffffffff);
int matches = indices->length();
if (matches == 0) return *subject;
// Detect integer overflow.
int64_t result_len_64 = (static_cast<int64_t>(replacement_len) -
static_cast<int64_t>(pattern_len)) *
static_cast<int64_t>(matches) +
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; // Provoke exception.
} else {
result_len = static_cast<int>(result_len_64);
}
int subject_pos = 0;
int result_pos = 0;
MaybeHandle<SeqString> maybe_res;
if (ResultSeqString::kHasOneByteEncoding) {
maybe_res = isolate->factory()->NewRawOneByteString(result_len);
} else {
maybe_res = isolate->factory()->NewRawTwoByteString(result_len);
}
Handle<SeqString> untyped_res;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, untyped_res, maybe_res);
Handle<ResultSeqString> result = Handle<ResultSeqString>::cast(untyped_res);
for (int i = 0; i < matches; i++) {
// Copy non-matched subject content.
if (subject_pos < indices->at(i)) {
String::WriteToFlat(*subject, result->GetChars() + result_pos,
subject_pos, indices->at(i));
result_pos += indices->at(i) - subject_pos;
}
// Replace match.
if (replacement_len > 0) {
String::WriteToFlat(*replacement, result->GetChars() + result_pos, 0,
replacement_len);
result_pos += replacement_len;
}
subject_pos = indices->at(i) + pattern_len;
}
// Add remaining subject content at the end.
if (subject_pos < subject_len) {
String::WriteToFlat(*subject, result->GetChars() + result_pos, subject_pos,
subject_len);
}
int32_t match_indices[] = {indices->at(matches - 1),
indices->at(matches - 1) + pattern_len};
RegExpImpl::SetLastMatchInfo(last_match_info, subject, 0, match_indices);
TruncateRegexpIndicesList(isolate);
return *result;
}
MUST_USE_RESULT static Object* StringReplaceGlobalRegExpWithString(
Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp,
Handle<String> replacement, Handle<RegExpMatchInfo> last_match_info) {
DCHECK(subject->IsFlat());
DCHECK(replacement->IsFlat());
int capture_count = regexp->CaptureCount();
int subject_length = subject->length();
// CompiledReplacement uses zone allocation.
Zone zone(isolate->allocator(), ZONE_NAME);
CompiledReplacement compiled_replacement(&zone);
bool simple_replace =
compiled_replacement.Compile(replacement, capture_count, subject_length);
// Shortcut for simple non-regexp global replacements
if (regexp->TypeTag() == JSRegExp::ATOM && simple_replace) {
if (subject->HasOnlyOneByteChars() && replacement->HasOnlyOneByteChars()) {
return StringReplaceGlobalAtomRegExpWithString<SeqOneByteString>(
isolate, subject, regexp, replacement, last_match_info);
} else {
return StringReplaceGlobalAtomRegExpWithString<SeqTwoByteString>(
isolate, subject, regexp, replacement, last_match_info);
}
}
RegExpImpl::GlobalCache global_cache(regexp, subject, isolate);
if (global_cache.HasException()) return isolate->heap()->exception();
int32_t* current_match = global_cache.FetchNext();
if (current_match == NULL) {
if (global_cache.HasException()) return isolate->heap()->exception();
return *subject;
}
// Guessing the number of parts that the final result string is built
// from. Global regexps can match any number of times, so we guess
// conservatively.
int expected_parts = (compiled_replacement.parts() + 1) * 4 + 1;
ReplacementStringBuilder builder(isolate->heap(), subject, expected_parts);
// Number of parts added by compiled replacement plus preceeding
// string and possibly suffix after last match. It is possible for
// all components to use two elements when encoded as two smis.
const int parts_added_per_loop = 2 * (compiled_replacement.parts() + 2);
int prev = 0;
do {
builder.EnsureCapacity(parts_added_per_loop);
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 = global_cache.FetchNext();
} while (current_match != NULL);
if (global_cache.HasException()) return isolate->heap()->exception();
if (prev < subject_length) {
builder.EnsureCapacity(2);
builder.AddSubjectSlice(prev, subject_length);
}
RegExpImpl::SetLastMatchInfo(last_match_info, subject, capture_count,
global_cache.LastSuccessfulMatch());
RETURN_RESULT_OR_FAILURE(isolate, builder.ToString());
}
template <typename ResultSeqString>
MUST_USE_RESULT static Object* StringReplaceGlobalRegExpWithEmptyString(
Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp,
Handle<RegExpMatchInfo> last_match_info) {
DCHECK(subject->IsFlat());
// Shortcut for simple non-regexp global replacements
if (regexp->TypeTag() == JSRegExp::ATOM) {
Handle<String> empty_string = isolate->factory()->empty_string();
if (subject->IsOneByteRepresentation()) {
return StringReplaceGlobalAtomRegExpWithString<SeqOneByteString>(
isolate, subject, regexp, empty_string, last_match_info);
} else {
return StringReplaceGlobalAtomRegExpWithString<SeqTwoByteString>(
isolate, subject, regexp, empty_string, last_match_info);
}
}
RegExpImpl::GlobalCache global_cache(regexp, subject, isolate);
if (global_cache.HasException()) return isolate->heap()->exception();
int32_t* current_match = global_cache.FetchNext();
if (current_match == NULL) {
if (global_cache.HasException()) return isolate->heap()->exception();
return *subject;
}
int start = current_match[0];
int end = current_match[1];
int capture_count = regexp->CaptureCount();
int subject_length = subject->length();
int new_length = subject_length - (end - start);
if (new_length == 0) return isolate->heap()->empty_string();
Handle<ResultSeqString> answer;
if (ResultSeqString::kHasOneByteEncoding) {
answer = Handle<ResultSeqString>::cast(
isolate->factory()->NewRawOneByteString(new_length).ToHandleChecked());
} else {
answer = Handle<ResultSeqString>::cast(
isolate->factory()->NewRawTwoByteString(new_length).ToHandleChecked());
}
int prev = 0;
int position = 0;
do {
start = current_match[0];
end = current_match[1];
if (prev < start) {
// Add substring subject[prev;start] to answer string.
String::WriteToFlat(*subject, answer->GetChars() + position, prev, start);
position += start - prev;
}
prev = end;
current_match = global_cache.FetchNext();
} while (current_match != NULL);
if (global_cache.HasException()) return isolate->heap()->exception();
RegExpImpl::SetLastMatchInfo(last_match_info, subject, capture_count,
global_cache.LastSuccessfulMatch());
if (prev < subject_length) {
// Add substring subject[prev;length] to answer string.
String::WriteToFlat(*subject, answer->GetChars() + position, prev,
subject_length);
position += subject_length - prev;
}
if (position == 0) return isolate->heap()->empty_string();
// Shorten string and fill
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();
// The trimming is performed on a newly allocated object, which is on a
// fresly allocated page or on an already swept page. Hence, the sweeper
// thread can not get confused with the filler creation. No synchronization
// needed.
// TODO(hpayer): We should shrink the large object page if the size
// of the object changed significantly.
if (!heap->lo_space()->Contains(*answer)) {
heap->CreateFillerObjectAt(end_of_string, delta, ClearRecordedSlots::kNo);
}
heap->AdjustLiveBytes(*answer, -delta, Heap::CONCURRENT_TO_SWEEPER);
return *answer;
}
namespace {
Object* StringReplaceGlobalRegExpWithStringHelper(
Isolate* isolate, Handle<JSRegExp> regexp, Handle<String> subject,
Handle<String> replacement, Handle<RegExpMatchInfo> last_match_info) {
CHECK(regexp->GetFlags() & JSRegExp::kGlobal);
subject = String::Flatten(subject);
if (replacement->length() == 0) {
if (subject->HasOnlyOneByteChars()) {
return StringReplaceGlobalRegExpWithEmptyString<SeqOneByteString>(
isolate, subject, regexp, last_match_info);
} else {
return StringReplaceGlobalRegExpWithEmptyString<SeqTwoByteString>(
isolate, subject, regexp, last_match_info);
}
}
replacement = String::Flatten(replacement);
return StringReplaceGlobalRegExpWithString(isolate, subject, regexp,
replacement, last_match_info);
}
} // namespace
RUNTIME_FUNCTION(Runtime_StringReplaceGlobalRegExpWithString) {
HandleScope scope(isolate);
DCHECK(args.length() == 4);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
CONVERT_ARG_HANDLE_CHECKED(String, replacement, 2);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 1);
CONVERT_ARG_HANDLE_CHECKED(RegExpMatchInfo, last_match_info, 3);
return StringReplaceGlobalRegExpWithStringHelper(
isolate, regexp, subject, replacement, last_match_info);
}
RUNTIME_FUNCTION(Runtime_StringSplit) {
HandleScope handle_scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
CONVERT_ARG_HANDLE_CHECKED(String, pattern, 1);
CONVERT_NUMBER_CHECKED(uint32_t, limit, Uint32, args[2]);
CHECK(limit > 0);
int subject_length = subject->length();
int pattern_length = pattern->length();
CHECK(pattern_length > 0);
if (limit == 0xffffffffu) {
FixedArray* last_match_cache_unused;
Handle<Object> cached_answer(
RegExpResultsCache::Lookup(isolate->heap(), *subject, *pattern,
&last_match_cache_unused,
RegExpResultsCache::STRING_SPLIT_SUBSTRINGS),
isolate);
if (*cached_answer != Smi::kZero) {
// The cache FixedArray is a COW-array and can therefore be reused.
Handle<JSArray> result = isolate->factory()->NewJSArrayWithElements(
Handle<FixedArray>::cast(cached_answer));
return *result;
}
}
// The limit can be very large (0xffffffffu), but since the pattern
// isn't empty, we can never create more parts than ~half the length
// of the subject.
subject = String::Flatten(subject);
pattern = String::Flatten(pattern);
List<int>* indices = GetRewoundRegexpIndicesList(isolate);
FindStringIndicesDispatch(isolate, *subject, *pattern, indices, limit);
if (static_cast<uint32_t>(indices->length()) < limit) {
indices->Add(subject_length);
}
// The list indices now contains the end of each part to create.
// Create JSArray of substrings separated by separator.
int part_count = indices->length();
Handle<JSArray> result =
isolate->factory()->NewJSArray(FAST_ELEMENTS, part_count, part_count,
INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE);
DCHECK(result->HasFastObjectElements());
Handle<FixedArray> elements(FixedArray::cast(result->elements()));
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);
Handle<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->HasFastObjectElements()) {
RegExpResultsCache::Enter(isolate, subject, pattern, elements,
isolate->factory()->empty_fixed_array(),
RegExpResultsCache::STRING_SPLIT_SUBSTRINGS);
}
}
TruncateRegexpIndicesList(isolate);
return *result;
}
// ES##sec-regexpcreate
// RegExpCreate ( P, F )
RUNTIME_FUNCTION(Runtime_RegExpCreate) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(Object, source_object, 0);
Handle<String> source;
if (source_object->IsUndefined(isolate)) {
source = isolate->factory()->empty_string();
} else {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, source, Object::ToString(isolate, source_object));
}
Handle<Map> map(isolate->regexp_function()->initial_map());
Handle<JSRegExp> regexp =
Handle<JSRegExp>::cast(isolate->factory()->NewJSObjectFromMap(map));
JSRegExp::Flags flags = JSRegExp::kNone;
RETURN_FAILURE_ON_EXCEPTION(isolate,
JSRegExp::Initialize(regexp, source, flags));
return *regexp;
}
RUNTIME_FUNCTION(Runtime_RegExpExec) {
HandleScope scope(isolate);
DCHECK(args.length() == 4);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 0);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 1);
CONVERT_INT32_ARG_CHECKED(index, 2);
CONVERT_ARG_HANDLE_CHECKED(RegExpMatchInfo, last_match_info, 3);
// Due to the way the JS calls are constructed this must be less than the
// length of a string, i.e. it is always a Smi. We check anyway for security.
CHECK(index >= 0);
CHECK(index <= subject->length());
isolate->counters()->regexp_entry_runtime()->Increment();
RETURN_RESULT_OR_FAILURE(
isolate, RegExpImpl::Exec(regexp, subject, index, last_match_info));
}
RUNTIME_FUNCTION(Runtime_RegExpInternalReplace) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 0);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 1);
CONVERT_ARG_HANDLE_CHECKED(String, replacement, 2);
Handle<RegExpMatchInfo> internal_match_info =
isolate->regexp_internal_match_info();
return StringReplaceGlobalRegExpWithStringHelper(
isolate, regexp, subject, replacement, internal_match_info);
}
namespace {
class MatchInfoBackedMatch : public String::Match {
public:
MatchInfoBackedMatch(Isolate* isolate, Handle<String> subject,
Handle<RegExpMatchInfo> match_info)
: isolate_(isolate), match_info_(match_info) {
subject_ = String::Flatten(subject);
}
Handle<String> GetMatch() override {
return RegExpUtils::GenericCaptureGetter(isolate_, match_info_, 0, nullptr);
}
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
Handle<Object> capture_obj = RegExpUtils::GenericCaptureGetter(
isolate_, match_info_, i, capture_exists);
return (*capture_exists) ? Object::ToString(isolate_, capture_obj)
: isolate_->factory()->empty_string();
}
Handle<String> GetPrefix() override {
const int match_start = match_info_->Capture(0);
return isolate_->factory()->NewSubString(subject_, 0, match_start);
}
Handle<String> GetSuffix() override {
const int match_end = match_info_->Capture(1);
return isolate_->factory()->NewSubString(subject_, match_end,
subject_->length());
}
int CaptureCount() override {
return match_info_->NumberOfCaptureRegisters() / 2;
}
virtual ~MatchInfoBackedMatch() {}
private:
Isolate* isolate_;
Handle<String> subject_;
Handle<RegExpMatchInfo> match_info_;
};
class VectorBackedMatch : public String::Match {
public:
VectorBackedMatch(Isolate* isolate, Handle<String> subject,
Handle<String> match, int match_position,
ZoneVector<Handle<Object>>* captures)
: isolate_(isolate),
match_(match),
match_position_(match_position),
captures_(captures) {
subject_ = String::Flatten(subject);
}
Handle<String> GetMatch() override { return match_; }
MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
Handle<Object> capture_obj = captures_->at(i);
if (capture_obj->IsUndefined(isolate_)) {
*capture_exists = false;
return isolate_->factory()->empty_string();
}
*capture_exists = true;
return Object::ToString(isolate_, capture_obj);
}
Handle<String> GetPrefix() override {
return isolate_->factory()->NewSubString(subject_, 0, match_position_);
}
Handle<String> GetSuffix() override {
const int match_end_position = match_position_ + match_->length();
return isolate_->factory()->NewSubString(subject_, match_end_position,
subject_->length());
}
int CaptureCount() override { return static_cast<int>(captures_->size()); }
virtual ~VectorBackedMatch() {}
private:
Isolate* isolate_;
Handle<String> subject_;
Handle<String> match_;
const int match_position_;
ZoneVector<Handle<Object>>* captures_;
};
// 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>
static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
Handle<JSRegExp> regexp,
Handle<RegExpMatchInfo> last_match_array,
Handle<JSArray> result_array) {
DCHECK(subject->IsFlat());
DCHECK_NE(has_capture, regexp->CaptureCount() == 0);
int capture_count = regexp->CaptureCount();
int subject_length = subject->length();
static const int kMinLengthToCache = 0x1000;
if (subject_length > kMinLengthToCache) {
FixedArray* last_match_cache;
Object* cached_answer = RegExpResultsCache::Lookup(
isolate->heap(), *subject, regexp->data(), &last_match_cache,
RegExpResultsCache::REGEXP_MULTIPLE_INDICES);
if (cached_answer->IsFixedArray()) {
int capture_registers = (capture_count + 1) * 2;
int32_t* last_match = NewArray<int32_t>(capture_registers);
for (int i = 0; i < capture_registers; i++) {
last_match[i] = Smi::cast(last_match_cache->get(i))->value();
}
Handle<FixedArray> cached_fixed_array =
Handle<FixedArray>(FixedArray::cast(cached_answer));
// The cache FixedArray is a COW-array and we need to return a copy.
Handle<FixedArray> copied_fixed_array =
isolate->factory()->CopyFixedArrayWithMap(
cached_fixed_array, isolate->factory()->fixed_array_map());
JSArray::SetContent(result_array, copied_fixed_array);
RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count,
last_match);
DeleteArray(last_match);
return *result_array;
}
}
RegExpImpl::GlobalCache global_cache(regexp, subject, isolate);
if (global_cache.HasException()) return isolate->heap()->exception();
// Ensured in Runtime_RegExpExecMultiple.
DCHECK(result_array->HasFastObjectElements());
Handle<FixedArray> result_elements(
FixedArray::cast(result_array->elements()));
if (result_elements->length() < 16) {
result_elements = isolate->factory()->NewFixedArrayWithHoles(16);
}
FixedArrayBuilder builder(result_elements);
// Position to search from.
int match_start = -1;
int match_end = 0;
bool first = true;
// Two smis before and after the match, for very long strings.
static const int kMaxBuilderEntriesPerRegExpMatch = 5;
while (true) {
int32_t* current_match = global_cache.FetchNext();
if (current_match == NULL) break;
match_start = current_match[0];
builder.EnsureCapacity(kMaxBuilderEntriesPerRegExpMatch);
if (match_end < match_start) {
ReplacementStringBuilder::AddSubjectSlice(&builder, match_end,
match_start);
}
match_end = current_match[1];
{
// Avoid accumulating new handles inside loop.
HandleScope temp_scope(isolate);
Handle<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) {
// 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);
elements->set(0, *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);
Handle<String> substring =
isolate->factory()->NewSubString(subject, start, end);
elements->set(i, *substring);
} else {
DCHECK(current_match[i * 2 + 1] < 0);
elements->set(i, isolate->heap()->undefined_value());
}
}
elements->set(capture_count + 1, Smi::FromInt(match_start));
elements->set(capture_count + 2, *subject);
builder.Add(*isolate->factory()->NewJSArrayWithElements(elements));
} else {
builder.Add(*match);
}
}
}
if (global_cache.HasException()) return isolate->heap()->exception();
if (match_start >= 0) {
// Finished matching, with at least one match.
if (match_end < subject_length) {
ReplacementStringBuilder::AddSubjectSlice(&builder, match_end,
subject_length);
}
RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count,
global_cache.LastSuccessfulMatch());
if (subject_length > kMinLengthToCache) {
// Store the last successful match into the array for caching.
// TODO(yangguo): do not expose last match to JS and simplify caching.
int capture_registers = (capture_count + 1) * 2;
Handle<FixedArray> last_match_cache =
isolate->factory()->NewFixedArray(capture_registers);
int32_t* last_match = global_cache.LastSuccessfulMatch();
for (int i = 0; i < capture_registers; i++) {
last_match_cache->set(i, Smi::FromInt(last_match[i]));
}
Handle<FixedArray> result_fixed_array = builder.array();
result_fixed_array->Shrink(builder.length());
// Cache the result and copy the FixedArray into a COW array.
Handle<FixedArray> copied_fixed_array =
isolate->factory()->CopyFixedArrayWithMap(
result_fixed_array, isolate->factory()->fixed_array_map());
RegExpResultsCache::Enter(
isolate, subject, handle(regexp->data(), isolate), copied_fixed_array,
last_match_cache, RegExpResultsCache::REGEXP_MULTIPLE_INDICES);
}
return *builder.ToJSArray(result_array);
} else {
return isolate->heap()->null_value(); // No matches at all.
}
}
MUST_USE_RESULT MaybeHandle<String> StringReplaceNonGlobalRegExpWithFunction(
Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp,
Handle<Object> replace_obj) {
Factory* factory = isolate->factory();
Handle<RegExpMatchInfo> last_match_info = isolate->regexp_last_match_info();
// TODO(jgruber): This is a pattern we could refactor.
Handle<Object> match_indices_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, match_indices_obj,
RegExpImpl::Exec(regexp, subject, 0, last_match_info), String);
if (match_indices_obj->IsNull(isolate)) {
RETURN_ON_EXCEPTION(isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0),
String);
return subject;
}
Handle<RegExpMatchInfo> match_indices =
Handle<RegExpMatchInfo>::cast(match_indices_obj);
const int index = match_indices->Capture(0);
const int end_of_match = match_indices->Capture(1);
IncrementalStringBuilder builder(isolate);
builder.AppendString(factory->NewSubString(subject, 0, index));
// Compute the parameter list consisting of the match, captures, index,
// and subject for the replace function invocation.
// The number of captures plus one for the match.
const int m = match_indices->NumberOfCaptureRegisters() / 2;
const int argc = m + 2;
ScopedVector<Handle<Object>> argv(argc);
for (int j = 0; j < m; j++) {
bool ok;
Handle<String> capture =
RegExpUtils::GenericCaptureGetter(isolate, match_indices, j, &ok);
if (ok) {
argv[j] = capture;
} else {
argv[j] = factory->undefined_value();
}
}
argv[argc - 2] = handle(Smi::FromInt(index), isolate);
argv[argc - 1] = subject;
Handle<Object> replacement_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, replacement_obj,
Execution::Call(isolate, replace_obj, factory->undefined_value(), argc,
argv.start()),
String);
Handle<String> replacement;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, replacement, Object::ToString(isolate, replacement_obj), String);
builder.AppendString(replacement);
builder.AppendString(
factory->NewSubString(subject, end_of_match, subject->length()));
return builder.Finish();
}
// Legacy implementation of RegExp.prototype[Symbol.replace] which
// doesn't properly call the underlying exec method.
MUST_USE_RESULT MaybeHandle<String> RegExpReplace(Isolate* isolate,
Handle<JSRegExp> regexp,
Handle<String> string,
Handle<Object> replace_obj) {
Factory* factory = isolate->factory();
// TODO(jgruber): We need the even stricter guarantee of an unmodified
// JSRegExp map here for access to GetFlags to be legal.
const int flags = regexp->GetFlags();
const bool global = (flags & JSRegExp::kGlobal) != 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);
replace = String::Flatten(replace);
Handle<RegExpMatchInfo> last_match_info = isolate->regexp_last_match_info();
if (!global) {
// Non-global regexp search, string replace.
Handle<Object> match_indices_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, match_indices_obj,
RegExpImpl::Exec(regexp, string, 0, last_match_info), String);
if (match_indices_obj->IsNull(isolate)) {
RETURN_ON_EXCEPTION(
isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0), String);
return string;
}
auto match_indices = Handle<RegExpMatchInfo>::cast(match_indices_obj);
const int start_index = match_indices->Capture(0);
const int end_index = match_indices->Capture(1);
IncrementalStringBuilder builder(isolate);
builder.AppendString(factory->NewSubString(string, 0, start_index));
if (replace->length() > 0) {
MatchInfoBackedMatch m(isolate, string, match_indices);
Handle<String> replacement;
ASSIGN_RETURN_ON_EXCEPTION(isolate, replacement,
String::GetSubstitution(isolate, &m, replace),
String);
builder.AppendString(replacement);
}
builder.AppendString(
factory->NewSubString(string, end_index, string->length()));
return builder.Finish();
} else {
// Global regexp search, string replace.
DCHECK(global);
RETURN_ON_EXCEPTION(isolate, RegExpUtils::SetLastIndex(isolate, regexp, 0),
String);
if (replace->length() == 0) {
if (string->HasOnlyOneByteChars()) {
Object* result =
StringReplaceGlobalRegExpWithEmptyString<SeqOneByteString>(
isolate, string, regexp, last_match_info);
return handle(String::cast(result), isolate);
} else {
Object* result =
StringReplaceGlobalRegExpWithEmptyString<SeqTwoByteString>(
isolate, string, regexp, last_match_info);
return handle(String::cast(result), isolate);
}
}
Object* result = StringReplaceGlobalRegExpWithString(
isolate, string, regexp, replace, last_match_info);
if (result->IsString()) {
return handle(String::cast(result), isolate);
} else {
return MaybeHandle<String>();
}
}
UNREACHABLE();
return MaybeHandle<String>();
}
} // namespace
// This is only called for StringReplaceGlobalRegExpWithFunction.
RUNTIME_FUNCTION(Runtime_RegExpExecMultiple) {
HandleScope handles(isolate);
DCHECK(args.length() == 4);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 0);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 1);
CONVERT_ARG_HANDLE_CHECKED(RegExpMatchInfo, last_match_info, 2);
CONVERT_ARG_HANDLE_CHECKED(JSArray, result_array, 3);
CHECK(result_array->HasFastObjectElements());
subject = String::Flatten(subject);
CHECK(regexp->GetFlags() & JSRegExp::kGlobal);
if (regexp->CaptureCount() == 0) {
return SearchRegExpMultiple<false>(isolate, subject, regexp,
last_match_info, result_array);
} else {
return SearchRegExpMultiple<true>(isolate, subject, regexp, last_match_info,
result_array);
}
}
RUNTIME_FUNCTION(Runtime_StringReplaceNonGlobalRegExpWithFunction) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
CONVERT_ARG_HANDLE_CHECKED(JSRegExp, regexp, 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, replace, 2);
RETURN_RESULT_OR_FAILURE(isolate, StringReplaceNonGlobalRegExpWithFunction(
isolate, subject, regexp, replace));
}
// Slow path for:
// ES#sec-regexp.prototype-@@replace
// RegExp.prototype [ @@replace ] ( string, replaceValue )
RUNTIME_FUNCTION(Runtime_RegExpReplace) {
HandleScope scope(isolate);
DCHECK(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, recv, 0);
CONVERT_ARG_HANDLE_CHECKED(String, string, 1);
Handle<Object> replace_obj = args.at<Object>(2);
Factory* factory = isolate->factory();
string = String::Flatten(string);
// Fast-path for unmodified JSRegExps.
if (RegExpUtils::IsUnmodifiedRegExp(isolate, recv)) {
RETURN_RESULT_OR_FAILURE(
isolate, RegExpReplace(isolate, Handle<JSRegExp>::cast(recv), string,
replace_obj));
}
const int length = string->length();
const bool functional_replace = replace_obj->IsCallable();
Handle<String> replace;
if (!functional_replace) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, replace,
Object::ToString(isolate, replace_obj));
}
Handle<Object> global_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, global_obj,
JSReceiver::GetProperty(recv, factory->global_string()));
const bool global = global_obj->BooleanValue();
bool unicode = false;
if (global) {
Handle<Object> unicode_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, unicode_obj,
JSReceiver::GetProperty(recv, factory->unicode_string()));
unicode = unicode_obj->BooleanValue();
RETURN_FAILURE_ON_EXCEPTION(isolate,
RegExpUtils::SetLastIndex(isolate, recv, 0));
}
Zone zone(isolate->allocator(), ZONE_NAME);
ZoneVector<Handle<Object>> results(&zone);
while (true) {
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, RegExpUtils::RegExpExec(isolate, recv, string,
factory->undefined_value()));
if (result->IsNull(isolate)) break;
results.push_back(result);
if (!global) break;
Handle<Object> match_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj,
Object::GetElement(isolate, result, 0));
Handle<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, unicode));
}
}
// TODO(jgruber): Look into ReplacementStringBuilder instead.
IncrementalStringBuilder builder(isolate);
int next_source_position = 0;
for (const auto& result : results) {
Handle<Object> captures_length_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, captures_length_obj,
Object::GetProperty(result, factory->length_string()));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, captures_length_obj,
Object::ToLength(isolate, captures_length_obj));
const int captures_length =
std::max(Handle<Smi>::cast(captures_length_obj)->value(), 0);
Handle<Object> match_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match_obj,
Object::GetElement(isolate, result, 0));
Handle<String> match;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, match,
Object::ToString(isolate, match_obj));
const int match_length = match->length();
Handle<Object> position_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
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 int position =
std::max(std::min(Handle<Smi>::cast(position_obj)->value(), length), 0);
ZoneVector<Handle<Object>> captures(&zone);
for (int n = 0; n < captures_length; n++) {
Handle<Object> capture;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, capture, Object::GetElement(isolate, result, n));
if (!capture->IsUndefined(isolate)) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, capture,
Object::ToString(isolate, capture));
}
captures.push_back(capture);
}
Handle<String> replacement;
if (functional_replace) {
const int argc = captures_length + 2;
ScopedVector<Handle<Object>> argv(argc);
for (int j = 0; j < captures_length; j++) {
argv[j] = captures[j];
}
argv[captures_length] = handle(Smi::FromInt(position), isolate);
argv[captures_length + 1] = string;
Handle<Object> replacement_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, replacement_obj,
Execution::Call(isolate, replace_obj, factory->undefined_value(),
argc, argv.start()));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, replacement, Object::ToString(isolate, replacement_obj));
} else {
VectorBackedMatch m(isolate, string, match, position, &captures);
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_RegExpExecReThrow) {
SealHandleScope shs(isolate);
DCHECK(args.length() == 4);
Object* exception = isolate->pending_exception();
isolate->clear_pending_exception();
return isolate->ReThrow(exception);
}
RUNTIME_FUNCTION(Runtime_IsRegExp) {
SealHandleScope shs(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_CHECKED(Object, obj, 0);
return isolate->heap()->ToBoolean(obj->IsJSRegExp());
}
} // namespace internal
} // namespace v8