runtime-strings.cc 19.8 KB
Newer Older
1 2 3 4
// 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.

5
#include "src/arguments-inl.h"
6 7
#include "src/conversions.h"
#include "src/counters.h"
8
#include "src/heap/heap-inl.h"
9
#include "src/objects-inl.h"
10
#include "src/objects/js-array-inl.h"
11
#include "src/objects/slots.h"
12
#include "src/objects/smi.h"
13
#include "src/regexp/jsregexp-inl.h"
14
#include "src/regexp/regexp-utils.h"
15
#include "src/runtime/runtime-utils.h"
16
#include "src/string-builder-inl.h"
17 18 19 20 21
#include "src/string-search.h"

namespace v8 {
namespace internal {

22 23
RUNTIME_FUNCTION(Runtime_GetSubstitution) {
  HandleScope scope(isolate);
24
  DCHECK_EQ(5, args.length());
25 26 27 28
  CONVERT_ARG_HANDLE_CHECKED(String, matched, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, subject, 1);
  CONVERT_SMI_ARG_CHECKED(position, 2);
  CONVERT_ARG_HANDLE_CHECKED(String, replacement, 3);
29
  CONVERT_SMI_ARG_CHECKED(start_index, 4);
30 31 32 33 34 35 36 37 38

  // A simple match without captures.
  class SimpleMatch : public String::Match {
   public:
    SimpleMatch(Handle<String> match, Handle<String> prefix,
                Handle<String> suffix)
        : match_(match), prefix_(prefix), suffix_(suffix) {}

    Handle<String> GetMatch() override { return match_; }
39 40 41 42 43
    Handle<String> GetPrefix() override { return prefix_; }
    Handle<String> GetSuffix() override { return suffix_; }

    int CaptureCount() override { return 0; }
    bool HasNamedCaptures() override { return false; }
44 45 46 47
    MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
      *capture_exists = false;
      return match_;  // Return arbitrary string handle.
    }
48
    MaybeHandle<String> GetNamedCapture(Handle<String> name,
49
                                        CaptureState* state) override {
50 51
      UNREACHABLE();
    }
52 53 54 55 56 57 58 59 60 61 62 63

   private:
    Handle<String> match_, prefix_, suffix_;
  };

  Handle<String> prefix =
      isolate->factory()->NewSubString(subject, 0, position);
  Handle<String> suffix = isolate->factory()->NewSubString(
      subject, position + matched->length(), subject->length());
  SimpleMatch match(matched, prefix, suffix);

  RETURN_RESULT_OR_FAILURE(
64 65
      isolate,
      String::GetSubstitution(isolate, &match, replacement, start_index));
66 67
}

68 69 70 71 72 73 74 75 76 77 78
// This may return an empty MaybeHandle if an exception is thrown or
// we abort due to reaching the recursion limit.
MaybeHandle<String> StringReplaceOneCharWithString(
    Isolate* isolate, Handle<String> subject, Handle<String> search,
    Handle<String> replace, bool* found, int recursion_limit) {
  StackLimitCheck stackLimitCheck(isolate);
  if (stackLimitCheck.HasOverflowed() || (recursion_limit == 0)) {
    return MaybeHandle<String>();
  }
  recursion_limit--;
  if (subject->IsConsString()) {
79
    ConsString cons = ConsString::cast(*subject);
80 81
    Handle<String> first = handle(cons->first(), isolate);
    Handle<String> second = handle(cons->second(), isolate);
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
    Handle<String> new_first;
    if (!StringReplaceOneCharWithString(isolate, first, search, replace, found,
                                        recursion_limit).ToHandle(&new_first)) {
      return MaybeHandle<String>();
    }
    if (*found) return isolate->factory()->NewConsString(new_first, second);

    Handle<String> new_second;
    if (!StringReplaceOneCharWithString(isolate, second, search, replace, found,
                                        recursion_limit)
             .ToHandle(&new_second)) {
      return MaybeHandle<String>();
    }
    if (*found) return isolate->factory()->NewConsString(first, new_second);

    return subject;
  } else {
99
    int index = String::IndexOf(isolate, subject, search, 0);
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
    if (index == -1) return subject;
    *found = true;
    Handle<String> first = isolate->factory()->NewSubString(subject, 0, index);
    Handle<String> cons1;
    ASSIGN_RETURN_ON_EXCEPTION(
        isolate, cons1, isolate->factory()->NewConsString(first, replace),
        String);
    Handle<String> second =
        isolate->factory()->NewSubString(subject, index + 1, subject->length());
    return isolate->factory()->NewConsString(cons1, second);
  }
}

RUNTIME_FUNCTION(Runtime_StringReplaceOneCharWithString) {
  HandleScope scope(isolate);
115
  DCHECK_EQ(3, args.length());
116 117 118 119 120 121 122 123 124 125 126 127 128
  CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, search, 1);
  CONVERT_ARG_HANDLE_CHECKED(String, replace, 2);

