Browse Source

No more LUA_ERRGCMM errors

Errors in finalizers (__gc metamethods) are never propagated.
Instead, they generate a warning.
pull/22/head
Roberto Ierusalimschy 6 years ago
parent
commit
c6f7181e91
  1. 4
      lapi.c
  2. 28
      lgc.c
  3. 7
      lstate.c
  4. 1
      lstate.h
  5. 61
      ltests.c
  6. 3
      lua.h
  7. 26
      manual/manual.of
  8. 11
      testes/all.lua
  9. 28
      testes/api.lua
  10. 87
      testes/gc.lua

4
lapi.c

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

28
lgc.c

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

7
lstate.c

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

1
lstate.h

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

61
ltests.c

@ -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 */
l_memcontrol.numblocks = l_memcontrol.total = 0;
exit(EXIT_FAILURE);
@ -71,9 +75,9 @@ static void badexit (void) {
static int tpanic (lua_State *L) {
fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n",
lua_tostring(L, -1));
return (badexit(), 0); /* do not return to Lua */
return (badexit("PANIC: unprotected error in call to Lua API (%s)\n",
lua_tostring(L, -1)),
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) {
if (*pud == NULL) /* continuation line? */
printf("%s", msg); /* print it */
else if (msg[0] == '*') /* expected warning? */
printf("Expected Lua warning: %s", msg + 1); /* print without the star */
else { /* a real warning; should not happen during tests */
fprintf(stderr, "Warning in test mode (%s), aborting...\n", msg);
badexit();
}
*pud = islast(msg) ? pud : NULL;
static char buff[200]; /* should be enough for tests... */
static int cont = 0; /* message to be continued */
if (cont) { /* continuation? */
if (strlen(msg) >= sizeof(buff) - strlen(buff))
badexit("warnf-buffer overflow");
strcat(buff, msg); /* add new message to current warning */
}
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);
}
}
}

3
lua.h

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

26
manual/manual.of

@ -722,8 +722,6 @@ Lua calls the finalizers of all objects marked for finalization,
following the reverse order that they were marked.
If any finalizer marks objects for collection during that phase,
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.
@ -2645,8 +2643,7 @@ by looking only at its arguments
The third field, @T{x},
tells whether the function may raise errors:
@Char{-} means the function never raises any error;
@Char{m} means the function may raise out-of-memory errors
and errors running a finalizer;
@Char{m} means the function may raise only out-of-memory errors;
@Char{v} means the function may raise the errors explained in the text;
@Char{e} means the function can run arbitrary Lua code,
either directly or through metamethods,
@ -3364,12 +3361,6 @@ syntax error during precompilation;}
@item{@Lid{LUA_ERRMEM}|
@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
@ -3564,13 +3555,6 @@ For such errors, Lua does not call 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)|
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.)
}
@item{
The constant @Lid{LUA_ERRGCMM} was removed.
Errors in finalizers are never propagated;
instead, they generate a warning.
}
}
}

11
testes/all.lua

@ -190,12 +190,17 @@ assert(dofile('verybig.lua', true) == 10); collectgarbage()
dofile('files.lua')
if #msgs > 0 then
warn("*tests not performed:\n ")
warn("#tests not performed:\n ")
for i=1,#msgs do
warn(msgs[i]); warn("\n ")
end
warn("\n")
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'
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 lasttime
warn("*This is "); warn("an expected"); warn(" warning\n")
warn("*This is"); warn(" another one\n")
if not usertests then
-- open file with time of last performed test
local f = io.open(fname)

28
testes/api.lua

@ -114,13 +114,12 @@ end
-- testing warnings
T.testC([[
warning "*This "
warning "warning "
warning "should be in a"
warning " single line
warning "#This shold be a"
warning " single "
warning "warning
"
warning "*This should be "
warning "another warning
warning "#This should be "
warning "another one
"
]])
@ -896,24 +895,15 @@ do -- testing errors during GC
a[i] = T.newuserdata(i) -- creates several udata
end
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
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})
end
a = nil
_G.A = 0
a = 0
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
collectgarbage()
assert(A == 10) -- number of normal collections
collectgarbage("restart")
end

87
testes/gc.lua

@ -353,40 +353,36 @@ GC()
-- testing errors during GC
do
collectgarbage("stop") -- stop collection
local u = {}
local s = {}; setmetatable(s, {__mode = 'k'})
setmetatable(u, {__gc = function (o)
local i = s[o]
s[i] = true
assert(not s[i - 1]) -- check proper finalization order
if i == 8 then error("here") end -- error during GC
end})
for i = 6, 10 do
local n = setmetatable({}, getmetatable(u))
s[n] = i
end
assert(not pcall(collectgarbage))
for i = 8, 10 do assert(s[i]) end
for i = 1, 5 do
local n = setmetatable({}, getmetatable(u))
s[n] = i
end
if T then
collectgarbage("stop") -- stop collection
local u = {}
local s = {}; setmetatable(s, {__mode = 'k'})
setmetatable(u, {__gc = function (o)
local i = s[o]
s[i] = true
assert(not s[i - 1]) -- check proper finalization order
if i == 8 then error("@expected@") end -- error during GC
end})
for i = 6, 10 do
local n = setmetatable({}, getmetatable(u))
s[n] = i
end
collectgarbage()
for i = 1, 10 do assert(s[i]) end
collectgarbage()
assert(string.find(_WARN, "error in __gc metamethod"))
assert(string.match(_WARN, "@(.-)@") == "expected")
for i = 8, 10 do assert(s[i]) end
getmetatable(u).__gc = false
for i = 1, 5 do
local n = setmetatable({}, getmetatable(u))
s[n] = i
end
collectgarbage()
for i = 1, 10 do assert(s[i]) end
-- __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"))
getmetatable(u).__gc = false
end
print '+'
@ -478,9 +474,11 @@ end
-- errors during collection
u = setmetatable({}, {__gc = function () error "!!!" end})
u = nil
assert(not pcall(collectgarbage))
if T then
u = setmetatable({}, {__gc = function () error "@expected error" end})
u = nil
collectgarbage()
end
if not _soft then
@ -645,11 +643,26 @@ do
end
-- create several objects to raise errors when collected while closing state
do
local mt = {__gc = function (o) return o + 1 end}
for i = 1,10 do
if T then
local error, assert, warn, find = error, assert, warn, string.find
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
table.insert(___Glob, setmetatable({}, mt))
table.insert(___Glob, setmetatable({i}, mt))
end
end

Loading…
Cancel
Save