Boost C++ Libraries: Ticket #12578: Crash with boost::filesystem's directory_iterator and recursive_directory_iterator https://svn.boost.org/trac10/ticket/12578 <p> I have a simple example using <code>recursive_directory_iterator</code> that crashes, but it also crashes with <code>directory_iterator</code>. I am aware from the documentation that "[t]he practical consequence of not preserving equality is that directory iterators can only be used for single-pass algorithms", but I don't see why my use here would invoke undefined behavior since I make a copy of a <code>const</code> iterator. </p> <div class="wikipage" style="font-size: 80%"><div class="wiki-code"><div class="code"><pre><span class="cp">#include</span> <span class="cpf">&lt;boost/filesystem.hpp&gt;</span><span class="cp"></span> <span class="cp">#include</span> <span class="cpf">&lt;boost/range/iterator_range.hpp&gt;</span><span class="cp"></span> <span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp"></span> <span class="k">namespace</span> <span class="n">fs</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">filesystem</span><span class="p">;</span> <span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">fs</span><span class="o">::</span><span class="n">recursive_directory_iterator</span><span class="p">{</span> <span class="s">&quot;.&quot;</span> <span class="p">};</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">distance</span><span class="p">(</span> <span class="n">iter</span><span class="p">,</span> <span class="p">{}</span> <span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">;</span> <span class="k">for</span><span class="p">(</span> <span class="k">const</span> <span class="k">auto</span><span class="o">&amp;</span> <span class="nl">entry</span> <span class="p">:</span> <span class="n">boost</span><span class="o">::</span><span class="n">make_iterator_range</span><span class="p">(</span> <span class="n">iter</span><span class="p">,</span> <span class="c1">// CRASH! </span> <span class="p">{}</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">entry</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">catch</span><span class="p">(</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">exception</span><span class="o">&amp;</span> <span class="n">e</span> <span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="n">e</span><span class="p">.</span><span class="n">what</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">;</span> <span class="p">}</span> <span class="k">catch</span><span class="p">(</span> <span class="p">...</span> <span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;Unknown exception.</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </pre></div></div></div><p> This crashes on several compilers, Windows and Linux, and multiple versions of Boost.<sup><a class="closed ticket" href="https://svn.boost.org/trac10/ticket/12578#crash" title="#12578: Bugs: Crash with boost::filesystem's directory_iterator and ... (closed: fixed)">(a)</a></sup> </p> <p> Note that <code>iter</code> is <code>const</code>. I can workaround this by (1) replacing <code>iter</code><code></code><sup><a class="closed ticket" href="https://svn.boost.org/trac10/ticket/12578#replaceIter" title="#12578: Bugs: Crash with boost::filesystem's directory_iterator and ... (closed: fixed)">(b)</a></sup> on the line marked "CRASH" with <code>fs::recursive_directory_iterator{ "." }</code> or (2) deleting the call to <code>std::distance()</code> just before the for loop or by changing its arguments<sup><a class="closed ticket" href="https://svn.boost.org/trac10/ticket/12578#replaceDist" title="#12578: Bugs: Crash with boost::filesystem's directory_iterator and ... (closed: fixed)">(c)</a></sup>. Either of these have the effect of not advancing a copy of <code>iter</code> to the end. </p> <p> I suspect this has something to do with the copy of <code>iter</code> made by <code>std::distance()</code> and <code>boost::make_iterator_range()</code> still being secretly connected to the internal state of <code>iter</code>, though it is a <code>const</code> iterator from which a deep copy was supposed to be made. It also happens with experimental::filesystem<sup><a class="closed ticket" href="https://svn.boost.org/trac10/ticket/12578#expFS" title="#12578: Bugs: Crash with boost::filesystem's directory_iterator and ... (closed: fixed)">(d)</a></sup> and multiple versions of Boost (at least 1.61.0 and 1.55.0). </p> <p> This same problem can also be seen in other scenarios such as iterating over a directory range twice<sup><a class="closed ticket" href="https://svn.boost.org/trac10/ticket/12578#twice" title="#12578: Bugs: Crash with boost::filesystem's directory_iterator and ... (closed: fixed)">(e)</a></sup> or any other use of a recursive directory iterator which has had a copy made and advanced to end. </p> <p> I asked about this on the Boost Users list<sup><a class="closed ticket" href="https://svn.boost.org/trac10/ticket/12578#user" title="#12578: Bugs: Crash with boost::filesystem's directory_iterator and ... (closed: fixed)">(f)</a></sup>, but got no reply. </p> <p> <strong>Links</strong> </p> <ul><li><span class="wikianchor" id="crash">(a)</span> See, e.g., the crash on Coliru with gcc: coliru.stacked-crooked.com/a/15c4a731100d25f2 </li><li><span class="wikianchor" id="replaceIter">(b)</span> coliru.stacked-crooked.com/a/b37f94ebc809fa65 </li><li><span class="wikianchor" id="replaceDist">(c)</span> coliru.stacked-crooked.com/a/17277df962ab5989 </li><li><span class="wikianchor" id="expFS">(d)</span> coliru.stacked-crooked.com/a/8e83f5c7e2adfe90 </li><li><span class="wikianchor" id="twice">(e)</span> coliru.stacked-crooked.com/a/0bfab95a513ebed8 </li><li><span class="wikianchor" id="user">(f)</span> lists.boost.org/boost-users/2016/10/86840.php </li></ul> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/12578 Trac 1.4.3 mlimber@… Thu, 03 Nov 2016 19:09:08 GMT component changed; owner set https://svn.boost.org/trac10/ticket/12578#comment:1 https://svn.boost.org/trac10/ticket/12578#comment:1 <ul> <li><strong>owner</strong> set to <span class="trac-author">Beman Dawes</span> </li> <li><strong>component</strong> <span class="trac-field-old">None</span> → <span class="trac-field-new">filesystem</span> </li> </ul> Ticket mlimber@… Thu, 03 Nov 2016 19:13:11 GMT cc set https://svn.boost.org/trac10/ticket/12578#comment:2 https://svn.boost.org/trac10/ticket/12578#comment:2 <ul> <li><strong>cc</strong> <span class="trac-author">mlimber@…</span> added </li> </ul> Ticket Charles Olivi <charles.olivi@…> Sun, 20 Nov 2016 08:50:31 GMT <link>https://svn.boost.org/trac10/ticket/12578#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:3</guid> <description> <p> Hi, </p> <p> There is no defect, the std::distance calls iteratively the same operation on the single_pass_traversal_tag iterator, i.e. increment(). </p> <p> recursive_directory_iterator is shared_ptr implemented and increment() call pops one element from the iterator stack. </p> <p> In the end, when you enter your loop, iter points to end() which makes the segfault you get. </p> </description> <category>Ticket</category> </item> <item> <author>mlimber@…</author> <pubDate>Mon, 21 Nov 2016 14:00:51 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:4</guid> <description> <p> I guessed that that is what was happening. Thanks for confirming. </p> <p> I would suggest that if that is intentionally part of the design, it is quite a surprising choice and reflects at least a defect in the documentation, if not the design or code. </p> <p> Moreover, if this is in fact intended, I do not understand the reasoning on why this is a good design choice. <em>Prima facie</em>, I would not expect a <em>copy</em> of an iterator to share state with the original (except that they are iterating over the same range). I would expect this function to behave the same regardless of whether I used <code>boost::filesystem::directory_iterator</code> or <code>std::vector::const_iterator</code>: </p> <div class="wikipage" style="font-size: 80%"><div class="wiki-code"><div class="code"><pre><span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">Iter</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="n">print</span><span class="p">(</span> <span class="k">const</span> <span class="n">Iter</span> <span class="n">begin</span><span class="p">,</span> <span class="k">const</span> <span class="n">Iter</span> <span class="n">end</span> <span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">distance</span><span class="p">(</span> <span class="n">begin</span><span class="p">,</span> <span class="n">end</span> <span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">;</span> <span class="k">for</span><span class="p">(</span> <span class="k">const</span> <span class="k">auto</span><span class="o">&amp;</span> <span class="nl">entry</span> <span class="p">:</span> <span class="n">boost</span><span class="o">::</span><span class="n">make_iterator_range</span><span class="p">(</span> <span class="n">begin</span><span class="p">,</span> <span class="n">end</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">entry</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </pre></div></div></div><p> But watch it fail here: coliru.stacked-crooked.com/a/ce85e7adb3c16bdc </p> <p> If I remove the call to <code>std::distance()</code>, it works as expected. This, I contend, is a defect of some kind. </p> </description> <category>Ticket</category> </item> <item> <author>mlimber@…</author> <pubDate>Mon, 21 Nov 2016 17:02:26 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:5</guid> <description> <p> The design rationale appears to be this comment in the code for a member of <code>directory_iterator</code>: </p> <div class="wikipage" style="font-size: 80%"><div class="wiki-code"><div class="code"><pre> <span class="c1">// shared_ptr provides shallow-copy semantics required for InputIterators.</span> <span class="c1">// m_imp.get()==0 indicates the end iterator.</span> <span class="n">boost</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o">&lt;</span> <span class="n">detail</span><span class="o">::</span><span class="n">dir_itr_imp</span> <span class="o">&gt;</span> <span class="n">m_imp</span><span class="p">;</span> </pre></div></div></div><p> While the single-pass requirement is clear from the <code>InputIterator</code> concept, I don't see how it requires this shallow-copy behavior, which appears to me to be a defect in the design/code, as described above. </p> <p> I took a closer look to see what it would take to change that <code>shared_ptr</code> to a <code>unique_ptr</code> (or similar) in order to move <code>directory_iterator</code> to deep-copy semantics, but <code>detail::dir_itr_imp</code> would also need them and that looked platform-specific and non-trivial. </p> <p> I'd be willing to take a whack at creating a patch if you tell me it is a desired change, though I mainly work with mainstream platforms like Windows and POSIX/Linux and so couldn't easily test others. </p> </description> <category>Ticket</category> </item> <item> <author>Charles Olivi <charles.olivi@…></author> <pubDate>Mon, 21 Nov 2016 17:35:25 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:6</guid> <description> <p> Hi, </p> <p> Yep it's indeed the shared pointer that does the job for the single pass iterator used and described here: <a href="http://www.boost.org/doc/libs/1_55_0/doc/html/InputIterator.html">http://www.boost.org/doc/libs/1_55_0/doc/html/InputIterator.html</a> </p> <p> I'm not the maintainer of boost.org/filesystem, I'm pretty sure your fix will be discarded. </p> <p> If you really want to do it with shared_ptr you probably need a copy constructor based on [ {std::move; std::make_shared} / boost::make_shared ] and operator= based on std::swap. </p> <p> Thanks, Charles </p> </description> <category>Ticket</category> </item> <item> <author>mlimber@…</author> <pubDate>Mon, 21 Nov 2016 19:07:36 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:7</guid> <description> <p> Thanks, Charles. </p> <p> I see now: <code>InputIterator</code> and single-pass behavior means a copy of an iterator invalidates any other input iterators to the underlying source, akin to <code>std::istream_iterator</code>. My intuition was that a <code>directory_iterator</code> would be a <code>ForwardIterator</code>, and honestly I didn't fully grasp the distinction between those categories until now. </p> <p> Strictly speaking, there is no defect since this is documented, but (1) the documentation of this is too oblique, IMHO, and (2) I'm not clear on why directory iterators should not be <code>ForwardIterator</code>s. </p> <p> For (1), I would propose adjusting the wording of the notes on <code>directory_iterator</code> and <code>recursive_directory_iterator</code> to make this behavior more apparent, something like: </p> <blockquote> <p> Note: The practical consequence of not preserving equality is that directory iterators can only be used for single-pass algorithms. <em>Also, each copy of an iterator shares state with all the copies, so when one is incremented, they are all effectively incremented.</em> --end note </p> </blockquote> <p> For (2), I would think <code>ForwardIterator</code> would be the preferable concept here, so I'm not sure why <code>InputIterator</code> was chosen. </p> </description> <category>Ticket</category> </item> <item> <author>Charles Olivi <charles.olivi@…></author> <pubDate>Mon, 21 Nov 2016 20:40:33 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:8 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:8</guid> <description> <p> Beware that if you copy iterator the origin is not invalidated, it's just that the shared_ptr increases its ref_count. </p> <p> test the following code based on your example: </p> <ul><li>define a method that takes a fs::recursive_directory_iterator as parameter </li></ul><blockquote> <p> void process_iter(fs::recursive_directory_iterator myIter) { </p> <blockquote> <p> std::cout &lt;&lt; *(myIter++) &lt;&lt; std::endl; </p> </blockquote> <p> } </p> </blockquote> <ul><li>and then in main: </li></ul><blockquote> <p> int main(void) { </p> <blockquote> <p> fs::path aPath("."); </p> </blockquote> </blockquote> <blockquote> <blockquote> <p> fs::recursive_directory_iterator iter(aPath); </p> </blockquote> </blockquote> <blockquote> <blockquote> <p> fs::recursive_directory_iterator end; </p> </blockquote> </blockquote> <blockquote> <blockquote> <p> process_iter(iter); </p> </blockquote> </blockquote> <blockquote> <blockquote> <p> for( const auto&amp; entry : boost::make_iterator_range(iter, end) { </p> <blockquote> <p> std::cout &lt;&lt; entry; </p> </blockquote> <p> } </p> </blockquote> </blockquote> <blockquote> <blockquote> <p> return 0; </p> </blockquote> <p> } </p> </blockquote> <p> expected result is that process_iter call will pop the first element in current directory stream, successive calls in following loop will pop remaining elements, starting from the second one. in the end iter points to end(). </p> <p> Hope this clarifies </p> </description> <category>Ticket</category> </item> <item> <author>mlimber@…</author> <pubDate>Mon, 21 Nov 2016 22:32:39 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:9 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:9</guid> <description> <p> Ah, but but but..... </p> <p> Your example still crashes if the folder you run in has one entry only, as here: </p> <div class="wiki-code"><div class="code"><pre><span class="cp">#include</span> <span class="cpf">&lt;boost/filesystem.hpp&gt;</span><span class="cp"></span> <span class="cp">#include</span> <span class="cpf">&lt;boost/range/iterator_range.hpp&gt;</span><span class="cp"></span> <span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp"></span> <span class="k">namespace</span> <span class="n">fs</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">filesystem</span><span class="p">;</span> <span class="kt">void</span> <span class="nf">process_iter</span><span class="p">(</span><span class="n">fs</span><span class="o">::</span><span class="n">recursive_directory_iterator</span> <span class="n">myIter</span><span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="o">*</span><span class="p">(</span><span class="n">myIter</span><span class="o">++</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="p">}</span> <span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="c1">// Move to a folder with only one entry</span> <span class="n">fs</span><span class="o">::</span><span class="n">create_directories</span><span class="p">(</span> <span class="s">&quot;dir/dir2&quot;</span> <span class="p">);</span> <span class="n">fs</span><span class="o">::</span><span class="n">current_path</span><span class="p">(</span> <span class="n">fs</span><span class="o">::</span><span class="n">current_path</span><span class="p">()</span> <span class="o">/</span> <span class="s">&quot;dir&quot;</span> <span class="p">);</span> <span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">fs</span><span class="o">::</span><span class="n">recursive_directory_iterator</span><span class="p">{</span> <span class="s">&quot;.&quot;</span> <span class="p">};</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">end</span> <span class="o">=</span> <span class="n">fs</span><span class="o">::</span><span class="n">recursive_directory_iterator</span><span class="p">{};</span> <span class="n">process_iter</span><span class="p">(</span> <span class="n">iter</span> <span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="n">iter</span> <span class="o">!=</span> <span class="n">end</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> <span class="k">for</span><span class="p">(</span> <span class="k">const</span> <span class="k">auto</span><span class="o">&amp;</span> <span class="nl">entry</span> <span class="p">:</span> <span class="n">boost</span><span class="o">::</span><span class="n">make_iterator_range</span><span class="p">(</span> <span class="n">iter</span><span class="p">,</span> <span class="n">end</span> <span class="p">)</span> <span class="p">)</span> <span class="c1">// OR for( const auto&amp; entry : iter )</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">entry</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// OR</span> <span class="c1">// while( iter != end )</span> <span class="c1">// {</span> <span class="c1">// std::cout &lt;&lt; &quot;Post: &quot; &lt;&lt; *iter &lt;&lt; &quot;\n&quot;;</span> <span class="c1">// ++iter;</span> <span class="c1">// }</span> <span class="p">}</span> <span class="k">catch</span><span class="p">(</span> <span class="p">...</span> <span class="p">)</span> <span class="p">{</span> <span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o">&lt;&lt;</span> <span class="s">&quot;Exception.</span><span class="se">\n</span><span class="s">&quot;</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </pre></div></div><p> See it fail: coliru.stacked-crooked.com/a/3c4e31bbef79cc6b </p> <p> Note that we are NOT dereferencing <code>iter</code> after the call to <code>process_iter()</code>. The problem is that <code>iter</code> is itself invalidated once the <code>myIter</code> copy is advanced to the end. But as far as I can tell, there is NO WAY to tell whether <code>iter</code> has been invalidated or can still be used. Adding a print statement as I did after the call to <code>process_iter()</code> indicates that the iterator is not equal to end and is therefore valid, but that is wrong! </p> <p> So at the very least, your suggested usage invokes undefined behavior. </p> <p> Compare how istream_iterator behaves: coliru.stacked-crooked.com/a/e5988c4626f2c339 </p> <p> Is that lesson here that input iterators should be considered move-only, not copyable, because copies are too prone to error in untested circumstances (like having only one file, above)? Should that be codified in the standard or at least coding standards? </p> </description> <category>Ticket</category> </item> <item> <author>mlimber@…</author> <pubDate>Tue, 22 Nov 2016 14:46:16 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:10 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:10</guid> <description> <p> On further thought, perhaps the solution is that the <code>directory_iterator</code> should use some sort of observer pattern or weak reference rather than just a <code>shared_ptr</code> for its pointer to the implementation <code>m_imp</code>. </p> <p> At present, the iterator depends on <code>m_imp</code> becoming null when the last directory entry has been reached (see the comment in the operations.hpp: "m_imp.get()==0 indicates the end iterator."), but in the face of shared ownership, one owner can get to end without the others hearing about it or being able to detect it. From boost_1_62_0/libs/filesystem/operations.cpp: </p> <div class="wiki-code"><div class="code"><pre> <span class="kt">void</span> <span class="nf">directory_iterator_increment</span><span class="p">(</span><span class="n">directory_iterator</span><span class="o">&amp;</span> <span class="n">it</span><span class="p">,</span> <span class="n">system</span><span class="o">::</span><span class="n">error_code</span><span class="o">*</span> <span class="n">ec</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// ...</span> <span class="k">if</span> <span class="p">(</span><span class="n">it</span><span class="p">.</span><span class="n">m_imp</span><span class="o">-&gt;</span><span class="n">handle</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">// eof, make end</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">m_imp</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// ...</span> <span class="p">}</span> </pre></div></div><p> So the current copy of the <code>it</code> gets its <code>m_imp</code> reset and is thus now equal to the end iterator, but this does not tell the other shared owners of <code>m_imp</code> that they should reset too. By the nature of <code>shared_ptr</code>, the reference count decreased but the object still lives for all other owners. </p> <p> I personally would still prefer a copy-on-write or other deep-copy semantic (or making it a full-up forward iterator) but I understand that this may be difficult to impossible within the bounds of POSIX <code>readdir_r()</code> and other supported OS file systems. </p> <p> To sum up, possible paths I see to address this are: </p> <ol><li>Make directory iterators do a deep copy or COW. </li><li>Make directory iterators a <code>ForwardIterator</code> (additional semantics beyond deep copy). </li><li>Make directory iterators move-only rather than copyable. This could involve changing the concept of <code>InputIterator</code> (or making a new concept <code>MoveableInputIterator</code> so as to leave other input iterators undisturbed). </li><li>Make directory iterator use some sort of weak ref or observer pattern to update all copies that they have been invalidated. </li><li>Update the documentation to indicate the pitfalls more clearly and suggest best practices for coding standards to adopt. </li></ol><p> I'm willing to lend a hand, but I don't want to go down a path that you all know would be rejected in principle. What is the best option? </p> </description> <category>Ticket</category> </item> <item> <author>mlimber@…</author> <pubDate>Tue, 22 Nov 2016 16:45:43 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:11 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:11</guid> <description> <p> On further inspection, I have a proposed patch without doing any of the above. Instead, I change the <code>equal()</code> function of <code>directory_iterator</code> and <code>recursive_directory_iterator</code> to take account of what their shared owners may have done: </p> <table class="wiki"> <tr><td><p> Old </p> </td><td><p> New </p> </td></tr><tr> <td><div class="wiki-code"><div class="code"><pre> <span class="kt">bool</span> <span class="nf">equal</span><span class="p">(</span><span class="k">const</span> <span class="n">directory_iterator</span><span class="o">&amp;</span> <span class="n">rhs</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">m_imp</span> <span class="o">==</span> <span class="n">rhs</span><span class="p">.</span><span class="n">m_imp</span><span class="p">;</span> <span class="p">}</span> </pre></div></div></td><td><div class="wiki-code"><div class="code"><pre> <span class="kt">bool</span> <span class="nf">equal</span><span class="p">(</span><span class="k">const</span> <span class="n">directory_iterator</span><span class="o">&amp;</span> <span class="n">rhs</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span> <span class="k">const</span> <span class="kt">bool</span> <span class="n">is_lhs_valid</span> <span class="o">=</span> <span class="n">m_imp</span> <span class="o">&amp;&amp;</span> <span class="n">m_imp</span><span class="o">-&gt;</span><span class="n">handle</span><span class="p">;</span> <span class="k">const</span> <span class="kt">bool</span> <span class="n">is_rhs_valid</span> <span class="o">=</span> <span class="n">rhs</span><span class="p">.</span><span class="n">m_imp</span> <span class="o">&amp;&amp;</span> <span class="n">rhs</span><span class="p">.</span><span class="n">m_imp</span><span class="o">-&gt;</span><span class="n">handle</span><span class="p">;</span> <span class="k">return</span> <span class="p">(</span><span class="n">is_lhs_valid</span> <span class="o">&amp;&amp;</span> <span class="n">is_rhs_valid</span> <span class="o">&amp;&amp;</span> <span class="n">m_imp</span> <span class="o">==</span> <span class="n">rhs</span><span class="p">.</span><span class="n">m_imp</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="o">!</span><span class="n">is_lhs_valid</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">is_rhs_valid</span><span class="p">);</span> <span class="p">}</span> </pre></div></div></td></tr><tr> <td><div class="wiki-code"><div class="code"><pre> <span class="kt">bool</span> <span class="nf">equal</span><span class="p">(</span><span class="k">const</span> <span class="n">recursive_directory_iterator</span><span class="o">&amp;</span> <span class="n">rhs</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">m_imp</span> <span class="o">==</span> <span class="n">rhs</span><span class="p">.</span><span class="n">m_imp</span><span class="p">;</span> <span class="p">}</span> </pre></div></div></td><td><div class="wiki-code"><div class="code"><pre> <span class="kt">bool</span> <span class="nf">equal</span><span class="p">(</span><span class="k">const</span> <span class="n">recursive_directory_iterator</span><span class="o">&amp;</span> <span class="n">rhs</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span> <span class="k">const</span> <span class="kt">bool</span> <span class="n">is_lhs_valid</span> <span class="o">=</span> <span class="n">m_imp</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">m_imp</span><span class="o">-&gt;</span><span class="n">m_stack</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span> <span class="k">const</span> <span class="kt">bool</span> <span class="n">is_rhs_valid</span> <span class="o">=</span> <span class="n">rhs</span><span class="p">.</span><span class="n">m_imp</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">rhs</span><span class="p">.</span><span class="n">m_imp</span><span class="o">-&gt;</span><span class="n">m_stack</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span> <span class="k">return</span> <span class="p">(</span><span class="n">is_lhs_valid</span> <span class="o">&amp;&amp;</span> <span class="n">is_rhs_valid</span> <span class="o">&amp;&amp;</span> <span class="n">m_imp</span> <span class="o">==</span> <span class="n">rhs</span><span class="p">.</span><span class="n">m_imp</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="o">!</span><span class="n">is_lhs_valid</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">is_rhs_valid</span><span class="p">);</span> <span class="p">}</span> </pre></div></div></td></tr></table> <p> This allows my problematic programs above to detect if the iterator has been invalidated by comparing it against the empty iterator. </p> <p> Should I submit this as an official patch? (If so, what process should I go through to do so? I assume I'll have to add a unit test and validate existing tests as well.) </p> </description> <category>Ticket</category> </item> <item> <author>mlimber@…</author> <pubDate>Tue, 22 Nov 2016 19:53:14 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/12578#comment:12 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/12578#comment:12</guid> <description> <p> I created a pull request with unit test here: github.com/boostorg/filesystem/pull/37 </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Beman Dawes</dc:creator> <pubDate>Wed, 23 Nov 2016 12:58:17 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/12578#comment:13 https://svn.boost.org/trac10/ticket/12578#comment:13 <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> Fixed in develop. Should make it into 1.63. </p> <p> Thanks for the bug report, and thanks to Charles Olivi for helping with discussion of the issue. </p> Ticket