  // If the cons string tree is too deep, we simply abort the recursion and
  // retry with a flattened subject string.
  const int kRecursionLimit = 0x1000;
  bool found = false;
  Handle<String> result;
  if (StringReplaceOneCharWithString(isolate, subject, search, replace, &found,
                                     kRecursionLimit).ToHandle(&result)) {
    return *result;
  }
129 130
  if (isolate->has_pending_exception())
    return ReadOnlyRoots(isolate).exception();
131

132
  subject = String::Flatten(isolate, subject);
133 134 135 136
  if (StringReplaceOneCharWithString(isolate, subject, search, replace, &found,
                                     kRecursionLimit).ToHandle(&result)) {
    return *result;
  }
137 138
  if (isolate->has_pending_exception())
    return ReadOnlyRoots(isolate).exception();
139 140
  // In case of empty handle and no pending exception we have stack overflow.
  return isolate->StackOverflow();
141 142
}

143 144 145 146 147 148
RUNTIME_FUNCTION(Runtime_StringTrim) {
  HandleScope scope(isolate);
  DCHECK_EQ(2, args.length());
  Handle<String> string = args.at<String>(0);
  CONVERT_SMI_ARG_CHECKED(mode, 1);
  String::TrimMode trim_mode = static_cast<String::TrimMode>(mode);
149
  return *String::Trim(isolate, string, trim_mode);
150 151
}

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
// ES6 #sec-string.prototype.includes
// String.prototype.includes(searchString [, position])
RUNTIME_FUNCTION(Runtime_StringIncludes) {
  HandleScope scope(isolate);
  DCHECK_EQ(3, args.length());

  Handle<Object> receiver = args.at(0);
  if (receiver->IsNullOrUndefined(isolate)) {
    THROW_NEW_ERROR_RETURN_FAILURE(
        isolate, NewTypeError(MessageTemplate::kCalledOnNullOrUndefined,
                              isolate->factory()->NewStringFromAsciiChecked(
                                  "String.prototype.includes")));
  }
  Handle<String> receiver_string;
  ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver_string,
                                     Object::ToString(isolate, receiver));

  // Check if the search string is a regExp and fail if it is.
  Handle<Object> search = args.at(1);
  Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search);
  if (is_reg_exp.IsNothing()) {
    DCHECK(isolate->has_pending_exception());
174
    return ReadOnlyRoots(isolate).exception();
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
  }
  if (is_reg_exp.FromJust()) {
    THROW_NEW_ERROR_RETURN_FAILURE(
        isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp,
                              isolate->factory()->NewStringFromStaticChars(
                                  "String.prototype.includes")));
  }
  Handle<String> search_string;
  ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string,
                                     Object::ToString(isolate, args.at(1)));
  Handle<Object> position;
  ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position,
                                     Object::ToInteger(isolate, args.at(2)));

  uint32_t index = receiver_string->ToValidIndex(*position);
  int index_in_str =
      String::IndexOf(isolate, receiver_string, search_string, index);
  return *isolate->factory()->ToBoolean(index_in_str != -1);
}

195 196
// ES6 #sec-string.prototype.indexof
// String.prototype.indexOf(searchString [, position])
197 198
RUNTIME_FUNCTION(Runtime_StringIndexOf) {
  HandleScope scope(isolate);
199
  DCHECK_EQ(3, args.length());
200 201 202 203 204 205 206 207 208
  return String::IndexOf(isolate, args.at(0), args.at(1), args.at(2));
}

// ES6 #sec-string.prototype.indexof
// String.prototype.indexOf(searchString, position)
// Fast version that assumes that does not perform conversions of the incoming
// arguments.
RUNTIME_FUNCTION(Runtime_StringIndexOfUnchecked) {
  HandleScope scope(isolate);
209
  DCHECK_EQ(3, args.length());
210 211 212 213 214 215
  Handle<String> receiver_string = args.at<String>(0);
  Handle<String> search_string = args.at<String>(1);
  int index = std::min(std::max(args.smi_at(2), 0), receiver_string->length());

  return Smi::FromInt(String::IndexOf(isolate, receiver_string, search_string,
                                      static_cast<uint32_t>(index)));
216 217 218
}

RUNTIME_FUNCTION(Runtime_StringLastIndexOf) {
219
  HandleScope handle_scope(isolate);
220
  return String::LastIndexOf(isolate, args.at(0), args.at(1),
221
                             isolate->factory()->undefined_value());
222 223
}

224
RUNTIME_FUNCTION(Runtime_StringSubstring) {
225
  HandleScope scope(isolate);
226
  DCHECK_EQ(3, args.length());
227
  CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
228 229 230 231 232
  CONVERT_INT32_ARG_CHECKED(start, 1);
  CONVERT_INT32_ARG_CHECKED(end, 2);
  DCHECK_LE(0, start);
  DCHECK_LE(start, end);
  DCHECK_LE(end, string->length());
233 234 235 236
  isolate->counters()->sub_string_runtime()->Increment();
  return *isolate->factory()->NewSubString(string, start, end);
}

237
RUNTIME_FUNCTION(Runtime_StringAdd) {
238
  HandleScope scope(isolate);
239
  DCHECK_EQ(2, args.length());
240 241
  CONVERT_ARG_HANDLE_CHECKED(String, str1, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, str2, 1);
242
  isolate->counters()->string_add_runtime()->Increment();
243 244
  RETURN_RESULT_OR_FAILURE(isolate,
                           isolate->factory()->NewConsString(str1, str2));
245 246 247 248 249
}


RUNTIME_FUNCTION(Runtime_InternalizeString) {
  HandleScope handles(isolate);
250
  DCHECK_EQ(1, args.length());
251 252 253 254
  CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
  return *isolate->factory()->InternalizeString(string);
}

255
RUNTIME_FUNCTION(Runtime_StringCharCodeAt) {
256
  HandleScope handle_scope(isolate);
257
  DCHECK_EQ(2, args.length());
258 259 260 261 262 263 264

  CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
  CONVERT_NUMBER_CHECKED(uint32_t, i, Uint32, args[1]);

  // Flatten the string.  If someone wants to get a char at an index
  // in a cons string, it is likely that more indices will be
  // accessed.
265
  subject = String::Flatten(isolate, subject);
266 267

  if (i >= static_cast<uint32_t>(subject->length())) {
268
    return ReadOnlyRoots(isolate).nan_value();
269 270 271 272 273 274 275
  }

  return Smi::FromInt(subject->Get(i));
}

RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
  HandleScope scope(isolate);
276
  DCHECK_EQ(3, args.length());
277 278 279 280 281 282 283 284
  CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
  int32_t array_length;
  if (!args[1]->ToInt32(&array_length)) {
    THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewInvalidStringLengthError());
  }
  CONVERT_ARG_HANDLE_CHECKED(String, special, 2);

  size_t actual_array_length = 0;
285
  CHECK(TryNumberToSize(array->length(), &actual_array_length));
286
  CHECK_GE(array_length, 0);
287
  CHECK(static_cast<size_t>(array_length) <= actual_array_length);
288 289

  // This assumption is used by the slice encoding in one or two smis.
290
  DCHECK_GE(Smi::kMaxValue, String::kMaxLength);
291

292
  CHECK(array->HasFastElements());
293 294 295
  JSObject::EnsureCanContainHeapObjectElements(array);

  int special_length = special->length();
296
  if (!array->HasObjectElements()) {
297
    return isolate->Throw(ReadOnlyRoots(isolate).illegal_argument_string());
298 299 300
  }

  int length;
301
  bool one_byte = special->IsOneByteRepresentation();
302 303 304

  {
    DisallowHeapAllocation no_gc;
305
    FixedArray fixed_array = FixedArray::cast(array->elements());
306 307 308 309 310
    if (fixed_array->length() < array_length) {
      array_length = fixed_array->length();
    }

    if (array_length == 0) {
311
      return ReadOnlyRoots(isolate).empty_string();
312
    } else if (array_length == 1) {
313
      Object first = fixed_array->get(0);
314 315 316 317 318 319 320
      if (first->IsString()) return first;
    }
    length = StringBuilderConcatLength(special_length, fixed_array,
                                       array_length, &one_byte);
  }

  if (length == -1) {
321
    return isolate->Throw(ReadOnlyRoots(isolate).illegal_argument_string());
322
  }
323
  if (length == 0) {
324
    return ReadOnlyRoots(isolate).empty_string();
325
  }
326 327 328 329 330

  if (one_byte) {
    Handle<SeqOneByteString> answer;
    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
        isolate, answer, isolate->factory()->NewRawOneByteString(length));
331
    DisallowHeapAllocation no_gc;
332
    StringBuilderConcatHelper(*special, answer->GetChars(no_gc),
333 334 335 336 337 338 339
                              FixedArray::cast(array->elements()),
                              array_length);
    return *answer;
  } else {
    Handle<SeqTwoByteString> answer;
    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
        isolate, answer, isolate->factory()->NewRawTwoByteString(length));
340
    DisallowHeapAllocation no_gc;
341
    StringBuilderConcatHelper(*special, answer->GetChars(no_gc),
342 343 344 345 346 347 348 349 350 351 352 353
                              FixedArray::cast(array->elements()),
                              array_length);
    return *answer;
  }
}


// Copies Latin1 characters to the given fixed array looking up
// one-char strings in the cache. Gives up on the first char that is
// not in the cache and fills the remainder with smi zeros. Returns
// the length of the successfully copied prefix.
static int CopyCachedOneByteCharsToArray(Heap* heap, const uint8_t* chars,
354
                                         FixedArray elements, int length) {
355
  DisallowHeapAllocation no_gc;
356
  FixedArray one_byte_cache = heap->single_character_string_cache();
357
  Object undefined = ReadOnlyRoots(heap).undefined_value();
358 359 360
  int i;
  WriteBarrierMode mode = elements->GetWriteBarrierMode(no_gc);
  for (i = 0; i < length; ++i) {
361
    Object value = one_byte_cache->get(chars[i]);
362 363 364 365
    if (value == undefined) break;
    elements->set(i, value, mode);
  }
  if (i < length) {
366
    MemsetTagged(elements->RawFieldOfElementAt(i), Smi::kZero, length - i);
367 368 369
  }
#ifdef DEBUG
  for (int j = 0; j < length; ++j) {
370
    Object element = elements->get(j);
371
    DCHECK(element == Smi::kZero ||
372 373 374 375 376 377 378 379 380 381
           (element->IsString() && String::cast(element)->LooksValid()));
  }
#endif
  return i;
}

// Converts a String to JSArray.
// For example, "foo" => ["f", "o", "o"].
RUNTIME_FUNCTION(Runtime_StringToArray) {
  HandleScope scope(isolate);
382
  DCHECK_EQ(2, args.length());
383 384 385
  CONVERT_ARG_HANDLE_CHECKED(String, s, 0);
  CONVERT_NUMBER_CHECKED(uint32_t, limit, Uint32, args[1]);

386
  s = String::Flatten(isolate, s);
387 388 389 390 391 392 393 394 395
  const int length = static_cast<int>(Min<uint32_t>(s->length(), limit));

  Handle<FixedArray> elements;
  int position = 0;
  if (s->IsFlat() && s->IsOneByteRepresentation()) {
    // Try using cached chars where possible.
    elements = isolate->factory()->NewUninitializedFixedArray(length);

    DisallowHeapAllocation no_gc;
396
    String::FlatContent content = s->GetFlatContent(no_gc);
397 398 399 400
    if (content.IsOneByte()) {
      Vector<const uint8_t> chars = content.ToOneByteVector();
      // Note, this will initialize all elements (not only the prefix)
      // to prevent GC from seeing partially initialized array.
401
      position = CopyCachedOneByteCharsToArray(isolate->heap(), chars.begin(),
402 403
                                               *elements, length);
    } else {
404 405
      MemsetTagged(elements->data_start(),
                   ReadOnlyRoots(isolate).undefined_value(), length);
406 407 408 409 410 411 412 413 414 415 416 417
    }
  } else {
    elements = isolate->factory()->NewFixedArray(length);
  }
  for (int i = position; i < length; ++i) {
    Handle<Object> str =
        isolate->factory()->LookupSingleCharacterStringFromCode(s->Get(i));
    elements->set(i, *str);
  }

#ifdef DEBUG
  for (int i = 0; i < length; ++i) {
418
    DCHECK_EQ(String::cast(elements->get(i))->length(), 1);
419 420 421 422 423 424
  }
#endif

  return *isolate->factory()->NewJSArrayWithElements(elements);
}

425 426 427 428 429
RUNTIME_FUNCTION(Runtime_StringLessThan) {
  HandleScope handle_scope(isolate);
  DCHECK_EQ(2, args.length());
  CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
430
  ComparisonResult result = String::Compare(isolate, x, y);
431 432 433
  DCHECK_NE(result, ComparisonResult::kUndefined);
  return isolate->heap()->ToBoolean(
      ComparisonResultToBool(Operation::kLessThan, result));
434 435 436 437 438 439 440
}

RUNTIME_FUNCTION(Runtime_StringLessThanOrEqual) {
  HandleScope handle_scope(isolate);
  DCHECK_EQ(2, args.length());
  CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
441
  ComparisonResult result = String::Compare(isolate, x, y);
442 443 444
  DCHECK_NE(result, ComparisonResult::kUndefined);
  return isolate->heap()->ToBoolean(
      ComparisonResultToBool(Operation::kLessThanOrEqual, result));
445 446 447 448 449 450 451
}

