Skip to content

misc cleanup and bug fixes#541

Merged
nschimme merged 19 commits into
MUME:masterfrom
nschimme:jahara-wip-2026-07-15a
Jun 15, 2026
Merged

misc cleanup and bug fixes#541
nschimme merged 19 commits into
MUME:masterfrom
nschimme:jahara-wip-2026-07-15a

Conversation

@nschimme

@nschimme nschimme commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Refine object ownership and construction patterns, harden error handling and safety checks, and clean up unused or redundant logic across the UI, map storage, networking proxy, parsing, and OpenGL subsystems.

New Features:

  • Introduce generic circular increment/decrement utilities with compile-time validation for unsigned and boolean types.

Bug Fixes:

  • Prevent potential use-after-free and null-access issues in the proxy user socket handling by guarding operations through an indirect socket getter.
  • Ensure ConnectionListener proactively deletes its Proxy on destruction to avoid proxy shutdown use-after-free when sending goodbye messages.
  • Fix AbstractParser syntax display to defensively reject null C-string input instead of dereferencing a null pointer.
  • Allow ProgressCounter to report 100 percent completion instead of capping at 99 percent.
  • Avoid unsafe assumptions about internal pointers in Action records by wrapping IAction ownership in an ActionHolder that validates construction and centralizes matching.
  • Make MapDestination destructor robust against exceptions from finalization to avoid std::terminate during stack unwinding.
  • Remove dead exit/connection validation code paths that could assert in now-impossible scenarios.
  • Tighten logging stream operator to throw on null string input instead of asserting and dereferencing null pointers.
  • Avoid potential null dereference in MainWindow log handling by routing through a stored member pointer.

Enhancements:

  • Reorganize MainWindow construction to use small lambdas for initializing child widgets, dock panels, and controllers, improving readability and object lifetime clarity.
  • Ensure platform-specific MapDestination invariants are checked at construction time and simplify accessors to use existing deref helpers.
  • Refine map storage metadata to use a strongly typed destination kind enum and propagate it through device selection logic.
  • Strengthen GameObserver signal wiring with more explicit type usage and value semantics in multiple widgets and sessions.
  • Simplify and modernize parser action construction by moving callbacks and using lightweight holder structs instead of raw unique_ptr exposure.
  • Tighten various constructors and parameters by dropping unnecessary const-by-value usage and enforcing clearer ownership semantics.
  • Refine CanvasDisabler and MapFrontendBlocker to use clearer member naming and encapsulated state.

Build:

  • Minorly reorder CMake source file listings for timers and main window components for consistency.

Tests:

  • Add compile-time tests and static assertions for the new circular increment/decrement utilities to validate supported configurations and behavior.

Chores:

  • Remove several blocks of commented or dead diagnostic and assertion code related to non-existent rooms and connections in rendering and pathfinding.
  • Tidy include ordering in several OpenGL and window-related headers for consistency and clarity.
  • Eliminate an unused Proxy::hasConnectedUserSocket helper and related dead declarations.

@sourcery-ai

sourcery-ai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors several subsystems for safer lifetime/ownership semantics, introduces reusable circular index helpers, and tightens null/exception handling across UI, map storage, proxy, parser, and utility code while removing some dead code and minor build ordering issues.

Sequence diagram for Proxy::UserSocket null-safe operations

sequenceDiagram
participant Proxy
participant UserSocket
participant AbstractSocket
participant UserSocketOutputs as Outputs

Proxy->>Proxy: allocUserSocket()
Proxy->>UserSocket: UserSocket(get_socket_lambda, parent, outputs)
UserSocket->>AbstractSocket: getSocket()
AbstractSocket-->>UserSocket: sig_disconnected
UserSocket->>Outputs: onDisconnected()
AbstractSocket-->>UserSocket: readyRead
UserSocket->>Outputs: onReadyRead()

UserSocket->>UserSocket: disconnectFromHost()
UserSocket->>AbstractSocket: getSocket()
alt [socket is null]
  UserSocket->>UserSocket: qWarning("non-existent user socket")
else [socket exists]
  UserSocket->>AbstractSocket: flush()
  UserSocket->>AbstractSocket: disconnectFromHost()
end

UserSocket->>UserSocket: sendToSocket(bytes)
UserSocket->>AbstractSocket: getSocket()
alt [socket is null]
  UserSocket->>UserSocket: qWarning("non-existent user socket")
else [socket exists]
  UserSocket->>AbstractSocket: isConnected()
  alt [!isConnected]
    UserSocket->>UserSocket: qWarning("closed user socket")
  else [connected]
    UserSocket->>AbstractSocket: write(bytes.getQByteArray())
  end
end
Loading

File-Level Changes

Change Details Files
MainWindow initialization is restructured to use local variables, lambdas, and member caching for UI widgets and dialogs, improving object naming, deref usage, and some status/config behaviors.
  • Wrap MapData and Mmapper2PathMachine construction in std::invoke lambdas, assign to member pointers, and ensure objectName is set immediately after construction.
  • Refactor creation of log, group, room, adventure, description, timers, and client dock widgets into lambdas that both configure the dock/widget and store them into member variables, adding a new m_logWindow member used in slot_log.
  • Adjust AdventureTracker and AdventureWidget creation so the tracker is created locally, wired, then stored in m_adventureTracker; tweak XPStatusWidget connections to use deref(m_dockDialogAdventure).
  • Rework ConnectionListener and ClientWidget setup into a lambda that safely references m_dockDialogTimers and captures m_listener and dock pointers, with an added std::ignore guard for deref(timers).
  • Change ConfigDialog handling to construct into a local unique_ptr, wire signals using the raw pointer, then move into m_configDialog; subsequent access goes through deref(m_configDialog).
  • Simplify slot_newInfomarkSelection by computing a bool once, updating m_infoMarkSelection via shared_from_this when non-null, using deref(infomarkActions.infomarkGroup), and relying on the raw pointer for size/empty checks.
src/mainwindow/mainwindow.cpp
src/mainwindow/mainwindow.h
Add circular_increment/circular_decrement utilities with compile-time validation and tests to support safe circular index arithmetic.
  • Introduce details::cpp11::IsValidCircularSize and details::cpp17::IsValidCircularSize_v to validate N and T for circular operations, with special handling for bool/2.
  • Implement constexpr circular_increment and circular_decrement templates that perform modulo arithmetic for unsigned types, toggle bool for N==2, and static_assert otherwise before abort().
  • Add a block of static_asserts in utils.cpp validating IsValidCircularSize_v and circular increment/decrement behavior for several type/size combinations.
src/global/utils.h
src/global/utils.cpp
MapDestination and AbstractMapStorage are tightened for ownership, platform-specific invariants, and directory-vs-file handling, including RAII-style finalization and non-const buffer access.
  • Make MapDestination::alloc and ctor take QString by value and move it, include utils.h/QDebug, and in the ctor assert platform vs. destination type, deref the underlying buffer or saver via std::ignore.
  • Add a MapDestination destructor that calls finalize() and catches exceptions, logging with qWarning instead of allowing termination during stack unwinding.
  • Change getIODevice/getWasmBufferData to use deref() instead of manual asserts; make getWasmBufferData return QByteArray by value.
  • Adjust MapDestination::finalize to branch on CURRENT_PLATFORM via runtime comparison, deref(m_buffer) on Wasm, deref(m_fileSaver).close() on native files, and assert directory otherwise.
  • Refine AbstractMapStorage::Data to use a strongly-typed uint8_t-backed TypeEnum and set destinationType via isDirectory(); Update getDevice() to compare against TypeEnum::Directory.
src/mapstorage/MapDestination.cpp
src/mapstorage/MapDestination.h
src/mapstorage/abstractmapstorage.h
src/mapstorage/abstractmapstorage.cpp
Proxy user socket handling is made more robust to teardown by indirecting socket access, adding null checks, and simplifying the Proxy interface.
  • Introduce AbstractSocketGetter (std::function<AbstractSocket*()>) and store it in Proxy::UserSocket instead of a reference to a unique_ptr.
  • Modify UserSocket to connect signals using a helper getSocket(), and update disconnectFromHost/sendToSocket to handle null sockets gracefully, logging warnings and using deref() when valid.
  • Change Proxy::allocUserSocket to pass a lambda returning m_userSocket.get() into the UserSocket ctor.
  • Remove the unused hasConnectedUserSocket() private helper from Proxy.
src/proxy/proxy.cpp
src/proxy/proxy.h
Parser action storage and construction are adjusted to move callbacks eagerly and wrap actions in a small holder class, changing the ActionRecordMap value type and its usage.
  • Update StartsWithAction and EndsWithAction constructors to take ActionCallback by value and std::move it into the member, instead of copying from const reference.
  • Introduce ActionHolder owning a std::unique_ptr, with move-only semantics, a ctor that derefs the pointer for side effects, and a match() forwarding call.
  • Change ActionRecordMap to map ActionHint to ActionHolder instead of std::unique_ptr and update MumeXmlParserBase::evalActionMap to call action.match(line).
src/parser/Action.h
src/parser/AbstractParser-Actions.cpp
Multiple defensive and stylistic tweaks improve null handling, exception safety, and readability in parsers, logging, clock widget, and other utilities.
  • In AbstractParser::tryGetDir, use an init-statement for lower and simplify the comparison; in showSyntax, enforce rest != nullptr by throwing std::invalid_argument instead of assert and remove the fallback empty string.
  • In AbstractDebugOStream::operator<<(const char*), replace assert with a NullPointerException throw on nullptr and always call writeUtf8(s).
  • Tighten MumeClockWidget signal connections by adding const to lambda parameter types and minor formatting changes.
  • Reorder Weather-related includes to use relative ./legacy headers and forward-declare FrameManager; minor include ordering fixes in UboManager, Weather.cpp, and Weather.h.
  • Add mmqt::makeQScopedPointer helper that constructs a QObject-derived instance via unique_ptr and returns a QScopedPointer.
  • Change RemoteEditSession and derived session constructors to drop unnecessary const qualifiers on value-type IDs.
src/parser/abstractparser.cpp
src/global/logging.h
src/clock/mumeclockwidget.cpp
src/opengl/Weather.h
src/opengl/Weather.cpp
src/opengl/UboManager.h
src/opengl/legacy/Legacy.cpp
src/global/MakeQPointer.h
src/mpi/remoteeditsession.h
Clean up or simplify various map/rendering paths and RAII helpers, removing dead checks and clarifying member naming.
  • Remove dead-code checks and asserts around non-existent target rooms in ConnectionDrawer::drawRoomConnectionsAndDoors and MapData::shortestPathSearch, assuming validated maps.
  • Rename CanvasDisabler and MapFrontendBlocker members to m_window/m_data, moving MapFrontendBlocker’s MapFrontend reference into the private section, with consistent use in ctor/dtor.
  • Minor CMakeLists source ordering tweaks for UpdateDialog and TimerDelegate, plus a ProgressCounter::State change to allow reaching 100% instead of clamping to 99%.
  • In AbstractMapStorage, treat directory save destinations as a hard error when requesting a QIODevice.
  • In MainWindow::slot_log, reference m_logWindow via a local pointer variable before appending and moving the cursor.
