-
Notifications
You must be signed in to change notification settings - Fork 164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Memory leak in image class for empty dimensions #649
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #649 +/- ##
===========================================
- Coverage 80.69% 80.67% -0.03%
===========================================
Files 116 116
Lines 5072 5076 +4
===========================================
+ Hits 4093 4095 +2
- Misses 979 981 +2 |
These might be silly questions, but I will ask anyway:
|
To observe the memory leak in the current develop branch, run either this minimal example, or the example in the linked issue, with enabled address sanitizer: #include <boost/gil.hpp>
namespace gil = boost::gil;
int main()
{
gil::rgb32_image_t image(0, 0);
return 0;
} In Linux, just add Running the same examples against my branch from this PR do not show the leak anymore. The second question about regression testing is a valid concern. I am not very familiar with the boost unit testing framework, but my naive suggestion is to add a dedicated Asan-enabled job to our CI matrix and run the complete test suite with Asan enabled. It would be a good idea to check how / if other boost libraries also include Asan in their CI, does anyone know? |
Thanks for reporting this corner case. The memory leak can we reproduced with #include <memory>
int main() {
std::allocator<char> alloc;
alloc.allocate(0);
} Zero size is a valid size and diff --git a/include/boost/gil/image.hpp b/include/boost/gil/image.hpp
index 23763d412..d4ffb7725 100644
--- a/include/boost/gil/image.hpp
+++ b/include/boost/gil/image.hpp
@@ -392,7 +392,7 @@ private:
void deallocate()
{
- if (_memory && _allocated_bytes > 0)
+ if (_memory)
_alloc.deallocate(_memory, _allocated_bytes);
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marco-langer Would it be possible to change this to @sdebionne 's suggestion?
You are right, there are two possible solutions to avoid this bug: either not allocating at all zero bytes (as done in my PR), or allocating zero bytes and freeing this data pointer in the destructor. In this PR I decided to bail out early in the constructor in order to not allocate zero bytes. The non-nullptr returned by the allocator shall not be dereferenced according to the standard. Quoting the behaviour of malloc, which might - or might not (implementation defined) - be used internally by
What is the point about carrying such a "toxic" pointer from the point of construction until the point of destruction and risking the chance of invoking undefined behaviour while the image instance exists? Wouldn't it be better to keep the data pointer as nullptr during object lifetime? But of cause I can change my PR accordingly if you still think it is a good idea. Edit: Here on stack overflow is the behaviour of |
A very good question. I've taken this from Scott Meyers' "Effective C++"
Ensuring a non-null pointer invariant makes things simpler w.r.t. expected state of object. Could you update this PR with the latest |
I usually learn a lot by reading Scott Meyers, but I think this specific quote is not worded correctly: The pointer returned from allocating zero bytes is not legitimate as it shall not be dereferenced according to the standard (please correct me if I am wrong). Thereby the invariant is broken anyway, because the invariant of a non-null pointer is that one can safely dereference it. I would argue that the risk of accidentally dereferencing a null pointer is less than the risk of derefencing a non-null-but-not-dereferencable pointer. The former can easily be checked via |
06eefe5
to
9d330a7
Compare
I agree. If @sdebionne does not object with any insights that we have been missing, then I think we should take your PR as is. |
The codecov failure is interesting: adding a new code path (early |
Yes, I've been just wondering what's happening there... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since time for Boost 1.80 is pressing on, I'm merging this one as is.
If we keep observing this fix triggers codecov (and alike) annoyances, then we will resort to @sdebionne 's way of fixing it and we will agree on the discused trade-offs of non-dereferencable non-nullptr.
Thanks @marco-langer !
* develop: test: Add more basic cases for image class (#423) test: Add virtual_2d_locator fixture; is_2d_traversable test case test: Check more properties of indexed_image_view from extension/toolbox test: Add basic is_1d_traversable cases for image_view chore: Update CMakeSettings.json sample [ci skip] chore: Update CMake to use latest cmake-conan/0.18.1 [ci skip] Add pmr image typedefs (#529) test: Add test cases for image with empty dimensions (#702) test: Case test_constructor_from_view was not called fix: Memory leak in image class for empty dimensions (#649) docs: Bump C++11 to C++14 as current required (#700) ci: Remove C++11 build jobs after C++14 switch (#698) build: Fix CMake source file extensions must be explicit refactor: Switch to trailing return types (#599) build: Bump Boost required by CMake from 1.72 to 1.80 build: Update CMAKE_CXX_STANDARD from 11 to 14
Description
Fixes #561
Invoking
image::allocate_a
with an empty dimension (i.e. zero bytes to allocate) causes a memory leak on implementations which return a non-nullptr for zero allocated bytes. In this caseimage::deallocate
will not invoke the deallocation method on the allocator.Examples, for which
image::allocate_a
tries to allocate zero bytes:In the above mentioned issue the leak happens during the call of
read_image
overloaded for any_image: An image is copy-constructed from a default-constructed image. The other overloads ofread_image
do not leak memory.The issue is not related to the jpeg io extension, it occurs as well with the png extension in combination with any_image.
References
Tasklist