Skip to content

cleanup: 6 safety fixes from clang-tidy worklist - #773

Merged
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:cleanup/clang-tidy-safety-fixes
May 30, 2026
Merged

cleanup: 6 safety fixes from clang-tidy worklist#773
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:cleanup/clang-tidy-safety-fixes

Conversation

@got3nks

@got3nks got3nks commented May 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Second cleanup PR from the worklist on #766, addressing six small, isolated safety guards surfaced by the .clang-tidy baseline in #770:

File Issue Check
src/utils/cas/html.c:89 fopen(template,"r") result not NULL-checked before fgetc dereferences — crash on missing template clang-analyzer-unix.Stream
src/PartFile.cpp:3341, 3420 uint16 partNumber loop against uint32 partCount — would silently truncate above 65535 if GetPartCount() ever widens bugprone-too-small-loop-variable
src/utils/aLinkCreator/src/ed2khash.cpp:114 User-cancel path frees buf but leaks the realloc-grown tmpCharHash clang-analyzer-unix.Malloc
src/kademlia/kademlia/Search.cpp:241 (time_t)(uint32 + 3) — addition wraps in uint32 before the cast (2106 boundary) bugprone-misplaced-widening-cast
src/ED2KLinkParser.cpp:107 string(getenv("HOME")) is UB if HOME unset; mirror the existing macOS-branch's defensive pattern clang-analyzer-cplusplus.StringChecker
src/BitVector.h:144 SetBuffer calls memcpy(NULL, src, 0) after clear() — C-standard UB, matches SetAllTrue() pattern above clang-analyzer-core.NonNullParamChecker

Test plan

  • amule, amuled, amulegui, cas, alc build clean on macOS
  • clang-tidy with the chore: add baseline .clang-tidy configuration #770 baseline reports 0 warnings on the patched sites (was 6+ pre-fix); no new warnings introduced
  • Each fix is minimal and isolated — no behavioural change on the happy path

Depends on #770 for the baseline config that surfaced this worklist.

…mule-project#770)

Fixes the medium-priority cluster from the lint worklist that's surfaced by
the .clang-tidy baseline in amule-project#770. Each is a small, isolated safety guard:

  - src/utils/cas/html.c:89 -- fopen(template,"r") result wasn't NULL-
    checked before fgetc() dereferenced it; a missing template file
    crashed the CGI helper.  Add the NULL-check + perror+exit to match
    the existing calloc/fstat error-handling shape in this file.

  - src/PartFile.cpp:3341, 3420 -- loop variable promoted from uint16
    to uint32 to match `partCount` (declared uint32 at line 3193).
    GetPartCount() currently returns uint16 so today's behaviour is
    unchanged, but a future widening wouldn't silently truncate the
    iteration above 65535.  Fires bugprone-too-small-loop-variable.

  - src/utils/aLinkCreator/src/ed2khash.cpp:114 -- user-cancel path
    (progress hook returning false) freed `buf` but leaked the
    realloc()-grown `tmpCharHash`.  Hash of every mid-run cancelled
    file leaked the cumulative parthash buffer.  Free both.  Fires
    clang-analyzer-unix.Malloc.

  - src/kademlia/kademlia/Search.cpp:241 -- `(time_t)(uint32 + 3) >
    time(NULL)` did the addition in uint32 before the cast, so the
    comparison would reorder near the 2106 32-bit time wraparound.
    Cast first, add second.  Eighty years out, but cheap to write
    correctly.  Fires bugprone-misplaced-widening-cast.

  - src/ED2KLinkParser.cpp:107 -- `string(getenv("HOME"))` is UB if
    HOME is unset (rare but possible).  Mirror the existing
    macOS-branch pattern further up the same function:
    `string(home ? home : "")`.  Fires
    clang-analyzer-cplusplus.StringChecker.

  - src/BitVector.h:144 -- SetBuffer() ran memcpy(m_vector, src,
    m_bytes) unconditionally.  After clear(), m_vector is NULL and
    m_bytes is 0, so the call reduces to memcpy(NULL, src, 0), which
    is C-standard UB even though real libcs no-op it.  Guard on
    m_bytes (matches SetAllTrue() above).  Fires
    clang-analyzer-core.NonNullParamChecker.

