Guard recon/recoff against nil Spellups["RT"][rc] to prevent state corruption#103
Open
rodarvus wants to merge 1 commit into
Open
Guard recon/recoff against nil Spellups["RT"][rc] to prevent state corruption#103rodarvus wants to merge 1 commit into
rodarvus wants to merge 1 commit into
Conversation
{recon}sn,timer and {recoff}sn carry a recovery GROUP number, not a
skill number. Spellups["RT"][rc] is only populated by SlistUpdate for
recovery groups where the player has a practiced spell anchoring them
(pc > 1). If the player casts a spell whose recovery group has no
practiced anchor in their slist -- e.g. an attack spell they just
learned, or any group missing because slist was interrupted -- both
recon() and recoff() dereference Spellups["RT"][sn] on a nil and
crash.
The crash is silently destructive: recon() writes
Spellups["RT"]["affectedrecovery"][sn] = tm BEFORE the metadata read,
so the stale entry is committed to state. recoff() crashes on the
very first line, so the cleanup at the end -- the only place that
clears affectedrecovery[sn] -- never runs. The stale entry then
persists to disk via OnPluginSaveState, survives across MUSHclient
restarts, and silently blocks all future autocasts for any spell in
that recovery group, because affoff()'s recovery check at line ~1158
sees the group as still in cooldown. Recovery requires manually
deleting the state file.
Guard both reads against a nil Spellups["RT"][rc]:
- recon(): write the affectedrecovery state, then log either with
the entry metadata or with a generic "unknown group" message.
- recoff(): if the entry is nil, still clear affectedrecovery[sn]
(the cleanup that the old code never reached on this path) and
return early. Otherwise behave exactly as before.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
{recon}sn,timerand{recoff}sncarry a recovery group number (thercfromslist recoveries), not a skill number.Spellups["RT"][rc]is only populated bySlistUpdatefor recovery groups where the player has a practiced spell anchor (rc ~= -1 and pc > 1atSpellupRecast/Hadar_Spellups.xml:984). If the player casts a spell whose recovery group has no practiced anchor — e.g. an attack spell they only just learned, or any group missing because slist was interrupted — bothrecon()andrecoff()dereferenceSpellups["RT"][sn]on a nil and crash.The crash is silently destructive:
recon()writesSpellups["RT"]["affectedrecovery"][sn] = tmbefore the metadata read. The stale entry is committed to state.OnPluginSaveStateserializes the partially-populated table to disk.{recoff}snfires.recoff()'s very first line is the same nil-deref. The cleanup at the end — the only place that clearsaffectedrecovery[sn]— never runs.OnPluginEnableit's loaded back from disk. Across MUSHclient restarts.{affoff}for any spell in that recovery group hits the check at line ~1158, sees the group still "in cooldown", and silently skips the recast. The user sees: certain spellups stop autocasting and never recover, even after/reloador restart.This was reported in the wild by a user who hit it casting hex of entropy (a Necromancer subclass attack spell in recovery group Hex) shortly after learning it. With Hex of Entropy at low practice and no other Hex-group spell to anchor
Spellups["RT"][40], the crash chain above fires on the very first cast.Fix
Guard both reads:
recon— writeaffectedrecovery(unchanged), then look upSpellups["RT"][sn]once; log either the spell name or a generic "unknown recovery group" debug line. No state-mutating path changes.recoff— look upSpellups["RT"][sn]once at the top. If nil, still clearaffectedrecovery[sn](the cleanup the old code never reached on this path) and early-return. Otherwise behave exactly as before.The unknown-group case becomes a safe no-op + debug log instead of a crash + corrupted state.
Diff overview
Test plan
Spellups["RT"][rc]is only written bySlistUpdate:986 and only whenpc > 1. Any{recon}for a group without a practiced anchor hits this path.RT[sn]populated) is unchanged. The same metadata is read, the same downstream logic runs.affectedrecoverytable).🤖 Generated with Claude Code