Boost C++ Libraries: Ticket #8698: Boost.Intrusive unordered_set should use different value for end() https://svn.boost.org/trac10/ticket/8698 <p> The current implementation uses one-past the end of the provided buckets array as the special value for end(). </p> <p> While this is generally OK, there are cases where it is problematic (i.e., the collection is now unusable). Specifically, consider where the buckets array is created on the stack, and the objects are also on the stack. The compiler can (and gcc does) reorder stack variables. If this happens in just the right way, you can end up with an object that you add to the collection living at the address of the special end marker. </p> <p> If this happens, then the insert is successful (the cached size even increments), but since the pointer to the inserted value has the same value as end(), then finding and iterating is now broken. </p> <p> Furthermore, if both the bucket array and objectes inserted are allocated from the heap, then the possibility of the above is still present (though unlikely). </p> <p> Unfortunately, this has actually happened to me so that I now allocate an extra bucket in the bucket array and pass size as N-1. This wastes a bucket that is never used, but prevents the horrible bug of inserting an object, but not finding it... </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/8698 Trac 1.4.3 viboes Thu, 20 Jun 2013 17:32:31 GMT component changed; owner set https://svn.boost.org/trac10/ticket/8698#comment:1 https://svn.boost.org/trac10/ticket/8698#comment:1 <ul> <li><strong>owner</strong> set to <span class="trac-author">Ion Gaztañaga</span> </li> <li><strong>component</strong> <span class="trac-field-old">None</span> → <span class="trac-field-new">intrusive</span> </li> </ul> Ticket Ion Gaztañaga Sat, 22 Jun 2013 18:28:12 GMT <link>https://svn.boost.org/trac10/ticket/8698#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8698#comment:2</guid> <description> <p> Thanks for the report. I don't fully understand your explanation, though. Could you provide an small compilable example of your scenario so that I can reproduce the issue and solve it quickly? Thanks. </p> </description> <category>Ticket</category> </item> <item> <author>jody_boost@…</author> <pubDate>Sat, 22 Jun 2013 22:41:05 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8698#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8698#comment:3</guid> <description> <p> Here is a contrived example to illustrate the problem. Note that while it is contrived, the actual problem can occur if the memory allocator allocates a node in the first memory location after the buckets array... or both the node and the buckets array are on the stack... the compiler has freedom to reorder stack objects and if it reorders the objects in this special arrangement, then the collection breaks. </p> <pre class="wiki">#include &lt;boost/intrusive/unordered_set.hpp&gt; struct Foo { using By_Id_Hook = boost::intrusive::unordered_set_member_hook&lt; boost::intrusive::link_mode&lt;boost::intrusive::safe_link&gt;&gt;; struct Id_Equal { bool operator()(Foo const &amp;lhs, Foo const &amp;rhs) const { return lhs.id_ == rhs.id_; } }; struct Id_Hash { std::size_t operator()(Foo const &amp;x) const { return x.id_; } }; By_Id_Hook by_id_hook_; std::size_t id_; using By_Id = boost::intrusive::unordered_set&lt; Foo, boost::intrusive::member_hook&lt; Foo, By_Id_Hook, &amp;Foo::by_id_hook_&gt;, boost::intrusive::equal&lt;Id_Equal&gt;, boost::intrusive::hash&lt;Id_Hash&gt;, boost::intrusive::power_2_buckets&lt;true&gt;, boost::intrusive::constant_time_size&lt;true&gt;, boost::intrusive::cache_begin&lt;true&gt; &gt;; Foo() { static std::size_t nextid; id_ = nextid++; } }; int main() { // This test demonstrates the problem exactly, though it is a // contrived use case. The problem is that the unordered_set // collection uses one-past the end of the buckets array to // denote the end of the collection. So, this example reproduces // the problem... constexpr size_t num_buckets = 1024; struct b_t { Foo::By_Id::bucket_type buckets[num_buckets]; Foo f; // To force node memory location } b; Foo::By_Id by_id(Foo::By_Id::bucket_traits(b.buckets, num_buckets)); assert(by_id.size() == 0u); assert(by_id.begin() == by_id.end()); // b.f resides in memory at the same location as one-past the // end of the buckets array -- which is also the same value // used to denote the "end" of the unordered_set. That means // once we insert b.f into the collection, the collection is // broken because the iterator to the object is the same value // as "end" assert(!b.f.by_id_hook_.is_linked()); by_id.insert(b.f); assert(by_id.size() == 1u); // Collection says it has the value assert(b.f.by_id_hook_.is_linked()); // Node says it is linked // This assertion will fail... assert(by_id.begin() != by_id.end()); // Iterators are broken } </pre> </description> <category>Ticket</category> </item> <item> <author>jody_boost@…</author> <pubDate>Sat, 22 Jun 2013 22:48:30 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8698#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8698#comment:4</guid> <description> <p> FWIW, I contrived the example to consistently demonstrate the problem. I actually encountered it in real code. </p> <p> Consider the case where the compiler reorders stack variables. </p> <p> Also, consider the case where you want to create the buckets and objects in sequential memory, to keep the buckets and memory objects close together. </p> <p> As a workaround, I currently construct the unordered set with <a class="missing wiki">BucketSize</a>-1, so the one-past the end of the buffer address is never used for anything else. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Ion Gaztañaga</dc:creator> <pubDate>Mon, 24 Jun 2013 19:58:37 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/8698#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/8698#comment:5</guid> <description> <p> Thanks for the test, it's definitely a bug. An alternative would be to a pointer to the first bucket as the end iterator as this element will never be an inserted type. I'll try this approach for the next release as it might require several changes in the class. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Ion Gaztañaga</dc:creator> <pubDate>Sun, 28 Jul 2013 22:10:45 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/8698#comment:6 https://svn.boost.org/trac10/ticket/8698#comment:6 <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">fixed</span> </li> </ul> <p> (In <a class="changeset" href="https://svn.boost.org/trac10/changeset/85165" title="* Big refactoring in order to reduce template and debug symbol bloat. ...">[85165]</a>) * Big refactoring in order to reduce template and debug symbol bloat. </p> <ul><li>Fixes <a class="closed ticket" href="https://svn.boost.org/trac10/ticket/8698" title="#8698: Bugs: Boost.Intrusive unordered_set should use different value for end() (closed: fixed)">#8698</a> </li><li>Implemented SCARY iterators </li></ul> Ticket