| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| |
| #include "google/protobuf/micro_string.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <cstring> |
| #include <utility> |
| |
| #include "absl/log/absl_check.h" |
| #include "absl/strings/string_view.h" |
| #include "google/protobuf/arena.h" |
| #include "google/protobuf/arena_align.h" |
| #include "google/protobuf/port.h" |
| |
| // must be last: |
| #include "google/protobuf/port_def.inc" |
| |
| namespace google { |
| namespace protobuf { |
| namespace internal { |
| |
| MicroString MicroString::MakeDefaultValuePrototype( |
| absl::string_view default_value) { |
| if (default_value.empty()) return MicroString(); |
| return MicroString(*new auto(MakeUnownedPayload(default_value))); |
| } |
| |
| void MicroString::DestroyDefaultValuePrototype() { |
| if (is_inline()) { |
| // The empty case |
| return; |
| } |
| // This is a prototype dynamic object so we actually own the unowned payload. |
| ABSL_DCHECK(is_large_rep()); |
| ABSL_DCHECK_EQ(+large_rep_kind(), +kUnowned); |
| ::operator delete(large_rep()); |
| } |
| |
| void MicroString::DestroySlow() { |
| if (is_micro_rep()) { |
| internal::SizedDelete(micro_rep(), MicroRepSize(micro_rep()->capacity)); |
| return; |
| } |
| |
| switch (large_rep_kind()) { |
| case kOwned: |
| internal::SizedDelete(large_rep(), OwnedRepSize(large_rep()->capacity)); |
| break; |
| case kString: |
| delete string_rep(); |
| break; |
| case kAlias: |
| delete large_rep(); |
| break; |
| case kUnowned: |
| // Nothing to destroy. |
| break; |
| } |
| } |
| |
| void MicroString::ClearSlow() { |
| if (is_micro_rep()) { |
| micro_rep()->ChangeSize(0); |
| return; |
| } |
| |
| switch (large_rep_kind()) { |
| case kOwned: |
| large_rep()->ChangeSize(0); |
| return; |
| case kString: { |
| auto* rep = string_rep(); |
| rep->str.clear(); |
| rep->ResetBase(); |
| return; |
| } |
| case kAlias: { |
| // We have a large rep we can't really use much. |
| // Transform it into a micro rep to use the space for something. |
| MicroRep* rep = reinterpret_cast<MicroRep*>(large_rep()); |
| rep->capacity = sizeof(LargeRep) - sizeof(MicroRep); |
| rep->SetInitialSize(0); |
| rep_ = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(rep) + |
| kIsMicroRepTag); |
| return; |
| } |
| case kUnowned: |
| // We don't own any memory, so just reset to default. |
| InitDefault(); |
| break; |
| } |
| } |
| |
| void MicroString::SetFromOtherSlow(const MicroString& other, Arena* arena, |
| size_t inline_capacity) { |
| // Unowned property gets propagated, even if we have a rep already. |
| if (other.is_large_rep() && other.large_rep_kind() == kUnowned) { |
| if (arena == nullptr) Destroy(); |
| rep_ = other.rep_; |
| return; |
| } |
| SetImpl(other.Get(), arena, inline_capacity); |
| } |
| |
| MicroString::MicroRep* MicroString::AllocateMicroRep(size_t size, |
| Arena* arena) { |
| MicroRep* h; |
| size_t capacity = size; |
| if (arena == nullptr) { |
| size_t requested_size = ArenaAlignDefault::Ceil(MicroRepSize(capacity)); |
| const internal::SizedPtr alloc = internal::AllocateAtLeast(requested_size); |
| // Maybe we rounded up too much. |
| capacity = std::min(kMaxMicroRepCapacity, alloc.n - sizeof(MicroRep)); |
| // Verify that the size we are going to free later is at least what we asked |
| // for. |
| ABSL_DCHECK_LE(requested_size, MicroRepSize(capacity)); |
| h = reinterpret_cast<MicroRep*>(alloc.p); |
| } else { |
| capacity = |
| ArenaAlignDefault::Ceil(capacity + sizeof(MicroRep)) - sizeof(MicroRep); |
| capacity = std::min(kMaxMicroRepCapacity, capacity); |
| auto* alloc = arena->AllocateAligned(MicroRepSize(capacity)); |
| h = reinterpret_cast<MicroRep*>(alloc); |
| } |
| h->capacity = capacity; |
| h->SetInitialSize(size); |
| |
| rep_ = |
| reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(h) + kIsMicroRepTag); |
| ABSL_DCHECK(is_micro_rep()); |
| |
| return h; |
| } |
| |
| MicroString::LargeRep* MicroString::AllocateOwnedRep(size_t size, |
| Arena* arena) { |
| size_t capacity = size; |
| LargeRep* h; |
| ABSL_DCHECK_GE(capacity, kOwned); |
| if (arena == nullptr) { |
| const internal::SizedPtr alloc = internal::AllocateAtLeast( |
| ArenaAlignDefault::Ceil(OwnedRepSize(capacity))); |
| capacity = alloc.n - sizeof(LargeRep); |
| h = reinterpret_cast<LargeRep*>(alloc.p); |
| } else { |
| size_t alloc_size = ArenaAlignDefault::Ceil(OwnedRepSize(capacity)); |
| capacity = alloc_size - sizeof(LargeRep); |
| auto* alloc = arena->AllocateAligned(alloc_size); |
| h = reinterpret_cast<LargeRep*>(alloc); |
| } |
| |
| rep_ = |
| reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(h) | kIsLargeRepTag); |
| h->capacity = capacity; |
| h->payload = h->owned_head(); |
| h->SetInitialSize(size); |
| |
| ABSL_DCHECK_EQ(+large_rep_kind(), +kOwned); |
| |
| return h; |
| } |
| |
| MicroString::StringRep* MicroString::AllocateStringRep(Arena* arena) { |
| StringRep* h = Arena::Create<StringRep>(arena); |
| h->capacity = kString; |
| rep_ = |
| reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(h) | kIsLargeRepTag); |
| ABSL_DCHECK_EQ(+large_rep_kind(), +kString); |
| return h; |
| } |
| |
| void MicroString::SetImpl(absl::string_view data, Arena* arena, |
| size_t inline_capacity) { |
| // Reuse space if possible. |
| if (is_micro_rep()) { |
| auto* h = micro_rep(); |
| if (data.empty()) { |
| h->ChangeSize(0); |
| return; |
| } else if (h->capacity >= data.size()) { |
| // We unpoison the buffer first, memmove, then repoison to the new size. |
| // We can't poison to the new size first because the input data might be |
| // aliasing the previously allowed part of `this`. |
| h->Unpoison(); |
| memmove(h->data(), data.data(), data.size()); |
| h->ChangeSize(data.size()); |
| return; |
| } |
| if (arena == nullptr) { |
| DestroySlow(); |
| } |
| } else if (is_large_rep()) { |
| switch (large_rep_kind()) { |
| case kOwned: { |
| auto* h = large_rep(); |
| if (data.empty()) { |
| h->ChangeSize(0); |
| return; |
| } else if (h->capacity >= data.size()) { |
| h->Unpoison(); |
| memmove(h->payload, data.data(), data.size()); |
| h->ChangeSize(data.size()); |
| return; |
| } |
| break; |
| } |
| case kString: { |
| auto* h = string_rep(); |
| if (h->str.capacity() >= data.size()) { |
| h->str.assign(data.data(), data.size()); |
| h->ResetBase(); |
| return; |
| } |
| break; |
| } |
| case kAlias: |
| case kUnowned: |
| // No capacity to reuse. |
| break; |
| } |
| if (arena == nullptr) { |
| DestroySlow(); |
| } |
| } |
| |
| // If we fit in the inline space, use it. |
| if (data.size() <= inline_capacity) { |
| set_inline_size(data.size()); |
| if (!data.empty()) { |
| memmove(inline_head(), data.data(), data.size()); |
| } |
| return; |
| } |
| |
| // Try MicroString rep first. |
| if (data.size() <= kMaxMicroRepCapacity) { |
| MicroRep* h = AllocateMicroRep(data.size(), arena); |
| memcpy(h->data(), data.data(), data.size()); |
| return; |
| } |
| |
| // Input is too big for MicroString, use the large large_rep representation. |
| LargeRep* h = AllocateOwnedRep(data.size(), arena); |
| memcpy(h->payload, data.data(), data.size()); |
| } |
| |
| void MicroString::SetAlias(absl::string_view data, Arena* arena, |
| size_t inline_capacity) { |
| // If we already have an alias, reuse the block. |
| if (is_large_rep() && large_rep_kind() == kAlias) { |
| auto* h = large_rep(); |
| h->SetExternalBuffer(data); |
| return; |
| } |
| // If we can fit in the inline rep, avoid allocating memory. |
| if (data.size() <= inline_capacity) { |
| return Set(data, arena, inline_capacity); |
| } |
| |
| LargeRep* h; |
| if (!is_large_rep() || large_rep_kind() != kAlias) { |
| if (arena == nullptr) { |
| Destroy(); |
| } |
| |
| h = Arena::Create<LargeRep>(arena); |
| h->capacity = kAlias; |
| rep_ = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(h) | |
| kIsLargeRepTag); |
| } else { |
| h = large_rep(); |
| } |
| h->SetExternalBuffer(data); |
| ABSL_DCHECK_EQ(+large_rep_kind(), +kAlias); |
| } |
| |
| void MicroString::SetString(std::string&& data, Arena* arena, |
| size_t inline_capacity) { |
| if (data.size() <= std::max(inline_capacity, size_t{32})) { |
| // Just copy the data. The overhead of the string is not worth it. |
| return Set(data, arena, inline_capacity); |
| } |
| |
| StringRep* h; |
| if (!is_large_rep() || large_rep_kind() != kString) { |
| if (arena == nullptr) Destroy(); |
| h = AllocateStringRep(arena); |
| } else { |
| h = string_rep(); |
| } |
| |
| h->str = std::move(data); |
| h->ResetBase(); |
| } |
| |
| void MicroString::SetUnowned(const UnownedPayload& unowned_input, |
| Arena* arena) { |
| if (arena == nullptr) Destroy(); |
| rep_ = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(&unowned_input) | |
| kIsLargeRepTag); |
| ABSL_DCHECK_EQ(+large_rep_kind(), +kUnowned); |
| } |
| |
| void MicroString::ClearToDefault(const UnownedPayload& unowned_input, |
| Arena* arena) { |
| auto input = unowned_input.get(); |
| if (arena != nullptr && Capacity() >= input.size()) { |
| // If we are in an arena and the input fits in the existing capacity, use |
| // that instead. |
| Set(input, arena); |
| } else { |
| SetUnowned(unowned_input, arena); |
| } |
| } |
| |
| void MicroString::ClearToDefault(const MicroString& other, Arena* arena) { |
| auto input = other.Get(); |
| if (arena != nullptr && Capacity() >= input.size()) { |
| // If we are in an arena and the input fits in the existing capacity, use |
| // that instead. |
| Set(input, arena); |
| } else { |
| // Otherwise, set to the unowned instance. |
| ABSL_DCHECK_EQ(+other.large_rep_kind(), +kUnowned); |
| if (arena == nullptr) Destroy(); |
| rep_ = other.rep_; |
| } |
| } |
| |
| size_t MicroString::Capacity() const { |
| if (is_inline()) { |
| return kInlineCapacity; |
| } else if (is_micro_rep()) { |
| return micro_rep()->capacity; |
| } else { |
| switch (large_rep_kind()) { |
| case kOwned: |
| return large_rep()->capacity; |
| case kString: |
| return string_rep()->str.capacity(); |
| case kAlias: |
| case kUnowned: |
| return 0; |
| } |
| } |
| Unreachable(); |
| } |
| |
| size_t MicroString::SpaceUsedExcludingSelfLong() const { |
| if (is_inline()) return 0; |
| if (is_micro_rep()) return MicroRepSize(micro_rep()->capacity); |
| switch (large_rep_kind()) { |
| case kOwned: |
| return sizeof(LargeRep) + large_rep()->capacity; |
| case kString: |
| return sizeof(StringRep) + |
| StringSpaceUsedExcludingSelfLong(string_rep()->str); |
| case kAlias: |
| return sizeof(LargeRep); |
| case kUnowned: |
| return 0; |
| } |
| Unreachable(); |
| } |
| |
| } // namespace internal |
| } // namespace protobuf |
| } // namespace google |
| |
| #include "google/protobuf/port_undef.inc" |