From 77c0455e351be054a1429ab441b8f81e8dee7c7e Mon Sep 17 00:00:00 2001 From: mjodheim <110968830+mjodheim@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:59:16 +0200 Subject: [PATCH 1/3] fix: nil-guard UpdateRangeList and UnitInSpellsRange UpdateRangeList(db) can be called with a nil db, and UnitInSpellsRange reads list[which] which may be nil before the range list is built; both then index/ iterate nil and error. Return early in those cases. Co-Authored-By: Claude Opus 4.8 --- ElvUI/Game/Shared/Modules/UnitFrames/Elements/Range.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ElvUI/Game/Shared/Modules/UnitFrames/Elements/Range.lua b/ElvUI/Game/Shared/Modules/UnitFrames/Elements/Range.lua index b92ce30492..e06d416880 100644 --- a/ElvUI/Game/Shared/Modules/UnitFrames/Elements/Range.lua +++ b/ElvUI/Game/Shared/Modules/UnitFrames/Elements/Range.lua @@ -26,6 +26,8 @@ local list = {} UF.RangeSpells = list function UF:UpdateRangeList(db) + if not db then return {} end + local spells = {} for spell, value in next, db do if value then @@ -78,6 +80,8 @@ end function UF:UnitInSpellsRange(unit, which) local spells = list[which] + if not spells then return nil end + local range = (not next(spells) and 1) or UF:UnitSpellRange(unit, spells) if (not range or range == 1) and not InCombatLockdown() then From 31947afbd549d036f79524b9d1996de22b005fe7 Mon Sep 17 00:00:00 2001 From: mjodheim <110968830+mjodheim@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:59:16 +0200 Subject: [PATCH 2/3] fix: avoid repeated pet spell-load callbacks on the pet bar Cache the loaded spellID per pet button and cancel/replace the ContinueWithCancelOnSpellLoad callback only when it actually changes, instead of registering a new callback on every UpdatePet. Prevents stacked callbacks and stale tooltipSubtext. Co-Authored-By: Claude Opus 4.8 --- .../Game/Shared/Modules/ActionBars/PetBar.lua | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/ElvUI/Game/Shared/Modules/ActionBars/PetBar.lua b/ElvUI/Game/Shared/Modules/ActionBars/PetBar.lua index d8ece95db4..6ca4a63291 100644 --- a/ElvUI/Game/Shared/Modules/ActionBars/PetBar.lua +++ b/ElvUI/Game/Shared/Modules/ActionBars/PetBar.lua @@ -28,6 +28,16 @@ local bar = CreateFrame('Frame', 'ElvUI_BarPet', E.UIParent, 'SecureHandlerState bar:SetFrameStrata('LOW') bar.buttons = {} +local function ClearPetButtonSpellData(button) + if button.spellDataLoadedCancelFunc then + button.spellDataLoadedCancelFunc() + button.spellDataLoadedCancelFunc = nil + end + + button.spellDataLoadedSpellID = nil + button.tooltipSubtext = nil +end + function AB:UpdatePet(event, unit) if (event == 'UNIT_FLAGS' and unit ~= 'pet') or (event == 'UNIT_PET' and unit ~= 'player') then return end @@ -54,11 +64,23 @@ function AB:UpdatePet(event, unit) button.tooltipName = _G[name] end - if spellID then + if spellID and button.spellDataLoadedSpellID ~= spellID then + ClearPetButtonSpellData(button) + button.spellDataLoadedSpellID = spellID + local spell = _G.Spell:CreateFromSpellID(spellID) - button.spellDataLoadedCancelFunc = spell:ContinueWithCancelOnSpellLoad(function() - button.tooltipSubtext = spell:GetSpellSubtext() + local spellDataLoaded + local cancelFunc = spell:ContinueWithCancelOnSpellLoad(function() + if button.spellDataLoadedSpellID == spellID then + button.tooltipSubtext = spell:GetSpellSubtext() + button.spellDataLoadedCancelFunc = nil + end + + spellDataLoaded = true end) + button.spellDataLoadedCancelFunc = not spellDataLoaded and cancelFunc or nil + elseif not spellID and button.spellDataLoadedSpellID then + ClearPetButtonSpellData(button) end if isActive and name ~= 'PET_ACTION_FOLLOW' then @@ -227,10 +249,7 @@ end function AB:PetBar_OnHide() for _, button in ipairs(bar.buttons) do - if button.spellDataLoadedCancelFunc then - button.spellDataLoadedCancelFunc() - button.spellDataLoadedCancelFunc = nil - end + ClearPetButtonSpellData(button) end end From b53f2c77b306e27a2eaa808da4c38bbd0c8b6523 Mon Sep 17 00:00:00 2001 From: mjodheim <110968830+mjodheim@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:59:16 +0200 Subject: [PATCH 3/3] fix: nil-safe aura iteration in oUF plugins (AuraBars, AuraWatch, AuraHighlight) AuraFiltered[filter][unit] can be nil (no auras of that filter yet for the unit); next(nil) then errors. Bail out early when it's nil. For AuraBars and AuraWatch also snapshot the filtered set into a local array before iterating, so callbacks that mutate AuraFiltered during the loop can't break next()'s traversal. Co-Authored-By: Claude Opus 4.8 --- .../Game/Shared/oUF_Plugins/oUF_AuraBars.lua | 24 +++++++++++++++---- .../Shared/oUF_Plugins/oUF_AuraHighlight.lua | 2 ++ .../Game/Shared/oUF_Plugins/oUF_AuraWatch.lua | 22 +++++++++++++---- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraBars.lua b/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraBars.lua index 8af573d68d..6960fe995b 100644 --- a/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraBars.lua +++ b/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraBars.lua @@ -247,13 +247,28 @@ end local function FilterBars(frame, element, unit, filter, limit, isDebuff, offset, dontHide) if(not offset) then offset = 0 end + + local unitAuraFiltered = AuraFiltered[filter][unit] + if not unitAuraFiltered then return 0, 0 end + + local temp = {} + local count = 0 + for auraInstanceID, aura in next, unitAuraFiltered do + count = count + 1 + temp[count] = auraInstanceID + count = count + 1 + temp[count] = aura + end + local visible = 0 local hidden = 0 - local index = 1 - local unitAuraFiltered = AuraFiltered[filter][unit] - local auraInstanceID, aura = next(unitAuraFiltered) - while aura and (visible < limit) do + for i = 1, count, 2 do + if visible >= limit then break end + + local auraInstanceID = temp[i] + local aura = temp[i+1] + local result = AuraUpdate(frame, element, unit, aura, index, offset, filter, isDebuff, visible) if result == VISIBLE then visible = visible + 1 @@ -262,7 +277,6 @@ local function FilterBars(frame, element, unit, filter, limit, isDebuff, offset, end index = index + 1 - auraInstanceID, aura = next(unitAuraFiltered, auraInstanceID) end if(not dontHide) then diff --git a/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraHighlight.lua b/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraHighlight.lua index 571145eb29..15c475f674 100644 --- a/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraHighlight.lua +++ b/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraHighlight.lua @@ -45,6 +45,8 @@ end local function Looper(unit, filter, check, list, func) local unitAuraFiltered = AuraFiltered[filter][unit] + if not unitAuraFiltered then return end -- next(nil) would error + local auraInstanceID, aura = next(unitAuraFiltered) while aura do local name, icon, count, auraType, duration, expiration, source, isStealable, nameplateShowPersonal, spellID = oUF:UnpackAuraData(aura) diff --git a/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraWatch.lua b/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraWatch.lua index 7f1d6d5b04..3ade08df30 100644 --- a/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraWatch.lua +++ b/ElvUI_Libraries/Game/Shared/oUF_Plugins/oUF_AuraWatch.lua @@ -224,10 +224,25 @@ end local function FilterIcons(element, unit, filter, limit, isDebuff, offset, dontHide) if not offset then offset = 0 end - local index, visible, hidden = 1, 0, 0 local unitAuraFiltered = AuraFiltered[filter][unit] - local auraInstanceID, aura = next(unitAuraFiltered) - while aura and (visible < limit) do + if not unitAuraFiltered then return 0, 0 end + + local temp = {} + local count = 0 + for auraInstanceID, aura in next, unitAuraFiltered do + count = count + 1 + temp[count] = auraInstanceID + count = count + 1 + temp[count] = aura + end + + local index, visible, hidden = 1, 0, 0 + for i = 1, count, 2 do + if visible >= limit then break end + + local auraInstanceID = temp[i] + local aura = temp[i+1] + local result = UpdateIcon(element, unit, aura, index, offset, filter, isDebuff, visible) if result == VISIBLE then visible = visible + 1 @@ -236,7 +251,6 @@ local function FilterIcons(element, unit, filter, limit, isDebuff, offset, dontH end index = index + 1 - auraInstanceID, aura = next(unitAuraFiltered, auraInstanceID) end if not dontHide then