From 9a1ea8a0bff1bd017d9ea38cf9b734538989e7fd Mon Sep 17 00:00:00 2001 From: Nathaniel Date: Wed, 16 Mar 2022 15:47:17 -0500 Subject: [PATCH] Enable classes to customize their serialization Add support for classes to define members to customize their serialization from the default from reflection. --- include/fc/io/raw.hpp | 28 ++++++++-- include/fc/reflect/variant.hpp | 37 +++++++++++++- tests/serialization_test.cpp | 93 ++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 5 deletions(-) diff --git a/include/fc/io/raw.hpp b/include/fc/io/raw.hpp index 80d97e3..3a5a8da 100755 --- a/include/fc/io/raw.hpp +++ b/include/fc/io/raw.hpp @@ -417,13 +417,35 @@ namespace fc { } }; template<> - struct if_reflected { - template + struct if_reflected { + private: + template using void_t = void; + + template + struct has_custom_packing : std::false_type {}; + template + struct has_custom_packing().fc_pack(std::declval&>(), 1)), + decltype(std::declval().fc_unpack(std::declval&>(), 1))>> + : std::true_type {}; + public: + template::value, bool> = true> + static inline void pack( Stream& s, const T& v, uint32_t _max_depth ) { + FC_ASSERT( _max_depth > 0 ); + v.fc_pack( s, _max_depth - 1 ); + } + template::value, bool> = true> static inline void pack( Stream& s, const T& v, uint32_t _max_depth ) { FC_ASSERT( _max_depth > 0 ); if_enum< typename fc::reflector::is_enum >::pack( s, v, _max_depth - 1 ); } - template + + template::value, bool> = true> + static inline void unpack( Stream& s, T& v, uint32_t _max_depth ) { + FC_ASSERT( _max_depth > 0 ); + v.fc_unpack( s, _max_depth - 1 ); + } + template::value, bool> = true> static inline void unpack( Stream& s, T& v, uint32_t _max_depth ) { FC_ASSERT( _max_depth > 0 ); if_enum< typename fc::reflector::is_enum >::unpack( s, v, _max_depth - 1 ); diff --git a/include/fc/reflect/variant.hpp b/include/fc/reflect/variant.hpp index 60dcea8..d03917c 100755 --- a/include/fc/reflect/variant.hpp +++ b/include/fc/reflect/variant.hpp @@ -99,16 +99,49 @@ namespace fc } }; + namespace detail { + template using void_t = void; + + template + struct has_custom_variant_conversion : std::false_type {}; + template + struct has_custom_variant_conversion().fc_to_variant(std::declval(), 1)), + decltype(std::declval().fc_from_variant(std::declval(), 1))>> + : std::true_type {}; + + template::value, bool> = true> + void to_variant( const T& o, variant& v, uint32_t max_depth ) + { + if_enum::to_variant( o, v, max_depth ); + } + template::value, bool> = true> + void to_variant( const T& o, variant& v, uint32_t max_depth ) + { + o.fc_to_variant(v, max_depth); + } + + template::value, bool> = true> + void from_variant( const variant& v, T& o, uint32_t max_depth ) + { + if_enum::from_variant( v, o, max_depth ); + } + template::value, bool> = true> + void from_variant( const variant& v, T& o, uint32_t max_depth ) + { + o.fc_from_variant(v, max_depth); + } + } // namespace detail template void to_variant( const T& o, variant& v, uint32_t max_depth ) { - if_enum::is_enum>::to_variant( o, v, max_depth ); + detail::to_variant(o, v, max_depth); } template void from_variant( const variant& v, T& o, uint32_t max_depth ) { - if_enum::is_enum>::from_variant( v, o, max_depth ); + detail::from_variant(v, o, max_depth); } } diff --git a/tests/serialization_test.cpp b/tests/serialization_test.cpp index 1e35f86..6e92304 100644 --- a/tests/serialization_test.cpp +++ b/tests/serialization_test.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include namespace fc { namespace test { @@ -44,6 +46,35 @@ namespace fc { namespace test { FC_REFLECT( fc::test::item_wrapper, (v) ); FC_REFLECT( fc::test::item, (level)(w) ); +// These are used in the custom_serialization test + +static bool custom_serialization_used = false; +// Custom serialized type with a fake serializer that does nothing but set the above bool to true +struct custom_serialized_type { + int a = 1; + std::string b = "Hi!"; + bool c = true; + + // Override default serialization for reflected types + template + void fc_pack(Stream&, uint32_t) const { + custom_serialization_used = true; + } + template + void fc_unpack(Stream&, uint32_t) { + custom_serialization_used = true; + } + + void fc_to_variant(fc::variant&, uint32_t) const { + custom_serialization_used = true; + } + void fc_from_variant(const fc::variant&, uint32_t) { + custom_serialization_used = true; + } +}; + +FC_REFLECT(custom_serialized_type, (a)(b)(c)) + BOOST_AUTO_TEST_SUITE(fc_serialization) BOOST_AUTO_TEST_CASE( nested_objects_test ) @@ -105,4 +136,66 @@ BOOST_AUTO_TEST_CASE( nested_objects_test ) } FC_CAPTURE_LOG_AND_RETHROW ( (0) ) } +BOOST_AUTO_TEST_CASE( unpack_recursion_test ) +{ + try + { + std::stringstream ss; + int recursion_level = 100000; + uint64_t allocation_per_level = 500000; + + for ( int i = 0; i < recursion_level; i++ ) + { + fc::raw::pack( ss, fc::unsigned_int( allocation_per_level ) ); + fc::raw::pack( ss, static_cast< uint8_t >( fc::variant::array_type ) ); + } + + std::vector< fc::variant > v; + BOOST_REQUIRE_THROW( fc::raw::unpack( ss, v ), fc::assert_exception ); + } + FC_LOG_AND_RETHROW(); +} + +BOOST_AUTO_TEST_CASE( custom_serialization ) +{ try { + // Create our custom serialized type and serialize it; check that only our fake serializer ran + custom_serialized_type obj; + auto packed = fc::raw::pack(obj); + BOOST_CHECK(packed.empty()); + BOOST_CHECK(custom_serialization_used); + + // Deserializer won't run on an empty vector; put a byte in so deserialization tests can run + packed = {'a'}; + + // Reset the flag, alter the object, and deserialize it, again checking that only the fake serializer ran + custom_serialization_used = false; + obj.c = false; + fc::raw::unpack(packed, obj); + BOOST_CHECK(obj.c == false); + BOOST_CHECK(custom_serialization_used); + + // Reset the flag, deserialize again without existing ref, check if fake serializer ran + custom_serialization_used = false; + auto obj2 = fc::raw::unpack(packed); + BOOST_CHECK(custom_serialization_used); + + // All over again, but for variants instead of binary serialization + fc::variant v(obj, 10); + BOOST_CHECK(v.is_null()); + BOOST_CHECK(custom_serialization_used); + custom_serialization_used = false; + fc::to_variant(obj, v, 10); + BOOST_CHECK(custom_serialization_used); + // JSON should be affected too. Check it is. + custom_serialization_used = false; + BOOST_CHECK_EQUAL(fc::json::to_string(obj), "null"); + BOOST_CHECK(custom_serialization_used); + + custom_serialization_used = false; + obj.c = true; + fc::from_variant(v, obj, 10); + BOOST_CHECK(custom_serialization_used); + BOOST_CHECK(obj.c); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END()