// 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/string-builder-inl.h"
|
|
#include "src/isolate-inl.h"
|
#include "src/objects/fixed-array-inl.h"
|
#include "src/objects/js-array-inl.h"
|
|
namespace v8 {
|
namespace internal {
|
|
template <typename sinkchar>
|
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<uint8_t>(String* special, uint8_t* sink,
|
FixedArray* fixed_array,
|
int array_length);
|
|
template void StringBuilderConcatHelper<uc16>(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->HasOnlyOneByteChars()) {
|
*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<FixedArray> 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<FixedArray> 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());
|
DCHECK(length_ < capacity());
|
array_->set(length_, value);
|
length_++;
|
has_non_smi_elements_ = true;
|
}
|
|
void FixedArrayBuilder::Add(Smi* value) {
|
DCHECK(value->IsSmi());
|
DCHECK(length_ < capacity());
|
array_->set(length_, value);
|
length_++;
|
}
|
|
int FixedArrayBuilder::capacity() { return array_->length(); }
|
|
Handle<JSArray> FixedArrayBuilder::ToJSArray(Handle<JSArray> target_array) {
|
JSArray::SetContent(target_array, array_);
|
target_array->set_length(Smi::FromInt(length_));
|
return target_array;
|
}
|
|
ReplacementStringBuilder::ReplacementStringBuilder(Heap* heap,
|
Handle<String> subject,
|
int estimated_part_count)
|
: heap_(heap),
|
array_builder_(heap->isolate(), 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(heap_->isolate(), elements);
|
}
|
|
void ReplacementStringBuilder::AddString(Handle<String> string) {
|
int length = string->length();
|
DCHECK_GT(length, 0);
|
AddElement(*string);
|
if (!string->IsOneByteRepresentation()) {
|
is_one_byte_ = false;
|
}
|
IncrementCharacterCount(length);
|
}
|
|
MaybeHandle<String> ReplacementStringBuilder::ToString() {
|
Isolate* isolate = heap_->isolate();
|
if (array_builder_.length() == 0) {
|
return isolate->factory()->empty_string();
|
}
|
|
Handle<String> joined_string;
|
if (is_one_byte_) {
|
Handle<SeqOneByteString> seq;
|
ASSIGN_RETURN_ON_EXCEPTION(
|
isolate, seq, isolate->factory()->NewRawOneByteString(character_count_),
|
String);
|
|
DisallowHeapAllocation no_gc;
|
uint8_t* char_buffer = seq->GetChars();
|
StringBuilderConcatHelper(*subject_, char_buffer, *array_builder_.array(),
|
array_builder_.length());
|
joined_string = Handle<String>::cast(seq);
|
} else {
|
// Two-byte.
|
Handle<SeqTwoByteString> seq;
|
ASSIGN_RETURN_ON_EXCEPTION(
|
isolate, seq, isolate->factory()->NewRawTwoByteString(character_count_),
|
String);
|
|
DisallowHeapAllocation no_gc;
|
uc16* char_buffer = seq->GetChars();
|
StringBuilderConcatHelper(*subject_, char_buffer, *array_builder_.array(),
|
array_builder_.length());
|
joined_string = Handle<String>::cast(seq);
|
}
|
return joined_string;
|
}
|
|
void ReplacementStringBuilder::AddElement(Object* element) {
|
DCHECK(element->IsSmi() || element->IsString());
|
DCHECK(array_builder_.capacity() > array_builder_.length());
|
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<String>::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<String> new_part) {
|
Handle<String> 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<String> 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<String> IncrementalStringBuilder::Finish() {
|
ShrinkCurrentPart();
|
Accumulate(current_part());
|
if (overflowed_) {
|
THROW_NEW_ERROR(isolate_, NewInvalidStringLengthError(), String);
|
}
|
return accumulator();
|
}
|
|
|
void IncrementalStringBuilder::AppendString(Handle<String> string) {
|
ShrinkCurrentPart();
|
part_length_ = kInitialPartLength; // Allocate conservatively.
|
Extend(); // Attach current part and allocate new part.
|
Accumulate(string);
|
}
|
} // namespace internal
|
} // namespace v8
|