/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ProfileBufferEntrySerialization_h #define ProfileBufferEntrySerialization_h #include "mozilla/Assertions.h" #include "mozilla/leb128iterator.h" #include "mozilla/Likely.h" #include "mozilla/Maybe.h" #include "mozilla/ProfileBufferIndex.h" #include "mozilla/Span.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/Unused.h" #include "mozilla/Variant.h" #include #include namespace mozilla { class ProfileBufferEntryWriter; // Iterator-like class used to read from an entry. // An entry may be split in two memory segments (e.g., the ends of a ring // buffer, or two chunks of a chunked buffer); it doesn't deal with this // underlying buffer, but only with one or two spans pointing at the space // where the entry lives. class ProfileBufferEntryReader { public: using Byte = uint8_t; using Length = uint32_t; using SpanOfConstBytes = Span; // Class to be specialized for types to be read from a profile buffer entry. // See common specializations at the bottom of this header. // The following static functions must be provided: // static void ReadInto(EntryReader aER&, T& aT) // { // /* Call `aER.ReadX(...)` function to deserialize into aT, be sure to // read exactly `Bytes(aT)`! */ // } // static T Read(EntryReader& aER) { // /* Call `aER.ReadX(...)` function to deserialize and return a `T`, be // sure to read exactly `Bytes(returned value)`! */ // } template struct Deserializer; ProfileBufferEntryReader() = default; // Reader over one Span. ProfileBufferEntryReader(SpanOfConstBytes aSpan, ProfileBufferBlockIndex aCurrentBlockIndex, ProfileBufferBlockIndex aNextBlockIndex) : mCurrentSpan(aSpan), mNextSpanOrEmpty(aSpan.Last(0)), mCurrentBlockIndex(aCurrentBlockIndex), mNextBlockIndex(aNextBlockIndex) { // 2nd internal Span points at the end of the 1st internal Span, to enforce // invariants. CheckInvariants(); } // Reader over two Spans, the second one must not be empty. ProfileBufferEntryReader(SpanOfConstBytes aSpanHead, SpanOfConstBytes aSpanTail, ProfileBufferBlockIndex aCurrentBlockIndex, ProfileBufferBlockIndex aNextBlockIndex) : mCurrentSpan(aSpanHead), mNextSpanOrEmpty(aSpanTail), mCurrentBlockIndex(aCurrentBlockIndex), mNextBlockIndex(aNextBlockIndex) { MOZ_RELEASE_ASSERT(!mNextSpanOrEmpty.IsEmpty()); if (MOZ_UNLIKELY(mCurrentSpan.IsEmpty())) { // First span is already empty, skip it. mCurrentSpan = mNextSpanOrEmpty; mNextSpanOrEmpty = mNextSpanOrEmpty.Last(0); } CheckInvariants(); } // Allow copying, which is needed when used as an iterator in some std // functions (e.g., string assignment), and to occasionally backtrack. // Be aware that the main profile buffer APIs give a reference to an entry // reader, and expect that reader to advance to the end of the entry, so don't // just advance copies! ProfileBufferEntryReader(const ProfileBufferEntryReader&) = default; ProfileBufferEntryReader& operator=(const ProfileBufferEntryReader&) = default; // Don't =default moving, as it doesn't bring any benefit in this class. [[nodiscard]] Length RemainingBytes() const { return mCurrentSpan.LengthBytes() + mNextSpanOrEmpty.LengthBytes(); } void SetRemainingBytes(Length aBytes) { MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); if (aBytes <= mCurrentSpan.LengthBytes()) { mCurrentSpan = mCurrentSpan.First(aBytes); mNextSpanOrEmpty = mCurrentSpan.Last(0); } else { mNextSpanOrEmpty = mNextSpanOrEmpty.First(aBytes - mCurrentSpan.LengthBytes()); } } [[nodiscard]] ProfileBufferBlockIndex CurrentBlockIndex() const { return mCurrentBlockIndex; } [[nodiscard]] ProfileBufferBlockIndex NextBlockIndex() const { return mNextBlockIndex; } // Create a reader of size zero, pointing at aOffset past the current position // of this Reader, so it can be used as end iterator. [[nodiscard]] ProfileBufferEntryReader EmptyIteratorAtOffset( Length aOffset) const { MOZ_RELEASE_ASSERT(aOffset <= RemainingBytes()); if (MOZ_LIKELY(aOffset < mCurrentSpan.LengthBytes())) { // aOffset is before the end of mCurrentSpan. return ProfileBufferEntryReader(mCurrentSpan.Subspan(aOffset, 0), mCurrentBlockIndex, mNextBlockIndex); } // aOffset is right at the end of mCurrentSpan, or inside mNextSpanOrEmpty. return ProfileBufferEntryReader( mNextSpanOrEmpty.Subspan(aOffset - mCurrentSpan.LengthBytes(), 0), mCurrentBlockIndex, mNextBlockIndex); } // Be like a limited input iterator, with only `*`, prefix-`++`, `==`, `!=`. // These definitions are expected by std functions, to recognize this as an // iterator. See https://en.cppreference.com/w/cpp/iterator/iterator_traits using difference_type = std::make_signed_t; using value_type = Byte; using pointer = const Byte*; using reference = const Byte&; using iterator_category = std::input_iterator_tag; [[nodiscard]] const Byte& operator*() { // Assume the caller will read from the returned reference (and not just // take the address). MOZ_RELEASE_ASSERT(mCurrentSpan.LengthBytes() >= 1); return *(mCurrentSpan.Elements()); } ProfileBufferEntryReader& operator++() { MOZ_RELEASE_ASSERT(mCurrentSpan.LengthBytes() >= 1); if (MOZ_LIKELY(mCurrentSpan.LengthBytes() > 1)) { // More than 1 byte left in mCurrentSpan, just eat it. mCurrentSpan = mCurrentSpan.From(1); } else { // mCurrentSpan will be empty, move mNextSpanOrEmpty to mCurrentSpan. mCurrentSpan = mNextSpanOrEmpty; mNextSpanOrEmpty = mNextSpanOrEmpty.Last(0); } CheckInvariants(); return *this; } ProfileBufferEntryReader& operator+=(Length aBytes) { MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); if (MOZ_LIKELY(aBytes <= mCurrentSpan.LengthBytes())) { // All bytes are in mCurrentSpan. // Update mCurrentSpan past the read bytes. mCurrentSpan = mCurrentSpan.From(aBytes); if (mCurrentSpan.IsEmpty() && !mNextSpanOrEmpty.IsEmpty()) { // Don't leave mCurrentSpan empty, move non-empty mNextSpanOrEmpty into // mCurrentSpan. mCurrentSpan = mNextSpanOrEmpty; mNextSpanOrEmpty = mNextSpanOrEmpty.Last(0); } } else { // mCurrentSpan does not hold enough bytes. // This should only happen at most once: Only for double spans, and when // data crosses the gap. const Length tail = aBytes - static_cast(mCurrentSpan.LengthBytes()); // Move mNextSpanOrEmpty to mCurrentSpan, past the data. So the next call // will go back to the true case above. mCurrentSpan = mNextSpanOrEmpty.From(tail); mNextSpanOrEmpty = mNextSpanOrEmpty.Last(0); } CheckInvariants(); return *this; } [[nodiscard]] bool operator==(const ProfileBufferEntryReader& aOther) const { return mCurrentSpan.Elements() == aOther.mCurrentSpan.Elements(); } [[nodiscard]] bool operator!=(const ProfileBufferEntryReader& aOther) const { return mCurrentSpan.Elements() != aOther.mCurrentSpan.Elements(); } // Read an unsigned LEB128 number and move iterator ahead. template [[nodiscard]] T ReadULEB128() { return ::mozilla::ReadULEB128(*this); } // This struct points at a number of bytes through either one span, or two // separate spans (in the rare cases when it is split between two chunks). // So the possibilities are: // - Totally empty: { [] [] } // - First span is not empty: { [content] [] } (Most common case.) // - Both spans are not empty: { [cont] [ent] } // But something like { [] [content] } is not possible. // // Recommended usage patterns: // - Call a utility function like `CopyBytesTo` if you always need to copy the // data to an outside buffer, e.g., to deserialize an aligned object. // - Access both spans one after the other; Note that the second one may be // empty; and the fist could be empty as well if there is no data at all. // - Check is the second span is empty, in which case you only need to read // the first one; and since its part of a chunk, it may be directly passed // as an unaligned pointer or reference, thereby saving one copy. But // remember to always handle the double-span case as well. // // Reminder: An empty span still has a non-null pointer, so it's safe to use // with functions like memcpy. struct DoubleSpanOfConstBytes { SpanOfConstBytes mFirstOrOnly; SpanOfConstBytes mSecondOrEmpty; void CheckInvariants() const { MOZ_ASSERT(mFirstOrOnly.IsEmpty() ? mSecondOrEmpty.IsEmpty() : true, "mSecondOrEmpty should not be the only span to contain data"); } DoubleSpanOfConstBytes() : mFirstOrOnly(), mSecondOrEmpty() { CheckInvariants(); } DoubleSpanOfConstBytes(const Byte* aOnlyPointer, size_t aOnlyLength) : mFirstOrOnly(aOnlyPointer, aOnlyLength), mSecondOrEmpty() { CheckInvariants(); } DoubleSpanOfConstBytes(const Byte* aFirstPointer, size_t aFirstLength, const Byte* aSecondPointer, size_t aSecondLength) : mFirstOrOnly(aFirstPointer, aFirstLength), mSecondOrEmpty(aSecondPointer, aSecondLength) { CheckInvariants(); } // Is there no data at all? [[nodiscard]] bool IsEmpty() const { // We only need to check the first span, because if it's empty, the second // one must be empty as well. return mFirstOrOnly.IsEmpty(); } // Total length (in bytes) pointed at by both spans. [[nodiscard]] size_t LengthBytes() const { return mFirstOrOnly.LengthBytes() + mSecondOrEmpty.LengthBytes(); } // Utility functions to copy all `LengthBytes()` to a given buffer. void CopyBytesTo(void* aDest) const { memcpy(aDest, mFirstOrOnly.Elements(), mFirstOrOnly.LengthBytes()); if (MOZ_UNLIKELY(!mSecondOrEmpty.IsEmpty())) { memcpy(static_cast(aDest) + mFirstOrOnly.LengthBytes(), mSecondOrEmpty.Elements(), mSecondOrEmpty.LengthBytes()); } } // If the second span is empty, only the first span may point at data. [[nodiscard]] bool IsSingleSpan() const { return mSecondOrEmpty.IsEmpty(); } }; // Get Span(s) to a sequence of bytes, see `DoubleSpanOfConstBytes` for usage. // Note that the reader location is *not* updated, do `+=` on it afterwards. [[nodiscard]] DoubleSpanOfConstBytes PeekSpans(Length aBytes) const { MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); if (MOZ_LIKELY(aBytes <= mCurrentSpan.LengthBytes())) { // All `aBytes` are in the current chunk, only one span is needed. return DoubleSpanOfConstBytes{mCurrentSpan.Elements(), aBytes}; } // Otherwise the first span covers then end of the current chunk, and the // second span starts in the next chunk. return DoubleSpanOfConstBytes{ mCurrentSpan.Elements(), mCurrentSpan.LengthBytes(), mNextSpanOrEmpty.Elements(), aBytes - mCurrentSpan.LengthBytes()}; } // Get Span(s) to a sequence of bytes, see `DoubleSpanOfConstBytes` for usage, // and move the reader forward. [[nodiscard]] DoubleSpanOfConstBytes ReadSpans(Length aBytes) { DoubleSpanOfConstBytes spans = PeekSpans(aBytes); (*this) += aBytes; return spans; } // Read a sequence of bytes, like memcpy. void ReadBytes(void* aDest, Length aBytes) { DoubleSpanOfConstBytes spans = ReadSpans(aBytes); MOZ_ASSERT(spans.LengthBytes() == aBytes); spans.CopyBytesTo(aDest); } template void ReadIntoObject(T& aObject) { Deserializer::ReadInto(*this, aObject); } // Read into one or more objects, sequentially. // `EntryReader::ReadIntoObjects()` with nothing is implicitly allowed, this // could be useful for generic programming. template void ReadIntoObjects(Ts&... aTs) { (ReadIntoObject(aTs), ...); } // Read data as an object and move iterator ahead. template [[nodiscard]] T ReadObject() { T ob = Deserializer::Read(*this); return ob; } private: friend class ProfileBufferEntryWriter; // Invariants: // - mCurrentSpan cannot be empty unless mNextSpanOrEmpty is also empty. So // mCurrentSpan always points at the next byte to read or the end. // - If mNextSpanOrEmpty is empty, it points at the end of mCurrentSpan. So // when reaching the end of mCurrentSpan, we can blindly move // mNextSpanOrEmpty to mCurrentSpan and keep the invariants. SpanOfConstBytes mCurrentSpan; SpanOfConstBytes mNextSpanOrEmpty; ProfileBufferBlockIndex mCurrentBlockIndex; ProfileBufferBlockIndex mNextBlockIndex; void CheckInvariants() const { MOZ_ASSERT(!mCurrentSpan.IsEmpty() || mNextSpanOrEmpty.IsEmpty()); MOZ_ASSERT(!mNextSpanOrEmpty.IsEmpty() || (mNextSpanOrEmpty == mCurrentSpan.Last(0))); } }; // Iterator-like class used to write into an entry. // An entry may be split in two memory segments (e.g., the ends of a ring // buffer, or two chunks of a chunked buffer); it doesn't deal with this // underlying buffer, but only with one or two spans pointing at the space // reserved for the entry. class ProfileBufferEntryWriter { public: using Byte = uint8_t; using Length = uint32_t; using SpanOfBytes = Span; // Class to be specialized for types to be written in an entry. // See common specializations at the bottom of this header. // The following static functions must be provided: // static Length Bytes(const T& aT) { // /* Return number of bytes that will be written. */ // } // static void Write(ProfileBufferEntryWriter& aEW, // const T& aT) { // /* Call `aEW.WriteX(...)` functions to serialize aT, be sure to write // exactly `Bytes(aT)` bytes! */ // } template struct Serializer; ProfileBufferEntryWriter() = default; ProfileBufferEntryWriter(SpanOfBytes aSpan, ProfileBufferBlockIndex aCurrentBlockIndex, ProfileBufferBlockIndex aNextBlockIndex) : mCurrentSpan(aSpan), mCurrentBlockIndex(aCurrentBlockIndex), mNextBlockIndex(aNextBlockIndex) {} ProfileBufferEntryWriter(SpanOfBytes aSpanHead, SpanOfBytes aSpanTail, ProfileBufferBlockIndex aCurrentBlockIndex, ProfileBufferBlockIndex aNextBlockIndex) : mCurrentSpan(aSpanHead), mNextSpanOrEmpty(aSpanTail), mCurrentBlockIndex(aCurrentBlockIndex), mNextBlockIndex(aNextBlockIndex) { // Either: // - mCurrentSpan is not empty, OR // - mNextSpanOrEmpty is empty if mNextSpanOrEmpty is empty as well. MOZ_RELEASE_ASSERT(!mCurrentSpan.IsEmpty() || mNextSpanOrEmpty.IsEmpty()); } // Disable copying and moving, so we can't have multiple writing heads. ProfileBufferEntryWriter(const ProfileBufferEntryWriter&) = delete; ProfileBufferEntryWriter& operator=(const ProfileBufferEntryWriter&) = delete; ProfileBufferEntryWriter(ProfileBufferEntryWriter&&) = delete; ProfileBufferEntryWriter& operator=(ProfileBufferEntryWriter&&) = delete; void Set() { mCurrentSpan = SpanOfBytes{}; mNextSpanOrEmpty = SpanOfBytes{}; mCurrentBlockIndex = nullptr; mNextBlockIndex = nullptr; } void Set(SpanOfBytes aSpan, ProfileBufferBlockIndex aCurrentBlockIndex, ProfileBufferBlockIndex aNextBlockIndex) { mCurrentSpan = aSpan; mNextSpanOrEmpty = SpanOfBytes{}; mCurrentBlockIndex = aCurrentBlockIndex; mNextBlockIndex = aNextBlockIndex; } void Set(SpanOfBytes aSpan0, SpanOfBytes aSpan1, ProfileBufferBlockIndex aCurrentBlockIndex, ProfileBufferBlockIndex aNextBlockIndex) { mCurrentSpan = aSpan0; mNextSpanOrEmpty = aSpan1; mCurrentBlockIndex = aCurrentBlockIndex; mNextBlockIndex = aNextBlockIndex; // Either: // - mCurrentSpan is not empty, OR // - mNextSpanOrEmpty is empty if mNextSpanOrEmpty is empty as well. MOZ_RELEASE_ASSERT(!mCurrentSpan.IsEmpty() || mNextSpanOrEmpty.IsEmpty()); } [[nodiscard]] Length RemainingBytes() const { return mCurrentSpan.LengthBytes() + mNextSpanOrEmpty.LengthBytes(); } [[nodiscard]] ProfileBufferBlockIndex CurrentBlockIndex() const { return mCurrentBlockIndex; } [[nodiscard]] ProfileBufferBlockIndex NextBlockIndex() const { return mNextBlockIndex; } // Be like a limited output iterator, with only `*` and prefix-`++`. // These definitions are expected by std functions, to recognize this as an // iterator. See https://en.cppreference.com/w/cpp/iterator/iterator_traits using value_type = Byte; using pointer = Byte*; using reference = Byte&; using iterator_category = std::output_iterator_tag; [[nodiscard]] Byte& operator*() { MOZ_RELEASE_ASSERT(RemainingBytes() >= 1); return *( (MOZ_LIKELY(!mCurrentSpan.IsEmpty()) ? mCurrentSpan : mNextSpanOrEmpty) .Elements()); } ProfileBufferEntryWriter& operator++() { if (MOZ_LIKELY(mCurrentSpan.LengthBytes() >= 1)) { // There is at least 1 byte in mCurrentSpan, eat it. mCurrentSpan = mCurrentSpan.From(1); } else { // mCurrentSpan is empty, move mNextSpanOrEmpty (past the first byte) to // mCurrentSpan. MOZ_RELEASE_ASSERT(mNextSpanOrEmpty.LengthBytes() >= 1); mCurrentSpan = mNextSpanOrEmpty.From(1); mNextSpanOrEmpty = mNextSpanOrEmpty.First(0); } return *this; } ProfileBufferEntryWriter& operator+=(Length aBytes) { // Note: This is a rare operation. The code below is a copy of `WriteBytes` // but without the `memcpy`s. MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); if (MOZ_LIKELY(aBytes <= mCurrentSpan.LengthBytes())) { // Data fits in mCurrentSpan. // Update mCurrentSpan. It may become empty, so in case of a double span, // the next call will go to the false case below. mCurrentSpan = mCurrentSpan.From(aBytes); } else { // Data does not fully fit in mCurrentSpan. // This should only happen at most once: Only for double spans, and when // data crosses the gap or starts there. const Length tail = aBytes - static_cast(mCurrentSpan.LengthBytes()); // Move mNextSpanOrEmpty to mCurrentSpan, past the data. So the next call // will go back to the true case above. mCurrentSpan = mNextSpanOrEmpty.From(tail); mNextSpanOrEmpty = mNextSpanOrEmpty.First(0); } return *this; } // Number of bytes needed to represent `aValue` in unsigned LEB128. template [[nodiscard]] static unsigned ULEB128Size(T aValue) { return ::mozilla::ULEB128Size(aValue); } // Write number as unsigned LEB128 and move iterator ahead. template void WriteULEB128(T aValue) { ::mozilla::WriteULEB128(aValue, *this); } // Number of bytes needed to serialize objects. template [[nodiscard]] static Length SumBytes(const Ts&... aTs) { return (0 + ... + Serializer::Bytes(aTs)); } // Write a sequence of bytes, like memcpy. void WriteBytes(const void* aSrc, Length aBytes) { MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); if (MOZ_LIKELY(aBytes <= mCurrentSpan.LengthBytes())) { // Data fits in mCurrentSpan. memcpy(mCurrentSpan.Elements(), aSrc, aBytes); // Update mCurrentSpan. It may become empty, so in case of a double span, // the next call will go to the false case below. mCurrentSpan = mCurrentSpan.From(aBytes); } else { // Data does not fully fit in mCurrentSpan. // This should only happen at most once: Only for double spans, and when // data crosses the gap or starts there. // Split data between the end of mCurrentSpan and the beginning of // mNextSpanOrEmpty. (mCurrentSpan could be empty, it's ok to do a memcpy // because Span::Elements() is never null.) memcpy(mCurrentSpan.Elements(), aSrc, mCurrentSpan.LengthBytes()); const Length tail = aBytes - static_cast(mCurrentSpan.LengthBytes()); memcpy(mNextSpanOrEmpty.Elements(), reinterpret_cast(aSrc) + mCurrentSpan.LengthBytes(), tail); // Move mNextSpanOrEmpty to mCurrentSpan, past the data. So the next call // will go back to the true case above. mCurrentSpan = mNextSpanOrEmpty.From(tail); mNextSpanOrEmpty = mNextSpanOrEmpty.First(0); } } void WriteFromReader(ProfileBufferEntryReader& aReader, Length aBytes) { MOZ_RELEASE_ASSERT(aBytes <= RemainingBytes()); MOZ_RELEASE_ASSERT(aBytes <= aReader.RemainingBytes()); Length read0 = std::min( aBytes, static_cast(aReader.mCurrentSpan.LengthBytes())); if (read0 != 0) { WriteBytes(aReader.mCurrentSpan.Elements(), read0); } Length read1 = aBytes - read0; if (read1 != 0) { WriteBytes(aReader.mNextSpanOrEmpty.Elements(), read1); } aReader += aBytes; } // Write a single object by using the appropriate Serializer. template void WriteObject(const T& aObject) { Serializer::Write(*this, aObject); } // Write one or more objects, sequentially. // Allow `EntryWrite::WriteObjects()` with nothing, this could be useful // for generic programming. template void WriteObjects(const Ts&... aTs) { (WriteObject(aTs), ...); } private: // The two spans covering the memory still to be written. SpanOfBytes mCurrentSpan; SpanOfBytes mNextSpanOrEmpty; ProfileBufferBlockIndex mCurrentBlockIndex; ProfileBufferBlockIndex mNextBlockIndex; }; // ============================================================================ // Serializer and Deserializer ready-to-use specializations. // ---------------------------------------------------------------------------- // Trivially-copyable types (default) // The default implementation works for all trivially-copyable types (e.g., // PODs). // // Usage: `aEW.WriteObject(123);`. // // Raw pointers, though trivially-copyable, are explictly forbidden when writing // (to avoid unexpected leaks/UAFs), instead use one of // `WrapProfileBufferLiteralCStringPointer`, `WrapProfileBufferUnownedCString`, // or `WrapProfileBufferRawPointer` as needed. template struct ProfileBufferEntryWriter::Serializer { static_assert(std::is_trivially_copyable::value, "Serializer only works with trivially-copyable types by " "default, use/add specialization for other types."); static constexpr Length Bytes(const T&) { return sizeof(T); } static void Write(ProfileBufferEntryWriter& aEW, const T& aT) { static_assert(!std::is_pointer::value, "Serializer won't write raw pointers by default, use " "WrapProfileBufferRawPointer or other."); aEW.WriteBytes(&aT, sizeof(T)); } }; // Usage: `aER.ReadObject();` or `int x; aER.ReadIntoObject(x);`. template struct ProfileBufferEntryReader::Deserializer { static_assert(std::is_trivially_copyable::value, "Deserializer only works with trivially-copyable types by " "default, use/add specialization for other types."); static void ReadInto(ProfileBufferEntryReader& aER, T& aT) { aER.ReadBytes(&aT, sizeof(T)); } static T Read(ProfileBufferEntryReader& aER) { // Note that this creates a default `T` first, and then overwrites it with // bytes from the buffer. Trivially-copyable types support this without UB. T ob; ReadInto(aER, ob); return ob; } }; // ---------------------------------------------------------------------------- // Strip const/volatile/reference from types. // Automatically strip `const`. template struct ProfileBufferEntryWriter::Serializer : public ProfileBufferEntryWriter::Serializer {}; template struct ProfileBufferEntryReader::Deserializer : public ProfileBufferEntryReader::Deserializer {}; // Automatically strip `volatile`. template struct ProfileBufferEntryWriter::Serializer : public ProfileBufferEntryWriter::Serializer {}; template struct ProfileBufferEntryReader::Deserializer : public ProfileBufferEntryReader::Deserializer {}; // Automatically strip `lvalue-reference`. template struct ProfileBufferEntryWriter::Serializer : public ProfileBufferEntryWriter::Serializer {}; template struct ProfileBufferEntryReader::Deserializer : public ProfileBufferEntryReader::Deserializer {}; // Automatically strip `rvalue-reference`. template struct ProfileBufferEntryWriter::Serializer : public ProfileBufferEntryWriter::Serializer {}; template struct ProfileBufferEntryReader::Deserializer : public ProfileBufferEntryReader::Deserializer {}; // ---------------------------------------------------------------------------- // ProfileBufferBlockIndex // ProfileBufferBlockIndex, serialized as the underlying value. template <> struct ProfileBufferEntryWriter::Serializer { static constexpr Length Bytes(const ProfileBufferBlockIndex& aBlockIndex) { return sizeof(ProfileBufferBlockIndex); } static void Write(ProfileBufferEntryWriter& aEW, const ProfileBufferBlockIndex& aBlockIndex) { aEW.WriteBytes(&aBlockIndex, sizeof(aBlockIndex)); } }; template <> struct ProfileBufferEntryReader::Deserializer { static void ReadInto(ProfileBufferEntryReader& aER, ProfileBufferBlockIndex& aBlockIndex) { aER.ReadBytes(&aBlockIndex, sizeof(aBlockIndex)); } static ProfileBufferBlockIndex Read(ProfileBufferEntryReader& aER) { ProfileBufferBlockIndex blockIndex; ReadInto(aER, blockIndex); return blockIndex; } }; // ---------------------------------------------------------------------------- // Literal C string pointer // Wrapper around a pointer to a literal C string. template struct ProfileBufferLiteralCStringPointer { const char* mCString; }; // Wrap a pointer to a literal C string. template ProfileBufferLiteralCStringPointer WrapProfileBufferLiteralCStringPointer( const char (&aCString)[CharactersIncludingTerminal]) { return {aCString}; } // Literal C strings, serialized as the raw pointer because it is unique and // valid for the whole program lifetime. // // Usage: `aEW.WriteObject(WrapProfileBufferLiteralCStringPointer("hi"));`. // // No deserializer is provided for this type, instead it must be deserialized as // a raw pointer: `aER.ReadObject();` template struct ProfileBufferEntryReader::Deserializer< ProfileBufferLiteralCStringPointer> { static constexpr Length Bytes( const ProfileBufferLiteralCStringPointer&) { // We're only storing a pointer, its size is independent from the pointer // value. return sizeof(const char*); } static void Write( ProfileBufferEntryWriter& aEW, const ProfileBufferLiteralCStringPointer& aWrapper) { // Write the pointer *value*, not the string contents. aEW.WriteBytes(aWrapper.mCString, sizeof(aWrapper.mCString)); } }; // ---------------------------------------------------------------------------- // C string contents // Wrapper around a pointer to a C string whose contents will be serialized. struct ProfileBufferUnownedCString { const char* mCString; }; // Wrap a pointer to a C string whose contents will be serialized. inline ProfileBufferUnownedCString WrapProfileBufferUnownedCString( const char* aCString) { return {aCString}; } // The contents of a (probably) unowned C string are serialized as the number of // characters (encoded as ULEB128) and all the characters in the string. The // terminal '\0' is omitted. // // Usage: `aEW.WriteObject(WrapProfileBufferUnownedCString(str.c_str()))`. // // No deserializer is provided for this pointer type, instead it must be // deserialized as one of the other string types that manages its contents, // e.g.: `aER.ReadObject();` template <> struct ProfileBufferEntryWriter::Serializer { static Length Bytes(const ProfileBufferUnownedCString& aS) { const auto len = strlen(aS.mCString); return ULEB128Size(len) + len; } static void Write(ProfileBufferEntryWriter& aEW, const ProfileBufferUnownedCString& aS) { const auto len = strlen(aS.mCString); aEW.WriteULEB128(len); aEW.WriteBytes(aS.mCString, len); } }; // ---------------------------------------------------------------------------- // Raw pointers // Wrapper around a pointer to be serialized as the raw pointer value. template struct ProfileBufferRawPointer { T* mRawPointer; }; // Wrap a pointer to be serialized as the raw pointer value. template ProfileBufferRawPointer WrapProfileBufferRawPointer(T* aRawPointer) { return {aRawPointer}; } // Raw pointers are serialized as the raw pointer value. // // Usage: `aEW.WriteObject(WrapProfileBufferRawPointer(ptr));` // // The wrapper is compulsory when writing pointers (to avoid unexpected // leaks/UAFs), but reading can be done straight into a raw pointer object, // e.g.: `aER.ReadObject;`. template struct ProfileBufferEntryWriter::Serializer> { template static constexpr Length Bytes(const U&) { return sizeof(T*); } static void Write(ProfileBufferEntryWriter& aEW, const ProfileBufferRawPointer& aWrapper) { aEW.WriteBytes(&aWrapper.mRawPointer, sizeof(aWrapper.mRawPointer)); } }; // Usage: `aER.ReadObject;` or `Foo* p; aER.ReadIntoObject(p);`, no // wrapper necessary. template struct ProfileBufferEntryReader::Deserializer> { static void ReadInto(ProfileBufferEntryReader& aER, ProfileBufferRawPointer& aPtr) { aER.ReadBytes(&aPtr.mRawPointer, sizeof(aPtr)); } static ProfileBufferRawPointer Read(ProfileBufferEntryReader& aER) { ProfileBufferRawPointer rawPointer; ReadInto(aER, rawPointer); return rawPointer; } }; // ---------------------------------------------------------------------------- // std::string contents // std::string contents are serialized as the number of characters (encoded as // ULEB128) and all the characters in the string. The terminal '\0' is omitted. // // Usage: `std::string s = ...; aEW.WriteObject(s);` template struct ProfileBufferEntryWriter::Serializer> { static Length Bytes(const std::basic_string& aS) { const Length len = static_cast(aS.length()); return ULEB128Size(len) + len; } static void Write(ProfileBufferEntryWriter& aEW, const std::basic_string& aS) { const Length len = static_cast(aS.length()); aEW.WriteULEB128(len); aEW.WriteBytes(aS.c_str(), len * sizeof(CHAR)); } }; // Usage: `std::string s = aEW.ReadObject(s);` or // `std::string s; aER.ReadIntoObject(s);` template struct ProfileBufferEntryReader::Deserializer> { static void ReadCharsInto(ProfileBufferEntryReader& aER, std::basic_string& aS, size_t aLength) { // Assign to `aS` by using iterators. // (`aER+0` so we get the same iterator type as `aER+len`.) aS.assign(aER, aER.EmptyIteratorAtOffset(aLength)); aER += aLength; } static void ReadInto(ProfileBufferEntryReader& aER, std::basic_string& aS) { ReadCharsInto( aER, aS, aER.ReadULEB128::size_type>()); } static std::basic_string ReadChars(ProfileBufferEntryReader& aER, size_t aLength) { // Construct a string by using iterators. // (`aER+0` so we get the same iterator type as `aER+len`.) std::basic_string s(aER, aER.EmptyIteratorAtOffset(aLength)); aER += aLength; return s; } static std::basic_string Read(ProfileBufferEntryReader& aER) { return ReadChars( aER, aER.ReadULEB128::size_type>()); } }; // ---------------------------------------------------------------------------- // mozilla::UniqueFreePtr // UniqueFreePtr, which points at a string allocated with `malloc` // (typically generated by `strdup()`), is serialized as the number of // *bytes* (encoded as ULEB128) and all the characters in the string. The // null terminator is omitted. // `CHAR` can be any type that has a specialization for // `std::char_traits::length(const CHAR*)`. // // Note: A nullptr pointer will be serialized like an empty string, so when // deserializing it will result in an allocated buffer only containing a // single null terminator. template struct ProfileBufferEntryWriter::Serializer> { static Length Bytes(const UniqueFreePtr& aS) { if (!aS) { // Null pointer, store it as if it was an empty string (so: 0 bytes). return ULEB128Size(0u); } // Note that we store the size in *bytes*, not in number of characters. const auto bytes = std::char_traits::length(aS.get()) * sizeof(CHAR); return ULEB128Size(bytes) + bytes; } static void Write(ProfileBufferEntryWriter& aEW, const UniqueFreePtr& aS) { if (!aS) { // Null pointer, store it as if it was an empty string (so we write a // length of 0 bytes). aEW.WriteULEB128(0u); return; } // Note that we store the size in *bytes*, not in number of characters. const auto bytes = std::char_traits::length(aS.get()) * sizeof(CHAR); aEW.WriteULEB128(bytes); aEW.WriteBytes(aS.get(), bytes); } }; template struct ProfileBufferEntryReader::Deserializer> { static void ReadInto(ProfileBufferEntryReader& aER, UniqueFreePtr& aS) { aS = Read(aER); } static UniqueFreePtr Read(ProfileBufferEntryReader& aER) { // Read the number of *bytes* that follow. const auto bytes = aER.ReadULEB128(); // We need a buffer of the non-const character type. using NC_CHAR = std::remove_const_t; // We allocate the required number of bytes, plus one extra character for // the null terminator. NC_CHAR* buffer = static_cast(malloc(bytes + sizeof(NC_CHAR))); // Copy the characters into the buffer. aER.ReadBytes(buffer, bytes); // And append a null terminator. buffer[bytes / sizeof(NC_CHAR)] = NC_CHAR(0); return UniqueFreePtr(buffer); } }; // ---------------------------------------------------------------------------- // std::tuple // std::tuple is serialized as a sequence of each recursively-serialized item. // // This is equivalent to manually serializing each item, so reading/writing // tuples is equivalent to reading/writing their elements in order, e.g.: // ``` // std::tuple is = ...; // aEW.WriteObject(is); // Write the tuple, equivalent to: // aEW.WriteObject(/* int */ std::get<0>(is), /* string */ std::get<1>(is)); // ... // // Reading back can be done directly into a tuple: // auto is = aER.ReadObject>(); // // Or each item could be read separately: // auto i = aER.ReadObject(); auto s = aER.ReadObject(); // ``` template struct ProfileBufferEntryWriter::Serializer> { private: template static Length TupleBytes(const std::tuple& aTuple, std::index_sequence) { return (0 + ... + SumBytes(std::get(aTuple))); } template static void TupleWrite(ProfileBufferEntryWriter& aEW, const std::tuple& aTuple, std::index_sequence) { (aEW.WriteObject(std::get(aTuple)), ...); } public: static Length Bytes(const std::tuple& aTuple) { // Generate a 0..N-1 index pack, we'll add the sizes of each item. return TupleBytes(aTuple, std::index_sequence_for()); } static void Write(ProfileBufferEntryWriter& aEW, const std::tuple& aTuple) { // Generate a 0..N-1 index pack, we'll write each item. TupleWrite(aEW, aTuple, std::index_sequence_for()); } }; template struct ProfileBufferEntryReader::Deserializer> { template static void TupleIReadInto(ProfileBufferEntryReader& aER, std::tuple& aTuple) { aER.ReadIntoObject(std::get(aTuple)); } template static void TupleReadInto(ProfileBufferEntryReader& aER, std::tuple& aTuple, std::index_sequence) { (TupleIReadInto(aER, aTuple), ...); } static void ReadInto(ProfileBufferEntryReader& aER, std::tuple& aTuple) { TupleReadInto(aER, aTuple, std::index_sequence_for()); } static std::tuple Read(ProfileBufferEntryReader& aER) { // Note that this creates default `Ts` first, and then overwrites them. std::tuple ob; ReadInto(aER, ob); return ob; } }; // ---------------------------------------------------------------------------- // mozilla::Span // Span. All elements are serialized in sequence. // The caller is assumed to know the number of elements (they may manually // write&read it before the span if needed). // Similar to tuples, reading/writing spans is equivalent to reading/writing // their elements in order. template struct ProfileBufferEntryWriter::Serializer> { static Length Bytes(const Span& aSpan) { Length bytes = 0; for (const T& element : aSpan) { bytes += SumBytes(element); } return bytes; } static void Write(ProfileBufferEntryWriter& aEW, const Span& aSpan) { for (const T& element : aSpan) { aEW.WriteObject(element); } } }; template struct ProfileBufferEntryReader::Deserializer> { // Read elements back into span pointing at a pre-allocated buffer. static void ReadInto(ProfileBufferEntryReader& aER, Span& aSpan) { for (T& element : aSpan) { aER.ReadIntoObject(element); } } // A Span does not own its data, this would probably leak so we forbid this. static Span Read(ProfileBufferEntryReader& aER) = delete; }; // ---------------------------------------------------------------------------- // mozilla::Maybe // Maybe is serialized as one byte containing either 'm' (Nothing), // or 'M' followed by the recursively-serialized `T` object. template struct ProfileBufferEntryWriter::Serializer> { static Length Bytes(const Maybe& aMaybe) { // 1 byte to store nothing/something flag, then object size if present. return aMaybe.isNothing() ? 1 : (1 + SumBytes(aMaybe.ref())); } static void Write(ProfileBufferEntryWriter& aEW, const Maybe& aMaybe) { // 'm'/'M' is just an arbitrary 1-byte value to distinguish states. if (aMaybe.isNothing()) { aEW.WriteObject('m'); } else { aEW.WriteObject('M'); // Use the Serializer for the contained type. aEW.WriteObject(aMaybe.ref()); } } }; template struct ProfileBufferEntryReader::Deserializer> { static void ReadInto(ProfileBufferEntryReader& aER, Maybe& aMaybe) { char c = aER.ReadObject(); if (c == 'm') { aMaybe.reset(); } else { MOZ_ASSERT(c == 'M'); // If aMaybe is empty, create a default `T` first, to be overwritten. // Otherwise we'll just overwrite whatever was already there. if (aMaybe.isNothing()) { aMaybe.emplace(); } // Use the Deserializer for the contained type. aER.ReadIntoObject(aMaybe.ref()); } } static Maybe Read(ProfileBufferEntryReader& aER) { Maybe maybe; char c = aER.ReadObject(); MOZ_ASSERT(c == 'M' || c == 'm'); if (c == 'M') { // Note that this creates a default `T` inside the Maybe first, and then // overwrites it. maybe = Some(T{}); // Use the Deserializer for the contained type. aER.ReadIntoObject(maybe.ref()); } return maybe; } }; // ---------------------------------------------------------------------------- // mozilla::Variant // Variant is serialized as the tag (0-based index of the stored type, encoded // as ULEB128), and the recursively-serialized object. template struct ProfileBufferEntryWriter::Serializer> { public: static Length Bytes(const Variant& aVariantTs) { return aVariantTs.match([](auto aIndex, const auto& aAlternative) { return ULEB128Size(aIndex) + SumBytes(aAlternative); }); } static void Write(ProfileBufferEntryWriter& aEW, const Variant& aVariantTs) { aVariantTs.match([&aEW](auto aIndex, const auto& aAlternative) { aEW.WriteULEB128(aIndex); aEW.WriteObject(aAlternative); }); } }; template struct ProfileBufferEntryReader::Deserializer> { private: // Called from the fold expression in `VariantReadInto()`, only the selected // variant will deserialize the object. template static void VariantIReadInto(ProfileBufferEntryReader& aER, Variant& aVariantTs, unsigned aTag) { if (I == aTag) { // Ensure the variant contains the target type. Note that this may create // a default object. if (!aVariantTs.template is()) { aVariantTs = Variant(VariantIndex{}); } aER.ReadIntoObject(aVariantTs.template as()); } } template static void VariantReadInto(ProfileBufferEntryReader& aER, Variant& aVariantTs, std::index_sequence) { unsigned tag = aER.ReadULEB128(); (VariantIReadInto(aER, aVariantTs, tag), ...); } public: static void ReadInto(ProfileBufferEntryReader& aER, Variant& aVariantTs) { // Generate a 0..N-1 index pack, the selected variant will deserialize // itself. VariantReadInto(aER, aVariantTs, std::index_sequence_for()); } static Variant Read(ProfileBufferEntryReader& aER) { // Note that this creates a default `Variant` of the first type, and then // overwrites it. Consider using `ReadInto` for more control if needed. Variant variant(VariantIndex<0>{}); ReadInto(aER, variant); return variant; } }; } // namespace mozilla #endif // ProfileBufferEntrySerialization_h