While I think this is a performance improvement over the original, this actually fixes a correctness issue: For an appendable underlying stream, padToAlignment would fail if the additional padding would have caused the stream to grow since it was doing its own check on bounds. By deferring to the regular writeArray method this takes the same path as everything else, which does the correct bounds check in WritableBinaryStreamRef::checkOffsetForWrite (i.e. skips the extension check if BSF_Append is set). I had started to fix the existing bounds check in BinaryStreamWriter but deferred to this because it layered better and is more efficient/consistent. It didn't look like this method was tested at all, so I added a unit test. Differential Revision: https://reviews.llvm.org/D124746
948 lines
31 KiB
C++
948 lines
31 KiB
C++
//===- llvm/unittest/Support/BinaryStreamTest.cpp -------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/Support/Allocator.h"
|
|
#include "llvm/Support/BinaryByteStream.h"
|
|
#include "llvm/Support/BinaryItemStream.h"
|
|
#include "llvm/Support/BinaryStreamArray.h"
|
|
#include "llvm/Support/BinaryStreamReader.h"
|
|
#include "llvm/Support/BinaryStreamRef.h"
|
|
#include "llvm/Support/BinaryStreamWriter.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::support;
|
|
|
|
namespace {
|
|
|
|
class BrokenStream : public WritableBinaryStream {
|
|
public:
|
|
BrokenStream(MutableArrayRef<uint8_t> Data, endianness Endian, uint32_t Align)
|
|
: Data(Data), PartitionIndex(alignDown(Data.size() / 2, Align)),
|
|
Endian(Endian) {}
|
|
|
|
endianness getEndian() const override { return Endian; }
|
|
|
|
Error readBytes(uint64_t Offset, uint64_t Size,
|
|
ArrayRef<uint8_t> &Buffer) override {
|
|
if (auto EC = checkOffsetForRead(Offset, Size))
|
|
return EC;
|
|
uint64_t S = startIndex(Offset);
|
|
auto Ref = Data.drop_front(S);
|
|
if (Ref.size() >= Size) {
|
|
Buffer = Ref.take_front(Size);
|
|
return Error::success();
|
|
}
|
|
|
|
uint64_t BytesLeft = Size - Ref.size();
|
|
uint8_t *Ptr = Allocator.Allocate<uint8_t>(Size);
|
|
::memcpy(Ptr, Ref.data(), Ref.size());
|
|
::memcpy(Ptr + Ref.size(), Data.data(), BytesLeft);
|
|
Buffer = makeArrayRef<uint8_t>(Ptr, Size);
|
|
return Error::success();
|
|
}
|
|
|
|
Error readLongestContiguousChunk(uint64_t Offset,
|
|
ArrayRef<uint8_t> &Buffer) override {
|
|
if (auto EC = checkOffsetForRead(Offset, 1))
|
|
return EC;
|
|
uint64_t S = startIndex(Offset);
|
|
Buffer = Data.drop_front(S);
|
|
return Error::success();
|
|
}
|
|
|
|
uint64_t getLength() override { return Data.size(); }
|
|
|
|
Error writeBytes(uint64_t Offset, ArrayRef<uint8_t> SrcData) override {
|
|
if (auto EC = checkOffsetForWrite(Offset, SrcData.size()))
|
|
return EC;
|
|
if (SrcData.empty())
|
|
return Error::success();
|
|
|
|
uint64_t S = startIndex(Offset);
|
|
MutableArrayRef<uint8_t> Ref(Data);
|
|
Ref = Ref.drop_front(S);
|
|
if (Ref.size() >= SrcData.size()) {
|
|
::memcpy(Ref.data(), SrcData.data(), SrcData.size());
|
|
return Error::success();
|
|
}
|
|
|
|
uint64_t BytesLeft = SrcData.size() - Ref.size();
|
|
::memcpy(Ref.data(), SrcData.data(), Ref.size());
|
|
::memcpy(&Data[0], SrcData.data() + Ref.size(), BytesLeft);
|
|
return Error::success();
|
|
}
|
|
Error commit() override { return Error::success(); }
|
|
|
|
private:
|
|
uint64_t startIndex(uint64_t Offset) const {
|
|
return (Offset + PartitionIndex) % Data.size();
|
|
}
|
|
|
|
uint64_t endIndex(uint64_t Offset, uint64_t Size) const {
|
|
return (startIndex(Offset) + Size - 1) % Data.size();
|
|
}
|
|
|
|
// Buffer is organized like this:
|
|
// -------------------------------------------------
|
|
// | N/2 | N/2+1 | ... | N-1 | 0 | 1 | ... | N/2-1 |
|
|
// -------------------------------------------------
|
|
// So reads from the beginning actually come from the middle.
|
|
MutableArrayRef<uint8_t> Data;
|
|
uint32_t PartitionIndex = 0;
|
|
endianness Endian;
|
|
BumpPtrAllocator Allocator;
|
|
};
|
|
|
|
constexpr endianness Endians[] = {big, little, native};
|
|
constexpr uint32_t NumEndians = llvm::array_lengthof(Endians);
|
|
constexpr uint32_t NumStreams = 2 * NumEndians;
|
|
|
|
class BinaryStreamTest : public testing::Test {
|
|
|
|
public:
|
|
BinaryStreamTest() {}
|
|
|
|
void SetUp() override {
|
|
Streams.clear();
|
|
Streams.resize(NumStreams);
|
|
for (uint32_t I = 0; I < NumStreams; ++I)
|
|
Streams[I].IsContiguous = (I % 2 == 0);
|
|
|
|
InputData.clear();
|
|
OutputData.clear();
|
|
}
|
|
|
|
protected:
|
|
struct StreamPair {
|
|
bool IsContiguous;
|
|
std::unique_ptr<BinaryStream> Input;
|
|
std::unique_ptr<WritableBinaryStream> Output;
|
|
};
|
|
|
|
void initializeInput(ArrayRef<uint8_t> Input, uint32_t Align) {
|
|
InputData = Input;
|
|
|
|
BrokenInputData.resize(InputData.size());
|
|
if (!Input.empty()) {
|
|
uint64_t PartitionIndex = alignDown(InputData.size() / 2, Align);
|
|
uint64_t RightBytes = InputData.size() - PartitionIndex;
|
|
uint64_t LeftBytes = PartitionIndex;
|
|
if (RightBytes > 0)
|
|
::memcpy(&BrokenInputData[PartitionIndex], Input.data(), RightBytes);
|
|
if (LeftBytes > 0)
|
|
::memcpy(&BrokenInputData[0], Input.data() + RightBytes, LeftBytes);
|
|
}
|
|
|
|
for (uint32_t I = 0; I < NumEndians; ++I) {
|
|
auto InByteStream =
|
|
std::make_unique<BinaryByteStream>(InputData, Endians[I]);
|
|
auto InBrokenStream = std::make_unique<BrokenStream>(
|
|
BrokenInputData, Endians[I], Align);
|
|
|
|
Streams[I * 2].Input = std::move(InByteStream);
|
|
Streams[I * 2 + 1].Input = std::move(InBrokenStream);
|
|
}
|
|
}
|
|
|
|
void initializeOutput(uint64_t Size, uint32_t Align) {
|
|
OutputData.resize(Size);
|
|
BrokenOutputData.resize(Size);
|
|
|
|
for (uint32_t I = 0; I < NumEndians; ++I) {
|
|
Streams[I * 2].Output =
|
|
std::make_unique<MutableBinaryByteStream>(OutputData, Endians[I]);
|
|
Streams[I * 2 + 1].Output = std::make_unique<BrokenStream>(
|
|
BrokenOutputData, Endians[I], Align);
|
|
}
|
|
}
|
|
|
|
void initializeOutputFromInput(uint32_t Align) {
|
|
for (uint32_t I = 0; I < NumEndians; ++I) {
|
|
Streams[I * 2].Output =
|
|
std::make_unique<MutableBinaryByteStream>(InputData, Endians[I]);
|
|
Streams[I * 2 + 1].Output = std::make_unique<BrokenStream>(
|
|
BrokenInputData, Endians[I], Align);
|
|
}
|
|
}
|
|
|
|
void initializeInputFromOutput(uint32_t Align) {
|
|
for (uint32_t I = 0; I < NumEndians; ++I) {
|
|
Streams[I * 2].Input =
|
|
std::make_unique<BinaryByteStream>(OutputData, Endians[I]);
|
|
Streams[I * 2 + 1].Input = std::make_unique<BrokenStream>(
|
|
BrokenOutputData, Endians[I], Align);
|
|
}
|
|
}
|
|
|
|
std::vector<uint8_t> InputData;
|
|
std::vector<uint8_t> BrokenInputData;
|
|
|
|
std::vector<uint8_t> OutputData;
|
|
std::vector<uint8_t> BrokenOutputData;
|
|
|
|
std::vector<StreamPair> Streams;
|
|
};
|
|
|
|
// Tests that a we can read from a BinaryByteStream without a StreamReader.
|
|
TEST_F(BinaryStreamTest, BinaryByteStreamBounds) {
|
|
std::vector<uint8_t> InputData = {1, 2, 3, 4, 5};
|
|
initializeInput(InputData, 1);
|
|
|
|
for (auto &Stream : Streams) {
|
|
ArrayRef<uint8_t> Buffer;
|
|
|
|
// 1. If the read fits it should work.
|
|
ASSERT_EQ(InputData.size(), Stream.Input->getLength());
|
|
ASSERT_THAT_ERROR(Stream.Input->readBytes(2, 1, Buffer), Succeeded());
|
|
EXPECT_EQ(makeArrayRef(InputData).slice(2, 1), Buffer);
|
|
ASSERT_THAT_ERROR(Stream.Input->readBytes(0, 4, Buffer), Succeeded());
|
|
EXPECT_EQ(makeArrayRef(InputData).slice(0, 4), Buffer);
|
|
|
|
// 2. Reading past the bounds of the input should fail.
|
|
EXPECT_THAT_ERROR(Stream.Input->readBytes(4, 2, Buffer), Failed());
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamRefBounds) {
|
|
std::vector<uint8_t> InputData = {1, 2, 3, 4, 5};
|
|
initializeInput(InputData, 1);
|
|
|
|
for (const auto &Stream : Streams) {
|
|
ArrayRef<uint8_t> Buffer;
|
|
BinaryStreamRef Ref(*Stream.Input);
|
|
|
|
// Read 1 byte from offset 2 should work
|
|
ASSERT_EQ(InputData.size(), Ref.getLength());
|
|
ASSERT_THAT_ERROR(Ref.readBytes(2, 1, Buffer), Succeeded());
|
|
EXPECT_EQ(makeArrayRef(InputData).slice(2, 1), Buffer);
|
|
|
|
// Reading everything from offset 2 on.
|
|
ASSERT_THAT_ERROR(Ref.readLongestContiguousChunk(2, Buffer), Succeeded());
|
|
if (Stream.IsContiguous)
|
|
EXPECT_EQ(makeArrayRef(InputData).slice(2), Buffer);
|
|
else
|
|
EXPECT_FALSE(Buffer.empty());
|
|
|
|
// Reading 6 bytes from offset 0 is too big.
|
|
EXPECT_THAT_ERROR(Ref.readBytes(0, 6, Buffer), Failed());
|
|
EXPECT_THAT_ERROR(Ref.readLongestContiguousChunk(6, Buffer), Failed());
|
|
|
|
// Reading 1 byte from offset 2 after dropping 1 byte is the same as reading
|
|
// 1 byte from offset 3.
|
|
Ref = Ref.drop_front(1);
|
|
ASSERT_THAT_ERROR(Ref.readBytes(2, 1, Buffer), Succeeded());
|
|
if (Stream.IsContiguous)
|
|
EXPECT_EQ(makeArrayRef(InputData).slice(3, 1), Buffer);
|
|
else
|
|
EXPECT_FALSE(Buffer.empty());
|
|
|
|
// Reading everything from offset 2 on after dropping 1 byte.
|
|
ASSERT_THAT_ERROR(Ref.readLongestContiguousChunk(2, Buffer), Succeeded());
|
|
if (Stream.IsContiguous)
|
|
EXPECT_EQ(makeArrayRef(InputData).slice(3), Buffer);
|
|
else
|
|
EXPECT_FALSE(Buffer.empty());
|
|
|
|
// Reading 2 bytes from offset 2 after dropping 2 bytes is the same as
|
|
// reading 2 bytes from offset 4, and should fail.
|
|
Ref = Ref.drop_front(1);
|
|
EXPECT_THAT_ERROR(Ref.readBytes(2, 2, Buffer), Failed());
|
|
|
|
// But if we read the longest contiguous chunk instead, we should still
|
|
// get the 1 byte at the end.
|
|
ASSERT_THAT_ERROR(Ref.readLongestContiguousChunk(2, Buffer), Succeeded());
|
|
EXPECT_EQ(makeArrayRef(InputData).take_back(), Buffer);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamRefDynamicSize) {
|
|
StringRef Strings[] = {"1", "2", "3", "4"};
|
|
AppendingBinaryByteStream Stream(support::little);
|
|
|
|
BinaryStreamWriter Writer(Stream);
|
|
BinaryStreamReader Reader(Stream);
|
|
const uint8_t *Byte;
|
|
StringRef Str;
|
|
|
|
// When the stream is empty, it should report a 0 length and we should get an
|
|
// error trying to read even 1 byte from it.
|
|
BinaryStreamRef ConstRef(Stream);
|
|
EXPECT_EQ(0U, ConstRef.getLength());
|
|
EXPECT_THAT_ERROR(Reader.readObject(Byte), Failed());
|
|
|
|
// But if we write to it, its size should increase and we should be able to
|
|
// read not just a byte, but the string that was written.
|
|
EXPECT_THAT_ERROR(Writer.writeCString(Strings[0]), Succeeded());
|
|
EXPECT_EQ(2U, ConstRef.getLength());
|
|
EXPECT_THAT_ERROR(Reader.readObject(Byte), Succeeded());
|
|
|
|
Reader.setOffset(0);
|
|
EXPECT_THAT_ERROR(Reader.readCString(Str), Succeeded());
|
|
EXPECT_EQ(Str, Strings[0]);
|
|
|
|
// If we drop some bytes from the front, we should still track the length as
|
|
// the
|
|
// underlying stream grows.
|
|
BinaryStreamRef Dropped = ConstRef.drop_front(1);
|
|
EXPECT_EQ(1U, Dropped.getLength());
|
|
|
|
EXPECT_THAT_ERROR(Writer.writeCString(Strings[1]), Succeeded());
|
|
EXPECT_EQ(4U, ConstRef.getLength());
|
|
EXPECT_EQ(3U, Dropped.getLength());
|
|
|
|
// If we drop zero bytes from the back, we should continue tracking the
|
|
// length.
|
|
Dropped = Dropped.drop_back(0);
|
|
EXPECT_THAT_ERROR(Writer.writeCString(Strings[2]), Succeeded());
|
|
EXPECT_EQ(6U, ConstRef.getLength());
|
|
EXPECT_EQ(5U, Dropped.getLength());
|
|
|
|
// If we drop non-zero bytes from the back, we should stop tracking the
|
|
// length.
|
|
Dropped = Dropped.drop_back(1);
|
|
EXPECT_THAT_ERROR(Writer.writeCString(Strings[3]), Succeeded());
|
|
EXPECT_EQ(8U, ConstRef.getLength());
|
|
EXPECT_EQ(4U, Dropped.getLength());
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, DropOperations) {
|
|
std::vector<uint8_t> InputData = {1, 2, 3, 4, 5, 4, 3, 2, 1};
|
|
auto RefData = makeArrayRef(InputData);
|
|
initializeInput(InputData, 1);
|
|
|
|
ArrayRef<uint8_t> Result;
|
|
BinaryStreamRef Original(InputData, support::little);
|
|
ASSERT_EQ(InputData.size(), Original.getLength());
|
|
|
|
EXPECT_THAT_ERROR(Original.readBytes(0, InputData.size(), Result),
|
|
Succeeded());
|
|
EXPECT_EQ(RefData, Result);
|
|
|
|
auto Dropped = Original.drop_front(2);
|
|
EXPECT_THAT_ERROR(Dropped.readBytes(0, Dropped.getLength(), Result),
|
|
Succeeded());
|
|
EXPECT_EQ(RefData.drop_front(2), Result);
|
|
|
|
Dropped = Original.drop_back(2);
|
|
EXPECT_THAT_ERROR(Dropped.readBytes(0, Dropped.getLength(), Result),
|
|
Succeeded());
|
|
EXPECT_EQ(RefData.drop_back(2), Result);
|
|
|
|
Dropped = Original.keep_front(2);
|
|
EXPECT_THAT_ERROR(Dropped.readBytes(0, Dropped.getLength(), Result),
|
|
Succeeded());
|
|
EXPECT_EQ(RefData.take_front(2), Result);
|
|
|
|
Dropped = Original.keep_back(2);
|
|
EXPECT_THAT_ERROR(Dropped.readBytes(0, Dropped.getLength(), Result),
|
|
Succeeded());
|
|
EXPECT_EQ(RefData.take_back(2), Result);
|
|
|
|
Dropped = Original.drop_symmetric(2);
|
|
EXPECT_THAT_ERROR(Dropped.readBytes(0, Dropped.getLength(), Result),
|
|
Succeeded());
|
|
EXPECT_EQ(RefData.drop_front(2).drop_back(2), Result);
|
|
}
|
|
|
|
// Test that we can write to a BinaryStream without a StreamWriter.
|
|
TEST_F(BinaryStreamTest, MutableBinaryByteStreamBounds) {
|
|
std::vector<uint8_t> InputData = {'T', 'e', 's', 't', '\0'};
|
|
initializeInput(InputData, 1);
|
|
initializeOutput(InputData.size(), 1);
|
|
|
|
// For every combination of input stream and output stream.
|
|
for (auto &Stream : Streams) {
|
|
ASSERT_EQ(InputData.size(), Stream.Input->getLength());
|
|
|
|
// 1. Try two reads that are supposed to work. One from offset 0, and one
|
|
// from the middle.
|
|
uint32_t Offsets[] = {0, 3};
|
|
for (auto Offset : Offsets) {
|
|
uint64_t ExpectedSize = Stream.Input->getLength() - Offset;
|
|
|
|
// Read everything from Offset until the end of the input data.
|
|
ArrayRef<uint8_t> Data;
|
|
ASSERT_THAT_ERROR(Stream.Input->readBytes(Offset, ExpectedSize, Data),
|
|
Succeeded());
|
|
ASSERT_EQ(ExpectedSize, Data.size());
|
|
|
|
// Then write it to the destination.
|
|
ASSERT_THAT_ERROR(Stream.Output->writeBytes(0, Data), Succeeded());
|
|
|
|
// Then we read back what we wrote, it should match the corresponding
|
|
// slice of the original input data.
|
|
ArrayRef<uint8_t> Data2;
|
|
ASSERT_THAT_ERROR(Stream.Output->readBytes(Offset, ExpectedSize, Data2),
|
|
Succeeded());
|
|
EXPECT_EQ(makeArrayRef(InputData).drop_front(Offset), Data2);
|
|
}
|
|
|
|
std::vector<uint8_t> BigData = {0, 1, 2, 3, 4};
|
|
// 2. If the write is too big, it should fail.
|
|
EXPECT_THAT_ERROR(Stream.Output->writeBytes(3, BigData), Failed());
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, AppendingStream) {
|
|
AppendingBinaryByteStream Stream(llvm::support::little);
|
|
EXPECT_EQ(0U, Stream.getLength());
|
|
|
|
std::vector<uint8_t> InputData = {'T', 'e', 's', 't', 'T', 'e', 's', 't'};
|
|
auto Test = makeArrayRef(InputData).take_front(4);
|
|
// Writing past the end of the stream is an error.
|
|
EXPECT_THAT_ERROR(Stream.writeBytes(4, Test), Failed());
|
|
|
|
// Writing exactly at the end of the stream is ok.
|
|
EXPECT_THAT_ERROR(Stream.writeBytes(0, Test), Succeeded());
|
|
EXPECT_EQ(Test, Stream.data());
|
|
|
|
// And now that the end of the stream is where we couldn't write before, now
|
|
// we can write.
|
|
EXPECT_THAT_ERROR(Stream.writeBytes(4, Test), Succeeded());
|
|
EXPECT_EQ(MutableArrayRef<uint8_t>(InputData), Stream.data());
|
|
}
|
|
|
|
// Test that FixedStreamArray works correctly.
|
|
TEST_F(BinaryStreamTest, FixedStreamArray) {
|
|
std::vector<uint32_t> Ints = {90823, 12908, 109823, 209823};
|
|
ArrayRef<uint8_t> IntBytes(reinterpret_cast<uint8_t *>(Ints.data()),
|
|
Ints.size() * sizeof(uint32_t));
|
|
|
|
initializeInput(IntBytes, alignof(uint32_t));
|
|
|
|
for (auto &Stream : Streams) {
|
|
ASSERT_EQ(InputData.size(), Stream.Input->getLength());
|
|
|
|
FixedStreamArray<uint32_t> Array(*Stream.Input);
|
|
auto Iter = Array.begin();
|
|
ASSERT_EQ(Ints[0], *Iter++);
|
|
ASSERT_EQ(Ints[1], *Iter++);
|
|
ASSERT_EQ(Ints[2], *Iter++);
|
|
ASSERT_EQ(Ints[3], *Iter++);
|
|
ASSERT_EQ(Array.end(), Iter);
|
|
}
|
|
}
|
|
|
|
// Ensure FixedStreamArrayIterator::operator-> works.
|
|
// Added for coverage of r302257.
|
|
TEST_F(BinaryStreamTest, FixedStreamArrayIteratorArrow) {
|
|
std::vector<std::pair<uint32_t, uint32_t>> Pairs = {{867, 5309}, {555, 1212}};
|
|
ArrayRef<uint8_t> PairBytes(reinterpret_cast<uint8_t *>(Pairs.data()),
|
|
Pairs.size() * sizeof(Pairs[0]));
|
|
|
|
initializeInput(PairBytes, alignof(uint32_t));
|
|
|
|
for (auto &Stream : Streams) {
|
|
ASSERT_EQ(InputData.size(), Stream.Input->getLength());
|
|
|
|
const FixedStreamArray<std::pair<uint32_t, uint32_t>> Array(*Stream.Input);
|
|
auto Iter = Array.begin();
|
|
ASSERT_EQ(Pairs[0].first, Iter->first);
|
|
ASSERT_EQ(Pairs[0].second, Iter->second);
|
|
++Iter;
|
|
ASSERT_EQ(Pairs[1].first, Iter->first);
|
|
ASSERT_EQ(Pairs[1].second, Iter->second);
|
|
++Iter;
|
|
ASSERT_EQ(Array.end(), Iter);
|
|
}
|
|
}
|
|
|
|
// Test that VarStreamArray works correctly.
|
|
TEST_F(BinaryStreamTest, VarStreamArray) {
|
|
StringLiteral Strings("1. Test2. Longer Test3. Really Long Test4. Super "
|
|
"Extra Longest Test Of All");
|
|
ArrayRef<uint8_t> StringBytes(
|
|
reinterpret_cast<const uint8_t *>(Strings.data()), Strings.size());
|
|
initializeInput(StringBytes, 1);
|
|
|
|
struct StringExtractor {
|
|
public:
|
|
Error operator()(BinaryStreamRef Stream, uint32_t &Len, StringRef &Item) {
|
|
if (Index == 0)
|
|
Len = strlen("1. Test");
|
|
else if (Index == 1)
|
|
Len = strlen("2. Longer Test");
|
|
else if (Index == 2)
|
|
Len = strlen("3. Really Long Test");
|
|
else
|
|
Len = strlen("4. Super Extra Longest Test Of All");
|
|
ArrayRef<uint8_t> Bytes;
|
|
if (auto EC = Stream.readBytes(0, Len, Bytes))
|
|
return EC;
|
|
Item =
|
|
StringRef(reinterpret_cast<const char *>(Bytes.data()), Bytes.size());
|
|
++Index;
|
|
return Error::success();
|
|
}
|
|
|
|
uint32_t Index = 0;
|
|
};
|
|
|
|
for (auto &Stream : Streams) {
|
|
VarStreamArray<StringRef, StringExtractor> Array(*Stream.Input);
|
|
auto Iter = Array.begin();
|
|
ASSERT_EQ("1. Test", *Iter++);
|
|
ASSERT_EQ("2. Longer Test", *Iter++);
|
|
ASSERT_EQ("3. Really Long Test", *Iter++);
|
|
ASSERT_EQ("4. Super Extra Longest Test Of All", *Iter++);
|
|
ASSERT_EQ(Array.end(), Iter);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamReaderBounds) {
|
|
std::vector<uint8_t> Bytes;
|
|
|
|
initializeInput(Bytes, 1);
|
|
for (auto &Stream : Streams) {
|
|
StringRef S;
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
EXPECT_EQ(0U, Reader.bytesRemaining());
|
|
EXPECT_THAT_ERROR(Reader.readFixedString(S, 1), Failed());
|
|
}
|
|
|
|
Bytes.resize(5);
|
|
initializeInput(Bytes, 1);
|
|
for (auto &Stream : Streams) {
|
|
StringRef S;
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
EXPECT_EQ(Bytes.size(), Reader.bytesRemaining());
|
|
EXPECT_THAT_ERROR(Reader.readFixedString(S, 5), Succeeded());
|
|
EXPECT_THAT_ERROR(Reader.readFixedString(S, 6), Failed());
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamReaderIntegers) {
|
|
support::ulittle64_t Little{908234};
|
|
support::ubig32_t Big{28907823};
|
|
short NS = 2897;
|
|
int NI = -89723;
|
|
unsigned long NUL = 902309023UL;
|
|
constexpr uint32_t Size =
|
|
sizeof(Little) + sizeof(Big) + sizeof(NS) + sizeof(NI) + sizeof(NUL);
|
|
|
|
initializeOutput(Size, alignof(support::ulittle64_t));
|
|
initializeInputFromOutput(alignof(support::ulittle64_t));
|
|
|
|
for (auto &Stream : Streams) {
|
|
BinaryStreamWriter Writer(*Stream.Output);
|
|
ASSERT_THAT_ERROR(Writer.writeObject(Little), Succeeded());
|
|
ASSERT_THAT_ERROR(Writer.writeObject(Big), Succeeded());
|
|
ASSERT_THAT_ERROR(Writer.writeInteger(NS), Succeeded());
|
|
ASSERT_THAT_ERROR(Writer.writeInteger(NI), Succeeded());
|
|
ASSERT_THAT_ERROR(Writer.writeInteger(NUL), Succeeded());
|
|
|
|
const support::ulittle64_t *Little2;
|
|
const support::ubig32_t *Big2;
|
|
short NS2;
|
|
int NI2;
|
|
unsigned long NUL2;
|
|
|
|
// 1. Reading fields individually.
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
ASSERT_THAT_ERROR(Reader.readObject(Little2), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readObject(Big2), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readInteger(NS2), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readInteger(NI2), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readInteger(NUL2), Succeeded());
|
|
ASSERT_EQ(0U, Reader.bytesRemaining());
|
|
|
|
EXPECT_EQ(Little, *Little2);
|
|
EXPECT_EQ(Big, *Big2);
|
|
EXPECT_EQ(NS, NS2);
|
|
EXPECT_EQ(NI, NI2);
|
|
EXPECT_EQ(NUL, NUL2);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamReaderIntegerArray) {
|
|
// 1. Arrays of integers
|
|
std::vector<int> Ints = {1, 2, 3, 4, 5};
|
|
ArrayRef<uint8_t> IntBytes(reinterpret_cast<uint8_t *>(&Ints[0]),
|
|
Ints.size() * sizeof(int));
|
|
|
|
initializeInput(IntBytes, alignof(int));
|
|
for (auto &Stream : Streams) {
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
ArrayRef<int> IntsRef;
|
|
ASSERT_THAT_ERROR(Reader.readArray(IntsRef, Ints.size()), Succeeded());
|
|
ASSERT_EQ(0U, Reader.bytesRemaining());
|
|
EXPECT_EQ(makeArrayRef(Ints), IntsRef);
|
|
|
|
Reader.setOffset(0);
|
|
FixedStreamArray<int> FixedIntsRef;
|
|
ASSERT_THAT_ERROR(Reader.readArray(FixedIntsRef, Ints.size()), Succeeded());
|
|
ASSERT_EQ(0U, Reader.bytesRemaining());
|
|
ASSERT_EQ(Ints, std::vector<int>(FixedIntsRef.begin(), FixedIntsRef.end()));
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamReaderEnum) {
|
|
enum class MyEnum : int64_t { Foo = -10, Bar = 0, Baz = 10 };
|
|
|
|
std::vector<MyEnum> Enums = {MyEnum::Bar, MyEnum::Baz, MyEnum::Foo};
|
|
|
|
initializeOutput(Enums.size() * sizeof(MyEnum), alignof(MyEnum));
|
|
initializeInputFromOutput(alignof(MyEnum));
|
|
for (auto &Stream : Streams) {
|
|
BinaryStreamWriter Writer(*Stream.Output);
|
|
for (auto Value : Enums)
|
|
ASSERT_THAT_ERROR(Writer.writeEnum(Value), Succeeded());
|
|
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
|
|
FixedStreamArray<MyEnum> FSA;
|
|
|
|
for (size_t I = 0; I < Enums.size(); ++I) {
|
|
MyEnum Value;
|
|
ASSERT_THAT_ERROR(Reader.readEnum(Value), Succeeded());
|
|
EXPECT_EQ(Enums[I], Value);
|
|
}
|
|
ASSERT_EQ(0U, Reader.bytesRemaining());
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamReaderULEB128) {
|
|
std::vector<uint64_t> TestValues = {
|
|
0, // Zero
|
|
0x7F, // One byte
|
|
0xFF, // One byte, all-ones
|
|
0xAAAA, // Two bytes
|
|
0xAAAAAAAA, // Four bytes
|
|
0xAAAAAAAAAAAAAAAA, // Eight bytes
|
|
0xffffffffffffffff // Eight bytess, all-ones
|
|
};
|
|
|
|
// Conservatively assume a 10-byte encoding for each of our LEB128s, with no
|
|
// alignment requirement.
|
|
initializeOutput(10 * TestValues.size(), 1);
|
|
initializeInputFromOutput(1);
|
|
|
|
for (auto &Stream : Streams) {
|
|
// Write fields.
|
|
BinaryStreamWriter Writer(*Stream.Output);
|
|
for (const auto &Value : TestValues)
|
|
ASSERT_THAT_ERROR(Writer.writeULEB128(Value), Succeeded());
|
|
|
|
// Read fields.
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
std::vector<uint64_t> Results;
|
|
Results.resize(TestValues.size());
|
|
for (unsigned I = 0; I != TestValues.size(); ++I)
|
|
ASSERT_THAT_ERROR(Reader.readULEB128(Results[I]), Succeeded());
|
|
|
|
for (unsigned I = 0; I != TestValues.size(); ++I)
|
|
EXPECT_EQ(TestValues[I], Results[I]);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamReaderSLEB128) {
|
|
std::vector<int64_t> TestValues = {
|
|
0, // Zero
|
|
0x7F, // One byte
|
|
-0x7F, // One byte, negative
|
|
0xFF, // One byte, all-ones
|
|
0xAAAA, // Two bytes
|
|
-0xAAAA, // Two bytes, negative
|
|
0xAAAAAAAA, // Four bytes
|
|
-0xAAAAAAAA, // Four bytes, negative
|
|
0x2AAAAAAAAAAAAAAA, // Eight bytes
|
|
-0x7ffffffffffffff // Eight bytess, negative
|
|
};
|
|
|
|
// Conservatively assume a 10-byte encoding for each of our LEB128s, with no
|
|
// alignment requirement.
|
|
initializeOutput(10 * TestValues.size(), 1);
|
|
initializeInputFromOutput(1);
|
|
|
|
for (auto &Stream : Streams) {
|
|
// Write fields.
|
|
BinaryStreamWriter Writer(*Stream.Output);
|
|
for (const auto &Value : TestValues)
|
|
ASSERT_THAT_ERROR(Writer.writeSLEB128(Value), Succeeded());
|
|
|
|
// Read fields.
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
std::vector<int64_t> Results;
|
|
Results.resize(TestValues.size());
|
|
for (unsigned I = 0; I != TestValues.size(); ++I)
|
|
ASSERT_THAT_ERROR(Reader.readSLEB128(Results[I]), Succeeded());
|
|
|
|
for (unsigned I = 0; I != TestValues.size(); ++I)
|
|
EXPECT_EQ(TestValues[I], Results[I]);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamReaderObject) {
|
|
struct Foo {
|
|
int X;
|
|
double Y;
|
|
char Z;
|
|
|
|
bool operator==(const Foo &Other) const {
|
|
return X == Other.X && Y == Other.Y && Z == Other.Z;
|
|
}
|
|
};
|
|
|
|
std::vector<Foo> Foos;
|
|
Foos.push_back({-42, 42.42, 42});
|
|
Foos.push_back({100, 3.1415, static_cast<char>(-89)});
|
|
Foos.push_back({200, 2.718, static_cast<char>(-12) });
|
|
|
|
const uint8_t *Bytes = reinterpret_cast<const uint8_t *>(&Foos[0]);
|
|
|
|
initializeInput(makeArrayRef(Bytes, 3 * sizeof(Foo)), alignof(Foo));
|
|
|
|
for (auto &Stream : Streams) {
|
|
// 1. Reading object pointers.
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
const Foo *FPtrOut = nullptr;
|
|
const Foo *GPtrOut = nullptr;
|
|
const Foo *HPtrOut = nullptr;
|
|
ASSERT_THAT_ERROR(Reader.readObject(FPtrOut), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readObject(GPtrOut), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readObject(HPtrOut), Succeeded());
|
|
EXPECT_EQ(0U, Reader.bytesRemaining());
|
|
EXPECT_EQ(Foos[0], *FPtrOut);
|
|
EXPECT_EQ(Foos[1], *GPtrOut);
|
|
EXPECT_EQ(Foos[2], *HPtrOut);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamReaderStrings) {
|
|
std::vector<uint8_t> Bytes = {'O', 'n', 'e', '\0', 'T', 'w', 'o',
|
|
'\0', 'T', 'h', 'r', 'e', 'e', '\0',
|
|
'F', 'o', 'u', 'r', '\0'};
|
|
initializeInput(Bytes, 1);
|
|
|
|
for (auto &Stream : Streams) {
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
|
|
StringRef S1;
|
|
StringRef S2;
|
|
StringRef S3;
|
|
StringRef S4;
|
|
ASSERT_THAT_ERROR(Reader.readCString(S1), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readCString(S2), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readCString(S3), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readCString(S4), Succeeded());
|
|
ASSERT_EQ(0U, Reader.bytesRemaining());
|
|
|
|
EXPECT_EQ("One", S1);
|
|
EXPECT_EQ("Two", S2);
|
|
EXPECT_EQ("Three", S3);
|
|
EXPECT_EQ("Four", S4);
|
|
|
|
S1 = S2 = S3 = S4 = "";
|
|
Reader.setOffset(0);
|
|
ASSERT_THAT_ERROR(Reader.readFixedString(S1, 3), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.skip(1), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readFixedString(S2, 3), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.skip(1), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readFixedString(S3, 5), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.skip(1), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.readFixedString(S4, 4), Succeeded());
|
|
ASSERT_THAT_ERROR(Reader.skip(1), Succeeded());
|
|
ASSERT_EQ(0U, Reader.bytesRemaining());
|
|
|
|
EXPECT_EQ("One", S1);
|
|
EXPECT_EQ("Two", S2);
|
|
EXPECT_EQ("Three", S3);
|
|
EXPECT_EQ("Four", S4);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamWriterBounds) {
|
|
initializeOutput(5, 1);
|
|
|
|
for (auto &Stream : Streams) {
|
|
BinaryStreamWriter Writer(*Stream.Output);
|
|
|
|
// 1. Can write a string that exactly fills the buffer.
|
|
EXPECT_EQ(5U, Writer.bytesRemaining());
|
|
EXPECT_THAT_ERROR(Writer.writeFixedString("abcde"), Succeeded());
|
|
EXPECT_EQ(0U, Writer.bytesRemaining());
|
|
|
|
// 2. Can write an empty string even when you're full
|
|
EXPECT_THAT_ERROR(Writer.writeFixedString(""), Succeeded());
|
|
EXPECT_THAT_ERROR(Writer.writeFixedString("a"), Failed());
|
|
|
|
// 3. Can't write a string that is one character too long.
|
|
Writer.setOffset(0);
|
|
EXPECT_THAT_ERROR(Writer.writeFixedString("abcdef"), Failed());
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamWriterIntegerArrays) {
|
|
// 3. Arrays of integers
|
|
std::vector<int> SourceInts = {1, 2, 3, 4, 5};
|
|
ArrayRef<uint8_t> SourceBytes(reinterpret_cast<uint8_t *>(&SourceInts[0]),
|
|
SourceInts.size() * sizeof(int));
|
|
|
|
initializeInput(SourceBytes, alignof(int));
|
|
initializeOutputFromInput(alignof(int));
|
|
|
|
for (auto &Stream : Streams) {
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
BinaryStreamWriter Writer(*Stream.Output);
|
|
ArrayRef<int> Ints;
|
|
ArrayRef<int> Ints2;
|
|
// First read them, then write them, then read them back.
|
|
ASSERT_THAT_ERROR(Reader.readArray(Ints, SourceInts.size()), Succeeded());
|
|
ASSERT_THAT_ERROR(Writer.writeArray(Ints), Succeeded());
|
|
|
|
BinaryStreamReader ReaderBacker(*Stream.Output);
|
|
ASSERT_THAT_ERROR(ReaderBacker.readArray(Ints2, SourceInts.size()),
|
|
Succeeded());
|
|
|
|
EXPECT_EQ(makeArrayRef(SourceInts), Ints2);
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamWriterStrings) {
|
|
StringRef Strings[] = {"First", "Second", "Third", "Fourth"};
|
|
|
|
size_t Length = 0;
|
|
for (auto S : Strings)
|
|
Length += S.size() + 1;
|
|
initializeOutput(Length, 1);
|
|
initializeInputFromOutput(1);
|
|
|
|
for (auto &Stream : Streams) {
|
|
BinaryStreamWriter Writer(*Stream.Output);
|
|
for (auto S : Strings)
|
|
ASSERT_THAT_ERROR(Writer.writeCString(S), Succeeded());
|
|
std::vector<StringRef> InStrings;
|
|
BinaryStreamReader Reader(*Stream.Input);
|
|
while (!Reader.empty()) {
|
|
StringRef S;
|
|
ASSERT_THAT_ERROR(Reader.readCString(S), Succeeded());
|
|
InStrings.push_back(S);
|
|
}
|
|
EXPECT_EQ(makeArrayRef(Strings), makeArrayRef(InStrings));
|
|
}
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamWriterPadToAlignment) {
|
|
// This test may seem excessive but it is checking for past bugs and corner
|
|
// cases by making sure that the stream is allowed to grow and that
|
|
// both multiple pad chunks and single chunk extensions work.
|
|
AppendingBinaryByteStream Stream(support::little);
|
|
BinaryStreamWriter Writer(Stream);
|
|
|
|
// Offset 0: '0'
|
|
EXPECT_THAT_ERROR(Writer.writeInteger('0'), Succeeded());
|
|
// Offset 1..110: 0
|
|
EXPECT_THAT_ERROR(Writer.padToAlignment(111), Succeeded());
|
|
// Offset 111: '*'
|
|
EXPECT_THAT_ERROR(Writer.writeInteger('*'), Succeeded());
|
|
// Offset 112..120: 0
|
|
EXPECT_THAT_ERROR(Writer.padToAlignment(11), Succeeded());
|
|
|
|
BinaryStreamReader Reader(Stream);
|
|
char c;
|
|
// Offset 0
|
|
EXPECT_THAT_ERROR(Reader.readInteger<char>(c), Succeeded());
|
|
EXPECT_EQ('0', c);
|
|
// Offset 1..110
|
|
for (int i = 0; i < 110; ++i) {
|
|
char c;
|
|
EXPECT_THAT_ERROR(Reader.readInteger<char>(c), Succeeded());
|
|
EXPECT_EQ('\0', c);
|
|
}
|
|
// Offset 111
|
|
EXPECT_THAT_ERROR(Reader.readInteger<char>(c), Succeeded());
|
|
EXPECT_EQ('*', c);
|
|
// Offset 112..120
|
|
for (int i = 0; i < 9; ++i) {
|
|
char c;
|
|
EXPECT_THAT_ERROR(Reader.readInteger<char>(c), Succeeded());
|
|
EXPECT_EQ('\0', c);
|
|
}
|
|
|
|
// EOF.
|
|
EXPECT_THAT_ERROR(Reader.readInteger<char>(c), Failed());
|
|
}
|
|
|
|
TEST_F(BinaryStreamTest, StreamWriterAppend) {
|
|
StringRef Strings[] = {"First", "Second", "Third", "Fourth"};
|
|
AppendingBinaryByteStream Stream(support::little);
|
|
BinaryStreamWriter Writer(Stream);
|
|
|
|
for (auto &Str : Strings) {
|
|
EXPECT_THAT_ERROR(Writer.writeCString(Str), Succeeded());
|
|
}
|
|
|
|
BinaryStreamReader Reader(Stream);
|
|
for (auto &Str : Strings) {
|
|
StringRef S;
|
|
EXPECT_THAT_ERROR(Reader.readCString(S), Succeeded());
|
|
EXPECT_EQ(Str, S);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
struct BinaryItemStreamObject {
|
|
explicit BinaryItemStreamObject(ArrayRef<uint8_t> Bytes) : Bytes(Bytes) {}
|
|
|
|
ArrayRef<uint8_t> Bytes;
|
|
};
|
|
}
|
|
|
|
namespace llvm {
|
|
template <> struct BinaryItemTraits<BinaryItemStreamObject> {
|
|
static size_t length(const BinaryItemStreamObject &Item) {
|
|
return Item.Bytes.size();
|
|
}
|
|
|
|
static ArrayRef<uint8_t> bytes(const BinaryItemStreamObject &Item) {
|
|
return Item.Bytes;
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace {
|
|
|
|
TEST_F(BinaryStreamTest, BinaryItemStream) {
|
|
std::vector<BinaryItemStreamObject> Objects;
|
|
|
|
struct Foo {
|
|
int X;
|
|
double Y;
|
|
};
|
|
std::vector<Foo> Foos = {{1, 1.0}, {2, 2.0}, {3, 3.0}};
|
|
BumpPtrAllocator Allocator;
|
|
for (const auto &F : Foos) {
|
|
uint8_t *Ptr = static_cast<uint8_t *>(Allocator.Allocate(sizeof(Foo),
|
|
alignof(Foo)));
|
|
MutableArrayRef<uint8_t> Buffer(Ptr, sizeof(Foo));
|
|
MutableBinaryByteStream Stream(Buffer, llvm::support::big);
|
|
BinaryStreamWriter Writer(Stream);
|
|
ASSERT_THAT_ERROR(Writer.writeObject(F), Succeeded());
|
|
Objects.push_back(BinaryItemStreamObject(Buffer));
|
|
}
|
|
|
|
BinaryItemStream<BinaryItemStreamObject> ItemStream(big);
|
|
ItemStream.setItems(Objects);
|
|
BinaryStreamReader Reader(ItemStream);
|
|
|
|
for (const auto &F : Foos) {
|
|
const Foo *F2;
|
|
ASSERT_THAT_ERROR(Reader.readObject(F2), Succeeded());
|
|
|
|
EXPECT_EQ(F.X, F2->X);
|
|
EXPECT_DOUBLE_EQ(F.Y, F2->Y);
|
|
}
|
|
}
|
|
|
|
} // end anonymous namespace
|