Skip to content

Commit

Permalink
Bug: yielding in '__close' mess up number of returns
Browse files Browse the repository at this point in the history
Yielding in a __close metamethod called when returning vararg results
changes the top and so messes up the number of returned values.
  • Loading branch information
roberto-ieru committed Apr 16, 2021
1 parent 5148954 commit 6812971
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 2 deletions.
2 changes: 1 addition & 1 deletion lstate.h
Expand Up @@ -165,7 +165,7 @@ typedef struct stringtable {
** - field 'nyield' is used only while a function is "doing" an
** yield (from the yield until the next resume);
** - field 'nres' is used only while closing tbc variables when
** returning from a C function;
** returning from a function;
** - field 'transferinfo' is used only during call/returnhooks,
** before the function starts or after it ends.
*/
Expand Down
12 changes: 11 additions & 1 deletion lvm.c
Expand Up @@ -847,10 +847,19 @@ void luaV_finishOp (lua_State *L) {
luaV_concat(L, total); /* concat them (may yield again) */
break;
}
case OP_CLOSE: case OP_RETURN: { /* yielded closing variables */
case OP_CLOSE: { /* yielded closing variables */
ci->u.l.savedpc--; /* repeat instruction to close other vars. */
break;
}
case OP_RETURN: { /* yielded closing variables */
StkId ra = base + GETARG_A(inst);
/* adjust top to signal correct number of returns, in case the
return is "up to top" ('isIT') */
L->top = ra + ci->u2.nres;
/* repeat instruction to close other vars. and complete the return */
ci->u.l.savedpc--;
break;
}
default: {
/* only these other opcodes can yield */
lua_assert(op == OP_TFORCALL || op == OP_CALL ||
Expand Down Expand Up @@ -1672,6 +1681,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
n = cast_int(L->top - ra); /* get what is available */
savepc(ci);
if (TESTARG_k(i)) { /* may there be open upvalues? */
ci->u2.nres = n; /* save number of returns */
if (L->top < ci->top)
L->top = ci->top;
luaF_close(L, base, CLOSEKTOP, 1);
Expand Down
59 changes: 59 additions & 0 deletions testes/locals.lua
Expand Up @@ -813,6 +813,65 @@ do
end


do
-- yielding inside closing metamethods while returning
-- (bug in 5.4.3)

local extrares -- result from extra yield (if any)

local function check (body, extra, ...)
local t = table.pack(...) -- expected returns
local co = coroutine.wrap(body)
if extra then
extrares = co() -- runs until first (extra) yield
end
local res = table.pack(co()) -- runs until yield inside '__close'
assert(res.n == 2 and res[2] == nil)
local res2 = table.pack(co()) -- runs until end of function
assert(res2.n == t.n)
for i = 1, #t do
if t[i] == "x" then
assert(res2[i] == res[1]) -- value that was closed
else
assert(res2[i] == t[i])
end
end
end

local function foo ()
local x <close> = func2close(coroutine.yield)
local extra <close> = func2close(function (self)
assert(self == extrares)
coroutine.yield(100)
end)
extrares = extra
return table.unpack{10, x, 30}
end
check(foo, true, 10, "x", 30)
assert(extrares == 100)

local function foo ()
local x <close> = func2close(coroutine.yield)
return
end
check(foo, false)

local function foo ()
local x <close> = func2close(coroutine.yield)
local y, z = 20, 30
return x
end
check(foo, false, "x")

local function foo ()
local x <close> = func2close(coroutine.yield)
local extra <close> = func2close(coroutine.yield)
return table.unpack({}, 1, 100) -- 100 nils
end
check(foo, true, table.unpack({}, 1, 100))

end

do
-- yielding inside closing metamethods after an error

Expand Down

0 comments on commit 6812971

Please sign in to comment.