Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: serialization debug utility #290

Merged
merged 1 commit into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ option(BENCHMARKS "Build benchmarks" ON)
option(FUZZING "Build fuzzing harnesses" OFF)
option(DISABLE_TBB "Intel Thread Building Blocks" ON)
option(COVERAGE "Enable collecting coverage from tests" OFF)
option(SERIALIZE_CANARY "Build with serialize canary" OFF)
option(ENABLE_HEAVY_TESTS "Enable heavy tests when collecting coverage" OFF)
option(INSTALL_BARRETENBERG "Enable installation of barretenberg. (Projects embedding barretenberg may want to turn this OFF.)" ON)

Expand All @@ -31,6 +32,10 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "a
set(DISABLE_TBB 0)
endif()

if(SERIALIZE_CANARY)
add_definitions(-DENABLE_SERIALIZE_CANARY)
endif()

if(FUZZING)
add_definitions(-DFUZZING=1)

Expand Down
39 changes: 39 additions & 0 deletions cpp/src/barretenberg/common/serialize.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* - to_buffer
*/
#pragma once
#include <cassert>
#include <array>
#include "barretenberg/common/net.hpp"
#include <type_traits>
Expand Down Expand Up @@ -149,38 +150,61 @@ template <typename T> inline std::enable_if_t<std::is_integral_v<T>> write(std::
write(ptr, value);
os.write((char*)buf.data(), sizeof(T));
}

// DEBUG_CANARY_READ and DEBUG_CANARY_WRITE write strings during debug testing
// so that we can detect serialization misalignment for more complicated types.
// This is in an awkward location as it must see the above functions, and be seen by the below functions.
#ifndef ENABLE_SERIALIZE_CANARY
#define DEBUG_CANARY_WRITE(buf, x)
#define DEBUG_CANARY_READ(it, x)
#else
#define DEBUG_CANARY_WRITE(buf, x) serialize::write(buf, (uint64_t) typeid(x).hash_code())
#define DEBUG_CANARY_READ(it, x) \
{ \
uint64_t hash_code; \
serialize::read(it, hash_code); \
if (hash_code != (uint64_t) typeid(x).hash_code()) { \
throw std::runtime_error(std::string("Could not read magic string for ") + typeid(x).name()); \
} \
}
#endif
} // namespace serialize

