| // Copyright 2014 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/execution/arguments-inl.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/numbers/conversions.h" |
| #include "src/objects/dictionary-inl.h" |
| #include "src/objects/feedback-vector-inl.h" |
| #include "src/objects/js-array-inl.h" |
| #include "src/objects/objects-inl.h" |
| #include "src/objects/slots.h" |
| #include "src/objects/smi.h" |
| #include "src/runtime/runtime-utils.h" |
| #include "src/strings/string-builder-inl.h" |
| #include "src/strings/unicode-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| RUNTIME_FUNCTION(Runtime_GetSubstitution) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(5, args.length()); |
| DirectHandle<String> matched = args.at<String>(0); |
| DirectHandle<String> subject = args.at<String>(1); |
| int position = args.smi_value_at(2); |
| DirectHandle<String> replacement = args.at<String>(3); |
| int start_index = args.smi_value_at(4); |
| |
| // A simple match without captures. |
| class SimpleMatch : public String::Match { |
| public: |
| SimpleMatch(DirectHandle<String> match, DirectHandle<String> prefix, |
| DirectHandle<String> suffix) |
| : match_(match), prefix_(prefix), suffix_(suffix) {} |
| |
| DirectHandle<String> GetMatch() override { return match_; } |
| DirectHandle<String> GetPrefix() override { return prefix_; } |
| DirectHandle<String> GetSuffix() override { return suffix_; } |
| |
| int CaptureCount() override { return 0; } |
| bool HasNamedCaptures() override { return false; } |
| MaybeDirectHandle<String> GetCapture(int i, bool* capture_exists) override { |
| *capture_exists = false; |
| return match_; // Return arbitrary string handle. |
| } |
| MaybeDirectHandle<String> GetNamedCapture(DirectHandle<String> name, |
| CaptureState* state) override { |
| UNREACHABLE(); |
| } |
| |
| private: |
| DirectHandle<String> match_, prefix_, suffix_; |
| }; |
| |
| DirectHandle<String> prefix = |
| isolate->factory()->NewSubString(subject, 0, position); |
| DirectHandle<String> suffix = isolate->factory()->NewSubString( |
| subject, position + matched->length(), subject->length()); |
| SimpleMatch match(matched, prefix, suffix); |
| |
| RETURN_RESULT_OR_FAILURE( |
| isolate, |
| String::GetSubstitution(isolate, &match, replacement, start_index)); |
| } |
| |
| // This may return an empty MaybeDirectHandle if an exception is thrown or |
| // we abort due to reaching the recursion limit. |
| MaybeDirectHandle<String> StringReplaceOneCharWithString( |
| Isolate* isolate, DirectHandle<String> subject, DirectHandle<String> search, |
| DirectHandle<String> replace, bool* found, int recursion_limit) { |
| StackLimitCheck stackLimitCheck(isolate); |
| if (stackLimitCheck.HasOverflowed() || (recursion_limit == 0)) { |
| return MaybeDirectHandle<String>(); |
| } |
| recursion_limit--; |
| if (IsConsString(*subject)) { |
| Tagged<ConsString> cons = Cast<ConsString>(*subject); |
| DirectHandle<String> first(cons->first(), isolate); |
| DirectHandle<String> second(cons->second(), isolate); |
| DirectHandle<String> new_first; |
| if (!StringReplaceOneCharWithString(isolate, first, search, replace, found, |
| recursion_limit).ToHandle(&new_first)) { |
| return MaybeDirectHandle<String>(); |
| } |
| if (*found) return isolate->factory()->NewConsString(new_first, second); |
| |
| DirectHandle<String> new_second; |
| if (!StringReplaceOneCharWithString(isolate, second, search, replace, found, |
| recursion_limit) |
| .ToHandle(&new_second)) { |
| return MaybeDirectHandle<String>(); |
| } |
| if (*found) return isolate->factory()->NewConsString(first, new_second); |
| |
| return subject; |
| } else { |
| int index = String::IndexOf(isolate, subject, search, 0); |
| if (index == -1) return subject; |
| *found = true; |
| DirectHandle<String> first = |
| isolate->factory()->NewSubString(subject, 0, index); |
| DirectHandle<String> cons1; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, cons1, isolate->factory()->NewConsString(first, replace)); |
| DirectHandle<String> second = |
| isolate->factory()->NewSubString(subject, index + 1, subject->length()); |
| return isolate->factory()->NewConsString(cons1, second); |
| } |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringReplaceOneCharWithString) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| DirectHandle<String> subject = args.at<String>(0); |
| DirectHandle<String> search = args.at<String>(1); |
| DirectHandle<String> replace = args.at<String>(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; |
| DirectHandle<String> result; |
| if (StringReplaceOneCharWithString(isolate, subject, search, replace, &found, |
| kRecursionLimit).ToHandle(&result)) { |
| return *result; |
| } |
| if (isolate->has_exception()) return ReadOnlyRoots(isolate).exception(); |
| |
| subject = String::Flatten(isolate, subject); |
| if (StringReplaceOneCharWithString(isolate, subject, search, replace, &found, |
| kRecursionLimit).ToHandle(&result)) { |
| return *result; |
| } |
| if (isolate->has_exception()) return ReadOnlyRoots(isolate).exception(); |
| // In case of empty handle and no exception we have stack overflow. |
| return isolate->StackOverflow(); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringLastIndexOf) { |
| HandleScope handle_scope(isolate); |
| return String::LastIndexOf(isolate, args.at(0), args.at(1), |
| isolate->factory()->undefined_value()); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringSubstring) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| DirectHandle<String> string = args.at<String>(0); |
| int start = args.smi_value_at(1); |
| int end = args.smi_value_at(2); |
| DCHECK_LE(0, start); |
| DCHECK_LE(start, end); |
| DCHECK_LE(end, string->length()); |
| return *isolate->factory()->NewSubString(string, start, end); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringAdd) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| DirectHandle<String> str1 = args.at<String>(0); |
| DirectHandle<String> str2 = args.at<String>(1); |
| RETURN_RESULT_OR_FAILURE(isolate, |
| isolate->factory()->NewConsString(str1, str2)); |
| } |
| |
| namespace { |
| |
| using ASVariant = AddStringConstantAndInternalizeVariant; |
| |
| Tagged<Object> StringAdd_StringConstant_Internalize( |
| Isolate* isolate, DirectHandle<Object> lhs, DirectHandle<Object> rhs, |
| Handle<HeapObject> maybe_feedback_vector, int slot_index, |
| ASVariant as_variant) { |
| const bool lhs_is_string_constant = |
| as_variant == ASVariant::kLhsIsStringConstant; |
| DirectHandle<Object> const_str_operand = lhs_is_string_constant ? lhs : rhs; |
| DirectHandle<Object> other_operand = lhs_is_string_constant ? rhs : lhs; |
| DCHECK(IsInternalizedString(*const_str_operand)); |
| |
| DirectHandle<String> other_operand_string; |
| if (IsString(*other_operand)) { |
| other_operand_string = Cast<String>(other_operand); |
| } else { |
| // According to spec we first have to do ToPrimitive and only then |
| // ToString. |
| // https://tc39.es/ecma262/#sec-applystringornumericbinaryoperator |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, other_operand, Object::ToPrimitive(isolate, other_operand)); |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, other_operand_string, |
| Object::ToString(isolate, other_operand)); |
| } |
| |
| auto f = isolate->factory(); |
| auto other_operand_internalize = |
| IsInternalizedString(*other_operand_string) |
| ? Cast<InternalizedString>(other_operand_string) |
| : f->InternalizeString(other_operand_string); |
| |
| auto lhs_internalize = lhs_is_string_constant |
| ? Cast<String>(const_str_operand) |
| : Cast<String>(other_operand_internalize); |
| auto rhs_internalize = lhs_is_string_constant |
| ? Cast<String>(other_operand_internalize) |
| : Cast<String>(const_str_operand); |
| |
| if (IsUndefined(*maybe_feedback_vector)) { |
| DirectHandle<String> cons; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, cons, f->NewConsString(lhs_internalize, rhs_internalize)); |
| return *f->InternalizeString(cons); |
| } |
| |
| auto feedback_vector = Cast<FeedbackVector>(maybe_feedback_vector); |
| |
| // The cache is stored in the extra slot of the feedback slot group. |
| FeedbackSlot slot(FeedbackVector::ToSlot(slot_index)); |
| DCHECK_LT( |
| slot.WithOffset(kAdd_StringConstant_Internalize_CacheSlotOffset).ToInt(), |
| feedback_vector->length().value()); |
| |
| FeedbackNexus nexus(isolate, feedback_vector, slot); |
| Handle<Object> cache_obj(Cast<Object>(nexus.GetFeedbackExtra()), isolate); |
| Handle<SimpleNameDictionary> cache; |
| if (*cache_obj == ReadOnlyRoots{isolate}.uninitialized_symbol()) { |
| cache = SimpleNameDictionary::New(isolate, 1); |
| nexus.ConfigureStringAddInternalizeCache(*cache); |
| } else { |
| cache = Cast<SimpleNameDictionary>(cache_obj); |
| } |
| |
| InternalIndex entry = cache->FindEntry(isolate, other_operand_internalize); |
| if (entry.is_found()) { |
| auto result = cache->ValueAt(entry); |
| DCHECK(IsInternalizedString(result)); |
| return result; |
| } |
| |
| DirectHandle<String> cons; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, cons, f->NewConsString(lhs_internalize, rhs_internalize)); |
| |
| auto internalized = f->InternalizeString(cons); |
| auto new_cache = SimpleNameDictionary::Set( |
| isolate, cache, other_operand_internalize, internalized); |
| if (*new_cache != *cache) { |
| nexus.ConfigureStringAddInternalizeCache(*new_cache); |
| } |
| |
| return *internalized; |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_StringAdd_LhsIsStringConstant_Internalize) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| DirectHandle<String> lhs = args.at<String>(0); |
| DirectHandle<Object> rhs = args.at<Object>(1); |
| Handle<HeapObject> maybe_feedback_vector = args.at<HeapObject>(2); |
| int slot_index = args.tagged_index_value_at(3); |
| return StringAdd_StringConstant_Internalize(isolate, lhs, rhs, |
| maybe_feedback_vector, slot_index, |
| ASVariant::kLhsIsStringConstant); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringAdd_RhsIsStringConstant_Internalize) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(4, args.length()); |
| DirectHandle<Object> lhs = args.at<Object>(0); |
| DirectHandle<String> rhs = args.at<String>(1); |
| Handle<HeapObject> maybe_feedback_vector = args.at<HeapObject>(2); |
| int slot_index = args.tagged_index_value_at(3); |
| return StringAdd_StringConstant_Internalize(isolate, lhs, rhs, |
| maybe_feedback_vector, slot_index, |
| ASVariant::kRhsIsStringConstant); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_InternalizeString) { |
| HandleScope handles(isolate); |
| DCHECK_EQ(1, args.length()); |
| DirectHandle<String> string = args.at<String>(0); |
| return *isolate->factory()->InternalizeString(string); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringCharCodeAt) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| |
| DirectHandle<String> subject = args.at<String>(0); |
| uint32_t i = NumberToUint32(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. |
| subject = String::Flatten(isolate, subject); |
| |
| if (i >= static_cast<uint32_t>(subject->length())) { |
| return ReadOnlyRoots(isolate).nan_value(); |
| } |
| |
| return Smi::FromInt(subject->Get(i)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringCodePointAt) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| |
| DirectHandle<String> subject = args.at<String>(0); |
| uint32_t i = NumberToUint32(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. |
| subject = String::Flatten(isolate, subject); |
| |
| if (i >= static_cast<uint32_t>(subject->length())) { |
| return ReadOnlyRoots(isolate).nan_value(); |
| } |
| |
| int first_code_point = subject->Get(i); |
| if ((first_code_point & 0xFC00) != 0xD800) { |
| return Smi::FromInt(first_code_point); |
| } |
| |
| if (i + 1 >= static_cast<uint32_t>(subject->length())) { |
| return Smi::FromInt(first_code_point); |
| } |
| |
| int second_code_point = subject->Get(i + 1); |
| if ((second_code_point & 0xFC00) != 0xDC00) { |
| return Smi::FromInt(first_code_point); |
| } |
| |
| int surrogate_offset = 0x10000 - (0xD800 << 10) - 0xDC00; |
| return Smi::FromInt((first_code_point << 10) + |
| (second_code_point + surrogate_offset)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringBuilderConcat) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(3, args.length()); |
| DirectHandle<FixedArray> array = args.at<FixedArray>(0); |
| |
| int array_length = args.smi_value_at(1); |
| DCHECK_GE(array_length, 0); |
| |
| DirectHandle<String> special = args.at<String>(2); |
| |
| // This assumption is used by the slice encoding in one or two smis. |
| DCHECK_GE(Smi::kMaxValue, String::kMaxLength); |
| |
| int special_length = special->length(); |
| |
| int length; |
| bool one_byte = special->IsOneByteRepresentation(); |
| |
| { |
| DisallowGarbageCollection no_gc; |
| Tagged<FixedArray> fixed_array = *array; |
| |
| if (array_length == 0) { |
| return ReadOnlyRoots(isolate).empty_string(); |
| } else if (array_length == 1) { |
| Tagged<Object> first = fixed_array->get(0); |
| if (IsString(first)) return first; |
| } |
| length = StringBuilderConcatLength(special_length, fixed_array, |
| array_length, &one_byte); |
| } |
| |
| if (length == -1) { |
| return isolate->Throw(ReadOnlyRoots(isolate).illegal_argument_string()); |
| } |
| if (length == 0) { |
| return ReadOnlyRoots(isolate).empty_string(); |
| } |
| |
| if (one_byte) { |
| DirectHandle<SeqOneByteString> answer; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, answer, isolate->factory()->NewRawOneByteString(length)); |
| DisallowGarbageCollection no_gc; |
| StringBuilderConcatHelper(*special, answer->GetChars(no_gc), *array, |
| static_cast<uint32_t>(array_length)); |
| return *answer; |
| } else { |
| DirectHandle<SeqTwoByteString> answer; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, answer, isolate->factory()->NewRawTwoByteString(length)); |
| DisallowGarbageCollection no_gc; |
| StringBuilderConcatHelper(*special, answer->GetChars(no_gc), *array, |
| static_cast<uint32_t>(array_length)); |
| return *answer; |
| } |
| } |
| |
| // Converts a String to JSArray. |
| // For example, "foo" => ["f", "o", "o"]. |
| RUNTIME_FUNCTION(Runtime_StringToArray) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| DirectHandle<String> s = args.at<String>(0); |
| uint32_t limit = NumberToUint32(args[1]); |
| |
| s = String::Flatten(isolate, s); |
| const int length = |
| static_cast<int>(std::min(static_cast<uint32_t>(s->length()), limit)); |
| |
| DirectHandle<FixedArray> elements = isolate->factory()->NewFixedArray(length); |
| bool elements_are_initialized = false; |
| |
| if (s->IsFlat() && s->IsOneByteRepresentation()) { |
| DisallowGarbageCollection no_gc; |
| String::FlatContent content = s->GetFlatContent(no_gc); |
| // Use pre-initialized single characters to initialize all the elements. |
| // This can be false if the string is sliced from an externalized |
| // two-byte string that has only one-byte chars, in that case we will do |
| // a LookupSingleCharacterStringFromCode for each of the characters. |
| if (content.IsOneByte()) { |
| base::Vector<const uint8_t> chars = content.ToOneByteVector(); |
| ReadOnlyRoots roots(isolate); |
| for (int i = 0; i < length; ++i) { |
| Tagged<String> value = roots.single_character_string(chars[i]); |
| DCHECK(ReadOnlyHeap::Contains(Cast<HeapObject>(value))); |
| // The single-character strings are in RO space so it should |
| // be safe to skip the write barriers. |
| elements->set(i, value, SKIP_WRITE_BARRIER); |
| } |
| elements_are_initialized = true; |
| } |
| } |
| |
| if (!elements_are_initialized) { |
| for (int i = 0; i < length; ++i) { |
| DirectHandle<Object> str = |
| isolate->factory()->LookupSingleCharacterStringFromCode(s->Get(i)); |
| elements->set(i, *str); |
| } |
| } |
| |
| #ifdef DEBUG |
| for (int i = 0; i < length; ++i) { |
| DCHECK_EQ(Cast<String>(elements->get(i))->length(), 1); |
| } |
| #endif |
| |
| return *isolate->factory()->NewJSArrayWithElements(elements); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringLessThan) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| DirectHandle<String> x = args.at<String>(0); |
| DirectHandle<String> y = args.at<String>(1); |
| ComparisonResult result = String::Compare(isolate, x, y); |
| DCHECK_NE(result, ComparisonResult::kUndefined); |
| return isolate->heap()->ToBoolean( |
| ComparisonResultToBool(Operation::kLessThan, result)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringLessThanOrEqual) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| DirectHandle<String> x = args.at<String>(0); |
| DirectHandle<String> y = args.at<String>(1); |
| ComparisonResult result = String::Compare(isolate, x, y); |
| DCHECK_NE(result, ComparisonResult::kUndefined); |
| return isolate->heap()->ToBoolean( |
| ComparisonResultToBool(Operation::kLessThanOrEqual, result)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringGreaterThan) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| DirectHandle<String> x = args.at<String>(0); |
| DirectHandle<String> y = args.at<String>(1); |
| ComparisonResult result = String::Compare(isolate, x, y); |
| DCHECK_NE(result, ComparisonResult::kUndefined); |
| return isolate->heap()->ToBoolean( |
| ComparisonResultToBool(Operation::kGreaterThan, result)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringGreaterThanOrEqual) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| DirectHandle<String> x = args.at<String>(0); |
| DirectHandle<String> y = args.at<String>(1); |
| ComparisonResult result = String::Compare(isolate, x, y); |
| DCHECK_NE(result, ComparisonResult::kUndefined); |
| return isolate->heap()->ToBoolean( |
| ComparisonResultToBool(Operation::kGreaterThanOrEqual, result)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringEqual) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(2, args.length()); |
| // This function can be called from Wasm: optimized Wasm code calls |
| // straight to the "StringEqual" builtin, which tail-calls here. So on |
| // the stack, the CEntryStub's EXIT frame will sit right on top of the |
| // Wasm frame; and Wasm frames don't scan their outgoing parameters (in |
| // order to support tail-calls between Wasm functions), while the EXIT |
| // frame doesn't scan its incoming parameters (because it expects to be |
| // called from JS). |
| // Working around this by calling through a trampoline builtin is slow. |
| // Teaching the stack walker to be smarter has proven to be difficult. |
| // In the future, Conservative Stack Scanning will trivially solve the |
| // problem. In the meantime, we can work around it by explicitly creating |
| // handles here (rather than treating the on-stack arguments as handles). |
| // |
| // TODO(42203211): Don't create new handles here once direct handles and CSS |
| // are enabled by default. |
| DirectHandle<String> x(*args.at<String>(0), isolate); |
| DirectHandle<String> y(*args.at<String>(1), isolate); |
| return isolate->heap()->ToBoolean(String::Equals(isolate, x, y)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringCompare) { |
| DCHECK_EQ(2, args.length()); |
| HandleScope scope(isolate); |
| DirectHandle<String> lhs(Cast<String>(args[0]), isolate); |
| DirectHandle<String> rhs(Cast<String>(args[1]), isolate); |
| ComparisonResult result = String::Compare(isolate, lhs, rhs); |
| DCHECK_NE(result, ComparisonResult::kUndefined); |
| return Smi::FromInt(static_cast<int>(result)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_FlattenString) { |
| HandleScope scope(isolate); |
| DCHECK_EQ(1, args.length()); |
| DirectHandle<String> str = args.at<String>(0); |
| return *String::Flatten(isolate, str); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringMaxLength) { |
| SealHandleScope shs(isolate); |
| return Smi::FromInt(String::kMaxLength); |
| } |
| |
| namespace { |
| |
| template <const char* replacement, typename Char, typename IndicesT> |
| void CopyEscapedRaw(Char* dst, const Char* src, int src_length, |
| const IndicesT& indices, |
| const DisallowGarbageCollection& no_gc) { |
| constexpr size_t replacement_length = |
| std::char_traits<char>::length(replacement); |
| int prev_index = -1; // Start at -1 to avoid special-casing the first match. |
| int dst_index = 0; |
| for (int index : indices) { |
| const int slice_start = prev_index + 1; |
| const int slice_end = index; |
| if (slice_end > slice_start) { |
| const int slice_length = slice_end - slice_start; |
| CopyChars(dst + dst_index, src + slice_start, slice_length); |
| dst_index += slice_length; |
| } |
| CopyChars(dst + dst_index, replacement, replacement_length); |
| dst_index += replacement_length; |
| prev_index = index; |
| } |
| |
| if (prev_index < src_length - 1) { |
| const int remaining_length = src_length - prev_index - 1; |
| CopyChars(dst + dst_index, src + prev_index + 1, remaining_length); |
| } |
| } |
| |
| } // namespace |
| |
| RUNTIME_FUNCTION(Runtime_StringEscapeQuotes) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(1, args.length()); |
| DirectHandle<String> string = args.at<String>(0); |
| |
| // Equivalent to global replacement `string.replace(/"/g, """)`, but this |
| // does not modify any global state (e.g. the regexp match info). |
| |
| const int string_length = string->length(); |
| DirectHandle<String> quotes = |
| isolate->factory()->LookupSingleCharacterStringFromCode('"'); |
| |
| string = String::Flatten(isolate, string); |
| int quote_index = String::IndexOf(isolate, string, quotes, 0); |
| |
| // No quotes, nothing to do. |
| if (quote_index == -1) return *string; |
| |
| // Find all quotes. |
| base::SmallVector<int, 2> indices = {quote_index}; |
| while (quote_index + 1 < string_length) { |
| quote_index = String::IndexOf(isolate, string, quotes, quote_index + 1); |
| if (quote_index == -1) break; |
| indices.emplace_back(quote_index); |
| } |
| |
| static constexpr char replacement[] = """; |
| constexpr size_t replacement_length = |
| std::char_traits<char>::length(replacement); |
| constexpr size_t replace_length_factor = |
| replacement_length - 1 /* original '"' */; |
| static_assert(replace_length_factor == 5); |
| uint32_t result_length = |
| string_length + |
| static_cast<uint32_t>(indices.size()) * replace_length_factor; |
| const bool is_one_byte = String::IsOneByteRepresentationUnderneath(*string); |
| |
| DirectHandle<String> result; |
| if (is_one_byte) { |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, result, |
| isolate->factory()->NewRawOneByteString(result_length)); |
| } else { |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, result, |
| isolate->factory()->NewRawTwoByteString(result_length)); |
| } |
| DisallowGarbageCollection no_gc; |
| String::FlatContent flat_string = string->GetFlatContent(no_gc); |
| if (is_one_byte) { |
| base::Vector<const uint8_t> string_vec = flat_string.ToOneByteVector(); |
| uint8_t* dst = Cast<SeqOneByteString>(result)->GetChars(no_gc); |
| CopyEscapedRaw<replacement>(dst, string_vec.data(), string_vec.length(), |
| indices, no_gc); |
| } else { |
| base::Vector<const base::uc16> string_vec = flat_string.ToUC16Vector(); |
| base::uc16* dst = Cast<SeqTwoByteString>(result)->GetChars(no_gc); |
| CopyEscapedRaw<replacement>(dst, string_vec.data(), string_vec.length(), |
| indices, no_gc); |
| } |
| |
| return *result; |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringIsWellFormed) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(1, args.length()); |
| DirectHandle<String> string = args.at<String>(0); |
| return isolate->heap()->ToBoolean( |
| String::IsWellFormedUnicode(isolate, string)); |
| } |
| |
| RUNTIME_FUNCTION(Runtime_StringToWellFormed) { |
| HandleScope handle_scope(isolate); |
| DCHECK_EQ(1, args.length()); |
| DirectHandle<String> source = args.at<String>(0); |
| if (String::IsWellFormedUnicode(isolate, source)) return *source; |
| // String::IsWellFormedUnicode would have returned true above otherwise. |
| DCHECK(!String::IsOneByteRepresentationUnderneath(*source)); |
| const int length = source->length(); |
| DirectHandle<SeqTwoByteString> dest = |
| isolate->factory()->NewRawTwoByteString(length).ToHandleChecked(); |
| DisallowGarbageCollection no_gc; |
| String::FlatContent source_contents = source->GetFlatContent(no_gc); |
| DCHECK(source_contents.IsFlat()); |
| const uint16_t* source_data = source_contents.ToUC16Vector().begin(); |
| uint16_t* dest_data = dest->GetChars(no_gc); |
| unibrow::Utf16::ReplaceUnpairedSurrogates(source_data, dest_data, length); |
| return *dest; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |