From b996f8fd1be7fb711cc6f754a31a1c87d2c2fd9b Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sun, 12 Jul 2026 14:57:55 -0300 Subject: [PATCH] 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. --- lvm.c | 18 ++++++++++++------ testes/events.lua | 12 ++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lvm.c b/lvm.c index f9e87b61..f83d47d1 100644 --- a/lvm.c +++ b/lvm.c @@ -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"); } diff --git a/testes/events.lua b/testes/events.lua index 7e434b1f..fa9966ab 100644 --- a/testes/events.lua +++ b/testes/events.lua @@ -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