Bug: Issues with write barrier for __newindex

In 'luaV_finishset', there is an update on a table that is a field on
another table. If the first table is the same as the one with the field
(e.g., after 't.__newindex = t'), the update can change the value on
that field (e.g., there may be a collision and the field is moved, or
the field being updated is '__newindex' itself). After that, the
barrier is called with the table stored in that field, which is not
the correct table anymore.
This commit is contained in:
Roberto I
2026-07-12 14:57:55 -03:00
parent bc4bbcef65
commit b996f8fd1b
2 changed files with 24 additions and 6 deletions

18
lvm.c
View File

@@ -360,13 +360,19 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key,
luaT_callTM(L, tm, t, key, val);
return;
}
t = tm; /* else repeat assignment over 'tm' */
luaV_fastset(t, key, val, hres, luaH_pset);
if (hres == HOK) {
luaV_finishfastset(L, t, val);
return; /* done */
t = tm; /* else must repeat assignment over 'tm' */
/* do the equivalent to 'luaV_fastset', but saving 'h' */
if (!ttistable(t))
hres = HNOTATABLE;
else {
Table *h = hvalue(t); /* next call can change the value at 't' */
hres = luaH_pset(h, key, val);
if (hres == HOK) {
luaC_barrierback(L, obj2gco(h), val); /* luaV_finishfastset */
return; /* done */
}
}
/* else 'return luaV_finishset(L, t, key, val, slot)' (loop) */
/* else 'return luaV_finishset(L, t, key, val, hres)' (loop) */
}
luaG_runerror(L, "'__newindex' chain too long; possible loop");
}

View File

@@ -390,6 +390,18 @@ do
for i=1, 10 do t[i] = 1 end
end
do -- bug since 5.4
local parent = {}
parent.__newindex = parent
collectgarbage()
local child = setmetatable({}, parent)
child.__newindex = {x = "hello"}
collectgarbage("step")
assert(parent.__newindex.x == "hello")
end
-- concat metamethod x numbers (bug in 5.1.1)
c = {}
local x