Small improvements in hooks
- 'L->top' is set once in 'luaD_hook', instead of being set in
'luaD_hookcall' and 'rethook';
- resume discard arguments when returning after an yield inside a hook
(arguments may interfere with the coroutine stack);
- yield inside a hook asserts it has no arguments.
diff --git a/ldo.c b/ldo.c
index 9076c0e..80c7980 100644
--- a/ldo.c
+++ b/ldo.c
@@ -306,6 +306,8 @@
ci->u2.transferinfo.ftransfer = ftransfer;
ci->u2.transferinfo.ntransfer = ntransfer;
}
+ if (isLua(ci) && L->top < ci->top)
+ L->top = ci->top; /* protect entire activation register */
luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */
if (ci->top < L->top + LUA_MINSTACK)
ci->top = L->top + LUA_MINSTACK;
@@ -333,7 +335,6 @@
int event = (ci->callstatus & CIST_TAIL) ? LUA_HOOKTAILCALL
: LUA_HOOKCALL;
Proto *p = ci_func(ci)->p;
- L->top = ci->top; /* prepare top */
ci->u.l.savedpc++; /* hooks assume 'pc' is already incremented */
luaD_hook(L, event, -1, 1, p->numparams);
ci->u.l.savedpc--; /* correct 'pc' */
@@ -349,21 +350,17 @@
static void rethook (lua_State *L, CallInfo *ci, int nres) {
if (L->hookmask & LUA_MASKRET) { /* is return hook on? */
StkId firstres = L->top - nres; /* index of first result */
- ptrdiff_t oldtop = savestack(L, L->top); /* hook may change top */
int delta = 0; /* correction for vararg functions */
int ftransfer;
- if (isLuacode(ci)) {
+ if (isLua(ci)) {
Proto *p = ci_func(ci)->p;
if (p->is_vararg)
delta = ci->u.l.nextraargs + p->numparams + 1;
- if (L->top < ci->top)
- L->top = ci->top; /* correct top to run hook */
}
ci->func += delta; /* if vararg, back to virtual 'func' */
ftransfer = cast(unsigned short, firstres - ci->func);
luaD_hook(L, LUA_HOOKRET, -1, ftransfer, nres); /* call it */
ci->func -= delta;
- L->top = restorestack(L, oldtop);
}
if (isLua(ci = ci->previous))
L->oldpc = pcRel(ci->u.l.savedpc, ci_func(ci)->p); /* update 'oldpc' */
@@ -707,8 +704,10 @@
lua_assert(L->status == LUA_YIELD);
L->status = LUA_OK; /* mark that it is running (again) */
luaE_incCstack(L); /* control the C stack */
- if (isLua(ci)) /* yielded inside a hook? */
+ if (isLua(ci)) { /* yielded inside a hook? */
+ L->top = firstArg; /* discard arguments */
luaV_execute(L, ci); /* just continue running Lua code */
+ }
else { /* 'common' yield */
if (ci->u.c.k != NULL) { /* does it have a continuation function? */
lua_unlock(L);
@@ -793,15 +792,15 @@
luaG_runerror(L, "attempt to yield from outside a coroutine");
}
L->status = LUA_YIELD;
+ ci->u2.nyield = nresults; /* save number of results */
if (isLua(ci)) { /* inside a hook? */
lua_assert(!isLuacode(ci));
+ api_check(L, nresults == 0, "hooks cannot yield values");
api_check(L, k == NULL, "hooks cannot continue after yielding");
- ci->u2.nyield = 0; /* no results */
}
else {
if ((ci->u.c.k = k) != NULL) /* is there a continuation? */
ci->u.c.ctx = ctx; /* save context */
- ci->u2.nyield = nresults; /* save number of results */
luaD_throw(L, LUA_YIELD);
}
lua_assert(ci->callstatus & CIST_HOOKED); /* must be inside a hook */
diff --git a/testes/coroutine.lua b/testes/coroutine.lua
index fbeabd0..b36b76e 100644
--- a/testes/coroutine.lua
+++ b/testes/coroutine.lua
@@ -498,6 +498,28 @@
assert(B // A == 7) -- fact(7) // fact(6)
+ do -- hooks vs. multiple values
+ local done
+ local function test (n)
+ done = false
+ return coroutine.wrap(function ()
+ local a = {}
+ for i = 1, n do a[i] = i end
+ -- 'pushint' just to perturb the stack
+ T.sethook("pushint 10; yield 0", "", 1) -- yield at each op.
+ local a1 = {table.unpack(a)} -- must keep top between ops.
+ assert(#a1 == n)
+ for i = 1, n do assert(a[i] == i) end
+ done = true
+ end)
+ end
+ -- arguments to the coroutine are just to perturb its stack
+ local co = test(0); while not done do co(30) end
+ co = test(1); while not done do co(20, 10) end
+ co = test(3); while not done do co() end
+ co = test(100); while not done do co() end
+ end
+
local line = debug.getinfo(1, "l").currentline + 2 -- get line number
local function foo ()
local x = 10 --<< this line is 'line'