tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ProfileChunkedBufferDetail.h (14761B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef ProfileChunkedBufferDetail_h
      8 #define ProfileChunkedBufferDetail_h
      9 
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/Likely.h"
     12 #include "mozilla/ProfileBufferChunk.h"
     13 #include "mozilla/ProfileBufferEntrySerialization.h"
     14 
     15 namespace mozilla::profiler::detail {
     16 
     17 // Internal accessor pointing at a position inside a chunk.
     18 // It can handle two groups of chunks (typically the extant chunks stored in
     19 // the store manager, and the current chunk).
     20 // The main operations are:
     21 // - ReadEntrySize() to read an entry size, 0 means failure.
     22 // - operator+=(Length) to skip a number of bytes.
     23 // - EntryReader() creates an entry reader at the current position for a given
     24 //   size (it may fail with an empty reader), and skips the entry.
     25 // Note that there is no "past-the-end" position -- as soon as InChunkPointer
     26 // reaches the end, it becomes effectively null.
     27 class InChunkPointer {
     28 public:
     29  using Byte = ProfileBufferChunk::Byte;
     30  using Length = ProfileBufferChunk::Length;
     31 
     32  // Nullptr-like InChunkPointer, may be used as end iterator.
     33  InChunkPointer()
     34      : mChunk(nullptr), mNextChunkGroup(nullptr), mOffsetInChunk(0) {}
     35 
     36  // InChunkPointer over one or two chunk groups, pointing at the given
     37  // block index (if still in range).
     38  // This constructor should only be used with *trusted* block index values!
     39  InChunkPointer(const ProfileBufferChunk* aChunk,
     40                 const ProfileBufferChunk* aNextChunkGroup,
     41                 ProfileBufferBlockIndex aBlockIndex)
     42      : mChunk(aChunk), mNextChunkGroup(aNextChunkGroup) {
     43    if (mChunk) {
     44      mOffsetInChunk = mChunk->OffsetFirstBlock();
     45      Adjust();
     46    } else if (mNextChunkGroup) {
     47      mChunk = mNextChunkGroup;
     48      mNextChunkGroup = nullptr;
     49      mOffsetInChunk = mChunk->OffsetFirstBlock();
     50      Adjust();
     51    } else {
     52      mOffsetInChunk = 0;
     53    }
     54 
     55    // Try to advance to given position.
     56    if (!AdvanceToGlobalRangePosition(aBlockIndex)) {
     57      // Block does not exist anymore (or block doesn't look valid), reset the
     58      // in-chunk pointer.
     59      mChunk = nullptr;
     60      mNextChunkGroup = nullptr;
     61    }
     62  }
     63 
     64  // InChunkPointer over one or two chunk groups, will start at the first
     65  // block (if any). This may be slow, so avoid using it too much.
     66  InChunkPointer(const ProfileBufferChunk* aChunk,
     67                 const ProfileBufferChunk* aNextChunkGroup,
     68                 ProfileBufferIndex aIndex = ProfileBufferIndex(0))
     69      : mChunk(aChunk), mNextChunkGroup(aNextChunkGroup) {
     70    if (mChunk) {
     71      mOffsetInChunk = mChunk->OffsetFirstBlock();
     72      Adjust();
     73    } else if (mNextChunkGroup) {
     74      mChunk = mNextChunkGroup;
     75      mNextChunkGroup = nullptr;
     76      mOffsetInChunk = mChunk->OffsetFirstBlock();
     77      Adjust();
     78    } else {
     79      mOffsetInChunk = 0;
     80    }
     81 
     82    // Try to advance to given position.
     83    if (!AdvanceToGlobalRangePosition(aIndex)) {
     84      // Block does not exist anymore, reset the in-chunk pointer.
     85      mChunk = nullptr;
     86      mNextChunkGroup = nullptr;
     87    }
     88  }
     89 
     90  // Compute the current position in the global range.
     91  // 0 if null (including if we're reached the end).
     92  [[nodiscard]] ProfileBufferIndex GlobalRangePosition() const {
     93    if (IsNull()) {
     94      return 0;
     95    }
     96    return mChunk->RangeStart() + mOffsetInChunk;
     97  }
     98 
     99  // Move InChunkPointer forward to the block at the given global block
    100  // position, which is assumed to be valid exactly -- but it may be obsolete.
    101  // 0 stays where it is (if valid already).
    102  // MOZ_ASSERTs if the index is invalid.
    103  [[nodiscard]] bool AdvanceToGlobalRangePosition(
    104      ProfileBufferBlockIndex aBlockIndex) {
    105    if (IsNull()) {
    106      // Pointer is null already. (Not asserting because it's acceptable.)
    107      return false;
    108    }
    109    if (!aBlockIndex) {
    110      // Special null position, just stay where we are.
    111      return ShouldPointAtValidBlock();
    112    }
    113    if (aBlockIndex.ConvertToProfileBufferIndex() < GlobalRangePosition()) {
    114      // Past the requested position, stay where we are (assuming the current
    115      // position was valid).
    116      return ShouldPointAtValidBlock();
    117    }
    118    for (;;) {
    119      if (aBlockIndex.ConvertToProfileBufferIndex() <
    120          mChunk->RangeStart() + mChunk->OffsetPastLastBlock()) {
    121        // Target position is in this chunk's written space, move to it.
    122        mOffsetInChunk =
    123            aBlockIndex.ConvertToProfileBufferIndex() - mChunk->RangeStart();
    124        return ShouldPointAtValidBlock();
    125      }
    126      // Position is after this chunk, try next chunk.
    127      GoToNextChunk();
    128      if (IsNull()) {
    129        return false;
    130      }
    131      // Skip whatever block tail there is, we don't allow pointing in the
    132      // middle of a block.
    133      mOffsetInChunk = mChunk->OffsetFirstBlock();
    134      if (aBlockIndex.ConvertToProfileBufferIndex() < GlobalRangePosition()) {
    135        // Past the requested position, meaning that the given position was in-
    136        // between blocks -> Failure.
    137        MOZ_ASSERT(false, "AdvanceToGlobalRangePosition - In-between blocks");
    138        return false;
    139      }
    140    }
    141  }
    142 
    143  // Move InChunkPointer forward to the block at or after the given global
    144  // range position.
    145  // 0 stays where it is (if valid already).
    146  [[nodiscard]] bool AdvanceToGlobalRangePosition(
    147      ProfileBufferIndex aPosition) {
    148    if (aPosition == 0) {
    149      // Special position '0', just stay where we are.
    150      // Success if this position is already valid.
    151      return !IsNull();
    152    }
    153    for (;;) {
    154      ProfileBufferIndex currentPosition = GlobalRangePosition();
    155      if (currentPosition == 0) {
    156        // Pointer is null.
    157        return false;
    158      }
    159      if (aPosition <= currentPosition) {
    160        // At or past the requested position, stay where we are.
    161        return true;
    162      }
    163      if (aPosition < mChunk->RangeStart() + mChunk->OffsetPastLastBlock()) {
    164        // Target position is in this chunk's written space, move to it.
    165        for (;;) {
    166          // Skip the current block.
    167          mOffsetInChunk += ReadEntrySize();
    168          if (mOffsetInChunk >= mChunk->OffsetPastLastBlock()) {
    169            // Reached the end of the chunk, this can happen for the last
    170            // block, let's just continue to the next chunk.
    171            break;
    172          }
    173          if (aPosition <= mChunk->RangeStart() + mOffsetInChunk) {
    174            // We're at or after the position, return at this block position.
    175            return true;
    176          }
    177        }
    178      }
    179      // Position is after this chunk, try next chunk.
    180      GoToNextChunk();
    181      if (IsNull()) {
    182        return false;
    183      }
    184      // Skip whatever block tail there is, we don't allow pointing in the
    185      // middle of a block.
    186      mOffsetInChunk = mChunk->OffsetFirstBlock();
    187    }
    188  }
    189 
    190  [[nodiscard]] Byte ReadByte() {
    191    MOZ_ASSERT(!IsNull());
    192    MOZ_ASSERT(mOffsetInChunk < mChunk->OffsetPastLastBlock());
    193    Byte byte = mChunk->ByteAt(mOffsetInChunk);
    194    if (MOZ_UNLIKELY(++mOffsetInChunk == mChunk->OffsetPastLastBlock())) {
    195      Adjust();
    196    }
    197    return byte;
    198  }
    199 
    200  // Read and skip a ULEB128-encoded size.
    201  // 0 means failure (0-byte entries are not allowed.)
    202  // Note that this doesn't guarantee that there are actually that many bytes
    203  // available to read! (EntryReader() below may gracefully fail.)
    204  [[nodiscard]] Length ReadEntrySize() {
    205    ULEB128Reader<Length> reader;
    206    if (IsNull()) {
    207      return 0;
    208    }
    209    for (;;) {
    210      const bool isComplete = reader.FeedByteIsComplete(ReadByte());
    211      if (MOZ_UNLIKELY(IsNull())) {
    212        // End of chunks, so there's no actual entry after this anyway.
    213        return 0;
    214      }
    215      if (MOZ_LIKELY(isComplete)) {
    216        if (MOZ_UNLIKELY(reader.Value() > mChunk->BufferBytes())) {
    217          // Don't allow entries larger than a chunk.
    218          return 0;
    219        }
    220        return reader.Value();
    221      }
    222    }
    223  }
    224 
    225  InChunkPointer& operator+=(Length aLength) {
    226    MOZ_ASSERT(!IsNull());
    227    mOffsetInChunk += aLength;
    228    Adjust();
    229    return *this;
    230  }
    231 
    232  [[nodiscard]] ProfileBufferEntryReader EntryReader(Length aLength) {
    233    if (IsNull() || aLength == 0) {
    234      return ProfileBufferEntryReader();
    235    }
    236 
    237    MOZ_ASSERT(mOffsetInChunk < mChunk->OffsetPastLastBlock());
    238 
    239    // We should be pointing at the entry, past the entry size.
    240    const ProfileBufferIndex entryIndex = GlobalRangePosition();
    241    // Verify that there's enough space before for the size (starting at index
    242    // 1 at least).
    243    MOZ_ASSERT(entryIndex >= 1u + ULEB128Size(aLength));
    244 
    245    const Length remaining = mChunk->OffsetPastLastBlock() - mOffsetInChunk;
    246    Span<const Byte> mem0 = mChunk->BufferSpan();
    247    mem0 = mem0.From(mOffsetInChunk);
    248    if (aLength <= remaining) {
    249      // Move to the end of this block, which could make this null if we have
    250      // reached the end of all buffers.
    251      *this += aLength;
    252      return ProfileBufferEntryReader(
    253          mem0.To(aLength),
    254          // Block starts before the entry size.
    255          ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
    256              entryIndex - ULEB128Size(aLength)),
    257          // Block ends right after the entry (could be null for last entry).
    258          ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
    259              GlobalRangePosition()));
    260    }
    261 
    262    // We need to go to the next chunk for the 2nd part of this block.
    263    GoToNextChunk();
    264    if (IsNull()) {
    265      return ProfileBufferEntryReader();
    266    }
    267 
    268    Span<const Byte> mem1 = mChunk->BufferSpan();
    269    const Length tail = aLength - remaining;
    270    MOZ_ASSERT(tail <= mChunk->BufferBytes());
    271    MOZ_ASSERT(tail == mChunk->OffsetFirstBlock());
    272    // We are in the correct chunk, move the offset to the end of the block.
    273    mOffsetInChunk = tail;
    274    // And adjust as needed, which could make this null if we have reached the
    275    // end of all buffers.
    276    Adjust();
    277    return ProfileBufferEntryReader(
    278        mem0, mem1.To(tail),
    279        // Block starts before the entry size.
    280        ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
    281            entryIndex - ULEB128Size(aLength)),
    282        // Block ends right after the entry (could be null for last entry).
    283        ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
    284            GlobalRangePosition()));
    285  }
    286 
    287  [[nodiscard]] bool IsNull() const { return !mChunk; }
    288 
    289  [[nodiscard]] bool operator==(const InChunkPointer& aOther) const {
    290    if (IsNull() || aOther.IsNull()) {
    291      return IsNull() && aOther.IsNull();
    292    }
    293    return mChunk == aOther.mChunk && mOffsetInChunk == aOther.mOffsetInChunk;
    294  }
    295 
    296  [[nodiscard]] bool operator!=(const InChunkPointer& aOther) const {
    297    return !(*this == aOther);
    298  }
    299 
    300  [[nodiscard]] Byte operator*() const {
    301    MOZ_ASSERT(!IsNull());
    302    MOZ_ASSERT(mOffsetInChunk < mChunk->OffsetPastLastBlock());
    303    return mChunk->ByteAt(mOffsetInChunk);
    304  }
    305 
    306  InChunkPointer& operator++() {
    307    MOZ_ASSERT(!IsNull());
    308    MOZ_ASSERT(mOffsetInChunk < mChunk->OffsetPastLastBlock());
    309    if (MOZ_UNLIKELY(++mOffsetInChunk == mChunk->OffsetPastLastBlock())) {
    310      mOffsetInChunk = 0;
    311      GoToNextChunk();
    312      Adjust();
    313    }
    314    return *this;
    315  }
    316 
    317 private:
    318  void GoToNextChunk() {
    319    MOZ_ASSERT(!IsNull());
    320    const ProfileBufferIndex expectedNextRangeStart =
    321        mChunk->RangeStart() + mChunk->BufferBytes();
    322 
    323    mChunk = mChunk->GetNext();
    324    if (!mChunk) {
    325      // Reached the end of the current chunk group, try the next one (which
    326      // may be null too, especially on the 2nd try).
    327      mChunk = mNextChunkGroup;
    328      mNextChunkGroup = nullptr;
    329    }
    330 
    331    if (mChunk && mChunk->RangeStart() == 0) {
    332      // Reached a chunk without a valid (non-null) range start, assume there
    333      // are only unused chunks from here on.
    334      mChunk = nullptr;
    335    }
    336 
    337    MOZ_ASSERT(!mChunk || mChunk->RangeStart() == expectedNextRangeStart,
    338               "We don't handle discontinuous buffers (yet)");
    339    // Non-DEBUG fallback: Stop reading past discontinuities.
    340    // (They should be rare, only happening on temporary OOMs.)
    341    // TODO: Handle discontinuities (by skipping over incomplete blocks).
    342    if (mChunk && mChunk->RangeStart() != expectedNextRangeStart) {
    343      mChunk = nullptr;
    344    }
    345  }
    346 
    347  // We want `InChunkPointer` to always point at a valid byte (or be null).
    348  // After some operations, `mOffsetInChunk` may point past the end of the
    349  // current `mChunk`, in which case we need to adjust our position to be inside
    350  // the appropriate chunk. E.g., if we're 10 bytes after the end of the current
    351  // chunk, we should end up at offset 10 in the next chunk.
    352  // Note that we may "fall off" the last chunk and make this `InChunkPointer`
    353  // effectively null.
    354  void Adjust() {
    355    while (mChunk && mOffsetInChunk >= mChunk->OffsetPastLastBlock()) {
    356      // TODO: Try to adjust offset between chunks relative to mRangeStart
    357      // differences. But we don't handle discontinuities yet.
    358      if (mOffsetInChunk < mChunk->BufferBytes()) {
    359        mOffsetInChunk -= mChunk->BufferBytes();
    360      } else {
    361        mOffsetInChunk -= mChunk->OffsetPastLastBlock();
    362      }
    363      GoToNextChunk();
    364    }
    365  }
    366 
    367  // Check if the current position is likely to point at a valid block.
    368  // (Size should be reasonable, and block should fully fit inside buffer.)
    369  // MOZ_ASSERTs on failure, to catch incorrect uses of block indices (which
    370  // should only point at valid blocks if still in range). Non-asserting build
    371  // fallback should still be handled.
    372  [[nodiscard]] bool ShouldPointAtValidBlock() const {
    373    if (IsNull()) {
    374      // Pointer is null, no blocks here.
    375      MOZ_ASSERT(false, "ShouldPointAtValidBlock - null pointer");
    376      return false;
    377    }
    378    // Use a copy, so we don't modify `*this`.
    379    InChunkPointer pointer = *this;
    380    // Try to read the entry size.
    381    Length entrySize = pointer.ReadEntrySize();
    382    if (entrySize == 0) {
    383      // Entry size of zero means we read 0 or a way-too-big value.
    384      MOZ_ASSERT(false, "ShouldPointAtValidBlock - invalid size");
    385      return false;
    386    }
    387    // See if the last byte of the entry is still inside the buffer.
    388    pointer += entrySize - 1;
    389    MOZ_ASSERT(!pointer.IsNull(),
    390               "ShouldPointAtValidBlock - past end of buffer");
    391    return !pointer.IsNull();
    392  }
    393 
    394  const ProfileBufferChunk* mChunk;
    395  const ProfileBufferChunk* mNextChunkGroup;
    396  Length mOffsetInChunk;
    397 };
    398 
    399 }  // namespace mozilla::profiler::detail
    400 
    401 #endif  // ProfileChunkedBufferDetail_h