serialization: protect blob serialization from undefined behavior
There is currently no compiler protection when someone tries to do (for example) `BLOB_SERIALIZER(std::vector<int>)`. You just get runtime allocation errors. This has already eaten up dev time before, so this PR adds a static assertion that the type must be trivially copyable, as defined by the C++ standard. Types can override this if applicable if they use `BLOB_SERIALIZER_FORCED`.
This commit is contained in:
parent
059028a30a
commit
9d101d5ea0
|
@ -767,7 +767,7 @@ namespace std
|
||||||
BLOB_SERIALIZER(rct::key);
|
BLOB_SERIALIZER(rct::key);
|
||||||
BLOB_SERIALIZER(rct::key64);
|
BLOB_SERIALIZER(rct::key64);
|
||||||
BLOB_SERIALIZER(rct::ctkey);
|
BLOB_SERIALIZER(rct::ctkey);
|
||||||
BLOB_SERIALIZER(rct::multisig_kLRki);
|
BLOB_SERIALIZER_FORCED(rct::multisig_kLRki);
|
||||||
BLOB_SERIALIZER(rct::boroSig);
|
BLOB_SERIALIZER(rct::boroSig);
|
||||||
|
|
||||||
VARIANT_TAG(debug_archive, rct::key, "rct::key");
|
VARIANT_TAG(debug_archive, rct::key, "rct::key");
|
||||||
|
|
|
@ -81,7 +81,7 @@ BLOB_SERIALIZER(crypto::chacha_iv);
|
||||||
BLOB_SERIALIZER(crypto::hash);
|
BLOB_SERIALIZER(crypto::hash);
|
||||||
BLOB_SERIALIZER(crypto::hash8);
|
BLOB_SERIALIZER(crypto::hash8);
|
||||||
BLOB_SERIALIZER(crypto::public_key);
|
BLOB_SERIALIZER(crypto::public_key);
|
||||||
BLOB_SERIALIZER(crypto::secret_key);
|
BLOB_SERIALIZER_FORCED(crypto::secret_key);
|
||||||
BLOB_SERIALIZER(crypto::key_derivation);
|
BLOB_SERIALIZER(crypto::key_derivation);
|
||||||
BLOB_SERIALIZER(crypto::key_image);
|
BLOB_SERIALIZER(crypto::key_image);
|
||||||
BLOB_SERIALIZER(crypto::signature);
|
BLOB_SERIALIZER(crypto::signature);
|
||||||
|
|
|
@ -50,13 +50,16 @@
|
||||||
#include <boost/type_traits/integral_constant.hpp>
|
#include <boost/type_traits/integral_constant.hpp>
|
||||||
#include <boost/mpl/bool.hpp>
|
#include <boost/mpl/bool.hpp>
|
||||||
|
|
||||||
/*! \struct is_blob_type
|
/*! \struct is_blob_type / is_blob_forced
|
||||||
*
|
*
|
||||||
* \brief a descriptor for dispatching serialize
|
* \brief descriptors for dispatching serialize: whether to take byte-wise copy/store to type
|
||||||
*/
|
*/
|
||||||
template <class T>
|
template <class T>
|
||||||
struct is_blob_type { typedef boost::false_type type; };
|
struct is_blob_type { typedef boost::false_type type; };
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct is_blob_forced: std::false_type {};
|
||||||
|
|
||||||
/*! \fn do_serialize(Archive &ar, T &v)
|
/*! \fn do_serialize(Archive &ar, T &v)
|
||||||
*
|
*
|
||||||
* \brief main function for dispatching serialization for a given pair of archive and value types
|
* \brief main function for dispatching serialization for a given pair of archive and value types
|
||||||
|
@ -68,6 +71,8 @@ struct is_blob_type { typedef boost::false_type type; };
|
||||||
template <class Archive, class T>
|
template <class Archive, class T>
|
||||||
inline std::enable_if_t<is_blob_type<T>::type::value, bool> do_serialize(Archive &ar, T &v)
|
inline std::enable_if_t<is_blob_type<T>::type::value, bool> do_serialize(Archive &ar, T &v)
|
||||||
{
|
{
|
||||||
|
static_assert(std::is_trivially_copyable<T>() || is_blob_forced<T>(),
|
||||||
|
"sanity check: types that can't be trivially copied shouldn't be using the blob serializer");
|
||||||
ar.serialize_blob(&v, sizeof(v));
|
ar.serialize_blob(&v, sizeof(v));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +99,9 @@ inline bool do_serialize(Archive &ar, bool &v)
|
||||||
/*! \macro BLOB_SERIALIZER
|
/*! \macro BLOB_SERIALIZER
|
||||||
*
|
*
|
||||||
* \brief makes the type have a blob serializer trait defined
|
* \brief makes the type have a blob serializer trait defined
|
||||||
|
*
|
||||||
|
* In case your type is not a good candidate to be blob serialized, a static assertion may be thrown
|
||||||
|
* at compile-time.
|
||||||
*/
|
*/
|
||||||
#define BLOB_SERIALIZER(T) \
|
#define BLOB_SERIALIZER(T) \
|
||||||
template<> \
|
template<> \
|
||||||
|
@ -101,6 +109,20 @@ inline bool do_serialize(Archive &ar, bool &v)
|
||||||
typedef boost::true_type type; \
|
typedef boost::true_type type; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! \macro BLOB_SERIALIZER_FORCED
|
||||||
|
*
|
||||||
|
* \brief makes the type have a blob serializer trait defined, even if it isn't trivially copyable
|
||||||
|
*
|
||||||
|
* Caution: do NOT use this macro for your type <T>, unless you are absolutely sure that moving raw
|
||||||
|
* bytes in/out of this type will not cause undefined behavior. Any types with managed memory
|
||||||
|
* (e.g. vector, string, etc) will segfault and/or cause memory errors if you use this macro with
|
||||||
|
* that type.
|
||||||
|
*/
|
||||||
|
#define BLOB_SERIALIZER_FORCED(T) \
|
||||||
|
BLOB_SERIALIZER(T); \
|
||||||
|
template<> \
|
||||||
|
struct is_blob_forced<T>: std::true_type {};
|
||||||
|
|
||||||
/*! \macro VARIANT_TAG
|
/*! \macro VARIANT_TAG
|
||||||
*
|
*
|
||||||
* \brief Adds the tag \tag to the \a Archive of \a Type
|
* \brief Adds the tag \tag to the \a Archive of \a Type
|
||||||
|
|
|
@ -51,6 +51,11 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace crypto;
|
using namespace crypto;
|
||||||
|
|
||||||
|
static_assert(!std::is_trivially_copyable<std::vector<unsigned char>>(),
|
||||||
|
"should fail to compile when applying blob serializer");
|
||||||
|
static_assert(!std::is_trivially_copyable<std::string>(),
|
||||||
|
"should fail to compile when applying blob serializer");
|
||||||
|
|
||||||
struct Struct
|
struct Struct
|
||||||
{
|
{
|
||||||
int32_t a;
|
int32_t a;
|
||||||
|
|
Loading…
Reference in New Issue