Supressed errors in '__close' generate warnings

This commit is contained in:
Roberto Ierusalimschy 2019-08-16 09:51:54 -03:00
parent a1d8eb2743
commit ca13be9af7
10 changed files with 166 additions and 45 deletions

View File

@ -1010,9 +1010,9 @@ static int panic (lua_State *L) {
static void warnf (void *ud, const char *message, int tocont) { static void warnf (void *ud, const char *message, int tocont) {
int *warnstate = (int *)ud; int *warnstate = (int *)ud;
if (*warnstate != 2 && !tocont && *message == '@') { /* control message? */ if (*warnstate != 2 && !tocont && *message == '@') { /* control message? */
if (strcmp(message + 1, "off") == 0) if (strcmp(message, "@off") == 0)
*warnstate = 0; *warnstate = 0;
else if (strcmp(message + 1, "on") == 0) else if (strcmp(message, "@on") == 0)
*warnstate = 1; *warnstate = 1;
return; return;
} }

View File

@ -164,8 +164,12 @@ static int callclosemth (lua_State *L, StkId level, int status) {
int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0); int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0);
if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */ if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */
status = newstatus; /* this will be the new 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); L->top = restorestack(L, oldtop);
}
} }
/* else no metamethod; ignore this case and keep original error */ /* else no metamethod; ignore this case and keep original error */
} }

7
lgc.c
View File

@ -854,12 +854,7 @@ static void GCTM (lua_State *L) {
L->allowhook = oldah; /* restore hooks */ L->allowhook = oldah; /* restore hooks */
g->gcrunning = running; /* restore state */ g->gcrunning = running; /* restore state */
if (unlikely(status != LUA_OK)) { /* error while running __gc? */ if (unlikely(status != LUA_OK)) { /* error while running __gc? */
const char *msg = (ttisstring(s2v(L->top - 1))) luaE_warnerror(L, "__gc metamethod");
? 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);
L->top--; /* pops error object */ L->top--; /* pops error object */
} }
} }

View File

@ -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);
}

View File

@ -355,6 +355,7 @@ LUAI_FUNC void luaE_freeCI (lua_State *L);
LUAI_FUNC void luaE_shrinkCI (lua_State *L); LUAI_FUNC void luaE_shrinkCI (lua_State *L);
LUAI_FUNC void luaE_enterCcall (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_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++) #define luaE_exitCcall(L) ((L)->nCcalls++)

View File

