Skip to content

cleanup: wxASSERT -> wxCHECK_MSG on array-access guards in Logger / UserEvents - #772

Merged
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:cleanup/wxassert-to-wxcheck
May 30, 2026
Merged

cleanup: wxASSERT -> wxCHECK_MSG on array-access guards in Logger / UserEvents#772
mrjimenez merged 1 commit into
amule-project:masterfrom
got3nks:cleanup/wxassert-to-wxcheck

Conversation

@got3nks

@got3nks got3nks commented May 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Switches the wxASSERT(precondition); use_value() pattern in CLogger::GetDebugCategory and across CUserEvents's s_EventList accessors to wxCHECK_MSG / wxCHECK_RET. wxASSERT compiles to a no-op in release, so the existing pattern would read past the array in release on a bad index (where a debug build would assert and abort). Surfaced by clang-analyzer-security.ArrayBound in the baseline config from #770 — this was the most-actionable cluster in that worklist (the only category that genuinely UB's in release builds on bad input).

wxCHECK_* keeps the debug-build behaviour identical (assert + abort on first mis-use) and on release substitutes a safe sentinel return (s_EventList[0] / g_debugcats[0]) or short-circuits void paths, plus emits a runtime log line so the misuse is still observable.

Mechanical change

CUserEvents's CheckIndex was previously wrapped in #ifdef __WXDEBUG__ because wxASSERT doesn't evaluate its argument. wxCHECK_* does, so the function is now always-defined. It's a one-liner (return idx < itemsof(s_EventList);), no measurable cost in release.

Out of scope

MD4Hash.h:202 appeared in the same cluster on lint output but isn't a wxASSERT issue — the analyzer can't track the per-byte writes through RawPokeUInt64 inside CMD4Hash::SetHash and concludes m_hash is partially uninitialised when EncodeSTL reads it. The code is correct; the analyzer is symbolically wrong on that one. Left alone.

