From c4ebf37503f4d64f945645c0f5c8c296f0c1c7bb Mon Sep 17 00:00:00 2001 From: Erik Lindahl Date: Sun, 29 Dec 2019 14:43:35 +0100 Subject: [PATCH] Write TPR body as opaque XDR data in big-endian format This implements support for opaque data in our serializers, as well as a few missing XDR support opaque routines, including support for data objects larger than 2GB. By using this for the new serialization of the TPR body we avoid the unintended 4x growth of the file size, and avoid introducing extra endian swapping or padding. We also adjust the endian so the InMemorySerializer swaps to big endian (i.e., swap if host is little endian) such that the body of the TPR file is compatible with the header that is already written as big-endian by the XDR routines. Fixes #3276. Change-Id: I99011dffe190155e671bd656127c2a33459923e2 --- src/gromacs/fileio/gmxfio_xdr.cpp | 82 ++++++++++++++++--- src/gromacs/fileio/gmxfio_xdr.h | 12 +++ src/gromacs/fileio/mrcdensitymap.cpp | 4 +- src/gromacs/fileio/tpxio.cpp | 27 ++++-- src/gromacs/utility/inmemoryserializer.cpp | 54 +++++++++++- src/gromacs/utility/inmemoryserializer.h | 18 +++- src/gromacs/utility/iserializer.h | 29 ++++--- .../utility/tests/inmemoryserializer.cpp | 8 +- .../utility/tests/keyvaluetreeserializer.cpp | 3 + 9 files changed, 191 insertions(+), 46 deletions(-) diff --git a/src/gromacs/fileio/gmxfio_xdr.cpp b/src/gromacs/fileio/gmxfio_xdr.cpp index 254dc37fb8..9c088a794d 100644 --- a/src/gromacs/fileio/gmxfio_xdr.cpp +++ b/src/gromacs/fileio/gmxfio_xdr.cpp @@ -38,9 +38,12 @@ #include "gmxfio_xdr.h" +#include #include #include +#include + #include "gromacs/fileio/gmxfio.h" #include "gromacs/fileio/xdrf.h" #include "gromacs/utility/fatalerror.h" @@ -67,12 +70,13 @@ enum eioNRVEC, eioIVEC, eioSTRING, + eioOPAQUE, eioNR }; -static const char* eioNames[eioNR] = { "REAL", "FLOAT", "DOUBLE", "INT", "INT32", - "INT64", "UCHAR", "CHAR", "NCHAR", "NUCHAR", - "USHORT", "RVEC", "NRVEC", "IVEC", "STRING" }; +static const char* eioNames[eioNR] = { "REAL", "FLOAT", "DOUBLE", "INT", "INT32", "INT64", + "UCHAR", "CHAR", "NCHAR", "NUCHAR", "USHORT", "RVEC", + "NRVEC", "IVEC", "STRING", "OPAQUE" }; void gmx_fio_setprecision(t_fileio* fio, gmx_bool bDouble) { @@ -101,15 +105,16 @@ XDR* gmx_fio_getxdr(t_fileio* fio) } /* check the number of items given against the type */ -static void gmx_fio_check_nitem(int eio, int nitem, const char* file, int line) +static void gmx_fio_check_nitem(int eio, std::size_t nitem, const char* file, int line) { - if ((nitem != 1) && !((eio == eioNRVEC) || (eio == eioNUCHAR) || (eio == eioNCHAR))) + if ((nitem != 1) + && !((eio == eioNRVEC) || (eio == eioNUCHAR) || (eio == eioNCHAR) || (eio == eioOPAQUE))) { gmx_fatal(FARGS, - "nitem (%d) may differ from 1 only for %s, %s or %s, not for %s" + "nitem may differ from 1 only for %s, %s, %s or %s, not for %s" "(%s, %d)", - nitem, eioNames[eioNUCHAR], eioNames[eioNRVEC], eioNames[eioNCHAR], eioNames[eio], - file, line); + eioNames[eioNUCHAR], eioNames[eioNRVEC], eioNames[eioNCHAR], eioNames[eioOPAQUE], + eioNames[eio], file, line); } } @@ -122,14 +127,15 @@ static void gmx_fio_check_nitem(int eio, int nitem, const char* file, int line) /* This is the part that reads xdr files. */ -static gmx_bool do_xdr(t_fileio* fio, void* item, int nitem, int eio, const char* desc, const char* srcfile, int line) +static gmx_bool +do_xdr(t_fileio* fio, void* item, std::size_t nitem, int eio, const char* desc, const char* srcfile, int line) { unsigned char ucdum, *ucptr; char cdum, *cptr; bool_t res = 0; float fvec[DIM]; double dvec[DIM]; - int j, m, *iptr, idum; + int m, *iptr, idum; int32_t s32dum; int64_t s64dum; real* ptr; @@ -246,12 +252,17 @@ static gmx_bool do_xdr(t_fileio* fio, void* item, int nitem, int eio, const char break; case eioNCHAR: cptr = static_cast(item); - res = xdr_vector(fio->xdr, cptr, nitem, static_cast(sizeof(char)), + GMX_RELEASE_ASSERT(nitem < static_cast(std::numeric_limits::max()), + "The XDR interface cannot handle array lengths > 2^31"); + res = xdr_vector(fio->xdr, cptr, static_cast(nitem), + static_cast(sizeof(char)), reinterpret_cast(xdr_char)); break; case eioNUCHAR: ucptr = static_cast(item); - res = xdr_vector(fio->xdr, reinterpret_cast(ucptr), nitem, + GMX_RELEASE_ASSERT(nitem < static_cast(std::numeric_limits::max()), + "The XDR interface cannot handle array lengths > 2^31"); + res = xdr_vector(fio->xdr, reinterpret_cast(ucptr), static_cast(nitem), static_cast(sizeof(unsigned char)), reinterpret_cast(xdr_u_char)); break; @@ -311,7 +322,7 @@ static gmx_bool do_xdr(t_fileio* fio, void* item, int nitem, int eio, const char case eioNRVEC: ptr = nullptr; res = 1; - for (j = 0; (j < nitem) && res; j++) + for (std::size_t j = 0; j < nitem && res; j++) { if (item) { @@ -386,6 +397,38 @@ static gmx_bool do_xdr(t_fileio* fio, void* item, int nitem, int eio, const char } break; } + case eioOPAQUE: + { + if (item == nullptr && nitem > 0) + { + gmx_fatal(FARGS, "Null pointer provided for non-zero length XDR opaque data."); + } + + if (nitem > 0) + { + // We need to support very large opaque data objects although the default + // XDR interface only uses integers for the size field, since gromacs-2020 + // e.g. embeds the entire TPR body as a single such object, which would break all + // TPR files larger than 2GB unless we handle it as a special case. + // To avoid inserting extra padding, we calculate the chunk size as: + // - The max value of a signed integer + 1 + // - Subtract 4 (the XDR object size) to get a size within the range of the signed int. + const std::size_t maxChunk = + static_cast(std::numeric_limits::max()) + 1 - 4; + + for (res = 1; res > 0 && nitem > 0;) + { + std::size_t thisChunk = std::min(maxChunk, nitem); + res = xdr_opaque(fio->xdr, reinterpret_cast(item), thisChunk); + nitem -= thisChunk; + } + } + else + { + res = 1; + } + break; + } default: gmx_fio_fe(fio, eio, desc, srcfile, line); } @@ -537,6 +580,14 @@ gmx_bool gmx_fio_doe_string(t_fileio* fio, char* item, const char* desc, const c return ret; } +gmx_bool gmx_fio_doe_opaque(t_fileio* fio, char* data, std::size_t size, const char* desc, const char* srcfile, int line) +{ + gmx_bool ret; + gmx_fio_lock(fio); + ret = do_xdr(fio, data, size, eioOPAQUE, desc, srcfile, line); + gmx_fio_unlock(fio); + return ret; +} /* Array reading & writing */ @@ -809,4 +860,9 @@ void FileIOXdrSerializer::doString(std::string* value) } } +void FileIOXdrSerializer::doOpaque(char* data, std::size_t size) +{ + gmx_fio_do_opaque(fio_, data, size); +} + } // namespace gmx diff --git a/src/gromacs/fileio/gmxfio_xdr.h b/src/gromacs/fileio/gmxfio_xdr.h index b70011e686..90a65cf9ff 100644 --- a/src/gromacs/fileio/gmxfio_xdr.h +++ b/src/gromacs/fileio/gmxfio_xdr.h @@ -37,6 +37,8 @@ #ifndef GMX_FILEIO_GMXFIO_XDR_H #define GMX_FILEIO_GMXFIO_XDR_H +#include + #include #include "gromacs/fileio/xdrf.h" @@ -75,6 +77,12 @@ gmx_bool gmx_fio_doe_ushort(struct t_fileio* fio, gmx_bool gmx_fio_doe_rvec(struct t_fileio* fio, rvec* item, const char* desc, const char* srcfile, int line); gmx_bool gmx_fio_doe_ivec(struct t_fileio* fio, ivec* item, const char* desc, const char* srcfile, int line); gmx_bool gmx_fio_doe_string(struct t_fileio* fio, char* item, const char* desc, const char* srcfile, int line); +gmx_bool gmx_fio_doe_opaque(struct t_fileio* fio, + char* item, + std::size_t size, + const char* desc, + const char* srcfile, + int line); /* array reading & writing */ gmx_bool gmx_fio_ndoe_real(struct t_fileio* fio, real* item, int n, const char* desc, const char* srcfile, int line); @@ -125,6 +133,8 @@ gmx_bool gmx_fio_ndoe_string(struct t_fileio* fio, char* item[], int n, const ch #define gmx_fio_do_rvec(fio, item) gmx_fio_doe_rvec(fio, &(item), (#item), __FILE__, __LINE__) #define gmx_fio_do_ivec(fio, item) gmx_fio_doe_ivec(fio, &(item), (#item), __FILE__, __LINE__) #define gmx_fio_do_string(fio, item) gmx_fio_doe_string(fio, item, (#item), __FILE__, __LINE__) +#define gmx_fio_do_opaque(fio, item, size) \ + gmx_fio_doe_opaque(fio, item, size, (#item), __FILE__, __LINE__) #define gmx_fio_ndo_real(fio, item, n) gmx_fio_ndoe_real(fio, item, n, (#item), __FILE__, __LINE__) @@ -188,6 +198,8 @@ public: void doRvec(rvec* value) override; //! Handle I/O if string. void doString(std::string* value) override; + //! Handle opaque data. + void doOpaque(char* data, std::size_t size) override; //! Special case for handling I/O of a vector of characters. void doCharArray(char* values, int elements) override; //! Special case for handling I/O of a vector of unsigned characters. diff --git a/src/gromacs/fileio/mrcdensitymap.cpp b/src/gromacs/fileio/mrcdensitymap.cpp index ff866adfde..067dc33bc7 100644 --- a/src/gromacs/fileio/mrcdensitymap.cpp +++ b/src/gromacs/fileio/mrcdensitymap.cpp @@ -180,8 +180,8 @@ MrcDensityMapOfFloatFromFileReader::Impl::Impl(const std::string& filename) : { if (!mrcHeaderIsSane(reader_->header())) { - serializer_ = std::make_unique(buffer_, false, EndianSwapBehavior::DoSwap); - reader_ = std::make_unique(serializer_.get()); + serializer_ = std::make_unique(buffer_, false, EndianSwapBehavior::Swap); + reader_ = std::make_unique(serializer_.get()); if (!mrcHeaderIsSane(reader_->header())) { GMX_THROW(FileIOError( diff --git a/src/gromacs/fileio/tpxio.cpp b/src/gromacs/fileio/tpxio.cpp index 86698e5e82..db5d9d6864 100644 --- a/src/gromacs/fileio/tpxio.cpp +++ b/src/gromacs/fileio/tpxio.cpp @@ -3123,7 +3123,7 @@ static TpxFileHeader populateTpxHeader(const t_state& state, const t_inputrec* i } /*! \brief - * Process the body of a TPR file as char buffer. + * Process the body of a TPR file as an opaque data buffer. * * Reads/writes the information in \p buffer from/to the \p serializer * provided to the function. Does not interact with the actual @@ -3137,7 +3137,7 @@ static TpxFileHeader populateTpxHeader(const t_state& state, const t_inputrec* i */ static void doTpxBodyBuffer(gmx::ISerializer* serializer, gmx::ArrayRef buffer) { - serializer->doCharArray(buffer.data(), buffer.size()); + serializer->doOpaque(buffer.data(), buffer.size()); } /*! \brief @@ -3193,7 +3193,12 @@ static PartialDeserializedTprFile readTpxBody(TpxFileHeader* tpx, // we prepare a new char buffer with the information we have already read // in on master. partialDeserializedTpr.header = populateTpxHeader(*state, ir, mtop); - gmx::InMemorySerializer tprBodySerializer; + // Long-term we should move to use little endian in files to avoid extra byte swapping, + // but since we just used the default XDR format (which is big endian) for the TPR + // header it would cause third-party libraries reading our raw data to tear their hair + // if we swap the endian in the middle of the file, so we stick to big endian in the + // TPR file for now - and thus we ask the serializer to swap if this host is little endian. + gmx::InMemorySerializer tprBodySerializer(gmx::EndianSwapBehavior::SwapIfHostIsLittleEndian); do_tpx_body(&tprBodySerializer, &partialDeserializedTpr.header, ir, mtop); partialDeserializedTpr.body = tprBodySerializer.finishAndGetBuffer(); @@ -3230,8 +3235,12 @@ void write_tpx_state(const char* fn, const t_inputrec* ir, const t_state* state, t_fileio* fio; TpxFileHeader tpx = populateTpxHeader(*state, ir, mtop); - - gmx::InMemorySerializer tprBodySerializer; + // Long-term we should move to use little endian in files to avoid extra byte swapping, + // but since we just used the default XDR format (which is big endian) for the TPR + // header it would cause third-party libraries reading our raw data to tear their hair + // if we swap the endian in the middle of the file, so we stick to big endian in the + // TPR file for now - and thus we ask the serializer to swap if this host is little endian. + gmx::InMemorySerializer tprBodySerializer(gmx::EndianSwapBehavior::SwapIfHostIsLittleEndian); do_tpx_body(&tprBodySerializer, &tpx, const_cast(ir), const_cast(state), nullptr, nullptr, const_cast(mtop)); @@ -3254,8 +3263,14 @@ int completeTprDeserialization(PartialDeserializedTprFile* partialDeserializedTp rvec* v, gmx_mtop_t* mtop) { + // Long-term we should move to use little endian in files to avoid extra byte swapping, + // but since we just used the default XDR format (which is big endian) for the TPR + // header it would cause third-party libraries reading our raw data to tear their hair + // if we swap the endian in the middle of the file, so we stick to big endian in the + // TPR file for now - and thus we ask the serializer to swap if this host is little endian. gmx::InMemoryDeserializer tprBodyDeserializer(partialDeserializedTpr->body, - partialDeserializedTpr->header.isDouble); + partialDeserializedTpr->header.isDouble, + gmx::EndianSwapBehavior::SwapIfHostIsLittleEndian); return do_tpx_body(&tprBodyDeserializer, &partialDeserializedTpr->header, ir, state, x, v, mtop); } diff --git a/src/gromacs/utility/inmemoryserializer.cpp b/src/gromacs/utility/inmemoryserializer.cpp index eb012c36fc..89edcf07d0 100644 --- a/src/gromacs/utility/inmemoryserializer.cpp +++ b/src/gromacs/utility/inmemoryserializer.cpp @@ -36,6 +36,8 @@ #include "inmemoryserializer.h" +#include "config.h" + #include #include @@ -88,6 +90,27 @@ T swapEndian(const T& value) return endianessSwappedValue.value_; } +//! \brief Change the host-dependent endian settings to either Swap or DoNotSwap. +// +// \param endianSwapBehavior input swap behavior, might depend on host. +// +// \return Host-independent setting, either Swap or DoNotSwap. +EndianSwapBehavior setEndianSwapBehaviorFromHost(EndianSwapBehavior endianSwapBehavior) +{ + if (endianSwapBehavior == EndianSwapBehavior::SwapIfHostIsBigEndian) + { + return GMX_INTEGER_BIG_ENDIAN ? EndianSwapBehavior::Swap : EndianSwapBehavior::DoNotSwap; + } + else if (endianSwapBehavior == EndianSwapBehavior::SwapIfHostIsLittleEndian) + { + return GMX_INTEGER_BIG_ENDIAN ? EndianSwapBehavior::DoNotSwap : EndianSwapBehavior::Swap; + } + else + { + return endianSwapBehavior; + } +} + } // namespace /******************************************************************** @@ -97,11 +120,15 @@ T swapEndian(const T& value) class InMemorySerializer::Impl { public: - Impl(EndianSwapBehavior endianSwapBehavior) : endianSwapBehavior_(endianSwapBehavior) {} + Impl(EndianSwapBehavior endianSwapBehavior) : + endianSwapBehavior_(setEndianSwapBehaviorFromHost(endianSwapBehavior)) + { + } + template void doValue(T value) { - if (endianSwapBehavior_ == EndianSwapBehavior::DoSwap) + if (endianSwapBehavior_ == EndianSwapBehavior::Swap) { CharBuffer(swapEndian(value)).appendTo(&buffer_); } @@ -115,6 +142,10 @@ public: doValue(value.size()); buffer_.insert(buffer_.end(), value.begin(), value.end()); } + void doOpaque(const char* data, std::size_t size) + { + buffer_.insert(buffer_.end(), data, data + size); + } std::vector buffer_; EndianSwapBehavior endianSwapBehavior_; @@ -203,6 +234,11 @@ void InMemorySerializer::doString(std::string* value) impl_->doString(*value); } +void InMemorySerializer::doOpaque(char* data, std::size_t size) +{ + impl_->doOpaque(data, size); +} + /******************************************************************** * InMemoryDeserializer */ @@ -214,14 +250,14 @@ public: buffer_(buffer), sourceIsDouble_(sourceIsDouble), pos_(0), - endianSwapBehavior_(endianSwapBehavior) + endianSwapBehavior_(setEndianSwapBehaviorFromHost(endianSwapBehavior)) { } template void doValue(T* value) { - if (endianSwapBehavior_ == EndianSwapBehavior::DoSwap) + if (endianSwapBehavior_ == EndianSwapBehavior::Swap) { *value = swapEndian(CharBuffer(&buffer_[pos_]).value()); } @@ -238,6 +274,11 @@ public: *value = std::string(&buffer_[pos_], size); pos_ += size; } + void doOpaque(char* data, std::size_t size) + { + std::copy(&buffer_[pos_], &buffer_[pos_ + size], data); + pos_ += size; + } ArrayRef buffer_; bool sourceIsDouble_; @@ -341,4 +382,9 @@ void InMemoryDeserializer::doString(std::string* value) impl_->doString(value); } +void InMemoryDeserializer::doOpaque(char* data, std::size_t size) +{ + impl_->doOpaque(data, size); +} + } // namespace gmx diff --git a/src/gromacs/utility/inmemoryserializer.h b/src/gromacs/utility/inmemoryserializer.h index 8b6ac98a70..b9370404a5 100644 --- a/src/gromacs/utility/inmemoryserializer.h +++ b/src/gromacs/utility/inmemoryserializer.h @@ -43,6 +43,8 @@ #ifndef GMX_UTILITY_INMEMORYSERIALIZER_H #define GMX_UTILITY_INMEMORYSERIALIZER_H +#include + #include #include "gromacs/utility/arrayref.h" @@ -52,12 +54,18 @@ namespace gmx { -//! Specify endian swapping behvaior +//! Specify endian swapping behavoir. +// +// The host-dependent choices avoid the calling file having to +// depend on config.h. +// enum class EndianSwapBehavior : int { - DoSwap, //!< Swap the bytes - DoNotSwap, //!< Do not swap the bytes - Count //!< Number of possible behaviors + DoNotSwap, //!< Don't touch anything + Swap, //!< User-enforced swapping + SwapIfHostIsBigEndian, //!< Only swap if machine we execute on is big-endian + SwapIfHostIsLittleEndian, //!< Only swap if machine we execute on is little-endian + Count //!< Number of possible behaviors }; class InMemorySerializer : public ISerializer @@ -83,6 +91,7 @@ public: void doIvec(ivec* value) override; void doRvec(rvec* value) override; void doString(std::string* value) override; + void doOpaque(char* data, std::size_t size) override; private: class Impl; @@ -116,6 +125,7 @@ public: void doIvec(ivec* value) override; void doRvec(rvec* value) override; void doString(std::string* value) override; + void doOpaque(char* data, std::size_t size) override; private: class Impl; diff --git a/src/gromacs/utility/iserializer.h b/src/gromacs/utility/iserializer.h index 44ad11e3eb..6cde441f1f 100644 --- a/src/gromacs/utility/iserializer.h +++ b/src/gromacs/utility/iserializer.h @@ -46,6 +46,8 @@ #ifndef GMX_UTILITY_ISERIALIZER_H #define GMX_UTILITY_ISERIALIZER_H +#include + #include #include "gromacs/math/vectypes.h" @@ -72,19 +74,20 @@ public: virtual bool reading() const = 0; //! \brief Serialize values of different types. ///@{ - virtual void doBool(bool* value) = 0; - virtual void doUChar(unsigned char* value) = 0; - virtual void doChar(char* value) = 0; - virtual void doUShort(unsigned short* value) = 0; - virtual void doInt(int* value) = 0; - virtual void doInt32(int32_t* value) = 0; - virtual void doInt64(int64_t* value) = 0; - virtual void doFloat(float* value) = 0; - virtual void doDouble(double* value) = 0; - virtual void doReal(real* value) = 0; - virtual void doIvec(ivec* value) = 0; - virtual void doRvec(rvec* value) = 0; - virtual void doString(std::string* value) = 0; + virtual void doBool(bool* value) = 0; + virtual void doUChar(unsigned char* value) = 0; + virtual void doChar(char* value) = 0; + virtual void doUShort(unsigned short* value) = 0; + virtual void doInt(int* value) = 0; + virtual void doInt32(int32_t* value) = 0; + virtual void doInt64(int64_t* value) = 0; + virtual void doFloat(float* value) = 0; + virtual void doDouble(double* value) = 0; + virtual void doReal(real* value) = 0; + virtual void doIvec(ivec* value) = 0; + virtual void doRvec(rvec* value) = 0; + virtual void doString(std::string* value) = 0; + virtual void doOpaque(char* data, std::size_t size) = 0; ///@} //! \brief Serialize arrays of values of different types. diff --git a/src/gromacs/utility/tests/inmemoryserializer.cpp b/src/gromacs/utility/tests/inmemoryserializer.cpp index a3612eb5af..81bf81cbc2 100644 --- a/src/gromacs/utility/tests/inmemoryserializer.cpp +++ b/src/gromacs/utility/tests/inmemoryserializer.cpp @@ -198,14 +198,14 @@ TEST_F(InMemorySerializerTest, Roundtrip) TEST_F(InMemorySerializerTest, RoundtripWithEndianessSwap) { - InMemorySerializer serializerWithSwap(EndianSwapBehavior::DoSwap); + InMemorySerializer serializerWithSwap(EndianSwapBehavior::Swap); SerializerValues values = defaultValues_; serialize(&serializerWithSwap, &values); auto buffer = serializerWithSwap.finishAndGetBuffer(); InMemoryDeserializer deserializerWithSwap(buffer, std::is_same::value, - EndianSwapBehavior::DoSwap); + EndianSwapBehavior::Swap); SerializerValues deserialisedValues = deserialize(&deserializerWithSwap); @@ -214,7 +214,7 @@ TEST_F(InMemorySerializerTest, RoundtripWithEndianessSwap) TEST_F(InMemorySerializerTest, SerializerExplicitEndianessSwap) { - InMemorySerializer serializerWithSwap(EndianSwapBehavior::DoSwap); + InMemorySerializer serializerWithSwap(EndianSwapBehavior::Swap); SerializerValues values = defaultValues_; serialize(&serializerWithSwap, &values); @@ -235,7 +235,7 @@ TEST_F(InMemorySerializerTest, DeserializerExplicitEndianessSwap) auto buffer = serializer.finishAndGetBuffer(); InMemoryDeserializer deserializerWithSwap(buffer, std::is_same::value, - EndianSwapBehavior::DoSwap); + EndianSwapBehavior::Swap); SerializerValues deserialisedValues = deserialize(&deserializerWithSwap); checkSerializerValuesforEquality(endianessSwappedValues_, deserialisedValues); diff --git a/src/gromacs/utility/tests/keyvaluetreeserializer.cpp b/src/gromacs/utility/tests/keyvaluetreeserializer.cpp index c273d1c9b5..83eefe635a 100644 --- a/src/gromacs/utility/tests/keyvaluetreeserializer.cpp +++ b/src/gromacs/utility/tests/keyvaluetreeserializer.cpp @@ -36,6 +36,8 @@ #include "gromacs/utility/keyvaluetreeserializer.h" +#include + #include #include "gromacs/utility/inmemoryserializer.h" @@ -73,6 +75,7 @@ public: void doFloat(float* value) override { checker_.checkFloat(*value, nullptr); } void doDouble(double* value) override { checker_.checkDouble(*value, nullptr); } void doString(std::string* value) override { checker_.checkString(*value, nullptr); } + void doOpaque(char* /* value */, std::size_t /* size */) override { raiseAssert(); } void doReal(real* /* value */) override { raiseAssert(); } void doIvec(ivec* /* value */) override { raiseAssert(); } void doRvec(rvec* /* value */) override { raiseAssert(); } -- 2.22.0