Re: [eigen] Memory Leak When Constructor of User-Defined Type Throws Exception |
[ Thread Index | Date Index | More lists.tuxfamily.org/eigen Archives ]
Good evening, thank you for looking at the patch and sorry for my late reply. I have (finally) applied your suggestions now (see attached). The unit test is now using a static object count as suggested. However, this can only check that the member-wise destruction happens. I don't know of a way to check from within C++ that a 'std::malloc()'ed pointer was 'std::free()'ed again. (Eigen does not use operator 'new' / 'delete' to manage the storage array.) While adding '#ifdef EIGEN_EXCEPTIONS' to the code, I found that there is an existing try-catch in 'Memory.h' that is not surrounded by such guards. I've left it unchanged since it has nothing to do with the reason for this patch. By the way, I find that #ifdef EIGEN_EXCEPTIONS try #endif { do_stuff(); if (bad_things_happened()) #ifdef EIGEN_EXCEPTIONS throw Ball("oh no!"); #else std::abort(); #endif } #ifdef EIGEN_EXCEPTIONS catch (const Ball&) { do_other_stuff(); } #endif is quite hard to read. In GCC source, I found the following macros: #ifdef EIGEN_EXCEPTIONS # define EIGEN_THROW(X) throw X # define EIGEN_TRY try # define EIGEN_CATCH(X) catch (x) #else # define EIGEN_THROW(X) std::abort() # define EIGEN_TRY if (true) # define EIGEN_CATCH(X) else #endif Then, we can write the above code as EIGEN_TRY { do_stuff(); if (bad_things_happened()) EIGEN_THROW(Ball("oh no!")); } EIGEN_CATCH(const Ball&) { do_other_stuff(); } which looks much cleaner to my eyes. Maybe I have misunderstood the current infrastructure but if not: What do you think about this style? Moritz Christoph Hertzberg <chtz@xxxxxxxxxxxxxxxxxxxxxxxx> writes: > On 06.07.2014 07:47, Moritz Klammler wrote: >> unfortunately, the Bugzilla server is not functional (I don't know >> for how long this might be) so I could not check whether this issue >> has already been reported. > > This appears to be related to a software update by > http://tuxfamily.org/ Jitse is currently investigating. > >> Currently, if the constructor of a (user-defined) type throws an >> exception during construction of a dense matrix with dynamic size, >> the memory for the storage of the (not created) matrix is leaked. >> This is not a documented [1] limitation for user-defined types so I >> assume that it is not intended behavior. (The page also does not >> mention that user-defined types must be default-constructible. >> Although, that might go without saying.) > > Yes, we shall make Eigen leak-proof wherever possible. And we could > add a note to [1] that types must be default-constructible. > > I'm confident that modern compilers are smart enough to optimize away > the try-catch block if the Scalar constructor does not throw, however > we need to #ifdef-guard it for platforms which do not support > exceptions. For that we already have: > #ifdef EIGEN_EXCEPTIONS > > >> Attached is a unit test that demonstrates the leak if run through >> Valgrind (or some other debugger) as in > > It would be nice to have a proper unit-test that actually checks if > everything gets deconstucted properly, maybe by keeping track of > allocations in a static variable. (Also, your unit test will > wrongfully assert with a probability of (10/11)^1288 *g* ) > > > Christoph > >> [1] http://eigen.tuxfamily.org/dox/TopicCustomizingEigen.html#user_defined_scalars -- OpenPGP: Public Key: http://openpgp.klammler.eu Fingerprint: 80C1 EC79 B554 3D84 0A35 A728 7057 B288 CE61 2235
# HG changeset patch # User Moritz Klammler <moritz@xxxxxxxxxxx> # Date 1404622693 -7200 # Sun Jul 06 06:58:13 2014 +0200 # Node ID 28d75bc35611b28c5021c0456d33b03b425a7cde # Parent a7a7cf52fb22ecaec44495a784b834c760c53177 Avoid memory leak when constructor of user-defined type throws exception. The added check `ctorleak.cpp` demonstrates how the leak can be reproduced. The test appears to pass but it is leaking the storage of the (not created) matrix. I don't know how to make this test fail in the existing test suite but you can run it through Valgrind (or another debugger) to verify the leak. $ ./check.sh ctorleak && valgrind --leak-check=full ./test/ctorleak This patch fixes this leak by adding some try-catch-delete-rethrow blocks to `Eigen/src/Core/util/Memory.h`. diff --git a/Eigen/src/Core/util/Memory.h b/Eigen/src/Core/util/Memory.h --- a/Eigen/src/Core/util/Memory.h +++ b/Eigen/src/Core/util/Memory.h @@ -333,35 +333,44 @@ template<> inline void* conditional_alig { return std::realloc(ptr, new_size); } /***************************************************************************** *** Construction/destruction of array elements *** *****************************************************************************/ -/** \internal Constructs the elements of an array. - * The \a size parameter tells on how many objects to call the constructor of T. - */ -template<typename T> inline T* construct_elements_of_array(T *ptr, size_t size) -{ - for (size_t i=0; i < size; ++i) ::new (ptr + i) T; - return ptr; -} - /** \internal Destructs the elements of an array. * The \a size parameters tells on how many objects to call the destructor of T. */ template<typename T> inline void destruct_elements_of_array(T *ptr, size_t size) { // always destruct an array starting from the end. if(ptr) while(size) ptr[--size].~T(); } +/** \internal Constructs the elements of an array. + * The \a size parameter tells on how many objects to call the constructor of T. + */ +template<typename T> inline T* construct_elements_of_array(T *ptr, size_t size) +{ + size_t i; + try + { + for (i = 0; i < size; ++i) ::new (ptr + i) T; + return ptr; + } + catch (...) + { + destruct_elements_of_array(ptr, i); + throw; + } +} + /***************************************************************************** *** Implementation of aligned new/delete-like functions *** *****************************************************************************/ template<typename T> EIGEN_ALWAYS_INLINE void check_size_for_overflow(size_t size) { if(size > size_t(-1) / sizeof(T)) @@ -371,24 +380,40 @@ EIGEN_ALWAYS_INLINE void check_size_for_ /** \internal Allocates \a size objects of type T. The returned pointer is guaranteed to have 16 bytes alignment. * On allocation error, the returned pointer is undefined, but a std::bad_alloc is thrown. * The default constructor of T is called. */ template<typename T> inline T* aligned_new(size_t size) { check_size_for_overflow<T>(size); T *result = reinterpret_cast<T*>(aligned_malloc(sizeof(T)*size)); - return construct_elements_of_array(result, size); + try + { + return construct_elements_of_array(result, size); + } + catch (...) + { + aligned_free(result); + throw; + } } template<typename T, bool Align> inline T* conditional_aligned_new(size_t size) { check_size_for_overflow<T>(size); T *result = reinterpret_cast<T*>(conditional_aligned_malloc<Align>(sizeof(T)*size)); - return construct_elements_of_array(result, size); + try + { + return construct_elements_of_array(result, size); + } + catch (...) + { + conditional_aligned_free<Align>(result); + throw; + } } /** \internal Deletes objects constructed with aligned_new * The \a size parameters tells on how many objects to call the destructor of T. */ template<typename T> inline void aligned_delete(T *ptr, size_t size) { destruct_elements_of_array<T>(ptr, size); @@ -407,39 +432,69 @@ template<typename T, bool Align> inline template<typename T, bool Align> inline T* conditional_aligned_realloc_new(T* pts, size_t new_size, size_t old_size) { check_size_for_overflow<T>(new_size); check_size_for_overflow<T>(old_size); if(new_size < old_size) destruct_elements_of_array(pts+new_size, old_size-new_size); T *result = reinterpret_cast<T*>(conditional_aligned_realloc<Align>(reinterpret_cast<void*>(pts), sizeof(T)*new_size, sizeof(T)*old_size)); if(new_size > old_size) - construct_elements_of_array(result+old_size, new_size-old_size); + { + try + { + construct_elements_of_array(result+old_size, new_size-old_size); + } + catch (...) + { + conditional_aligned_free<Align>(result); + throw; + } + } return result; } template<typename T, bool Align> inline T* conditional_aligned_new_auto(size_t size) { check_size_for_overflow<T>(size); T *result = reinterpret_cast<T*>(conditional_aligned_malloc<Align>(sizeof(T)*size)); if(NumTraits<T>::RequireInitialization) - construct_elements_of_array(result, size); + { + try + { + construct_elements_of_array(result, size); + } + catch (...) + { + conditional_aligned_free<Align>(result); + throw; + } + } return result; } template<typename T, bool Align> inline T* conditional_aligned_realloc_new_auto(T* pts, size_t new_size, size_t old_size) { check_size_for_overflow<T>(new_size); check_size_for_overflow<T>(old_size); if(NumTraits<T>::RequireInitialization && (new_size < old_size)) destruct_elements_of_array(pts+new_size, old_size-new_size); T *result = reinterpret_cast<T*>(conditional_aligned_realloc<Align>(reinterpret_cast<void*>(pts), sizeof(T)*new_size, sizeof(T)*old_size)); if(NumTraits<T>::RequireInitialization && (new_size > old_size)) - construct_elements_of_array(result+old_size, new_size-old_size); + { + try + { + construct_elements_of_array(result+old_size, new_size-old_size); + } + catch (...) + { + conditional_aligned_free<Align>(result); + throw; + } + } return result; } template<typename T, bool Align> inline void conditional_aligned_delete_auto(T *ptr, size_t size) { if(NumTraits<T>::RequireInitialization) destruct_elements_of_array<T>(ptr, size); conditional_aligned_free<Align>(ptr); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -246,16 +246,18 @@ ei_add_test(rvalue_types) ei_add_test(dense_storage) ei_add_test(simplicial_cholesky) ei_add_test(conjugate_gradient) ei_add_test(bicgstab) ei_add_test(sparselu) ei_add_test(sparseqr) +ei_add_test(ctorleak) + # ei_add_test(denseLM) if(QT4_FOUND) ei_add_test(qtvector "" "${QT_QTCORE_LIBRARY}") endif(QT4_FOUND) if(UMFPACK_FOUND) ei_add_test(umfpack_support "" "${UMFPACK_ALL_LIBS}") diff --git a/test/ctorleak.cpp b/test/ctorleak.cpp new file mode 100644 --- /dev/null +++ b/test/ctorleak.cpp @@ -0,0 +1,43 @@ +#include "main.h" + +#include <exception> // std::exception + +struct Foo +{ + int dummy; + Foo() { if (!internal::random(0, 10)) throw Foo::Fail(); } + class Fail : public std::exception {}; +}; + +namespace Eigen +{ + template<> + struct NumTraits<Foo> + { + typedef double Real; + typedef double NonInteger; + typedef double Nested; + enum + { + IsComplex = 0, + IsInteger = 1, + ReadCost = -1, + AddCost = -1, + MulCost = -1, + IsSigned = 1, + RequireInitialization = 1 + }; + static inline Real epsilon() { return 1.0; } + static inline Real dummy_epsilon() { return 0.0; } + }; +} + +void test_ctorleak() +{ + try + { + Matrix<Foo, Dynamic, Dynamic> m(14, 92); + eigen_assert(false); // not reached + } + catch (const Foo::Fail&) { /* ignore */ } +} # HG changeset patch # User Moritz Klammler <moritz@xxxxxxxxxxx> # Date 1405718396 -7200 # Fri Jul 18 23:19:56 2014 +0200 # Node ID 2737292b0292f4c08b72531e44805b75a99df62b # Parent 28d75bc35611b28c5021c0456d33b03b425a7cde Applied changes suggested by Christoph Hertzberg to c'tor leak fix. - Enclose exception handling in '#ifdef EIGEN_EXCEPTIONS'. - Use an object counter to demonstrate the bug more readily. diff --git a/Eigen/src/Core/util/Memory.h b/Eigen/src/Core/util/Memory.h --- a/Eigen/src/Core/util/Memory.h +++ b/Eigen/src/Core/util/Memory.h @@ -349,26 +349,30 @@ template<typename T> inline void destruc } /** \internal Constructs the elements of an array. * The \a size parameter tells on how many objects to call the constructor of T. */ template<typename T> inline T* construct_elements_of_array(T *ptr, size_t size) { size_t i; +#ifdef EIGEN_EXCEPTIONS try +#endif { for (i = 0; i < size; ++i) ::new (ptr + i) T; return ptr; } +#ifdef EIGEN_EXCEPTIONS catch (...) { destruct_elements_of_array(ptr, i); throw; } +#endif } /***************************************************************************** *** Implementation of aligned new/delete-like functions *** *****************************************************************************/ template<typename T> EIGEN_ALWAYS_INLINE void check_size_for_overflow(size_t size) @@ -380,40 +384,48 @@ EIGEN_ALWAYS_INLINE void check_size_for_ /** \internal Allocates \a size objects of type T. The returned pointer is guaranteed to have 16 bytes alignment. * On allocation error, the returned pointer is undefined, but a std::bad_alloc is thrown. * The default constructor of T is called. */ template<typename T> inline T* aligned_new(size_t size) { check_size_for_overflow<T>(size); T *result = reinterpret_cast<T*>(aligned_malloc(sizeof(T)*size)); +#ifdef EIGEN_EXCEPTIONS try +#endif { return construct_elements_of_array(result, size); } +#ifdef EIGEN_EXCEPTIONS catch (...) { aligned_free(result); throw; } +#endif } template<typename T, bool Align> inline T* conditional_aligned_new(size_t size) { check_size_for_overflow<T>(size); T *result = reinterpret_cast<T*>(conditional_aligned_malloc<Align>(sizeof(T)*size)); +#ifdef EIGEN_EXCEPTIONS try +#endif { return construct_elements_of_array(result, size); } +#ifdef EIGEN_EXCEPTIONS catch (...) { conditional_aligned_free<Align>(result); throw; } +#endif } /** \internal Deletes objects constructed with aligned_new * The \a size parameters tells on how many objects to call the destructor of T. */ template<typename T> inline void aligned_delete(T *ptr, size_t size) { destruct_elements_of_array<T>(ptr, size); @@ -433,67 +445,79 @@ template<typename T, bool Align> inline { check_size_for_overflow<T>(new_size); check_size_for_overflow<T>(old_size); if(new_size < old_size) destruct_elements_of_array(pts+new_size, old_size-new_size); T *result = reinterpret_cast<T*>(conditional_aligned_realloc<Align>(reinterpret_cast<void*>(pts), sizeof(T)*new_size, sizeof(T)*old_size)); if(new_size > old_size) { +#ifdef EIGEN_EXCEPTIONS try +#endif { construct_elements_of_array(result+old_size, new_size-old_size); } +#ifdef EIGEN_EXCEPTIONS catch (...) { conditional_aligned_free<Align>(result); throw; } +#endif } return result; } template<typename T, bool Align> inline T* conditional_aligned_new_auto(size_t size) { check_size_for_overflow<T>(size); T *result = reinterpret_cast<T*>(conditional_aligned_malloc<Align>(sizeof(T)*size)); if(NumTraits<T>::RequireInitialization) { +#ifdef EIGEN_EXCEPTIONS try +#endif { construct_elements_of_array(result, size); } +#ifdef EIGEN_EXCEPTIONS catch (...) { conditional_aligned_free<Align>(result); throw; } +#endif } return result; } template<typename T, bool Align> inline T* conditional_aligned_realloc_new_auto(T* pts, size_t new_size, size_t old_size) { check_size_for_overflow<T>(new_size); check_size_for_overflow<T>(old_size); if(NumTraits<T>::RequireInitialization && (new_size < old_size)) destruct_elements_of_array(pts+new_size, old_size-new_size); T *result = reinterpret_cast<T*>(conditional_aligned_realloc<Align>(reinterpret_cast<void*>(pts), sizeof(T)*new_size, sizeof(T)*old_size)); if(NumTraits<T>::RequireInitialization && (new_size > old_size)) { +#ifdef EIGEN_EXCEPTIONS try +#endif { construct_elements_of_array(result+old_size, new_size-old_size); } +#ifdef EIGEN_EXCEPTIONS catch (...) { conditional_aligned_free<Align>(result); throw; } +#endif } return result; } template<typename T, bool Align> inline void conditional_aligned_delete_auto(T *ptr, size_t size) { if(NumTraits<T>::RequireInitialization) destruct_elements_of_array<T>(ptr, size); diff --git a/test/ctorleak.cpp b/test/ctorleak.cpp --- a/test/ctorleak.cpp +++ b/test/ctorleak.cpp @@ -1,19 +1,38 @@ #include "main.h" #include <exception> // std::exception struct Foo { + static unsigned object_count; + static unsigned object_limit; int dummy; - Foo() { if (!internal::random(0, 10)) throw Foo::Fail(); } + + Foo() + { +#ifdef EIGEN_EXCEPTIONS + // TODO: Is this the correct way to handle this? + if (Foo::object_count > Foo::object_limit) { throw Foo::Fail(); } +#endif + ++Foo::object_count; + } + + ~Foo() + { + --Foo::object_count; + } + class Fail : public std::exception {}; }; +unsigned Foo::object_count = 0; +unsigned Foo::object_limit = 0; + namespace Eigen { template<> struct NumTraits<Foo> { typedef double Real; typedef double NonInteger; typedef double Nested; @@ -29,15 +48,22 @@ namespace Eigen }; static inline Real epsilon() { return 1.0; } static inline Real dummy_epsilon() { return 0.0; } }; } void test_ctorleak() { + Foo::object_count = 0; + Foo::object_limit = internal::random(0, 14 * 92 - 2); +#ifdef EIGEN_EXCEPTIONS try +#endif { Matrix<Foo, Dynamic, Dynamic> m(14, 92); eigen_assert(false); // not reached } +#ifdef EIGEN_EXCEPTIONS catch (const Foo::Fail&) { /* ignore */ } +#endif + VERIFY_IS_EQUAL(static_cast<unsigned>(0), Foo::object_count); }
Attachment:
pgpPqyUIH59v7.pgp
Description: PGP signature
Mail converted by MHonArc 2.6.19+ | http://listengine.tuxfamily.org/ |