Skip to content

Release 30#1

Closed
vadi2 wants to merge 191 commits intodevelopmentfrom
release_30
Closed

Release 30#1
vadi2 wants to merge 191 commits intodevelopmentfrom
release_30

Conversation

@vadi2
Copy link
Copy Markdown
Owner

@vadi2 vadi2 commented Mar 20, 2017

No description provided.

Chris7 and others added 30 commits December 30, 2014 13:55
Avoid content being overwritten in editor
bug fix for losing old trigger/key/etc content with new item addition
…ember

The specification for this member is:
    QMultiMap<int, QPair<int, int> > exits
and it is a record of the rooms that are on border on the TArea instance.
The outer QMultiMap key is the room id of the in_area room, and the value
pair is: first=out_of_area room id
         second=direction code
The original direction codes were defined at the start of the TArea.cpp
class file but there did not appear to be any logic to the values used:
N=12, NE=1, E=3, SE=4, S=6, SW=7, W=9, NW=10, UP=13, DOWN=14, IN=15,
OUT=16, OTHER=17.  Other than a lua function getAreaExits(areaId) which was
supposed to return just a list of unique keys via a TArea::getAreaExits()
method - i.e. a list of rooms in the given area which were exits to another
area, no other use was made of this data.  I propose to replace this list
of values with the better known list stored in the TRoom class header file
but as that does not currently have a value for "other" {a.k.a. Special}
exits I had added a new define: #DEFINE DIR_OTHER with the value 13.

The data IS stored in the map file but is not changed except by calls to:
* (void)TArea::ausgaengeBestimmen() called from:
    (void)TRoomDB::initAreasForOldMaps()
* (void)TArea::fast_ausgaengeBestimmen(int id) called from:
    (bool)TMap::setExit(int from, int to, int dir)
    (void)TRoom::setArea(int _areaID)
    (void)TRoom::setSpecialExit(int to, const QString & cmd)
    (void)TRoom::removeAllSpecialExitsToRoom(int _id)
    (void)dlgRoomExits::save()
* (void)TArea::removeRoom(int room) called from:
    (void)TRoom::setArea(int _areaID)
    (bool)TRoomDB::__removeRoom(int id)

The bugs are that the code in both "ausgaengeBestimmen" methods (with
and without the "fast_" prefix) used the FROM room Id as the first member
of the value pair rather than the TO (a.k.a. exit) room Id for Normal
exits and use the room ID of the TO (a.k.a. exit) room Id as the key for
Special exits (i.e. stores a room that is NOT in the relevant area as a
room IN the area that is an exit from THAT area!)  Additionally, even if
the code was correct the existing flawed data would not be corrected when
the map is loaded by running TArea::ausgaengeBestimmen() again because that
is only called at the end of TRoomDB::initAreasForOldMaps() which was
called only for old pre-version 15 map file formats (and we are currently
on 16 - though I have plans in pipeline for something that would require an
increment on that).  To further stir the faulty data up previous faulty
code that introduced problems when moving a room from one area to another
may also have contributed to errors with the TArea::exits data.

However:

tl;dr;

running the lua command "auditAreas()" runs TRoomDB::initAreasForOldMaps()
which will fix-up existing maps after this commit has been committed...!

