wipe: reset 5.0's remembered table size, so a refilled list counts from 1

`wipe` cleared elements with a bare `pairs` loop, which leaves Lua 5.0's remembered table size
stale (the `getsizes` side store `luaL_setn` writes). TurtleWoW's LFT fills its group list, wipes
it on the next `S2C_GROUPS_LIST_UPDATE` start, and refills; the refill's `getn` answered the old
length, so `LFT_UpdateGroupsList` read nil entries and `CustomSort` raised on `value.category`.
Take `Globals.lua:126`'s body instead: a `tremove` walk down from `getn` resets the size, then
clear the non-array keys. The test now pins the return and the size reset.
This commit is contained in:
chairmanpow
2026-10-03 16:23:39 +01:00
parent bd0e24e9a9
commit 6eaba32006
2 changed files with 36 additions and 16 deletions
+14 -5
View File
@@ -311,11 +311,20 @@ do
local _gsub = string.gsub
function wipe(t)
-- `t = t or {}` and a returned table: TurtleWoW's `Blizzard_RaidUI.lua:77,116` clears its
-- `RAID_SUBGROUP_LISTS` with `t[i] = wipe(t[i])` and then `tinsert`s into it, so `wipe` of
-- a never-populated entry must hand back a table to insert into.
t = t or {}
for k in pairs(t) do t[k] = nil end
-- `Globals.lua:126`'s body: the `tremove` walk down from `getn` resets 5.0's remembered
-- table size, which a bare `pairs` clear leaves stale — a list refilled after `wipe` would
-- then count the old length and read holes (LFT's group list). A nil (or non-table) hands
-- back a table, as TurtleWoW's `Blizzard_RaidUI.lua:77,116` clears `RAID_SUBGROUP_LISTS`
-- with `t[i] = wipe(t[i])` and then `tinsert`s into it.
if type(t) ~= "table" then
return {}
end
for i = getn(t), 1, -1 do
tremove(t, i)
end
for k in pairs(t) do
rawset(t, k, nil)
end
return t
end
+22 -11
View File
@@ -71,29 +71,40 @@ fn stdlib_aliases_and_helpers() {
}
/// `wipe` empties the table IN PLACE (the reference's `UIFrameFade_CreateInfo` reuses the same
/// `fadeInfo` table across calls) and returns nothing.
/// `fadeInfo` table across calls) and hands the same table back, as TurtleWoW's `Globals.lua:126`
/// does; the `tremove` walk resets 5.0's remembered size.
#[test]
fn wipe_empties_in_place_and_returns_nothing() {
fn wipe_empties_in_place_and_returns_the_table() {
let s = script();
assert_eq!(
s.eval::<i64>(
r#"
local t = { "a", 2, [ "k" ] = true }
local held = t
wipe(t)
return getn(t) == 0 and t.k == nil and held == t and 1 or 0
local back = wipe(t)
return getn(t) == 0 and t.k == nil and held == t and back == t and 1 or 0
"#
)
.unwrap(),
1,
"wipe must empty the caller's own table, not replace it, and must leave no keys"
"wipe must empty the caller's own table, leave no keys, and return it"
);
// The remembered size resets, so a list refilled after `wipe` counts from 1: a stale size is
// the nil-index crash TurtleWoW's LFT group list hit on its second fill.
assert_eq!(
s.eval::<i64>(
r#"
local t = {}
tinsert(t, "a"); tinsert(t, "b"); tinsert(t, "c")
wipe(t)
tinsert(t, "x")
return getn(t)
"#
)
.unwrap(),
1,
"a refill after wipe must count from 1, not the wiped length"
);
// 1.12's VM has no `select` (a 5.1 addition), so the arity is read by packing into locals:
// only a no-value return leaves both nil.
let returns_nothing: bool = s
.eval("local a, b = wipe({1}) return a == nil and b == nil")
.unwrap();
assert!(returns_nothing, "wipe returns no values");
}
/// `strsplit` splits on PLAIN text, not Lua patterns: a `.` is a full stop, not "any character".