src/display/Connections.cpp
src/mapdata/shortestpath.cpp
src/mainwindow/utils.cpp
src/mainwindow/utils.h
src/CMakeLists.txt
src/global/progresscounter.h
src/mainwindow/mainwindow.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@nschimme nschimme changed the title Jahara wip 2026 07 15a misc cleanup and bug fixes Jun 15, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In MainWindow’s Log Panel setup lambda, new QTextBrowser(m_dockDialogLog) still uses the (null) member as parent instead of the freshly created dock, which will break the parent/ownership chain; this should be new QTextBrowser(dock) and m_dockDialogLog updated afterward as you already do.
  • Changing AbstractParser::showSyntax to throw std::invalid_argument on a null rest pointer is a behavior change from the prior assert/empty-string handling; verify that all existing callers never pass null or consider preserving the old behavior to avoid unexpected exceptions in release builds.
  • The new manual delete m_proxy in ConnectionListener::~ConnectionListener subtly changes ownership semantics of m_proxy; it would be safer and clearer to convert this to an owning smart pointer (as hinted in the comment) and rely on member destruction order rather than a raw-pointer delete in the destructor.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In MainWindow’s Log Panel setup lambda, `new QTextBrowser(m_dockDialogLog)` still uses the (null) member as parent instead of the freshly created `dock`, which will break the parent/ownership chain; this should be `new QTextBrowser(dock)` and `m_dockDialogLog` updated afterward as you already do.
- Changing `AbstractParser::showSyntax` to throw `std::invalid_argument` on a null `rest` pointer is a behavior change from the prior `assert`/empty-string handling; verify that all existing callers never pass null or consider preserving the old behavior to avoid unexpected exceptions in release builds.
- The new manual `delete m_proxy` in `ConnectionListener::~ConnectionListener` subtly changes ownership semantics of `m_proxy`; it would be safer and clearer to convert this to an owning smart pointer (as hinted in the comment) and rely on member destruction order rather than a raw-pointer delete in the destructor.

## Individual Comments

