c++ - cereal serialization and polymorphism -


ok i'm running problem in c++11 cereal (http://uscilab.github.io/cereal/).

in abstract sense have large graph serializing lots of shared pointers connecting edges , vertices. edges (and vertices) have attributes attached them.

now 1 of these attributes (base class) account (child class). account inherits idable serializable. here pertinent snips of code show of cereal usage. i'll explain issue after context:

attribute.hpp/cpp

class attribute { ...  template<class archive> void serialize(archive&) {    }     friend class cereal::access; ...  cereal_register_type(mgraph::attribute) 

idable.hpp/cpp

class idable { ...  id id;  template<class archive> void serialize(archive& archive) {     archive(cereal::make_nvp("id", id));  }  template<class archive> static void load_and_construct(archive& ar, cereal::construct<mcommon::idable>& construct) {     mcommon::id id;     ar(id);     construct(id); }  friend class cereal::access; ...  cereal_register_type(mcommon::idable) 

position.hpp/cpp

class position : public mgraph::attribute   , public mcommon::displayable {  template<class archive> void serialize(archive& archive) {        archive(cereal::make_nvp("attribute",                              cereal::base_class<mgraph::attribute>(this))); }     friend class cereal::access; ...  cereal_register_type(mfin::position) 

account.hpp/cpp

class account : public mcommon::idable   , public position { ... currency balance;  template<class archive> void serialize(archive& archive) {        archive(cereal::make_nvp("idable",                              cereal::base_class<mcommon::idable>(this)),             cereal::make_nvp("position",                              cereal::base_class<mfin::position>(this)),             cereal::make_nvp("balance", balance)); }  template<class archive> static void load_and_construct(archive& ar, cereal::construct<account>& construct) {     mcommon::id iden;     currency::code code;     ar(iden, code);     construct(iden, code); }  friend class cereal::access; ...  cereal_register_type(mfin::account) 

so problem comes when mfin::account being serialized. mfin::account belongs std::list>. when down serialize function idable object invalid.

going gdb halts on segfault go few stackframes this line: /usr/include/cereal/types/polymorphic.hpp:341. is:

(gdb) list 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 polymorphic types 345   template <class archive, class t> inline 

now here ptr is:

(gdb) print *((mfin::account*)(ptr.get())) $10 = {<mcommon::idable> = {_vptr.idable = 0x4f0d50 <vtable mfin::account+16>, id = "bank"}, <mfin::position> = {<mgraph::attribute> = {       _vptr.attribute = 0x4f0d78 <vtable mfin::account+56>}, <mcommon::displayable> = {_vptr.displayable = 0x4f0da0 <vtable mfin::account+96>}, <no data fields>}, balance = {<mcommon::displayable> = {       _vptr.displayable = 0x4f0570 <vtable mfin::currency+16>}, amount = 0, code = mfin::currency::usd}} (gdb) print ptr $11 = std::shared_ptr (count 3, weak 0) 0x758ad0 

everything looking good. notice when cast void*:

$11 = std::shared_ptr (count 3, weak 0) 0x758ad0 (gdb) print *((mfin::account*)((void*)ptr.get())) $12 = {<mcommon::idable> = {_vptr.idable = 0x4f0d78 <vtable mfin::account+56>,      id = "\363al\000\000\000\000\000pbl\000\000\000\000\000\304\031l\000\000\000\000\000\021#l", '\000' <repeats 13 times>, " \232n", '\000' <repeats 21 times>, "p\251@\000\000\000\000\000\370\377\377\377\377\377\377\377 \232n", '\000' <repeats 21 times>, "\304\031l\000\000\000\000\000p\251@", '\000' <repeats 45 times>, "st19_sp_counted_deleteripn4mfin7accounte"...}, <mfin::position> = {<mgraph::attribute> = {       _vptr.attribute = 0x4f0570 <vtable mfin::currency+16>}, <mcommon::displayable> = {_vptr.displayable = 0x0}, <no data fields>}, balance = {<mcommon::displayable> = {_vptr.displayable = 0x0}, amount = 49,      code = (unknown: 7702648)}} 

this of course happens in binding->second.shared_ptr (seen below) takes const void*.

(gdb) list 295             writemetadata(ar); 296  297             #ifdef _msc_ver 298             savepolymorphicsharedptr( ar, dptr, ::cereal::traits::has_shared_from_this<t>::type() ); // msvc doesn't typename here 299             #else // not _msc_ver 300             savepolymorphicsharedptr( ar, dptr, typename ::cereal::traits::has_shared_from_this<t>::type() ); 301             #endif // _msc_ver 302           }; 303  304         serializers.unique_ptr = 

what wrong in usage of cereal cause this? here final error get:

program received signal sigsegv, segmentation fault. 0x000000000040f7cd in rapidjson::writer<rapidjson::genericwritestream, rapidjson::utf8<char>, rapidjson::memorypoolallocator<rapidjson::crtallocator> >::writestring (this=0x7fffffffd358,      str=0x4f1ae0 <vtable mfin::account+96> "\363al", length=4989722) @ /usr/include/cereal/external/rapidjson/writer.h:276 276             if ((sizeof(ch) == 1 || characterok(*p)) && escape[(unsigned char)*p])  { missing separate debuginfos, use: debuginfo-install boost-date-time-1.55.0-8.fc21.x86_64 boost-filesystem-1.55.0-8.fc21.x86_64 boost-program-options-1.55.0-8.fc21.x86_64 boost-system-1.55.0-8.fc21.x86_64 boost-thread-1.55.0-8.fc21.x86_64 fcgi-2.4.0-24.fc21.x86_64 glog-0.3.3-3.128tech.x86_64 libgcc-4.9.2-1.fc21.x86_64 libstdc++-4.9.2-1.fc21.x86_64 

ok after investigation believe have answer problem. , believe bug in library. after have confirmed library owners ensure date results there.

i have produced simple program below demonstrates problem. issue stems multiple inheritance, polymorphism, , casting. in program below notice create derived object. derived object when laid out in memory have format approximately.:

derived:   base2::vtable   base2::var   base::vtable 

consider:

(gdb) print ptr $2 = std::shared_ptr (count 1, weak 0) 0x63c580 (gdb) print *ptr $3 = (derived &) @0x63c580: {<base2> = {_vptr.base2 = 0x421f90 <vtable derived+16>, var = ""}, <base> = {_vptr.base = 0x421fa8 <vtable derived+40>}, <no data fields>} 

now when dynamic_pointer_cast base have:

(gdb) print ptr $8 = std::shared_ptr (count 2, weak 0) 0x63c590 (gdb) print *ptr $9 = (base &) @0x63c590: {_vptr.base = 0x421fa8 <vtable derived+40>} 

this problem begins. on /usr/include/cereal/types/polymorphic.hpp, line 341. have ptr base. here have:

binding->second.shared_ptr(&ar, ptr.get()); 

which ends being cast const void*. later on based on type info cast type registered polymorphic type. since shared_ptr points object of derived type means derived*. seen below:

272       static inline void savepolymorphicsharedptr( archive & ar, void const * dptr, std::false_type /* has_shared_from_this */ ) 273       { 274         polymorphicsharedpointerwrapper psptr( dptr ); 275         ar( cereal_nvp_("ptr_wrapper", memory_detail::make_ptr_wrapper( psptr() ) ) ); 276       } 

now means down stack ptr base* cast void* , cast derived*. , cast chain results in invalid object. seen below ptr invalid now:

(gdb) print *ptr $7 = (const derived &) @0x63c590: {<base2> = {_vptr.base2 = 0x421fa8 <vtable derived+40>, var = <error reading variable: cannot access memory @ address 0x49>}, <base> = {_vptr.base = 0x0}, <no data fields>} 

the pointer pointing vtable base , not derived/base2 should program crashes:

{     "ptr": {         "polymorphic_id": 2147483649,         "polymorphic_name": "derived",         "ptr_wrapper": {             "id": 2147483649,             "data": {                 "base2": {  program received signal sigsegv, segmentation fault. 0x00007ffff7b8e9e3 in std::string::size() const () /lib64/libstdc++.so.6 

below sample program reproduces this:

// g++ test.cpp -std=c++11 -ggdb -o test && gdb ./test #include <cereal/archives/json.hpp> #include <cereal/types/polymorphic.hpp> #include <iostream>  struct base {     virtual void foo() { }      template<class archive> void serialize(archive& archive) { }  };  struct base2 {     virtual void foo() { }      std::string var;     template<class archive> void serialize(archive& archive) {            archive(cereal::make_nvp("var", var));     }    };  struct derived : public base2, public base {     template<class archive> void serialize(archive& archive) {            archive(cereal::make_nvp("base2",                                  cereal::base_class<base2>(this)),                 cereal::make_nvp("base",                                  cereal::base_class<base>(this)));     }    };  cereal_register_type(base); cereal_register_type(base2); cereal_register_type(derived);  int main() {     auto ptr = std::make_shared<derived>();     cereal::jsonoutputarchive ar(std::cout);     ar(cereal::make_nvp("ptr", std::dynamic_pointer_cast<base>(ptr)));      return 0; }