Skip to content

Dead-code audit: findings + cleanup tracker #447

Description

@Aybook

Tracker for the dead-code audit (2026-07-16). Scope decided up front: our code only (core/, scripts/, hub/hub.c, build glue) — vendored deps (lua/, luasocket/, luasec/, …) are report-only. Conservative on contract surface: anything in docs/PLUGIN_API.md or core/scripts.lua's SANDBOX_GLOBALS stays even if unused internally, because third-party hubowner plugins we never see can reach it.

Method: 10 parallel agents on disjoint axes, then main-thread synthesis. Every finding below is file:line-anchored; each still needs re-derivation from source before removal (§1a.4) — the audit already produced three wrong calls, see "Lessons" at the bottom.

Already fixed (the audit's real yield so far)

These were not dead code — they fell out while looking:


PR 1 — core/hub.lua dead file-scope locals (highest value)

Why first: hub.lua's main chunk sits at Lua's 200-local cap, and CLAUDE.md §3 therefore freezes file-scope locals ("any new top-level local fails the build"). These 19 are declared and never used — removing them frees 19 slots and un-freezes the file. Smallest diff, biggest structural win.

Declared-and-never-referenced (each verified whole-file, matches are the declaration plus comment prose only):

Line Local Note
167 tablesize last consumer was the PING UC field; #179 replaced it
177 loadfile
183 io
194 table_concat hub_user_object has its own
211 adclib_hash moved to hub_dispatch (Phase 6d)
214 adclib_hashpas "
217 adclib_createsalt "
218 adclib_hasholdpas "
269 types_check types.add / types_utf8 are separate and live
272 util_difftime moved to hub_dispatch
305 killuser never assigned; body is the commented block at 1120-1158
310 usercount never assigned — see below
322 isuseronline assigned at 1025, never called
336-339 _verify, _normal, _protocol, _identify never assigned; the four state tables live in hub_dispatch since Phase 6d
342 _G trap, see below
394 _cfg_nick_change never assigned/read. The nick_change FEATURE is live (cfg key read by cmd_nickchange / hub_inf_manager) — only hub.lua's stale cache slot is dead

_G removal trap (would be a #353-class boot crash): local _G (342) + _G = _G (525) must go together. Delete only the local and line 525 becomes a bare global write → the restricted env raises "attempt to write undeclared var" at hub load. Delete both or neither.

Riding along:

  • hub.lua:1025-1043 isuseronline() — 19 lines, defined, never called, deliberately commented out of the export at 1276. Strict superset of the live isnickonline/iscidonline/issidonline trio.
  • hub.lua:1120-1158 killuser — 39 commented-out lines whose only purpose is to justify a dead local slot.
  • hub.lua:310 + :1263hub.usercount is a nil export. Never assigned anywhere in the repo's history (git log -S"usercount = function" is empty), so { usercount = nil } never creates the key and pairs(hub) never sees it. Any plugin calling it gets "attempt to call a nil value". Not contract surface — a 15-year-old broken stub. 2 lines.

PR 2 — core/doc.lua, whole module (309 lines)

Commented out of _core (init.lua:121 --"doc",), its doc.export() call site commented out (init.lua:328), its own 19 doc.add(...) payload lines sit inside a --[[ ]] block so even if loaded it would document nothing, its output target docs/documentation.txt does not exist, zero use "doc" anywhere, not in SANDBOX_GLOBALS, not in PLUGIN_API.md. Untouched since the SourceForge migration. install(DIRECTORY core/) is wholesale so no build change needed.

Nuance: it is a deliberate disable (two separate comment-outs), so it is a maintainer call whether to keep it as a dormant design artifact. PLUGIN_API.md supersedes it.

Also: init.lua:202 --"test", references core/test.lua, which does not exist. 1 line.

PR 3 — core/server.lua (~180 lines; contains judgement calls)

  • :1019-1056 addclient + :247-290 wrapclient + :80 socket_tcpno in-tree caller (the source says so itself at :1025-1028); wrapclient is called only by addclient. server is not in SANDBOX_GLOBALS so it is not plugin-reachable. ~85 lines. Judgement call: this is the only outbound-TCP convenience wrapper; http_client does outbound via socket + server.addtimer instead. Two latent bugs die with it (ignored wrapclient return → handler = nil on the timeout path; unguarded failure(...) when listeners.failure is nil).
  • :914-955 the handler.starttls / deferred-TLS branch — starttls is never called. Inside it: needtls (516/918/950) written never read; handler.receivedata = handler.handshake / dispatchdata = handler.handshake (952-953) assign nil because handler.handshake does not exist; local shutdown (480) assigned 936/967, never called. ~45 lines. Judgement call: removes the STARTTLS seam. The no-op sub-defects inside are worth fixing regardless of what happens to the block.
  • :1185-1203 getsettings / changesettings — exported, zero callers anywhere. ~20 lines.
  • :1214-1216 stats — exported, zero callers; cascade: _readtraffic / _sendtraffic (163-164, incremented 708/788) are read only there. ~10 lines. Note handler.getstats (the per-connection twin, :544) is LIVE (cmd_userinfo.lua:212) — do not confuse them.
  • :633-636 handler.pattern + the dead pattern parameter threaded through wrapclient / wrapserver / wrapconnection / addclient and p.pattern at :1138no caller ever supplies it; vestige of Phase 8 replacing LuaSocket's "*l" with the iostream pipeline.
  • Unused accessors, zero call sites: handler.bufferlen (637-641), handler.id (531-533 + 369-371), handler.dispatch (534-536), handler.disconnect (537-539), handler.socket (401-403). ~20 lines. Live siblings that must not be touched: handler.ssl, handler.getsslinfo, handler.setlistener, the server-handler shutdown, handler.getstats.
  • Unused module locals: :125 stop (never assigned), :81 socket_bind, :66 table_remove (tick() calls table.remove directly), :92+:103-104 signal / signal_set / signal_get. 6 lines.
  • :249 / :300 — the disconnect binding in wrapclient / wrapserver is unused (wrapconnection's at :487 is live — do not touch).

PR 4 — dead unreachable branches (no-ops, but they are landmines)

All confirmed by executing the shapes under the hub's Lua 5.4.8, not by reading:

  • core/util.lua:927-930 + 948-951encode/decode's else branches are unreachable (local str = tostring( str ) above makes if str then always true, tostring(nil) is the truthy string "nil"). Worse: the dead branch reads tbl, undeclared in that scope — under the restricted env it would raise "attempt to read undeclared var" instead of returning (nil, err). Disarmed only by being unreachable. util.encode/decode themselves are contract (PLUGIN_API §5.3) — keep the functions, drop the branches.
  • core/util.lua:638-641, 671-674, 743-746, 747-750 — four dead tonumber-shadow type guards (formatseconds, formatbytes, difftime ×2). Same shape Fix type check for number in util.lua #439 "fixed": local t = tonumber( t ) + if not t then return guarantees a number, so the type check can never fire either way. Pure no-op deletions. Do not touch convertepochdate (791-793) / generatepass (702-703) — same shadow but correctly written and reachable.
  • core/adc.lua:353-354 — duplicate FC / TO keys in the SCH np table (same class as the Refactor bool function in adc.lua #438 bool dup). Both writes store the identical value, so unlike Refactor bool function in adc.lua #438 this is provably behaviour-neutral. Hygiene only — must not be sold as a fix.

PR 5 — small dead locals across the rest (~60 lines, trivially reviewable)

core/cfg.lua Phase-6c extraction leftovers (470, 488-489, 491-496, 528+540 — 11 lines; note removing the types_* set also orphans local types = use "types", which currently triggers types.lua's load — safe because cfg_defaults.lua:42 does the same, but say so in the PR body) · core/scripts.lua (31, 43, 56, 59, 87, 330 — 7 lines) · core/util.lua (90, 104, 130, 136, 140+204, 190 — 7 lines) · core/hub_dispatch.lua:68 · core/hub_user_object.lua:52-53, 293, 632 · core/hub_bot_object.lua:34, 94, 311 · core/hbri.lua:70+72 (+ the now-false hbri_test.lua:35-38 stub/comment) · core/adc.lua:45, 47, 59, 62, 82 + :1127-1128 (parse-local shadows of the module-level _contextsend/_contextdirect) · core/ratelimit.lua:35 · core/ipmatch.lua:69, 70, 72 · core/blocklist.lua:100 + :191 + agg.last_seen (235/254, written never read — LRU vestiges) · core/whitelist.lua:240-247 _rebuild_indices (copy-pasted from blocklist, whose only caller is a bulk_replace whitelist deliberately does not have) · core/http.lua:57/125-131/215 logsafe (+ its orphaned string_sub/string_gsub) · core/http_events.lua:68/363-364 _max_size, :322 types_str · core/http_router.lua:66, 73 · core/http_filter.lua:52 · core/cfg_secret.lua:66, 69, 70, 80 + is_active (264-266/320, whose only consumer is itself a dead local at cfg_users.lua:59) · core/cfg_users.lua:62, 69+74 · core/secrets.lua:52 · core/sysinfo.lua:34 · core/out.lua:16, 35 · core/types.lua:14, 15, 39, 40 · core/util.lua:535-539 (savetbl builds and sorts a tmp that is never read, then calls iterate(tbl) which rebuilds it — runs on every user.tbl save)

PR 6 — plugins (~57 lines)

Dead exports not in the PLUGIN_API §8 contract table: etc_report.lua:73-82+:91 broadcast() (whole function; 96 callers use report.send, zero use .broadcast) · etc_usercommands.lua:89 format · bot_opchat.lua:399 bot · cmd_ban.lua:1259 bans_path. Plus 17 dead local X = lang.Y or "..." locals and their now-orphaned lang keys (40 lines), and 2 never-looked-up lang keys (cmd_reg ucmd_passwort, etc_userlogininfo msg_ccpm_1/2/3).

Reported, deliberately NOT removed

  • CONTRACT / sandbox-reachable: usr_uptime.tbl, etc_trafficmanager.add, mmdb.MAX_FILE_SIZE, hmac.BLOCK_SIZE/sha256_bytes, whitelist.reload, util.handlebom, secrets.list_secret_keys, ipmatch.family/normalize, user.getblom, hub_bot_object.lua:252-256 bot.reply (the stub must stay — removing it turns a silent no-op into a nil-call crash for unseen plugins; only its 5 commented body lines are dead).
  • Vendored (merge-cost on dep bumps): luasocket/src/usocket.c:106-118 socket_select() — newly orphaned on POSIX by our own Hub event loop hits FD_SETSIZE=1024 Linux limit (select -> poll port) #310 patch; :42-73 the SOCKET_SELECT variant of socket_waitfd (macro never defined anywhere); luasec/src/luasocket/ — 5 uncompiled .c files, but its headers are LIVE (luasec/CMakeLists.txt:29-32 puts them on the include path and ssl.c includes them), a latent ODR trap worth its own issue.
  • Not dead, coverage gaps instead: http_events has no unit test at all despite carrying the long-poll deadline logic that already flaked (feat(cfg)!: flip kill_wrong_ips default from true to false #332/fix(http_events): use socket.gettime for long-poll deadline (#263) #334); etc_whitelist._list_filter_spec/_SOURCE_ENUM lack the assertions their etc_blocklist twins have.

Clean — no findings

Lua 5.1→5.4 migration leftovers: zero (Phase 3 left no compat scaffolding). Unused plugin helper functions: zero of 336. en/de lang-file key drift: zero. Plugins missing from cfg.tbl: zero. Dead cfg toggles: zero. Read-but-undefined cfg keys in-repo: zero. hub/hub.c: essentially clean (all 7 lua_register globals live, all 12 statics called). Dead cfg keys: no_cid_taken, ranks, owner_rank, use_ping, op_rank/admin_rank (cfg_defaults.lua 731/736/778/4872/768/773) — reachable via GET /v1/config, so PUT /v1/config/use_ping returns 200 and does nothing. Always-true validator: ssl_params (cfg_defaults.lua:4439) is the only one of 448 that never rejects — arguably deliberate (nested TLS table), report only.

Lessons from the audit itself (why every finding needs re-derivation)

Three agent calls were wrong, all caught by verifying against source:

  1. "Drop [-]? from cmd_ban's parser" — no-op. %S+ already matches -5 (verified in Lua). The gap was validation, not parsing.
  2. "core/hci.lua is a dead stub" — the opposite: live runtime state, and chasing it found hub_runtime: total runtime resets on every upgrade (mutable state lives in core/hci.lua) #445.
  3. My own first lang test asserted type(v) == "string"149 false positives of 151 hits (ucmd_menu*, month_name, pics are legitimately tables); my second enumerated via io.popen("ls") → passes vacuously under a native Windows Lua (cmd.exe has no ls).

Also: an audit agent overwrote the repo's real CMakeLists.txt with a 4-line zlib probe and left a b/ build dir behind. Restored from git; future fan-outs get read-only agents.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions