| // Copyright 2025 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef QUICHE_QUIC_CORE_QUIC_INLINED_STRING_VIEW_H_ |
| #define QUICHE_QUIC_CORE_QUIC_INLINED_STRING_VIEW_H_ |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <type_traits> |
| |
| #include "absl/numeric/bits.h" |
| #include "absl/strings/string_view.h" |
| #include "quiche/common/platform/api/quiche_export.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| |
| namespace quic { |
| |
| // QuicInlinedStringView<kSize> is a class that is similar to absl::string_view, |
| // with a notable distinction that it can inline up to `kSize` characters |
| // (between 15 and 127). |
| // |
| // Important use notes: |
| // - QuicInlinedStringView makes no assumptions about ownership of non-inlined |
| // data; its primary purpose is to be a building block for other data |
| // structures. |
| // - Unlike a regular string_view, the data pointer for QuicInlinedStringView |
| // will start pointing to a different location if inlined. |
| // - The string will be inlined iff its size is strictly below kSize; this is |
| // a guaranteeed API behavior. |
| template <size_t kSize> |
| class QUICHE_NO_EXPORT QuicInlinedStringView { |
| public: |
| // The largest size of a string that can be inlined. |
| static constexpr size_t kMaxInlinedSize = kSize - 1; |
| |
| static_assert(kSize >= 16); |
| static_assert(kSize < 255); |
| |
| QuicInlinedStringView() { clear(); } |
| explicit QuicInlinedStringView(absl::string_view source) { |
| // Special-case empty strings, since memcpy has weird edge case behaviors |
| // around null pointers. |
| if (source.empty()) { |
| clear(); |
| return; |
| } |
| |
| QUICHE_DCHECK_EQ(source.size() & (~kLengthMask), 0); |
| if (source.size() > kMaxInlinedSize) { |
| ViewRep rep; |
| rep.data = source.data(); |
| rep.size = source.size(); |
| memcpy(&data_, &rep, sizeof(rep)); |
| set_last_byte(kNotInlinedMarker); |
| return; |
| } |
| memcpy(data_, source.data(), source.size()); |
| set_last_byte(source.size()); |
| } |
| |
| // QuicInlinedStringView is trivially copyable and assignable; see, however, |
| // the warning above regarding the pointer stability. |
| QuicInlinedStringView(const QuicInlinedStringView&) = default; |
| QuicInlinedStringView(QuicInlinedStringView&&) = default; |
| QuicInlinedStringView& operator=(const QuicInlinedStringView&) = default; |
| QuicInlinedStringView& operator=(QuicInlinedStringView&&) = default; |
| |
| // Returns true if the string is inlined into the view. |
| bool IsInlined() const { return last_byte() != kNotInlinedMarker; } |
| |
| // string_view-compatible API. |
| const char* data() const { |
| if (!IsInlined()) { |
| return view_rep_data(); |
| } |
| return last_byte() == 0 ? nullptr : data_; |
| } |
| size_t size() const { |
| return IsInlined() ? last_byte() : (view_rep_size() & kLengthMask); |
| } |
| bool empty() const { return size() == 0; } |
| |
| absl::string_view view() const { return absl::string_view(data(), size()); } |
| void clear() { set_last_byte(0); } |
| |
| private: |
| // On 64-bit platforms, we want to support kSize of 16, so we take the top |
| // byte of the length, and use it for inlining. On 32-bit platforms, that |
| // would limit us to 24-bit lengths, which is too short, so we just require |
| // the length to not overlap with the last byte (by setting minimum size to 16 |
| // bytes), and no masking is necessary. |
| static_assert(sizeof(size_t) == 4 || sizeof(size_t) == 8); |
| // Here, we have to spell out kLengthMask in terms of numeric_limits, since |
| // specifying the value directly would fail compile-time overflow check on |
| // 32-bit platforms. |
| static constexpr size_t kLengthMask = |
| sizeof(size_t) > 4 ? ((std::numeric_limits<size_t>::max() << 8) >> 8) |
| : 0xffffffff; |
| static constexpr size_t kNotInlinedMarker = 0xff; |
| |
| #if defined(__x86_64__) |
| static_assert(kLengthMask == 0x00ffffffffffffff); |
| #endif |
| |
| // Representation of the string view when it is not inlined. |
| struct ViewRep { |
| const char* data; |
| size_t size; |
| }; |
| static_assert(sizeof(ViewRep) <= kSize); |
| static_assert(absl::endian::native == absl::endian::little); |
| |
| // Accessors for ViewRep; necessary to work around C++ strict aliasing |
| // limitations. Clang will turn this into direct field access at `-O1`. |
| const char* view_rep_data() const { |
| ViewRep rep; |
| memcpy(&rep, data_, sizeof(rep)); |
| return rep.data; |
| } |
| size_t view_rep_size() const { |
| ViewRep rep; |
| memcpy(&rep, data_, sizeof(rep)); |
| return rep.size; |
| } |
| // Those casts should be valid as long as uint8_t is unsigned char. |
| const uint8_t& last_byte() const { |
| return *reinterpret_cast<const uint8_t*>(data_ + kSize - 1); |
| } |
| void set_last_byte(uint8_t byte) { |
| *reinterpret_cast<uint8_t*>(data_ + kSize - 1) = byte; |
| } |
| |
| // Internal representation: if the string is inlined, the last byte is the |
| // length of the inlined string, and all of the preceding bytes are the |
| // inlined string. If the string is not inlined, the `ViewRep` is at the |
| // front, and 0xff is at the end (on 64-bit platforms, those may overlap). |
| alignas(ViewRep) char data_[kSize]; |
| }; |
| |
| static_assert(std::is_trivially_destructible_v<QuicInlinedStringView<16>>); |
| static_assert(std::is_trivially_copyable_v<QuicInlinedStringView<16>>); |
| |
| } // namespace quic |
| |
| #endif // QUICHE_QUIC_CORE_QUIC_INLINED_STRING_VIEW_H_ |