No more LUA_ERRGCMM errors
Errors in finalizers (__gc metamethods) are never propagated.
Instead, they generate a warning.
diff --git a/lapi.c b/lapi.c
index 0f0166e..8ff7bfb 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1276,10 +1276,8 @@
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);
}
diff --git a/lgc.c b/lgc.c
index 95a8ad5..0c3386e 100644
--- a/lgc.c
+++ b/lgc.c
@@ -824,7 +824,7 @@
}
-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 @@
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 @@
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 @@
/*
** 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 @@
checkSizes(L, g);
g->gcstate = GCSpropagate; /* skip restart */
if (!g->gcemergency)
- callallpendingfinalizers(L, 1);
+ callallpendingfinalizers(L);
}
@@ -1334,7 +1332,7 @@
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 */
diff --git a/lstate.c b/lstate.c
index b3e9ec6..7f6475a 100644
--- a/lstate.c
+++ b/lstate.c
@@ -409,3 +409,10 @@
}
+void luaE_warning (lua_State *L, const char *msg) {
+ lua_WarnFunction wf = G(L)->warnf;
+ if (wf != NULL)
+ wf(&G(L)->ud_warn, msg);
+}
+
+
diff --git a/lstate.h b/lstate.h
index f379325..05a74dd 100644
--- a/lstate.h
+++ b/lstate.h
@@ -316,6 +316,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);
#define luaE_exitCcall(L) ((L)->nCcalls--)
diff --git a/ltests.c b/ltests.c
index 5ea8b08..0cb6d3a 100644
--- a/ltests.c
+++ b/ltests.c
@@ -63,7 +63,11 @@
}
-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 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 @@
}
+/*
+** 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();
+ 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 */
}
- *pud = islast(msg) ? pud : NULL;
+ 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);
+ }
+ }
}
diff --git a/lua.h b/lua.h
index a6f8268..b777624 100644
--- a/lua.h
+++ b/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;
diff --git a/manual/manual.of b/manual/manual.of
index 196ea1e..d64f0f1 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -722,8 +722,6 @@
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 @@
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 @@
@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 @@
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 @@
@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 @@
address space.)
}
+@item{
+The constant @Lid{LUA_ERRGCMM} was removed.
+Errors in finalizers are never propagated;
+instead, they generate a warning.
+}
+
}
}
diff --git a/testes/all.lua b/testes/all.lua
index bde4195..506afad 100644
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -190,12 +190,17 @@
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 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)
diff --git a/testes/api.lua b/testes/api.lua
index b4d6386..893a36c 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -114,13 +114,12 @@
-- 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 @@
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
diff --git a/testes/gc.lua b/testes/gc.lua
index 8b9179c..84e8ffb 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -353,40 +353,36 @@
-- 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})
+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
+ 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
+ collectgarbage()
+ 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
- local n = setmetatable({}, getmetatable(u))
- s[n] = i
-end
+ for i = 1, 5 do
+ local n = setmetatable({}, getmetatable(u))
+ s[n] = i
+ end
-collectgarbage()
-for i = 1, 10 do assert(s[i]) end
+ collectgarbage()
+ for i = 1, 10 do assert(s[i]) end
-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"))
+ getmetatable(u).__gc = false
end
print '+'
@@ -478,9 +474,11 @@
-- 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 @@
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