Skip to content

Commit 52021d2

Browse files
committed
Fix serialization of LLTimers and LLEvents, add test
1 parent 2eb9334 commit 52021d2

File tree

2 files changed

+60
-44
lines changed

2 files changed

+60
-44
lines changed

VM/src/ares.cpp

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,68 +1401,64 @@ static void u_userdata(Info *info) { /* ... */
14011401
}
14021402
case UTAG_LLEVENTS:
14031403
{
1404-
// Because we have an inner, wrapped table reference we need to reserve
1405-
// the idx for the outer event handler first, since we saw it first.
1406-
int reference = allocate_ref_idx(info);
1407-
1408-
unpersist(info); /* ... handlers_tab */
1409-
eris_checktype(info, -1, LUA_TTABLE);
1410-
// We need to add a ref to the table so it stays alive as long as LLEvents
1411-
int tab_ref = lua_ref(info->L, -1);
1412-
LuaTable *listeners_tab = hvalue(luaA_toobject(info->L, -1));
1413-
lua_pop(info->L, 1); /* ... */
1414-
1404+
// Create userdata with safe initial values FIRST to handle cycles
14151405
auto *llevents = (lua_LLEvents*)lua_newuserdatataggedwithmetatable(
14161406
info->L,
14171407
sizeof(lua_LLEvents),
14181408
UTAG_LLEVENTS
14191409
);
14201410
/* ... llevents */
1421-
llevents->listeners_tab_ref = tab_ref;
1422-
llevents->listeners_tab = listeners_tab;
1411+
llevents->listeners_tab_ref = -1;
1412+
llevents->listeners_tab = nullptr;
1413+
1414+
// Register immediately to handle cycles (listeners_tab may reference this)
1415+
registerobject(info);
1416+
1417+
// Unpersist listeners_tab and store ref IMMEDIATELY to prevent leaks
1418+
unpersist(info); /* ... llevents handlers_tab */
1419+
eris_checktype(info, -1, LUA_TTABLE);
1420+
llevents->listeners_tab_ref = lua_ref(info->L, -1);
1421+
llevents->listeners_tab = hvalue(luaA_toobject(info->L, -1));
1422+
lua_pop(info->L, 1); /* ... llevents */
14231423

1424-
// Manually put the LLEvents in the references table at the correct reference index
1425-
lua_pushvalue(info->L, -1); /* perms reftbl ... obj obj */
1426-
lua_rawseti(info->L, REFTIDX, reference); /* perms reftbl ... obj */
14271424
break;
14281425
}
14291426
case UTAG_LLTIMERS:
14301427
{
1431-
// Because we have an inner, wrapped table reference we need to reserve
1432-
// the idx for the outer timer manager first, since we saw it first.
1433-
int reference = allocate_ref_idx(info);
1428+
// Create userdata with safe initial values FIRST to handle cycles
1429+
auto *lltimers = (lua_LLTimers*)lua_newuserdatataggedwithmetatable(
1430+
info->L,
1431+
sizeof(lua_LLTimers),
1432+
UTAG_LLTIMERS
1433+
);
1434+
/* ... lltimers */
1435+
lltimers->timers_tab_ref = -1;
1436+
lltimers->llevents_ref = -1;
1437+
lltimers->timer_wrapper_ref = -1;
1438+
lltimers->timers_tab = nullptr;
14341439

1435-
unpersist(info); /* ... timers_tab */
1440+
// Register immediately to handle cycles (timer_wrapper has LLTimers as upvalue)
1441+
registerobject(info);
1442+
1443+
// Unpersist timers_tab and store ref IMMEDIATELY to prevent leaks
1444+
unpersist(info); /* ... lltimers timers_tab */
14361445
eris_checktype(info, -1, LUA_TTABLE);
1437-
// We need to add a ref to the table so it stays alive as long as LLTimers
1438-
int tab_ref = lua_ref(info->L, -1);
1439-
LuaTable *timers_tab = hvalue(luaA_toobject(info->L, -1));
1440-
lua_pop(info->L, 1); /* ... */
1446+
lltimers->timers_tab_ref = lua_ref(info->L, -1);
1447+
lltimers->timers_tab = hvalue(luaA_toobject(info->L, -1));
1448+
lua_pop(info->L, 1); /* ... lltimers */
14411449

1442-
unpersist(info); /* ... llevents */
1450+
// Unpersist llevents and store ref IMMEDIATELY
1451+
unpersist(info); /* ... lltimers llevents */
14431452
eris_checkuserdatatag(info, -1, UTAG_LLEVENTS);
1444-
int llevents_ref = lua_ref(info->L, -1);
1445-
lua_pop(info->L, 1); /* ... */
1453+
lltimers->llevents_ref = lua_ref(info->L, -1);
1454+
lua_pop(info->L, 1); /* ... lltimers */
14461455

1447-
unpersist(info); /* ... timer_wrapper */
1456+
// Unpersist timer_wrapper and store ref IMMEDIATELY
1457+
unpersist(info); /* ... lltimers timer_wrapper */
14481458
eris_checktype(info, -1, LUA_TFUNCTION);
1449-
int timer_wrapper_ref = lua_ref(info->L, -1);
1450-
lua_pop(info->L, 1); /* ... */
1459+
lltimers->timer_wrapper_ref = lua_ref(info->L, -1);
1460+
lua_pop(info->L, 1); /* ... lltimers */
14511461

1452-
auto *lltimers = (lua_LLTimers*)lua_newuserdatataggedwithmetatable(
1453-
info->L,
1454-
sizeof(lua_LLTimers),
1455-
UTAG_LLTIMERS
1456-
);
1457-
/* ... lltimers */
1458-
lltimers->timers_tab_ref = tab_ref;
1459-
lltimers->timers_tab = timers_tab;
1460-
lltimers->llevents_ref = llevents_ref;
1461-
lltimers->timer_wrapper_ref = timer_wrapper_ref;
1462-
1463-
// Manually put the LLTimers in the references table at the correct reference index
1464-
lua_pushvalue(info->L, -1); /* perms reftbl ... obj obj */
1465-
lua_rawseti(info->L, REFTIDX, reference); /* perms reftbl ... obj */
14661462
break;
14671463
}
14681464
default:

tests/conformance/lltimers.lua

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,24 @@ incrementclock(0.3)
345345
LLEvents:_handleEvent('timer')
346346
assert(callable_count == 2)
347347

348+
-- make sure serialization still works
349+
setclock(0)
350+
-- error() is the poor man's long-return.
351+
local function throw_error() error("called!") end
352+
LLTimers:on(0.5, throw_error)
353+
354+
-- In reality you wouldn't give users primitives to clone these, but just for testing!
355+
local timers_clone = ares.unpersist(ares.persist(LLTimers))
356+
setclock(0.6)
357+
358+
assert_errors(function() timers_clone:_tick() end, "called!")
359+
assert_errors(function() LLTimers:_tick() end, "called!")
360+
361+
incrementclock(0.6)
362+
363+
LLTimers:off(throw_error)
364+
-- Only one of them now has the problematic handler
365+
LLTimers:_tick()
366+
assert_errors(function() timers_clone:_tick() end, "called!")
367+
348368
return "OK"

0 commit comments

Comments
 (0)