tor-browser

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

BufferList.h (20057B)


      1 /* -*- Mode: C++; tab-width: 8; 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 mozilla_BufferList_h
      8 #define mozilla_BufferList_h
      9 
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/MemoryReporting.h"
     12 #include "mozilla/Vector.h"
     13 
     14 #include <algorithm>
     15 #include <cstdint>
     16 #include <cstring>
     17 #include <type_traits>
     18 #include <utility>
     19 
     20 // BufferList represents a sequence of buffers of data. A BufferList can choose
     21 // to own its buffers or not. The class handles writing to the buffers,
     22 // iterating over them, and reading data out. Unlike SegmentedVector, the
     23 // buffers may be of unequal size. Like SegmentedVector, BufferList is a nice
     24 // way to avoid large contiguous allocations (which can trigger OOMs).
     25 
     26 class InfallibleAllocPolicy;
     27 
     28 namespace mozilla {
     29 
     30 template <typename AllocPolicy>
     31 class BufferList : private AllocPolicy {
     32  // Each buffer in a BufferList has a size and a capacity. The first mSize
     33  // bytes are initialized and the remaining |mCapacity - mSize| bytes are free.
     34  struct Segment {
     35    char* mData;
     36    size_t mSize;
     37    size_t mCapacity;
     38 
     39    Segment(char* aData, size_t aSize, size_t aCapacity)
     40        : mData(aData), mSize(aSize), mCapacity(aCapacity) {}
     41 
     42    Segment(const Segment&) = delete;
     43    Segment& operator=(const Segment&) = delete;
     44 
     45    Segment(Segment&&) = default;
     46    Segment& operator=(Segment&&) = default;
     47 
     48    char* Start() const { return mData; }
     49    char* End() const { return mData + mSize; }
     50  };
     51 
     52  template <typename OtherAllocPolicy>
     53  friend class BufferList;
     54 
     55 public:
     56  // For the convenience of callers, all segments are required to be a multiple
     57  // of 8 bytes in capacity. Also, every buffer except the last one is required
     58  // to be full (i.e., size == capacity). Therefore, a byte at offset N within
     59  // the BufferList and stored in memory at an address A will satisfy
     60  // (N % Align == A % Align) if Align == 2, 4, or 8.
     61  static const size_t kSegmentAlignment = 8;
     62 
     63  // Allocate a BufferList. The BufferList will free all its buffers when it is
     64  // destroyed. If an infallible allocator is used, an initial buffer of size
     65  // aInitialSize and capacity aInitialCapacity is allocated automatically. This
     66  // data will be contiguous and can be accessed via |Start()|. If a fallible
     67  // alloc policy is used, aInitialSize must be 0, and the fallible |Init()|
     68  // method may be called instead. Subsequent buffers will be allocated with
     69  // capacity aStandardCapacity.
     70  BufferList(size_t aInitialSize, size_t aInitialCapacity,
     71             size_t aStandardCapacity, AllocPolicy aAP = AllocPolicy())
     72      : AllocPolicy(aAP),
     73        mOwning(true),
     74        mSegments(aAP),
     75        mSize(0),
     76        mStandardCapacity(aStandardCapacity) {
     77    MOZ_ASSERT(aInitialCapacity % kSegmentAlignment == 0);
     78    MOZ_ASSERT(aStandardCapacity % kSegmentAlignment == 0);
     79 
     80    if (aInitialCapacity) {
     81      MOZ_ASSERT((aInitialSize == 0 ||
     82                  std::is_same_v<AllocPolicy, InfallibleAllocPolicy>),
     83                 "BufferList may only be constructed with an initial size when "
     84                 "using an infallible alloc policy");
     85 
     86      AllocateSegment(aInitialSize, aInitialCapacity);
     87    }
     88  }
     89 
     90  BufferList(const BufferList& aOther) = delete;
     91 
     92  BufferList(BufferList&& aOther)
     93      : mOwning(aOther.mOwning),
     94        mSegments(std::move(aOther.mSegments)),
     95        mSize(aOther.mSize),
     96        mStandardCapacity(aOther.mStandardCapacity) {
     97    aOther.mSegments.clear();
     98    aOther.mSize = 0;
     99  }
    100 
    101  BufferList& operator=(const BufferList& aOther) = delete;
    102 
    103  BufferList& operator=(BufferList&& aOther) {
    104    Clear();
    105 
    106    mOwning = aOther.mOwning;
    107    mSegments = std::move(aOther.mSegments);
    108    mSize = aOther.mSize;
    109    aOther.mSegments.clear();
    110    aOther.mSize = 0;
    111    return *this;
    112  }
    113 
    114  ~BufferList() { Clear(); }
    115 
    116  // Initializes the BufferList with a segment of the given size and capacity.
    117  // May only be called once, before any segments have been allocated.
    118  bool Init(size_t aInitialSize, size_t aInitialCapacity) {
    119    MOZ_ASSERT(mSegments.empty());
    120    MOZ_ASSERT(aInitialCapacity != 0);
    121    MOZ_ASSERT(aInitialCapacity % kSegmentAlignment == 0);
    122 
    123    return AllocateSegment(aInitialSize, aInitialCapacity);
    124  }
    125 
    126  bool CopyFrom(const BufferList& aOther) {
    127    MOZ_ASSERT(mOwning);
    128 
    129    Clear();
    130 
    131    // We don't make an exact copy of aOther. Instead, create a single segment
    132    // with enough space to hold all data in aOther.
    133    if (!Init(aOther.mSize, (aOther.mSize + kSegmentAlignment - 1) &
    134                                ~(kSegmentAlignment - 1))) {
    135      return false;
    136    }
    137 
    138    size_t offset = 0;
    139    for (const Segment& segment : aOther.mSegments) {
    140      memcpy(Start() + offset, segment.mData, segment.mSize);
    141      offset += segment.mSize;
    142    }
    143    MOZ_ASSERT(offset == mSize);
    144 
    145    return true;
    146  }
    147 
    148  // Returns the sum of the sizes of all the buffers.
    149  size_t Size() const { return mSize; }
    150 
    151  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
    152    size_t size = mSegments.sizeOfExcludingThis(aMallocSizeOf);
    153    for (Segment& segment : mSegments) {
    154      size += aMallocSizeOf(segment.Start());
    155    }
    156    return size;
    157  }
    158 
    159  void Clear() {
    160    if (mOwning) {
    161      for (Segment& segment : mSegments) {
    162        this->free_(segment.mData, segment.mCapacity);
    163      }
    164    }
    165    mSegments.clear();
    166 
    167    mSize = 0;
    168  }
    169 
    170  // Iterates over bytes in the segments. You can advance it by as many bytes as
    171  // you choose.
    172  class IterImpl {
    173    // Invariants:
    174    //   (0) mSegment <= bufferList.mSegments.length()
    175    //   (1) mData <= mDataEnd
    176    //   (2) If mSegment is not the last segment, mData < mDataEnd
    177    uintptr_t mSegment{0};
    178    char* mData{nullptr};
    179    char* mDataEnd{nullptr};
    180    size_t mAbsoluteOffset{0};
    181 
    182    friend class BufferList;
    183 
    184   public:
    185    explicit IterImpl(const BufferList& aBuffers) {
    186      if (!aBuffers.mSegments.empty()) {
    187        mData = aBuffers.mSegments[0].Start();
    188        mDataEnd = aBuffers.mSegments[0].End();
    189      }
    190    }
    191 
    192    // Returns a pointer to the raw data. It is valid to access up to
    193    // RemainingInSegment bytes of this buffer.
    194    char* Data() const {
    195      MOZ_RELEASE_ASSERT(!Done());
    196      return mData;
    197    }
    198 
    199    bool operator==(const IterImpl& other) const {
    200      return mAbsoluteOffset == other.mAbsoluteOffset;
    201    }
    202    bool operator!=(const IterImpl& other) const { return !(*this == other); }
    203 
    204    // Returns true if the memory in the range [Data(), Data() + aBytes) is all
    205    // part of one contiguous buffer.
    206    bool HasRoomFor(size_t aBytes) const {
    207      return RemainingInSegment() >= aBytes;
    208    }
    209 
    210    // Returns the largest value aBytes for which HasRoomFor(aBytes) will be
    211    // true.
    212    size_t RemainingInSegment() const {
    213      MOZ_RELEASE_ASSERT(mData <= mDataEnd);
    214      return mDataEnd - mData;
    215    }
    216 
    217    // Returns true if there are at least aBytes entries remaining in the
    218    // BufferList after this iterator.
    219    bool HasBytesAvailable(const BufferList& aBuffers, size_t aBytes) const {
    220      return TotalBytesAvailable(aBuffers) >= aBytes;
    221    }
    222 
    223    // Returns the largest value `aBytes` for which HasBytesAvailable(aBytes)
    224    // will be true.
    225    size_t TotalBytesAvailable(const BufferList& aBuffers) const {
    226      return aBuffers.mSize - mAbsoluteOffset;
    227    }
    228 
    229    // Advances the iterator by aBytes bytes. aBytes must be less than
    230    // RemainingInSegment(). If advancing by aBytes takes the iterator to the
    231    // end of a buffer, it will be moved to the beginning of the next buffer
    232    // unless it is the last buffer.
    233    void Advance(const BufferList& aBuffers, size_t aBytes) {
    234      const Segment& segment = aBuffers.mSegments[mSegment];
    235      MOZ_RELEASE_ASSERT(segment.Start() <= mData);
    236      MOZ_RELEASE_ASSERT(mData <= mDataEnd);
    237      MOZ_RELEASE_ASSERT(mDataEnd == segment.End());
    238 
    239      MOZ_RELEASE_ASSERT(HasRoomFor(aBytes));
    240      mData += aBytes;
    241      mAbsoluteOffset += aBytes;
    242 
    243      if (mData == mDataEnd && mSegment + 1 < aBuffers.mSegments.length()) {
    244        mSegment++;
    245        const Segment& nextSegment = aBuffers.mSegments[mSegment];
    246        mData = nextSegment.Start();
    247        mDataEnd = nextSegment.End();
    248        MOZ_RELEASE_ASSERT(mData < mDataEnd);
    249      }
    250    }
    251 
    252    // Advance the iterator by aBytes, possibly crossing segments. This function
    253    // returns false if it runs out of buffers to advance through. Otherwise it
    254    // returns true.
    255    bool AdvanceAcrossSegments(const BufferList& aBuffers, size_t aBytes) {
    256      // If we don't need to cross segments, we can directly use `Advance` to
    257      // get to our destination.
    258      if (MOZ_LIKELY(aBytes <= RemainingInSegment())) {
    259        Advance(aBuffers, aBytes);
    260        return true;
    261      }
    262 
    263      // Check if we have enough bytes to scan this far forward.
    264      if (!HasBytesAvailable(aBuffers, aBytes)) {
    265        return false;
    266      }
    267 
    268      // Compare the distance to our target offset from the end of the
    269      // BufferList to the distance from the start of our next segment.
    270      // Depending on which is closer, we'll advance either forwards or
    271      // backwards.
    272      size_t targetOffset = mAbsoluteOffset + aBytes;
    273      size_t fromEnd = aBuffers.mSize - targetOffset;
    274      if (aBytes - RemainingInSegment() < fromEnd) {
    275        // Advance through the buffer list until we reach the desired absolute
    276        // offset.
    277        while (mAbsoluteOffset < targetOffset) {
    278          Advance(aBuffers, std::min(targetOffset - mAbsoluteOffset,
    279                                     RemainingInSegment()));
    280        }
    281        MOZ_ASSERT(mAbsoluteOffset == targetOffset);
    282        return true;
    283      }
    284 
    285      // Scanning starting from the end of the BufferList. We advance
    286      // backwards from the final segment until we find the segment to end in.
    287      //
    288      // If we end on a segment boundary, make sure to place the cursor at the
    289      // beginning of the next segment.
    290      mSegment = aBuffers.mSegments.length() - 1;
    291      while (fromEnd > aBuffers.mSegments[mSegment].mSize) {
    292        fromEnd -= aBuffers.mSegments[mSegment].mSize;
    293        mSegment--;
    294      }
    295      mDataEnd = aBuffers.mSegments[mSegment].End();
    296      mData = mDataEnd - fromEnd;
    297      mAbsoluteOffset = targetOffset;
    298      MOZ_ASSERT_IF(Done(), mSegment == aBuffers.mSegments.length() - 1);
    299      MOZ_ASSERT_IF(Done(), mAbsoluteOffset == aBuffers.mSize);
    300      return true;
    301    }
    302 
    303    // Returns true when the iterator reaches the end of the BufferList.
    304    bool Done() const { return mData == mDataEnd; }
    305 
    306    // The absolute offset of this iterator within the BufferList.
    307    size_t AbsoluteOffset() const { return mAbsoluteOffset; }
    308 
    309   private:
    310    bool IsIn(const BufferList& aBuffers) const {
    311      return mSegment < aBuffers.mSegments.length() &&
    312             mData >= aBuffers.mSegments[mSegment].mData &&
    313             mData < aBuffers.mSegments[mSegment].End();
    314    }
    315  };
    316 
    317  // Special convenience method that returns Iter().Data().
    318  char* Start() {
    319    MOZ_RELEASE_ASSERT(!mSegments.empty());
    320    return mSegments[0].mData;
    321  }
    322  const char* Start() const { return mSegments[0].mData; }
    323 
    324  IterImpl Iter() const { return IterImpl(*this); }
    325 
    326  // Copies aSize bytes from aData into the BufferList. The storage for these
    327  // bytes may be split across multiple buffers. Size() is increased by aSize.
    328  [[nodiscard]] inline bool WriteBytes(const char* aData, size_t aSize);
    329 
    330  // Allocates a buffer of at most |aMaxBytes| bytes and, if successful, returns
    331  // that buffer, and places its size in |aSize|. If unsuccessful, returns null
    332  // and leaves |aSize| undefined.
    333  inline char* AllocateBytes(size_t aMaxSize, size_t* aSize);
    334 
    335  // Copies possibly non-contiguous byte range starting at aIter into
    336  // aData. aIter is advanced by aSize bytes. Returns false if it runs out of
    337  // data before aSize.
    338  inline bool ReadBytes(IterImpl& aIter, char* aData, size_t aSize) const;
    339 
    340  // Return a new BufferList that shares storage with this BufferList. The new
    341  // BufferList is read-only. It allows iteration over aSize bytes starting at
    342  // aIter. Borrow can fail, in which case *aSuccess will be false upon
    343  // return. The borrowed BufferList can use a different AllocPolicy than the
    344  // original one. However, it is not responsible for freeing buffers, so the
    345  // AllocPolicy is only used for the buffer vector.
    346  template <typename BorrowingAllocPolicy>
    347  BufferList<BorrowingAllocPolicy> Borrow(
    348      IterImpl& aIter, size_t aSize, bool* aSuccess,
    349      BorrowingAllocPolicy aAP = BorrowingAllocPolicy()) const;
    350 
    351  // Return a new BufferList and move storage from this BufferList to it. The
    352  // new BufferList owns the buffers. Move can fail, in which case *aSuccess
    353  // will be false upon return. The new BufferList can use a different
    354  // AllocPolicy than the original one. The new OtherAllocPolicy is responsible
    355  // for freeing buffers, so the OtherAllocPolicy must use freeing method
    356  // compatible to the original one.
    357  template <typename OtherAllocPolicy>
    358  BufferList<OtherAllocPolicy> MoveFallible(
    359      bool* aSuccess, OtherAllocPolicy aAP = OtherAllocPolicy());
    360 
    361  // Return the number of bytes from 'start' to 'end', two iterators within
    362  // this BufferList.
    363  size_t RangeLength(const IterImpl& start, const IterImpl& end) const {
    364    MOZ_ASSERT(start.IsIn(*this) && end.IsIn(*this));
    365    return end.mAbsoluteOffset - start.mAbsoluteOffset;
    366  }
    367 
    368  // This takes ownership of the data
    369  [[nodiscard]] bool WriteBytesZeroCopy(char* aData, size_t aSize,
    370                                        size_t aCapacity) {
    371    MOZ_ASSERT(mOwning);
    372    MOZ_ASSERT(aSize <= aCapacity);
    373 
    374    // Don't create zero-length segments; that can cause problems for
    375    // consumers of the data (bug 1595453).
    376    if (aSize == 0) {
    377      this->free_(aData, aCapacity);
    378      return true;
    379    }
    380 
    381    if (!mSegments.append(Segment(aData, aSize, aCapacity))) {
    382      this->free_(aData, aCapacity);
    383      return false;
    384    }
    385    mSize += aSize;
    386    return true;
    387  }
    388 
    389  // Truncate this BufferList at the given iterator location, discarding all
    390  // data after this point. After this call, all other iterators will be
    391  // invalidated, and the passed-in iterator will be "Done".
    392  //
    393  // Returns the number of bytes discarded by this truncation.
    394  size_t Truncate(IterImpl& aIter);
    395 
    396 private:
    397  explicit BufferList(AllocPolicy aAP)
    398      : AllocPolicy(aAP), mOwning(false), mSize(0), mStandardCapacity(0) {}
    399 
    400  char* AllocateSegment(size_t aSize, size_t aCapacity) {
    401    MOZ_RELEASE_ASSERT(mOwning);
    402    MOZ_ASSERT(aCapacity != 0);
    403    MOZ_ASSERT(aSize <= aCapacity);
    404 
    405    char* data = this->template pod_malloc<char>(aCapacity);
    406    if (!data) {
    407      return nullptr;
    408    }
    409    if (!mSegments.append(Segment(data, aSize, aCapacity))) {
    410      this->free_(data, aCapacity);
    411      return nullptr;
    412    }
    413    mSize += aSize;
    414    return data;
    415  }
    416 
    417  void AssertConsistentSize() const {
    418 #ifdef DEBUG
    419    size_t realSize = 0;
    420    for (const auto& segment : mSegments) {
    421      realSize += segment.mSize;
    422    }
    423    MOZ_ASSERT(realSize == mSize, "cached size value is inconsistent!");
    424 #endif
    425  }
    426 
    427  bool mOwning;
    428  Vector<Segment, 1, AllocPolicy> mSegments;
    429  size_t mSize;
    430  size_t mStandardCapacity;
    431 };
    432 
    433 template <typename AllocPolicy>
    434 [[nodiscard]] bool BufferList<AllocPolicy>::WriteBytes(const char* aData,
    435                                                       size_t aSize) {
    436  MOZ_RELEASE_ASSERT(mOwning);
    437  MOZ_RELEASE_ASSERT(mStandardCapacity);
    438 
    439  size_t copied = 0;
    440  while (copied < aSize) {
    441    size_t toCopy;
    442    char* data = AllocateBytes(aSize - copied, &toCopy);
    443    if (!data) {
    444      return false;
    445    }
    446    memcpy(data, aData + copied, toCopy);
    447    copied += toCopy;
    448  }
    449 
    450  return true;
    451 }
    452 
    453 template <typename AllocPolicy>
    454 char* BufferList<AllocPolicy>::AllocateBytes(size_t aMaxSize, size_t* aSize) {
    455  MOZ_RELEASE_ASSERT(mOwning);
    456  MOZ_RELEASE_ASSERT(mStandardCapacity);
    457 
    458  if (!mSegments.empty()) {
    459    Segment& lastSegment = mSegments.back();
    460 
    461    size_t capacity = lastSegment.mCapacity - lastSegment.mSize;
    462    if (capacity) {
    463      size_t size = std::min(aMaxSize, capacity);
    464      char* data = lastSegment.mData + lastSegment.mSize;
    465 
    466      lastSegment.mSize += size;
    467      mSize += size;
    468 
    469      *aSize = size;
    470      return data;
    471    }
    472  }
    473 
    474  size_t size = std::min(aMaxSize, mStandardCapacity);
    475  char* data = AllocateSegment(size, mStandardCapacity);
    476  if (data) {
    477    *aSize = size;
    478  }
    479  return data;
    480 }
    481 
    482 template <typename AllocPolicy>
    483 bool BufferList<AllocPolicy>::ReadBytes(IterImpl& aIter, char* aData,
    484                                        size_t aSize) const {
    485  size_t copied = 0;
    486  size_t remaining = aSize;
    487  while (remaining) {
    488    size_t toCopy = std::min(aIter.RemainingInSegment(), remaining);
    489    if (!toCopy) {
    490      // We've run out of data in the last segment.
    491      return false;
    492    }
    493    memcpy(aData + copied, aIter.Data(), toCopy);
    494    copied += toCopy;
    495    remaining -= toCopy;
    496 
    497    aIter.Advance(*this, toCopy);
    498  }
    499 
    500  return true;
    501 }
    502 
    503 template <typename AllocPolicy>
    504 template <typename BorrowingAllocPolicy>
    505 BufferList<BorrowingAllocPolicy> BufferList<AllocPolicy>::Borrow(
    506    IterImpl& aIter, size_t aSize, bool* aSuccess,
    507    BorrowingAllocPolicy aAP) const {
    508  BufferList<BorrowingAllocPolicy> result(aAP);
    509 
    510  size_t size = aSize;
    511  while (size) {
    512    size_t toAdvance = std::min(size, aIter.RemainingInSegment());
    513 
    514    if (!toAdvance || !result.mSegments.append(
    515                          typename BufferList<BorrowingAllocPolicy>::Segment(
    516                              aIter.mData, toAdvance, toAdvance))) {
    517      *aSuccess = false;
    518      return result;
    519    }
    520    aIter.Advance(*this, toAdvance);
    521    size -= toAdvance;
    522  }
    523 
    524  result.mSize = aSize;
    525  *aSuccess = true;
    526  return result;
    527 }
    528 
    529 template <typename AllocPolicy>
    530 template <typename OtherAllocPolicy>
    531 BufferList<OtherAllocPolicy> BufferList<AllocPolicy>::MoveFallible(
    532    bool* aSuccess, OtherAllocPolicy aAP) {
    533  BufferList<OtherAllocPolicy> result(0, 0, mStandardCapacity, aAP);
    534 
    535  IterImpl iter = Iter();
    536  while (!iter.Done()) {
    537    size_t toAdvance = iter.RemainingInSegment();
    538 
    539    if (!toAdvance ||
    540        !result.mSegments.append(typename BufferList<OtherAllocPolicy>::Segment(
    541            iter.mData, toAdvance, toAdvance))) {
    542      *aSuccess = false;
    543      result.mSegments.clear();
    544      return result;
    545    }
    546    iter.Advance(*this, toAdvance);
    547  }
    548 
    549  result.mSize = mSize;
    550  mSegments.clear();
    551  mSize = 0;
    552  *aSuccess = true;
    553  return result;
    554 }
    555 
    556 template <typename AllocPolicy>
    557 size_t BufferList<AllocPolicy>::Truncate(IterImpl& aIter) {
    558  MOZ_ASSERT(aIter.IsIn(*this) || aIter.Done());
    559  if (aIter.Done()) {
    560    return 0;
    561  }
    562 
    563  size_t prevSize = mSize;
    564 
    565  // Remove any segments after the iterator's current segment.
    566  while (mSegments.length() > aIter.mSegment + 1) {
    567    Segment& toFree = mSegments.back();
    568    mSize -= toFree.mSize;
    569    if (mOwning) {
    570      this->free_(toFree.mData, toFree.mCapacity);
    571    }
    572    mSegments.popBack();
    573  }
    574 
    575  // The last segment is now aIter's current segment. Truncate or remove it.
    576  Segment& seg = mSegments.back();
    577  MOZ_ASSERT(aIter.mDataEnd == seg.End());
    578  mSize -= aIter.RemainingInSegment();
    579  seg.mSize -= aIter.RemainingInSegment();
    580  if (!seg.mSize) {
    581    if (mOwning) {
    582      this->free_(seg.mData, seg.mCapacity);
    583    }
    584    mSegments.popBack();
    585  }
    586 
    587  // Correct `aIter` to point to the new end of the BufferList.
    588  if (mSegments.empty()) {
    589    MOZ_ASSERT(mSize == 0);
    590    aIter.mSegment = 0;
    591    aIter.mData = aIter.mDataEnd = nullptr;
    592  } else {
    593    aIter.mSegment = mSegments.length() - 1;
    594    aIter.mData = aIter.mDataEnd = mSegments.back().End();
    595  }
    596  MOZ_ASSERT(aIter.Done());
    597 
    598  AssertConsistentSize();
    599  return prevSize - mSize;
    600 }
    601 
    602 }  // namespace mozilla
    603 
    604 #endif /* mozilla_BufferList_h */