blob: 156467c89514fb5aa384eda76710e1f380d76c9e [file]
// Copyright 2017 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "riegeli/bytes/string_writer.h"
#include <stddef.h>
#include <cstring>
#include <limits>
#include <optional>
#include <string>
#include <utility>
#include "absl/base/optimization.h"
#include "absl/strings/cord.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "riegeli/base/arithmetic.h"
#include "riegeli/base/assert.h"
#include "riegeli/base/buffering.h"
#include "riegeli/base/byte_fill.h"
#include "riegeli/base/chain.h"
#include "riegeli/base/cord_utils.h"
#include "riegeli/base/external_ref.h"
#include "riegeli/base/string_utils.h"
#include "riegeli/base/types.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/bytes/string_reader.h"
#include "riegeli/bytes/writer.h"
namespace riegeli {
void StringWriterBase::Done() {
StringWriterBase::FlushImpl(FlushType::kFromObject);
Writer::Done();
secondary_buffer_ = Chain();
associated_reader_.Reset();
}
inline size_t StringWriterBase::used_size() const {
return UnsignedMax(IntCast<size_t>(pos()), written_size_);
}
inline size_t StringWriterBase::used_dest_size() const {
if (uses_secondary_buffer()) {
RIEGELI_ASSERT_EQ(available(), 0u)
<< "Failed precondition of StringWriterBase::used_dest_size(): "
<< "secondary buffer has free space";
}
RIEGELI_ASSERT_GE(used_size(), secondary_buffer_.size())
<< "Failed invariant of StringWriterBase: "
"negative destination size";
return used_size() - secondary_buffer_.size();
}
inline void StringWriterBase::MakeDestBuffer(std::string& dest,
size_t cursor_index) {
RIEGELI_ASSERT(!uses_secondary_buffer())
<< "Failed precondition in StringWriterBase::MakeDestBuffer(): "
"secondary buffer is used";
set_buffer(dest.data(), dest.size(), cursor_index);
set_start_pos(0);
}
inline void StringWriterBase::GrowDestAndMakeBuffer(std::string& dest,
size_t cursor_index,
size_t new_size) {
if (uses_secondary_buffer()) {
RIEGELI_ASSERT_EQ(available(), 0u)
<< "Failed precondition of StringWriterBase::GrowDestAndMakeBuffer(): "
<< "secondary buffer has free space";
}
const size_t old_size = dest.size();
if (ABSL_PREDICT_TRUE(new_size > old_size)) {
StringResizeAmortized(dest, new_size);
MarkPoisoned(dest.data() + old_size, new_size - old_size);
}
set_buffer(dest.data(), dest.size(), cursor_index);
set_start_pos(0);
}
inline bool StringWriterBase::GrowDestUnderCapacityAndMakeBuffer(
std::string& dest, size_t cursor_index, size_t min_length) {
RIEGELI_ASSERT(!uses_secondary_buffer())
<< "Failed precondition in "
"StringWriterBase::GrowDestUnderCapacityAndMakeBuffer(): "
"secondary buffer is used";
RIEGELI_ASSERT_LE(min_length,
std::numeric_limits<size_t>::max() - cursor_index)
<< "Failed precondition of "
"StringWriterBase::GrowDestUnderCapacityAndMakeBuffer(): "
"Writer position overflow";
size_t new_size = cursor_index + min_length;
if (new_size > dest.capacity()) return false;
new_size = UnsignedClamp(
dest.size() + UnsignedClamp(dest.size(), kDefaultMinBlockSize,
kDefaultMaxBlockSize),
new_size, dest.capacity());
const size_t old_size = dest.size();
if (ABSL_PREDICT_TRUE(new_size > old_size)) {
dest.resize(new_size);
MarkPoisoned(dest.data() + old_size, new_size - old_size);
}
set_buffer(dest.data(), dest.size(), cursor_index);
set_start_pos(0);
return true;
}
inline void StringWriterBase::SyncSecondaryBuffer() {
set_start_pos(pos());
secondary_buffer_.RemoveSuffix(available(), options_);
set_buffer();
}
inline void StringWriterBase::MakeSecondaryBuffer(size_t min_length,
size_t recommended_length) {
const absl::Span<char> buffer = secondary_buffer_.AppendBuffer(
min_length, recommended_length, Chain::kAnyLength, options_);
set_buffer(buffer.data(), buffer.size());
}
void StringWriterBase::SetWriteSizeHintImpl(
std::optional<Position> write_size_hint) {
if (write_size_hint == std::nullopt || ABSL_PREDICT_FALSE(!ok())) return;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
const size_t cursor_index = IntCast<size_t>(pos());
const size_t size_hint = UnsignedMax(
SaturatingAdd(cursor_index, SaturatingIntCast<size_t>(*write_size_hint)),
written_size_);
if (!uses_secondary_buffer()) {
GrowDestAndMakeBuffer(dest, cursor_index, size_hint);
return;
}
SyncSecondaryBuffer();
dest.erase(used_dest_size());
StringReserveAmortized(dest, size_hint);
std::move(secondary_buffer_).AppendTo(dest);
secondary_buffer_.Clear();
MakeDestBuffer(dest, cursor_index);
}
bool StringWriterBase::PushSlow(size_t min_length, size_t recommended_length) {
RIEGELI_ASSERT_LT(available(), min_length)
<< "Failed precondition of Writer::PushSlow(): "
"enough space available, use Push() instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (ABSL_PREDICT_FALSE(min_length > std::numeric_limits<size_t>::max() -
IntCast<size_t>(pos()))) {
return FailOverflow();
}
if (!uses_secondary_buffer()) {
const size_t cursor_index = IntCast<size_t>(pos());
if (cursor_index == 0 || ABSL_PREDICT_FALSE(written_size_ > cursor_index)) {
// Allocate the first block directly in `dest`. It is possible that it
// will not need to be copied if it turns out to be the only block,
// although this decision might cause it to remain wasteful if less data
// are written than space requested.
//
// Resize `dest` also if data follow the current position, to make the
// data available for partial overwriting.
const size_t size_hint = SaturatingAdd(
cursor_index, UnsignedMax(min_length, recommended_length));
StringReserveAmortized(dest, size_hint);
}
if (GrowDestUnderCapacityAndMakeBuffer(dest, cursor_index, min_length)) {
return true;
}
set_start_pos(cursor_index);
set_buffer();
written_size_ = 0;
} else {
SyncSecondaryBuffer();
}
MakeSecondaryBuffer(min_length, recommended_length);
return true;
}
bool StringWriterBase::WriteSlow(absl::string_view src) {
RIEGELI_ASSERT_LT(available(), src.size())
<< "Failed precondition of Writer::WriteSlow(string_view): "
"enough space available, use Write(string_view) instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (ABSL_PREDICT_FALSE(src.size() > std::numeric_limits<size_t>::max() -
IntCast<size_t>(pos()))) {
return FailOverflow();
}
if (!uses_secondary_buffer()) {
const size_t cursor_index = IntCast<size_t>(pos());
const size_t new_cursor_index = cursor_index + src.size();
if (cursor_index == 0) {
// Allocate the first block directly in `dest`. It is possible that it
// will not need to be copied if it turns out to be the only block,
// although this decision might cause it to remain wasteful if less data
// are written than space requested.
StringReserveAmortized(dest, new_cursor_index);
}
if (new_cursor_index <= dest.capacity()) {
if (ABSL_PREDICT_FALSE(new_cursor_index <= dest.size())) {
std::memcpy(dest.data() + cursor_index, src.data(), src.size());
} else {
dest.erase(cursor_index);
dest.append(src);
}
GrowDestUnderCapacityAndMakeBuffer(dest, new_cursor_index);
return true;
}
const size_t prefix_size = dest.capacity() - cursor_index;
dest.erase(cursor_index);
dest.append(src.data(), prefix_size);
src.remove_prefix(prefix_size);
set_start_pos(cursor_index + prefix_size);
set_buffer();
written_size_ = 0;
} else {
SyncSecondaryBuffer();
}
move_start_pos(src.size());
secondary_buffer_.Append(src, options_);
MakeSecondaryBuffer();
return true;
}
bool StringWriterBase::WriteSlow(ExternalRef src) {
RIEGELI_ASSERT_LT(UnsignedMin(available(), kMaxBytesToCopy), src.size())
<< "Failed precondition of Writer::WriteSlow(ExternalRef): "
"enough space available, use Write(ExternalRef) instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (ABSL_PREDICT_FALSE(src.size() > std::numeric_limits<size_t>::max() -
IntCast<size_t>(pos()))) {
return FailOverflow();
}
if (!uses_secondary_buffer()) {
const size_t cursor_index = IntCast<size_t>(pos());
const size_t new_cursor_index = cursor_index + src.size();
if (new_cursor_index <= dest.capacity()) {
if (ABSL_PREDICT_FALSE(new_cursor_index <= dest.size())) {
std::memcpy(dest.data() + cursor_index, src.data(), src.size());
} else {
dest.erase(cursor_index);
dest.append(absl::string_view(src));
}
GrowDestUnderCapacityAndMakeBuffer(dest, new_cursor_index);
return true;
}
set_start_pos(cursor_index);
set_buffer();
written_size_ = 0;
} else {
SyncSecondaryBuffer();
}
move_start_pos(src.size());
secondary_buffer_.Append(std::move(src), options_);
MakeSecondaryBuffer();
return true;
}
bool StringWriterBase::WriteSlow(const Chain& src) {
RIEGELI_ASSERT_LT(UnsignedMin(available(), kMaxBytesToCopy), src.size())
<< "Failed precondition of Writer::WriteSlow(Chain): "
"enough space available, use Write(Chain) instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (ABSL_PREDICT_FALSE(src.size() > std::numeric_limits<size_t>::max() -
IntCast<size_t>(pos()))) {
return FailOverflow();
}
if (!uses_secondary_buffer()) {
const size_t cursor_index = IntCast<size_t>(pos());
const size_t new_cursor_index = cursor_index + src.size();
if (new_cursor_index <= dest.capacity()) {
if (ABSL_PREDICT_FALSE(new_cursor_index <= dest.size())) {
src.CopyTo(dest.data() + cursor_index);
} else {
dest.erase(cursor_index);
src.AppendTo(dest);
}
GrowDestUnderCapacityAndMakeBuffer(dest, new_cursor_index);
return true;
}
set_start_pos(cursor_index);
set_buffer();
written_size_ = 0;
} else {
SyncSecondaryBuffer();
}
move_start_pos(src.size());
secondary_buffer_.Append(src, options_);
MakeSecondaryBuffer();
return true;
}
bool StringWriterBase::WriteSlow(Chain&& src) {
RIEGELI_ASSERT_LT(UnsignedMin(available(), kMaxBytesToCopy), src.size())
<< "Failed precondition of Writer::WriteSlow(Chain&&): "
"enough space available, use Write(Chain) instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (ABSL_PREDICT_FALSE(src.size() > std::numeric_limits<size_t>::max() -
IntCast<size_t>(pos()))) {
return FailOverflow();
}
if (!uses_secondary_buffer()) {
const size_t cursor_index = IntCast<size_t>(pos());
const size_t new_cursor_index = cursor_index + src.size();
if (new_cursor_index <= dest.capacity()) {
if (ABSL_PREDICT_FALSE(new_cursor_index <= dest.size())) {
src.CopyTo(dest.data() + cursor_index);
} else {
dest.erase(cursor_index);
std::move(src).AppendTo(dest);
}
GrowDestUnderCapacityAndMakeBuffer(dest, new_cursor_index);
return true;
}
set_start_pos(cursor_index);
set_buffer();
written_size_ = 0;
} else {
SyncSecondaryBuffer();
}
move_start_pos(src.size());
secondary_buffer_.Append(std::move(src), options_);
MakeSecondaryBuffer();
return true;
}
bool StringWriterBase::WriteSlow(const absl::Cord& src) {
RIEGELI_ASSERT_LT(UnsignedMin(available(), kMaxBytesToCopy), src.size())
<< "Failed precondition of Writer::WriteSlow(Cord): "
"enough space available, use Write(Cord) instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (ABSL_PREDICT_FALSE(src.size() > std::numeric_limits<size_t>::max() -
IntCast<size_t>(pos()))) {
return FailOverflow();
}
if (!uses_secondary_buffer()) {
const size_t cursor_index = IntCast<size_t>(pos());
const size_t new_cursor_index = cursor_index + src.size();
if (new_cursor_index <= dest.capacity()) {
if (ABSL_PREDICT_FALSE(new_cursor_index <= dest.size())) {
cord_internal::CopyCordToArray(src, dest.data() + cursor_index);
} else {
dest.erase(cursor_index);
absl::AppendCordToString(src, &dest);
}
GrowDestUnderCapacityAndMakeBuffer(dest, new_cursor_index);
return true;
}
set_start_pos(cursor_index);
set_buffer();
written_size_ = 0;
} else {
SyncSecondaryBuffer();
}
move_start_pos(src.size());
secondary_buffer_.Append(src, options_);
MakeSecondaryBuffer();
return true;
}
bool StringWriterBase::WriteSlow(absl::Cord&& src) {
RIEGELI_ASSERT_LT(UnsignedMin(available(), kMaxBytesToCopy), src.size())
<< "Failed precondition of Writer::WriteSlow(Cord&&): "
"enough space available, use Write(Cord&&) instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (ABSL_PREDICT_FALSE(src.size() > std::numeric_limits<size_t>::max() -
IntCast<size_t>(pos()))) {
return FailOverflow();
}
if (!uses_secondary_buffer()) {
const size_t cursor_index = IntCast<size_t>(pos());
const size_t new_cursor_index = cursor_index + src.size();
if (new_cursor_index <= dest.capacity()) {
if (ABSL_PREDICT_FALSE(new_cursor_index <= dest.size())) {
cord_internal::CopyCordToArray(src, dest.data() + cursor_index);
} else {
dest.erase(cursor_index);
absl::AppendCordToString(src, &dest);
}
GrowDestUnderCapacityAndMakeBuffer(dest, new_cursor_index);
return true;
}
set_start_pos(cursor_index);
set_buffer();
written_size_ = 0;
} else {
SyncSecondaryBuffer();
}
move_start_pos(src.size());
secondary_buffer_.Append(std::move(src), options_);
MakeSecondaryBuffer();
return true;
}
bool StringWriterBase::WriteSlow(ByteFill src) {
RIEGELI_ASSERT_LT(UnsignedMin(available(), kMaxBytesToCopy), src.size())
<< "Failed precondition of Writer::WriteSlow(ByteFill): "
"enough space available, use Write(ByteFill) instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (ABSL_PREDICT_FALSE(src.size() > std::numeric_limits<size_t>::max() -
IntCast<size_t>(pos()))) {
return FailOverflow();
}
if (!uses_secondary_buffer()) {
const size_t cursor_index = IntCast<size_t>(pos());
const size_t new_cursor_index = cursor_index + IntCast<size_t>(src.size());
if (new_cursor_index <= dest.capacity()) {
if (ABSL_PREDICT_FALSE(new_cursor_index <= dest.size())) {
std::memset(dest.data() + cursor_index, src.fill(),
IntCast<size_t>(src.size()));
} else {
dest.erase(cursor_index);
dest.append(IntCast<size_t>(src.size()), src.fill());
}
GrowDestUnderCapacityAndMakeBuffer(dest, new_cursor_index);
return true;
}
set_start_pos(cursor_index);
set_buffer();
written_size_ = 0;
} else {
SyncSecondaryBuffer();
}
move_start_pos(src.size());
src.AppendTo(secondary_buffer_, options_);
MakeSecondaryBuffer();
return true;
}
bool StringWriterBase::FlushImpl(FlushType flush_type) {
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
const size_t cursor_index = IntCast<size_t>(pos());
if (!uses_secondary_buffer()) {
dest.erase(used_size());
} else {
SyncSecondaryBuffer();
dest.erase(used_dest_size());
std::move(secondary_buffer_).AppendTo(dest);
secondary_buffer_.Clear();
}
set_buffer();
set_start_pos(cursor_index);
return true;
}
bool StringWriterBase::SeekSlow(Position new_pos) {
RIEGELI_ASSERT_NE(new_pos, pos())
<< "Failed precondition of Writer::SeekSlow(): "
"position unchanged, use Seek() instead";
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (new_pos > pos()) {
if (ABSL_PREDICT_FALSE(uses_secondary_buffer())) return false;
if (ABSL_PREDICT_FALSE(new_pos > used_size())) {
MakeDestBuffer(dest, used_size());
return false;
}
} else {
if (uses_secondary_buffer()) {
SyncSecondaryBuffer();
dest.erase(used_dest_size());
std::move(secondary_buffer_).AppendTo(dest);
secondary_buffer_.Clear();
}
written_size_ = used_size();
}
MakeDestBuffer(dest, IntCast<size_t>(new_pos));
return true;
}
std::optional<Position> StringWriterBase::SizeImpl() {
if (ABSL_PREDICT_FALSE(!ok())) return std::nullopt;
return used_size();
}
bool StringWriterBase::TruncateImpl(Position new_size) {
if (ABSL_PREDICT_FALSE(!ok())) return false;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (new_size > pos()) {
if (ABSL_PREDICT_FALSE(uses_secondary_buffer())) return false;
if (ABSL_PREDICT_FALSE(new_size > used_size())) {
MakeDestBuffer(dest, used_size());
return false;
}
} else if (new_size > limit_pos() - secondary_buffer_.size()) {
secondary_buffer_.RemoveSuffix(
IntCast<size_t>(limit_pos()) - IntCast<size_t>(new_size), options_);
set_start_pos(new_size);
set_buffer();
return true;
} else {
secondary_buffer_.Clear();
}
written_size_ = 0;
MakeDestBuffer(dest, IntCast<size_t>(new_size));
return true;
}
Reader* StringWriterBase::ReadModeImpl(Position initial_pos) {
if (ABSL_PREDICT_FALSE(!ok())) return nullptr;
std::string& dest = *DestString();
RIEGELI_ASSERT_GE(dest.size(), limit_pos() - secondary_buffer_.size())
<< "StringWriter destination changed unexpectedly";
if (uses_secondary_buffer()) {
SyncSecondaryBuffer();
dest.erase(used_dest_size());
std::move(secondary_buffer_).AppendTo(dest);
secondary_buffer_.Clear();
}
StringReader<>* const reader =
associated_reader_.ResetReader(dest.data(), used_size());
reader->Seek(initial_pos);
return reader;
}
} // namespace riegeli