namespace std {

// Forwarding functions from std to serialize namespace for integers.
template <typename B, typename T> inline std::enable_if_t<std::is_integral_v<T>> read(B& buf, T& value)
{
DEBUG_CANARY_READ(buf, value);
serialize::read(buf, value);
}

template <typename B, typename T> inline std::enable_if_t<std::is_integral_v<T>> write(B& buf, T value)
{
DEBUG_CANARY_WRITE(buf, value);
serialize::write(buf, value);
}

// Optimised specialisation for reading arrays of bytes from a raw buffer.
template <size_t N> inline void read(uint8_t const*& it, std::array<uint8_t, N>& value)
{
DEBUG_CANARY_READ(it, value);
std::copy(it, it + N, value.data());
it += N;
}

// Optimised specialisation for writing arrays of bytes to a raw buffer.
template <size_t N> inline void write(uint8_t*& buf, std::array<uint8_t, N> const& value)
{
DEBUG_CANARY_WRITE(buf, value);
std::copy(value.begin(), value.end(), buf);
buf += N;
}

// Optimised specialisation for reading vectors of bytes from a raw buffer.
inline void read(uint8_t const*& it, std::vector<uint8_t>& value)
{
DEBUG_CANARY_READ(it, value);
uint32_t size;
read(it, size);
value.resize(size);
Expand All @@ -191,6 +215,7 @@ inline void read(uint8_t const*& it, std::vector<uint8_t>& value)
// Optimised specialisation for writing vectors of bytes to a raw buffer.
inline void write(uint8_t*& buf, std::vector<uint8_t> const& value)
{
DEBUG_CANARY_WRITE(buf, value);
write(buf, static_cast<uint32_t>(value.size()));
std::copy(value.begin(), value.end(), buf);
buf += value.size();
Expand All @@ -199,6 +224,7 @@ inline void write(uint8_t*& buf, std::vector<uint8_t> const& value)
// Optimised specialisation for reading vectors of bytes from an input stream.
inline void read(std::istream& is, std::vector<uint8_t>& value)
{
DEBUG_CANARY_READ(is, value);
uint32_t size;
read(is, size);
value.resize(size);
Expand All @@ -208,13 +234,15 @@ inline void read(std::istream& is, std::vector<uint8_t>& value)
// Optimised specialisation for writing vectors of bytes to an output stream.
inline void write(std::ostream& os, std::vector<uint8_t> const& value)
{
DEBUG_CANARY_WRITE(os, value);
write(os, static_cast<uint32_t>(value.size()));
os.write((char*)value.data(), (std::streamsize)value.size());
}

// Optimised specialisation for writing arrays of bytes to a vector.
template <size_t N> inline void write(std::vector<uint8_t>& buf, std::array<uint8_t, N> const& value)
{
DEBUG_CANARY_WRITE(buf, value);
buf.resize(buf.size() + N);
auto ptr = &*buf.end() - N;
write(ptr, value);
Expand All @@ -223,12 +251,14 @@ template <size_t N> inline void write(std::vector<uint8_t>& buf, std::array<uint
// Optimised specialisation for writing arrays of bytes to an output stream.
template <size_t N> inline void write(std::ostream& os, std::array<uint8_t, N> const& value)
{
DEBUG_CANARY_WRITE(os, value);
os.write((char*)value.data(), value.size());
}

// Generic read of array of types from supported buffer types.
template <typename B, typename T, size_t N> inline void read(B& it, std::array<T, N>& value)
{
DEBUG_CANARY_READ(it, value);
for (size_t i = 0; i < N; ++i) {
read(it, value[i]);
}
Expand All @@ -237,6 +267,7 @@ template <typename B, typename T, size_t N> inline void read(B& it, std::array<T
// Generic write of array of types to supported buffer types.
template <typename B, typename T, size_t N> inline void write(B& buf, std::array<T, N> const& value)
{
DEBUG_CANARY_WRITE(buf, value);
for (size_t i = 0; i < N; ++i) {
write(buf, value[i]);
}
Expand All @@ -245,6 +276,7 @@ template <typename B, typename T, size_t N> inline void write(B& buf, std::array
// Generic read of vector of types from supported buffer types.
template <typename B, typename T> inline void read(B& it, std::vector<T>& value)
{
DEBUG_CANARY_READ(it, value);
uint32_t size;
read(it, size);
value.resize(size);
Expand All @@ -265,6 +297,7 @@ template <typename B, typename T> inline void write(B& buf, std::vector<T> const
// Read string from supported buffer types.
template <typename B> inline void read(B& it, std::string& value)
{
DEBUG_CANARY_READ(it, value);
std::vector<uint8_t> buf;
read(it, buf);
value = std::string(buf.begin(), buf.end());
Expand All @@ -279,20 +312,23 @@ template <typename B> inline void write(B& buf, std::string const& value)
// Read std::pair.
template <typename B, typename T, typename U> inline void read(B& it, std::pair<T, U>& value)
{
DEBUG_CANARY_READ(it, value);
read(it, value.first);
read(it, value.second);
}

// Write std::pair.
template <typename B, typename T, typename U> inline void write(B& buf, std::pair<T, U> const& value)
{
DEBUG_CANARY_WRITE(buf, value);
write(buf, value.first);
write(buf, value.second);
}

// Read std::map
template <typename B, typename T, typename U> inline void read(B& it, std::map<T, U>& value)
{
DEBUG_CANARY_READ(it, value);
value.clear();
uint32_t size;
read(it, size);
Expand All @@ -306,6 +342,7 @@ template <typename B, typename T, typename U> inline void read(B& it, std::map<T
// Write std::map.
template <typename B, typename T, typename U> inline void write(B& buf, std::map<T, U> const& value)
{
DEBUG_CANARY_WRITE(buf, value);
write(buf, static_cast<uint32_t>(value.size()));
for (auto const& kv : value) {
write(buf, kv);
Expand All @@ -315,6 +352,7 @@ template <typename B, typename T, typename U> inline void write(B& buf, std::map
// Read std::optional<T>.
template <typename B, typename T> inline void read(B& it, std::optional<T>& opt_value)
{
DEBUG_CANARY_READ(it, opt_value);
bool is_nullopt;
read(it, is_nullopt);
if (is_nullopt) {
Expand All @@ -331,6 +369,7 @@ template <typename B, typename T> inline void read(B& it, std::optional<T>& opt_
// value.
template <typename B, typename T> inline void write(B& buf, std::optional<T> const& opt_value)
{
DEBUG_CANARY_WRITE(buf, opt_value);
if (opt_value) {
write(buf, false); // is not nullopt
write(buf, *opt_value);
Expand Down