Boost C++ Libraries: Ticket #6844: [function] Memory leaks if used with allocator and throwing copy-c'tor of functor https://svn.boost.org/trac10/ticket/6844 <p> There are two places where the implementation of boost::function uses a user-provided allocator: </p> <ol><li>boost/function/function_base.hpp, line 485 </li><li>boost/function/function_template.hpp, line 591 </li></ol><p> Both use cases do not protected against a possibly throwing copy constructor of a function-object because there is an unprotected two-step sequence of the following style: </p> <pre class="wiki">wrapper_allocator_pointer_type copy = wrapper_allocator.allocate(1); wrapper_allocator.construct(copy, functor_wrapper_type(f,a)); </pre><p> If the second step fails, no clean-up of the allocated memory takes place. The following test program emulates this situation and demonstrates the problem: </p> <pre class="wiki">#include "boost/function.hpp" #include &lt;memory&gt; #include &lt;iostream&gt; struct F { static bool dothrow; unsigned char prevent_short_object_opt[sizeof(boost::detail::function::function_buffer) + 1]; F(){} F(const F&amp;) { if (dothrow) throw 0; } void operator()() {} }; bool F::dothrow = false; int alloc_cnt = 0; template&lt;class T&gt; struct my_alloc : std::allocator&lt;T&gt; { template&lt;class Other&gt; struct rebind { typedef my_alloc&lt;T&gt; other; }; void construct(typename std::allocator&lt;T&gt;::pointer p, const T&amp; val) { F::dothrow = true; ::new((void *)p) T(val); } void deallocate(typename std::allocator&lt;T&gt;::pointer p, typename std::allocator&lt;T&gt;::size_type n) { --alloc_cnt; std::cout &lt;&lt; "deallocate: " &lt;&lt; alloc_cnt &lt;&lt; std::endl; return std::allocator&lt;T&gt;::deallocate(p, n); } typename std::allocator&lt;T&gt;::pointer allocate(typename std::allocator&lt;T&gt;::size_type n) { ++alloc_cnt; std::cout &lt;&lt; "allocate: " &lt;&lt; alloc_cnt &lt;&lt; std::endl; return std::allocator&lt;T&gt;::allocate(n); } }; int main() { F f; my_alloc&lt;F&gt; a; try { boost::function&lt;void()&gt; fu(f, a); } catch (int) { std::cout &lt;&lt; "Caught expected - allocation count: " &lt;&lt; alloc_cnt &lt;&lt; std::endl; } } </pre><p> The program outputs </p> <pre class="wiki">allocate: 1 Caught expected - allocation count: 1 </pre><p> on all systems I tested (Visual Studio 11 beta and mingw with gcc 4.8) showing that the deallocation function is never called. </p> <p> The two-step process of allocation+construct needs to be made exception-safe. In my own type-erased allocators I'm using a helper type </p> <pre class="wiki">template&lt;class Alloc&gt; struct allocated_ptr { typedef typename Alloc::pointer pointer; explicit allocated_ptr(Alloc&amp; alloc) : alloc(alloc), ptr(alloc.allocate(1)) {} ~allocated_ptr() { if (ptr != pointer()) { alloc.deallocate(ptr, 1); } } pointer get() const { return ptr; } pointer release() { pointer result = ptr; ptr = pointer(); return result; } private: Alloc&amp; alloc; pointer ptr; }; </pre><p> to handle this, invoking the release function, after successful construction. Of-course other means are possible. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/6844 Trac 1.4.3 Daniel Krügler <daniel.kruegler@…> Sun, 29 Apr 2012 11:29:25 GMT <link>https://svn.boost.org/trac10/ticket/6844#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/6844#comment:1</guid> <description> <p> I should mention that the original test contained a hack to simplify the allocator's emulation. To demonstrate that the test is not based on false usage of the allocators API here follows a corrected version, which does not behave differently in regard to the relevant point: </p> <pre class="wiki">#include "boost/function.hpp" #include &lt;memory&gt; #include &lt;iostream&gt; struct F { static bool dothrow; unsigned char prevent_short_object_opt[sizeof(boost::detail::function::function_buffer) + 1]; F(){} F(const F&amp;) { if (dothrow) throw 0; } void operator()() {} }; bool F::dothrow = false; int alloc_cnt = 0; template&lt;class T&gt; struct my_alloc : std::allocator&lt;T&gt; { my_alloc() : std::allocator&lt;T&gt;() {} template&lt;class U&gt; my_alloc(const my_alloc&lt;U&gt;&amp; other) : std::allocator&lt;T&gt;(other) {} template&lt;class Other&gt; struct rebind { typedef my_alloc&lt;Other&gt; other; }; void construct(typename std::allocator&lt;T&gt;::pointer p, const T&amp; val) { F::dothrow = true; ::new((void *)p) T(val); } void deallocate(typename std::allocator&lt;T&gt;::pointer p, typename std::allocator&lt;T&gt;::size_type n) { --alloc_cnt; std::cout &lt;&lt; "deallocate: " &lt;&lt; alloc_cnt &lt;&lt; std::endl; return std::allocator&lt;T&gt;::deallocate(p, n); } typename std::allocator&lt;T&gt;::pointer allocate(typename std::allocator&lt;T&gt;::size_type n) { ++alloc_cnt; std::cout &lt;&lt; "allocate: " &lt;&lt; alloc_cnt &lt;&lt; std::endl; return std::allocator&lt;T&gt;::allocate(n); } }; int main() { F f; my_alloc&lt;F&gt; a; try { boost::function&lt;void()&gt; fu(f, a); } catch (int) { std::cout &lt;&lt; "Caught expected - allocation count: " &lt;&lt; alloc_cnt &lt;&lt; std::endl; } } </pre> </description> <category>Ticket</category> </item> </channel> </rss>