Boost C++ Libraries: Ticket #12542: multi_index constraint violation if rollback callable is passed, but doesn't correct the issue https://svn.boost.org/trac10/ticket/12542 <p> If modify() is called taking two callables (the modifier and the rollback) in which the modifier changes a value one the element that results in the element not being acceptable (because a unique index would have a duplicate entry), then the rollback callable is called to correct the issue. </p> <p> If the rollback callable does not correct the issue, the modification is committed anyway which leads to corruption of the unique index(es) involved. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/12542 Trac 1.4.3 Jon Kalb <jonkalb@…> Fri, 21 Oct 2016 21:05:08 GMT attachment set https://svn.boost.org/trac10/ticket/12542 https://svn.boost.org/trac10/ticket/12542 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">main.cpp</span> </li> </ul> <p> Demonstrating multi_index index corruption bug. </p> Ticket Joaquín M López Muñoz Sat, 22 Oct 2016 08:26:19 GMT <link>https://svn.boost.org/trac10/ticket/12542#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12542#comment:1</guid> <description> <p> Hi Jon, </p> <p> The behavior is in conformance with the reference: </p> <p> "<strong>Requires:</strong> [...] The sequence of operations <code>mod(e)</code>, <code>back(e)</code> restores all keys of the element to their original state." </p> <p> So, if the user-provided rollback fails to restore keys, it is a violation of requirements on the user's side, not the lib's fault. Note that the rollback function has the option to throw if, for some reason, it can't restore the keys: </p> <p> "<strong>Exception safety:</strong> Strong, except if <code>back</code> throws an exception, in which case the modified element is erased [...]" </p> <p> Another issue is whether the documented behavior should be changed so that the library re-checks invariants after <code>back</code> is executed. My personal opinion is that it shouldn't, as we would be imposing an uncalled for overhead on the users who provide a conforming rollback function. Also, the user has the option to set the <a href="http://www.boost.org/libs/multi_index/doc/tutorial/debug.html#invariant_check">invariant-checking mode</a>, which detects and signals this situation. </p> </description> <category>Ticket</category> </item> <item> <author>Jon Kalb <jonkalb@…></author> <pubDate>Sat, 22 Oct 2016 14:51:12 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12542#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12542#comment:2</guid> <description> <p> Joaquin, </p> <p> Thanks very much for your library and for your quick response on this issue. </p> <p> We'll just have to disagree on the correct design for this feature. </p> <p> Obviously I believe that the design is so dangerous that I reported it as a bug rather than a design flaw. It never occurred to me that this design was intentional. </p> <p> The possibility of error on the user's part, particularly in a non-trivial situation, the slight additional overhead of the verification (particularly given the likely uncommonness of the rollback needing to be called), and the catastrophic nature of the potential failure are what make me think that shifting the burden to the user here is ill-designed. </p> <p> Consider that the user cannot verify that fallback either will or has done the correct thing, nor is there a way to query if the index has been corrupted (not that there would be a way to correct the situation if it were). </p> <p> I discovered this while preparing a talk on various Boost libraries. When presenting this I will warn attendees that the rollback feature is unsafe to use in any but the most trivial cases. This warning will certainly weaken the generally positive thrust of the talk on this library, but to omit the warning would be a disservice to my audience. </p> <p> Of course if it were possible for the user to themselves verify (in the rollback) if the rollback has met the requirement, then it would be a usable feature. In that case, the best practice recommendation would be for the user to *always* do the verify in any non-trivial situations (where a uniqueness constraint is or may be in place). So the user is not benefiting from the lack of "uncalled for overhead," because the best practice would be to call for it. (Of course an opt-in method of disabling the check would be a useful feature in the, uncommon, I would expect, situation where the rollback is both in a tight loop and guaranteed to be successful.) </p> <p> Just rhetorically I'll ask you: Why did you make all iterators const? This design decision adds an uncalled for overhead in those situations where the user wishes to modify data that the users knows is not in any index. But it is clearly the correct decision because otherwise the burden placed on the user and the devastating nature of a mistake on the user's part would make this a dangerous library to use. </p> <p> As it is, it has one very dangerous design flaw--the rollback feature. </p> <p> I didn't address the invariant-checking mode issue because I feel that is clearly not relevant. It effects performance of the entire library, not just in the probably rare rollback scenario and it isn't well designed to be useful in that scenario. </p> <p> But I do agree very much with one thing in the documentation describing the invariant-checking mode: "Any assertion of this kind should be regarded in principle as a bug in the library." I think that any opportunity for the user to corrupt the index in "normal" code (code without casts, buffer overruns, or language undefined behavior) is a bug in the design of the library. </p> <p> Please reconsider the design of this particular feature. </p> <p> Thanks again for your library and for your quick reply. </p> <p> Jon </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Sat, 22 Oct 2016 16:03:33 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12542#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12542#comment:3</guid> <description> <p> Hi Jon, </p> <p> Let me keep this open to think it over more carefully. I am sympathetic to, but not fully convinced about, your position. You say: </p> <p> <em>Of course if it were possible for the user to themselves verify (in the rollback) if the rollback has met the requirement, then it would be a usable feature.</em> </p> <p> I have a hard time figuring out what kind of rollback function would be unable to determine that the invariant has been restored, as this user-provided function is meant to <em>restore the original key</em>, not to try something new (of course the user can do whatever she pleases, but that's another matter). Following your example, she'd do: </p> <pre class="wiki">bool succeed{name_index.modify( it, [](employee&amp; e) {e.id = 1;}, [original_id = it-&gt;id](employee&amp; e) { e.id = original_id;})}; </pre><p> This is not a rhetorical question: could you conceive of some realistic situation in which the rollback function honestly tries to restore the key but could potentially fail to do so (exceptions aside)? The existence of such scenario would certainly make it advisable to folllow your proposed design. </p> <p> One thing I readily concede to you is that my suggestion of using the invariant-checking mode for this is ill-advised. You're right this mode should be reserved to detect internal bugs, not user misbehaviors. </p> <p> <em>This warning will certainly weaken the generally positive thrust of the talk on this library, but to omit the warning would be a disservice to my audience.</em> </p> <p> I don't expect from you anything less than telling the audience about any perceived problem you observe :-) </p> </description> <category>Ticket</category> </item> <item> <author>Jon Kalb <jonkalb@…></author> <pubDate>Sat, 22 Oct 2016 18:14:55 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12542#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12542#comment:4</guid> <description> <p> I think we are mostly in agreement here and the discussion is coming down to two issues. One is should the library ever “take the user’s word” that constraints are met and the other is how reasonable/likely is it that the user can be certain (or uncertain) that rollback is successful. </p> <p> On the second, I have somewhat mixed feelings. I can see that in many cases, the only change being made is to a single field and restoring the old value of the field would seem to be a straightforward fix. This is, of course, the example I sent you. But I deliberately chose as trivial an example as possible for this report. </p> <p> I can imagine that in real code, the element modification might involve calling a transformation function whose effects might be non-trivial to rollback (particularly under maintenance which might not be sensitive to the rollback issue). The solution that would make me, as a library user, feel comfortable would be to have a copy of the original element value to restore. But since the copy would have to be made in all cases (not just the rollback case), the modify() member function would, in practice, have no advantage over the replace() member function. I’d still have to make copies of the element with each modify. </p> <p> On the first issue, I’m still of the opinion that the library code should always be defensive about any change that could corrupt the indexes and never just put the burden on the user to always have done the right thing. I think this is consistent with your own thinking when you wrote the comment that I quoted from the invariant-checking mode which says that any invariant validation is in principle a bug in the library. </p> <p> I hope that the example of a transformation function which may be non-trivial to rollback is sufficient to sway you, but even if not, consider that the library code really should defend against corruption. Perhaps it can’t (and for performance reasons shouldn’t) guard against Machiavelli, but it should always be on guard against Murphy. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Sun, 23 Oct 2016 18:30:39 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12542#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12542#comment:5</guid> <description> <p> Hi Jon, </p> <p> OK, I think I'm buying your point. My thought process has been somewhat convoluted, though, let me explain: </p> <p> Re-reading the specs for non-rollback <code>modify</code> I noticed this (italics mine): </p> <p> "<strong>Exception safety:</strong> Basic. If an exception is thrown by some user-provided operation <em>(except possibly <code>mod</code>)</em>, then the element pointed to by <code>position</code> is erased." </p> <p> So I was like, am I really allowing the user to throw inside <code>mod</code> without the lib checking the consistency of the element? In fact I am: </p> <pre class="wiki">template&lt;typename Modifier&gt; bool modify_(Modifier&amp; mod,node_type* x) { mod(const_cast&lt;value_type&amp;&gt;(x-&gt;value())); BOOST_TRY{ ... </pre><p> and this opens up a real case of consistency breach: </p> <pre class="wiki">struct element { std::string x,y; }; ... i.modify(it,[](element&amp; e){ e.x="whatever"; // dynamic allocation, can throw e.y="and ever"; // idem }); </pre><p> This example can throw in between assigments to <code>x</code> and <code>y</code>, making <code>modify</code> return without checking for consistency (uniqueness, rearranging). So, this is a design flaw in <code>modify</code> (both with and without rollback) and I have to correct it. We have two options when <code>mod</code> throws: delete the element or go ahead with rearranging; as throwing is an indication of failure, I think the natural thing to do is delete, and moreover going ahead in the rollback version is problematic, since the element was left in mid-modification and <code>rollback</code> could expect something else (your opinion on this point most welcome). The current situation is then </p> <ul><li><code>mod</code> is not guarded against throws but is guarded against inconsistency (rollback or element deletion if no rollback provided) </li><li><code>rollback</code> is guarded against throws (element deletion) but is not guarded against inconsistency </li></ul><p> and after I correct the problem with <code>mod</code> we'll have </p> <ul><li><code>mod</code> is guarded against throws (element deletion) and inconsistency (rollback or element deletion if no rollback provided) </li><li><code>rollback</code> is guarded against throws (element deletion) but not guarded against inconsistency </li></ul><p> and now I'm leaning more towards your position out of considerations of symmetry: </p> <ul><li><code>mod</code> is guarded against throws (element deletion) and inconsistency (rollback or element deletion if no rollback provided) </li><li><code>rollback</code> is guarded against throws and inconsistency (element deletion) </li></ul><p> So, I'll put myself to clean this up. The changes required are not entirely trivial, though, and require extra care to get them right (as I guess you'll guess after this conversation), so it's going to take me some weeks before I find the time --I'm also in the process of finishing something up for Boost submission, so my current C++ slot is occupied. </p> <p> Thanks for the interesting discussion, </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Joaquín M López Muñoz</dc:creator> <pubDate>Sun, 23 Oct 2016 18:32:03 GMT</pubDate> <title>status changed https://svn.boost.org/trac10/ticket/12542#comment:6 https://svn.boost.org/trac10/ticket/12542#comment:6 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">assigned</span> </li> </ul> Ticket Joaquín M López Muñoz Thu, 24 Aug 2017 21:46:08 GMT status changed; resolution set https://svn.boost.org/trac10/ticket/12542#comment:7 https://svn.boost.org/trac10/ticket/12542#comment:7 <ul> <li><strong>status</strong> <span class="trac-field-old">assigned</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <p> Addressed at <a class="ext-link" href="https://github.com/boostorg/multi_index/commit/b9a4467f3425cdae1d472b53516efbac74770792"><span class="icon">​</span>b9a4467f3425cdae1d472b53516efbac74770792</a>. </p> Ticket