-
-
Notifications
You must be signed in to change notification settings - Fork 39
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
Memory leaks caught by ASAN #243
Comments
Thanks for sharing this, I'll have a detailed view into it in next couple of days and before releasing patch 0.5.1. One user already pointed out a crash caused by our manual deletes (#242) so that is definitely something that we need to take more care of. |
Sorry, initially I just quickly read through your post and totally missed that you were already looking into the #242 issue. I'm removing now those deletes with parents, afterwards I'll have a look into the leaks. |
I have a branch locally for fixing the leak (which actually worked, no leaks blamed on this program in asan, and no use-after-free caught, hopefully none that exist but weren't caught). But it started out as a mix of adding manual deletes and refactoring to add parent-child relationships, and I ended up finding and fixing more leaks not caught upon merely opening and closing the program, and adding more and more parent-child relationships, which takes more time to refactor than merely adding a So my branch is kinda unfinished and not in shape to send a PR (and I don't know whether to submit the minimal patch, or put in more effort and perform a comprehensive code review complete with a planning document to parent-child everything I can find). But I've learned the above things about the codebase while trying to write my branch. Unfortunately C++ memory management is not compositional, raw pointers don't express responsibility for who has to free them, or how long they're valid for, and there are multiple valid ways (new/delete, new/QObject, unique_ptr RAII) to manage memory self-consistently, and mixing these ways results in a non-self-consistent system causing memory leaks or use-after-free. So I don't know if my branch is actually self-consistent or not, since I didn't document all usages of every class I touched to ensure it's not being misused. ASAN doesn't report any errors, but that just means no errors have been triggered so far. |
Thanks for looking into this. You can provide a PR and I'll have a look and eventually pick up from there. |
I've pushed my unfinished code to https://github.com/nyanpasu64/kImageAnnotator/tree/fix-memory-leaks. Note that it may not necessarily compile or be logically correct, since it's in the middle of a refactor that I never finished (and don't plan to work on right now). And also the formatting is broken since I replaced tabs with spaces in one file. Nonetheless, some of the changes, such as nyanpasu64@91c2c25#diff-d560f5780ec25e3f6dc40faf966b4742194221f3f93e3cbee017bdfa381364f1, should be correct. |
I have fixed the leaks that have been found by Valgrind. I haven't fixed all deletes and missing parent relations, I'll do that when I come across those places. Thanks for pointing me to the issue and providing a detailed explanation, it gave me a better understanding :) |
While investigating #242, I tried running kImageAnnotator's example program in Address Sanitizer (I took the quick-and-dirty approach of passing
-DCMAKE_CXX_FLAGS=-fsanitize=address -DCMAKE_LINKER_FLAGS=-fsanitize=address
into CMake). After fixing the initial destruction crash by removing FontPicker's destructor entirely (along with all delete statements), Address Sanitizer reported some memory leaks in the program.The largest leak (5 kilobytes) was in
ToolPicker::initGui()
, where you callnew QMenu()
6 times, then pass the result intocreateButton(menu)
. This in turn invokesCustomToolButton::setMenu(menu)
, which delegates toQToolButton::setMenu(menu)
. However, reading the QToolButton docs, it turns out thatQToolButton::setMenu()
does not take ownership of the pointer you pass in. So you have to delete the QMenu by hand… hopefully in a way that avoids a use-after-free though.I've observed that Qt methods are highly variable in whether they take ownership or not, and you have to read the docs on a case-by-case basis. Sometimes I remained unclear on whether a method took ownership, or whether deleting variables in the wrong order could cause a use-after-free (it usually doesn't, since Qt disconnects references from other objects to a deleted object), so I had to read the Qt source code, which is (to its credit) more readable than the usual C++ standard libraries.
There's lots of small leaks that ASAN catches, but most of them have ASAN stack traces that terminate in Qt's internal functions or KDE's theming/libraries (which weren't built with ASAN), so I don't know what causes them. One small leak is that
ModifyCanvasWidget::mSelectionHandler
doesn't seem to be freed by either~ModifyCanvasView
or~ModifyCanvasWidget
. Adding adelete mSelectionHandler;
to~ModifyCanvasWidget()
cleans it up nicely (hopefully it doesn't double-free or use-after-free). Another one isAnnotationTabWidget::mTabCloser
which I didn't dig deep enough to find out what to delete to resolve the bug without introducing UAF or double-frees.All in all, these memory leaks don't actually matter all that much, since they're on the order of kilobytes or <1 KB, and never grow even if a user opens Spectacle, repeatedly takes screenshots, and repeatedly opens and closes the annotator tool. And additionally, trying to fix memory leaks runs the risk of adding double-frees or UAF bugs (I don't know how likely). I just don't know whether there was a conscious decision made to leak these variables, or if you just didn't track ownership carefully (and will choose to either fix these leaks or not).
Personally I wouldn't write manual
delete
calls in most application code, but instead rely on either Qt's ownership and child-deletion system, orstd::unique_ptr
to replace raw pointers not owned and deleted by a QObject.The text was updated successfully, but these errors were encountered: