diff --git a/ldo.c b/ldo.c index f8d8f11c..077109c4 100644 --- a/ldo.c +++ b/ldo.c @@ -118,6 +118,7 @@ l_noret luaD_throw (lua_State *L, int errcode) { } else { /* thread has no error handler */ global_State *g = G(L); + errcode = luaF_close(L, L->stack, errcode); /* close all upvalues */ L->status = cast_byte(errcode); /* mark it as dead */ if (g->mainthread->errorJmp) { /* main thread has a handler? */ setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */ diff --git a/lfunc.c b/lfunc.c index bdf3cd25..362b798c 100644 --- a/lfunc.c +++ b/lfunc.c @@ -100,28 +100,23 @@ UpVal *luaF_findupval (lua_State *L, StkId level) { static void callclose (lua_State *L, void *ud) { UNUSED(ud); - luaD_callnoyield(L, L->top - 2, 0); + luaD_callnoyield(L, L->top - 3, 0); } /* -** Prepare closing method plus its argument for object 'obj' with +** Prepare closing method plus its arguments for object 'obj' with ** error message 'err'. (This function assumes EXTRA_STACK.) */ static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) { StkId top = L->top; - if (ttisfunction(obj)) { /* object to-be-closed is a function? */ - setobj2s(L, top, obj); /* push function */ - setobj2s(L, top + 1, err); /* push error msg. as argument */ - } - else { /* try '__close' metamethod */ - 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 argument */ - } - L->top = top + 2; /* add function and argument */ + 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; } @@ -156,6 +151,7 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) { if (newstatus != LUA_OK) /* another error when closing? */ status = newstatus; /* this will be the new error */ } + /* else no metamethod; ignore this case and keep original error */ } return status; } diff --git a/ltests.c b/ltests.c index 0cb6d3a7..36a974ae 100644 --- a/ltests.c +++ b/ltests.c @@ -1201,8 +1201,8 @@ static const char *const delimits = " \t\n,;"; static void skip (const char **pc) { for (;;) { if (**pc != '\0' && strchr(delimits, **pc)) (*pc)++; - else if (**pc == '#') { - while (**pc != '\n' && **pc != '\0') (*pc)++; + else if (**pc == '#') { /* comment? */ + while (**pc != '\n' && **pc != '\0') (*pc)++; /* until end-of-line */ } else break; } @@ -1544,18 +1544,22 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) { } else if EQ("setfield") { int t = getindex; - lua_setfield(L1, t, getstring); + const char *s = getstring; + lua_setfield(L1, t, s); } else if EQ("setglobal") { - lua_setglobal(L1, getstring); + const char *s = getstring; + lua_setglobal(L1, s); } else if EQ("sethook") { int mask = getnum; int count = getnum; - sethookaux(L1, mask, count, getstring); + const char *s = getstring; + sethookaux(L1, mask, count, s); } else if EQ("setmetatable") { - lua_setmetatable(L1, getindex); + int idx = getindex; + lua_setmetatable(L1, idx); } else if EQ("settable") { lua_settable(L1, getindex); @@ -1620,7 +1624,7 @@ static struct X { int x; } x; return lua_yieldk(L1, nres, i, Cfunck); } else if EQ("toclose") { - lua_toclose(L, getnum); + lua_toclose(L1, getnum); } else luaL_error(L, "unknown instruction %s", buff); } diff --git a/manual/manual.of b/manual/manual.of index d64f0f1a..b9ab1ebe 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1536,33 +1536,29 @@ goes out of scope, including normal block termination, exiting its block by @Rw{break}/@Rw{goto}/@Rw{return}, or exiting by an error. -To \emph{close} a value has the following meaning here: -If the value of the variable when it goes out of scope is a function, -that function is called; -otherwise, if the value has a @idx{__close} metamethod, -that metamethod is called; -otherwise, if the value is @nil, nothing is done; -otherwise, an error is raised. -In the function case, -if the scope is being closed by an error, -the error object is passed as an argument to the function; -if there is no error, the function gets @nil. -In the metamethod case, -the value itself always is passed as an argument to the metamethod. +Here, to \emph{close} a value means +to call its @idx{__close} metamethod. +If the value is @nil, it is ignored; +otherwise, +if it does not have a @idx{__close} metamethod, +an error is raised. +When calling the metamethod, +the value itself is passed as the first argument +and the error object (if any) is passed as a second argument; +if there was no error, the second argument is @nil. If several to-be-closed variables go out of scope at the same event, they are closed in the reverse order that they were declared. -If there is any error while running a closing function, +If there is any error while running a closing method, that error is handled like an error in the regular code where the variable was defined; in particular, -the other pending closing functions will still be called. +the other pending closing methods will still be called. If a coroutine yields inside a block and is never resumed again, the variables visible at that block will never go out of scope, and therefore they will not be closed. -Similarly, if a script is interrupted by an unprotected error, -its to-be-closed variables will not be closed. +(You should use finalizers to handle this case.) } @@ -3002,7 +2998,7 @@ and therefore never returns } @APIEntry{int lua_gc (lua_State *L, int what, int data);| -@apii{0,0,v} +@apii{0,0,-} Controls the garbage collector. @@ -3056,8 +3052,6 @@ returns a boolean that tells whether the collector is running For more details about these options, see @Lid{collectgarbage}. -This function may raise errors when calling finalizers. - } @APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);| diff --git a/testes/api.lua b/testes/api.lua index 893a36cb..9904dadf 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -396,6 +396,23 @@ do assert(string.find(msg, "stack overflow")) end + -- exit in panic still close to-be-closed variables + assert(T.checkpanic([[ + pushstring "return {__close = function () Y = 'ho'; end}" + newtable + loadstring -2 + call 0 1 + setmetatable -2 + toclose -1 + pushstring "hi" + error + ]], + [[ + getglobal Y + concat 2 # concat original error with global Y + ]]) == "hiho") + + end -- testing deep C stack @@ -1115,7 +1132,7 @@ end) testamem("to-be-closed variables", function() local flag do - local *toclose x = function () flag = true end + local *toclose x = setmetatable({}, {__close = function () flag = true end}) flag = false local x = {} end diff --git a/testes/coroutine.lua b/testes/coroutine.lua index ca30011f..a4321bed 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -142,8 +142,15 @@ do -- to-be-closed variables in coroutines local X + + local function func2close (f) + return setmetatable({}, {__close = f}) + end + co = coroutine.create(function () - local *toclose x = function (err) assert(err == nil); X = false end + local *toclose x = func2close(function (self, err) + assert(err == nil); X = false + end) X = true coroutine.yield() end) @@ -154,7 +161,9 @@ do -- error killing a coroutine co = coroutine.create(function() - local *toclose x = function (err) assert(err == nil); error(111) end + local *toclose x = func2close(function (self, err) + assert(err == nil); error(111) + end) coroutine.yield() end) coroutine.resume(co) diff --git a/testes/goto.lua b/testes/goto.lua index 5d863a42..f3dcfd4a 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -258,7 +258,7 @@ do ::L2:: goto L3 ::L1:: do - local *toclose a = function () X = true end + local *toclose a = setmetatable({}, {__close = function () X = true end}) assert(X == nil) if a then goto L2 end -- jumping back out of scope of 'a' end diff --git a/testes/locals.lua b/testes/locals.lua index 340af61c..de47ae31 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -177,13 +177,19 @@ print"testing to-be-closed variables" local function stack(n) n = ((n == 0) or stack(n - 1)) end +local function func2close (f) + return setmetatable({}, {__close = f}) +end + do local a = {} do local *toclose x = setmetatable({"x"}, {__close = function (self) a[#a + 1] = self[1] end}) - local *toclose y = function (x) assert(x == nil); a[#a + 1] = "y" end + local *toclose y = func2close(function (self, err) + assert(err == nil); a[#a + 1] = "y" + end) a[#a + 1] = "in" end a[#a + 1] = "out" @@ -193,7 +199,7 @@ end do local X = false - local function closescope () stack(10); X = true end + local closescope = func2close(function () stack(10); X = true end) -- closing functions do not corrupt returning values local function foo (x) @@ -228,13 +234,13 @@ do -- calls cannot be tail in the scope of to-be-closed variables local X, Y local function foo () - local *toclose _ = function () Y = 10 end + local *toclose _ = func2close(function () Y = 10 end) assert(X == true and Y == nil) -- 'X' not closed yet return 1,2,3 end local function bar () - local *toclose _ = function () X = false end + local *toclose _ = func2close(function () X = false end) X = true do return foo() -- not a tail call! @@ -249,11 +255,15 @@ end do -- errors in __close local log = {} local function foo (err) - local *toclose x = function (msg) log[#log + 1] = msg; error(1) end - local *toclose x1 = function (msg) log[#log + 1] = msg; end - local *toclose gc = function () collectgarbage() end - local *toclose y = function (msg) log[#log + 1] = msg; error(2) end - local *toclose z = function (msg) log[#log + 1] = msg or 10; error(3) end + local *toclose x = + func2close(function (self, msg) log[#log + 1] = msg; error(1) end) + local *toclose x1 = + func2close(function (self, msg) log[#log + 1] = msg; end) + local *toclose gc = func2close(function () collectgarbage() end) + local *toclose y = + func2close(function (self, msg) log[#log + 1] = msg; error(2) end) + local *toclose z = + func2close(function (self, msg) log[#log + 1] = msg or 10; error(3) end) if err then error(4) end end local stat, msg = pcall(foo, false) @@ -282,7 +292,7 @@ do -- with other errors, non-closable values are ignored local function foo () local *toclose x = 34 - local *toclose y = function () error(32) end + local *toclose y = func2close(function () error(32) end) end local stat, msg = pcall(foo) assert(not stat and msg == 32) @@ -294,7 +304,7 @@ if rawget(_G, "T") then -- memory error inside closing function local function foo () - local *toclose y = function () T.alloccount() end + local *toclose y = func2close(function () T.alloccount() end) local *toclose x = setmetatable({}, {__close = function () T.alloccount(0); local x = {} -- force a memory error end}) @@ -308,12 +318,12 @@ if rawget(_G, "T") then local _, msg = pcall(foo) assert(msg == "not enough memory") - local function close (msg) + local close = func2close(function (self, msg) T.alloccount() assert(msg == "not enough memory") - end + end) - -- set a memory limit and return a closing function to remove the limit + -- set a memory limit and return a closing object to remove the limit local function enter (count) stack(10) -- reserve some stack space T.alloccount(count) @@ -336,13 +346,13 @@ if rawget(_G, "T") then -- repeat test with extra closing upvalues local function test () - local *toclose xxx = function (msg) + local *toclose xxx = func2close(function (self, msg) assert(msg == "not enough memory"); error(1000) -- raise another error - end - local *toclose xx = function (msg) + end) + local *toclose xx = func2close(function (self, msg) assert(msg == "not enough memory"); - end + end) local *toclose x = enter(0) -- set a memory limit -- creation of previous upvalue will raise a memory error os.exit(false) -- should not run @@ -413,9 +423,9 @@ do local x = false local y = false local co = coroutine.create(function () - local *toclose xv = function () x = true end + local *toclose xv = func2close(function () x = true end) do - local *toclose yv = function () y = true end + local *toclose yv = func2close(function () y = true end) coroutine.yield(100) -- yield doesn't close variable end coroutine.yield(200) -- yield doesn't close variable @@ -453,9 +463,7 @@ do end, nil, -- state nil, -- control variable - function () -- closing function - numopen = numopen - 1 - end + func2close(function () numopen = numopen - 1 end) -- closing function end local s = 0