Skip to content

Fix: memory leaks in sound player, dialogs#9142

Merged
vadi2 merged 8 commits intoMudlet:developmentfrom
vadi2:fix/leak-cpp-qt-resources
Apr 8, 2026
Merged

Fix: memory leaks in sound player, dialogs#9142
vadi2 merged 8 commits intoMudlet:developmentfrom
vadi2:fix/leak-cpp-qt-resources

Conversation

@vadi2
Copy link
Copy Markdown
Member

@vadi2 vadi2 commented Apr 4, 2026

Brief overview of PR changes/additions

Fixes memory leaks from playing sounds, opening the room exits dialog, opening Preferences, right-clicking the console, connecting to MUDs with compression, and viewing changelogs

Motivation for adding to Mudlet

Better memory management.

Other info (issues closed, discussion etc)

3. TMediaPlayer destructor leaks TMediaPlaylist + QAudioOutput
playSoundFile() [TLuaInterpreterMedia.cpp:703]
  → TMedia::playMedia() [TMedia.cpp:47] → play() [TMedia.cpp:1485]
    → getMediaPlayer() [TMedia.cpp:1150]
      → make_shared<TMediaPlayer>(mpHost, mediaData) [TMedia.cpp:1193]
        → constructor: mPlaylist(new TMediaPlaylist)       ← LEAK #1 [TMedia.h:47]
        → constructor: setAudioOutput(new QAudioOutput())  ← LEAK #2 [TMedia.h:50]
    → updateMediaPlayerList() [TMedia.cpp:1102]
      → purgeStoppedMediaPlayers() [TMedia.cpp:1056] (when list > 25)
        → shared_ptr destroyed → ~TMediaPlayer() = default  ← NEVER DELETES [TMedia.h:52]
6. dlgRoomExits TExit objects never freed
Right-click room → "Set exits..." [RoomContextMenuHandler.cpp:196]
  → T2DMap::slot_setExits() [T2DMap.cpp:4271]
    → new dlgRoomExits(...) [T2DMap.cpp:4278] + setAttribute(WA_DeleteOnClose) [T2DMap.cpp:4280]
      → init() [dlgRoomExits.cpp:279]
        → initExit() x12 [dlgRoomExits.cpp:1633-1775]
          → originalExits[dir] = makeExitFromControls(dir) [dlgRoomExits.cpp:1599]
            → new TExit() [dlgRoomExits.cpp:1968]  ← ALLOCATED
        → new TExit() per special exit [dlgRoomExits.cpp:1782] → originalSpecialExits[dir] [line 1848]
  User closes → ~dlgRoomExits() [dlgRoomExits.cpp:285] ← EMPTY, never deletes TExit objects
7. dlgProfilePreferences QKeySequence* leak (no destructor)
mudlet::showOptionsDialog() [mudlet.cpp:3578]
  → new dlgProfilePreferences(this, pHost) [mudlet.cpp:3585]
  → setAttribute(WA_DeleteOnClose) [mudlet.cpp:3603]
    → constructor loop [dlgProfilePreferences.cpp:1394-1399]:
        new QKeySequence(*pHost->profileShortcuts.value(key)) [line 1398]
        currentShortcuts.insert(key, sequence) [line 1399]  ← 17 raw pointers stored
  User closes → WA_DeleteOnClose triggers delete
    → implicit ~dlgProfilePreferences() ← NO DESTRUCTOR EXISTS
      → QMap destroyed, pointer values NOT deleted
10. ctelnet missing inflateEnd in destructor
Server sends WILL COMPRESS2 → processSocketData [ctelnet.cpp:4876-4880]
  → mNeedDecompression = true → initStreamDecompressor()
    → inflateInit(&mZstream) [ctelnet.cpp:4514]  ← allocates ~256KB

Connection drops → slot_socketDisconnected() [ctelnet.cpp:627]
  → mNeedDecompression = false [line 663]  ← NO inflateEnd()
  → reset() [line 664] ← NO inflateEnd()
  → ~cTelnet() [line 158] ← NO inflateEnd()
  (only inflateEnd is in decompressBuffer:4532, on Z_STREAM_END from server)
11. ctelnet QNetworkReply leak on file error paths
slot_replyFinished() [ctelnet.cpp:1365]
  → reply->error() == NoError [line 1398]
    → file.open() fails [line 1400] → return ← LEAK (no reply->deleteLater())
    → file.commit() fails [line 1409] → return ← LEAK (no reply->deleteLater())
  (compare: error path at line 1393 correctly calls reply->deleteLater())