Test plan

  • amule, amuled, amulegui build clean on macOS
  • clang-tidy with the chore: add baseline .clang-tidy configuration #770 baseline reports 0 ArrayBound warnings on the two changed files (was 12 pre-fix)
  • No behavioural change on valid inputs (wxCHECK_* short-circuits identically to the previous code path's happy case)

         Logger / UserEvents

wxASSERT is a no-op in release builds, so the existing
"wxASSERT(precond); use_value()" pattern in CLogger::GetDebugCategory
and in CUserEvents' s_EventList accessors would read past the array
in release if an out-of-range index ever reached them (where in debug
it would have asserted + aborted). clang-tidy surfaces all of these as
clang-analyzer-security.ArrayBound on the release-build code path.

Switch to wxCHECK_MSG / wxCHECK_RET on the same precondition: debug
builds still assert + abort on first mis-use, release builds now log
the failure and either return a safe sentinel (entry [0] of the
array, which is always present and well-defined) or short-circuit
the void path.

UserEvents.cpp's CheckIndex was previously guarded behind
#ifdef __WXDEBUG__ because wxASSERT doesn't evaluate its argument;
wxCHECK_* does, so the function is now always-defined. It is a
trivial one-liner and adds no measurable cost in release.

Out of scope for this commit: the MD4Hash.h:202 warning that
appeared in the same cluster on lint output is not actually a
wxASSERT issue -- the analyzer can't track the per-byte writes
through RawPokeUInt64 in CMD4Hash::SetHash, so it concludes m_hash
is partially uninitialised when EncodeSTL reads it. The code is
correct; the analyzer is symbolically wrong on that one.

Verified the warnings disappear: a re-run of clang-tidy with the
.clang-tidy baseline from amule-project#770 over the two changed files emits
zero ArrayBound hits, down from 12 on the pre-fix tree.
@got3nks got3nks mentioned this pull request May 30, 2026
@mrjimenez
mrjimenez merged commit 40ce3bb into amule-project:master May 30, 2026
7 checks passed
mrjimenez pushed a commit that referenced this pull request Jun 1, 2026
mifritscher2 hit a debug-build assert switching ed2k servers:

  /home/x/code/amule/src/EncryptedStreamSocket.cpp(437): assert
    "m_nReceiveBytesWanted > 0" failed in Negotiate().

  bt: CServerSocket::OnReceive -> CEMSocket::OnReceive
   -> CEncryptedStreamSocket::Read -> Negotiate -> wxASSERT

CEncryptedStreamSocket::Read() unconditionally calls Negotiate()
whenever the socket sits in ECS_NEGOTIATING and bytes arrive. The
negotiation state machine consumes bytes in chunks of
m_nReceiveBytesWanted; when that counter hits 0 the inner loop exits
and the function returns either via ONS_COMPLETE (advancing to
ECS_ENCRYPTING) or via a state transition that resets the counter to
a fresh positive expectation.

There is a window where the socket can stay in ECS_NEGOTIATING with
m_nReceiveBytesWanted == 0 -- the obvious trigger is the user
switching servers while the kernel still has buffered bytes for the
previous half-negotiated connection. The next OnReceive lands in
Read(), Read() dispatches to Negotiate(), and we hit the precondition
assert.

The asserted precondition is a real bug shape: walking into the while
loop with m_nReceiveBytesWanted == 0 lets std::min(nLen - nRead, 0)
return 0 forever (infinite loop bails via ONS_COMPLETE check, but the
state math is bogus). wxASSERT compiles to a no-op in release builds,
so the production path is undefined behaviour rather than a clean
abort. Same shape as the wxASSERT-as-precondition cluster the
clang-tidy baseline (#770) and #772 already converted to wxCHECK_*
elsewhere -- just on a site #772 did not reach.

Switch this site to wxCHECK_MSG with a -1 sentinel return. Both
callers in Read() already check for nRead == (uint32_t)(-1) and
short-circuit to "encryption read error" -> abort the connection.
Debug builds still abort (wxCHECK_MSG asserts in debug); release
builds now drop the connection cleanly instead of UB'ing forward.
@got3nks
got3nks deleted the cleanup/wxassert-to-wxcheck branch June 3, 2026 14:16
got3nks added a commit to got3nks/amule that referenced this pull request Jun 4, 2026
…ndex

Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(amule-project#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for amule-project#785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), amule-project#794 (.dmg
  amuleweb path), amule-project#789 (<OS>-<arch> artifact naming), amule-project#780 / amule-project#796
  (Windows DPI + comctl32 manifest), amule-project#784 (FHS share/amule paths).

- Bug Fixes & Stability: post-amule-project#744 fixes including EC notification
  leak (amule-project#797), big-library scaling (amule-project#736, amule-project#840 superseding amule-project#728),
  amulegui ghost entries (amule-project#810, amule-project#819, amule-project#841, amule-project#824, amule-project#830, amule-project#760),
  PartFile early hash (amule-project#762), server protocol fixes (amule-project#835, amule-project#788,
  amule-project#721, amule-project#787), crypto stream UB (amule-project#779), UAF prevention (amule-project#756),
  Kad rotation (amule-project#795, amule-project#799/amule-project#805), GTK warning silencing (amule-project#833,
  amule-project#826/amule-project#836), and the clang-tidy worklist (amule-project#770, amule-project#772-amule-project#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (amule-project#753/amule-project#754/amule-project#776), Galician (amule-project#763), Slovenian (amule-project#771), pt-BR
  (amule-project#768/amule-project#775/amule-project#812), French (amule-project#811), plus man-page tooling for
  date+version drift (amule-project#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (amule-project#817/amule-project#818/amule-project#821/amule-project#828/amule-project#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with amule-project#746-amule-project#845 + amule-project#841.
got3nks added a commit to got3nks/amule that referenced this pull request Jun 4, 2026
…ndex

Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(amule-project#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for amule-project#785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), amule-project#794 (.dmg
  amuleweb path), amule-project#789 (<OS>-<arch> artifact naming), amule-project#780 / amule-project#796
  (Windows DPI + comctl32 manifest), amule-project#784 (FHS share/amule paths).

- Bug Fixes & Stability: post-amule-project#744 fixes including EC notification
  leak (amule-project#797), big-library scaling (amule-project#736, amule-project#840 superseding amule-project#728),
  amulegui ghost entries (amule-project#810, amule-project#819, amule-project#841, amule-project#824, amule-project#830, amule-project#760),
  PartFile early hash (amule-project#762), server protocol fixes (amule-project#835, amule-project#788,
  amule-project#721, amule-project#787), crypto stream UB (amule-project#779), UAF prevention (amule-project#756),
  Kad rotation (amule-project#795, amule-project#799/amule-project#805), GTK warning silencing (amule-project#833,
  amule-project#826/amule-project#836), and the clang-tidy worklist (amule-project#770, amule-project#772-amule-project#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (amule-project#753/amule-project#754/amule-project#776), Galician (amule-project#763), Slovenian (amule-project#771), pt-BR
  (amule-project#768/amule-project#775/amule-project#812), French (amule-project#811), plus man-page tooling for
  date+version drift (amule-project#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (amule-project#817/amule-project#818/amule-project#821/amule-project#828/amule-project#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with amule-project#746-amule-project#845 + amule-project#841.
mrjimenez pushed a commit that referenced this pull request Jun 4, 2026
Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for #785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), #794 (.dmg
  amuleweb path), #789 (<OS>-<arch> artifact naming), #780 / #796
  (Windows DPI + comctl32 manifest), #784 (FHS share/amule paths).

- Bug Fixes & Stability: post-#744 fixes including EC notification
  leak (#797), big-library scaling (#736, #840 superseding #728),
  amulegui ghost entries (#810, #819, #841, #824, #830, #760),
  PartFile early hash (#762), server protocol fixes (#835, #788,
  #721, #787), crypto stream UB (#779), UAF prevention (#756),
  Kad rotation (#795, #799/#805), GTK warning silencing (#833,
  #826/#836), and the clang-tidy worklist (#770, #772-#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (#753/#754/#776), Galician (#763), Slovenian (#771), pt-BR
  (#768/#775/#812), French (#811), plus man-page tooling for
  date+version drift (#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (#817/#818/#821/#828/#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with #746-#845 + #841.
mrjimenez pushed a commit to mrjimenez/amule that referenced this pull request Aug 4, 2026
…roject#772)

* perf(ec): build a client's tags only for fields that changed

The client list is rebuilt in full on every EC poll. Each field went
through `AddTag(CECTag(name, value), valuemap)`, which constructs the tag
first -- calling the getter, copying the string, allocating the tag -- and
only then asks the value map whether it was needed, discarding it if not.
So the diff suppressed the field from the wire but not the work, and that
work is identical whether a peer is idle or saturating the link.

CValueMap already had the other form. `CreateTag` compares the raw value
against a typed cache and constructs a CECTag only when it differs. It was
simply unused: all 56 value-map call sites in ECSpecialCoreTags.cpp took
the build-then-discard path, none took this one. It also stops caching
whole CECTag objects per field in `m_map_tag`, which held every field's
data buffer for every peer.

Field measurements on a live daemon put this loop at 55% of the whole
GET_UPDATE handler -- roughly 34us per peer, scaling linearly with peer
count while the shared-file walk stayed flat at a constant 1204 files. It
is the only phase that grows with anything a user can change.

`AddDiffTag` wraps the choice because `valuemap` is NULL for callers not
doing an incremental update, where every tag is emitted unconditionally.

Two details that would otherwise change the wire format:

bool and double get their own CreateTag overloads. CECTag has explicit
constructors for both, so without them a bool argument is ambiguous across
the integer overloads and resolving it with a cast would silently change
the tag's type for every client reading it.

EC_TAG_CLIENT_UPLOAD_FILE deliberately stays on the old path. It is
written from two branches, one a file ECID and the other a CECIntTag zero,
and the two forms keep separate caches -- converting one would leave
neither seeing the other's last value, so a transition between the
branches could be suppressed and the field would freeze in the GUI.

ForgetTag only clears `m_map_tag` and so would not reach the typed caches,
but it has no callers anywhere in the tree.

* perf(ec): pass value-map values by reference, not by copy

CreateTagT, the wxString/CMD4Hash/CUInt128 CreateTag overloads and the new
AddDiffTag all took their value by copy. The client getters hand back a
const reference (`const wxString &GetUserName() const`), so each string
field paid a wxString copy into AddDiffTag and a second into CreateTag --
where the original build-then-discard form paid one, into the tag. The
previous commit therefore improved the scalar fields and pessimised the
string ones.

Microbenchmark of the client tag shape, 50 peers x 300 polls, 3 of 37
fields changing:

  build-then-discard        3.88 us/peer/poll
  compare-first, by copy    1.10 us/peer/poll
  compare-first, by ref     0.55 us/peer/poll

and with 10 of 37 changing, 3.31 / 1.42 / 0.89. So half the remaining cost
was copies this file introduced.

Left the integer overloads by value: a reference to a uint8 buys nothing
and costs an indirection.

* perf(ec): one map probe per field, and close the literal-to-bool trap

Two follow-ups from review, plus the test that should have come with the
original change.

CreateTagT walked the tree twice on the unchanged path -- count() then
operator[] -- and three times when a value did change. That path is the
common one and the whole reason the function exists. One lower_bound now
serves as both the probe and the insertion hint.

A string literal reaching AddDiffTag would have emitted the wrong tag
type. CreateTag had no pointer overload, and `const char*` -> bool is a
standard conversion that beats the user-defined one to wxString, so a
literal bound to the bool overload through the value map while the plain
CECTag path -- which has its own const char* and const wxChar*
constructors -- built a string tag. The same call site would then carry a
different wire type on an incremental update than on a full request. No
caller passes one today, which is exactly why nothing caught it; the
overloads forward to wxString so none ever can.

CValueMapTest pins the contract. CValueMap needs no app, daemon or
database -- the EC tag types are the whole dependency -- and its failure
mode is silence: a field wrongly judged unchanged stops updating in the
GUI with no crash and no log line. Twelve cases, including the two branches
of AddDiffTag agreeing on the tag they produce, which is where the bug
above actually lived, and the one-name-two-types hazard that keeps
EC_TAG_CLIENT_UPLOAD_FILE on the old path. Each new assertion was checked
by mutation rather than assumed: folding double through the integer cache
fails DoubleKeepsItsFractionalPart, breaking the value-map-absent branch
fails both AddDiffTag equivalence cases.

Note the bool overload is required to resolve ambiguity, not to protect
the wire format: CECTag(name, bool) calls InitInt() exactly as
CECTag(name, uint8) does, so folding it into an integer overload would
produce an identical tag. The earlier claim that it guarded the wire type
was wrong. double is a real value concern -- routed through an integer it
loses its fractional part -- and that is what its test asserts.

The test target pulls in LoggerConsole.cpp for the no-op DoECLogLine and
theLogger symbols that libec.a's Debug build references unconditionally.
Release preprocesses those calls to `do {} while(0)` via ECLog.h, so the
symbols are unused there and only the Debug matrix entries fail to link --
which is how this reached CI green locally and red there. RefresherTest
pulls the same TU in for the same reason.

Benchmark, one run at this commit, 50 peers x 300 polls:

  3 of 37 fields changing   3.42 -> 0.48 us/peer/poll
  10 of 37 fields changing  3.25 -> 0.78 us/peer/poll
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