1 /*! \file polymorphic.hpp
2     \brief Support for pointers to polymorphic base classes
3     \ingroup OtherTypes */
4 /*
5   Copyright (c) 2014, Randolph Voorhies, Shane Grant
6   All rights reserved.
7 
8   Redistribution and use in source and binary forms, with or without
9   modification, are permitted provided that the following conditions are met:
10       * Redistributions of source code must retain the above copyright
11         notice, this list of conditions and the following disclaimer.
12       * Redistributions in binary form must reproduce the above copyright
13         notice, this list of conditions and the following disclaimer in the
14         documentation and/or other materials provided with the distribution.
15       * Neither the name of cereal nor the
16         names of its contributors may be used to endorse or promote products
17         derived from this software without specific prior written permission.
18 
19   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY
23   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30 #ifndef CEREAL_TYPES_POLYMORPHIC_HPP_
31 #define CEREAL_TYPES_POLYMORPHIC_HPP_
32 
33 #include <cereal/cereal.hpp>
34 #include <cereal/types/memory.hpp>
35 
36 #include <cereal/details/util.hpp>
37 #include <cereal/details/helpers.hpp>
38 #include <cereal/details/traits.hpp>
39 #include <cereal/details/polymorphic_impl.hpp>
40 
41 #ifdef _MSC_VER
42 #define STATIC_CONSTEXPR static
43 #else
44 #define STATIC_CONSTEXPR static constexpr
45 #endif
46 
47 //! Registers a derived polymorphic type with cereal
48 /*! Polymorphic types must be registered before smart
49     pointers to them can be serialized.  Note that base
50     classes do not need to be registered.
51 
52     Registering a type lets cereal know how to properly
53     serialize it when a smart pointer to a base object is
54     used in conjunction with a derived class.
55 
56     This assumes that all relevant archives have also
57     previously been registered.  Registration for archives
58     is usually done in the header file in which they are
59     defined.  This means that type registration needs to
60     happen after specific archives to be used are included.
61 
62     It is recommended that type registration be done in
63     the header file in which the type is declared.
64 
65     Registration can also be placed in a source file,
66     but this may require the use of the
67     CEREAL_REGISTER_DYNAMIC_INIT macro (see below).
68 
69     Registration may be called repeatedly for the same
70     type in different translation units to add support
71     for additional archives if they are not initially
72     available (included and registered).
73 
74     When building serialization support as a DLL on
75     Windows, registration must happen in the header file.
76     On Linux and Mac things should still work properly
77     if placed in a source file, but see the above comments
78     on registering in source files.
79 
80     Polymorphic support in cereal requires RTTI to be
81     enabled */
82 #define CEREAL_REGISTER_TYPE(T)                         \
83   namespace cereal {                                    \
84   namespace detail {                                    \
85   template <>                                           \
86   struct binding_name<T>                                \
87   {                                                     \
88     STATIC_CONSTEXPR char const * name() { return #T; } \
89   };                                                    \
90   } } /* end namespaces */                              \
91   CEREAL_BIND_TO_ARCHIVES(T)
92 
93 //! Registers a polymorphic type with cereal, giving it a
94 //! user defined name
95 /*! In some cases the default name used with
96     CEREAL_REGISTER_TYPE (the name of the type) may not be
97     suitable.  This macro allows any name to be associated
98     with the type.  The name should be unique */
99 #define CEREAL_REGISTER_TYPE_WITH_NAME(T, Name)              \
100   namespace cereal {                                         \
101   namespace detail {                                         \
102   template <>                                                \
103   struct binding_name<T>                                     \
104   { STATIC_CONSTEXPR char const * name() { return Name; } }; \
105   } } /* end namespaces */                                   \
106   CEREAL_BIND_TO_ARCHIVES(T)
107 
108 //! Adds a way to force initialization of a translation unit containing
109 //! calls to CEREAL_REGISTER_TYPE
110 /*! In C++, dynamic initialization of non-local variables of a translation
111     unit may be deferred until "the first odr-use of any function or variable
112     defined in the same translation unit as the variable to be initialized."
113 
114     Informally, odr-use means that your program takes the address of or binds
115     a reference directly to an object, which must have a definition.
116 
117     Since polymorphic type support in cereal relies on the dynamic
118     initialization of certain global objects happening before
119     serialization is performed, it is important to ensure that something
120     from files that call CEREAL_REGISTER_TYPE is odr-used before serialization
121     occurs, otherwise the registration will never take place.  This may often
122     be the case when serialization is built as a shared library external from
123     your main program.
124 
125     This macro, with any name of your choosing, should be placed into the
126     source file that contains calls to CEREAL_REGISTER_TYPE.
127 
128     Its counterpart, CEREAL_FORCE_DYNAMIC_INIT, should be placed in its
129     associated header file such that it is included in the translation units
130     (source files) in which you want the registration to appear.
131 
132     @relates CEREAL_FORCE_DYNAMIC_INIT
133     */
134 #define CEREAL_REGISTER_DYNAMIC_INIT(LibName)                \
135   namespace cereal {                                         \
136   namespace detail {                                         \
137     void CEREAL_DLL_EXPORT dynamic_init_dummy_##LibName() {} \
138   } } /* end namespaces */
139 
140 //! Forces dynamic initialization of polymorphic support in a
141 //! previously registered source file
142 /*! @sa CEREAL_REGISTER_DYNAMIC_INIT
143 
144     See CEREAL_REGISTER_DYNAMIC_INIT for detailed explanation
145     of how this macro should be used.  The name used should
146     match that for CEREAL_REGISTER_DYNAMIC_INIT. */
147 #define CEREAL_FORCE_DYNAMIC_INIT(LibName)              \
148   namespace cereal {                                    \
149   namespace detail {                                    \
150     void dynamic_init_dummy_##LibName();                \
151   } /* end detail */                                    \
152   namespace {                                           \
153     void dynamic_init_##LibName()                       \
154     {                                                   \
155       ::cereal::detail::dynamic_init_dummy_##LibName(); \
156     }                                                   \
157   } } /* end namespaces */
158 
159 #ifdef _MSC_VER
160 #undef CONSTEXPR
161 #endif
162 
163 namespace cereal
164 {
165   namespace polymorphic_detail
166   {
167     //! Error message used for unregistered polymorphic types
168     /*! @internal */
169     #define UNREGISTERED_POLYMORPHIC_EXCEPTION(LoadSave, Name)                                                                                      \
170       throw cereal::Exception("Trying to " #LoadSave " an unregistered polymorphic type (" + Name + ").\n"                                          \
171                               "Make sure your type is registered with CEREAL_REGISTER_TYPE and that the archive "                                   \
172                               "you are using was included (and registered with CEREAL_REGISTER_ARCHIVE) prior to calling CEREAL_REGISTER_TYPE.\n"   \
173                               "If your type is already registered and you still see this error, you may need to use CEREAL_REGISTER_DYNAMIC_INIT.");
174 
175     //! Get an input binding from the given archive by deserializing the type meta data
176     /*! @internal */
177     template<class Archive> inline
getInputBinding(Archive & ar,std::uint32_t const nameid)178     typename ::cereal::detail::InputBindingMap<Archive>::Serializers getInputBinding(Archive & ar, std::uint32_t const nameid)
179     {
180       // If the nameid is zero, we serialized a null pointer
181       if(nameid == 0)
182       {
183         typename ::cereal::detail::InputBindingMap<Archive>::Serializers emptySerializers;
184         emptySerializers.shared_ptr = [](void*, std::shared_ptr<void> & ptr) { ptr.reset(); };
185         emptySerializers.unique_ptr = [](void*, std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> & ptr) { ptr.reset( nullptr ); };
186         return emptySerializers;
187       }
188 
189       std::string name;
190       if(nameid & detail::msb_32bit)
191       {
192         ar( CEREAL_NVP_("polymorphic_name", name) );
193         ar.registerPolymorphicName(nameid, name);
194       }
195       else
196         name = ar.getPolymorphicName(nameid);
197 
198       auto & bindingMap = detail::StaticObject<detail::InputBindingMap<Archive>>::getInstance().map;
199 
200       auto binding = bindingMap.find(name);
201       if(binding == bindingMap.end())
202         UNREGISTERED_POLYMORPHIC_EXCEPTION(load, name)
203       return binding->second;
204     }
205 
206     //! Serialize a shared_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee
207     /*! This check lets us try and skip doing polymorphic machinery if we can get away with
208         using the derived class serialize function
209 
210         Note that on MSVC 2013 preview, is_default_constructible<T> returns true for abstract classes with
211         default constructors, but on clang/gcc this will return false.  So we also need to check for that here.
212         @internal */
213     template<class Archive, class T> inline
214     typename std::enable_if<(traits::is_default_constructible<T>::value
215                              || traits::has_load_and_construct<T, Archive>::value)
216                              && !std::is_abstract<T>::value, bool>::type
serialize_wrapper(Archive & ar,std::shared_ptr<T> & ptr,std::uint32_t const nameid)217     serialize_wrapper(Archive & ar, std::shared_ptr<T> & ptr, std::uint32_t const nameid)
218     {
219       if(nameid & detail::msb2_32bit)
220       {
221         ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
222         return true;
223       }
224       return false;
225     }
226 
227     //! Serialize a unique_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee
228     /*! This check lets us try and skip doing polymorphic machinery if we can get away with
229         using the derived class serialize function
230         @internal */
231     template<class Archive, class T, class D> inline
232     typename std::enable_if<(traits::is_default_constructible<T>::value
233                              || traits::has_load_and_construct<T, Archive>::value)
234                              && !std::is_abstract<T>::value, bool>::type
serialize_wrapper(Archive & ar,std::unique_ptr<T,D> & ptr,std::uint32_t const nameid)235     serialize_wrapper(Archive & ar, std::unique_ptr<T, D> & ptr, std::uint32_t const nameid)
236     {
237       if(nameid & detail::msb2_32bit)
238       {
239         ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
240         return true;
241       }
242       return false;
243     }
244 
245     //! Serialize a shared_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee
246     /*! This case is for when we can't actually construct the shared pointer.  Normally this would be caught
247         as the pointer itself is serialized, but since this is a polymorphic pointer, if we tried to serialize
248         the pointer we'd end up back here recursively.  So we have to catch the error here as well, if
249         this was a polymorphic type serialized by its proper pointer type
250         @internal */
251     template<class Archive, class T> inline
252     typename std::enable_if<(!traits::is_default_constructible<T>::value
253                              && !traits::has_load_and_construct<T, Archive>::value)
254                              || std::is_abstract<T>::value, bool>::type
serialize_wrapper(Archive &,std::shared_ptr<T> &,std::uint32_t const nameid)255     serialize_wrapper(Archive &, std::shared_ptr<T> &, std::uint32_t const nameid)
256     {
257       if(nameid & detail::msb2_32bit)
258         throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function");
259       return false;
260     }
261 
262     //! Serialize a unique_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee
263     /*! This case is for when we can't actually construct the unique pointer.  Normally this would be caught
264         as the pointer itself is serialized, but since this is a polymorphic pointer, if we tried to serialize
265         the pointer we'd end up back here recursively.  So we have to catch the error here as well, if
266         this was a polymorphic type serialized by its proper pointer type
267         @internal */
268     template<class Archive, class T, class D> inline
269      typename std::enable_if<(!traits::is_default_constructible<T>::value
270                                && !traits::has_load_and_construct<T, Archive>::value)
271                                || std::is_abstract<T>::value, bool>::type
serialize_wrapper(Archive &,std::unique_ptr<T,D> &,std::uint32_t const nameid)272     serialize_wrapper(Archive &, std::unique_ptr<T, D> &, std::uint32_t const nameid)
273     {
274       if(nameid & detail::msb2_32bit)
275         throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function");
276       return false;
277     }
278   } // polymorphic_detail
279 
280   // ######################################################################
281   // Pointer serialization for polymorphic types
282 
283   //! Saving std::shared_ptr for polymorphic types, abstract
284   template <class Archive, class T> inline
285   typename std::enable_if<std::is_polymorphic<T>::value && std::is_abstract<T>::value, void>::type
CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::shared_ptr<T> const & ptr)286   CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr )
287   {
288     if(!ptr)
289     {
290       // same behavior as nullptr in memory implementation
291       ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) );
292       return;
293     }
294 
295     std::type_info const & ptrinfo = typeid(*ptr.get());
296     // ptrinfo can never be equal to T info since we can't have an instance
297     // of an abstract object
298     //  this implies we need to do the lookup
299 
300     auto & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map;
301 
302     auto binding = bindingMap.find(std::type_index(ptrinfo));
303     if(binding == bindingMap.end())
304       UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
305 
306     binding->second.shared_ptr(&ar, ptr.get());
307   }
308 
309   //! Saving std::shared_ptr for polymorphic types, not abstract
310   template <class Archive, class T> inline
311   typename std::enable_if<std::is_polymorphic<T>::value && !std::is_abstract<T>::value, void>::type
CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::shared_ptr<T> const & ptr)312   CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr )
313   {
314     if(!ptr)
315     {
316       // same behavior as nullptr in memory implementation
317       ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) );
318       return;
319     }
320 
321     std::type_info const & ptrinfo = typeid(*ptr.get());
322     static std::type_info const & tinfo = typeid(T);
323 
324     if(ptrinfo == tinfo)
325     {
326       // The 2nd msb signals that the following pointer does not need to be
327       // cast with our polymorphic machinery
328       ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) );
329 
330       ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
331 
332       return;
333     }
334 
335     auto & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map;
336 
337     auto binding = bindingMap.find(std::type_index(ptrinfo));
338     if(binding == bindingMap.end())
339       UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
340 
341     binding->second.shared_ptr(&ar, ptr.get());
342   }
343 
344   //! Loading std::shared_ptr for polymorphic types
345   template <class Archive, class T> inline
346   typename std::enable_if<std::is_polymorphic<T>::value, void>::type
CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::shared_ptr<T> & ptr)347   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> & ptr )
348   {
349     std::uint32_t nameid;
350     ar( CEREAL_NVP_("polymorphic_id", nameid) );
351 
352     // Check to see if we can skip all of this polymorphism business
353     if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid))
354       return;
355 
356     auto binding = polymorphic_detail::getInputBinding(ar, nameid);
357     std::shared_ptr<void> result;
358     binding.shared_ptr(&ar, result);
359     ptr = std::static_pointer_cast<T>(result);
360   }
361 
362   //! Saving std::weak_ptr for polymorphic types
363   template <class Archive, class T> inline
364   typename std::enable_if<std::is_polymorphic<T>::value, void>::type
CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::weak_ptr<T> const & ptr)365   CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> const & ptr )
366   {
367     auto const sptr = ptr.lock();
368     ar( CEREAL_NVP_("locked_ptr", sptr) );
369   }
370 
371   //! Loading std::weak_ptr for polymorphic types
372   template <class Archive, class T> inline
373   typename std::enable_if<std::is_polymorphic<T>::value, void>::type
CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::weak_ptr<T> & ptr)374   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> & ptr )
375   {
376     std::shared_ptr<T> sptr;
377     ar( CEREAL_NVP_("locked_ptr", sptr) );
378     ptr = sptr;
379   }
380 
381   //! Saving std::unique_ptr for polymorphic types that are abstract
382   template <class Archive, class T, class D> inline
383   typename std::enable_if<std::is_polymorphic<T>::value && std::is_abstract<T>::value, void>::type
CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::unique_ptr<T,D> const & ptr)384   CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr )
385   {
386     if(!ptr)
387     {
388       // same behavior as nullptr in memory implementation
389       ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) );
390       return;
391     }
392 
393     std::type_info const & ptrinfo = typeid(*ptr.get());
394     // ptrinfo can never be equal to T info since we can't have an instance
395     // of an abstract object
396     //  this implies we need to do the lookup
397 
398     auto & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map;
399 
400     auto binding = bindingMap.find(std::type_index(ptrinfo));
401     if(binding == bindingMap.end())
402       UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
403 
404     binding->second.unique_ptr(&ar, ptr.get());
405   }
406 
407   //! Saving std::unique_ptr for polymorphic types, not abstract
408   template <class Archive, class T, class D> inline
409   typename std::enable_if<std::is_polymorphic<T>::value && !std::is_abstract<T>::value, void>::type
CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::unique_ptr<T,D> const & ptr)410   CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr )
411   {
412     if(!ptr)
413     {
414       // same behavior as nullptr in memory implementation
415       ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) );
416       return;
417     }
418 
419     std::type_info const & ptrinfo = typeid(*ptr.get());
420     static std::type_info const & tinfo = typeid(T);
421 
422     if(ptrinfo == tinfo)
423     {
424       // The 2nd msb signals that the following pointer does not need to be
425       // cast with our polymorphic machinery
426       ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) );
427 
428       ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
429 
430       return;
431     }
432 
433     auto & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map;
434 
435     auto binding = bindingMap.find(std::type_index(ptrinfo));
436     if(binding == bindingMap.end())
437       UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
438 
439     binding->second.unique_ptr(&ar, ptr.get());
440   }
441 
442   //! Loading std::unique_ptr, case when user provides load_and_construct for polymorphic types
443   template <class Archive, class T, class D> inline
444   typename std::enable_if<std::is_polymorphic<T>::value, void>::type
CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::unique_ptr<T,D> & ptr)445   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> & ptr )
446   {
447     std::uint32_t nameid;
448     ar( CEREAL_NVP_("polymorphic_id", nameid) );
449 
450     // Check to see if we can skip all of this polymorphism business
451     if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid))
452       return;
453 
454     auto binding = polymorphic_detail::getInputBinding(ar, nameid);
455     std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> result;
456     binding.unique_ptr(&ar, result);
457     ptr.reset(static_cast<T*>(result.release()));
458   }
459 
460   #undef UNREGISTERED_POLYMORPHIC_EXCEPTION
461 } // namespace cereal
462 #endif // CEREAL_TYPES_POLYMORPHIC_HPP_
463