Allow yields inside '__close' metamethods
Initial implementation to allow yields inside '__close' metamethods.
This current version still does not allow a '__close' metamethod
to yield when called due to an error. '__close' metamethods from
C functions also are not allowed to yield.
diff --git a/lapi.c b/lapi.c
index 0f0e31a..3583e9c 100644
--- a/lapi.c
+++ b/lapi.c
@@ -189,7 +189,7 @@
}
#if defined(LUA_COMPAT_5_4_0)
if (diff < 0 && hastocloseCfunc(ci->nresults))
- luaF_close(L, L->top + diff, CLOSEKTOP);
+ luaF_close(L, L->top + diff, CLOSEKTOP, 0);
#endif
L->top += diff;
api_check(L, L->openupval == NULL || uplevel(L->openupval) < L->top,
@@ -205,7 +205,7 @@
api_check(L, hastocloseCfunc(L->ci->nresults) && L->openupval != NULL &&
uplevel(L->openupval) == level,
"no variable to close at given level");
- luaF_close(L, level, CLOSEKTOP);
+ luaF_close(L, level, CLOSEKTOP, 0);
setnilvalue(s2v(level));
lua_unlock(L);
}
diff --git a/ldo.c b/ldo.c
index ba0c93b..aa159cf 100644
--- a/ldo.c
+++ b/ldo.c
@@ -406,7 +406,7 @@
default: /* multiple results (or to-be-closed variables) */
if (hastocloseCfunc(wanted)) { /* to-be-closed variables? */
ptrdiff_t savedres = savestack(L, res);
- luaF_close(L, res, CLOSEKTOP); /* may change the stack */
+ luaF_close(L, res, CLOSEKTOP, 0); /* may change the stack */
res = restorestack(L, savedres);
wanted = codeNresults(wanted); /* correct value */
if (wanted == LUA_MULTRET)
@@ -647,7 +647,7 @@
/* "finish" luaD_pcall */
L->ci = ci;
L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */
- luaF_close(L, func, status); /* may change the stack */
+ luaF_close(L, func, status, 0); /* may change the stack */
func = restorestack(L, ci->u2.funcidx);
luaD_seterrorobj(L, status, func);
luaD_shrinkstack(L); /* restore stack size in case of overflow */
@@ -803,7 +803,7 @@
*/
static void closepaux (lua_State *L, void *ud) {
struct CloseP *pcl = cast(struct CloseP *, ud);
- luaF_close(L, pcl->level, pcl->status);
+ luaF_close(L, pcl->level, pcl->status, 0);
}
diff --git a/lfunc.c b/lfunc.c
index a8030af..13e44d4 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -101,17 +101,21 @@
/*
-** Call closing method for object 'obj' with error message 'err'.
+** Call closing method for object 'obj' with error message 'err'. The
+** boolean 'yy' controls whether the call is yieldable.
** (This function assumes EXTRA_STACK.)
*/
-static void callclosemethod (lua_State *L, TValue *obj, TValue *err) {
+static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) {
StkId top = L->top;
const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE);
setobj2s(L, top, tm); /* will call metamethod... */
setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */
setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */
L->top = top + 3; /* add function and arguments */
- luaD_callnoyield(L, top, 0); /* call method */
+ if (yy)
+ luaD_call(L, top, 0);
+ else
+ luaD_callnoyield(L, top, 0);
}
@@ -137,7 +141,7 @@
** the 'level' of the upvalue being closed, as everything after that
** won't be used again.
*/
-static void prepcallclosemth (lua_State *L, StkId level, int status) {
+static void prepcallclosemth (lua_State *L, StkId level, int status, int yy) {
TValue *uv = s2v(level); /* value being closed */
TValue *errobj;
if (status == CLOSEKTOP)
@@ -146,7 +150,7 @@
errobj = s2v(level + 1); /* error object goes after 'uv' */
luaD_seterrorobj(L, status, level + 1); /* set error object */
}
- callclosemethod(L, uv, errobj);
+ callclosemethod(L, uv, errobj, yy);
}
@@ -174,7 +178,7 @@
if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */
lua_assert(status == LUA_ERRMEM);
luaD_seterrorobj(L, LUA_ERRMEM, level + 1); /* save error message */
- callclosemethod(L, s2v(level), s2v(level + 1));
+ callclosemethod(L, s2v(level), s2v(level + 1), 0);
luaD_throw(L, LUA_ERRMEM); /* throw memory error */
}
}
@@ -194,7 +198,7 @@
** to NOCLOSINGMETH closes upvalues without running any __close
** metamethods.
*/
-void luaF_close (lua_State *L, StkId level, int status) {
+void luaF_close (lua_State *L, StkId level, int status, int yy) {
UpVal *uv;
StkId upl; /* stack index pointed by 'uv' */
while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) {
@@ -209,7 +213,7 @@
}
if (uv->tbc && status != NOCLOSINGMETH) {
ptrdiff_t levelrel = savestack(L, level);
- prepcallclosemth(L, upl, status); /* may change the stack */
+ prepcallclosemth(L, upl, status, yy); /* may change the stack */
level = restorestack(L, levelrel);
}
}
diff --git a/lfunc.h b/lfunc.h
index 40de463..2e6df53 100644
--- a/lfunc.h
+++ b/lfunc.h
@@ -59,7 +59,7 @@
LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl);
LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);
LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level);
-LUAI_FUNC void luaF_close (lua_State *L, StkId level, int status);
+LUAI_FUNC void luaF_close (lua_State *L, StkId level, int status, int yy);
LUAI_FUNC void luaF_unlinkupval (UpVal *uv);
LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);
LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number,
diff --git a/lstate.c b/lstate.c
index a6ef82a..92ccbf9 100644
--- a/lstate.c
+++ b/lstate.c
@@ -313,7 +313,7 @@
void luaE_freethread (lua_State *L, lua_State *L1) {
LX *l = fromstate(L1);
- luaF_close(L1, L1->stack, NOCLOSINGMETH); /* close all upvalues */
+ luaF_close(L1, L1->stack, NOCLOSINGMETH, 0); /* close all upvalues */
lua_assert(L1->openupval == NULL);
luai_userstatefree(L, L1);
freestack(L1);
diff --git a/lvm.c b/lvm.c
index a6f0460..d6c05bb 100644
--- a/lvm.c
+++ b/lvm.c
@@ -842,6 +842,10 @@
luaV_concat(L, total); /* concat them (may yield again) */
break;
}
+ case OP_CLOSE: case OP_RETURN: { /* yielded closing variables */
+ ci->u.l.savedpc--; /* repeat instruction to close other vars. */
+ break;
+ }
default: {
/* only these other opcodes can yield */
lua_assert(op == OP_TFORCALL || op == OP_CALL ||
@@ -1524,7 +1528,7 @@
vmbreak;
}
vmcase(OP_CLOSE) {
- Protect(luaF_close(L, ra, LUA_OK));
+ Protect(luaF_close(L, ra, LUA_OK, 1));
vmbreak;
}
vmcase(OP_TBC) {
@@ -1632,7 +1636,7 @@
/* close upvalues from current call; the compiler ensures
that there are no to-be-closed variables here, so this
call cannot change the stack */
- luaF_close(L, base, NOCLOSINGMETH);
+ luaF_close(L, base, NOCLOSINGMETH, 0);
lua_assert(base == ci->func + 1);
}
while (!ttisfunction(s2v(ra))) { /* not a function? */
@@ -1662,7 +1666,7 @@
if (TESTARG_k(i)) { /* may there be open upvalues? */
if (L->top < ci->top)
L->top = ci->top;
- luaF_close(L, base, CLOSEKTOP);
+ luaF_close(L, base, CLOSEKTOP, 1);
updatetrap(ci);
updatestack(ci);
}
diff --git a/testes/locals.lua b/testes/locals.lua
index add023c..c9c93cc 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -641,6 +641,94 @@
print "to-be-closed variables in coroutines"
do
+ -- yielding inside closing metamethods
+
+ local function checktable (t1, t2)
+ assert(#t1 == #t2)
+ for i = 1, #t1 do
+ assert(t1[i] == t2[i])
+ end
+ end
+
+ local trace = {}
+ local co = coroutine.wrap(function ()
+
+ trace[#trace + 1] = "nowX"
+
+ -- will be closed after 'y'
+ local x <close> = func2close(function (_, msg)
+ assert(msg == nil)
+ trace[#trace + 1] = "x1"
+ coroutine.yield("x")
+ trace[#trace + 1] = "x2"
+ end)
+
+ return pcall(function ()
+ do -- 'z' will be closed first
+ local z <close> = func2close(function (_, msg)
+ assert(msg == nil)
+ trace[#trace + 1] = "z1"
+ coroutine.yield("z")
+ trace[#trace + 1] = "z2"
+ end)
+ end
+
+ trace[#trace + 1] = "nowY"
+
+ -- will be closed after 'z'
+ local y <close> = func2close(function(_, msg)
+ assert(msg == nil)
+ trace[#trace + 1] = "y1"
+ coroutine.yield("y")
+ trace[#trace + 1] = "y2"
+ end)
+
+ return 10, 20, 30
+ end)
+ end)
+
+ assert(co() == "z")
+ assert(co() == "y")
+ assert(co() == "x")
+ checktable({co()}, {true, 10, 20, 30})
+ checktable(trace, {"nowX", "z1", "z2", "nowY", "y1", "y2", "x1", "x2"})
+
+end
+
+
+do
+ -- yielding inside closing metamethods after an error:
+ -- not yet implemented; raises an error
+
+ local co = coroutine.wrap(function ()
+
+ local function foo (err)
+
+ local x <close> = func2close(function(_, msg)
+ assert(msg == err)
+ coroutine.yield("x")
+ return 100, 200
+ end)
+
+ if err then error(err) else return 10, 20 end
+ end
+
+ coroutine.yield(pcall(foo, nil)) -- no error
+ return pcall(foo, 10) -- 'foo' will raise an error
+ end)
+
+ local a, b = co()
+ assert(a == "x" and b == nil) -- yields inside 'x'; Ok
+
+ local a, b, c = co()
+ assert(a and b == 10 and c == 20) -- returns from 'pcall(foo, nil)'
+
+ local st, msg = co() -- error yielding after an error
+ assert(not st and string.find(msg, "attempt to yield"))
+end
+
+
+do
-- an error in a wrapped coroutine closes variables
local x = false
local y = false