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