| // 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()) { | |
| PROTOBUF_DEBUG_COUNTER("MicroString_Set.MicroRep").IncLog(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()) { | |
| PROTOBUF_DEBUG_COUNTER("MicroString_Set.LargeRep") | |
| .IncLog(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()) { | |
| PROTOBUF_DEBUG_COUNTER("MicroString_Set.String").IncLog(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()); | |
| PROTOBUF_DEBUG_COUNTER("MicroString_Set.Inline").IncBucket(data.size()); | |
| if (!data.empty()) { | |
| memmove(inline_head(), data.data(), data.size()); | |
| } | |
| return; | |
| } | |
| // Try MicroString rep first. | |
| if (data.size() <= kMaxMicroRepCapacity) { | |
| PROTOBUF_DEBUG_COUNTER("MicroString_Set.MicroRep").IncLog(data.size()); | |
| 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()); | |
| PROTOBUF_DEBUG_COUNTER("MicroString_Set.LargeRep").IncLog(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" |