diff options
author | Ujjwal Sharma <usharma1998@gmail.com> | 2019-03-15 18:35:06 +0530 |
---|---|---|
committer | Refael Ackermann <refack@gmail.com> | 2019-03-28 16:36:18 -0400 |
commit | f579e1194046c50f2e6bb54348d48c8e7d1a53cf (patch) | |
tree | 9125787c758358365f74f9fd9673c14f57e67870 /deps/v8/src/objects/string.cc | |
parent | 2c73868b0471fbd4038f500d076df056cbf697fe (diff) | |
download | android-node-v8-f579e1194046c50f2e6bb54348d48c8e7d1a53cf.tar.gz android-node-v8-f579e1194046c50f2e6bb54348d48c8e7d1a53cf.tar.bz2 android-node-v8-f579e1194046c50f2e6bb54348d48c8e7d1a53cf.zip |
deps: update V8 to 7.4.288.13
PR-URL: https://github.com/nodejs/node/pull/26685
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
Diffstat (limited to 'deps/v8/src/objects/string.cc')
-rw-r--r-- | deps/v8/src/objects/string.cc | 1526 |
1 files changed, 1526 insertions, 0 deletions
diff --git a/deps/v8/src/objects/string.cc b/deps/v8/src/objects/string.cc new file mode 100644 index 0000000000..a735d038fd --- /dev/null +++ b/deps/v8/src/objects/string.cc @@ -0,0 +1,1526 @@ +// Copyright 2019 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/objects/string.h" + +#include "src/char-predicates.h" +#include "src/conversions.h" +#include "src/handles-inl.h" +#include "src/heap/heap-inl.h" // For LooksValid implementation. +#include "src/objects/map.h" +#include "src/objects/oddball.h" +#include "src/objects/string-comparator.h" +#include "src/objects/string-inl.h" +#include "src/ostreams.h" +#include "src/string-builder-inl.h" +#include "src/string-hasher.h" +#include "src/string-search.h" +#include "src/string-stream.h" +#include "src/unicode-inl.h" + +namespace v8 { +namespace internal { + +Handle<String> String::SlowFlatten(Isolate* isolate, Handle<ConsString> cons, + PretenureFlag pretenure) { + DCHECK_NE(cons->second()->length(), 0); + + // TurboFan can create cons strings with empty first parts. + while (cons->first()->length() == 0) { + // We do not want to call this function recursively. Therefore we call + // String::Flatten only in those cases where String::SlowFlatten is not + // called again. + if (cons->second()->IsConsString() && !cons->second()->IsFlat()) { + cons = handle(ConsString::cast(cons->second()), isolate); + } else { + return String::Flatten(isolate, handle(cons->second(), isolate)); + } + } + + DCHECK(AllowHeapAllocation::IsAllowed()); + int length = cons->length(); + PretenureFlag tenure = ObjectInYoungGeneration(*cons) ? pretenure : TENURED; + Handle<SeqString> result; + if (cons->IsOneByteRepresentation()) { + Handle<SeqOneByteString> flat = isolate->factory() + ->NewRawOneByteString(length, tenure) + .ToHandleChecked(); + DisallowHeapAllocation no_gc; + WriteToFlat(*cons, flat->GetChars(no_gc), 0, length); + result = flat; + } else { + Handle<SeqTwoByteString> flat = isolate->factory() + ->NewRawTwoByteString(length, tenure) + .ToHandleChecked(); + DisallowHeapAllocation no_gc; + WriteToFlat(*cons, flat->GetChars(no_gc), 0, length); + result = flat; + } + cons->set_first(isolate, *result); + cons->set_second(isolate, ReadOnlyRoots(isolate).empty_string()); + DCHECK(result->IsFlat()); + return result; +} + +bool String::MakeExternal(v8::String::ExternalStringResource* resource) { + DisallowHeapAllocation no_allocation; + // Externalizing twice leaks the external resource, so it's + // prohibited by the API. + DCHECK(this->SupportsExternalization()); + DCHECK(resource->IsCacheable()); +#ifdef ENABLE_SLOW_DCHECKS + if (FLAG_enable_slow_asserts) { + // Assert that the resource and the string are equivalent. + DCHECK(static_cast<size_t>(this->length()) == resource->length()); + ScopedVector<uc16> smart_chars(this->length()); + String::WriteToFlat(*this, smart_chars.start(), 0, this->length()); + DCHECK_EQ(0, memcmp(smart_chars.start(), resource->data(), + resource->length() * sizeof(smart_chars[0]))); + } +#endif // DEBUG + int size = this->Size(); // Byte size of the original string. + // Abort if size does not allow in-place conversion. + if (size < ExternalString::kUncachedSize) return false; + Isolate* isolate; + // Read-only strings cannot be made external, since that would mutate the + // string. + if (!GetIsolateFromWritableObject(*this, &isolate)) return false; + Heap* heap = isolate->heap(); + bool is_internalized = this->IsInternalizedString(); + bool has_pointers = StringShape(*this).IsIndirect(); + if (has_pointers) { + heap->NotifyObjectLayoutChange(*this, size, no_allocation); + } + // Morph the string to an external string by replacing the map and + // reinitializing the fields. This won't work if the space the existing + // string occupies is too small for a regular external string. Instead, we + // resort to an uncached external string instead, omitting the field caching + // the address of the backing store. When we encounter uncached external + // strings in generated code, we need to bailout to runtime. + Map new_map; + ReadOnlyRoots roots(heap); + if (size < ExternalString::kSize) { + if (is_internalized) { + new_map = roots.uncached_external_internalized_string_map(); + } else { + new_map = roots.uncached_external_string_map(); + } + } else { + new_map = is_internalized ? roots.external_internalized_string_map() + : roots.external_string_map(); + } + + // Byte size of the external String object. + int new_size = this->SizeFromMap(new_map); + heap->CreateFillerObjectAt(this->address() + new_size, size - new_size, + ClearRecordedSlots::kNo); + if (has_pointers) { + heap->ClearRecordedSlotRange(this->address(), this->address() + new_size); + } + + // We are storing the new map using release store after creating a filler for + // the left-over space to avoid races with the sweeper thread. + this->synchronized_set_map(new_map); + + ExternalTwoByteString self = ExternalTwoByteString::cast(*this); + self->SetResource(isolate, resource); + heap->RegisterExternalString(*this); + if (is_internalized) self->Hash(); // Force regeneration of the hash value. + return true; +} + +bool String::MakeExternal(v8::String::ExternalOneByteStringResource* resource) { + DisallowHeapAllocation no_allocation; + // Externalizing twice leaks the external resource, so it's + // prohibited by the API. + DCHECK(this->SupportsExternalization()); + DCHECK(resource->IsCacheable()); +#ifdef ENABLE_SLOW_DCHECKS + if (FLAG_enable_slow_asserts) { + // Assert that the resource and the string are equivalent. + DCHECK(static_cast<size_t>(this->length()) == resource->length()); + if (this->IsTwoByteRepresentation()) { + ScopedVector<uint16_t> smart_chars(this->length()); + String::WriteToFlat(*this, smart_chars.start(), 0, this->length()); + DCHECK(String::IsOneByte(smart_chars.start(), this->length())); + } + ScopedVector<char> smart_chars(this->length()); + String::WriteToFlat(*this, smart_chars.start(), 0, this->length()); + DCHECK_EQ(0, memcmp(smart_chars.start(), resource->data(), + resource->length() * sizeof(smart_chars[0]))); + } +#endif // DEBUG + int size = this->Size(); // Byte size of the original string. + // Abort if size does not allow in-place conversion. + if (size < ExternalString::kUncachedSize) return false; + Isolate* isolate; + // Read-only strings cannot be made external, since that would mutate the + // string. + if (!GetIsolateFromWritableObject(*this, &isolate)) return false; + Heap* heap = isolate->heap(); + bool is_internalized = this->IsInternalizedString(); + bool has_pointers = StringShape(*this).IsIndirect(); + + if (has_pointers) { + heap->NotifyObjectLayoutChange(*this, size, no_allocation); + } + + // Morph the string to an external string by replacing the map and + // reinitializing the fields. This won't work if the space the existing + // string occupies is too small for a regular external string. Instead, we + // resort to an uncached external string instead, omitting the field caching + // the address of the backing store. When we encounter uncached external + // strings in generated code, we need to bailout to runtime. + Map new_map; + ReadOnlyRoots roots(heap); + if (size < ExternalString::kSize) { + new_map = is_internalized + ? roots.uncached_external_one_byte_internalized_string_map() + : roots.uncached_external_one_byte_string_map(); + } else { + new_map = is_internalized + ? roots.external_one_byte_internalized_string_map() + : roots.external_one_byte_string_map(); + } + + // Byte size of the external String object. + int new_size = this->SizeFromMap(new_map); + heap->CreateFillerObjectAt(this->address() + new_size, size - new_size, + ClearRecordedSlots::kNo); + if (has_pointers) { + heap->ClearRecordedSlotRange(this->address(), this->address() + new_size); + } + + // We are storing the new map using release store after creating a filler for + // the left-over space to avoid races with the sweeper thread. + this->synchronized_set_map(new_map); + + ExternalOneByteString self = ExternalOneByteString::cast(*this); + self->SetResource(isolate, resource); + heap->RegisterExternalString(*this); + if (is_internalized) self->Hash(); // Force regeneration of the hash value. + return true; +} + +bool String::SupportsExternalization() { + if (this->IsThinString()) { + return i::ThinString::cast(*this)->actual()->SupportsExternalization(); + } + + Isolate* isolate; + // RO_SPACE strings cannot be externalized. + if (!GetIsolateFromWritableObject(*this, &isolate)) { + return false; + } + + // Already an external string. + if (StringShape(*this).IsExternal()) { + return false; + } + +#ifdef V8_COMPRESS_POINTERS + // Small strings may not be in-place externalizable. + if (this->Size() < ExternalString::kUncachedSize) return false; +#else + DCHECK_LE(ExternalString::kUncachedSize, this->Size()); +#endif + + return !isolate->heap()->IsInGCPostProcessing(); +} + +void String::StringShortPrint(StringStream* accumulator, bool show_details) { + const char* internalized_marker = this->IsInternalizedString() ? "#" : ""; + + int len = length(); + if (len > kMaxShortPrintLength) { + accumulator->Add("<Very long string[%s%u]>", internalized_marker, len); + return; + } + + if (!LooksValid()) { + accumulator->Add("<Invalid String>"); + return; + } + + StringCharacterStream stream(*this); + + bool truncated = false; + if (len > kMaxShortPrintLength) { + len = kMaxShortPrintLength; + truncated = true; + } + bool one_byte = true; + for (int i = 0; i < len; i++) { + uint16_t c = stream.GetNext(); + + if (c < 32 || c >= 127) { + one_byte = false; + } + } + stream.Reset(*this); + if (one_byte) { + if (show_details) + accumulator->Add("<String[%s%u]: ", internalized_marker, length()); + for (int i = 0; i < len; i++) { + accumulator->Put(static_cast<char>(stream.GetNext())); + } + if (show_details) accumulator->Put('>'); + } else { + // Backslash indicates that the string contains control + // characters and that backslashes are therefore escaped. + if (show_details) + accumulator->Add("<String[%s%u]\\: ", internalized_marker, length()); + for (int i = 0; i < len; i++) { + uint16_t c = stream.GetNext(); + if (c == '\n') { + accumulator->Add("\\n"); + } else if (c == '\r') { + accumulator->Add("\\r"); + } else if (c == '\\') { + accumulator->Add("\\\\"); + } else if (c < 32 || c > 126) { + accumulator->Add("\\x%02x", c); + } else { + accumulator->Put(static_cast<char>(c)); + } + } + if (truncated) { + accumulator->Put('.'); + accumulator->Put('.'); + accumulator->Put('.'); + } + if (show_details) accumulator->Put('>'); + } + return; +} + +void String::PrintUC16(std::ostream& os, int start, int end) { // NOLINT + if (end < 0) end = length(); + StringCharacterStream stream(*this, start); + for (int i = start; i < end && stream.HasMore(); i++) { + os << AsUC16(stream.GetNext()); + } +} + +// static +Handle<String> String::Trim(Isolate* isolate, Handle<String> string, + TrimMode mode) { + string = String::Flatten(isolate, string); + int const length = string->length(); + + // Perform left trimming if requested. + int left = 0; + if (mode == kTrim || mode == kTrimStart) { + while (left < length && IsWhiteSpaceOrLineTerminator(string->Get(left))) { + left++; + } + } + + // Perform right trimming if requested. + int right = length; + if (mode == kTrim || mode == kTrimEnd) { + while (right > left && + IsWhiteSpaceOrLineTerminator(string->Get(right - 1))) { + right--; + } + } + + return isolate->factory()->NewSubString(string, left, right); +} + +bool String::LooksValid() { + // TODO(leszeks): Maybe remove this check entirely, Heap::Contains uses + // basically the same logic as the way we access the heap in the first place. + MemoryChunk* chunk = MemoryChunk::FromHeapObject(*this); + // RO_SPACE objects should always be valid. + if (chunk->owner()->identity() == RO_SPACE) return true; + if (chunk->heap() == nullptr) return false; + return chunk->heap()->Contains(*this); +} + +namespace { + +bool AreDigits(const uint8_t* s, int from, int to) { + for (int i = from; i < to; i++) { + if (s[i] < '0' || s[i] > '9') return false; + } + + return true; +} + +int ParseDecimalInteger(const uint8_t* s, int from, int to) { + DCHECK_LT(to - from, 10); // Overflow is not possible. + DCHECK(from < to); + int d = s[from] - '0'; + + for (int i = from + 1; i < to; i++) { + d = 10 * d + (s[i] - '0'); + } + + return d; +} + +} // namespace + +// static +Handle<Object> String::ToNumber(Isolate* isolate, Handle<String> subject) { + // Flatten {subject} string first. + subject = String::Flatten(isolate, subject); + + // Fast array index case. + uint32_t index; + if (subject->AsArrayIndex(&index)) { + return isolate->factory()->NewNumberFromUint(index); + } + + // Fast case: short integer or some sorts of junk values. + if (subject->IsSeqOneByteString()) { + int len = subject->length(); + if (len == 0) return handle(Smi::kZero, isolate); + + DisallowHeapAllocation no_gc; + uint8_t const* data = + Handle<SeqOneByteString>::cast(subject)->GetChars(no_gc); + bool minus = (data[0] == '-'); + int start_pos = (minus ? 1 : 0); + + if (start_pos == len) { + return isolate->factory()->nan_value(); + } else if (data[start_pos] > '9') { + // Fast check for a junk value. A valid string may start from a + // whitespace, a sign ('+' or '-'), the decimal point, a decimal digit + // or the 'I' character ('Infinity'). All of that have codes not greater + // than '9' except 'I' and . + if (data[start_pos] != 'I' && data[start_pos] != 0xA0) { + return isolate->factory()->nan_value(); + } + } else if (len - start_pos < 10 && AreDigits(data, start_pos, len)) { + // The maximal/minimal smi has 10 digits. If the string has less digits + // we know it will fit into the smi-data type. + int d = ParseDecimalInteger(data, start_pos, len); + if (minus) { + if (d == 0) return isolate->factory()->minus_zero_value(); + d = -d; + } else if (!subject->HasHashCode() && len <= String::kMaxArrayIndexSize && + (len == 1 || data[0] != '0')) { + // String hash is not calculated yet but all the data are present. + // Update the hash field to speed up sequential convertions. + uint32_t hash = StringHasher::MakeArrayIndexHash(d, len); +#ifdef DEBUG + subject->Hash(); // Force hash calculation. + DCHECK_EQ(static_cast<int>(subject->hash_field()), + static_cast<int>(hash)); +#endif + subject->set_hash_field(hash); + } + return handle(Smi::FromInt(d), isolate); + } + } + + // Slower case. + int flags = ALLOW_HEX | ALLOW_OCTAL | ALLOW_BINARY; + return isolate->factory()->NewNumber(StringToDouble(isolate, subject, flags)); +} + +String::FlatContent String::GetFlatContent( + const DisallowHeapAllocation& no_gc) { + USE(no_gc); + int length = this->length(); + StringShape shape(*this); + String string = *this; + int offset = 0; + if (shape.representation_tag() == kConsStringTag) { + ConsString cons = ConsString::cast(string); + if (cons->second()->length() != 0) { + return FlatContent(); + } + string = cons->first(); + shape = StringShape(string); + } else if (shape.representation_tag() == kSlicedStringTag) { + SlicedString slice = SlicedString::cast(string); + offset = slice->offset(); + string = slice->parent(); + shape = StringShape(string); + DCHECK(shape.representation_tag() != kConsStringTag && + shape.representation_tag() != kSlicedStringTag); + } + if (shape.representation_tag() == kThinStringTag) { + ThinString thin = ThinString::cast(string); + string = thin->actual(); + shape = StringShape(string); + DCHECK(!shape.IsCons()); + DCHECK(!shape.IsSliced()); + } + if (shape.encoding_tag() == kOneByteStringTag) { + const uint8_t* start; + if (shape.representation_tag() == kSeqStringTag) { + start = SeqOneByteString::cast(string)->GetChars(no_gc); + } else { + start = ExternalOneByteString::cast(string)->GetChars(); + } + return FlatContent(start + offset, length); + } else { + DCHECK_EQ(shape.encoding_tag(), kTwoByteStringTag); + const uc16* start; + if (shape.representation_tag() == kSeqStringTag) { + start = SeqTwoByteString::cast(string)->GetChars(no_gc); + } else { + start = ExternalTwoByteString::cast(string)->GetChars(); + } + return FlatContent(start + offset, length); + } +} + +std::unique_ptr<char[]> String::ToCString(AllowNullsFlag allow_nulls, + RobustnessFlag robust_flag, + int offset, int length, + int* length_return) { + if (robust_flag == ROBUST_STRING_TRAVERSAL && !LooksValid()) { + return std::unique_ptr<char[]>(); + } + // Negative length means the to the end of the string. + if (length < 0) length = kMaxInt - offset; + + // Compute the size of the UTF-8 string. Start at the specified offset. + StringCharacterStream stream(*this, offset); + int character_position = offset; + int utf8_bytes = 0; + int last = unibrow::Utf16::kNoPreviousCharacter; + while (stream.HasMore() && character_position++ < offset + length) { + uint16_t character = stream.GetNext(); + utf8_bytes += unibrow::Utf8::Length(character, last); + last = character; + } + + if (length_return) { + *length_return = utf8_bytes; + } + + char* result = NewArray<char>(utf8_bytes + 1); + + // Convert the UTF-16 string to a UTF-8 buffer. Start at the specified offset. + stream.Reset(*this, offset); + character_position = offset; + int utf8_byte_position = 0; + last = unibrow::Utf16::kNoPreviousCharacter; + while (stream.HasMore() && character_position++ < offset + length) { + uint16_t character = stream.GetNext(); + if (allow_nulls == DISALLOW_NULLS && character == 0) { + character = ' '; + } + utf8_byte_position += + unibrow::Utf8::Encode(result + utf8_byte_position, character, last); + last = character; + } + result[utf8_byte_position] = 0; + return std::unique_ptr<char[]>(result); +} + +std::unique_ptr<char[]> String::ToCString(AllowNullsFlag allow_nulls, + RobustnessFlag robust_flag, + int* length_return) { + return ToCString(allow_nulls, robust_flag, 0, -1, length_return); +} + +template <typename sinkchar> +void String::WriteToFlat(String src, sinkchar* sink, int f, int t) { + DisallowHeapAllocation no_gc; + String source = src; + int from = f; + int to = t; + while (true) { + DCHECK(0 <= from && from <= to && to <= source->length()); + switch (StringShape(source).full_representation_tag()) { + case kOneByteStringTag | kExternalStringTag: { + CopyChars(sink, ExternalOneByteString::cast(source)->GetChars() + from, + to - from); + return; + } + case kTwoByteStringTag | kExternalStringTag: { + const uc16* data = ExternalTwoByteString::cast(source)->GetChars(); + CopyChars(sink, data + from, to - from); + return; + } + case kOneByteStringTag | kSeqStringTag: { + CopyChars(sink, SeqOneByteString::cast(source)->GetChars(no_gc) + from, + to - from); + return; + } + case kTwoByteStringTag | kSeqStringTag: { + CopyChars(sink, SeqTwoByteString::cast(source)->GetChars(no_gc) + from, + to - from); + return; + } + case kOneByteStringTag | kConsStringTag: + case kTwoByteStringTag | kConsStringTag: { + ConsString cons_string = ConsString::cast(source); + String first = cons_string->first(); + int boundary = first->length(); + if (to - boundary >= boundary - from) { + // Right hand side is longer. Recurse over left. + if (from < boundary) { + WriteToFlat(first, sink, from, boundary); + if (from == 0 && cons_string->second() == first) { + CopyChars(sink + boundary, sink, boundary); + return; + } + sink += boundary - from; + from = 0; + } else { + from -= boundary; + } + to -= boundary; + source = cons_string->second(); + } else { + // Left hand side is longer. Recurse over right. + if (to > boundary) { + String second = cons_string->second(); + // When repeatedly appending to a string, we get a cons string that + // is unbalanced to the left, a list, essentially. We inline the + // common case of sequential one-byte right child. + if (to - boundary == 1) { + sink[boundary - from] = static_cast<sinkchar>(second->Get(0)); + } else if (second->IsSeqOneByteString()) { + CopyChars(sink + boundary - from, + SeqOneByteString::cast(second)->GetChars(no_gc), + to - boundary); + } else { + WriteToFlat(second, sink + boundary - from, 0, to - boundary); + } + to = boundary; + } + source = first; + } + break; + } + case kOneByteStringTag | kSlicedStringTag: + case kTwoByteStringTag | kSlicedStringTag: { + SlicedString slice = SlicedString::cast(source); + unsigned offset = slice->offset(); + WriteToFlat(slice->parent(), sink, from + offset, to + offset); + return; + } + case kOneByteStringTag | kThinStringTag: + case kTwoByteStringTag | kThinStringTag: + source = ThinString::cast(source)->actual(); + break; + } + } +} + +template <typename SourceChar> +static void CalculateLineEndsImpl(Isolate* isolate, std::vector<int>* line_ends, + Vector<const SourceChar> src, + bool include_ending_line) { + const int src_len = src.length(); + for (int i = 0; i < src_len - 1; i++) { + SourceChar current = src[i]; + SourceChar next = src[i + 1]; + if (IsLineTerminatorSequence(current, next)) line_ends->push_back(i); + } + + if (src_len > 0 && IsLineTerminatorSequence(src[src_len - 1], 0)) { + line_ends->push_back(src_len - 1); + } + if (include_ending_line) { + // Include one character beyond the end of script. The rewriter uses that + // position for the implicit return statement. + line_ends->push_back(src_len); + } +} + +Handle<FixedArray> String::CalculateLineEnds(Isolate* isolate, + Handle<String> src, + bool include_ending_line) { + src = Flatten(isolate, src); + // Rough estimate of line count based on a roughly estimated average + // length of (unpacked) code. + int line_count_estimate = src->length() >> 4; + std::vector<int> line_ends; + line_ends.reserve(line_count_estimate); + { + DisallowHeapAllocation no_allocation; // ensure vectors stay valid. + // Dispatch on type of strings. + String::FlatContent content = src->GetFlatContent(no_allocation); + DCHECK(content.IsFlat()); + if (content.IsOneByte()) { + CalculateLineEndsImpl(isolate, &line_ends, content.ToOneByteVector(), + include_ending_line); + } else { + CalculateLineEndsImpl(isolate, &line_ends, content.ToUC16Vector(), + include_ending_line); + } + } + int line_count = static_cast<int>(line_ends.size()); + Handle<FixedArray> array = isolate->factory()->NewFixedArray(line_count); + for (int i = 0; i < line_count; i++) { + array->set(i, Smi::FromInt(line_ends[i])); + } + return array; +} + +bool String::SlowEquals(String other) { + DisallowHeapAllocation no_gc; + // Fast check: negative check with lengths. + int len = length(); + if (len != other->length()) return false; + if (len == 0) return true; + + // Fast check: if at least one ThinString is involved, dereference it/them + // and restart. + if (this->IsThinString() || other->IsThinString()) { + if (other->IsThinString()) other = ThinString::cast(other)->actual(); + if (this->IsThinString()) { + return ThinString::cast(*this)->actual()->Equals(other); + } else { + return this->Equals(other); + } + } + + // Fast check: if hash code is computed for both strings + // a fast negative check can be performed. + if (HasHashCode() && other->HasHashCode()) { +#ifdef ENABLE_SLOW_DCHECKS + if (FLAG_enable_slow_asserts) { + if (Hash() != other->Hash()) { + bool found_difference = false; + for (int i = 0; i < len; i++) { + if (Get(i) != other->Get(i)) { + found_difference = true; + break; + } + } + DCHECK(found_difference); + } + } +#endif + if (Hash() != other->Hash()) return false; + } + + // We know the strings are both non-empty. Compare the first chars + // before we try to flatten the strings. + if (this->Get(0) != other->Get(0)) return false; + + if (IsSeqOneByteString() && other->IsSeqOneByteString()) { + const uint8_t* str1 = SeqOneByteString::cast(*this)->GetChars(no_gc); + const uint8_t* str2 = SeqOneByteString::cast(other)->GetChars(no_gc); + return CompareRawStringContents(str1, str2, len); + } + + StringComparator comparator; + return comparator.Equals(*this, other); +} + +bool String::SlowEquals(Isolate* isolate, Handle<String> one, + Handle<String> two) { + // Fast check: negative check with lengths. + int one_length = one->length(); + if (one_length != two->length()) return false; + if (one_length == 0) return true; + + // Fast check: if at least one ThinString is involved, dereference it/them + // and restart. + if (one->IsThinString() || two->IsThinString()) { + if (one->IsThinString()) + one = handle(ThinString::cast(*one)->actual(), isolate); + if (two->IsThinString()) + two = handle(ThinString::cast(*two)->actual(), isolate); + return String::Equals(isolate, one, two); + } + + // Fast check: if hash code is computed for both strings + // a fast negative check can be performed. + if (one->HasHashCode() && two->HasHashCode()) { +#ifdef ENABLE_SLOW_DCHECKS + if (FLAG_enable_slow_asserts) { + if (one->Hash() != two->Hash()) { + bool found_difference = false; + for (int i = 0; i < one_length; i++) { + if (one->Get(i) != two->Get(i)) { + found_difference = true; + break; + } + } + DCHECK(found_difference); + } + } +#endif + if (one->Hash() != two->Hash()) return false; + } + + // We know the strings are both non-empty. Compare the first chars + // before we try to flatten the strings. + if (one->Get(0) != two->Get(0)) return false; + + one = String::Flatten(isolate, one); + two = String::Flatten(isolate, two); + + DisallowHeapAllocation no_gc; + String::FlatContent flat1 = one->GetFlatContent(no_gc); + String::FlatContent flat2 = two->GetFlatContent(no_gc); + + if (flat1.IsOneByte() && flat2.IsOneByte()) { + return CompareRawStringContents(flat1.ToOneByteVector().start(), + flat2.ToOneByteVector().start(), + one_length); + } else { + for (int i = 0; i < one_length; i++) { + if (flat1.Get(i) != flat2.Get(i)) return false; + } + return true; + } +} + +// static +ComparisonResult String::Compare(Isolate* isolate, Handle<String> x, + Handle<String> y) { + // A few fast case tests before we flatten. + if (x.is_identical_to(y)) { + return ComparisonResult::kEqual; + } else if (y->length() == 0) { + return x->length() == 0 ? ComparisonResult::kEqual + : ComparisonResult::kGreaterThan; + } else if (x->length() == 0) { + return ComparisonResult::kLessThan; + } + + int const d = x->Get(0) - y->Get(0); + if (d < 0) { + return ComparisonResult::kLessThan; + } else if (d > 0) { + return ComparisonResult::kGreaterThan; + } + + // Slow case. + x = String::Flatten(isolate, x); + y = String::Flatten(isolate, y); + + DisallowHeapAllocation no_gc; + ComparisonResult result = ComparisonResult::kEqual; + int prefix_length = x->length(); + if (y->length() < prefix_length) { + prefix_length = y->length(); + result = ComparisonResult::kGreaterThan; + } else if (y->length() > prefix_length) { + result = ComparisonResult::kLessThan; + } + int r; + String::FlatContent x_content = x->GetFlatContent(no_gc); + String::FlatContent y_content = y->GetFlatContent(no_gc); + if (x_content.IsOneByte()) { + Vector<const uint8_t> x_chars = x_content.ToOneByteVector(); + if (y_content.IsOneByte()) { + Vector<const uint8_t> y_chars = y_content.ToOneByteVector(); + r = CompareChars(x_chars.start(), y_chars.start(), prefix_length); + } else { + Vector<const uc16> y_chars = y_content.ToUC16Vector(); + r = CompareChars(x_chars.start(), y_chars.start(), prefix_length); + } + } else { + Vector<const uc16> x_chars = x_content.ToUC16Vector(); + if (y_content.IsOneByte()) { + Vector<const uint8_t> y_chars = y_content.ToOneByteVector(); + r = CompareChars(x_chars.start(), y_chars.start(), prefix_length); + } else { + Vector<const uc16> y_chars = y_content.ToUC16Vector(); + r = CompareChars(x_chars.start(), y_chars.start(), prefix_length); + } + } + if (r < 0) { + result = ComparisonResult::kLessThan; + } else if (r > 0) { + result = ComparisonResult::kGreaterThan; + } + return result; +} + +Object String::IndexOf(Isolate* isolate, Handle<Object> receiver, + Handle<Object> search, Handle<Object> position) { + if (receiver->IsNullOrUndefined(isolate)) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kCalledOnNullOrUndefined, + isolate->factory()->NewStringFromAsciiChecked( + "String.prototype.indexOf"))); + } + Handle<String> receiver_string; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver_string, + Object::ToString(isolate, receiver)); + + Handle<String> search_string; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string, + Object::ToString(isolate, search)); + + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position, + Object::ToInteger(isolate, position)); + + uint32_t index = receiver_string->ToValidIndex(*position); + return Smi::FromInt( + String::IndexOf(isolate, receiver_string, search_string, index)); +} + +namespace { + +template <typename T> +int SearchString(Isolate* isolate, String::FlatContent receiver_content, + Vector<T> pat_vector, int start_index) { + if (receiver_content.IsOneByte()) { + return SearchString(isolate, receiver_content.ToOneByteVector(), pat_vector, + start_index); + } + return SearchString(isolate, receiver_content.ToUC16Vector(), pat_vector, + start_index); +} + +} // namespace + +int String::IndexOf(Isolate* isolate, Handle<String> receiver, + Handle<String> search, int start_index) { + DCHECK_LE(0, start_index); + DCHECK(start_index <= receiver->length()); + + uint32_t search_length = search->length(); + if (search_length == 0) return start_index; + + uint32_t receiver_length = receiver->length(); + if (start_index + search_length > receiver_length) return -1; + + receiver = String::Flatten(isolate, receiver); + search = String::Flatten(isolate, search); + + DisallowHeapAllocation no_gc; // ensure vectors stay valid + // Extract flattened substrings of cons strings before getting encoding. + String::FlatContent receiver_content = receiver->GetFlatContent(no_gc); + String::FlatContent search_content = search->GetFlatContent(no_gc); + + // dispatch on type of strings + if (search_content.IsOneByte()) { + Vector<const uint8_t> pat_vector = search_content.ToOneByteVector(); + return SearchString<const uint8_t>(isolate, receiver_content, pat_vector, + start_index); + } + Vector<const uc16> pat_vector = search_content.ToUC16Vector(); + return SearchString<const uc16>(isolate, receiver_content, pat_vector, + start_index); +} + +MaybeHandle<String> String::GetSubstitution(Isolate* isolate, Match* match, + Handle<String> replacement, + int start_index) { + DCHECK_GE(start_index, 0); + + Factory* factory = isolate->factory(); + + const int replacement_length = replacement->length(); + const int captures_length = match->CaptureCount(); + + replacement = String::Flatten(isolate, replacement); + + Handle<String> dollar_string = + factory->LookupSingleCharacterStringFromCode('$'); + int next_dollar_ix = + String::IndexOf(isolate, replacement, dollar_string, start_index); + if (next_dollar_ix < 0) { + return replacement; + } + + IncrementalStringBuilder builder(isolate); + + if (next_dollar_ix > 0) { + builder.AppendString(factory->NewSubString(replacement, 0, next_dollar_ix)); + } + + while (true) { + const int peek_ix = next_dollar_ix + 1; + if (peek_ix >= replacement_length) { + builder.AppendCharacter('$'); + return builder.Finish(); + } + + int continue_from_ix = -1; + const uint16_t peek = replacement->Get(peek_ix); + switch (peek) { + case '$': // $$ + builder.AppendCharacter('$'); + continue_from_ix = peek_ix + 1; + break; + case '&': // $& - match + builder.AppendString(match->GetMatch()); + continue_from_ix = peek_ix + 1; + break; + case '`': // $` - prefix + builder.AppendString(match->GetPrefix()); + continue_from_ix = peek_ix + 1; + break; + case '\'': // $' - suffix + builder.AppendString(match->GetSuffix()); + continue_from_ix = peek_ix + 1; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99 + int scaled_index = (peek - '0'); + int advance = 1; + + if (peek_ix + 1 < replacement_length) { + const uint16_t next_peek = replacement->Get(peek_ix + 1); + if (next_peek >= '0' && next_peek <= '9') { + const int new_scaled_index = scaled_index * 10 + (next_peek - '0'); + if (new_scaled_index < captures_length) { + scaled_index = new_scaled_index; + advance = 2; + } + } + } + + if (scaled_index == 0 || scaled_index >= captures_length) { + builder.AppendCharacter('$'); + continue_from_ix = peek_ix; + break; + } + + bool capture_exists; + Handle<String> capture; + ASSIGN_RETURN_ON_EXCEPTION( + isolate, capture, match->GetCapture(scaled_index, &capture_exists), + String); + if (capture_exists) builder.AppendString(capture); + continue_from_ix = peek_ix + advance; + break; + } + case '<': { // $<name> - named capture + typedef String::Match::CaptureState CaptureState; + + if (!match->HasNamedCaptures()) { + builder.AppendCharacter('$'); + continue_from_ix = peek_ix; + break; + } + + Handle<String> bracket_string = + factory->LookupSingleCharacterStringFromCode('>'); + const int closing_bracket_ix = + String::IndexOf(isolate, replacement, bracket_string, peek_ix + 1); + + if (closing_bracket_ix == -1) { + // No closing bracket was found, treat '$<' as a string literal. + builder.AppendCharacter('$'); + continue_from_ix = peek_ix; + break; + } + + Handle<String> capture_name = + factory->NewSubString(replacement, peek_ix + 1, closing_bracket_ix); + Handle<String> capture; + CaptureState capture_state; + ASSIGN_RETURN_ON_EXCEPTION( + isolate, capture, + match->GetNamedCapture(capture_name, &capture_state), String); + + switch (capture_state) { + case CaptureState::INVALID: + case CaptureState::UNMATCHED: + break; + case CaptureState::MATCHED: + builder.AppendString(capture); + break; + } + + continue_from_ix = closing_bracket_ix + 1; + break; + } + default: + builder.AppendCharacter('$'); + continue_from_ix = peek_ix; + break; + } + + // Go the the next $ in the replacement. + // TODO(jgruber): Single-char lookups could be much more efficient. + DCHECK_NE(continue_from_ix, -1); + next_dollar_ix = + String::IndexOf(isolate, replacement, dollar_string, continue_from_ix); + + // Return if there are no more $ characters in the replacement. If we + // haven't reached the end, we need to append the suffix. + if (next_dollar_ix < 0) { + if (continue_from_ix < replacement_length) { + builder.AppendString(factory->NewSubString( + replacement, continue_from_ix, replacement_length)); + } + return builder.Finish(); + } + + // Append substring between the previous and the next $ character. + if (next_dollar_ix > continue_from_ix) { + builder.AppendString( + factory->NewSubString(replacement, continue_from_ix, next_dollar_ix)); + } + } + + UNREACHABLE(); +} + +namespace { // for String.Prototype.lastIndexOf + +template <typename schar, typename pchar> +int StringMatchBackwards(Vector<const schar> subject, + Vector<const pchar> pattern, int idx) { + int pattern_length = pattern.length(); + DCHECK_GE(pattern_length, 1); + DCHECK(idx + pattern_length <= subject.length()); + + if (sizeof(schar) == 1 && sizeof(pchar) > 1) { + for (int i = 0; i < pattern_length; i++) { + uc16 c = pattern[i]; + if (c > String::kMaxOneByteCharCode) { + return -1; + } + } + } + + pchar pattern_first_char = pattern[0]; + for (int i = idx; i >= 0; i--) { + if (subject[i] != pattern_first_char) continue; + int j = 1; + while (j < pattern_length) { + if (pattern[j] != subject[i + j]) { + break; + } + j++; + } + if (j == pattern_length) { + return i; + } + } + return -1; +} + +} // namespace + +Object String::LastIndexOf(Isolate* isolate, Handle<Object> receiver, + Handle<Object> search, Handle<Object> position) { + if (receiver->IsNullOrUndefined(isolate)) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kCalledOnNullOrUndefined, + isolate->factory()->NewStringFromAsciiChecked( + "String.prototype.lastIndexOf"))); + } + Handle<String> receiver_string; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, receiver_string, + Object::ToString(isolate, receiver)); + + Handle<String> search_string; + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, search_string, + Object::ToString(isolate, search)); + + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position, + Object::ToNumber(isolate, position)); + + uint32_t start_index; + + if (position->IsNaN()) { + start_index = receiver_string->length(); + } else { + ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, position, + Object::ToInteger(isolate, position)); + start_index = receiver_string->ToValidIndex(*position); + } + + uint32_t pattern_length = search_string->length(); + uint32_t receiver_length = receiver_string->length(); + + if (start_index + pattern_length > receiver_length) { + start_index = receiver_length - pattern_length; + } + + if (pattern_length == 0) { + return Smi::FromInt(start_index); + } + + receiver_string = String::Flatten(isolate, receiver_string); + search_string = String::Flatten(isolate, search_string); + + int last_index = -1; + DisallowHeapAllocation no_gc; // ensure vectors stay valid + + String::FlatContent receiver_content = receiver_string->GetFlatContent(no_gc); + String::FlatContent search_content = search_string->GetFlatContent(no_gc); + + if (search_content.IsOneByte()) { + Vector<const uint8_t> pat_vector = search_content.ToOneByteVector(); + if (receiver_content.IsOneByte()) { + last_index = StringMatchBackwards(receiver_content.ToOneByteVector(), + pat_vector, start_index); + } else { + last_index = StringMatchBackwards(receiver_content.ToUC16Vector(), + pat_vector, start_index); + } + } else { + Vector<const uc16> pat_vector = search_content.ToUC16Vector(); + if (receiver_content.IsOneByte()) { + last_index = StringMatchBackwards(receiver_content.ToOneByteVector(), + pat_vector, start_index); + } else { + last_index = StringMatchBackwards(receiver_content.ToUC16Vector(), + pat_vector, start_index); + } + } + return Smi::FromInt(last_index); +} + +bool String::IsUtf8EqualTo(Vector<const char> str, bool allow_prefix_match) { + int slen = length(); + // Can't check exact length equality, but we can check bounds. + int str_len = str.length(); + if (!allow_prefix_match && + (str_len < slen || + str_len > slen * static_cast<int>(unibrow::Utf8::kMaxEncodedSize))) { + return false; + } + + int i = 0; + unibrow::Utf8Iterator it = unibrow::Utf8Iterator(str); + while (i < slen && !it.Done()) { + if (Get(i++) != *it) return false; + ++it; + } + + return (allow_prefix_match || i == slen) && it.Done(); +} + +template <> +bool String::IsEqualTo(Vector<const uint8_t> str) { + return IsOneByteEqualTo(str); +} + +template <> +bool String::IsEqualTo(Vector<const uc16> str) { + return IsTwoByteEqualTo(str); +} + +bool String::IsOneByteEqualTo(Vector<const uint8_t> str) { + int slen = length(); + if (str.length() != slen) return false; + DisallowHeapAllocation no_gc; + FlatContent content = GetFlatContent(no_gc); + if (content.IsOneByte()) { + return CompareChars(content.ToOneByteVector().start(), str.start(), slen) == + 0; + } + return CompareChars(content.ToUC16Vector().start(), str.start(), slen) == 0; +} + +bool String::IsTwoByteEqualTo(Vector<const uc16> str) { + int slen = length(); + if (str.length() != slen) return false; + DisallowHeapAllocation no_gc; + FlatContent content = GetFlatContent(no_gc); + if (content.IsOneByte()) { + return CompareChars(content.ToOneByteVector().start(), str.start(), slen) == + 0; + } + return CompareChars(content.ToUC16Vector().start(), str.start(), slen) == 0; +} + +uint32_t String::ComputeAndSetHash() { + DisallowHeapAllocation no_gc; + // Should only be called if hash code has not yet been computed. + DCHECK(!HasHashCode()); + + // Store the hash code in the object. + uint32_t field = + IteratingStringHasher::Hash(*this, HashSeed(GetReadOnlyRoots())); + set_hash_field(field); + + // Check the hash code is there. + DCHECK(HasHashCode()); + uint32_t result = field >> kHashShift; + DCHECK_NE(result, 0); // Ensure that the hash value of 0 is never computed. + return result; +} + +bool String::ComputeArrayIndex(uint32_t* index) { + int length = this->length(); + if (length == 0 || length > kMaxArrayIndexSize) return false; + StringCharacterStream stream(*this); + return StringToArrayIndex(&stream, index); +} + +bool String::SlowAsArrayIndex(uint32_t* index) { + DisallowHeapAllocation no_gc; + if (length() <= kMaxCachedArrayIndexLength) { + Hash(); // force computation of hash code + uint32_t field = hash_field(); + if ((field & kIsNotArrayIndexMask) != 0) return false; + // Isolate the array index form the full hash field. + *index = ArrayIndexValueBits::decode(field); + return true; + } else { + return ComputeArrayIndex(index); + } +} + +void String::PrintOn(FILE* file) { + int length = this->length(); + for (int i = 0; i < length; i++) { + PrintF(file, "%c", Get(i)); + } +} + +Handle<String> SeqString::Truncate(Handle<SeqString> string, int new_length) { + if (new_length == 0) return string->GetReadOnlyRoots().empty_string_handle(); + + int new_size, old_size; + int old_length = string->length(); + if (old_length <= new_length) return string; + + if (string->IsSeqOneByteString()) { + old_size = SeqOneByteString::SizeFor(old_length); + new_size = SeqOneByteString::SizeFor(new_length); + } else { + DCHECK(string->IsSeqTwoByteString()); + old_size = SeqTwoByteString::SizeFor(old_length); + new_size = SeqTwoByteString::SizeFor(new_length); + } + + int delta = old_size - new_size; + + Address start_of_string = string->address(); + DCHECK(IsAligned(start_of_string, kObjectAlignment)); + DCHECK(IsAligned(start_of_string + new_size, kObjectAlignment)); + + Heap* heap = Heap::FromWritableHeapObject(*string); + // Sizes are pointer size aligned, so that we can use filler objects + // that are a multiple of pointer size. + heap->CreateFillerObjectAt(start_of_string + new_size, delta, + ClearRecordedSlots::kNo); + // We are storing the new length using release store after creating a filler + // for the left-over space to avoid races with the sweeper thread. + string->synchronized_set_length(new_length); + + return string; +} + +void SeqOneByteString::clear_padding() { + int data_size = SeqString::kHeaderSize + length() * kOneByteSize; + memset(reinterpret_cast<void*>(address() + data_size), 0, + SizeFor(length()) - data_size); +} + +void SeqTwoByteString::clear_padding() { + int data_size = SeqString::kHeaderSize + length() * kUC16Size; + memset(reinterpret_cast<void*>(address() + data_size), 0, + SizeFor(length()) - data_size); +} + +uint16_t ConsString::ConsStringGet(int index) { + DCHECK(index >= 0 && index < this->length()); + + // Check for a flattened cons string + if (second()->length() == 0) { + String left = first(); + return left->Get(index); + } + + String string = String::cast(*this); + + while (true) { + if (StringShape(string).IsCons()) { + ConsString cons_string = ConsString::cast(string); + String left = cons_string->first(); + if (left->length() > index) { + string = left; + } else { + index -= left->length(); + string = cons_string->second(); + } + } else { + return string->Get(index); + } + } + + UNREACHABLE(); +} + +uint16_t ThinString::ThinStringGet(int index) { return actual()->Get(index); } + +uint16_t SlicedString::SlicedStringGet(int index) { + return parent()->Get(offset() + index); +} + +int ExternalString::ExternalPayloadSize() const { + int length_multiplier = IsTwoByteRepresentation() ? i::kShortSize : kCharSize; + return length() * length_multiplier; +} + +FlatStringReader::FlatStringReader(Isolate* isolate, Handle<String> str) + : Relocatable(isolate), str_(str.location()), length_(str->length()) { + PostGarbageCollection(); +} + +FlatStringReader::FlatStringReader(Isolate* isolate, Vector<const char> input) + : Relocatable(isolate), + str_(nullptr), + is_one_byte_(true), + length_(input.length()), + start_(input.start()) {} + +void FlatStringReader::PostGarbageCollection() { + if (str_ == nullptr) return; + Handle<String> str(str_); + DCHECK(str->IsFlat()); + DisallowHeapAllocation no_gc; + // This does not actually prevent the vector from being relocated later. + String::FlatContent content = str->GetFlatContent(no_gc); + DCHECK(content.IsFlat()); + is_one_byte_ = content.IsOneByte(); + if (is_one_byte_) { + start_ = content.ToOneByteVector().start(); + } else { + start_ = content.ToUC16Vector().start(); + } +} + +void ConsStringIterator::Initialize(ConsString cons_string, int offset) { + DCHECK(!cons_string.is_null()); + root_ = cons_string; + consumed_ = offset; + // Force stack blown condition to trigger restart. + depth_ = 1; + maximum_depth_ = kStackSize + depth_; + DCHECK(StackBlown()); +} + +String ConsStringIterator::Continue(int* offset_out) { + DCHECK_NE(depth_, 0); + DCHECK_EQ(0, *offset_out); + bool blew_stack = StackBlown(); + String string; + // Get the next leaf if there is one. + if (!blew_stack) string = NextLeaf(&blew_stack); + // Restart search from root. + if (blew_stack) { + DCHECK(string.is_null()); + string = Search(offset_out); + } + // Ensure future calls return null immediately. + if (string.is_null()) Reset(ConsString()); + return string; +} + +String ConsStringIterator::Search(int* offset_out) { + ConsString cons_string = root_; + // Reset the stack, pushing the root string. + depth_ = 1; + maximum_depth_ = 1; + frames_[0] = cons_string; + const int consumed = consumed_; + int offset = 0; + while (true) { + // Loop until the string is found which contains the target offset. + String string = cons_string->first(); + int length = string->length(); + int32_t type; + if (consumed < offset + length) { + // Target offset is in the left branch. + // Keep going if we're still in a ConString. + type = string->map()->instance_type(); + if ((type & kStringRepresentationMask) == kConsStringTag) { + cons_string = ConsString::cast(string); + PushLeft(cons_string); + continue; + } + // Tell the stack we're done descending. + AdjustMaximumDepth(); + } else { + // Descend right. + // Update progress through the string. + offset += length; + // Keep going if we're still in a ConString. + string = cons_string->second(); + type = string->map()->instance_type(); + if ((type & kStringRepresentationMask) == kConsStringTag) { + cons_string = ConsString::cast(string); + PushRight(cons_string); + continue; + } + // Need this to be updated for the current string. + length = string->length(); + // Account for the possibility of an empty right leaf. + // This happens only if we have asked for an offset outside the string. + if (length == 0) { + // Reset so future operations will return null immediately. + Reset(ConsString()); + return String(); + } + // Tell the stack we're done descending. + AdjustMaximumDepth(); + // Pop stack so next iteration is in correct place. + Pop(); + } + DCHECK_NE(length, 0); + // Adjust return values and exit. + consumed_ = offset + length; + *offset_out = consumed - offset; + return string; + } + UNREACHABLE(); +} + +String ConsStringIterator::NextLeaf(bool* blew_stack) { + while (true) { + // Tree traversal complete. + if (depth_ == 0) { + *blew_stack = false; + return String(); + } + // We've lost track of higher nodes. + if (StackBlown()) { + *blew_stack = true; + return String(); + } + // Go right. + ConsString cons_string = frames_[OffsetForDepth(depth_ - 1)]; + String string = cons_string->second(); + int32_t type = string->map()->instance_type(); + if ((type & kStringRepresentationMask) != kConsStringTag) { + // Pop stack so next iteration is in correct place. + Pop(); + int length = string->length(); + // Could be a flattened ConsString. + if (length == 0) continue; + consumed_ += length; + return string; + } + cons_string = ConsString::cast(string); + PushRight(cons_string); + // Need to traverse all the way left. + while (true) { + // Continue left. + string = cons_string->first(); + type = string->map()->instance_type(); + if ((type & kStringRepresentationMask) != kConsStringTag) { + AdjustMaximumDepth(); + int length = string->length(); + if (length == 0) break; // Skip empty left-hand sides of ConsStrings. + consumed_ += length; + return string; + } + cons_string = ConsString::cast(string); + PushLeft(cons_string); + } + } + UNREACHABLE(); +} + +} // namespace internal +} // namespace v8 |