This fixes ["getAreaExits is broken (bug 1380822)"](http://bugs.launchpad.net/mudlet/+bug/1380822) 8-)

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
…rrect

Rename:
(void)TArea::fast_ausgaengeBestimmen(int)
                                     ==> TArea::determineAreaExitsOfRoom
(void)TArea::ausgaengeBestimmen(int) ==> TArea::determineAreaExits
(const)(QList<int>)TArea::getAreaExits() const
                                     ==> TArea::getAreaExitRoomIds()

Add new method to return area exit data in new, wanted format:
(const)(QMultiMap<int, QPair<QString, int> >) getAreaExitRoomData() const

In preparation to revising internal storage representation of area exit
data moved the: (QMultiMap<int, QPair<int, int> >)(TArea *)->exits member
from public to private area of class.  To permit save and load the
following have had to be made friends of the TArea class:
(bool)TMap::serialize( QDataStream & ) and  (bool)TMap::restore( QString )

Revise (void)TMap::init(Host *) to run (TArea *)->determineAreaExits() on
current and all previous map file format versions, will not be needed on
future version as the code to manage the areaExits data is now functional.
Previous code would have done this only for versions prior to 14 files
(current is 16) or if the lua function auditAreas() was manually run.  In
passing also modified code that "fixed-up" "old style" map labels so that
it is no longer run on current version files and pushes any messages that
that creates into the main profile console instead of using standard C++
cout calls which we deprecate now.

All code blocks that have been touched by this series of commits have been
re-formatted to current styles.

Update copyrights on all files touched that have not already been marked as
having been edited by myself.

Revised TLuaInterpreter::getAreaExits(...) to take a second optional
Boolean that if present and true cause it to return data about the area
exit directions and the destination rooms, if false or omitted, returns
only the rooms in the area that have exits out of it, reproducing the
previous implementation.  In either case the result is a table if there
are area exits (or a nil for an isolate area without exits); two additional
values are returned an informative, translatable, text message and an
integer status code that reflects the same information.

When moving a series of rooms to a different area via the 2D mapper's GUI
the recalculations for the area extremes {by TArea::calcSpan()} and the
out of area exits {by TArea::determineAreaExits()} can now be deferred
until the last room has been moved by passing a third true (boolean)
argument to TMap::setRoomArea(...) which defaults to false for other single
room at a time usages.  Though that method keeps a local copy of the areas
that have been modified and thus need updating, should the last room NOT
be processed (null TRooo pointer for room Id) a publicly accessible
"mIsDirty" flag is also used so that recovery code can identify and clean
up those affected areas otherwise.  It is possible that this flag may be
useful in other situations, such as when moving or adding multiple rooms
WITHIN an area.

*** This commit has been rebased so it's history might not be the same as
someone else's copy of it ***

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
…andard

Due to unawareness of standards I have deviated from them in producing
multiple return values upon success as well as failure.  Whilst I feel that
a second return value being a translatable status string and a third being
an integer encoding of the same might have some merit, particularly in a
fully internationalised and localised version of Mudlet in the future; no
decision has been made over precisely how I18n and L10n will be handled so
using them is a little premature and only confuses things for the moment.

The current methodology is:
* For bad argument TYPE situations: to throw a lua_error and provide a
error message using a fairly uniform construction, I provide these via
Qt's translation system to permit that part of the Lua API to be
translated with the C++ parts of the application's framework.

* For bad argument VALUE situations best practice is for a lua function to
provide a primary nil return value but to also provide a secondary value as
a string message describing the (first) argument value error.  A lua error
is NOT thrown but unless the user catches the error message by providing a
second variable to contain the second return value they may not find out
what is wrong.  Again I choose to provide a translatable message for this.

* For good arguments case that are satisfactory but produce no data, an
empty table, an empty string or a signal number value is an appropriate
primary return value. For example for either type of table that the lua
getAreaExits((int) areaId, <(bool) provide Full data> ) function now
returns, an empty table will be returned for an area which is isolated and
has no exits.  A secondary return value is NOT expected in this situation
as far as I can tell, though I believe one could be useful...

* For good arguments values for functions that provide data then the data
is returned as the primary and only return value.

* For good arguments values for functions that do not return data then
success should be indicated by a single true Boolean return value.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Crash on doubleclicking timestamps
Fix license claim on mudlet.svg
Code deficiencies that remove rooms without properly updating connected
rooms were causing segmentation faults because this method - perhaps
foolishly - expected a room to exist when another room had an exit Id to
the room given.  This commit corrects any faulty normal exits now by
resetting them to the no exit -1 value.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Each individual call to TRoom::setExit((int)exitRoomId,(int)directionCode)
from dlgRoomExit::save() creates additional entries in TRoom::entranceMap
even for non-exit directions.  To reduce (but unfortunately not eliminate)
the number of duplicates change the save() code to only use setExit()
when a difference between the current and saved exit room numbers is
found.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Obvious but was missing.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Though suitable for release code this was added to help
with debugging.  This was what enabled me to spot the
problems that the previous pair of commits ameliorates.

This currently only reports the rooms that have exit(s)
that lead to the given room Id.  It is anticipated that
there will be a future revision to report the particular
direction(s) from the given room(s) are the one(s) that
lead to the room, using a second argument that will be
a boolean true to trigger that behavior (the absence of,
or a false, second argument would then cause the result
that this code produces.)

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Set a break point in the method and change the static bool showDebug to
dis-/en-able output...

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
…oading

Previously we were not removing entries from the entranceMap involving
the value (a room that the room Id that was a key had an entrance FROM)
when a route was changed.  There is a performance cost in ensuring the
data is kept correctly - there may be a modest gain by storing the
entrance data within each TRoom class instance rather than a central
database in TRoomDB...

Deletion of multiple rooms and map loading can be done more efficiently
if we skip some redundant steps.

Also added/revised some timing code to measure things.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
* T2DMap::slot_setArea(): used a uint where I should have used an int as a
  method I called can return a -1 in some cases.

* TArea::getAreaExitRoomData(): a qWarning() in a debugging line I had
  used the wrong type (%1,%2,...) of format string argument characters when
  I should of used (%i or %s)...

* (bool)TArea::mIsDirty: was put in wrong block of lines in header

* TLuaInterpreter::resetRoomArea(): put in a spurious test that had already
  been covered by prior code.

* (boo)TRoom::setArea(int, bool): by not assigning a value to the
  TArea::isDirty variable the code was ineffective and might cause a bug
  that it was designed to prevent.

Signed-off-by: Stephen Lyons <stephen@ripley.lyonsnet>
SlySven and others added 18 commits November 17, 2016 13:39
Whilst looking at a different issue I spotted that there were two sets of
pointers in the dlgTriggerEditor class that were intended to track the
"current" item in each of Aliases, Buttons ("Actions"), Keys, Scripts,
Timers, Triggers and Variables.  The more appropriately named
mpCurrentXXXXItem ones were largely unused and the mCurrentXXXX ones were
mainly used.  However in the case of the ones for Variables the mixing of
the use of both of them, may, I thought, have resulted in some oddities in
hiding and un-hiding of variables that I previously remembered happening.

In fact it doesn't but that is another story...

Also:
* Found some systematic duplicate checks in code for Aliases, Keys etc.
  for a QTreeWidgetItem pointer being non-null which I was able to remove.
  I then spotted a similar test in some Variable handling code in:
  (void) dlgTriggerEditor::slot_var_selected(QTreeWidgetItem *)
  but there were several calls to other methods which used the pointer as
  an argument and it was not immediately clear whether they would modify
  the pointer value or it's pointee.  Further investigation enable me to
  add some const qualifiers to those methods which revealed that the
  address stored in the pointer was not going to be changed so that the
  second test in the method was redundent.

* There was some unneeded C-Style casts in this class and in the cases
  where a cast was needed to resolve signature ambiguities for the compiler
  it is better to use a static_cast in C++ code.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
…licatePointersInDlgTriggerEditorClass

(release_30)refactor: eliminate duplicated set of pointers in dlgTriggerEditor class
* (release_30) Fix linked libraries in src.pro for Windows

This corrects the libraries linked in src.pro for Windows so SQL
functions work properly. It also removes reference to external
libraries/includes.
This moves sound from 4 static players into an unlimited QList,
fixing https://bugs.launchpad.net/mudlet/+bug/1645064

It also adds volume (0-100) to the LUA playSoundFile command, with 100
(maximum) substituted if not present for backwards compatability.
This is an amalgumation of a series of commits previously applied to the
development branch:

Cleanup: clear dead code & prevent reentrancy issue in Host::installPackage
---------------------------------------------------------------------------

Host::installPackage(...) can call Host::uninstallPackage(...) which in
turn can call Host::installPackage(...) back.  This reentrancy could cause
issues with a single instance of a QDialog used by the former which was
accessed via a class member pointer.  The previous code structure avoids
this, but if the code failed to maintain the case where this is safe (when
second argument has the value of 2) there would be multiple QDialogs
created but only one could be tracked by a class member pointer.  It is
possible to avoid this issue by using a method local pointer which is
enough in the new code which only uses the pointer in this method.

The previous (void)Host::showUnpackingProgress(QString) was rendered
ineffective with an immediate return statement.  As such it was dead code
and removing it also negated the reason for
 (void) mudlet::showUnzipProgress(const QString&) so that
 (int) TLuaInterpreter::showUnzipProgress(lua_State*) is also dead code.
To avoid any breakage that has been converted into a stub that just reports
via a nil + error message that it is such a stub and can be safely removed
from scripts.

Finally, the QDialog mentioned above was not being removed when
Host::installPackage(...) failed with an error.  This gave the effect of
Mudlet hanging when package/module installation failed.  Note that we are
STILL woefully short of error messages in this process but that needs to
be the subject of further commits.

Refactor: remove more dead/ineffective code from Host and dlgTriggerEditor
---------------------------------------------------------------------------

The (bool)Host::serialize() method was short-circuited with an initial
"return false;" and it was only called in:
(void) dlgTriggerEditor::slot_save_edit() which did not rely on the return
value.  Further examination of the latter method also revealed that all
of the dlgTriggerEditor::slot_saveXXXXAfterEdit() calls it used were just
wrappers around the corresponding dlgTriggerEditor::saveXXX() methods and
there was no other use of the "public slots:" wrappers so they can be
safely eliminated.

BugFix: scan for and create needed directories when installing packages
---------------------------------------------------------------------------
Some file archiving production systems do not seem to be inserting the
(zero length) entries with names ending in a '/' for sub-directories in
archive files.  This commit now makes two passes through any package or
module and identifies all directories that will be needed, it then makes
them before it extracts the files to populate those directories.

This should fix https://bugs.launchpad.net/mudlet/+bug/1413069 as it also
ensures that the proper cleaning process is done should package
installation fail, it removes the "Unpacking" dialog on failure which was
not happening before.

NOTE: There is still an extreme lack of error reporting but as
Host::installPackage(...) is used in several places by different parts of
the application that is probably addressed separately.

Also:
* wrapped strings in Host::installPackage(...) in QStringLiteral(...) or
 tr(...) wrappers and made it more likely to be Utf8 clean.
* Increased the size of the buffer used during extraction of possibly
 compressed archived package files from 100 to 4096 bytes which means
 less loops will be needed to process such archived files.
* Ensured path / file name manipulations/test are not Case Sensitive so
 that they will not misbehave on MacOS platforms.
* Tweaked displaying of the "Unpacking package/module..." dialog so that
 it shows more consistently (it often hid under other windows on my
 GNU/Linux setup!)

Tweak: Try to ensure "Unpacking package/module" message is shown correctly
---------------------------------------------------------------------------
I did not detect the dialog being put up but not being properly painted on
my Linux box but Vadim did produce a screen capture video showing how the
"Unpacking ..." message was being put up on top of the Mudlet application
but the box, although the frame was displayed did not have it's internal
contents filled in.  It was not clear which OS platform he got this on and
I did wonder whether it might have been a Mac which might have different
GUI systems to my Linux one, but this commit ensures that a repaint() is
used before a qApp->processEvents() call which should mean the widget is
properly filled out...

In fact this still doesn't work for some Linux systems but the reasons are
still not clear!

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
…InstallationAndReviseServerGuiHandling

(release_30)bugFix: fix loading of packagage/modules
replaced tabs with spaces
corrected location of comment
(release_30) sound updates - bugfix and enhance
…-updates

Revert "(release_30) sound updates - bugfix and enhance"
* Change version from iota to kappa

New release primarily to have working SQL in 3.0.0 previews again.
Conditional has been fixed so that it hits the same 10 pixel range on
either side when you drag the mouse up and down a console.
…tive (Mudlet#378)

This is likely the cause of bug:
https://bugs.launchpad.net/mudlet/+bug/1417234
which is where a suitable file is having an extra xml extension added,
probably because the MacOS File system and native file dialogues are not
necessarily case sensitive and could produce a file with an extension XML
rather than xml that would fail checks for the latter in the past.

This is regarded as a "ShowStopper" bug for Mudlet 3.0 release...

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
I made an error to the Server Address for the game server for Wheel Of Time MUD - as per
http://forums.mudlet.org/viewtopic.php?f=9&t=16368&p=36495#p36495
I should have used "game.wotmud.org" which resolves to a different address than the old
"wotmud.org" which is still extant but not operative (since 2014 IIRC).

Due to the need to resolve this quickly (and due to the microscopic change needed 8-) )
- I have edited the repository code directly rather than via a Pull Request.
…port/export … (Mudlet#373)

* BugFix: bad parsing of map room/exit sizes + other XML import/export issues

This contains the BugFix and other elements of a Pull Request made onto the
development branch but without the code cleanup/layout changes.

* Fix the parsing of the "mRoomSize" and "mLineSize" ATTRIBUTES to the
  "Host" element of Mudlet XML files. The reader code was treating the
  stored strings as integers when they were floating point decimal numbers,
  whilst this is not an issue for Host::mLineSize which currently DOES
  operate as a whole number without a decimal part it BREAKS "mRoomSize"
  which ranges from 0.1 to 1.1 in 0.1 steps.  An attempt was made to fix
  the fault on 2011-06-09 in commit:
  Mudlet@7b4da59
  but that added two sub-elements to the Host element to store the same
  data rather than correcting the original fault. This commit cannot remove
  those two sub-elements because that would currently break saved XML files
  if they were then used in Mudlet versions without this commit which would
  only have the "attribute" form of these items and mis-read them.

  The above sub-elements ARE however something that can be removed from the
  XMLexport code in the future if a separate pending Pull-Request to
  restore version string checking to the code that parses the
  "MudletPackage" element as then we can explain and justify the quick and
  easy one-off fix for an older version of the XML format (to adjust the
  settings in the 2D Mapper and then save the map in the new format - which
  may be set to happen by default).

* Removed mis-spelled "commandSeperator" sub-element of the "Host" element
  and the associated (QString) Host::mCommandSeperator which was unused
  and duplicated the (QString) Host::mCommandSeperator member...!

* Removed a couple of empty string literals from:
  (bool) XMLimportPackage(QIODevice *, QString, int)
  this raised an interesting point in that where one of those was used it
  was necessary (and valid) to add the const qualifier to:
  TAlias::setScript( QString & )
  so that it became:
  TAlias::setScript( const QString & ) - this is different from the
  development branch code because that passes an actual QString as the
  argument (which should be harmless because QStrings are Copy-on-Write and
  no changes seem to be made to the value being passed).  In this branch of
  code a reference is used instead and the default constructed null QString
  can only be used to initialise the variable concerned if it is kept
  constant, or so it seems...!

* Removed redundant:
  (QString) XMLexport::mType
  XMLexport::XMLexport(Host *, bool)

* Renamed:
  (void) XMLimport::readMapList(QMap<QString,QStringList> &)
   ==> (void) XMLimport::readModulesDetailsMap(QMap<QString,QStringList> &)
  to emphasis it is related to populating a QMap data container rather than
  doing something with a Mudlet Map!

* Added qDebug() code for further methods in XMLimport class when unknown
  elements are encountered - for some reason they were not in all the
  similar ones for different types of Mudlet items  (Triggers, Aliases,
  etc.)

* Added qDebug() code for further methods in XMLimport class when the
  script part of different types of Mudlet items (Triggers, Aliases, etc.)
  are read and inserted into the application data so that if they fail to
  compile it is reported - for some reason they were not in all the similar
  place for different types of Mudlet items.

* Added an argument to the XMLimport::readIntegerList(...) to pass the name
  of the parent trigger for use if/when the error condition of having an
  invalid (not a number) as an element that encodes the type of the trigger
  condition for a particular condition.  This is justified because under
  those conditions qFatal() is used {probably because it is the only QDebug
  class method that WILL be detectable on a WINDOWS platform for a RELEASE
  build} unfortunately it will KILL the Mudlet application - so it behoves
  Mudlet to report what killed it when it does die through this!

* Rename an argument in:
  * (void) XMLimport::readHostPackage(Host *)
  * (bool) XMLexport::writeGenericPackage(Host *)
  * (bool) XMLexport::writeHost(Host *)
  from pT to pHost as the latter name is more prevalent elsewhere in the
  application.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>

* Fixup: correct search+replace error and a further logic issue

When doing a search and replace for "pT" in the XMLexport.cpp file I hit a
few cases where that text was a part of a longer variable name and broke
them!

Whilst fixing that I spotted that a boolean flag in several methods in that
class that was intended to be cleared on error was capable of being
returned to true afterwards (although practically it was perhaps unlikely
to happen) - for predictable behaviour the code has been amended to never
allow the flag to be returned to the set state.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>

* Enhance: apply clang-formatting to XMLimport class

Following inter-developer discussion it was decided to apply the same
clang-format code formatting to the XMLimport class in this branch as
for the development branch - as this will increase the complexity of the
changes I felt there was little need to limit the scope of changes in this
branch to the minimum to provide the functional changes so have increased
the change-set to include some redundant code elimination steps already
done in the other branch commits to reduce the overall differences between
the changed files in the two branches.

I have also added the .clang-format file to the QMake project file so that
it shows up in the Qt Creator IDE - however the file is in the base
directory of the project in this branch's code whereas it is in the ./src/
sub-directory in the development branch at some point one branch should
be revised to use the same location for the file as the other one.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
@vadi2
Copy link
Copy Markdown
Owner Author

vadi2 commented Mar 20, 2017

Testing that Githubs web interface can handle resolving these conflicts... nope, it can't.

@vadi2 vadi2 closed this Mar 20, 2017
@vadi2 vadi2 deleted the release_30 branch April 21, 2017 05:48
vadi2 pushed a commit that referenced this pull request May 31, 2017
vadi2 added a commit that referenced this pull request Jun 15, 2017
vadi2 added a commit that referenced this pull request Apr 6, 2020
* Adds an option for variables to be included in searches, which is disabled by default now. This option is in the search menu on the left side of the search bar, positioned with the Case Sensitivity option.

* Revise: add "with-variables" icons & persistent storage of search options

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>

* Update copyright stamp #1

* Update copyright date #2

* Trim tooltip to UX standard

Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Vadim Peretokin <vperetokin@gmail.com>
vadi2 added a commit that referenced this pull request Dec 15, 2024
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fixes Mudlet crash with IRC open when closing a profile
#### Motivation for adding to Mudlet
Mudlet should never crash
#### Other info (issues closed, discussion etc)
Closes Mudlet#7293. I'll fix other
cases of `Host* mpHost;` after this PR so we have consistency in the
codebase.

Running the provided test case through
[AddressSanitizer](https://wiki.mudlet.org/w/Compiling_Mudlet#Checking_memory_leaks_.26_other_issues_.28sanitizers.29_3)
revealed that mpHost was being used after it was deleted:

```
=================================================================
==17843==ERROR: AddressSanitizer: heap-use-after-free on address 0x623000025e58 at pc 0x5555560abe75 bp 0x7fffffff7460 sp 0x7fffffff7450
READ of size 8 at 0x623000025e58 thread T0
    #0 0x5555560abe74 in QWeakPointer<QObject>::internalData() const /usr/include/x86_64-linux-gnu/qt6/QtCore/qsharedpointer_impl.h:711
    #1 0x5555562c15ad in QPointer<dlgIRC>::data() const /usr/include/x86_64-linux-gnu/qt6/QtCore/qpointer.h:77
    #2 0x5555562bd715 in QPointer<dlgIRC>::operator dlgIRC*() const /usr/include/x86_64-linux-gnu/qt6/QtCore/qpointer.h:85
    #3 0x555556296c13 in dlgIRC::~dlgIRC() /home/vadi/Programs/Mudlet/src/dlgIRC.cpp:109
    #4 0x555556296e1f in dlgIRC::~dlgIRC() /home/vadi/Programs/Mudlet/src/dlgIRC.cpp:112
    #5 0x7ffff6ba04a0 in QObject::event(QEvent*) (/lib/x86_64-linux-gnu/libQt6Core.so.6+0x1a04a0)
...
```
This helped pinpoint the cause of the crash.

---------

Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
vadi2 added a commit that referenced this pull request Oct 18, 2025
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fix

```
Direct leak of 4000 byte(s) in 50 object(s) allocated from:
    #0 0x7f21286b61e7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99
    #1 0x5652d10ed62c in TriggerHighlighter::setTheme(QString const&) (/home/runner/work/Mudlet/Mudlet/src/mudlet+0x202562c)
    #2 0x5652d10ecba7 in TriggerHighlighter::TriggerHighlighter(QTextDocument*) (/home/runner/work/Mudlet/Mudlet/src/mudlet+0x2024ba7)
    #3 0x5652d0998ef2 in SingleLineTextEdit::SingleLineTextEdit(QWidget*) (/home/runner/work/Mudlet/Mudlet/src/mudlet+0x18d0ef2)
    #4 0x5652d05f581a in Ui_trigger_pattern_edit::setupUi(QWidget*) (/home/runner/work/Mudlet/Mudlet/src/mudlet+0x152d81a)
    #5 0x5652d05f2792 in dlgTriggerPatternEdit::dlgTriggerPatternEdit(QWidget*) (/home/runner/work/Mudlet/Mudlet/src/mudlet+0x152a792)
    #6 0x5652d044b2a2 in dlgTriggerEditor::dlgTriggerEditor(Host*) (/home/runner/work/Mudlet/Mudlet/src/mudlet+0x13832a2)
    #7 0x5652d08479a9 in mudlet::addConsoleForNewHost(Host*) (/home/runner/work/Mudlet/Mudlet/src/mudlet+0x177f9a9)
    #8 0x5652d086efcb in mudlet::slot_connectionDialogueFinished(QString const&, bool) (/home/runner/work/Mudlet/Mudlet/src/mudlet+0x17a6fcb)
```
#### Motivation for adding to Mudlet
Better app quality
#### Other info (issues closed, discussion etc)
Discovered when running Mudlet with cmake.
ZookaOnGit pushed a commit that referenced this pull request Nov 27, 2025
…rocessing (Mudlet#8571)

<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fix: heap-use-after-free when cleanup runs during alias/trigger/key
processing
#### Motivation for adding to Mudlet
Fixes crash when running
Mudlet#8559 (comment)
benchmark on Linux.
#### Other info (issues closed, discussion etc)
==617553==ERROR: AddressSanitizer: heap-use-after-free on address
0x51200086e6d0 at pc 0x589b650367f6 bp 0x7ffc44dbc700 sp 0x7ffc44dbc6f8
READ of size 8 at 0x51200086e6d0 thread T0
#0 0x589b650367f5 in Tree<TAlias>::isActive() const
(/home/vadi/Programs/Mudlet/build/src/mudlet+0xe8a7f5) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#1 0x589b65d81408 in TAlias::match(QString const&)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd5408) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#2 0x589b6560c156 in AliasUnit::processDataStream(QString const&)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1460156) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#3 0x589b65c872b4 in Host::send(QString, bool, bool)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1adb2b4) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#4 0x589b65d96517 in TCommandLine::enterCommand(QKeyEvent*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bea517) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#5 0x589b65d93095 in TCommandLine::event(QEvent*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1be7095) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#6 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*,
QEvent*)
/home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31
#7 0x7ac66839b2f0 in QApplication::notify(QObject*, QEvent*)
/home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:2725:39
#8 0x7ac668b83f7f in QCoreApplication::notifyInternal2(QObject*,
QEvent*)
/home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1109:24
#9 0x7ac66840cc0b in QWidgetWindow::event(QEvent*)
/home/qt/work/qt/qtbase/src/widgets/kernel/qwidgetwindow.cpp:285:23
#10 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*,
QEvent*)
/home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31
#11 0x7ac668b83f7f in QCoreApplication::notifyInternal2(QObject*,
QEvent*)
/home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1109:24
#12 0x7ac6677ee8e2 in
QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyEvent*)
/home/qt/work/qt/qtbase/src/gui/kernel/qguiapplication.cpp:2609:46
#13 0x7ac655cf9a04 in
QIBusPlatformInputContext::filterEventFinished(QDBusPendingCallWatcher*)
/home/qt/work/qt/qtbase/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp:523:57
#14 0x7ac668be8b74 in QtPrivate::QSlotObjectBase::call(QObject*, void**)
/home/qt/work/qt/qtbase/src/corelib/kernel/qobjectdefs_impl.h:461:57
#15 0x7ac668be8b74 in void doActivate<false>(QObject*, int, void**)
/home/qt/work/qt/qtbase/src/corelib/kernel/qobject.cpp:4255:30
#16 0x7ac6671e5142 in void QMetaObject::activate<void,
QDBusPendingCallWatcher*>(QObject*, QMetaObject const*, int, void*,
QDBusPendingCallWatcher* const&)
/home/qt/work/qt/qtbase/src/corelib/kernel/qobjectdefs.h:319:17
#17 0x7ac6671e5142 in
QDBusPendingCallWatcher::finished(QDBusPendingCallWatcher*)
/home/qt/work/qt/qtbase_build/src/dbus/DBus_autogen/include/moc_qdbuspendingcall.cpp:137:32
#18 0x7ac668bdd56b in QObject::event(QEvent*)
/home/qt/work/qt/qtbase/src/corelib/kernel/qobject.cpp:1411:31
#19 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*,
QEvent*)
/home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31
#20 0x7ac668b83f7f in QCoreApplication::notifyInternal2(QObject*,
QEvent*)
/home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1109:24
#21 0x7ac668b879e4 in
QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*)
/home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1904:36
#22 0x7ac668e7d416 in postEventSourceDispatch
/home/qt/work/qt/qtbase/src/corelib/kernel/qeventdispatcher_glib.cpp:246:39
#23 0x7ac6667145c4 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x5d5c4)
(BuildId: 1eb6131419edb83b2178b682829a6913cf682d75)
#24 0x7ac666773736 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0xbc736)
(BuildId: 1eb6131419edb83b2178b682829a6913cf682d75)
#25 0x7ac666713a62 in g_main_context_iteration
(/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x5ca62) (BuildId:
1eb6131419edb83b2178b682829a6913cf682d75)
#26 0x7ac668e7caad in
QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>)
/home/qt/work/qt/qtbase/src/corelib/kernel/qeventdispatcher_glib.cpp:399:43
#27 0x7ac668b9002a in
QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>)
/home/qt/work/qt/qtbase/src/corelib/kernel/qeventloop.cpp:186:22
#28 0x7ac668b8ba59 in QCoreApplication::exec()
/home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1452:36
#29 0x589b64ab0675 in main
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x904675) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#30 0x7ac66602a1c9 in __libc_start_call_main
csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#31 0x7ac66602a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#32 0x589b649c1d04 in _start
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x815d04) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)

0x51200086e6d0 is located 16 bytes inside of 296-byte region
[0x51200086e6c0,0x51200086e7e8)
freed by thread T0 here:
#0 0x589b64a9b9f1 in operator delete(void*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x8ef9f1) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#1 0x589b65d80711 in TAlias::~TAlias()
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd4711) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#2 0x589b6560eefc in AliasUnit::doCleanup()
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1462efc) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#3 0x589b65c8e88d in Host::incomingStreamProcessor(QString const&, int)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1ae288d) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#4 0x589b651b73b5 in TMainConsole::runTriggers(int)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x100b3b5) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#5 0x589b64e39c4b in TBuffer::commitLine(char, unsigned long&)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0xc8dc4b) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#6 0x589b64e28d4e in
TBuffer::translateToPlainText(std::__cxx11::basic_string<char,
std::char_traits<char>, std::allocator<char>>&, bool)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0xc7cd4e) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#7 0x589b651b638d in
TMainConsole::printOnDisplay(std::__cxx11::basic_string<char,
std::char_traits<char>, std::allocator<char>>&, bool)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x100a38d) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#8 0x589b64f9ed5b in TLuaInterpreter::feedTriggers(lua_State*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0xdf2d5b) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#9 0x7ac66942ffa0 in luaD_precall
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:320:10
#10 0x7ac66943ad7a in luaV_execute
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/lvm.c:591:17
#11 0x7ac66942e96c in luaD_call
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:378:5
#12 0x7ac66942af70 in luaD_rawrunprotected
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:116:3
#13 0x7ac66942bb94 in luaD_pcall
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:464:12
#14 0x7ac66942bce0 in lua_pcall
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/lapi.c:821:12
#15 0x589b64fd65f1 in TLuaInterpreter::call(QString const&, QString
const&, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xe2a5f1)
(BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#16 0x589b65d84d31 in TAlias::execute()
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd8d31) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#17 0x589b65d84577 in TAlias::match(QString const&)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd8577) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#18 0x589b6560c156 in AliasUnit::processDataStream(QString const&)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1460156) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#19 0x589b65c872b4 in Host::send(QString, bool, bool)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1adb2b4) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#20 0x589b65d96517 in TCommandLine::enterCommand(QKeyEvent*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bea517) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#21 0x589b65d93095 in TCommandLine::event(QEvent*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1be7095) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#22 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*,
QEvent*)
/home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31

previously allocated by thread T0 here:
#0 0x589b64a9b171 in operator new(unsigned long)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x8ef171) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#1 0x589b6500f595 in TLuaInterpreter::startTempAlias(QString const&,
QString const&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xe63595)
(BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#2 0x589b6512c41d in TLuaInterpreter::tempAlias(lua_State*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0xf8041d) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#3 0x7ac66942ffa0 in luaD_precall
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:320:10
#4 0x7ac66943ad7a in luaV_execute
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/lvm.c:591:17
#5 0x7ac66942e96c in luaD_call
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:378:5
#6 0x7ac66942af70 in luaD_rawrunprotected
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:116:3
#7 0x7ac66942bb94 in luaD_pcall
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:464:12
#8 0x7ac66942bce0 in lua_pcall
/build/lua5.1-rMDsVj/lua5.1-5.1.5/src/lapi.c:821:12
#9 0x589b64fd65f1 in TLuaInterpreter::call(QString const&, QString
const&, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xe2a5f1)
(BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#10 0x589b65d84d31 in TAlias::execute()
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd8d31) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#11 0x589b65d84577 in TAlias::match(QString const&)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd8577) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#12 0x589b6560c156 in AliasUnit::processDataStream(QString const&)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1460156) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#13 0x589b65c872b4 in Host::send(QString, bool, bool)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1adb2b4) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#14 0x589b65d96517 in TCommandLine::enterCommand(QKeyEvent*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bea517) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#15 0x589b65d93095 in TCommandLine::event(QEvent*)
(/home/vadi/Programs/Mudlet/build/src/mudlet+0x1be7095) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4)
#16 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*,
QEvent*)
/home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31

