// 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/strings/string-builder-inl.h" #include "src/execution/isolate-inl.h" #include "src/objects/fixed-array-inl.h" #include "src/objects/js-array-inl.h" namespace v8 { namespace internal { template void StringBuilderConcatHelper(String special, sinkchar* sink, FixedArray fixed_array, int array_length) { DisallowHeapAllocation no_gc; int position = 0; for (int i = 0; i < array_length; i++) { Object element = fixed_array.get(i); if (element.IsSmi()) { // Smi encoding of position and length. int encoded_slice = Smi::ToInt(element); int pos; int len; if (encoded_slice > 0) { // Position and length encoded in one smi. pos = StringBuilderSubstringPosition::decode(encoded_slice); len = StringBuilderSubstringLength::decode(encoded_slice); } else { // Position and length encoded in two smis. Object obj = fixed_array.get(++i); DCHECK(obj.IsSmi()); pos = Smi::ToInt(obj); len = -encoded_slice; } String::WriteToFlat(special, sink + position, pos, pos + len); position += len; } else { String string = String::cast(element); int element_length = string.length(); String::WriteToFlat(string, sink + position, 0, element_length); position += element_length; } } } template void StringBuilderConcatHelper(String special, uint8_t* sink, FixedArray fixed_array, int array_length); template void StringBuilderConcatHelper(String special, uc16* sink, FixedArray fixed_array, int array_length); int StringBuilderConcatLength(int special_length, FixedArray fixed_array, int array_length, bool* one_byte) { DisallowHeapAllocation no_gc; int position = 0; for (int i = 0; i < array_length; i++) { int increment = 0; Object elt = fixed_array.get(i); if (elt.IsSmi()) { // Smi encoding of position and length. int smi_value = Smi::ToInt(elt); int pos; int len; if (smi_value > 0) { // Position and length encoded in one smi. pos = StringBuilderSubstringPosition::decode(smi_value); len = StringBuilderSubstringLength::decode(smi_value); } else { // Position and length encoded in two smis. len = -smi_value; // Get the position and check that it is a positive smi. i++; if (i >= array_length) return -1; Object next_smi = fixed_array.get(i); if (!next_smi.IsSmi()) return -1; pos = Smi::ToInt(next_smi); if (pos < 0) return -1; } DCHECK_GE(pos, 0); DCHECK_GE(len, 0); if (pos > special_length || len > special_length - pos) return -1; increment = len; } else if (elt.IsString()) { String element = String::cast(elt); int element_length = element.length(); increment = element_length; if (*one_byte && !element.IsOneByteRepresentation()) { *one_byte = false; } } else { return -1; } if (increment > String::kMaxLength - position) { return kMaxInt; // Provoke throw on allocation. } position += increment; } return position; } FixedArrayBuilder::FixedArrayBuilder(Isolate* isolate, int initial_capacity) : array_(isolate->factory()->NewFixedArrayWithHoles(initial_capacity)), length_(0), has_non_smi_elements_(false) { // Require a non-zero initial size. Ensures that doubling the size to // extend the array will work. DCHECK_GT(initial_capacity, 0); } FixedArrayBuilder::FixedArrayBuilder(Handle backing_store) : array_(backing_store), length_(0), has_non_smi_elements_(false) { // Require a non-zero initial size. Ensures that doubling the size to // extend the array will work. DCHECK_GT(backing_store->length(), 0); } bool FixedArrayBuilder::HasCapacity(int elements) { int length = array_->length(); int required_length = length_ + elements; return (length >= required_length); } void FixedArrayBuilder::EnsureCapacity(Isolate* isolate, int elements) { int length = array_->length(); int required_length = length_ + elements; if (length < required_length) { int new_length = length; do { new_length *= 2; } while (new_length < required_length); Handle extended_array = isolate->factory()->NewFixedArrayWithHoles(new_length); array_->CopyTo(0, *extended_array, 0, length_); array_ = extended_array; } } void FixedArrayBuilder::Add(Object value) { DCHECK(!value.IsSmi()); array_->set(length_, value); length_++; has_non_smi_elements_ = true; } void FixedArrayBuilder::Add(Smi value) { DCHECK(value.IsSmi()); array_->set(length_, value); length_++; } int FixedArrayBuilder::capacity() { return array_->length(); } Handle FixedArrayBuilder::ToJSArray(Handle target_array) { JSArray::SetContent(target_array, array_); target_array->set_length(Smi::FromInt(length_)); return target_array; } ReplacementStringBuilder::ReplacementStringBuilder(Heap* heap, Handle subject, int estimated_part_count) : heap_(heap), array_builder_(Isolate::FromHeap(heap), estimated_part_count), subject_(subject), character_count_(0), is_one_byte_(subject->IsOneByteRepresentation()) { // Require a non-zero initial size. Ensures that doubling the size to // extend the array will work. DCHECK_GT(estimated_part_count, 0); } void ReplacementStringBuilder::EnsureCapacity(int elements) { array_builder_.EnsureCapacity(Isolate::FromHeap(heap_), elements); } void ReplacementStringBuilder::AddString(Handle string) { int length = string->length(); DCHECK_GT(length, 0); AddElement(string); if (!string->IsOneByteRepresentation()) { is_one_byte_ = false; } IncrementCharacterCount(length); } MaybeHandle ReplacementStringBuilder::ToString() { Isolate* isolate = Isolate::FromHeap(heap_); if (array_builder_.length() == 0) { return isolate->factory()->empty_string(); } Handle joined_string; if (is_one_byte_) { Handle seq; ASSIGN_RETURN_ON_EXCEPTION( isolate, seq, isolate->factory()->NewRawOneByteString(character_count_), String); DisallowHeapAllocation no_gc; uint8_t* char_buffer = seq->GetChars(no_gc); StringBuilderConcatHelper(*subject_, char_buffer, *array_builder_.array(), array_builder_.length()); joined_string = Handle::cast(seq); } else { // Two-byte. Handle seq; ASSIGN_RETURN_ON_EXCEPTION( isolate, seq, isolate->factory()->NewRawTwoByteString(character_count_), String); DisallowHeapAllocation no_gc; uc16* char_buffer = seq->GetChars(no_gc); StringBuilderConcatHelper(*subject_, char_buffer, *array_builder_.array(), array_builder_.length()); joined_string = Handle::cast(seq); } return joined_string; } void ReplacementStringBuilder::AddElement(Handle element) { DCHECK(element->IsSmi() || element->IsString()); EnsureCapacity(1); DisallowHeapAllocation no_gc; array_builder_.Add(*element); } IncrementalStringBuilder::IncrementalStringBuilder(Isolate* isolate) : isolate_(isolate), encoding_(String::ONE_BYTE_ENCODING), overflowed_(false), part_length_(kInitialPartLength), current_index_(0) { // Create an accumulator handle starting with the empty string. accumulator_ = Handle::New(ReadOnlyRoots(isolate).empty_string(), isolate); current_part_ = factory()->NewRawOneByteString(part_length_).ToHandleChecked(); } int IncrementalStringBuilder::Length() const { return accumulator_->length() + current_index_; } void IncrementalStringBuilder::Accumulate(Handle new_part) { Handle new_accumulator; if (accumulator()->length() + new_part->length() > String::kMaxLength) { // Set the flag and carry on. Delay throwing the exception till the end. new_accumulator = factory()->empty_string(); overflowed_ = true; } else { new_accumulator = factory()->NewConsString(accumulator(), new_part).ToHandleChecked(); } set_accumulator(new_accumulator); } void IncrementalStringBuilder::Extend() { DCHECK_EQ(current_index_, current_part()->length()); Accumulate(current_part()); if (part_length_ <= kMaxPartLength / kPartLengthGrowthFactor) { part_length_ *= kPartLengthGrowthFactor; } Handle new_part; if (encoding_ == String::ONE_BYTE_ENCODING) { new_part = factory()->NewRawOneByteString(part_length_).ToHandleChecked(); } else { new_part = factory()->NewRawTwoByteString(part_length_).ToHandleChecked(); } // Reuse the same handle to avoid being invalidated when exiting handle scope. set_current_part(new_part); current_index_ = 0; } MaybeHandle IncrementalStringBuilder::Finish() { ShrinkCurrentPart(); Accumulate(current_part()); if (overflowed_) { THROW_NEW_ERROR(isolate_, NewInvalidStringLengthError(), String); } return accumulator(); } // Short strings can be copied directly to {current_part_}. // Requires the IncrementalStringBuilder to either have two byte encoding or // the incoming string to have one byte representation "underneath" (The // one byte check requires the string to be flat). bool IncrementalStringBuilder::CanAppendByCopy(Handle string) { constexpr int kMaxStringLengthForCopy = 16; const bool representation_ok = encoding_ == String::TWO_BYTE_ENCODING || (string->IsFlat() && String::IsOneByteRepresentationUnderneath(*string)); return representation_ok && string->length() <= kMaxStringLengthForCopy && CurrentPartCanFit(string->length()); } void IncrementalStringBuilder::AppendStringByCopy(Handle string) { DCHECK(CanAppendByCopy(string)); Handle part = Handle::cast(current_part()); { DisallowHeapAllocation no_gc; String::WriteToFlat(*string, part->GetChars(no_gc) + current_index_, 0, string->length()); } current_index_ += string->length(); DCHECK(current_index_ <= part_length_); if (current_index_ == part_length_) Extend(); } void IncrementalStringBuilder::AppendString(Handle string) { if (CanAppendByCopy(string)) { AppendStringByCopy(string); return; } ShrinkCurrentPart(); part_length_ = kInitialPartLength; // Allocate conservatively. Extend(); // Attach current part and allocate new part. Accumulate(string); } } // namespace internal } // namespace v8