fune/mfbt/tests/TestBufferList.cpp
Nika Layzell 0e22f3df51 Bug 1717808 - aIter must be valid after extracting last segment of a BufferList, r=glandium
When advancing to Beta, we stop adding sentinels after serialized data
in IPC::Message objects. These sentinels would cause all Extract calls
to not reach the end of the message buffer on Nightly. This patch fixes
an assertion failure which can occur when extract calls fully empty the
buffer, and the finished iterator is advanced by 0 bytes.

Differential Revision: https://phabricator.services.mozilla.com/D118838
2021-07-07 18:03:03 +00:00

435 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 9; 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/. */
// This is included first to ensure it doesn't implicitly depend on anything
// else.
#include "mozilla/BufferList.h"
// It would be nice if we could use the InfallibleAllocPolicy from mozalloc,
// but MFBT cannot use mozalloc.
class InfallibleAllocPolicy {
public:
template <typename T>
T* pod_malloc(size_t aNumElems) {
if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
MOZ_CRASH("TestBufferList.cpp: overflow");
}
T* rv = static_cast<T*>(malloc(aNumElems * sizeof(T)));
if (!rv) {
MOZ_CRASH("TestBufferList.cpp: out of memory");
}
return rv;
}
template <typename T>
void free_(T* aPtr, size_t aNumElems = 0) {
free(aPtr);
}
void reportAllocOverflow() const {}
bool checkSimulatedOOM() const { return true; }
};
typedef mozilla::BufferList<InfallibleAllocPolicy> BufferList;
int main(void) {
const size_t kInitialSize = 16;
const size_t kInitialCapacity = 24;
const size_t kStandardCapacity = 32;
BufferList bl(kInitialSize, kInitialCapacity, kStandardCapacity);
memset(bl.Start(), 0x0c, kInitialSize);
MOZ_RELEASE_ASSERT(bl.Size() == kInitialSize);
// Simple iteration and access.
BufferList::IterImpl iter(bl.Iter());
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() == kInitialSize);
MOZ_RELEASE_ASSERT(iter.HasRoomFor(kInitialSize));
MOZ_RELEASE_ASSERT(!iter.HasRoomFor(kInitialSize + 1));
MOZ_RELEASE_ASSERT(!iter.HasRoomFor(size_t(-1)));
MOZ_RELEASE_ASSERT(*iter.Data() == 0x0c);
MOZ_RELEASE_ASSERT(!iter.Done());
iter.Advance(bl, 4);
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() == kInitialSize - 4);
MOZ_RELEASE_ASSERT(iter.HasRoomFor(kInitialSize - 4));
MOZ_RELEASE_ASSERT(*iter.Data() == 0x0c);
MOZ_RELEASE_ASSERT(!iter.Done());
iter.Advance(bl, 11);
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() == kInitialSize - 4 - 11);
MOZ_RELEASE_ASSERT(iter.HasRoomFor(kInitialSize - 4 - 11));
MOZ_RELEASE_ASSERT(!iter.HasRoomFor(kInitialSize - 4 - 11 + 1));
MOZ_RELEASE_ASSERT(*iter.Data() == 0x0c);
MOZ_RELEASE_ASSERT(!iter.Done());
iter.Advance(bl, kInitialSize - 4 - 11);
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() == 0);
MOZ_RELEASE_ASSERT(!iter.HasRoomFor(1));
MOZ_RELEASE_ASSERT(iter.Done());
// Writing to the buffer.
const size_t kSmallWrite = 16;
char toWrite[kSmallWrite];
memset(toWrite, 0x0a, kSmallWrite);
MOZ_ALWAYS_TRUE(bl.WriteBytes(toWrite, kSmallWrite));
MOZ_RELEASE_ASSERT(bl.Size() == kInitialSize + kSmallWrite);
iter = bl.Iter();
iter.Advance(bl, kInitialSize);
MOZ_RELEASE_ASSERT(!iter.Done());
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() ==
kInitialCapacity - kInitialSize);
MOZ_RELEASE_ASSERT(iter.HasRoomFor(kInitialCapacity - kInitialSize));
MOZ_RELEASE_ASSERT(*iter.Data() == 0x0a);
// AdvanceAcrossSegments.
iter = bl.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl, kInitialCapacity - 4));
MOZ_RELEASE_ASSERT(!iter.Done());
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() == 4);
MOZ_RELEASE_ASSERT(iter.HasRoomFor(4));
MOZ_RELEASE_ASSERT(*iter.Data() == 0x0a);
iter = bl.Iter();
MOZ_RELEASE_ASSERT(
iter.AdvanceAcrossSegments(bl, kInitialSize + kSmallWrite - 4));
MOZ_RELEASE_ASSERT(!iter.Done());
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() == 4);
MOZ_RELEASE_ASSERT(iter.HasRoomFor(4));
MOZ_RELEASE_ASSERT(*iter.Data() == 0x0a);
MOZ_RELEASE_ASSERT(
bl.Iter().AdvanceAcrossSegments(bl, kInitialSize + kSmallWrite - 1));
MOZ_RELEASE_ASSERT(
bl.Iter().AdvanceAcrossSegments(bl, kInitialSize + kSmallWrite));
MOZ_RELEASE_ASSERT(
!bl.Iter().AdvanceAcrossSegments(bl, kInitialSize + kSmallWrite + 1));
MOZ_RELEASE_ASSERT(!bl.Iter().AdvanceAcrossSegments(bl, size_t(-1)));
// Reading non-contiguous bytes.
char toRead[kSmallWrite];
iter = bl.Iter();
iter.Advance(bl, kInitialSize);
bl.ReadBytes(iter, toRead, kSmallWrite);
MOZ_RELEASE_ASSERT(memcmp(toRead, toWrite, kSmallWrite) == 0);
MOZ_RELEASE_ASSERT(iter.Done());
// Make sure reading up to the end of a segment advances the iter to the next
// segment.
iter = bl.Iter();
bl.ReadBytes(iter, toRead, kInitialSize);
MOZ_RELEASE_ASSERT(!iter.Done());
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() ==
kInitialCapacity - kInitialSize);
const size_t kBigWrite = 1024;
char* toWriteBig = static_cast<char*>(malloc(kBigWrite));
for (unsigned i = 0; i < kBigWrite; i++) {
toWriteBig[i] = i % 37;
}
MOZ_ALWAYS_TRUE(bl.WriteBytes(toWriteBig, kBigWrite));
char* toReadBig = static_cast<char*>(malloc(kBigWrite));
iter = bl.Iter();
MOZ_RELEASE_ASSERT(
iter.AdvanceAcrossSegments(bl, kInitialSize + kSmallWrite));
bl.ReadBytes(iter, toReadBig, kBigWrite);
MOZ_RELEASE_ASSERT(memcmp(toReadBig, toWriteBig, kBigWrite) == 0);
MOZ_RELEASE_ASSERT(iter.Done());
free(toReadBig);
free(toWriteBig);
// Currently bl contains these segments:
// #0: offset 0, [0x0c]*16 + [0x0a]*8, size 24
// #1: offset 24, [0x0a]*8 + [i%37 for i in 0..24], size 32
// #2: offset 56, [i%37 for i in 24..56, size 32
// ...
// #32: offset 1016, [i%37 for i in 984..1016], size 32
// #33: offset 1048, [i%37 for i in 1016..1024], size 8
static size_t kTotalSize = kInitialSize + kSmallWrite + kBigWrite;
MOZ_RELEASE_ASSERT(bl.Size() == kTotalSize);
static size_t kLastSegmentSize =
(kTotalSize - kInitialCapacity) % kStandardCapacity;
iter = bl.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(
bl, kTotalSize - kLastSegmentSize - kStandardCapacity));
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() == kStandardCapacity);
iter.Advance(bl, kStandardCapacity);
MOZ_RELEASE_ASSERT(iter.RemainingInSegment() == kLastSegmentSize);
MOZ_RELEASE_ASSERT(
unsigned(*iter.Data()) ==
(kTotalSize - kLastSegmentSize - kInitialSize - kSmallWrite) % 37);
// Clear.
bl.Clear();
MOZ_RELEASE_ASSERT(bl.Size() == 0);
MOZ_RELEASE_ASSERT(bl.Iter().Done());
// Move assignment.
const size_t kSmallCapacity = 8;
BufferList bl2(0, kSmallCapacity, kSmallCapacity);
MOZ_ALWAYS_TRUE(bl2.WriteBytes(toWrite, kSmallWrite));
MOZ_ALWAYS_TRUE(bl2.WriteBytes(toWrite, kSmallWrite));
MOZ_ALWAYS_TRUE(bl2.WriteBytes(toWrite, kSmallWrite));
bl = std::move(bl2);
MOZ_RELEASE_ASSERT(bl2.Size() == 0);
MOZ_RELEASE_ASSERT(bl2.Iter().Done());
iter = bl.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl, kSmallWrite * 3));
MOZ_RELEASE_ASSERT(iter.Done());
// MoveFallible
bool success;
bl2 = bl.MoveFallible<InfallibleAllocPolicy>(&success);
MOZ_RELEASE_ASSERT(success);
MOZ_RELEASE_ASSERT(bl.Size() == 0);
MOZ_RELEASE_ASSERT(bl.Iter().Done());
MOZ_RELEASE_ASSERT(bl2.Size() == kSmallWrite * 3);
iter = bl2.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl2, kSmallWrite * 3));
MOZ_RELEASE_ASSERT(iter.Done());
bl = bl2.MoveFallible<InfallibleAllocPolicy>(&success);
// Borrowing.
const size_t kBorrowStart = 4;
const size_t kBorrowSize = 24;
iter = bl.Iter();
iter.Advance(bl, kBorrowStart);
bl2 = bl.Borrow<InfallibleAllocPolicy>(iter, kBorrowSize, &success);
MOZ_RELEASE_ASSERT(success);
MOZ_RELEASE_ASSERT(bl2.Size() == kBorrowSize);
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(
bl, kSmallWrite * 3 - kBorrowSize - kBorrowStart));
MOZ_RELEASE_ASSERT(iter.Done());
iter = bl2.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl2, kBorrowSize));
MOZ_RELEASE_ASSERT(iter.Done());
BufferList::IterImpl iter1(bl.Iter()), iter2(bl2.Iter());
iter1.Advance(bl, kBorrowStart);
MOZ_RELEASE_ASSERT(iter1.Data() == iter2.Data());
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl, kBorrowSize - 5));
MOZ_RELEASE_ASSERT(iter2.AdvanceAcrossSegments(bl2, kBorrowSize - 5));
MOZ_RELEASE_ASSERT(iter1.Data() == iter2.Data());
// Extracting.
const size_t kExtractStart = 8;
const size_t kExtractSize = 24;
const size_t kExtractOverSize = 1000;
iter = bl.Iter();
iter.Advance(bl, kExtractStart);
bl2 = bl.Extract(iter, kExtractSize, &success);
MOZ_RELEASE_ASSERT(success);
MOZ_RELEASE_ASSERT(bl2.Size() == kExtractSize);
BufferList bl3 = bl.Extract(iter, kExtractOverSize, &success);
MOZ_RELEASE_ASSERT(!success);
iter = bl2.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl2, kExtractSize));
MOZ_RELEASE_ASSERT(iter.Done());
BufferList bl4(8, 8, 8);
MOZ_ALWAYS_TRUE(bl4.WriteBytes("abcd1234", 8));
iter = bl4.Iter();
iter.Advance(bl4, 8);
BufferList bl5 = bl4.Extract(iter, kExtractSize, &success);
MOZ_RELEASE_ASSERT(!success);
BufferList bl6(0, 0, 16);
MOZ_ALWAYS_TRUE(bl6.WriteBytes("abcdefgh12345678", 16));
MOZ_ALWAYS_TRUE(bl6.WriteBytes("ijklmnop87654321", 16));
iter = bl6.Iter();
iter.Advance(bl6, 8);
BufferList bl7 = bl6.Extract(iter, 16, &success);
MOZ_RELEASE_ASSERT(success);
char data[16];
MOZ_RELEASE_ASSERT(bl6.ReadBytes(iter, data, 8));
MOZ_RELEASE_ASSERT(memcmp(data, "87654321", 8) == 0);
iter = bl7.Iter();
MOZ_RELEASE_ASSERT(bl7.ReadBytes(iter, data, 16));
MOZ_RELEASE_ASSERT(memcmp(data, "12345678ijklmnop", 16) == 0);
BufferList bl8(0, 0, 16);
MOZ_ALWAYS_TRUE(bl8.WriteBytes("abcdefgh12345678", 16));
iter = bl8.Iter();
BufferList bl9 = bl8.Extract(iter, 8, &success);
MOZ_RELEASE_ASSERT(success);
MOZ_RELEASE_ASSERT(bl9.Size() == 8);
MOZ_RELEASE_ASSERT(!iter.Done());
BufferList bl10(0, 0, 8);
MOZ_ALWAYS_TRUE(bl10.WriteBytes("abcdefgh", 8));
MOZ_ALWAYS_TRUE(bl10.WriteBytes("12345678", 8));
iter = bl10.Iter();
BufferList bl11 = bl10.Extract(iter, 16, &success);
MOZ_RELEASE_ASSERT(success);
MOZ_RELEASE_ASSERT(bl11.Size() == 16);
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl10, 0));
MOZ_RELEASE_ASSERT(iter.Done());
iter = bl11.Iter();
MOZ_RELEASE_ASSERT(bl11.ReadBytes(iter, data, 16));
MOZ_RELEASE_ASSERT(memcmp(data, "abcdefgh12345678", 16) == 0);
// RangeLength.
BufferList bl12(0, 0, 8);
MOZ_ALWAYS_TRUE(bl12.WriteBytes("abcdefgh", 8));
MOZ_ALWAYS_TRUE(bl12.WriteBytes("12345678", 8));
// |iter| is at position 0 (1st segment).
iter = bl12.Iter();
iter1 = bl12.Iter();
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 0);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 4));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 4);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 4));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 8);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 4));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 12);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 3));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 15);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 1));
MOZ_RELEASE_ASSERT(iter1.Done());
// |iter| is at position 1 (1st segment).
iter = bl12.Iter();
iter1 = bl12.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl12, 1));
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 1));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 0);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 4));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 4);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 4));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 8);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 4));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 12);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 2));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 14);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 1));
MOZ_RELEASE_ASSERT(iter1.Done());
// |iter| is at position 8 (2nd segment).
iter = bl12.Iter();
iter1 = bl12.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl12, 8));
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 8));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 0);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 4));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 4);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 3));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 7);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 1));
MOZ_RELEASE_ASSERT(iter1.Done());
// |iter| is at position 9 (2nd segment).
iter = bl12.Iter();
iter1 = bl12.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl12, 9));
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 9));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 0);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 4));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 4);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 2));
MOZ_RELEASE_ASSERT(bl12.RangeLength(iter, iter1) == 6);
MOZ_RELEASE_ASSERT(iter1.AdvanceAcrossSegments(bl12, 1));
MOZ_RELEASE_ASSERT(iter1.Done());
BufferList bl13(0, 0, 8);
MOZ_ALWAYS_TRUE(bl13.WriteBytes("abcdefgh", 8));
MOZ_ALWAYS_TRUE(bl13.WriteBytes("12345678", 8));
MOZ_ALWAYS_TRUE(bl13.WriteBytes("ABCDEFGH", 8));
MOZ_RELEASE_ASSERT(bl13.Size() == 24);
// At segment border
iter = bl13.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl13, 8));
MOZ_RELEASE_ASSERT(bl13.Truncate(iter) == 16);
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(bl13.Size() == 8);
// Restore state
MOZ_ALWAYS_TRUE(bl13.WriteBytes("12345678", 8));
MOZ_ALWAYS_TRUE(bl13.WriteBytes("ABCDEFGH", 8));
MOZ_RELEASE_ASSERT(bl13.Size() == 24);
// Before segment border
iter = bl13.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl13, 7));
MOZ_RELEASE_ASSERT(bl13.Truncate(iter) == 17);
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(bl13.Size() == 7);
// Restore state
MOZ_ALWAYS_TRUE(bl13.WriteBytes("h", 1));
MOZ_ALWAYS_TRUE(bl13.WriteBytes("12345678", 8));
MOZ_ALWAYS_TRUE(bl13.WriteBytes("ABCDEFGH", 8));
MOZ_RELEASE_ASSERT(bl13.Size() == 24);
// In last segment
iter = bl13.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl13, 20));
MOZ_RELEASE_ASSERT(bl13.Truncate(iter) == 4);
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(bl13.Size() == 20);
// No-op truncate
MOZ_RELEASE_ASSERT(bl13.Truncate(iter) == 0);
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(bl13.Size() == 20);
// No-op truncate with fresh iterator
iter = bl13.Iter();
MOZ_RELEASE_ASSERT(iter.AdvanceAcrossSegments(bl13, 20));
MOZ_RELEASE_ASSERT(bl13.Truncate(iter) == 0);
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(bl13.Size() == 20);
// Truncate at start of buffer
iter = bl13.Iter();
MOZ_RELEASE_ASSERT(bl13.Truncate(iter) == 20);
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(bl13.Size() == 0);
// No-op truncate at start of buffer
iter = bl13.Iter();
MOZ_RELEASE_ASSERT(bl13.Truncate(iter) == 0);
MOZ_RELEASE_ASSERT(iter.Done());
MOZ_RELEASE_ASSERT(bl13.Size() == 0);
return 0;
}