Line data Source code
1 : // Copyright 2014 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include "src/arguments-inl.h"
6 : #include "src/conversions.h"
7 : #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 : #include "src/string-search.h"
18 :
19 : namespace v8 {
20 : namespace internal {
21 :
22 306 : RUNTIME_FUNCTION(Runtime_GetSubstitution) {
23 : HandleScope scope(isolate);
24 : DCHECK_EQ(5, args.length());
25 153 : CONVERT_ARG_HANDLE_CHECKED(String, matched, 0);
26 153 : CONVERT_ARG_HANDLE_CHECKED(String, subject, 1);
27 153 : CONVERT_SMI_ARG_CHECKED(position, 2);
28 153 : CONVERT_ARG_HANDLE_CHECKED(String, replacement, 3);
29 153 : CONVERT_SMI_ARG_CHECKED(start_index, 4);
30 :
31 : // A simple match without captures.
32 306 : class SimpleMatch : public String::Match {
33 : public:
34 : SimpleMatch(Handle<String> match, Handle<String> prefix,
35 : Handle<String> suffix)
36 153 : : match_(match), prefix_(prefix), suffix_(suffix) {}
37 :
38 36 : Handle<String> GetMatch() override { return match_; }
39 18 : Handle<String> GetPrefix() override { return prefix_; }
40 18 : Handle<String> GetSuffix() override { return suffix_; }
41 :
42 153 : int CaptureCount() override { return 0; }
43 0 : bool HasNamedCaptures() override { return false; }
44 0 : MaybeHandle<String> GetCapture(int i, bool* capture_exists) override {
45 0 : *capture_exists = false;
46 0 : return match_; // Return arbitrary string handle.
47 : }
48 0 : MaybeHandle<String> GetNamedCapture(Handle<String> name,
49 : CaptureState* state) override {
50 0 : UNREACHABLE();
51 : }
52 :
53 : private:
54 : Handle<String> match_, prefix_, suffix_;
55 : };
56 :
57 : Handle<String> prefix =
58 153 : isolate->factory()->NewSubString(subject, 0, position);
59 : Handle<String> suffix = isolate->factory()->NewSubString(
60 153 : subject, position + matched->length(), subject->length());
61 : SimpleMatch match(matched, prefix, suffix);
62 :
63 306 : RETURN_RESULT_OR_FAILURE(
64 : isolate,
65 : String::GetSubstitution(isolate, &match, replacement, start_index));
66 : }
67 :
68 : // This may return an empty MaybeHandle if an exception is thrown or
69 : // we abort due to reaching the recursion limit.
70 82932 : MaybeHandle<String> StringReplaceOneCharWithString(
71 : Isolate* isolate, Handle<String> subject, Handle<String> search,
72 : Handle<String> replace, bool* found, int recursion_limit) {
73 : StackLimitCheck stackLimitCheck(isolate);
74 82932 : if (stackLimitCheck.HasOverflowed() || (recursion_limit == 0)) {
75 420 : return MaybeHandle<String>();
76 : }
77 82512 : recursion_limit--;
78 82512 : if (subject->IsConsString()) {
79 : ConsString cons = ConsString::cast(*subject);
80 78075 : Handle<String> first = handle(cons->first(), isolate);
81 78075 : Handle<String> second = handle(cons->second(), isolate);
82 : Handle<String> new_first;
83 156150 : if (!StringReplaceOneCharWithString(isolate, first, search, replace, found,
84 : recursion_limit).ToHandle(&new_first)) {
85 73764 : return MaybeHandle<String>();
86 : }
87 4311 : if (*found) return isolate->factory()->NewConsString(new_first, second);
88 :
89 : Handle<String> new_second;
90 8622 : if (!StringReplaceOneCharWithString(isolate, second, search, replace, found,
91 : recursion_limit)
92 : .ToHandle(&new_second)) {
93 18 : return MaybeHandle<String>();
94 : }
95 4293 : if (*found) return isolate->factory()->NewConsString(first, new_second);
96 :
97 4275 : return subject;
98 : } else {
99 4437 : int index = String::IndexOf(isolate, subject, search, 0);
100 4437 : if (index == -1) return subject;
101 36 : *found = true;
102 36 : Handle<String> first = isolate->factory()->NewSubString(subject, 0, index);
103 : Handle<String> cons1;
104 72 : ASSIGN_RETURN_ON_EXCEPTION(
105 : isolate, cons1, isolate->factory()->NewConsString(first, replace),
106 : String);
107 : Handle<String> second =
108 36 : isolate->factory()->NewSubString(subject, index + 1, subject->length());
109 36 : return isolate->factory()->NewConsString(cons1, second);
110 : }
111 : }
112 :
113 600 : RUNTIME_FUNCTION(Runtime_StringReplaceOneCharWithString) {
114 : HandleScope scope(isolate);
115 : DCHECK_EQ(3, args.length());
116 300 : CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
117 300 : CONVERT_ARG_HANDLE_CHECKED(String, search, 1);
118 300 : CONVERT_ARG_HANDLE_CHECKED(String, replace, 2);
119 :
120 : // If the cons string tree is too deep, we simply abort the recursion and
121 : // retry with a flattened subject string.
122 : const int kRecursionLimit = 0x1000;
123 300 : bool found = false;
124 : Handle<String> result;
125 600 : if (StringReplaceOneCharWithString(isolate, subject, search, replace, &found,
126 : kRecursionLimit).ToHandle(&result)) {
127 : return *result;
128 : }
129 246 : if (isolate->has_pending_exception())
130 : return ReadOnlyRoots(isolate).exception();
131 :
132 246 : subject = String::Flatten(isolate, subject);
133 492 : if (StringReplaceOneCharWithString(isolate, subject, search, replace, &found,
134 : kRecursionLimit).ToHandle(&result)) {
135 : return *result;
136 : }
137 174 : if (isolate->has_pending_exception())
138 : return ReadOnlyRoots(isolate).exception();
139 : // In case of empty handle and no pending exception we have stack overflow.
140 174 : return isolate->StackOverflow();
141 : }
142 :
143 72 : RUNTIME_FUNCTION(Runtime_StringTrim) {
144 : HandleScope scope(isolate);
145 : DCHECK_EQ(2, args.length());
146 36 : Handle<String> string = args.at<String>(0);
147 36 : CONVERT_SMI_ARG_CHECKED(mode, 1);
148 36 : String::TrimMode trim_mode = static_cast<String::TrimMode>(mode);
149 72 : return *String::Trim(isolate, string, trim_mode);
150 : }
151 :
152 : // ES6 #sec-string.prototype.includes
153 : // String.prototype.includes(searchString [, position])
154 1026 : RUNTIME_FUNCTION(Runtime_StringIncludes) {
155 : HandleScope scope(isolate);
156 : DCHECK_EQ(3, args.length());
157 :
158 : Handle<Object> receiver = args.at(0);
159 513 : if (receiver->IsNullOrUndefined(isolate)) {
160 189 : THROW_NEW_ERROR_RETURN_FAILURE(
161 : isolate, NewTypeError(MessageTemplate::kCalledOnNullOrUndefined,
162 : isolate->factory()->NewStringFromAsciiChecked(
163 : "String.prototype.includes")));
164 : }
165 : Handle<String> receiver_string;
166 900 : ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver_string,
167 : Object::ToString(isolate, receiver));
168 :
169 : // Check if the search string is a regExp and fail if it is.
170 : Handle<Object> search = args.at(1);
171 432 : Maybe<bool> is_reg_exp = RegExpUtils::IsRegExp(isolate, search);
172 432 : if (is_reg_exp.IsNothing()) {
173 : DCHECK(isolate->has_pending_exception());
174 : return ReadOnlyRoots(isolate).exception();
175 : }
176 432 : if (is_reg_exp.FromJust()) {
177 216 : THROW_NEW_ERROR_RETURN_FAILURE(
178 : isolate, NewTypeError(MessageTemplate::kFirstArgumentNotRegExp,
179 : isolate->factory()->NewStringFromStaticChars(
180 : "String.prototype.includes")));
181 : }
182 : Handle<String> search_string;
183 720 : ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string,
184 : Object::ToString(isolate, args.at(1)));
185 : Handle<Object> position;
186 360 : ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position,
187 : Object::ToInteger(isolate, args.at(2)));
188 :
189 360 : uint32_t index = receiver_string->ToValidIndex(*position);
190 : int index_in_str =
191 360 : String::IndexOf(isolate, receiver_string, search_string, index);
192 720 : return *isolate->factory()->ToBoolean(index_in_str != -1);
193 : }
194 :
195 : // ES6 #sec-string.prototype.indexof
196 : // String.prototype.indexOf(searchString [, position])
197 1632 : RUNTIME_FUNCTION(Runtime_StringIndexOf) {
198 : HandleScope scope(isolate);
199 : DCHECK_EQ(3, args.length());
200 816 : return String::IndexOf(isolate, args.at(0), args.at(1), args.at(2));
201 : }
202 :
203 : // ES6 #sec-string.prototype.indexof
204 : // String.prototype.indexOf(searchString, position)
205 : // Fast version that assumes that does not perform conversions of the incoming
206 : // arguments.
207 2067330 : RUNTIME_FUNCTION(Runtime_StringIndexOfUnchecked) {
208 : HandleScope scope(isolate);
209 : DCHECK_EQ(3, args.length());
210 1033665 : Handle<String> receiver_string = args.at<String>(0);
211 1033665 : Handle<String> search_string = args.at<String>(1);
212 3100995 : int index = std::min(std::max(args.smi_at(2), 0), receiver_string->length());
213 :
214 : return Smi::FromInt(String::IndexOf(isolate, receiver_string, search_string,
215 1033665 : static_cast<uint32_t>(index)));
216 : }
217 :
218 10 : RUNTIME_FUNCTION(Runtime_StringLastIndexOf) {
219 : HandleScope handle_scope(isolate);
220 : return String::LastIndexOf(isolate, args.at(0), args.at(1),
221 5 : isolate->factory()->undefined_value());
222 : }
223 :
224 1694476 : RUNTIME_FUNCTION(Runtime_StringSubstring) {
225 : HandleScope scope(isolate);
226 : DCHECK_EQ(3, args.length());
227 847238 : CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
228 1694476 : CONVERT_INT32_ARG_CHECKED(start, 1);
229 1694476 : CONVERT_INT32_ARG_CHECKED(end, 2);
230 : DCHECK_LE(0, start);
231 : DCHECK_LE(start, end);
232 : DCHECK_LE(end, string->length());
233 847238 : isolate->counters()->sub_string_runtime()->Increment();
234 1694476 : return *isolate->factory()->NewSubString(string, start, end);
235 : }
236 :
237 13677440 : RUNTIME_FUNCTION(Runtime_StringAdd) {
238 : HandleScope scope(isolate);
239 : DCHECK_EQ(2, args.length());
240 6838720 : CONVERT_ARG_HANDLE_CHECKED(String, str1, 0);
241 6838720 : CONVERT_ARG_HANDLE_CHECKED(String, str2, 1);
242 6838720 : isolate->counters()->string_add_runtime()->Increment();
243 13677440 : RETURN_RESULT_OR_FAILURE(isolate,
244 : isolate->factory()->NewConsString(str1, str2));
245 : }
246 :
247 :
248 1800 : RUNTIME_FUNCTION(Runtime_InternalizeString) {
249 : HandleScope handles(isolate);
250 : DCHECK_EQ(1, args.length());
251 900 : CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
252 1800 : return *isolate->factory()->InternalizeString(string);
253 : }
254 :
255 224048 : RUNTIME_FUNCTION(Runtime_StringCharCodeAt) {
256 : HandleScope handle_scope(isolate);
257 : DCHECK_EQ(2, args.length());
258 :
259 112024 : CONVERT_ARG_HANDLE_CHECKED(String, subject, 0);
260 224048 : CONVERT_NUMBER_CHECKED(uint32_t, i, Uint32, args[1]);
261 :
262 : // Flatten the string. If someone wants to get a char at an index
263 : // in a cons string, it is likely that more indices will be
264 : // accessed.
265 112024 : subject = String::Flatten(isolate, subject);
266 :
267 112024 : if (i >= static_cast<uint32_t>(subject->length())) {
268 : return ReadOnlyRoots(isolate).nan_value();
269 : }
270 :
271 112024 : return Smi::FromInt(subject->Get(i));
272 : }
273 :
274 181390 : RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
275 : HandleScope scope(isolate);
276 : DCHECK_EQ(3, args.length());
277 90695 : CONVERT_ARG_HANDLE_CHECKED(JSArray, array, 0);
278 : int32_t array_length;
279 90695 : if (!args[1]->ToInt32(&array_length)) {
280 0 : THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewInvalidStringLengthError());
281 : }
282 90695 : CONVERT_ARG_HANDLE_CHECKED(String, special, 2);
283 :
284 : size_t actual_array_length = 0;
285 90695 : CHECK(TryNumberToSize(array->length(), &actual_array_length));
286 90695 : CHECK_GE(array_length, 0);
287 90695 : 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 90695 : CHECK(array->HasFastElements());
293 90695 : JSObject::EnsureCanContainHeapObjectElements(array);
294 :
295 : int special_length = special->length();
296 90695 : if (!array->HasObjectElements()) {
297 0 : return isolate->Throw(ReadOnlyRoots(isolate).illegal_argument_string());
298 : }
299 :
300 : int length;
301 90695 : bool one_byte = special->IsOneByteRepresentation();
302 :
303 : {
304 : DisallowHeapAllocation no_gc;
305 90695 : FixedArray fixed_array = FixedArray::cast(array->elements());
306 90695 : if (fixed_array->length() < array_length) {
307 0 : array_length = fixed_array->length();
308 : }
309 :
310 90695 : if (array_length == 0) {
311 : return ReadOnlyRoots(isolate).empty_string();
312 90695 : } else if (array_length == 1) {
313 90036 : Object first = fixed_array->get(0);
314 90036 : if (first->IsString()) return first;
315 : }
316 659 : length = StringBuilderConcatLength(special_length, fixed_array,
317 659 : array_length, &one_byte);
318 : }
319 :
320 659 : if (length == -1) {
321 0 : return isolate->Throw(ReadOnlyRoots(isolate).illegal_argument_string());
322 : }
323 659 : if (length == 0) {
324 : return ReadOnlyRoots(isolate).empty_string();
325 : }
326 :
327 659 : if (one_byte) {
328 : Handle<SeqOneByteString> answer;
329 1308 : ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
330 : isolate, answer, isolate->factory()->NewRawOneByteString(length));
331 : DisallowHeapAllocation no_gc;
332 : StringBuilderConcatHelper(*special, answer->GetChars(no_gc),
333 : FixedArray::cast(array->elements()),
334 1290 : array_length);
335 : return *answer;
336 : } else {
337 : Handle<SeqTwoByteString> answer;
338 10 : ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
339 : isolate, answer, isolate->factory()->NewRawTwoByteString(length));
340 : DisallowHeapAllocation no_gc;
341 : StringBuilderConcatHelper(*special, answer->GetChars(no_gc),
342 : FixedArray::cast(array->elements()),
343 10 : array_length);
344 : return *answer;
345 : }
346 : }
347 :
348 :
349 : // Copies Latin1 characters to the given fixed array looking up
350 : // one-char strings in the cache. Gives up on the first char that is
351 : // not in the cache and fills the remainder with smi zeros. Returns
352 : // the length of the successfully copied prefix.
353 107 : 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 : int i;
359 : WriteBarrierMode mode = elements->GetWriteBarrierMode(no_gc);
360 8319 : for (i = 0; i < length; ++i) {
361 4209 : Object value = one_byte_cache->get(chars[i]);
362 4209 : if (value == undefined) break;
363 4106 : elements->set(i, value, mode);
364 : }
365 107 : if (i < length) {
366 103 : MemsetTagged(elements->RawFieldOfElementAt(i), Smi::kZero, length - i);
367 : }
368 : #ifdef DEBUG
369 : for (int j = 0; j < length; ++j) {
370 : Object element = elements->get(j);
371 : DCHECK(element == Smi::kZero ||
372 : (element->IsString() && String::cast(element)->LooksValid()));
373 : }
374 : #endif
375 107 : return i;
376 : }
377 :
378 : // Converts a String to JSArray.
379 : // For example, "foo" => ["f", "o", "o"].
380 286 : RUNTIME_FUNCTION(Runtime_StringToArray) {
381 : HandleScope scope(isolate);
382 : DCHECK_EQ(2, args.length());
383 143 : CONVERT_ARG_HANDLE_CHECKED(String, s, 0);
384 286 : CONVERT_NUMBER_CHECKED(uint32_t, limit, Uint32, args[1]);
385 :
386 143 : s = String::Flatten(isolate, s);
387 286 : const int length = static_cast<int>(Min<uint32_t>(s->length(), limit));
388 :
389 : Handle<FixedArray> elements;
390 : int position = 0;
391 286 : if (s->IsFlat() && s->IsOneByteRepresentation()) {
392 : // Try using cached chars where possible.
393 107 : elements = isolate->factory()->NewUninitializedFixedArray(length);
394 :
395 : DisallowHeapAllocation no_gc;
396 107 : String::FlatContent content = s->GetFlatContent(no_gc);
397 107 : if (content.IsOneByte()) {
398 : Vector<const uint8_t> chars = content.ToOneByteVector();
399 : // Note, this will initialize all elements (not only the prefix)
400 : // to prevent GC from seeing partially initialized array.
401 : position = CopyCachedOneByteCharsToArray(isolate->heap(), chars.start(),
402 107 : *elements, length);
403 : } else {
404 0 : MemsetTagged(elements->data_start(),
405 : ReadOnlyRoots(isolate).undefined_value(), length);
406 : }
407 : } else {
408 36 : elements = isolate->factory()->NewFixedArray(length);
409 : }
410 112451 : for (int i = position; i < length; ++i) {
411 : Handle<Object> str =
412 56154 : isolate->factory()->LookupSingleCharacterStringFromCode(s->Get(i));
413 56154 : elements->set(i, *str);
414 : }
415 :
416 : #ifdef DEBUG
417 : for (int i = 0; i < length; ++i) {
418 : DCHECK_EQ(String::cast(elements->get(i))->length(), 1);
419 : }
420 : #endif
421 :
422 : return *isolate->factory()->NewJSArrayWithElements(elements);
423 : }
424 :
425 32986 : RUNTIME_FUNCTION(Runtime_StringLessThan) {
426 : HandleScope handle_scope(isolate);
427 : DCHECK_EQ(2, args.length());
428 16493 : CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
429 16493 : CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
430 16493 : ComparisonResult result = String::Compare(isolate, x, y);
431 : DCHECK_NE(result, ComparisonResult::kUndefined);
432 : return isolate->heap()->ToBoolean(
433 16493 : ComparisonResultToBool(Operation::kLessThan, result));
434 : }
435 :
436 6164364 : RUNTIME_FUNCTION(Runtime_StringLessThanOrEqual) {
437 : HandleScope handle_scope(isolate);
438 : DCHECK_EQ(2, args.length());
439 3082182 : CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
440 3082182 : CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
441 3082182 : ComparisonResult result = String::Compare(isolate, x, y);
442 : DCHECK_NE(result, ComparisonResult::kUndefined);
443 : return isolate->heap()->ToBoolean(
444 3082182 : ComparisonResultToBool(Operation::kLessThanOrEqual, result));
445 : }
446 :
447 0 : RUNTIME_FUNCTION(Runtime_StringGreaterThan) {
448 : HandleScope handle_scope(isolate);
449 : DCHECK_EQ(2, args.length());
450 0 : CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
451 0 : CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
452 0 : ComparisonResult result = String::Compare(isolate, x, y);
453 : DCHECK_NE(result, ComparisonResult::kUndefined);
454 : return isolate->heap()->ToBoolean(
455 0 : ComparisonResultToBool(Operation::kGreaterThan, result));
456 : }
457 :
458 2656452 : RUNTIME_FUNCTION(Runtime_StringGreaterThanOrEqual) {
459 : HandleScope handle_scope(isolate);
460 : DCHECK_EQ(2, args.length());
461 1328226 : CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
462 1328226 : CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
463 1328226 : ComparisonResult result = String::Compare(isolate, x, y);
464 : DCHECK_NE(result, ComparisonResult::kUndefined);
465 : return isolate->heap()->ToBoolean(
466 1328226 : ComparisonResultToBool(Operation::kGreaterThanOrEqual, result));
467 : }
468 :
469 1321976 : RUNTIME_FUNCTION(Runtime_StringEqual) {
470 : HandleScope handle_scope(isolate);
471 : DCHECK_EQ(2, args.length());
472 660988 : CONVERT_ARG_HANDLE_CHECKED(String, x, 0);
473 660988 : CONVERT_ARG_HANDLE_CHECKED(String, y, 1);
474 660988 : return isolate->heap()->ToBoolean(String::Equals(isolate, x, y));
475 : }
476 :
477 1296 : RUNTIME_FUNCTION(Runtime_FlattenString) {
478 : HandleScope scope(isolate);
479 : DCHECK_EQ(1, args.length());
480 648 : CONVERT_ARG_HANDLE_CHECKED(String, str, 0);
481 1296 : return *String::Flatten(isolate, str);
482 : }
483 :
484 304 : RUNTIME_FUNCTION(Runtime_StringMaxLength) {
485 : SealHandleScope shs(isolate);
486 : return Smi::FromInt(String::kMaxLength);
487 : }
488 :
489 2662 : RUNTIME_FUNCTION(Runtime_StringCompareSequence) {
490 : HandleScope handle_scope(isolate);
491 : DCHECK_EQ(3, args.length());
492 1331 : CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
493 1331 : CONVERT_ARG_HANDLE_CHECKED(String, search_string, 1);
494 2662 : CONVERT_NUMBER_CHECKED(int, start, Int32, args[2]);
495 :
496 : // Check if start + searchLength is in bounds.
497 : DCHECK_LE(start + search_string->length(), string->length());
498 :
499 1331 : FlatStringReader string_reader(isolate, String::Flatten(isolate, string));
500 : FlatStringReader search_reader(isolate,
501 1331 : String::Flatten(isolate, search_string));
502 :
503 83021 : for (int i = 0; i < search_string->length(); i++) {
504 81762 : if (string_reader.Get(start + i) != search_reader.Get(i)) {
505 : return ReadOnlyRoots(isolate).false_value();
506 : }
507 : }
508 :
509 : return ReadOnlyRoots(isolate).true_value();
510 : }
511 :
512 1422 : RUNTIME_FUNCTION(Runtime_StringEscapeQuotes) {
513 : HandleScope handle_scope(isolate);
514 : DCHECK_EQ(1, args.length());
515 711 : CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
516 :
517 : // Equivalent to global replacement `string.replace(/"/g, """)`, but this
518 : // does not modify any global state (e.g. the regexp match info).
519 :
520 : const int string_length = string->length();
521 : Handle<String> quotes =
522 711 : isolate->factory()->LookupSingleCharacterStringFromCode('"');
523 :
524 711 : int index = String::IndexOf(isolate, string, quotes, 0);
525 :
526 : // No quotes, nothing to do.
527 711 : if (index == -1) return *string;
528 :
529 : // Find all quotes.
530 270 : std::vector<int> indices = {index};
531 189 : while (index + 1 < string_length) {
532 90 : index = String::IndexOf(isolate, string, quotes, index + 1);
533 90 : if (index == -1) break;
534 54 : indices.emplace_back(index);
535 : }
536 :
537 : // Build the replacement string.
538 : Handle<String> replacement =
539 135 : isolate->factory()->NewStringFromAsciiChecked(""");
540 135 : const int estimated_part_count = static_cast<int>(indices.size()) * 2 + 1;
541 : ReplacementStringBuilder builder(isolate->heap(), string,
542 135 : estimated_part_count);
543 :
544 : int prev_index = -1; // Start at -1 to avoid special-casing the first match.
545 324 : for (int index : indices) {
546 189 : const int slice_start = prev_index + 1;
547 : const int slice_end = index;
548 189 : if (slice_end > slice_start) {
549 45 : builder.AddSubjectSlice(slice_start, slice_end);
550 : }
551 189 : builder.AddString(replacement);
552 : prev_index = index;
553 : }
554 :
555 135 : if (prev_index < string_length - 1) {
556 36 : builder.AddSubjectSlice(prev_index + 1, string_length);
557 : }
558 :
559 270 : return *builder.ToString().ToHandleChecked();
560 : }
561 :
562 : } // namespace internal
563 122004 : } // namespace v8
|