@ -95,15 +95,15 @@ static void warnf (void *ud, const char *msg, int tocont) {
if (!lasttocont && !tocont && *msg == '@') { /* control message? */ if (!lasttocont && !tocont && *msg == '@') { /* control message? */
if (buff[0] != '\0') if (buff[0] != '\0')
badexit("Control warning during warning: %s\naborting...\n", msg); badexit("Control warning during warning: %s\naborting...\n", msg);
if (strcmp(msg + 1, "off") == 0) if (strcmp(msg, "@off") == 0)
onoff = 0; onoff = 0;
else if (strcmp(msg + 1, "on") == 0) else if (strcmp(msg, "@on") == 0)
onoff = 1; onoff = 1;
else if (strcmp(msg + 1, "normal") == 0) else if (strcmp(msg, "@normal") == 0)
mode = 0; mode = 0;
else if (strcmp(msg + 1, "allow") == 0) else if (strcmp(msg, "@allow") == 0)
mode = 1; mode = 1;
else if (strcmp(msg + 1, "store") == 0) else if (strcmp(msg, "@store") == 0)
mode = 2; mode = 2;
else else
badexit("Invalid control warning in test mode: %s\naborting...\n", msg); badexit("Invalid control warning in test mode: %s\naborting...\n", msg);

View File

@ -1556,7 +1556,7 @@ However, Lua may call the method one more time.
After an error, After an error,
the other pending closing methods will still be called. the other pending closing methods will still be called.
Errors in these methods Errors in these methods
interrupt the respective method, interrupt the respective method and generate a warning,
but are otherwise ignored; but are otherwise ignored;
the error reported is only the original one. the error reported is only the original one.

View File

@ -209,12 +209,12 @@ if #msgs > 0 then
warn("#tests not performed:\n ", m, "\n") warn("#tests not performed:\n ", m, "\n")
end end
print("(there should be two warnings now)")
warn("#This is ", "an expected", " warning")
warn("@off") warn("@off")
warn("******** THIS WARNING SHOULD NOT APPEAR **********") warn("******** THIS WARNING SHOULD NOT APPEAR **********")
warn("******** THIS WARNING ALSO SHOULD NOT APPEAR **********") warn("******** THIS WARNING ALSO SHOULD NOT APPEAR **********")
warn("@on") warn("@on")
print("(there should be two warnings now)")
warn("#This is ", "an expected", " warning")
warn("#This is", " another one") warn("#This is", " another one")
-- no test module should define 'debug' -- no test module should define 'debug'

View File

@ -168,7 +168,7 @@ do
local y <close> = func2close(function (self,err) local y <close> = func2close(function (self,err)
if (err ~= 111) then os.exit(false) end -- should not happen if (err ~= 111) then os.exit(false) end -- should not happen
x = 200 x = 200
error(200) error("200")
end) end)
local x <close> = func2close(function (self, err) local x <close> = func2close(function (self, err)
assert(err == nil); error(111) assert(err == nil); error(111)
@ -177,7 +177,10 @@ do
end) end)
coroutine.resume(co) coroutine.resume(co)
assert(x == 0) assert(x == 0)
_WARN = nil; warn("@off"); warn("@store")
local st, msg = coroutine.close(co) 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(st == false and coroutine.status(co) == "dead" and msg == 111)
assert(x == 200) assert(x == 200)

View File

@ -286,57 +286,149 @@ do
end end
do -- errors in __close -- auxiliary functions for testing warnings in '__close'
local log = {} local function prepwarn ()
local function foo (err) 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 <close> = local x <close> =
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 <close> = local x1 <close> =
func2close(function (self, msg) log[#log + 1] = msg; end) func2close(function (self, msg)
checkwarn("@y")
assert(string.find(msg, "@z"))
end)
local gc <close> = func2close(function () collectgarbage() end) local gc <close> = func2close(function () collectgarbage() end)
local y <close> = local y <close> =
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 <close> =
-- '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 <close> =
func2close(function (self, msg)
assert(msg == 4)
end)
local x1 <close> =
func2close(function (self, msg)
checkwarn("@y")
assert(msg == 4)
error("@x1")
end)
local gc <close> = func2close(function () collectgarbage() end)
local y <close> =
func2close(function (self, msg)
assert(msg == 4) -- error in body
error("@y")
end)
local first = true
local z <close> = local z <close> =
func2close(function (self, msg) func2close(function (self, msg)
log[#log + 1] = (msg or 10) + 1; checkwarn("@z")
error(3) -- 'z' close is called once
assert(first and msg == 4)
first = false
error("@z")
end) 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) local stat, msg = pcall(foo, true)
assert(msg == 4) assert(msg == 4)
-- 'z' close is called once checkwarn("@x1") -- last error
assert(log[1] == 5 and log[2] == 4 and log[3] == 4 and log[4] == 4
and #log == 4)
-- error leaving a block -- error leaving a block
local function foo (...) local function foo (...)
do do
local x1 <close> = func2close(function () error("Y") end) local x1 <close> =
local x123 <close> = func2close(function () error("X") end) func2close(function ()
checkwarn("@X")
error("@Y")
end)
local x123 <close> =
func2close(function ()
error("@X")
end)
end end
os.exit(false) -- should not run
end end
local st, msg = xpcall(foo, debug.traceback) local st, msg = xpcall(foo, debug.traceback)
assert(string.match(msg, "^[^ ]* X")) assert(string.match(msg, "^[^ ]* @X"))
assert(string.find(msg, "in metamethod 'close'")) assert(string.find(msg, "in metamethod 'close'"))
-- error in toclose in vararg function -- error in toclose in vararg function
local function foo (...) local function foo (...)
local x123 <close> = func2close(function () error("X") end) local x123 <close> = func2close(function () error("@X") end)
end end
local st, msg = xpcall(foo, debug.traceback) local st, msg = xpcall(foo, debug.traceback)
assert(string.match(msg, "^[^ ]* X")) assert(string.match(msg, "^[^ ]* @X"))
assert(string.find(msg, "in metamethod 'close'")) assert(string.find(msg, "in metamethod 'close'"))
endwarn()
end end
@ -361,6 +453,8 @@ end
if rawget(_G, "T") then if rawget(_G, "T") then
warn("@off")
-- memory error inside closing function -- memory error inside closing function
local function foo () local function foo ()
local y <close> = func2close(function () T.alloccount() end) local y <close> = func2close(function () T.alloccount() end)
@ -437,7 +531,7 @@ if rawget(_G, "T") then
local s = string.rep("a", lim) 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} local a = {s, s}
collectgarbage() collectgarbage()
@ -472,6 +566,8 @@ if rawget(_G, "T") then
print'+' print'+'
end end
warn("@on")
end end
@ -501,17 +597,20 @@ end
do do
prepwarn()
-- error in a wrapped coroutine raising errors when closing a variable -- error in a wrapped coroutine raising errors when closing a variable
local x = 0 local x = 0
local co = coroutine.wrap(function () local co = coroutine.wrap(function ()
local xx <close> = func2close(function () x = x + 1; error("YYY") end) local xx <close> = func2close(function () x = x + 1; error("@YYY") end)
local xv <close> = func2close(function () x = x + 1; error("XXX") end) local xv <close> = func2close(function () x = x + 1; error("@XXX") end)
coroutine.yield(100) coroutine.yield(100)
error(200) error(200)
end) end)
assert(co() == 100); assert(x == 0) assert(co() == 100); assert(x == 0)
local st, msg = pcall(co); assert(x == 2) local st, msg = pcall(co); assert(x == 2)
assert(not st and msg == 200) -- should get first error raised assert(not st and msg == 200) -- should get first error raised
checkwarn("@YYY")
local x = 0 local x = 0
local y = 0 local y = 0
@ -526,6 +625,9 @@ do
assert(x == 2 and y == 1) -- first close is called twice assert(x == 2 and y == 1) -- first close is called twice
-- should get first error raised -- should get first error raised
assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX")) assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
checkwarn("YYY")
endwarn()
end end