#include #include #include #include #include #include #include namespace { using Bytes = std::vector; void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } template void requireOutOfRange(const TOperation& operation, const std::string& name, std::size_t offset) { try { operation(); } catch (const std::out_of_range& error) { require(std::string(error.what()) == name + " out of bounds", name + " changed its out-of-range diagnostic"); return; } throw std::runtime_error(name + " accepted invalid offset " + std::to_string(offset)); } template void invalidRanges(Bytes& bytes, const TOperation& operation, const std::string& name) { const auto original = bytes; constexpr auto maxOffset = std::numeric_limits::max(); for (const std::size_t offset : {bytes.size() - sizeof(TValue) + 1, bytes.size(), bytes.size() + 1, maxOffset - sizeof(TValue), maxOffset - sizeof(TValue) + 1, maxOffset}) { requireOutOfRange([&] { operation(bytes, offset); }, name, offset); require(bytes == original, name + " modified the buffer on failure"); } for (std::size_t size = 0; size < sizeof(TValue); ++size) { Bytes shortBuffer(size, 0xCC); requireOutOfRange([&] { operation(shortBuffer, 0); }, name, 0); require(shortBuffer == Bytes(size, 0xCC), name + " modified a short buffer on failure"); requireOutOfRange([&] { operation(shortBuffer, maxOffset); }, name, maxOffset); require(shortBuffer == Bytes(size, 0xCC), name + " modified a short buffer on failure"); } } template void readBounds(const TRead& read, const std::string& name) { const auto value = static_cast(0x8877665544332211ull); Bytes bytes(sizeof(value) + 2, 0xCC); std::memcpy(bytes.data() + 1, &value, sizeof(value)); require(read(bytes, 1) == value, name + " failed an unaligned read"); bytes.pop_back(); require(read(bytes, 1) == value, name + " rejected a read ending at the buffer boundary"); invalidRanges(bytes, read, name); } template void writeBounds(const TWrite& write, const std::string& name) { const auto value = static_cast(0x8877665544332211ull); Bytes bytes(sizeof(value) + 2, 0xCC); auto expected = bytes; std::memcpy(expected.data() + 1, &value, sizeof(value)); write(bytes, 1, value); require(bytes == expected, name + " failed an unaligned write"); bytes.pop_back(); expected.pop_back(); bytes.assign(expected.size(), 0xCC); write(bytes, 1, value); require(bytes == expected, name + " rejected a write ending at the buffer boundary"); invalidRanges(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name); } } int main() { try { const Io::ByteReader reader; readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64"); readBounds(Io::ReadU16, "ReadU16"); readBounds(Io::ReadU32, "ReadU32"); readBounds(Io::ReadU64, "ReadU64"); writeBounds(Io::WriteU8, "WriteU8"); writeBounds(Io::WriteU16, "WriteU16"); writeBounds(Io::WriteU32, "WriteU32"); writeBounds(Io::WriteU64, "WriteU64"); } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } return 0; }