| Index: src/runtime/runtime-regexp.cc
|
| diff --git a/src/runtime/runtime-regexp.cc b/src/runtime/runtime-regexp.cc
|
| index 0a9f1054dc5e48ff00fe53773f9ae4fbfeb9f21d..b6c71311de85a883fdd5471cdcff435b83a08b2e 100644
|
| --- a/src/runtime/runtime-regexp.cc
|
| +++ b/src/runtime/runtime-regexp.cc
|
| @@ -10,6 +10,7 @@
|
| #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"
|
|
|
| @@ -793,7 +794,6 @@ RUNTIME_FUNCTION(Runtime_RegExpFlags) {
|
| return regexp->flags();
|
| }
|
|
|
| -
|
| RUNTIME_FUNCTION(Runtime_RegExpSource) {
|
| SealHandleScope shs(isolate);
|
| DCHECK(args.length() == 1);
|
| @@ -822,7 +822,6 @@ RUNTIME_FUNCTION(Runtime_RegExpConstructResult) {
|
| return *array;
|
| }
|
|
|
| -
|
| RUNTIME_FUNCTION(Runtime_RegExpInitializeAndCompile) {
|
| HandleScope scope(isolate);
|
| DCHECK(args.length() == 3);
|
| @@ -836,14 +835,110 @@ RUNTIME_FUNCTION(Runtime_RegExpInitializeAndCompile) {
|
| return *regexp;
|
| }
|
|
|
| +namespace {
|
| +
|
| +class MatchInfoBackedMatch : public String::Match {
|
| + public:
|
| + MatchInfoBackedMatch(Isolate* isolate, Handle<String> subject,
|
| + Handle<JSObject> 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);
|
| + 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 {
|
| + const int match_start =
|
| + RegExpUtils::GetLastMatchCapture(isolate_, match_info_, 0);
|
| + return isolate_->factory()->NewSubString(subject_, 0, match_start);
|
| + }
|
| +
|
| + Handle<String> GetSuffix() override {
|
| + const int match_end =
|
| + RegExpUtils::GetLastMatchCapture(isolate_, match_info_, 1);
|
| + return isolate_->factory()->NewSubString(subject_, match_end,
|
| + subject_->length());
|
| + }
|
| +
|
| + int CaptureCount() override {
|
| + return RegExpUtils::GetLastMatchNumberOfCaptures(isolate_, match_info_) / 2;
|
| + }
|
| +
|
| + virtual ~MatchInfoBackedMatch() {}
|
|
|
| -// Only called from Runtime_RegExpExecMultiple so it doesn't need to maintain
|
| + private:
|
| + Isolate* isolate_;
|
| + Handle<String> subject_;
|
| + Handle<JSObject> 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 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<JSObject> last_match_array,
|
| - Handle<JSArray> result_array) {
|
| +MaybeHandle<Object> SearchRegExpMultiple(Isolate* isolate,
|
| + Handle<String> subject,
|
| + Handle<JSRegExp> regexp,
|
| + Handle<JSObject> last_match_array,
|
| + Handle<FixedArray> result_elements) {
|
| DCHECK(subject->IsFlat());
|
| DCHECK_NE(has_capture, regexp->CaptureCount() == 0);
|
|
|
| @@ -863,24 +958,20 @@ static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
|
| 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 can therefore be reused.
|
| - JSArray::SetContent(result_array, cached_fixed_array);
|
| + Handle<FixedArray> cached_fixed_array(FixedArray::cast(cached_answer));
|
| RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count,
|
| last_match);
|
| DeleteArray(last_match);
|
| - return *result_array;
|
| + // The cache FixedArray is a COW-array and we need to return a copy.
|
| + return isolate->factory()->CopyFixedArrayWithMap(
|
| + cached_fixed_array, isolate->factory()->fixed_array_map());
|
| }
|
| }
|
|
|
| RegExpImpl::GlobalCache global_cache(regexp, subject, isolate);
|
| - if (global_cache.HasException()) return isolate->heap()->exception();
|
| + if (global_cache.HasException()) return MaybeHandle<Object>();
|
|
|
| // 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);
|
| }
|
| @@ -947,7 +1038,7 @@ static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
|
| }
|
| }
|
|
|
| - if (global_cache.HasException()) return isolate->heap()->exception();
|
| + if (global_cache.HasException()) return MaybeHandle<Object>();
|
|
|
| if (match_start >= 0) {
|
| // Finished matching, with at least one match.
|
| @@ -959,6 +1050,9 @@ static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
|
| RegExpImpl::SetLastMatchInfo(last_match_array, subject, capture_count,
|
| global_cache.LastSuccessfulMatch());
|
|
|
| + Handle<FixedArray> result_fixed_array = builder.array();
|
| + result_fixed_array->Shrink(builder.length());
|
| +
|
| 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.
|
| @@ -969,33 +1063,28 @@ static Object* SearchRegExpMultiple(Isolate* isolate, Handle<String> subject,
|
| 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 turn the FixedArray into a COW array.
|
| RegExpResultsCache::Enter(
|
| isolate, subject, handle(regexp->data(), isolate), result_fixed_array,
|
| last_match_cache, RegExpResultsCache::REGEXP_MULTIPLE_INDICES);
|
| }
|
| - return *builder.ToJSArray(result_array);
|
| + // The cache FixedArray is a COW-array and we need to return a copy.
|
| + return isolate->factory()->CopyFixedArrayWithMap(
|
| + result_fixed_array, isolate->factory()->fixed_array_map());
|
| } else {
|
| - return isolate->heap()->null_value(); // No matches at all.
|
| + return isolate->factory()->null_value(); // No matches at all.
|
| }
|
| }
|
|
|
| -
|
| // This is only called for StringReplaceGlobalRegExpWithFunction. This sets
|
| // lastMatchInfoOverride to maintain the last match info, so we don't need to
|
| // set any other last match array info.
|
| -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(JSObject, last_match_info, 2);
|
| - CONVERT_ARG_HANDLE_CHECKED(JSArray, result_array, 3);
|
| +MaybeHandle<Object> RegExpExecMultiple(Isolate* isolate,
|
| + Handle<JSRegExp> regexp,
|
| + Handle<String> subject,
|
| + Handle<JSObject> last_match_info,
|
| + Handle<FixedArray> result_array) {
|
| CHECK(last_match_info->HasFastObjectElements());
|
| - CHECK(result_array->HasFastObjectElements());
|
|
|
| subject = String::Flatten(subject);
|
| CHECK(regexp->GetFlags() & JSRegExp::kGlobal);
|
| @@ -1009,6 +1098,520 @@ RUNTIME_FUNCTION(Runtime_RegExpExecMultiple) {
|
| }
|
| }
|
|
|
| +// Helper function for replacing regular expressions with the result of a
|
| +// function application in String.prototype.replace.
|
| +MaybeHandle<String> StringReplaceGlobalRegExpWithFunction(
|
| + Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp,
|
| + Handle<Object> replace_obj) {
|
| + Factory* factory = isolate->factory();
|
| +
|
| + // TODO(jgruber): Convert result_array into a List<Handle<Object>> (or
|
| + // similar) and adapt / remove FixedArrayBuilder.
|
| + Handle<JSObject> last_match_info = isolate->regexp_last_match_info();
|
| + Handle<FixedArray> result_array = factory->NewFixedArrayWithHoles(16);
|
| +
|
| + Handle<Object> res;
|
| + ASSIGN_RETURN_ON_EXCEPTION(isolate, res,
|
| + RegExpExecMultiple(isolate, regexp, subject,
|
| + last_match_info, result_array),
|
| + String);
|
| +
|
| + // Reload the last match info since it might have changed in the meantime.
|
| + last_match_info = isolate->regexp_last_match_info();
|
| +
|
| + if (res->IsNull(isolate)) return subject; // No matches at all.
|
| +
|
| + result_array = Handle<FixedArray>::cast(res);
|
| + const int result_length = result_array->length();
|
| +
|
| + const int num_captures =
|
| + RegExpUtils::GetLastMatchNumberOfCaptures(isolate, last_match_info) / 2;
|
| + if (num_captures == 1) {
|
| + // If the number of captures is one then there are no explicit captures in
|
| + // the regexp, just the implicit capture that captures the whole match. In
|
| + // this case we can simplify quite a bit and end up with something faster.
|
| + // The builder will consist of some integers that indicate slices of the
|
| + // input string and some replacements that were returned from the replace
|
| + // function.
|
| + int match_start = 0;
|
| + for (int i = 0; i < result_length; i++) {
|
| + Handle<Object> elem = FixedArray::get(*result_array, i, isolate);
|
| + if (elem->IsSmi()) {
|
| + // Integers represent slices of the original string.
|
| + // TODO(jgruber): Maybe we don't need this weird encoding anymore (in
|
| + // preparation to invoking StringBuilderConcat), but can just copy into
|
| + // the result string with the IncrementalStringBuilder as we go?
|
| + const int elem_value = Handle<Smi>::cast(elem)->value();
|
| + if (elem_value > 0) {
|
| + match_start = (elem_value >> 11) + (elem_value & 0x7ff);
|
| + } else {
|
| + Handle<Object> next_elem =
|
| + FixedArray::get(*result_array, ++i, isolate);
|
| + const int next_elem_value = Handle<Smi>::cast(next_elem)->value();
|
| + match_start = next_elem_value - elem_value;
|
| + }
|
| + } else {
|
| + DCHECK(elem->IsString());
|
| + Handle<String> elem_string = Handle<String>::cast(elem);
|
| +
|
| + // Overwrite the i'th element in the results with the string we got
|
| + // back from the callback function.
|
| + const int argc = 3;
|
| + ScopedVector<Handle<Object>> argv(argc);
|
| +
|
| + argv[0] = elem_string;
|
| + argv[1] = handle(Smi::FromInt(match_start), isolate);
|
| + argv[2] = 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);
|
| +
|
| + result_array->set(i, *replacement);
|
| + match_start += elem_string->length();
|
| + }
|
| + }
|
| + } else {
|
| + DCHECK(num_captures > 1);
|
| + for (int i = 0; i < result_length; i++) {
|
| + Handle<Object> elem = FixedArray::get(*result_array, i, isolate);
|
| + if (elem->IsSmi()) continue;
|
| +
|
| + // TODO(jgruber): We can skip this whole round-trip through a JS array
|
| + // for result_array.
|
| + Handle<JSArray> elem_array = Handle<JSArray>::cast(elem);
|
| + Handle<FixedArray> elem_array_elems(
|
| + FixedArray::cast(elem_array->elements()), isolate);
|
| +
|
| + const int argc = elem_array_elems->length();
|
| + ScopedVector<Handle<Object>> argv(argc);
|
| +
|
| + for (int j = 0; j < argc; j++) {
|
| + argv[j] = FixedArray::get(*elem_array_elems, j, isolate);
|
| + }
|
| +
|
| + // TODO(jgruber): This call is another pattern we could refactor.
|
| + 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);
|
| +
|
| + result_array->set(i, *replacement);
|
| + }
|
| + }
|
| +
|
| + if (result_length == 0) {
|
| + return factory->empty_string();
|
| + } else if (result_length == 1) {
|
| + Handle<Object> first = FixedArray::get(*result_array, 0, isolate);
|
| + if (first->IsString()) return Handle<String>::cast(first);
|
| + }
|
| +
|
| + bool one_byte = subject->HasOnlyOneByteChars();
|
| + const int length = StringBuilderConcatLength(subject->length(), *result_array,
|
| + result_length, &one_byte);
|
| +
|
| + if (length == -1) {
|
| + isolate->Throw(isolate->heap()->illegal_argument_string());
|
| + return MaybeHandle<String>();
|
| + }
|
| +
|
| + if (one_byte) {
|
| + Handle<SeqOneByteString> answer;
|
| + ASSIGN_RETURN_ON_EXCEPTION(isolate, answer,
|
| + isolate->factory()->NewRawOneByteString(length),
|
| + String);
|
| + StringBuilderConcatHelper(*subject, answer->GetChars(), *result_array,
|
| + result_length);
|
| + return answer;
|
| + } else {
|
| + DCHECK(!one_byte);
|
| + Handle<SeqTwoByteString> answer;
|
| + ASSIGN_RETURN_ON_EXCEPTION(isolate, answer,
|
| + isolate->factory()->NewRawTwoByteString(length),
|
| + String);
|
| + StringBuilderConcatHelper(*subject, answer->GetChars(), *result_array,
|
| + result_length);
|
| + return answer;
|
| + }
|
| +
|
| + UNREACHABLE();
|
| + return MaybeHandle<String>();
|
| +}
|
| +
|
| +MaybeHandle<String> StringReplaceNonGlobalRegExpWithFunction(
|
| + Isolate* isolate, Handle<String> subject, Handle<JSRegExp> regexp,
|
| + Handle<Object> replace_obj) {
|
| + Factory* factory = isolate->factory();
|
| + Handle<JSObject> 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<JSObject> match_indices = Handle<JSObject>::cast(match_indices_obj);
|
| +
|
| + const int index = RegExpUtils::GetLastMatchCapture(isolate, match_indices, 0);
|
| + const int end_of_match =
|
| + RegExpUtils::GetLastMatchCapture(isolate, match_indices, 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 =
|
| + RegExpUtils::GetLastMatchNumberOfCaptures(isolate, match_indices) / 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[m] = handle(Smi::FromInt(index), isolate);
|
| + argv[m + 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.
|
| +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;
|
| +
|
| + const bool functional_replace = replace_obj->IsCallable();
|
| + if (!functional_replace) {
|
| + Handle<String> replace;
|
| + ASSIGN_RETURN_ON_EXCEPTION(isolate, replace,
|
| + Object::ToString(isolate, replace_obj), String);
|
| + replace = String::Flatten(replace);
|
| +
|
| + Handle<JSObject> 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<JSReceiver>::cast(match_indices_obj);
|
| +
|
| + Handle<Object> start_index_obj =
|
| + JSReceiver::GetElement(isolate, match_indices,
|
| + RegExpImpl::kFirstCapture)
|
| + .ToHandleChecked();
|
| + const int start_index = Handle<Smi>::cast(start_index_obj)->value();
|
| +
|
| + Handle<Object> end_index_obj =
|
| + JSReceiver::GetElement(isolate, match_indices,
|
| + RegExpImpl::kFirstCapture + 1)
|
| + .ToHandleChecked();
|
| + const int end_index = Handle<Smi>::cast(end_index_obj)->value();
|
| +
|
| + IncrementalStringBuilder builder(isolate);
|
| + builder.AppendString(factory->NewSubString(string, 0, start_index));
|
| +
|
| + if (replace->length() > 0) {
|
| + MatchInfoBackedMatch m(isolate, string, last_match_info);
|
| + 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>();
|
| + }
|
| + }
|
| + } else {
|
| + DCHECK(functional_replace);
|
| + if (global) {
|
| + // Global regexp search, function replace.
|
| + return StringReplaceGlobalRegExpWithFunction(isolate, string, regexp,
|
| + replace_obj);
|
| + } else {
|
| + // Non-global regexp search, function replace.
|
| + return StringReplaceNonGlobalRegExpWithFunction(isolate, string, regexp,
|
| + replace_obj);
|
| + }
|
| + }
|
| +
|
| + UNREACHABLE();
|
| + return MaybeHandle<String>();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// 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);
|
| +
|
| + 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));
|
| + }
|
| +
|
| + // TODO(adamk): this fast path is wrong as we doesn't ensure that 'exec'
|
| + // is actually a data property on RegExp.prototype.
|
| + Handle<Object> exec = factory->undefined_value();
|
| + if (recv->IsJSRegExp()) {
|
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
| + isolate, exec, JSObject::GetProperty(
|
| + recv, factory->NewStringFromAsciiChecked("exec")));
|
| + if (RegExpUtils::IsBuiltinExec(exec)) {
|
| + RETURN_RESULT_OR_FAILURE(
|
| + isolate, RegExpReplace(isolate, Handle<JSRegExp>::cast(recv), string,
|
| + replace_obj));
|
| + }
|
| + }
|
| +
|
| + Zone zone(isolate->allocator());
|
| + ZoneVector<Handle<Object>> results(&zone);
|
| +
|
| + while (true) {
|
| + Handle<Object> result;
|
| + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
| + isolate, result, RegExpUtils::RegExpExec(isolate, recv, string, exec));
|
| +
|
| + // Ensure exec will be read again on the next loop through.
|
| + exec = 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);
|
| @@ -1025,5 +1628,6 @@ RUNTIME_FUNCTION(Runtime_IsRegExp) {
|
| CONVERT_ARG_CHECKED(Object, obj, 0);
|
| return isolate->heap()->ToBoolean(obj->IsJSRegExp());
|
| }
|
| +
|
| } // namespace internal
|
| } // namespace v8
|
|
|