SUMMARY: AddressSanitizer: heap-use-after-free
(/home/vadi/Programs/Mudlet/build/src/mudlet+0xe8a7f5) (BuildId:
c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) in Tree<TAlias>::isActive()
const
Shadow bytes around the buggy address:
  0x51200086e400: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x51200086e480: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa
  0x51200086e500: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x51200086e580: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x51200086e600: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa
=>0x51200086e680: fa fa fa fa fa fa fa fa fd fd[fd]fd fd fd fd fd
  0x51200086e700: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x51200086e780: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa
  0x51200086e800: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x51200086e880: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x51200086e900: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==617553==ABORTING

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
ZookaOnGit pushed a commit that referenced this pull request Nov 27, 2025
…gers (Mudlet#8567)

#### Brief overview of PR changes/additions

Fixed a heap-use-after-free crash in TBuffer destructor by converting
the raw `mTagWatchdog` pointer to `std::unique_ptr<QTimer>` and
implementing proper copy constructor and copy assignment operator.

#### Motivation for adding to Mudlet

The crash occurred when closing a profile (e.g., using "Close Profile"
menu item on Medievia MUD) that had been actively receiving data and
running triggers. The issue was caused by TBuffer objects being copied
without properly handling the ownership of the `mTagWatchdog` QTimer.
When temporary TBuffer objects were created during copy operations (like
in `TConsole::copy()`), both the original and copied instances shared
the same QTimer pointer. When one instance was destroyed, it deleted the
timer, leaving the other with a dangling pointer that caused a crash on
subsequent destruction.

AddressSanitizer trace showed:
```
ERROR: AddressSanitizer: heap-use-after-free on address 0x602000915670
READ of size 8 at 0x602000915670 thread T0
#0 in TBuffer::TBuffer()+0x88
#1 in TBuffer::TBuffer()+0x18
#2 in TMainConsole::~TMainConsole()+0x5a8

```

The fix ensures each TBuffer instance owns its own QTimer through a
unique_ptr, preventing double-deletion and use-after-free errors.

#### Other info (issues closed, discussion etc)

This resolves crashes that occurred during profile shutdown when the
copy() Lua function was used in triggers processing incoming game data.
vadi2 added a commit that referenced this pull request Jan 24, 2026
Mudlet#8694) (Mudlet#8751)

## Summary

Fixes Mudlet#8694 - `table.update` would error when the first table had a
non-table value (number, string, boolean) at a key where the second
table had a table value.

**Example that crashed:**
```lua
table.update({ x = 1 }, { x = { y = 2 } })
-- Error: bad argument #1 to 'pairs' (table expected, got number)
```

**Root cause:** The recursion logic `tbl[k] = table.update(tbl[k] or {},
v)` only checked for nil/false before recursing, not for other non-table
types.

**Fix:** Check `type(existing) == "table"` before passing to recursive
call.

## Test plan

- [x] Added 5 new test cases in `TableUtils_spec.lua`:
  - Replace number with table
  - Replace table with number  
  - Merge nested tables (both tables)
  - Replace string with table
  - Replace boolean with table

- [x] Verified fix works: `luajit -e "dofile('...TableUtils.lua');
print(table.update({x=1}, {x={y=2}}).x.y)"` outputs `2`

## Notes

Tests require running inside Mudlet with `runTests` command (uses Busted
framework loaded by Mudlet).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Vadim Peretokin <vperetokin@hey.com>
vadi2 added a commit that referenced this pull request Apr 8, 2026
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### 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
```

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
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.

9 participants