Skip to content

Commit fed1a35

Browse files
committed
Treat metamethods as non-yieldable in table.sort and table.find
1 parent 84e21fa commit fed1a35

3 files changed

Lines changed: 32 additions & 6 deletions

File tree

VM/src/ltablib.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,10 @@ static constexpr int SORT_YIELD_BUDGET = 512;
422422
else \
423423
{ \
424424
int _sa = t->sizearray; \
425+
/* ServerLua: guard nCcalls so __lt metamethods can't yield from this context */ \
426+
++L->nCcalls; \
425427
cmp_var = luaV_lessthan(L, &t->array[i_idx], &t->array[j_idx]); \
428+
--L->nCcalls; \
426429
if (t->sizearray != _sa) \
427430
luaL_error(L, "table modified during sorting"); \
428431
}
@@ -819,7 +822,11 @@ DEFINE_YIELDABLE(tfind, 0)
819822

820823
StkId v = L->base + 2;
821824

822-
if (equalobj(L, v, e))
825+
// ServerLua: guard nCcalls so __eq metamethods can't yield from this context
826+
++L->nCcalls;
827+
bool eq = equalobj(L, v, e);
828+
--L->nCcalls;
829+
if (eq)
823830
{
824831
lua_pushinteger(L, i);
825832
return 1;

tests/Conformance.test.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,13 +1480,11 @@ TEST_CASE("StdlibYield")
14801480
{
14811481
if (pmTimingMode)
14821482
{
1483-
if (gc >= 0 || gc == LUA_INTERRUPT_METAMETHOD)
1483+
// Skip GC interrupts and any non-yieldable context (e.g. inside
1484+
// a metamethod called from a yieldable function's nCcalls guard).
1485+
if (gc >= 0 || luaSL_may_interrupt(L) != YieldableStatus::OK)
14841486
return;
14851487

1486-
// All non-GC, non-metamethod interrupt points should be yieldable —
1487-
// validates the VM lets the scheduler preempt here.
1488-
LUAU_ASSERT(luaSL_may_interrupt(L) == YieldableStatus::OK);
1489-
14901488
double now = lua_cputime();
14911489
if (pmLastTimestamp > 0)
14921490
{

tests/conformance/stdlib_yield.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,27 @@ assert(not pcall(string.find, "a", string.rep("()", 33)))
189189
-- pattern too complex: 201 quantifiers exceed LUAI_MAXCCALLS (200) backtrack frames
190190
assert(not pcall(string.find, string.rep("a", 300), string.rep("a?", 201)))
191191

192+
-- Yielding from __lt during default-comparator table.sort should error cleanly,
193+
-- not corrupt the CI stack. The nCcalls guard in SORT_CMP blocks the yield.
194+
assert(not pcall(function()
195+
local mt = { __lt = function(a, b)
196+
coroutine.yield()
197+
return a[1] < b[1]
198+
end }
199+
local t = { setmetatable({2}, mt), setmetatable({1}, mt) }
200+
table.sort(t)
201+
end))
202+
203+
-- Same for __eq during table.find.
204+
assert(not pcall(function()
205+
local mt = { __eq = function(a, b)
206+
coroutine.yield()
207+
return a[1] == b[1]
208+
end }
209+
local t = { setmetatable({1}, mt), setmetatable({2}, mt) }
210+
table.find(t, setmetatable({2}, mt))
211+
end))
212+
192213
-- Enable interrupt-driven yields from YIELD_CHECK for remaining tests.
193214
-- The interrupt handler (installed by C++ test fixture) calls lua_yield
194215
-- on every YIELD_CHECK hit, and we Ares round-trip on each yield.

0 commit comments

Comments
 (0)