No more LUA_ERRGCMM errors

Errors in finalizers (__gc metamethods) are never propagated.
Instead, they generate a warning.
This commit is contained in:
Roberto Ierusalimschy
2019-01-01 12:14:56 -02:00
parent 437a5b07d4
commit c6f7181e91
10 changed files with 143 additions and 109 deletions

4
lapi.c
View File

@@ -1276,10 +1276,8 @@ void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) {
void lua_warning (lua_State *L, const char *msg) { void lua_warning (lua_State *L, const char *msg) {
lua_WarnFunction wf = G(L)->warnf;
lua_lock(L); lua_lock(L);
if (wf != NULL) luaE_warning(L, msg);
wf(&G(L)->ud_warn, msg);
lua_unlock(L); lua_unlock(L);
} }

28
lgc.c
View File

@@ -824,7 +824,7 @@ static void dothecall (lua_State *L, void *ud) {
} }
static void GCTM (lua_State *L, int propagateerrors) { static void GCTM (lua_State *L) {
global_State *g = G(L); global_State *g = G(L);
const TValue *tm; const TValue *tm;
TValue v; TValue v;
@@ -845,15 +845,13 @@ static void GCTM (lua_State *L, int propagateerrors) {
L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */ L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */
L->allowhook = oldah; /* restore hooks */ L->allowhook = oldah; /* restore hooks */
g->gcrunning = running; /* restore state */ g->gcrunning = running; /* restore state */
if (status != LUA_OK && propagateerrors) { /* error while running __gc? */ if (status != LUA_OK) { /* error while running __gc? */
if (status == LUA_ERRRUN) { /* is there an error object? */ const char *msg = (ttisstring(s2v(L->top - 1)))
const char *msg = (ttisstring(s2v(L->top - 1))) ? svalue(s2v(L->top - 1))
? svalue(s2v(L->top - 1)) : "error object is not a string";
: "no message"; luaE_warning(L, "error in __gc metamethod (");
luaO_pushfstring(L, "error in __gc metamethod (%s)", msg); luaE_warning(L, msg);
status = LUA_ERRGCMM; /* error in __gc metamethod */ luaE_warning(L, ")\n");
}
luaD_throw(L, status); /* re-throw error */
} }
} }
} }
@@ -866,7 +864,7 @@ static int runafewfinalizers (lua_State *L, int n) {
global_State *g = G(L); global_State *g = G(L);
int i; int i;
for (i = 0; i < n && g->tobefnz; i++) for (i = 0; i < n && g->tobefnz; i++)
GCTM(L, 1); /* call one finalizer */ GCTM(L); /* call one finalizer */
return i; return i;
} }
@@ -874,10 +872,10 @@ static int runafewfinalizers (lua_State *L, int n) {
/* /*
** call all pending finalizers ** call all pending finalizers
*/ */
static void callallpendingfinalizers (lua_State *L, int propagateerrors) { static void callallpendingfinalizers (lua_State *L) {
global_State *g = G(L); global_State *g = G(L);
while (g->tobefnz) while (g->tobefnz)
GCTM(L, propagateerrors); GCTM(L);
} }
@@ -1124,7 +1122,7 @@ static void finishgencycle (lua_State *L, global_State *g) {
checkSizes(L, g); checkSizes(L, g);
g->gcstate = GCSpropagate; /* skip restart */ g->gcstate = GCSpropagate; /* skip restart */
if (!g->gcemergency) if (!g->gcemergency)
callallpendingfinalizers(L, 1); callallpendingfinalizers(L);
} }
@@ -1334,7 +1332,7 @@ void luaC_freeallobjects (lua_State *L) {
luaC_changemode(L, KGC_INC); luaC_changemode(L, KGC_INC);
separatetobefnz(g, 1); /* separate all objects with finalizers */ separatetobefnz(g, 1); /* separate all objects with finalizers */
lua_assert(g->finobj == NULL); lua_assert(g->finobj == NULL);
callallpendingfinalizers(L, 0); callallpendingfinalizers(L);
deletelist(L, g->allgc, obj2gco(g->mainthread)); deletelist(L, g->allgc, obj2gco(g->mainthread));
deletelist(L, g->finobj, NULL); deletelist(L, g->finobj, NULL);
deletelist(L, g->fixedgc, NULL); /* collect fixed objects */ deletelist(L, g->fixedgc, NULL); /* collect fixed objects */

View File

@@ -409,3 +409,10 @@ LUA_API void lua_close (lua_State *L) {
} }
void luaE_warning (lua_State *L, const char *msg) {
lua_WarnFunction wf = G(L)->warnf;
if (wf != NULL)
wf(&G(L)->ud_warn, msg);
}

View File

@@ -316,6 +316,7 @@ LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L);
LUAI_FUNC void luaE_freeCI (lua_State *L); 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);
#define luaE_exitCcall(L) ((L)->nCcalls--) #define luaE_exitCcall(L) ((L)->nCcalls--)

View File

@@ -63,7 +63,11 @@ static void pushobject (lua_State *L, const TValue *o) {
} }
static void badexit (void) { static void badexit (const char *fmt, ...) {
va_list argp;
va_start(argp, fmt);
vfprintf(stderr, fmt, argp);
va_end(argp);
/* avoid assertion failures when exiting */ /* avoid assertion failures when exiting */
l_memcontrol.numblocks = l_memcontrol.total = 0; l_memcontrol.numblocks = l_memcontrol.total = 0;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@@ -71,9 +75,9 @@ static void badexit (void) {
static int tpanic (lua_State *L) { static int tpanic (lua_State *L) {
fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", return (badexit("PANIC: unprotected error in call to Lua API (%s)\n",
lua_tostring(L, -1)); lua_tostring(L, -1)),
return (badexit(), 0); /* do not return to Lua */ 0); /* do not return to Lua */
} }
@@ -83,16 +87,47 @@ static int islast (const char *message) {
} }
/*
** Warning function for tests. Fist, it concatenates all parts of
** a warning in buffer 'buff'. Then:
** messages starting with '#' are shown on standard output (used to
** test explicit warnings);
** messages containing '@' are stored in global '_WARN' (used to test
** errors that generate warnings);
** other messages abort the tests (they represent real warning conditions;
** the standard tests should not generate these conditions unexpectedly).
*/
static void warnf (void **pud, const char *msg) { static void warnf (void **pud, const char *msg) {
if (*pud == NULL) /* continuation line? */ static char buff[200]; /* should be enough for tests... */
printf("%s", msg); /* print it */ static int cont = 0; /* message to be continued */
else if (msg[0] == '*') /* expected warning? */ if (cont) { /* continuation? */
printf("Expected Lua warning: %s", msg + 1); /* print without the star */ if (strlen(msg) >= sizeof(buff) - strlen(buff))
else { /* a real warning; should not happen during tests */ badexit("warnf-buffer overflow");
fprintf(stderr, "Warning in test mode (%s), aborting...\n", msg); strcat(buff, msg); /* add new message to current warning */
badexit(); }
else { /* new warning */
if (strlen(msg) >= sizeof(buff))
badexit("warnf-buffer overflow");
strcpy(buff, msg); /* start a new warning */
}
if (!islast(msg)) /* message not finished yet? */
cont = 1; /* wait for more */
else { /* handle message */
cont = 0; /* prepare for next message */
if (buff[0] == '#') /* expected warning? */
printf("Expected Lua warning: %s", buff); /* print it */
else if (strchr(buff, '@') != NULL) { /* warning for test purposes? */
lua_State *L = cast(lua_State *, *pud);
lua_unlock(L);
lua_pushstring(L, buff);
lua_setglobal(L, "_WARN"); /* assign message to global '_WARN' */
lua_lock(L);
return;
}
else { /* a real warning; should not happen during tests */
badexit("Unexpected warning in test mode: %s\naborting...\n", buff);
}
} }
*pud = islast(msg) ? pud : NULL;
} }

3
lua.h
View File

@@ -51,8 +51,7 @@
#define LUA_ERRRUN 2 #define LUA_ERRRUN 2
#define LUA_ERRSYNTAX 3 #define LUA_ERRSYNTAX 3
#define LUA_ERRMEM 4 #define LUA_ERRMEM 4
#define LUA_ERRGCMM 5 #define LUA_ERRERR 5
#define LUA_ERRERR 6
typedef struct lua_State lua_State; typedef struct lua_State lua_State;

View File

@@ -722,8 +722,6 @@ Lua calls the finalizers of all objects marked for finalization,
following the reverse order that they were marked. following the reverse order that they were marked.
If any finalizer marks objects for collection during that phase, If any finalizer marks objects for collection during that phase,
these marks have no effect. these marks have no effect.
If any finalizer raises an error during that phase,
its execution is interrupted but the error is ignored.
Finalizers cannot yield. Finalizers cannot yield.
@@ -2645,8 +2643,7 @@ by looking only at its arguments
The third field, @T{x}, The third field, @T{x},
tells whether the function may raise errors: tells whether the function may raise errors:
@Char{-} means the function never raises any error; @Char{-} means the function never raises any error;
@Char{m} means the function may raise out-of-memory errors @Char{m} means the function may raise only out-of-memory errors;
and errors running a finalizer;
@Char{v} means the function may raise the errors explained in the text; @Char{v} means the function may raise the errors explained in the text;
@Char{e} means the function can run arbitrary Lua code, @Char{e} means the function can run arbitrary Lua code,
either directly or through metamethods, either directly or through metamethods,
@@ -3364,12 +3361,6 @@ syntax error during precompilation;}
@item{@Lid{LUA_ERRMEM}| @item{@Lid{LUA_ERRMEM}|
@x{memory allocation (out-of-memory) error};} @x{memory allocation (out-of-memory) error};}
@item{@Lid{LUA_ERRGCMM}|
error while running a @idx{__gc} metamethod.
(This error has no relation with the chunk being loaded.
It is generated by the garbage collector.)
}
} }
The @id{lua_load} function uses a user-supplied @id{reader} function The @id{lua_load} function uses a user-supplied @id{reader} function
@@ -3564,13 +3555,6 @@ For such errors, Lua does not call the @x{message handler}.
error while running the @x{message handler}. error while running the @x{message handler}.
} }
@item{@defid{LUA_ERRGCMM}|
error while running a @idx{__gc} metamethod.
For such errors, Lua does not call the @x{message handler}
(as this kind of error typically has no relation
with the function being called).
}
} }
} }
@@ -6298,6 +6282,8 @@ The current value of this variable is @St{Lua 5.4}.
@LibEntry{warn (message)| @LibEntry{warn (message)|
Emits a warning with the given message. Emits a warning with the given message.
Note that messages not ending with an end-of-line
are assumed to be continued by the message in the next call.
} }
@@ -8773,6 +8759,12 @@ so there is no need to check whether they are using the same
address space.) address space.)
} }
@item{
The constant @Lid{LUA_ERRGCMM} was removed.
Errors in finalizers are never propagated;
instead, they generate a warning.
}
} }
} }

View File

@@ -190,12 +190,17 @@ assert(dofile('verybig.lua', true) == 10); collectgarbage()
dofile('files.lua') dofile('files.lua')
if #msgs > 0 then if #msgs > 0 then
warn("*tests not performed:\n ") warn("#tests not performed:\n ")
for i=1,#msgs do for i=1,#msgs do
warn(msgs[i]); warn("\n ") warn(msgs[i]); warn("\n ")
end end
warn("\n")
end end
print("(there should be two warnings now)")
warn("#This is "); warn("an expected"); warn(" warning\n")
warn("#This is"); warn(" another one\n")
-- no test module should define 'debug' -- no test module should define 'debug'
assert(debug == nil) assert(debug == nil)
@@ -219,10 +224,6 @@ local _G, showmem, print, format, clock, time, difftime, assert, open =
local fname = T and "time-debug.txt" or "time.txt" local fname = T and "time-debug.txt" or "time.txt"
local lasttime local lasttime
warn("*This is "); warn("an expected"); warn(" warning\n")
warn("*This is"); warn(" another one\n")
if not usertests then if not usertests then
-- open file with time of last performed test -- open file with time of last performed test
local f = io.open(fname) local f = io.open(fname)

View File

@@ -114,13 +114,12 @@ end
-- testing warnings -- testing warnings
T.testC([[ T.testC([[
warning "*This " warning "#This shold be a"
warning "warning " warning " single "
warning "should be in a" warning "warning
warning " single line
" "
warning "*This should be " warning "#This should be "
warning "another warning warning "another one
" "
]]) ]])
@@ -896,24 +895,15 @@ do -- testing errors during GC
a[i] = T.newuserdata(i) -- creates several udata a[i] = T.newuserdata(i) -- creates several udata
end end
for i=1,20,2 do -- mark half of them to raise errors during GC for i=1,20,2 do -- mark half of them to raise errors during GC
debug.setmetatable(a[i], {__gc = function (x) error("error inside gc") end}) debug.setmetatable(a[i],
{__gc = function (x) error("@expected error in gc") end})
end end
for i=2,20,2 do -- mark the other half to count and to create more garbage for i=2,20,2 do -- mark the other half to count and to create more garbage
debug.setmetatable(a[i], {__gc = function (x) load("A=A+1")() end}) debug.setmetatable(a[i], {__gc = function (x) load("A=A+1")() end})
end end
a = nil
_G.A = 0 _G.A = 0
a = 0 collectgarbage()
while 1 do
local stat, msg = pcall(collectgarbage)
if stat then
break -- stop when no more errors
else
a = a + 1
assert(string.find(msg, "__gc"))
end
end
assert(a == 10) -- number of errors
assert(A == 10) -- number of normal collections assert(A == 10) -- number of normal collections
collectgarbage("restart") collectgarbage("restart")
end end

View File

@@ -353,40 +353,36 @@ GC()
-- testing errors during GC -- testing errors during GC
do if T then
collectgarbage("stop") -- stop collection collectgarbage("stop") -- stop collection
local u = {} local u = {}
local s = {}; setmetatable(s, {__mode = 'k'}) local s = {}; setmetatable(s, {__mode = 'k'})
setmetatable(u, {__gc = function (o) setmetatable(u, {__gc = function (o)
local i = s[o] local i = s[o]
s[i] = true s[i] = true
assert(not s[i - 1]) -- check proper finalization order assert(not s[i - 1]) -- check proper finalization order
if i == 8 then error("here") end -- error during GC if i == 8 then error("@expected@") end -- error during GC
end}) end})
for i = 6, 10 do for i = 6, 10 do
local n = setmetatable({}, getmetatable(u)) local n = setmetatable({}, getmetatable(u))
s[n] = i s[n] = i
end end
assert(not pcall(collectgarbage)) collectgarbage()
for i = 8, 10 do assert(s[i]) end assert(string.find(_WARN, "error in __gc metamethod"))
assert(string.match(_WARN, "@(.-)@") == "expected")
for i = 8, 10 do assert(s[i]) end
for i = 1, 5 do for i = 1, 5 do
local n = setmetatable({}, getmetatable(u)) local n = setmetatable({}, getmetatable(u))
s[n] = i s[n] = i
end end
collectgarbage() collectgarbage()
for i = 1, 10 do assert(s[i]) end for i = 1, 10 do assert(s[i]) end
getmetatable(u).__gc = false getmetatable(u).__gc = false
-- __gc errors with non-string messages
setmetatable({}, {__gc = function () error{} end})
local a, b = pcall(collectgarbage)
assert(not a and type(b) == "string" and string.find(b, "error in __gc"))
end end
print '+' print '+'
@@ -478,9 +474,11 @@ end
-- errors during collection -- errors during collection
u = setmetatable({}, {__gc = function () error "!!!" end}) if T then
u = nil u = setmetatable({}, {__gc = function () error "@expected error" end})
assert(not pcall(collectgarbage)) u = nil
collectgarbage()
end
if not _soft then if not _soft then
@@ -645,11 +643,26 @@ do
end end
-- create several objects to raise errors when collected while closing state -- create several objects to raise errors when collected while closing state
do if T then
local mt = {__gc = function (o) return o + 1 end} local error, assert, warn, find = error, assert, warn, string.find
for i = 1,10 do local n = 0
local lastmsg
local mt = {__gc = function (o)
n = n + 1
assert(n == o[1])
if n == 1 then
_WARN = nil
elseif n == 2 then
assert(find(_WARN, "@expected warning"))
lastmsg = _WARN -- get message from previous error (first 'o')
else
assert(lastmsg == _WARN) -- subsequent error messages are equal
end
error"@expected warning"
end}
for i = 10, 1, -1 do
-- create object and preserve it until the end -- create object and preserve it until the end
table.insert(___Glob, setmetatable({}, mt)) table.insert(___Glob, setmetatable({i}, mt))
end end
end end