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/execution/arguments-inl.h"
6
#include "src/heap/heap-inl.h"
7
#include "src/logging/counters.h"
8
#include "src/numbers/conversions.h"
9
#include "src/objects/js-array-inl.h"
10
#include "src/objects/objects-inl.h"
11
#include "src/objects/slots.h"
12
#include "src/objects/smi.h"
13
#include "src/regexp/regexp-utils.h"
14
#include "src/runtime/runtime-utils.h"
15 16
#include "src/strings/string-builder-inl.h"
#include "src/strings/string-search.h"
17 18 19 20

namespace v8 {
namespace internal {

21 22
RUNTIME_FUNCTION(Runtime_GetSubstitution) {
  HandleScope scope(isolate);
23
  DCHECK_EQ(5, args.length());
24 25 26 27
  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);
28
  CONVERT_SMI_ARG_CHECKED(start_index, 4);
29 30 31 32 33 34 35 36 37

  // 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_; }
38 39 40 41 42
    Handle<String> GetPrefix() override { return prefix_; }
    Handle<String> GetSuffix() override { return suffix_; }

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

   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(
63 64
      isolate,
      String::GetSubstitution(isolate, &match, replacement, start_index));
65 66
}

67 68 69 70 71 72 73 74 75 76 77
// 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()) {
78
    ConsString cons = ConsString::cast(*subject);
79 80
    Handle<String> first = handle(cons.first(), isolate);
    Handle<String> second = handle(cons.second(), isolate);
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    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 {
98
    int index = String::IndexOf(isolate, subject, search, 0);
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
    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);
114
  DCHECK_EQ(3, args.length());
115 116 117 118 119 120 121 122 123 124 125 126 127
  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;
  }
128 129
  if (isolate->has_pending_exception())
    return ReadOnlyRoots(isolate).exception();
130

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

142 143 144 145 146 147
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);
148
  return *String::Trim(isolate, string, trim_mode);
149 150
}

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
// 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());
173
    return ReadOnlyRoots(isolate).exception();
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
  }
  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);
}

194 195
// ES6 #sec-string.prototype.indexof
// String.prototype.indexOf(searchString [, position])
196 197
RUNTIME_FUNCTION(Runtime_StringIndexOf) {
  HandleScope scope(isolate);
198
  DCHECK_EQ(3, args.length());
199 200 201 202 203 204 205 206 207
  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);
208
  DCHECK_EQ(3, args.length());
209 210 211 212 213 214
  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)));
215 216 217
}

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

223
RUNTIME_FUNCTION(Runtime_StringSubstring) {
224
  HandleScope scope(isolate);
225
  DCHECK_EQ(3, args.length());
226
  CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
227 228 229 230 231
  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());
232 233 234 235
  isolate->counters()->sub_string_runtime()->Increment();
  return *isolate->factory()->NewSubString(string, start, end);
}

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


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

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

  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.
264
  subject = String::Flatten(isolate, subject);
265 266

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

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

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

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

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

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

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

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

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

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

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

  if (one_byte) {
    Handle<SeqOneByteString> answer;
    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
        isolate, answer, isolate->factory()->NewRawOneByteString(length));
330
    DisallowHeapAllocation no_gc;
331
    StringBuilderConcatHelper(*special, answer->GetChars(no_gc),
332 333 334 335 336 337 338
                              FixedArray::cast(array->elements()),
                              array_length);
    return *answer;
  } else {
    Handle<SeqTwoByteString> answer;
    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
        isolate, answer, isolate->factory()->NewRawTwoByteString(length));
339
    DisallowHeapAllocation no_gc;
340
    StringBuilderConcatHelper(*special, answer->GetChars(no_gc),
341 342 343 344 345 346 347 348 349 350 351 352
                              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,
353
                                         FixedArray elements, int length) {
354
  DisallowHeapAllocation no_gc;
355
  FixedArray one_byte_cache = heap->single_character_string_cache();
356
  Object undefined = ReadOnlyRoots(heap).undefined_value();
357
  int i;
358
  WriteBarrierMode mode = elements.GetWriteBarrierMode(no_gc);
359
  for (i = 0; i < length; ++i) {
360
    Object value = one_byte_cache.get(chars[i]);
361
    if (value == undefined) break;
362
    elements.set(i, value, mode);
363 364
  }
  if (i < length) {
365
    MemsetTagged(elements.RawFieldOfElementAt(i), Smi::zero(), length - i);
366 367 368
  }
#ifdef DEBUG
  for (int j = 0; j < length; ++j) {
369
    Object element = elements.get(j);
370
    DCHECK(element == Smi::zero() ||
371
           (element.IsString() && String::cast(element).LooksValid()));
372 373 374 375 376 377 378 379 380
  }
#endif
  return i;
}

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

385
  s = String::Flatten(isolate, s);
386 387 388 389 390 391 392 393 394
  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;
395
    String::FlatContent content = s->GetFlatContent(no_gc);
396 397 398 399
    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.
400
      position = CopyCachedOneByteCharsToArray(isolate->heap(), chars.begin(),
401 402
                                               *elements, length);
    } else {
403 404
      MemsetTagged(elements->data_start(),
                   ReadOnlyRoots(isolate).undefined_value(), length);
405 406 407 408 409 410 411 412 413 414 415 416
    }
  } 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) {
417
    DCHECK_EQ(String::cast(elements->get(i)).length(), 1);
418 419 420 421 422 423
  }
#endif

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

424 425 426 427 428
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);
429
  ComparisonResult result = String::Compare(isolate, x, y);
430 431 432
  DCHECK_NE(result, ComparisonResult::kUndefined);
  return isolate->heap()->ToBoolean(
      ComparisonResultToBool(Operation::kLessThan, result));
433 434 435 436 437 438 439
}

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);
440
  ComparisonResult result = String::Compare(isolate, x, y);
441 442 443
  DCHECK_NE(result, ComparisonResult::kUndefined);
  return isolate->heap()->ToBoolean(
      ComparisonResultToBool(Operation::kLessThanOrEqual, result));
444 445 446 447 448 449 450
}

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);
451
  ComparisonResult result = String::Compare(isolate, x, y);
452 453 454
  DCHECK_NE(result, ComparisonResult::kUndefined);
  return isolate->heap()->ToBoolean(
      ComparisonResultToBool(Operation::kGreaterThan, result));
455 456 457 458 459 460 461
}

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);
462
  ComparisonResult result = String::Compare(isolate, x, y);
463 464 465
  DCHECK_NE(result, ComparisonResult::kUndefined);
  return isolate->heap()->ToBoolean(
      ComparisonResultToBool(Operation::kGreaterThanOrEqual, result));
466 467
}

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

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

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

488
RUNTIME_FUNCTION(Runtime_StringCompareSequence) {
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
  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();
}
510 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

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();
}

561 562
}  // namespace internal
}  // namespace v8