All builds clean across amule, amuled, amulegui, cas, alc on macOS.
Re-run of clang-tidy with the amule-project#770 baseline confirms each of the
above sites no longer fires; no new warnings introduced.
@got3nks got3nks mentioned this pull request May 30, 2026
@mrjimenez
mrjimenez merged commit ade0bf7 into amule-project:master May 30, 2026
7 checks passed
@got3nks
got3nks deleted the cleanup/clang-tidy-safety-fixes branch June 3, 2026 14:16
got3nks added a commit to got3nks/amule that referenced this pull request Aug 3, 2026
… s (amule-project#773)

The poll handler walks a round-robin, one step per timer tick, so at a 1000 ms tick across three steps any one step -- the stats request, or the active page's data -- came round only every ~3 s. The tick halves to 500 ms and the round-robin loses a step that did nothing but advance the index, whose body went away in 255dd95 once the stats packet started carrying the connection state.

The tick had to move because the handler only runs on a tick boundary: a wall-clock guard can only round up to the next one, so on a 1000 ms tick the achievable intervals were 1000, 2000, 3000 and nothing between.

This is a 3x increase in poll rate for those two requests. It is affordable because they are now incremental updates against the daemon's value maps, so a poll with nothing to report costs a near-empty reply. The daemon-side cost of serving EC_OP_GET_UPDATE does not shrink with the reply -- it still walks the file and client lists to compute the diff -- and at 0.34-2.05 ms measured on a production daemon that is roughly 0.03-0.2% of a core at 1 Hz.

Unaffected: the statistics tree still honours GetStatsInterval() and the ED2K link scan still runs at 1 s, both being wall-clock guarded; the reconnect timer is a separate wxTimer; the 30 s reply watchdog is wall-clock based.

The round-robin stays because two of its properties are load-bearing. It emits at most one step's worth of requests per tick, so independently-scheduled concerns cannot drift into alignment and burst against a request FIFO whose threshold is 20. And the fifo-full early return sits above the switch, so back-pressure pauses the cycle in place rather than leaving every concern overdue to fire at once on recovery. That matters most for EC_OP_STAT_REQ, EC_OP_GET_SERVERINFO and EC_OP_GET_CHAT_MESSAGES, which go out through SendRequest rather than DoRequery and so have no in-flight guard of their own.
mrjimenez pushed a commit to mrjimenez/amule that referenced this pull request Aug 8, 2026
…den (amule-project#817)

Losing the EC link put a modal reconnect dialog on screen whether or not the window was there to be seen. Minimised to the taskbar or hidden to the tray it appeared over whatever the user was actually doing and took focus, to explain a frozen window they could not see.

The dialog earns its intrusion by explaining that frozen window, so it now waits until there is one. With the window out of sight the retry loop runs exactly as before -- every attempt logged, the countdown ticking, the same 5 s spacing -- and simply draws nothing. Reconnect while minimised and the dialog is never created: the link comes back, polling resumes, nobody is interrupted. Return to the window mid-reconnect and it appears, opening on whatever the loop is doing rather than on "attempt 1", Abort button and all.

"Out of sight" means two things and neither alone is enough: minimised to Dock or taskbar keeps IsShown() true with nothing on screen, while hidden to tray leaves the iconized bit clear with no frame at all. CMuleTrayIcon::DoShowHide already had that pair spelled out inline to label its Show/Hide entry; it moves to CamuleDlg::IsVisibleToUser() and both callers share the one definition. Restoring is likewise two paths, because tray-hide never fires wxIconizeEvent and iconize never fires EVT_SHOW, so OnMinimize and OnShow each carry the hook. Neither shows the dialog directly: that would run a nested modal loop whose abort path calls Quit(), and tearing the main window down from a nested loop is what amule-project#738 was about, so it goes through CallAfter.

The retry machinery needed no changes -- AttemptReconnect, ScheduleNextReconnect and OnReconnectTimer already guarded every dialog access. What did need splitting is the tail of BeginReconnect, which assumed it was resuming after ShowModal returned; that work moves to FinishReconnect() so it runs whether or not a dialog was ever shown.

The dialog was never gated on window state, so what changed recently is how often it is reached rather than when it may appear: amule-project#768 made a silent link trigger a reconnect instead of freezing, and amule-project#773's move to ~1 s polling keeps a request outstanding far more of the time, which is that watchdog's precondition.

Iconize is not observable on Wayland (xdg-shell has no such notification), so compositors that never report it keep the old behaviour for the minimise case; the tray paths are unaffected.
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.

2 participants