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

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);
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 */
}

7
lgc.c
View File

@ -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 */
}
}

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_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++)

View File

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

View File

@ -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.

View File

@ -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'

View File

@ -168,7 +168,7 @@ do
local y <close> = func2close(function (self,err)
if (err ~= 111) then os.exit(false) end -- should not happen
x = 200
error(200)
error("200")
end)
local x <close> = 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)

View File

@ -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 <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> =
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 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> =
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 <close> = func2close(function () error("Y") end)
local x123 <close> = func2close(function () error("X") end)
local x1 <close> =
func2close(function ()
checkwarn("@X")
error("@Y")
end)
local x123 <close> =
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 <close> = func2close(function () error("X") end)
local x123 <close> = 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 <close> = 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 <close> = func2close(function () x = x + 1; error("YYY") end)
local xv <close> = func2close(function () x = x + 1; error("XXX") end)
local xx <close> = func2close(function () x = x + 1; error("@YYY") end)
local xv <close> = 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