15. TTextEdit QAction accumulation (5-10 per right-click)
Right-click on console → mouseReleaseEvent [TTextEdit.cpp:2287]
  → new QAction(tr("Copy"), this) [line 2374]      ← parented to TTextEdit, not QMenu
  → new QAction(tr("Copy HTML"), this) [line 2385]
  → new QAction(tr("Copy as image"), this) [line 2389]
  → new QAction(tr("Select all"), this) [line 2392]
  → new QAction(tr("Search on %1"), this) [line 2397]
  → (+ conditional actions at 2416, 2434, 2438, 2448, 2465)
  → popup = new QMenu(this) + WA_DeleteOnClose [line 2406-2407]
  → Menu closes → QMenu deleted, but QActions survive as TTextEdit children ← ACCUMULATE
17. Updater changelog dialogs leaked
Help → Show changelog → Updater::showChangelog() [updater.cpp:202]
  → new dblsqd::UpdateDialog(feed, ...) ← no parent [line 202]
  → changelogDialog->show() [line 204] ← non-blocking, pointer lost at scope end
  (no WA_DeleteOnClose set; same at showFullChangelog:218)
20. ModernGLWidget mTexCoordBuffer.destroy() missing
initializeGL() → setupBuffers() [modern_glwidget.cpp:146]
  → mTexCoordBuffer.create() [line 183]
~ModernGLWidget() → cleanup() [line 70]
  → mVertexBuffer.destroy()  [line 78] ✓
  → mColorBuffer.destroy()   [line 79] ✓
  → mNormalBuffer.destroy()  [line 80] ✓
  → mIndexBuffer.destroy()   [line 81] ✓
  → mInstanceBuffer.destroy() [line 82] ✓
  → mTexCoordBuffer.destroy() ← MISSING between lines 82 and 83
  → mVAO.destroy()           [line 83] ✓
21. dlgComposer context menu + Hunspell suggestions
Right-click in composer → slot_contextMenu() [dlgComposer.cpp:226]
  → createStandardContextMenu() [line 228] ← returns new QMenu*, no parent
  → fillSpellCheckList() [line 233] → Hunspell_suggest() [line 331,335] ← allocates lists
  → popup->popup() [line 237] ← non-blocking, pointer lost
  ← QMenu leaked (no WA_DeleteOnClose, no deleteLater)
  ← If dismissed without selection: Hunspell_free_list() never called [only at line 468,472]
22-24. Lower-impact (also in this branch)
dlgColorTrigger after exec() [dlgTriggerEditor.cpp:12469,12532]:
  → new dlgColorTrigger(this, ...) → exec() → no delete pD

Hunspell shared dictionary [mudlet.cpp:4768]:
  → mpHunspell_sharedDictionary = nullptr ← missing Hunspell_destroy() before

ExitsTreeWidget takeTopLevelItem [exitstreewidget.cpp:45]:
  → takeTopLevelItem(indexOfTopLevelItem(pItem)) ← return value discarded, never deleted

vadi2 added 7 commits April 4, 2026 09:23
Use std::unique_ptr for TMediaPlaylist ownership. Delete QAudioOutput
in destructor since setAudioOutput() doesn't take ownership in Qt6.
QKeySequence is a lightweight, implicitly-shared value class that
doesn't need heap allocation. The pointer-based QMap entries were
never freed, leaking on every dialog close.
QActions were parented to TTextEdit (this) but the QMenu used
WA_DeleteOnClose, so the QActions leaked on every right-click.
Parent them to the QMenu so they're deleted with it.
- Call inflateEnd() on disconnect and destruction when MCCP compression
  is active (~256KB leaked per connect/disconnect cycle)
- Add reply->deleteLater() to early-return error paths in
  slot_replyFinished() that were skipping cleanup
@vadi2 vadi2 requested a review from a team as a code owner April 4, 2026 07:32
@add-deployment-links
Copy link
Copy Markdown

add-deployment-links bot commented Apr 4, 2026

Hey there! Thanks for helping Mudlet improve. 🌟

Test versions

You can directly test the changes here:

No need to install anything - just unzip and run.
Let us know if it works well, and if it doesn't, please give details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

Warnings
⚠️ PR makes changes to 13 source files. Double check the scope hasn't gotten out of hand

Generated by 🚫 dangerJS against 63a5750

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 4, 2026

Reviews (1): Last reviewed commit: "Apply clang-format to changed files" | Re-trigger Greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@mpconley
Copy link
Copy Markdown
Contributor

mpconley commented Apr 4, 2026

Verified sound/music works.

@ZookaOnGit ZookaOnGit added this to the 4.21.0 next release milestone Apr 7, 2026
Copy link
Copy Markdown
Contributor

@ZookaOnGit ZookaOnGit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

everything looks as per normal

@vadi2 vadi2 merged commit 05a4937 into Mudlet:development Apr 8, 2026
12 checks passed
@vadi2 vadi2 deleted the fix/leak-cpp-qt-resources branch April 8, 2026 05:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants