aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Ierusalimschy <roberto@inf.puc-rio.br>2019-01-01 12:14:56 -0200
committerRoberto Ierusalimschy <roberto@inf.puc-rio.br>2019-01-01 12:14:56 -0200
commitc6f7181e910b6b2ff1346b5486a31be87b1da5af (patch)
tree92cc716487c83ecd9860444f23fd55ef65358cbb
parent437a5b07d415e1a74160ddfd804017171d6cc5cb (diff)
downloadlua-c6f7181e910b6b2ff1346b5486a31be87b1da5af.tar.gz
lua-c6f7181e910b6b2ff1346b5486a31be87b1da5af.tar.bz2
lua-c6f7181e910b6b2ff1346b5486a31be87b1da5af.zip
No more LUA_ERRGCMM errors
Errors in finalizers (__gc metamethods) are never propagated. Instead, they generate a warning.
-rw-r--r--lapi.c4
-rw-r--r--lgc.c28
-rw-r--r--lstate.c7
-rw-r--r--lstate.h1
-rw-r--r--ltests.c61
-rw-r--r--lua.h3
-rw-r--r--manual/manual.of26
-rw-r--r--testes/all.lua11
-rw-r--r--testes/api.lua28
-rw-r--r--testes/gc.lua87
10 files changed, 145 insertions, 111 deletions
diff --git a/lapi.c b/lapi.c
index 0f0166e5..8ff7bfbd 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1276,10 +1276,8 @@ void lua_setwarnf (lua_State *L, lua_WarnFunction f, void *ud) {
1276 1276
1277 1277
1278void lua_warning (lua_State *L, const char *msg) { 1278void lua_warning (lua_State *L, const char *msg) {
1279 lua_WarnFunction wf = G(L)->warnf;
1280 lua_lock(L); 1279 lua_lock(L);
1281 if (wf != NULL) 1280 luaE_warning(L, msg);
1282 wf(&G(L)->ud_warn, msg);
1283 lua_unlock(L); 1281 lua_unlock(L);
1284} 1282}
1285 1283
diff --git a/lgc.c b/lgc.c
index 95a8ad5b..0c3386e7 100644
--- a/lgc.c
+++ b/lgc.c
@@ -824,7 +824,7 @@ static void dothecall (lua_State *L, void *ud) {
824} 824}
825 825
826 826
827static void GCTM (lua_State *L, int propagateerrors) { 827static void GCTM (lua_State *L) {
828 global_State *g = G(L); 828 global_State *g = G(L);
829 const TValue *tm; 829 const TValue *tm;
830 TValue v; 830 TValue v;
@@ -845,15 +845,13 @@ static void GCTM (lua_State *L, int propagateerrors) {
845 L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */ 845 L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */
846 L->allowhook = oldah; /* restore hooks */ 846 L->allowhook = oldah; /* restore hooks */
847 g->gcrunning = running; /* restore state */ 847 g->gcrunning = running; /* restore state */
848 if (status != LUA_OK && propagateerrors) { /* error while running __gc? */ 848 if (status != LUA_OK) { /* error while running __gc? */
849 if (status == LUA_ERRRUN) { /* is there an error object? */ 849 const char *msg = (ttisstring(s2v(L->top - 1)))
850 const char *msg = (ttisstring(s2v(L->top - 1))) 850 ? svalue(s2v(L->top - 1))
851 ? svalue(s2v(L->top - 1)) 851 : "error object is not a string";
852 : "no message"; 852 luaE_warning(L, "error in __gc metamethod (");
853 luaO_pushfstring(L, "error in __gc metamethod (%s)", msg); 853 luaE_warning(L, msg);
854 status = LUA_ERRGCMM; /* error in __gc metamethod */ 854 luaE_warning(L, ")\n");
855 }
856 luaD_throw(L, status); /* re-throw error */
857 } 855 }
858 } 856 }
859} 857}
@@ -866,7 +864,7 @@ static int runafewfinalizers (lua_State *L, int n) {
866 global_State *g = G(L); 864 global_State *g = G(L);
867 int i; 865 int i;
868 for (i = 0; i < n && g->tobefnz; i++) 866 for (i = 0; i < n && g->tobefnz; i++)
869 GCTM(L, 1); /* call one finalizer */ 867 GCTM(L); /* call one finalizer */
870 return i; 868 return i;
871} 869}
872 870
@@ -874,10 +872,10 @@ static int runafewfinalizers (lua_State *L, int n) {
874/* 872/*
875** call all pending finalizers 873** call all pending finalizers
876*/ 874*/
877static void callallpendingfinalizers (lua_State *L, int propagateerrors) { 875static void callallpendingfinalizers (lua_State *L) {
878 global_State *g = G(L); 876 global_State *g = G(L);
879 while (g->tobefnz) 877 while (g->tobefnz)
880 GCTM(L, propagateerrors); 878 GCTM(L);
881} 879}
882 880
883 881
@@ -1124,7 +1122,7 @@ static void finishgencycle (lua_State *L, global_State *g) {
1124 checkSizes(L, g); 1122 checkSizes(L, g);
1125 g->gcstate = GCSpropagate; /* skip restart */ 1123 g->gcstate = GCSpropagate; /* skip restart */
1126 if (!g->gcemergency) 1124 if (!g->gcemergency)
1127 callallpendingfinalizers(L, 1); 1125 callallpendingfinalizers(L);
1128} 1126}
1129 1127
1130 1128
@@ -1334,7 +1332,7 @@ void luaC_freeallobjects (lua_State *L) {
1334 luaC_changemode(L, KGC_INC); 1332 luaC_changemode(L, KGC_INC);
1335 separatetobefnz(g, 1); /* separate all objects with finalizers */ 1333 separatetobefnz(g, 1); /* separate all objects with finalizers */
1336 lua_assert(g->finobj == NULL); 1334 lua_assert(g->finobj == NULL);
1337 callallpendingfinalizers(L, 0); 1335 callallpendingfinalizers(L);
1338 deletelist(L, g->allgc, obj2gco(g->mainthread)); 1336 deletelist(L, g->allgc, obj2gco(g->mainthread));
1339 deletelist(L, g->finobj, NULL); 1337 deletelist(L, g->finobj, NULL);
1340 deletelist(L, g->fixedgc, NULL); /* collect fixed objects */ 1338 deletelist(L, g->fixedgc, NULL); /* collect fixed objects */
diff --git a/lstate.c b/lstate.c
index b3e9ec60..7f6475a8 100644
--- a/lstate.c
+++ b/lstate.c
@@ -409,3 +409,10 @@ LUA_API void lua_close (lua_State *L) {
409} 409}
410 410
411 411
412void luaE_warning (lua_State *L, const char *msg) {
413 lua_WarnFunction wf = G(L)->warnf;
414 if (wf != NULL)
415 wf(&G(L)->ud_warn, msg);
416}
417
418
diff --git a/lstate.h b/lstate.h
index f3793256..05a74dda 100644
--- a/lstate.h
+++ b/lstate.h
@@ -316,6 +316,7 @@ LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L);
316LUAI_FUNC void luaE_freeCI (lua_State *L); 316LUAI_FUNC void luaE_freeCI (lua_State *L);
317LUAI_FUNC void luaE_shrinkCI (lua_State *L); 317LUAI_FUNC void luaE_shrinkCI (lua_State *L);
318LUAI_FUNC void luaE_enterCcall (lua_State *L); 318LUAI_FUNC void luaE_enterCcall (lua_State *L);
319LUAI_FUNC void luaE_warning (lua_State *L, const char *msg);
319 320
320 321
321#define luaE_exitCcall(L) ((L)->nCcalls--) 322#define luaE_exitCcall(L) ((L)->nCcalls--)
diff --git a/ltests.c b/ltests.c
index 5ea8b080..0cb6d3a7 100644
--- a/ltests.c
+++ b/ltests.c
@@ -63,7 +63,11 @@ static void pushobject (lua_State *L, const TValue *o) {
63} 63}
64 64
65 65
66static void badexit (void) { 66static void badexit (const char *fmt, ...) {
67 va_list argp;
68 va_start(argp, fmt);
69 vfprintf(stderr, fmt, argp);
70 va_end(argp);
67 /* avoid assertion failures when exiting */ 71 /* avoid assertion failures when exiting */
68 l_memcontrol.numblocks = l_memcontrol.total = 0; 72 l_memcontrol.numblocks = l_memcontrol.total = 0;
69 exit(EXIT_FAILURE); 73 exit(EXIT_FAILURE);
@@ -71,9 +75,9 @@ static void badexit (void) {
71 75
72 76
73static int tpanic (lua_State *L) { 77static int tpanic (lua_State *L) {
74 fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", 78 return (badexit("PANIC: unprotected error in call to Lua API (%s)\n",
75 lua_tostring(L, -1)); 79 lua_tostring(L, -1)),
76 return (badexit(), 0); /* do not return to Lua */ 80 0); /* do not return to Lua */
77} 81}
78 82
79 83
@@ -83,16 +87,47 @@ static int islast (const char *message) {
83} 87}
84 88
85 89
90/*
91** Warning function for tests. Fist, it concatenates all parts of
92** a warning in buffer 'buff'. Then:
93** messages starting with '#' are shown on standard output (used to
94** test explicit warnings);
95** messages containing '@' are stored in global '_WARN' (used to test
96** errors that generate warnings);
97** other messages abort the tests (they represent real warning conditions;
98** the standard tests should not generate these conditions unexpectedly).
99*/
86static void warnf (void **pud, const char *msg) { 100static void warnf (void **pud, const char *msg) {
87 if (*pud == NULL) /* continuation line? */ 101 static char buff[200]; /* should be enough for tests... */
88 printf("%s", msg); /* print it */ 102 static int cont = 0; /* message to be continued */
89 else if (msg[0] == '*') /* expected warning? */ 103 if (cont) { /* continuation? */
90 printf("Expected Lua warning: %s", msg + 1); /* print without the star */ 104 if (strlen(msg) >= sizeof(buff) - strlen(buff))
91 else { /* a real warning; should not happen during tests */ 105 badexit("warnf-buffer overflow");
92 fprintf(stderr, "Warning in test mode (%s), aborting...\n", msg); 106 strcat(buff, msg); /* add new message to current warning */
93 badexit(); 107 }
94 } 108 else { /* new warning */
95 *pud = islast(msg) ? pud : NULL; 109 if (strlen(msg) >= sizeof(buff))
110 badexit("warnf-buffer overflow");
111 strcpy(buff, msg); /* start a new warning */
112 }
113 if (!islast(msg)) /* message not finished yet? */
114 cont = 1; /* wait for more */
115 else { /* handle message */
116 cont = 0; /* prepare for next message */
117 if (buff[0] == '#') /* expected warning? */
118 printf("Expected Lua warning: %s", buff); /* print it */
119 else if (strchr(buff, '@') != NULL) { /* warning for test purposes? */
120 lua_State *L = cast(lua_State *, *pud);
121 lua_unlock(L);
122 lua_pushstring(L, buff);
123 lua_setglobal(L, "_WARN"); /* assign message to global '_WARN' */
124 lua_lock(L);
125 return;
126 }
127 else { /* a real warning; should not happen during tests */
128 badexit("Unexpected warning in test mode: %s\naborting...\n", buff);
129 }
130 }
96} 131}
97 132
98 133
diff --git a/lua.h b/lua.h
index a6f8268b..b777624e 100644
--- a/lua.h
+++ b/lua.h
@@ -51,8 +51,7 @@
51#define LUA_ERRRUN 2 51#define LUA_ERRRUN 2
52#define LUA_ERRSYNTAX 3 52#define LUA_ERRSYNTAX 3
53#define LUA_ERRMEM 4 53#define LUA_ERRMEM 4
54#define LUA_ERRGCMM 5 54#define LUA_ERRERR 5
55#define LUA_ERRERR 6
56 55
57 56
58typedef struct lua_State lua_State; 57typedef struct lua_State lua_State;
diff --git a/manual/manual.of b/manual/manual.of
index 196ea1ef..d64f0f1a 100644
--- a/manual/manual.of
+++ b/manual/manual.of
@@ -722,8 +722,6 @@ Lua calls the finalizers of all objects marked for finalization,
722following the reverse order that they were marked. 722following the reverse order that they were marked.
723If any finalizer marks objects for collection during that phase, 723If any finalizer marks objects for collection during that phase,
724these marks have no effect. 724these marks have no effect.
725If any finalizer raises an error during that phase,
726its execution is interrupted but the error is ignored.
727 725
728Finalizers cannot yield. 726Finalizers cannot yield.
729 727
@@ -2645,8 +2643,7 @@ by looking only at its arguments
2645The third field, @T{x}, 2643The third field, @T{x},
2646tells whether the function may raise errors: 2644tells whether the function may raise errors:
2647@Char{-} means the function never raises any error; 2645@Char{-} means the function never raises any error;
2648@Char{m} means the function may raise out-of-memory errors 2646@Char{m} means the function may raise only out-of-memory errors;
2649and errors running a finalizer;
2650@Char{v} means the function may raise the errors explained in the text; 2647@Char{v} means the function may raise the errors explained in the text;
2651@Char{e} means the function can run arbitrary Lua code, 2648@Char{e} means the function can run arbitrary Lua code,
2652either directly or through metamethods, 2649either directly or through metamethods,
@@ -3364,12 +3361,6 @@ syntax error during precompilation;}
3364@item{@Lid{LUA_ERRMEM}| 3361@item{@Lid{LUA_ERRMEM}|
3365@x{memory allocation (out-of-memory) error};} 3362@x{memory allocation (out-of-memory) error};}
3366 3363
3367@item{@Lid{LUA_ERRGCMM}|
3368error while running a @idx{__gc} metamethod.
3369(This error has no relation with the chunk being loaded.
3370It is generated by the garbage collector.)
3371}
3372
3373} 3364}
3374 3365
3375The @id{lua_load} function uses a user-supplied @id{reader} function 3366The @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}.
3564error while running the @x{message handler}. 3555error while running the @x{message handler}.
3565} 3556}
3566 3557
3567@item{@defid{LUA_ERRGCMM}|
3568error while running a @idx{__gc} metamethod.
3569For such errors, Lua does not call the @x{message handler}
3570(as this kind of error typically has no relation
3571with the function being called).
3572}
3573
3574} 3558}
3575 3559
3576} 3560}
@@ -6298,6 +6282,8 @@ The current value of this variable is @St{Lua 5.4}.
6298@LibEntry{warn (message)| 6282@LibEntry{warn (message)|
6299 6283
6300Emits a warning with the given message. 6284Emits a warning with the given message.
6285Note that messages not ending with an end-of-line
6286are assumed to be continued by the message in the next call.
6301 6287
6302} 6288}
6303 6289
@@ -8773,6 +8759,12 @@ so there is no need to check whether they are using the same
8773address space.) 8759address space.)
8774} 8760}
8775 8761
8762@item{
8763The constant @Lid{LUA_ERRGCMM} was removed.
8764Errors in finalizers are never propagated;
8765instead, they generate a warning.
8766}
8767
8776} 8768}
8777 8769
8778} 8770}
diff --git a/testes/all.lua b/testes/all.lua
index bde4195e..506afad2 100644
--- a/testes/all.lua
+++ b/testes/all.lua
@@ -190,12 +190,17 @@ assert(dofile('verybig.lua', true) == 10); collectgarbage()
190dofile('files.lua') 190dofile('files.lua')
191 191
192if #msgs > 0 then 192if #msgs > 0 then
193 warn("*tests not performed:\n ") 193 warn("#tests not performed:\n ")
194 for i=1,#msgs do 194 for i=1,#msgs do
195 warn(msgs[i]); warn("\n ") 195 warn(msgs[i]); warn("\n ")
196 end 196 end
197 warn("\n")
197end 198end
198 199
200print("(there should be two warnings now)")
201warn("#This is "); warn("an expected"); warn(" warning\n")
202warn("#This is"); warn(" another one\n")
203
199-- no test module should define 'debug' 204-- no test module should define 'debug'
200assert(debug == nil) 205assert(debug == nil)
201 206
@@ -219,10 +224,6 @@ local _G, showmem, print, format, clock, time, difftime, assert, open =
219local fname = T and "time-debug.txt" or "time.txt" 224local fname = T and "time-debug.txt" or "time.txt"
220local lasttime 225local lasttime
221 226
222
223warn("*This is "); warn("an expected"); warn(" warning\n")
224warn("*This is"); warn(" another one\n")
225
226if not usertests then 227if not usertests then
227 -- open file with time of last performed test 228 -- open file with time of last performed test
228 local f = io.open(fname) 229 local f = io.open(fname)
diff --git a/testes/api.lua b/testes/api.lua
index b4d63866..893a36cb 100644
--- a/testes/api.lua
+++ b/testes/api.lua
@@ -114,13 +114,12 @@ end
114 114
115-- testing warnings 115-- testing warnings
116T.testC([[ 116T.testC([[
117 warning "*This " 117 warning "#This shold be a"
118 warning "warning " 118 warning " single "
119 warning "should be in a" 119 warning "warning
120 warning " single line
121" 120"
122 warning "*This should be " 121 warning "#This should be "
123 warning "another warning 122 warning "another one
124" 123"
125]]) 124]])
126 125
@@ -896,24 +895,15 @@ do -- testing errors during GC
896 a[i] = T.newuserdata(i) -- creates several udata 895 a[i] = T.newuserdata(i) -- creates several udata
897 end 896 end
898 for i=1,20,2 do -- mark half of them to raise errors during GC 897 for i=1,20,2 do -- mark half of them to raise errors during GC
899 debug.setmetatable(a[i], {__gc = function (x) error("error inside gc") end}) 898 debug.setmetatable(a[i],
899 {__gc = function (x) error("@expected error in gc") end})
900 end 900 end
901 for i=2,20,2 do -- mark the other half to count and to create more garbage 901 for i=2,20,2 do -- mark the other half to count and to create more garbage
902 debug.setmetatable(a[i], {__gc = function (x) load("A=A+1")() end}) 902 debug.setmetatable(a[i], {__gc = function (x) load("A=A+1")() end})
903 end 903 end
904 a = nil
904 _G.A = 0 905 _G.A = 0
905 a = 0 906 collectgarbage()
906 while 1 do
907 local stat, msg = pcall(collectgarbage)
908 if stat then
909 break -- stop when no more errors
910 else
911 a = a + 1
912 assert(string.find(msg, "__gc"))
913 end
914 end
915 assert(a == 10) -- number of errors
916
917 assert(A == 10) -- number of normal collections 907 assert(A == 10) -- number of normal collections
918 collectgarbage("restart") 908 collectgarbage("restart")
919end 909end
diff --git a/testes/gc.lua b/testes/gc.lua
index 8b9179c8..84e8ffb7 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -353,40 +353,36 @@ GC()
353 353
354 354
355-- testing errors during GC 355-- testing errors during GC
356do 356if T then
357collectgarbage("stop") -- stop collection 357 collectgarbage("stop") -- stop collection
358local u = {} 358 local u = {}
359local s = {}; setmetatable(s, {__mode = 'k'}) 359 local s = {}; setmetatable(s, {__mode = 'k'})
360setmetatable(u, {__gc = function (o) 360 setmetatable(u, {__gc = function (o)
361 local i = s[o] 361 local i = s[o]
362 s[i] = true 362 s[i] = true
363 assert(not s[i - 1]) -- check proper finalization order 363 assert(not s[i - 1]) -- check proper finalization order
364 if i == 8 then error("here") end -- error during GC 364 if i == 8 then error("@expected@") end -- error during GC
365end}) 365 end})
366 366
367for i = 6, 10 do 367 for i = 6, 10 do
368 local n = setmetatable({}, getmetatable(u)) 368 local n = setmetatable({}, getmetatable(u))
369 s[n] = i 369 s[n] = i
370end 370 end
371
372assert(not pcall(collectgarbage))
373for i = 8, 10 do assert(s[i]) end
374
375for i = 1, 5 do
376 local n = setmetatable({}, getmetatable(u))
377 s[n] = i
378end
379 371
380collectgarbage() 372 collectgarbage()
381for i = 1, 10 do assert(s[i]) end 373 assert(string.find(_WARN, "error in __gc metamethod"))
374 assert(string.match(_WARN, "@(.-)@") == "expected")
375 for i = 8, 10 do assert(s[i]) end
382 376
383getmetatable(u).__gc = false 377 for i = 1, 5 do
378 local n = setmetatable({}, getmetatable(u))
379 s[n] = i
380 end
384 381
382 collectgarbage()
383 for i = 1, 10 do assert(s[i]) end
385 384
386-- __gc errors with non-string messages 385 getmetatable(u).__gc = false
387setmetatable({}, {__gc = function () error{} end})
388local a, b = pcall(collectgarbage)
389assert(not a and type(b) == "string" and string.find(b, "error in __gc"))
390 386
391end 387end
392print '+' 388print '+'
@@ -478,9 +474,11 @@ end
478 474
479 475
480-- errors during collection 476-- errors during collection
481u = setmetatable({}, {__gc = function () error "!!!" end}) 477if T then
482u = nil 478 u = setmetatable({}, {__gc = function () error "@expected error" end})
483assert(not pcall(collectgarbage)) 479 u = nil
480 collectgarbage()
481end
484 482
485 483
486if not _soft then 484if not _soft then
@@ -645,11 +643,26 @@ do
645end 643end
646 644
647-- create several objects to raise errors when collected while closing state 645-- create several objects to raise errors when collected while closing state
648do 646if T then
649 local mt = {__gc = function (o) return o + 1 end} 647 local error, assert, warn, find = error, assert, warn, string.find
650 for i = 1,10 do 648 local n = 0
649 local lastmsg
650 local mt = {__gc = function (o)
651 n = n + 1
652 assert(n == o[1])
653 if n == 1 then
654 _WARN = nil
655 elseif n == 2 then
656 assert(find(_WARN, "@expected warning"))
657 lastmsg = _WARN -- get message from previous error (first 'o')
658 else
659 assert(lastmsg == _WARN) -- subsequent error messages are equal
660 end
661 error"@expected warning"
662 end}
663 for i = 10, 1, -1 do
651 -- create object and preserve it until the end 664 -- create object and preserve it until the end
652 table.insert(___Glob, setmetatable({}, mt)) 665 table.insert(___Glob, setmetatable({i}, mt))
653 end 666 end
654end 667end
655 668