Boost C++ Libraries: Ticket #10740: Multi-level containers do not cooperate with address tracking https://svn.boost.org/trac10/ticket/10740 <p> Serializing nested sequence containers does not track the object addresses correctly. See the attached example. </p> <p> Non-sequence containers may also be subject to this bug, I did not check them. </p> <p> The problem is the push_back in collections_load_imp.hpp:65. If t is itself a container, all elements of t get copied, but their address is not updated in the archive. In C++11, using emplace() and std::move() should solve the problem (did not test this). </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/10740 Trac 1.4.3 Simon Etter <ettersi@…> Mon, 03 Nov 2014 16:17:14 GMT attachment set https://svn.boost.org/trac10/ticket/10740 https://svn.boost.org/trac10/ticket/10740 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">bug.cpp</span> </li> </ul> <p> Minimal example </p> Ticket Robert Ramey Thu, 27 Nov 2014 07:55:52 GMT status changed; resolution set https://svn.boost.org/trac10/ticket/10740#comment:1 https://svn.boost.org/trac10/ticket/10740#comment:1 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">invalid</span> </li> </ul> <p> I looked at your example and I think it's wrong. I think that you've assumed incorrectly that the lower level container will be reloaded to the original address. There is no reason to believe this. I've modified you program to check the contents of the lower level container and verify that they have been restored correctly </p> <p> If you're not convinced, feel free to look into this and make another small example </p> Ticket Simon Etter <ettersi@…> Thu, 27 Nov 2014 09:21:30 GMT attachment set https://svn.boost.org/trac10/ticket/10740 https://svn.boost.org/trac10/ticket/10740 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">bug.2.cpp</span> </li> </ul> <p> bug.cpp with more comments </p> Ticket Simon Etter <ettersi@…> Thu, 27 Nov 2014 09:30:04 GMT <link>https://svn.boost.org/trac10/ticket/10740#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:2</guid> <description> <p> I am not convinced, but I most admit my initial bug report did not point out the problem very clearly. I apologize for that. </p> <p> I uploaded a new version of the minimal non-working example which I commented more extensively. I hope this makes it clear where I see the problem. </p> <p> I am aware that stored and reloaded objects may be located at different memory addresses. To my understanding, Boost.Serialization wishes to maintain the invariant that if a pointer pointed to an object before the serialization/deserialization process, it also does so afterwards. My example shows a setting where this is not the case. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Robert Ramey</dc:creator> <pubDate>Thu, 27 Nov 2014 18:19:57 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:3</guid> <description> <p> "To my understanding, Boost.Serialization wishes to maintain the invariant that if a pointer pointed to an object before the serialization/deserialization process, it also does so afterwards." </p> <p> nope. loading an object serialized through a pointer creates a NEW pointer which is "equivalent" to the old one. It's equivalent in that it points to a new and disjoint versions of the original. </p> <pre class="wiki"> // Again, we would like pd to point to the dummy object, independent // of where it is loaded. This is not the case, however. assert(&amp;l.back().back() == pd); // Does fire!!! </pre><p> Nope: we expect pd to point to some new object - disjoint but equivalent to the original one. </p> <pre class="wiki"> // Again, let pd point to the dummy object dummy* pd = &amp;l.back().back(); // Same as before... { std::ofstream ofs("two_level.xml"); boost::archive::xml_oarchive oa(ofs); oa &lt;&lt; BOOST_SERIALIZATION_NVP(l) &lt;&lt; BOOST_SERIALIZATION_NVP(pd); } dummy* pd1; { std::ifstream ifs("two_level.xml"); boost::archive::xml_iarchive ia(ifs); ia &gt;&gt; BOOST_SERIALIZATION_NVP(l) &gt;&gt; BOOST_SERIALIZATION_NVP(pd1); } assert(*pd == *pd1); // should be true </pre> </description> <category>Ticket</category> </item> <item> <dc:creator>Robert Ramey</dc:creator> <pubDate>Thu, 27 Nov 2014 18:38:13 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:4</guid> <description> <p> Actually I just did this and my assertion fails. This would suggest a failure to track individual members of the array. I'll look into this </p> </description> <category>Ticket</category> </item> <item> <author>Simon Etter <ettersi@…></author> <pubDate>Thu, 27 Nov 2014 21:28:08 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:5</guid> <description> <p> I don't understand your posted code snippet. The second line lets <code>pd</code> point to some element in <code>l</code>. On the third line from below, you read a new vector into <code>l</code>, which calls <code>clear()</code> on <code>l</code> (see collections_load_imp.hpp : 140) and therefore invalidates <code>pd</code>. On the last line, you nevertheless dereference <code>pd</code>. This is undefined behaviour. </p> <p> I think we are still talking past each other. Let me describe the situation once more for the one-level case. Assume <code>oa</code> is any output archive, and <code>l</code> and <code>pd</code> are defined and initialized as follows: </p> <pre class="wiki">std::vector&lt;dummy&gt; l(1); dummy* pd = &amp;l.back(); </pre><p> I call <code>d</code> the object of type <code>dummy</code> which is located at the address <code>&amp;l.back()</code>. I emphasize that the type and address of <code>d</code> are the only relevant properties here. We first serialize <code>l</code>: </p> <pre class="wiki">oa &lt;&lt; l; </pre><p> The implementation of serialization for <code>std::vector&lt;&gt;</code> calls serialize on every element of <code>l</code>, thus also on <code>d</code>. Next, we serialize <code>pd</code>. </p> <pre class="wiki">oa &lt;&lt; pd; </pre><p> According to <a href="http://www.boost.org/doc/libs/1_57_0/libs/serialization/doc/serialization.html#pointeroperators">http://www.boost.org/doc/libs/1_57_0/libs/serialization/doc/serialization.html#pointeroperators</a>, the serialization code checks whether an object of type <code>dummy</code> at address <code>&amp;d</code> has already been serialized. Since <code>d</code> was already serialized as an element of <code>l</code>, this is indeed the case. Thus, we only store some special tag for <code>pd</code>, no actual object information. </p> <p> Next, we create some input archive <code>ia</code> which reads the same file as <code>oa</code> wrote to. We first deserialize the <code>l</code> from above, which we now call <code>l_</code>: </p> <pre class="wiki">std::vector&lt;dummy&gt; l_; ia &gt;&gt; l_; </pre><p> This creates a new object of type <code>dummy</code> at some arbitrary address which we can get through <code>&amp;l_.back()</code>. We define <code>d_</code> to be this object identified by the combination of type and address. When deserializing the <code>pd</code> from above into <code>pd_</code>, </p> <pre class="wiki">dummy* pd_; ia &gt;&gt; pd_; </pre><p> we encounter the special tag that we wrote to the archive instead of the proper object. By the same section of the documentation as mentioned above, this should allow the serialization library to recognize that it does not need to create a new object but rather let <code>pd_</code> refer to <code>d_</code>. We check this through the following assert: </p> <pre class="wiki">assert(pd_ = &amp;l_.back()); </pre><p> As mentioned, for a single level of <code>std::vector</code>, this indeed works. </p> <p> The key point is that <code>oa &lt;&lt; l;</code> and <code>oa &lt;&lt; pd</code> try to serialize an object of the same type at the same address. According to the documentation on serializing pointers, the library detects this situation and stores only one object. But exactly the same situations occurs for multilevel containers! From the user's perspective, there is thus no reason to expect this situation not to work. </p> <hr /> <p> In the remainder, I'll try to explain why in fact it does not work with the current implementation. The problem is on lines 61 to 67 in collections_load_imp.hpp, and the line numbers below refer to this section. Assume we serialized by running the following code </p> <pre class="wiki">std::vector&lt;std::vector&lt;dummy&gt;&gt; l(1); oa &lt;&lt; l; </pre><p> and are now about to deserialize this object. The lines </p> <pre class="wiki">std::vector&lt;std::vector&lt;dummy&gt;&gt; l_; ia &gt;&gt; l_; </pre><p> cause the following to happen. We read that <code>l</code> contained a single element. We therefore create a temporary (line 62), which we call <code>tll_</code>, and deserialize this single element into it (line 64). Now <code>tll_</code> is a <code>std::vector&lt;dummy&gt;</code> of length 1. Next, we call <code>l_.push_back(tll_)</code> (line 65). Since we would like future pointers to this "logical" object to point to <code>&amp;l_.back()</code> and not <code>&amp;tll_</code>, we tell the new address to the archive by calling <code>reset_pointer_address()</code>. At this point, the error happens: We would also have to tell the archive that we want future pointers to reference <code>&amp;l_.back().back()</code> and not <code>&amp;tll_.back()</code>. We cannot do this, however, because at this point in the code we don't know that <code>tll_</code> (which corresponds to the variable <code>s</code> in the actual code) is in fact a vector. In conclusion, I thus know where the bug is (and I am pretty sure it is in fact a bug), but I don't know how to solve it. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Robert Ramey</dc:creator> <pubDate>Thu, 27 Nov 2014 21:40:05 GMT</pubDate> <title>attachment set https://svn.boost.org/trac10/ticket/10740 https://svn.boost.org/trac10/ticket/10740 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">test_z.cpp</span> </li> </ul> <p> altered version of bug.cpp </p> Ticket Robert Ramey Thu, 27 Nov 2014 21:42:56 GMT <link>https://svn.boost.org/trac10/ticket/10740#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:6</guid> <description> <p> OK - I got the code snippet wrong. I was disturbed by your example re-using the variable "l" which introduced other possibilities. I've pared your example down to the simplest one that I think should demonstrate any failure and uploaded it under the same name as before test_z.cpp. </p> <p> I used the simpler text_?archive I separated the output variables l and p from l1 and p1 I just included the failing case. </p> <p> I run this program and sometimes it asserts and sometimes it doesn't. This has to be a bug - and one that is going to be damn difficult to find. Is this equivalent to your bug? </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Robert Ramey</dc:creator> <pubDate>Thu, 27 Nov 2014 21:57:57 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:7</guid> <description> <p> " Since we would like future pointers to this "logical" object to point to &amp;l_.back() and not &amp;tll_, we tell the new address to the archive by calling reset_pointer_address(). At this point, the error happens: We would also have to tell the archive that we want future pointers to reference &amp;l_.back().back() and not &amp;tll_.back(). We cannot do this, however, because at this point in the code we don't know that tll_ (which corresponds to the variable s in the actual code) is in fact a vector." </p> <p> Not quite that simple </p> <p> given: std::vector&lt;dummy&gt; t1; std::vector&lt;std::vector&lt;dummy&gt;&gt; t2(t1) </p> <p> ia &gt;&gt; t2 </p> <blockquote> <p> for each t in t2 </p> <blockquote> <p> ia &gt;&gt; t </p> <blockquote> <p> for each x in t </p> <blockquote> <p> ia &gt;&gt; x resetobjectaddress (x) <em> moves pointer to everythingf t1 </em></p> </blockquote> <p> resetobjectaddress(t) <em> moves pointers to everything under t2 </em></p> </blockquote> </blockquote> </blockquote> <p> note that the addresses in t1 get moved twice ! std::vector&lt;dummy&gt; t </p> <p> So this is pretty subtle - the fact that sometimes it works and sometimes it doesn't means it's going to be a bitch to find. </p> </description> <category>Ticket</category> </item> <item> <author>Simon Etter <ettersi@…></author> <pubDate>Fri, 28 Nov 2014 08:21:10 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:8 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:8</guid> <description> <p> Regarding <a class="ticket" href="https://svn.boost.org/trac10/ticket/10740#comment:6" title="Comment 6">comment:6</a>: Let's say it is a consequence of my bug. The point is that <code>pd1</code> does <em>not</em> point to <code>&amp;l1[0][0]</code>. Rather it points to what I called <code>&amp;tll_.back()</code>. Since <code>tll_</code> was only a temporary and is deleted by now, dereferencing <code>pd1</code> results once again in undefined behaviour. </p> <p> On your machine, it seems that sometimes your "lucky" and <code>l1[0][0]</code> gets allocated at the location where <code>tll_[0]</code> was, which is the one <code>pd1</code> points to. On my machine, I am not that lucky and the assert fires every time. So if you want to do debugging, I suggest you look for a machine that behaves as mine. I use g++ version 4.8.1 with the <code>--std=c++11</code> switch running on a Ubuntu 14.04. If you need further information, you know where to find me :-) </p> <p> Yes, the addresses of the <code>dummy</code> objects (<code>x</code>, in your pseudocode) get moved twice. But the first move is covered by the resetobjectaddress(x), so this does not cause a problem. The problem is the second move, for which you only call resetobjectaddress(t), but you should also be calling </p> <pre class="wiki">for each x in t resetobjectaddress(x) </pre> </description> <category>Ticket</category> </item> <item> <author>Simon Etter <ettersi@…></author> <pubDate>Fri, 28 Nov 2014 08:52:33 GMT</pubDate> <title>attachment set https://svn.boost.org/trac10/ticket/10740 https://svn.boost.org/trac10/ticket/10740 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">vector.hpp</span> </li> </ul> <p> Partial fix. </p> Ticket Simon Etter <ettersi@…> Fri, 28 Nov 2014 09:01:01 GMT <link>https://svn.boost.org/trac10/ticket/10740#comment:9 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:9</guid> <description> <p> I uploaded a fix for <code>std::vector</code>. If I include this header instead of the Boost.Serialization code for <code>std::vector</code>, <code>test_z.cpp</code> runs without problems. </p> <p> The fix could be easily incorporated into <code>collections_load_imp.hpp</code>. The only problem is that it requires C++11 emplace and move semantics. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Robert Ramey</dc:creator> <pubDate>Sat, 13 Dec 2014 17:14:37 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:10 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:10</guid> <description> <p> OK - I'm pretty sure I've definitively fixed this for non-indexed containers - vector, list, etc. A fix for indexed containers is still pending and would be trickier. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Simon Etter</dc:creator> <pubDate>Sun, 14 Dec 2014 09:33:26 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:11 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:11</guid> <description> <p> Thanks a lot for your work! I had a look at your changes to <code>std::vector</code> deserialization and agree that this fixes the problem. However, the use of <code>std::vector::resize()</code> (vector.hpp:88) breaks support for non-default-constructible types, doesn't it? </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Robert Ramey</dc:creator> <pubDate>Sun, 14 Dec 2014 18:17:05 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:12 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:12</guid> <description> <p> Damn! </p> <p> This was probably why I implemented it the way I did originally. When I made this fix, I use presumed that the the type T in vector&lt;T&gt; had to be default constructible as a requirement. This turned out to be wrong - the requirement is <a class="missing wiki">CopyConststructable</a>. But it's worse, the requirements for c++11 are different. In fact, the requirements vary by function so it's even trickier to get right. </p> <p> To get this really right the following needs to be considered: a) Go back to the previous method - but use move and/or swap to place the item in the array. move is only available with C++11 though there is a boost move which works with previous versions but it may require some more declarations so I'm not sure it would work. b) use the current method and live with the new restriction. Or possible use boost::has_trivial_constructor to condition which method is used. c) one has to be sure that it's as fast as possible. The reason for this isn't performance, it's that some bonehead will (and have) made a "benchmark" which makes a really simple case then uses it to make a very broad statement that the serialization library is really slow. d) I would like to keep a c++03 implementation available - conditioned on a config flag. </p> <p> For now I'm setting this aside - but you're free to invest some effort in it if you want. I'm happy to incorporate improvements from other authors. But note that all the above has to be addressed - not just a fix for a specific case. </p> <p> Robert Ramey </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Sun, 14 Dec 2014 18:54:19 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:13 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:13</guid> <description> <p> uh oh - your comment made me realize that I created a huge blunder. I went and looked this up in the code. It turns out that that original implementation used stack_construct which in turn used load_construct_data which permitted non-default constructors. So the current implementation does use this - so it's likely breaking reads on existing archives !!! So This can't be ignored. I'll see what I can do about it. As usual, making any kind of change turns out to have a lot more implications than first meet the eye. The difficulty and effort required to write and maintain this library are way under-estimated. </p> <p> Robert Ramey </p> </description> <category>Ticket</category> </item> <item> <author>indrek@…</author> <pubDate>Wed, 23 Sep 2015 14:07:28 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10740#comment:14 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:14</guid> <description> <p> I just wasted an entire day of my life with this bug. Why has it not been fixed? Just use this patch. It fixes the problem, makes deserialize faster, and should not break anything existing: </p> <div class="wiki-code"><div class="code"><pre>--- original.collections_load_imp.hpp 2015-09-23 16:17:14.772000000 +0300 +++ collections_load_imp.hpp 2015-09-23 16:26:08.396000000 +0300 @@ -60,10 +60,9 @@ ){ typedef BOOST_DEDUCED_TYPENAME Container::value_type type; detail::stack_construct&lt;Archive, type&gt; t(ar, v); - // borland fails silently w/o full namespace - ar &gt;&gt; boost::serialization::make_nvp(&quot;item&quot;, t.reference()); s.push_back(t.reference()); - ar.reset_object_address(&amp; s.back() , &amp; t.reference()); + // borland fails silently w/o full namespace + ar &gt;&gt; boost::serialization::make_nvp(&quot;item&quot;, s.back()); return hint; } }; </pre></div></div> </description> <category>Ticket</category> </item> <item> <author>indrek@…</author> <pubDate>Wed, 23 Sep 2015 15:47:24 GMT</pubDate> <title>status changed; resolution deleted https://svn.boost.org/trac10/ticket/10740#comment:15 https://svn.boost.org/trac10/ticket/10740#comment:15 <ul> <li><strong>status</strong> <span class="trac-field-old">closed</span> → <span class="trac-field-new">reopened</span> </li> <li><strong>resolution</strong> <span class="trac-field-deleted">invalid</span> </li> </ul> <p> Reopening. The fix seems really simple and straightforward. So why not? </p> Ticket Robert Ramey Mon, 28 Sep 2015 05:24:00 GMT status changed; resolution set https://svn.boost.org/trac10/ticket/10740#comment:16 https://svn.boost.org/trac10/ticket/10740#comment:16 <ul> <li><strong>status</strong> <span class="trac-field-old">reopened</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">invalid</span> </li> </ul> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/10740#comment:15" title="Comment 15">indrek@…</a>: </p> <blockquote class="citation"> <p> Reopening. The fix seems really simple and straightforward. So why not? </p> </blockquote> <p> Well, it might look simple and straight forward to you - but not to me. </p> <p> Since version 1.56, this file has undergone some changes so I don't think that your patch can be applied. I think these changes were actually to address this specific problem. So if you try the latest version you might have better luck. </p> <p> Robert Ramey </p> Ticket indrek@… Mon, 28 Sep 2015 07:40:06 GMT <link>https://svn.boost.org/trac10/ticket/10740#comment:17 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10740#comment:17</guid> <description> <p> I clicked on the top left corner of this site, saw "The development branch is available at <a class="ext-link" href="http://svn.boost.org/svn/boost/trunk"><span class="icon">​</span>http://svn.boost.org/svn/boost/trunk</a>", got the development branch of boost, and it was still broken there. Apparently I got the wrong development branch. Would have helped if there was a note that it is no longer used -- I did try my best to report on the current version. I confirm it has been fixed in 1.58. </p> </description> <category>Ticket</category> </item> </channel> </rss>