LCOV - code coverage report
Current view: top level - cereal/types - memory.hpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 110 111 99.1 %
Date: 2022-01-16 21:05:07 Functions: 722 762 94.8 %

          Line data    Source code
       1             : /*! \file memory.hpp
       2             :     \brief Support for types found in \<memory\>
       3             :     \ingroup STLSupport */
       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 the copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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_SHARED_PTR_HPP_
      31             : #define CEREAL_TYPES_SHARED_PTR_HPP_
      32             : 
      33             : #include "cereal/cereal.hpp"
      34             : #include <memory>
      35             : #include <cstring>
      36             : 
      37             : namespace cereal
      38             : {
      39             :   namespace memory_detail
      40             :   {
      41             :     //! A wrapper class to notify cereal that it is ok to serialize the contained pointer
      42             :     /*! This mechanism allows us to intercept and properly handle polymorphic pointers
      43             :         @internal */
      44             :     template<class T>
      45             :     struct PtrWrapper
      46             :     {
      47      277648 :       PtrWrapper(T && p) : ptr(std::forward<T>(p)) {}
      48             :       T & ptr;
      49             : 
      50             :       PtrWrapper( PtrWrapper const & ) = default;
      51             :       PtrWrapper & operator=( PtrWrapper const & ) = delete;
      52             :     };
      53             : 
      54             :     //! Make a PtrWrapper
      55             :     /*! @internal */
      56             :     template<class T> inline
      57      277648 :     PtrWrapper<T> make_ptr_wrapper(T && t)
      58             :     {
      59      277648 :       return {std::forward<T>(t)};
      60             :     }
      61             : 
      62             :     //! A struct that acts as a wrapper around calling load_andor_construct
      63             :     /*! The purpose of this is to allow a load_and_construct call to properly enter into the
      64             :         'data' NVP of the ptr_wrapper
      65             :         @internal */
      66             :     template <class Archive, class T>
      67             :     struct LoadAndConstructLoadWrapper
      68             :     {
      69        6000 :       LoadAndConstructLoadWrapper( T * ptr ) :
      70        6000 :         construct( ptr )
      71        6000 :       { }
      72             : 
      73             :       //! Constructor for embedding an early call for restoring shared_from_this
      74             :       template <class F>
      75        2400 :       LoadAndConstructLoadWrapper( T * ptr, F && sharedFromThisFunc ) :
      76        2400 :         construct( ptr, sharedFromThisFunc )
      77        2400 :       { }
      78             : 
      79        8400 :       inline void CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar )
      80             :       {
      81        8400 :         ::cereal::detail::Construct<T, Archive>::load_andor_construct( ar, construct );
      82        8000 :       }
      83             : 
      84             :       ::cereal::construct<T> construct;
      85             :     };
      86             : 
      87             :     //! A helper struct for saving and restoring the state of types that derive from
      88             :     //! std::enable_shared_from_this
      89             :     /*! This special struct is necessary because when a user uses load_and_construct,
      90             :         the weak_ptr (or whatever implementation defined variant) that allows
      91             :         enable_shared_from_this to function correctly will not be initialized properly.
      92             : 
      93             :         This internal weak_ptr can also be modified by the shared_ptr that is created
      94             :         during the serialization of a polymorphic pointer, where cereal creates a
      95             :         wrapper shared_ptr out of a void pointer to the real data.
      96             : 
      97             :         In the case of load_and_construct, this happens because it is the allocation
      98             :         of shared_ptr that perform this initialization, which we let happen on a buffer
      99             :         of memory (aligned_storage).  This buffer is then used for placement new
     100             :         later on, effectively overwriting any initialized weak_ptr with a default
     101             :         initialized one, eventually leading to issues when the user calls shared_from_this.
     102             : 
     103             :         To get around these issues, we will store the memory for the enable_shared_from_this
     104             :         portion of the class and replace it after whatever happens to modify it (e.g. the
     105             :         user performing construction or the wrapper shared_ptr in saving).
     106             : 
     107             :         Note that this goes into undefined behavior territory, but as of the initial writing
     108             :         of this, all standard library implementations of std::enable_shared_from_this are
     109             :         compatible with this memory manipulation. It is entirely possible that this may someday
     110             :         break or may not work with convoluted use cases.
     111             : 
     112             :         Example usage:
     113             : 
     114             :         @code{.cpp}
     115             :         T * myActualPointer;
     116             :         {
     117             :           EnableSharedStateHelper<T> helper( myActualPointer ); // save the state
     118             :           std::shared_ptr<T> myPtr( myActualPointer ); // modifies the internal weak_ptr
     119             :           // helper restores state when it goes out of scope
     120             :         }
     121             :         @endcode
     122             : 
     123             :         When possible, this is designed to be used in an RAII fashion - it will save state on
     124             :         construction and restore it on destruction. The restore can be done at an earlier time
     125             :         (e.g. after construct() is called in load_and_construct) in which case the destructor will
     126             :         do nothing. Performing the restore immediately following construct() allows a user to call
     127             :         shared_from_this within their load_and_construct function.
     128             : 
     129             :         @tparam T Type pointed to by shared_ptr
     130             :         @internal */
     131             :     template <class T>
     132             :     class EnableSharedStateHelper
     133             :     {
     134             :       // typedefs for parent type and storage type
     135             :       using BaseType = typename ::cereal::traits::get_shared_from_this_base<T>::type;
     136             :       using ParentType = std::enable_shared_from_this<BaseType>;
     137             :       using StorageType = typename std::aligned_storage<sizeof(ParentType), CEREAL_ALIGNOF(ParentType)>::type;
     138             : 
     139             :       public:
     140             :         //! Saves the state of some type inheriting from enable_shared_from_this
     141             :         /*! @param ptr The raw pointer held by the shared_ptr */
     142        4000 :         inline EnableSharedStateHelper( T * ptr ) :
     143        3200 :           itsPtr( static_cast<ParentType *>( ptr ) ),
     144             :           itsState(),
     145        4000 :           itsRestored( false )
     146             :         {
     147        4000 :           std::memcpy( &itsState, itsPtr, sizeof(ParentType) );
     148        4000 :         }
     149             : 
     150             :         //! Restores the state of the held pointer (can only be done once)
     151        6400 :         inline void restore()
     152             :         {
     153        6400 :           if( !itsRestored )
     154             :           {
     155             :             // void * cast needed when type has no trivial copy-assignment
     156        4000 :             std::memcpy( static_cast<void *>(itsPtr), &itsState, sizeof(ParentType) );
     157        4000 :             itsRestored = true;
     158             :           }
     159        6400 :         }
     160             : 
     161             :         //! Restores the state of the held pointer if not done previously
     162        4000 :         inline ~EnableSharedStateHelper()
     163             :         {
     164        4000 :           restore();
     165        4000 :         }
     166             : 
     167             :       private:
     168             :         ParentType * itsPtr;
     169             :         StorageType itsState;
     170             :         bool itsRestored;
     171             :     }; // end EnableSharedStateHelper
     172             : 
     173             :     //! Performs loading and construction for a shared pointer that is derived from
     174             :     //! std::enable_shared_from_this
     175             :     /*! @param ar The archive
     176             :         @param ptr Raw pointer held by the shared_ptr
     177             :         @internal */
     178             :     template <class Archive, class T> inline
     179        2400 :     void loadAndConstructSharedPtr( Archive & ar, T * ptr, std::true_type /* has_shared_from_this */ )
     180             :     {
     181        4800 :       memory_detail::EnableSharedStateHelper<T> state( ptr );
     182        5400 :       memory_detail::LoadAndConstructLoadWrapper<Archive, T> loadWrapper( ptr, [&](){ state.restore(); } );
     183             : 
     184             :       // let the user perform their initialization, shared state will be restored as soon as construct()
     185             :       // is called
     186        2400 :       ar( CEREAL_NVP_("data", loadWrapper) );
     187        2400 :     }
     188             : 
     189             :     //! Performs loading and construction for a shared pointer that is NOT derived from
     190             :     //! std::enable_shared_from_this
     191             :     /*! This is the typical case, where we simply pass the load wrapper to the
     192             :         archive.
     193             : 
     194             :         @param ar The archive
     195             :         @param ptr Raw pointer held by the shared_ptr
     196             :         @internal */
     197             :     template <class Archive, class T> inline
     198        3600 :     void loadAndConstructSharedPtr( Archive & ar, T * ptr, std::false_type /* has_shared_from_this */ )
     199             :     {
     200        4500 :       memory_detail::LoadAndConstructLoadWrapper<Archive, T> loadWrapper( ptr );
     201        3600 :       ar( CEREAL_NVP_("data", loadWrapper) );
     202        3600 :     }
     203             :   } // end namespace memory_detail
     204             : 
     205             :   //! Saving std::shared_ptr for non polymorphic types
     206             :   template <class Archive, class T> inline
     207             :   typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
     208       89024 :   CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr )
     209             :   {
     210       89024 :     ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
     211       89024 :   }
     212             : 
     213             :   //! Loading std::shared_ptr, case when no user load and construct for non polymorphic types
     214             :   template <class Archive, class T> inline
     215             :   typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
     216       89024 :   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> & ptr )
     217             :   {
     218       89024 :     ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
     219       89024 :   }
     220             : 
     221             :   //! Saving std::weak_ptr for non polymorphic types
     222             :   template <class Archive, class T> inline
     223             :   typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
     224         800 :   CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> const & ptr )
     225             :   {
     226         800 :     auto const sptr = ptr.lock();
     227         800 :     ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( sptr )) );
     228         800 :   }
     229             : 
     230             :   //! Loading std::weak_ptr for non polymorphic types
     231             :   template <class Archive, class T> inline
     232             :   typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
     233         800 :   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> & ptr )
     234             :   {
     235         800 :     std::shared_ptr<T> sptr;
     236         800 :     ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( sptr )) );
     237         800 :     ptr = sptr;
     238         800 :   }
     239             : 
     240             :   //! Saving std::unique_ptr for non polymorphic types
     241             :   template <class Archive, class T, class D> inline
     242             :   typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
     243       43200 :   CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr )
     244             :   {
     245       43200 :     ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
     246       43200 :   }
     247             : 
     248             :   //! Loading std::unique_ptr, case when user provides load_and_construct for non polymorphic types
     249             :   template <class Archive, class T, class D> inline
     250             :   typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
     251       43600 :   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> & ptr )
     252             :   {
     253       43600 :     ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
     254       43200 :   }
     255             : 
     256             :   // ######################################################################
     257             :   // Pointer wrapper implementations follow below
     258             : 
     259             :   //! Saving std::shared_ptr (wrapper implementation)
     260             :   /*! @internal */
     261             :   template <class Archive, class T> inline
     262       94224 :   void CEREAL_SAVE_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> const &> const & wrapper )
     263             :   {
     264       94224 :     auto & ptr = wrapper.ptr;
     265             : 
     266       94224 :     uint32_t id = ar.registerSharedPointer( ptr );
     267       94224 :     ar( CEREAL_NVP_("id", id) );
     268             : 
     269       94224 :     if( id & detail::msb_32bit )
     270             :     {
     271       49608 :       ar( CEREAL_NVP_("data", *ptr) );
     272             :     }
     273       94224 :   }
     274             : 
     275             :   //! Loading std::shared_ptr, case when user load and construct (wrapper implementation)
     276             :   /*! @internal */
     277             :   template <class Archive, class T> inline
     278             :   typename std::enable_if<traits::has_load_and_construct<T, Archive>::value, void>::type
     279        6400 :   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> &> & wrapper )
     280             :   {
     281             :     uint32_t id;
     282             : 
     283        6400 :     ar( CEREAL_NVP_("id", id) );
     284             : 
     285        6400 :     if( id & detail::msb_32bit )
     286             :     {
     287             :       // Storage type for the pointer - since we can't default construct this type,
     288             :       // we'll allocate it using std::aligned_storage and use a custom deleter
     289             :       using ST = typename std::aligned_storage<sizeof(T), CEREAL_ALIGNOF(T)>::type;
     290             : 
     291             :       // Valid flag - set to true once construction finishes
     292             :       //  This prevents us from calling the destructor on
     293             :       //  uninitialized data.
     294       12000 :       auto valid = std::make_shared<bool>( false );
     295             : 
     296             :       // Allocate our storage, which we will treat as
     297             :       //  uninitialized until initialized with placement new
     298             :       using NonConstT = typename std::remove_const<T>::type;
     299       13600 :       std::shared_ptr<NonConstT> ptr(reinterpret_cast<NonConstT *>(new ST()),
     300       12000 :           [=]( NonConstT * t )
     301             :           {
     302        6000 :             if( *valid )
     303        6000 :               t->~T();
     304             : 
     305        6000 :             delete reinterpret_cast<ST *>( t );
     306             :           } );
     307             : 
     308             :       // Register the pointer
     309        6000 :       ar.registerSharedPointer( id, ptr );
     310             : 
     311             :       // Perform the actual loading and allocation
     312        6000 :       memory_detail::loadAndConstructSharedPtr( ar, ptr.get(), typename ::cereal::traits::has_shared_from_this<NonConstT>::type() );
     313             : 
     314             :       // Mark pointer as valid (initialized)
     315        6000 :       *valid = true;
     316        6000 :       wrapper.ptr = std::move(ptr);
     317             :     }
     318             :     else
     319         400 :       wrapper.ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
     320        6400 :   }
     321             : 
     322             :   //! Loading std::shared_ptr, case when no user load and construct (wrapper implementation)
     323             :   /*! @internal */
     324             :   template <class Archive, class T> inline
     325             :   typename std::enable_if<!traits::has_load_and_construct<T, Archive>::value, void>::type
     326       87824 :   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> &> & wrapper )
     327             :   {
     328             :     uint32_t id;
     329             : 
     330       87824 :     ar( CEREAL_NVP_("id", id) );
     331             : 
     332       87824 :     if( id & detail::msb_32bit )
     333             :     {
     334             :       using NonConstT = typename std::remove_const<T>::type;
     335       87216 :       std::shared_ptr<NonConstT> ptr( detail::Construct<NonConstT, Archive>::load_andor_construct() );
     336       43608 :       ar.registerSharedPointer( id, ptr );
     337       43608 :       ar( CEREAL_NVP_("data", *ptr) );
     338       43608 :       wrapper.ptr = std::move(ptr);
     339             :     }
     340             :     else
     341       44216 :       wrapper.ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
     342       87824 :   }
     343             : 
     344             :   //! Saving std::unique_ptr (wrapper implementation)
     345             :   /*! @internal */
     346             :   template <class Archive, class T, class D> inline
     347       44400 :   void CEREAL_SAVE_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> const &> const & wrapper )
     348             :   {
     349       44400 :     auto & ptr = wrapper.ptr;
     350             : 
     351             :     // unique_ptr get one byte of metadata which signifies whether they were a nullptr
     352             :     // 0 == nullptr
     353             :     // 1 == not null
     354             : 
     355       44400 :     if( !ptr )
     356         400 :       ar( CEREAL_NVP_("valid", uint8_t(0)) );
     357             :     else
     358             :     {
     359       44000 :       ar( CEREAL_NVP_("valid", uint8_t(1)) );
     360       44000 :       ar( CEREAL_NVP_("data", *ptr) );
     361             :     }
     362       44400 :   }
     363             : 
     364             :   //! Loading std::unique_ptr, case when user provides load_and_construct (wrapper implementation)
     365             :   /*! @internal */
     366             :   template <class Archive, class T, class D> inline
     367             :   typename std::enable_if<traits::has_load_and_construct<T, Archive>::value, void>::type
     368        2400 :   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> &> & wrapper )
     369             :   {
     370             :     uint8_t isValid;
     371        2400 :     ar( CEREAL_NVP_("valid", isValid) );
     372             : 
     373        2400 :     auto & ptr = wrapper.ptr;
     374             : 
     375        2400 :     if( isValid )
     376             :     {
     377             :       using NonConstT = typename std::remove_const<T>::type;
     378             :       // Storage type for the pointer - since we can't default construct this type,
     379             :       // we'll allocate it using std::aligned_storage
     380             :       using ST = typename std::aligned_storage<sizeof(NonConstT), CEREAL_ALIGNOF(NonConstT)>::type;
     381             : 
     382             :       // Allocate storage - note the ST type so that deleter is correct if
     383             :       //                    an exception is thrown before we are initialized
     384        6400 :       std::unique_ptr<ST> stPtr( new ST() );
     385             : 
     386             :       // Use wrapper to enter into "data" nvp of ptr_wrapper
     387        4800 :       memory_detail::LoadAndConstructLoadWrapper<Archive, NonConstT> loadWrapper( reinterpret_cast<NonConstT *>( stPtr.get() ) );
     388             : 
     389             :       // Initialize storage
     390        2400 :       ar( CEREAL_NVP_("data", loadWrapper) );
     391             : 
     392             :       // Transfer ownership to correct unique_ptr type
     393        2000 :       ptr.reset( reinterpret_cast<T *>( stPtr.release() ) );
     394             :     }
     395             :     else
     396           0 :       ptr.reset( nullptr );
     397        2000 :   }
     398             : 
     399             :   //! Loading std::unique_ptr, case when no load_and_construct (wrapper implementation)
     400             :   /*! @internal */
     401             :   template <class Archive, class T, class D> inline
     402             :   typename std::enable_if<!traits::has_load_and_construct<T, Archive>::value, void>::type
     403       42400 :   CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> &> & wrapper )
     404             :   {
     405             :     uint8_t isValid;
     406       42400 :     ar( CEREAL_NVP_("valid", isValid) );
     407             : 
     408       42400 :     if( isValid )
     409             :     {
     410             :       using NonConstT = typename std::remove_const<T>::type;
     411       84000 :       std::unique_ptr<NonConstT, D> ptr( detail::Construct<NonConstT, Archive>::load_andor_construct() );
     412       42000 :       ar( CEREAL_NVP_( "data", *ptr ) );
     413       42000 :       wrapper.ptr = std::move(ptr);
     414             :     }
     415             :     else
     416             :     {
     417         400 :       wrapper.ptr.reset( nullptr );
     418             :     }
     419       42400 :   }
     420             : } // namespace cereal
     421             : 
     422             : // automatically include polymorphic support
     423             : #include "cereal/types/polymorphic.hpp"
     424             : 
     425             : #endif // CEREAL_TYPES_SHARED_PTR_HPP_

Generated by: LCOV version 1.14