From ca13be9af784b7288d3a07d9b5bccb329086e885 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 16 Aug 2019 09:51:54 -0300 Subject: [PATCH] Supressed errors in '__close' generate warnings --- lauxlib.c | 4 +- lfunc.c | 6 +- lgc.c | 7 +- lstate.c | 16 +++++ lstate.h | 1 + ltests.c | 10 +-- manual/manual.of | 2 +- testes/all.lua | 4 +- testes/coroutine.lua | 5 +- testes/locals.lua | 156 +++++++++++++++++++++++++++++++++++-------- 10 files changed, 166 insertions(+), 45 deletions(-) diff --git a/lauxlib.c b/lauxlib.c index ba1980b7..014e7052 100644 --- a/lauxlib.c +++ b/lauxlib.c @@ -1010,9 +1010,9 @@ static int panic (lua_State *L) { static void warnf (void *ud, const char *message, int tocont) { int *warnstate = (int *)ud; if (*warnstate != 2 && !tocont && *message == '@') { /* control message? */ - if (strcmp(message + 1, "off") == 0) + if (strcmp(message, "@off") == 0) *warnstate = 0; - else if (strcmp(message + 1, "on") == 0) + else if (strcmp(message, "@on") == 0) *warnstate = 1; return; } diff --git a/lfunc.c b/lfunc.c index 8f39f6b0..1e61f03f 100644 --- a/lfunc.c +++ b/lfunc.c @@ -164,8 +164,12 @@ static int callclosemth (lua_State *L, StkId level, int status) { int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0); if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */ status = newstatus; /* this will be the new error */ - else /* leave original error (or nil) on top */ + else { + if (newstatus != LUA_OK) /* supressed error? */ + luaE_warnerror(L, "__close metamethod"); + /* leave original error (or nil) on top */ L->top = restorestack(L, oldtop); + } } /* else no metamethod; ignore this case and keep original error */ } diff --git a/lgc.c b/lgc.c index 75670c0a..f24074f9 100644 --- a/lgc.c +++ b/lgc.c @@ -854,12 +854,7 @@ static void GCTM (lua_State *L) { L->allowhook = oldah; /* restore hooks */ g->gcrunning = running; /* restore state */ if (unlikely(status != LUA_OK)) { /* error while running __gc? */ - const char *msg = (ttisstring(s2v(L->top - 1))) - ? svalue(s2v(L->top - 1)) - : "error object is not a string"; - luaE_warning(L, "error in __gc metamethod (", 1); - luaE_warning(L, msg, 1); - luaE_warning(L, ")", 0); + luaE_warnerror(L, "__gc metamethod"); L->top--; /* pops error object */ } } diff --git a/lstate.c b/lstate.c index d4bc53eb..86cd5fb8 100644 --- a/lstate.c +++ b/lstate.c @@ -443,3 +443,19 @@ void luaE_warning (lua_State *L, const char *msg, int tocont) { } +/* +** Generate a warning from an error message +*/ +void luaE_warnerror (lua_State *L, const char *where) { + TValue *errobj = s2v(L->top - 1); /* error object */ + const char *msg = (ttisstring(errobj)) + ? svalue(errobj) + : "error object is not a string"; + /* produce warning "error in %s (%s)" (where, msg) */ + luaE_warning(L, "error in ", 1); + luaE_warning(L, where, 1); + luaE_warning(L, " (", 1); + luaE_warning(L, msg, 1); + luaE_warning(L, ")", 0); +} + diff --git a/lstate.h b/lstate.h index 03448b82..638c1e5c 100644 --- a/lstate.h +++ b/lstate.h @@ -355,6 +355,7 @@ LUAI_FUNC void luaE_freeCI (lua_State *L); LUAI_FUNC void luaE_shrinkCI (lua_State *L); LUAI_FUNC void luaE_enterCcall (lua_State *L); LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont); +LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where); #define luaE_exitCcall(L) ((L)->nCcalls++) diff --git a/ltests.c b/ltests.c index fd55fc31..b460d018 100644 --- a/ltests.c +++ b/ltests.c @@ -95,15 +95,15 @@ static void warnf (void *ud, const char *msg, int tocont) { if (!lasttocont && !tocont && *msg == '@') { /* control message? */ if (buff[0] != '\0') badexit("Control warning during warning: %s\naborting...\n", msg); - if (strcmp(msg + 1, "off") == 0) + if (strcmp(msg, "@off") == 0) onoff = 0; - else if (strcmp(msg + 1, "on") == 0) + else if (strcmp(msg, "@on") == 0) onoff = 1; - else if (strcmp(msg + 1, "normal") == 0) + else if (strcmp(msg, "@normal") == 0) mode = 0; - else if (strcmp(msg + 1, "allow") == 0) + else if (strcmp(msg, "@allow") == 0) mode = 1; - else if (strcmp(msg + 1, "store") == 0) + else if (strcmp(msg, "@store") == 0) mode = 2; else badexit("Invalid control warning in test mode: %s\naborting...\n", msg); diff --git a/manual/manual.of b/manual/manual.of index 8c71c613..bb6ae884 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1556,7 +1556,7 @@ However, Lua may call the method one more time. After an error, the other pending closing methods will still be called. Errors in these methods -interrupt the respective method, +interrupt the respective method and generate a warning, but are otherwise ignored; the error reported is only the original one. diff --git a/testes/all.lua b/testes/all.lua index 5d698d4b..42809b9a 100644 --- a/testes/all.lua +++ b/testes/all.lua @@ -209,12 +209,12 @@ if #msgs > 0 then warn("#tests not performed:\n ", m, "\n") end +print("(there should be two warnings now)") +warn("#This is ", "an expected", " warning") warn("@off") warn("******** THIS WARNING SHOULD NOT APPEAR **********") warn("******** THIS WARNING ALSO SHOULD NOT APPEAR **********") warn("@on") -print("(there should be two warnings now)") -warn("#This is ", "an expected", " warning") warn("#This is", " another one") -- no test module should define 'debug' diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 457374ca..79c72a9d 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -168,7 +168,7 @@ do local y = func2close(function (self,err) if (err ~= 111) then os.exit(false) end -- should not happen x = 200 - error(200) + error("200") end) local x = func2close(function (self, err) assert(err == nil); error(111) @@ -177,7 +177,10 @@ do end) coroutine.resume(co) assert(x == 0) + _WARN = nil; warn("@off"); warn("@store") local st, msg = coroutine.close(co) + warn("@on"); warn("@normal") + assert(_WARN == nil or string.find(_WARN, "200")) assert(st == false and coroutine.status(co) == "dead" and msg == 111) assert(x == 200) diff --git a/testes/locals.lua b/testes/locals.lua index 99fa79cd..595e107a 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -286,57 +286,149 @@ do end -do -- errors in __close - local log = {} - local function foo (err) +-- auxiliary functions for testing warnings in '__close' +local function prepwarn () + warn("@off") -- do not show (lots of) warnings + if not T then + _WARN = "OFF" -- signal that warnings are not being captured + else + warn("@store") -- to test the warnings + end +end + + +local function endwarn () + assert(T or _WARN == "OFF") + warn("@on") -- back to normal + warn("@normal") + _WARN = nil +end + + +local function checkwarn (msg) + assert(_WARN == "OFF" or string.find(_WARN, msg)) +end + + +do print("testing errors in __close") + + prepwarn() + + -- original error is in __close + local function foo () + local x = - func2close(function (self, msg) log[#log + 1] = msg; error(1) end) + func2close(function (self, msg) + assert(string.find(msg, "@z")) + error("@x") + end) + local x1 = - func2close(function (self, msg) log[#log + 1] = msg; end) + func2close(function (self, msg) + checkwarn("@y") + assert(string.find(msg, "@z")) + end) + local gc = func2close(function () collectgarbage() end) + local y = - func2close(function (self, msg) log[#log + 1] = msg; error(2) end) + func2close(function (self, msg) + assert(string.find(msg, "@z")) -- error in 'z' + error("@y") + end) + + local first = true + local z = + -- 'z' close is called twice + func2close(function (self, msg) + if first then + assert(msg == nil) + first = false + else + assert(string.find(msg, "@z")) -- own error + end + error("@z") + end) + + return 200 + end + + local stat, msg = pcall(foo, false) + assert(string.find(msg, "@z")) + checkwarn("@x") + + + -- original error not in __close + local function foo () + + local x = + func2close(function (self, msg) + assert(msg == 4) + end) + + local x1 = + func2close(function (self, msg) + checkwarn("@y") + assert(msg == 4) + error("@x1") + end) + + local gc = func2close(function () collectgarbage() end) + + local y = + func2close(function (self, msg) + assert(msg == 4) -- error in body + error("@y") + end) + + local first = true local z = func2close(function (self, msg) - log[#log + 1] = (msg or 10) + 1; - error(3) + checkwarn("@z") + -- 'z' close is called once + assert(first and msg == 4) + first = false + error("@z") end) - if err then error(4) end - end - local stat, msg = pcall(foo, false) - assert(msg == 3) - -- 'z' close is called twice - assert(log[1] == 11 and log[2] == 4 and log[3] == 3 and log[4] == 3 - and log[5] == 3 and #log == 5) - log = {} + error(4) -- original error + end + local stat, msg = pcall(foo, true) assert(msg == 4) - -- 'z' close is called once - assert(log[1] == 5 and log[2] == 4 and log[3] == 4 and log[4] == 4 - and #log == 4) + checkwarn("@x1") -- last error -- error leaving a block local function foo (...) do - local x1 = func2close(function () error("Y") end) - local x123 = func2close(function () error("X") end) + local x1 = + func2close(function () + checkwarn("@X") + error("@Y") + end) + + local x123 = + func2close(function () + error("@X") + end) end + os.exit(false) -- should not run end local st, msg = xpcall(foo, debug.traceback) - assert(string.match(msg, "^[^ ]* X")) + assert(string.match(msg, "^[^ ]* @X")) assert(string.find(msg, "in metamethod 'close'")) -- error in toclose in vararg function local function foo (...) - local x123 = func2close(function () error("X") end) + local x123 = func2close(function () error("@X") end) end local st, msg = xpcall(foo, debug.traceback) - assert(string.match(msg, "^[^ ]* X")) + assert(string.match(msg, "^[^ ]* @X")) assert(string.find(msg, "in metamethod 'close'")) + endwarn() end @@ -361,6 +453,8 @@ end if rawget(_G, "T") then + warn("@off") + -- memory error inside closing function local function foo () local y = func2close(function () T.alloccount() end) @@ -437,7 +531,7 @@ if rawget(_G, "T") then local s = string.rep("a", lim) - -- concat this table needs two buffer resizes (one for each 's') + -- concat this table needs two buffer resizes (one for each 's') local a = {s, s} collectgarbage() @@ -472,6 +566,8 @@ if rawget(_G, "T") then print'+' end + + warn("@on") end @@ -501,17 +597,20 @@ end do + prepwarn() + -- error in a wrapped coroutine raising errors when closing a variable local x = 0 local co = coroutine.wrap(function () - local xx = func2close(function () x = x + 1; error("YYY") end) - local xv = func2close(function () x = x + 1; error("XXX") end) + local xx = func2close(function () x = x + 1; error("@YYY") end) + local xv = func2close(function () x = x + 1; error("@XXX") end) coroutine.yield(100) error(200) end) assert(co() == 100); assert(x == 0) local st, msg = pcall(co); assert(x == 2) assert(not st and msg == 200) -- should get first error raised + checkwarn("@YYY") local x = 0 local y = 0 @@ -526,6 +625,9 @@ do assert(x == 2 and y == 1) -- first close is called twice -- should get first error raised assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX")) + checkwarn("YYY") + + endwarn() end