### Comment 1
<location path="src/mapstorage/MapDestination.cpp" line_range="73-82" />
<code_context>
+    }
+}
+
+MapDestination::~MapDestination()
+{
+    // finalize() calls FileSaver::close() which can throw on rename failure;
+    // catch here to avoid std::terminate() if this destructor runs during
+    // stack unwinding from a prior finalize() error.
+    try {
+        finalize();
+    } catch (const std::exception &ex) {
+        qWarning() << "MapDestination::~MapDestination() caught exception:" << ex.what();
+    } catch (...) {
+        qWarning() << "MapDestination::~MapDestination() caught unknown exception";
+    }
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** Calling finalize() unconditionally in the destructor can double-close the FileSaver and change exception behavior.

With this change, destruction now implicitly calls `finalize()`, which calls `FileSaver::close()` on native platforms. If callers still invoke `finalize()` explicitly (e.g. to control when errors surface), `close()` may be executed twice on the same `FileSaver`, which may not be safe and could hide failures. It also alters error semantics: exceptions that used to propagate from an explicit `finalize()` may now be caught in the destructor instead.

Consider either making `finalize()` idempotent via a `m_finalized` flag, or reverting to explicit finalization and documenting that protocol instead of calling `finalize()` from the destructor.
</issue_to_address>

### Comment 2
<location path="src/proxy/connectionlistener.cpp" line_range="54-63" />
<code_context>
 {}

-ConnectionListener::~ConnectionListener() = default;
+ConnectionListener::~ConnectionListener()
+{
+    // Note: We're the owner of the proxy, so we're allowed to delete it.
+    // Deleting the proxy here somehow prevents the proxy's destructor from triggering
+    // use-after-free undefined behavior when the proxy tries to send a goodbye message
+    // to the user.
+    //
+    // Another option would be to make m_proxy an owning pointer (e.g. unique_ptr or QScopedPointer),
+    // and then make sure it's declared last so it'll be deleted first, and then make this dtor
+    // a default dtor again.
+    if (Proxy *const pProxy = m_proxy) {
+        MMLOG() << "ConnectionListener is deleting the proxy...";
+        delete pProxy;
+    }
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** Manually deleting m_proxy in the destructor is risky unless ConnectionListener is the sole owner.

Because `m_proxy` is a raw pointer, other parts of the code may still treat it as non-owning (e.g., via QObject parenting or separate ownership). Deleting it unconditionally here risks double-deletion or a dangling pointer during teardown.

If ConnectionListener is the true owner, please model that explicitly with `std::unique_ptr<Proxy>` or `QScopedPointer<Proxy>` and order the members so the proxy is destroyed before its users. If ownership is shared/unclear, avoid deleting it here and instead disconnect and null out `m_proxy` while relying on the actual owner to destroy it.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +73 to +82
MapDestination::~MapDestination()
{
// finalize() calls FileSaver::close() which can throw on rename failure;
// catch here to avoid std::terminate() if this destructor runs during
// stack unwinding from a prior finalize() error.
try {
finalize();
} catch (const std::exception &ex) {
qWarning() << "MapDestination::~MapDestination() caught exception:" << ex.what();
} catch (...) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Calling finalize() unconditionally in the destructor can double-close the FileSaver and change exception behavior.

With this change, destruction now implicitly calls finalize(), which calls FileSaver::close() on native platforms. If callers still invoke finalize() explicitly (e.g. to control when errors surface), close() may be executed twice on the same FileSaver, which may not be safe and could hide failures. It also alters error semantics: exceptions that used to propagate from an explicit finalize() may now be caught in the destructor instead.

Consider either making finalize() idempotent via a m_finalized flag, or reverting to explicit finalization and documenting that protocol instead of calling finalize() from the destructor.

Comment on lines +54 to +63
ConnectionListener::~ConnectionListener()
{
// Note: We're the owner of the proxy, so we're allowed to delete it.
// Deleting the proxy here somehow prevents the proxy's destructor from triggering
// use-after-free undefined behavior when the proxy tries to send a goodbye message
// to the user.
//
// Another option would be to make m_proxy an owning pointer (e.g. unique_ptr or QScopedPointer),
// and then make sure it's declared last so it'll be deleted first, and then make this dtor
// a default dtor again.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Manually deleting m_proxy in the destructor is risky unless ConnectionListener is the sole owner.

Because m_proxy is a raw pointer, other parts of the code may still treat it as non-owning (e.g., via QObject parenting or separate ownership). Deleting it unconditionally here risks double-deletion or a dangling pointer during teardown.

If ConnectionListener is the true owner, please model that explicitly with std::unique_ptr<Proxy> or QScopedPointer<Proxy> and order the members so the proxy is destroyed before its users. If ownership is shared/unclear, avoid deleting it here and instead disconnect and null out m_proxy while relying on the actual owner to destroy it.

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 1.16732% with 254 lines in your changes missing coverage. Please review.
✅ Project coverage is 25.66%. Comparing base (853f237) to head (ce0080b).

Files with missing lines Patch % Lines
src/mainwindow/mainwindow.cpp 0.00% 172 Missing ⚠️
src/proxy/proxy.cpp 0.00% 23 Missing ⚠️
src/mapstorage/MapDestination.cpp 0.00% 15 Missing ⚠️
src/mainwindow/utils.cpp 0.00% 9 Missing ⚠️
src/clock/mumeclockwidget.cpp 0.00% 8 Missing ⚠️
src/parser/Action.h 0.00% 8 Missing ⚠️
src/parser/abstractparser.cpp 0.00% 6 Missing ⚠️
src/parser/AbstractParser-Actions.cpp 0.00% 4 Missing ⚠️
src/proxy/connectionlistener.cpp 0.00% 4 Missing ⚠️
src/global/logging.h 75.00% 1 Missing ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #541      +/-   ##
==========================================
- Coverage   25.69%   25.66%   -0.04%     
==========================================
  Files         521      521              
  Lines       43187    43249      +62     
  Branches     4706     4708       +2     
==========================================
- Hits        11099    11098       -1     
- Misses      32088    32151      +63     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nschimme nschimme merged commit 36e80ba into MUME:master Jun 15, 2026
19 of 21 checks passed
@nschimme nschimme deleted the jahara-wip-2026-07-15a branch June 15, 2026 19:57
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.

1 participant