RUNTIME_FUNCTION(Runtime_StringGreaterThan) {
  HandleScope handle_scope(isolate);
  DCHECK_EQ(2, args.length());
  CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
452
  ComparisonResult result = String::Compare(isolate, x, y);
453 454 455
  DCHECK_NE(result, ComparisonResult::kUndefined);
  return isolate->heap()->ToBoolean(
      ComparisonResultToBool(Operation::kGreaterThan, result));
456 457 458 459 460 461 462
}

RUNTIME_FUNCTION(Runtime_StringGreaterThanOrEqual) {
  HandleScope handle_scope(isolate);
  DCHECK_EQ(2, args.length());
  CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
463
  ComparisonResult result = String::Compare(isolate, x, y);
464 465 466
  DCHECK_NE(result, ComparisonResult::kUndefined);
  return isolate->heap()->ToBoolean(
      ComparisonResultToBool(Operation::kGreaterThanOrEqual, result));
467 468
}

469
RUNTIME_FUNCTION(Runtime_StringEqual) {
470
  HandleScope handle_scope(isolate);
471
  DCHECK_EQ(2, args.length());
472 473
  CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
474
  return isolate->heap()->ToBoolean(String::Equals(isolate, x, y));
475 476
}

477 478
RUNTIME_FUNCTION(Runtime_FlattenString) {
  HandleScope scope(isolate);
479
  DCHECK_EQ(1, args.length());
480
  CONVERT_ARG_HANDLE_CHECKED(String, str, 0);
481
  return *String::Flatten(isolate, str);
482 483
}

484 485 486 487 488
RUNTIME_FUNCTION(Runtime_StringMaxLength) {
  SealHandleScope shs(isolate);
  return Smi::FromInt(String::kMaxLength);
}

489
RUNTIME_FUNCTION(Runtime_StringCompareSequence) {
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
  HandleScope handle_scope(isolate);
  DCHECK_EQ(3, args.length());
  CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
  CONVERT_ARG_HANDLE_CHECKED(String, search_string, 1);
  CONVERT_NUMBER_CHECKED(int, start, Int32, args[2]);

  // Check if start + searchLength is in bounds.
  DCHECK_LE(start + search_string->length(), string->length());

  FlatStringReader string_reader(isolate, String::Flatten(isolate, string));
  FlatStringReader search_reader(isolate,
                                 String::Flatten(isolate, search_string));

  for (int i = 0; i < search_string->length(); i++) {
    if (string_reader.Get(start + i) != search_reader.Get(i)) {
      return ReadOnlyRoots(isolate).false_value();
    }
  }

  return ReadOnlyRoots(isolate).true_value();
}
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561

RUNTIME_FUNCTION(Runtime_StringEscapeQuotes) {
  HandleScope handle_scope(isolate);
  DCHECK_EQ(1, args.length());
  CONVERT_ARG_HANDLE_CHECKED(String, string, 0);

  // Equivalent to global replacement `string.replace(/"/g, "&quot")`, but this
  // does not modify any global state (e.g. the regexp match info).

  const int string_length = string->length();
  Handle<String> quotes =
      isolate->factory()->LookupSingleCharacterStringFromCode('"');

  int index = String::IndexOf(isolate, string, quotes, 0);

  // No quotes, nothing to do.
  if (index == -1) return *string;

  // Find all quotes.
  std::vector<int> indices = {index};
  while (index + 1 < string_length) {
    index = String::IndexOf(isolate, string, quotes, index + 1);
    if (index == -1) break;
    indices.emplace_back(index);
  }

  // Build the replacement string.
  Handle<String> replacement =
      isolate->factory()->NewStringFromAsciiChecked("&quot;");
  const int estimated_part_count = static_cast<int>(indices.size()) * 2 + 1;
  ReplacementStringBuilder builder(isolate->heap(), string,
                                   estimated_part_count);

  int prev_index = -1;  // Start at -1 to avoid special-casing the first match.
  for (int index : indices) {
    const int slice_start = prev_index + 1;
    const int slice_end = index;
    if (slice_end > slice_start) {
      builder.AddSubjectSlice(slice_start, slice_end);
    }
    builder.AddString(replacement);
    prev_index = index;
  }

  if (prev_index < string_length - 1) {
    builder.AddSubjectSlice(prev_index + 1, string_length);
  }

  return *builder.ToString().ToHandleChecked();
}

562 563
}  // namespace internal
}  // namespace v8