diff --git a/lfunc.c b/lfunc.c index ae68487c..a8030afa 100644 --- a/lfunc.c +++ b/lfunc.c @@ -101,31 +101,32 @@ UpVal *luaF_findupval (lua_State *L, StkId level) { /* -** Prepare closing method plus its arguments for object 'obj' with -** error message 'err'. (This function assumes EXTRA_STACK.) +** Call closing method for object 'obj' with error message 'err'. +** (This function assumes EXTRA_STACK.) */ -static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) { +static void callclosemethod (lua_State *L, TValue *obj, TValue *err) { StkId top = L->top; const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); - if (ttisnil(tm)) /* no metamethod? */ - return 0; /* nothing to call */ 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 */ - return 1; + luaD_callnoyield(L, top, 0); /* call method */ } /* -** Raise an error with message 'msg', inserting the name of the -** local variable at position 'level' in the stack. +** Check whether 'obj' has a close metamethod and raise an error +** if not. */ -static void varerror (lua_State *L, StkId level, const char *msg) { - int idx = cast_int(level - L->ci->func); - const char *vname = luaG_findlocal(L, L->ci, idx, NULL); - if (vname == NULL) vname = "?"; - luaG_runerror(L, msg, vname); +static void checkclosemth (lua_State *L, StkId level, const TValue *obj) { + const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); + if (ttisnil(tm)) { /* no metamethod? */ + int idx = cast_int(level - L->ci->func); /* variable index */ + const char *vname = luaG_findlocal(L, L->ci, idx, NULL); + if (vname == NULL) vname = "?"; + luaG_runerror(L, "variable '%s' got a non-closable value", vname); + } } @@ -136,7 +137,7 @@ static void varerror (lua_State *L, StkId level, const char *msg) { ** the 'level' of the upvalue being closed, as everything after that ** won't be used again. */ -static void callclosemth (lua_State *L, StkId level, int status) { +static void prepcallclosemth (lua_State *L, StkId level, int status) { TValue *uv = s2v(level); /* value being closed */ TValue *errobj; if (status == CLOSEKTOP) @@ -145,10 +146,7 @@ static void callclosemth (lua_State *L, StkId level, int status) { errobj = s2v(level + 1); /* error object goes after 'uv' */ luaD_seterrorobj(L, status, level + 1); /* set error object */ } - if (prepclosingmethod(L, uv, errobj)) /* something to call? */ - luaD_callnoyield(L, L->top - 3, 0); /* call method */ - else if (!l_isfalse(uv)) /* non-closable non-false value? */ - varerror(L, level, "attempt to close non-closable variable '%s'"); + callclosemethod(L, uv, errobj); } @@ -171,16 +169,12 @@ void luaF_newtbcupval (lua_State *L, StkId level) { lua_assert(L->openupval == NULL || uplevel(L->openupval) < level); if (!l_isfalse(obj)) { /* false doesn't need to be closed */ int status; - const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); - if (ttisnil(tm)) /* no metamethod? */ - varerror(L, level, "variable '%s' got a non-closable value"); + checkclosemth(L, level, obj); status = luaD_rawrunprotected(L, trynewtbcupval, level); if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */ lua_assert(status == LUA_ERRMEM); luaD_seterrorobj(L, LUA_ERRMEM, level + 1); /* save error message */ - /* next call must succeed, as object is closable */ - prepclosingmethod(L, s2v(level), s2v(level + 1)); - luaD_callnoyield(L, L->top - 3, 0); /* call method */ + callclosemethod(L, s2v(level), s2v(level + 1)); luaD_throw(L, LUA_ERRMEM); /* throw memory error */ } } @@ -215,7 +209,7 @@ void luaF_close (lua_State *L, StkId level, int status) { } if (uv->tbc && status != NOCLOSINGMETH) { ptrdiff_t levelrel = savestack(L, level); - callclosemth(L, upl, status); /* may change the stack */ + prepcallclosemth(L, upl, status); /* may change the stack */ level = restorestack(L, levelrel); } } diff --git a/testes/locals.lua b/testes/locals.lua index 84e0b03a..1b43609b 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -459,8 +459,50 @@ do -- errors due to non-closable values getmetatable(xyz).__close = nil -- remove metamethod end local stat, msg = pcall(foo) - assert(not stat and - string.find(msg, "attempt to close non%-closable variable 'xyz'")) + assert(not stat and string.find(msg, "attempt to call a nil value")) +end + + +do -- tbc inside close methods + local track = {} + local function foo () + local x = func2close(function () + local xx = func2close(function (_, msg) + assert(msg == nil) + track[#track + 1] = "xx" + end) + track[#track + 1] = "x" + end) + track[#track + 1] = "foo" + return 20, 30, 40 + end + local a, b, c, d = foo() + assert(a == 20 and b == 30 and c == 40 and d == nil) + assert(track[1] == "foo" and track[2] == "x" and track[3] == "xx") + + -- again, with errors + local track = {} + local function foo () + local x0 = func2close(function (_, msg) + assert(msg == 202) + track[#track + 1] = "x0" + end) + local x = func2close(function () + local xx = func2close(function (_, msg) + assert(msg == 101) + track[#track + 1] = "xx" + error(202) + end) + track[#track + 1] = "x" + error(101) + end) + track[#track + 1] = "foo" + return 20, 30, 40 + end + local st, msg = pcall(foo) + assert(not st and msg == 202) + assert(track[1] == "foo" and track[2] == "x" and track[3] == "xx" and + track[4] == "x0") end