aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto I <roberto@inf.puc-rio.br>2026-01-11 15:36:03 -0300
committerRoberto I <roberto@inf.puc-rio.br>2026-01-11 15:36:03 -0300
commit2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded (patch)
treea99a8361664b0adda83186f04e2e9c98afd86b44
parent5cfc725a8b61a6f96c7324f60ac26739315095ba (diff)
downloadlua-2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded.tar.gz
lua-2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded.tar.bz2
lua-2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded.zip
More effort in avoiding errors in finalizersHEADmaster
Before calling a finalizer, Lua not only checks stack limits, but actually ensures that a minimum number of slots are already allocated for the call. (If it cannot ensure that, it postpones the finalizer.) That avoids finalizers not running due to memory errors that the programmer cannot control.
-rw-r--r--ldo.c20
-rw-r--r--lgc.c2
-rw-r--r--lstate.c17
-rw-r--r--lstate.h2
-rw-r--r--ltests.c23
-rw-r--r--testes/gc.lua42
-rw-r--r--testes/memerr.lua19
-rw-r--r--testes/tracegc.lua9
8 files changed, 118 insertions, 16 deletions
diff --git a/ldo.c b/ldo.c
index 6d0184ec..12e0364b 100644
--- a/ldo.c
+++ b/ldo.c
@@ -221,13 +221,21 @@ l_noret luaD_errerr (lua_State *L) {
221 221
222 222
223/* 223/*
224** Check whether stack has enough space to run a simple function (such 224** Check whether stacks have enough space to run a simple function (such
225** as a finalizer): At least BASIC_STACK_SIZE in the Lua stack and 225** as a finalizer): At least BASIC_STACK_SIZE in the Lua stack, two
226** 2 slots in the C stack. 226** available CallInfos, and two "slots" in the C stack.
227*/ 227*/
228int luaD_checkminstack (lua_State *L) { 228int luaD_checkminstack (lua_State *L) {
229 return ((stacksize(L) < MAXSTACK - BASIC_STACK_SIZE) && 229 if (getCcalls(L) >= LUAI_MAXCCALLS - 2)
230 (getCcalls(L) < LUAI_MAXCCALLS - 2)); 230 return 0; /* not enough C-stack slots */
231 if (L->ci->next == NULL && luaE_extendCI(L, 0) == NULL)
232 return 0; /* unable to allocate first ci */
233 if (L->ci->next->next == NULL && luaE_extendCI(L, 0) == NULL)
234 return 0; /* unable to allocate second ci */
235 if (L->stack_last.p - L->top.p >= BASIC_STACK_SIZE)
236 return 1; /* enough (BASIC_STACK_SIZE) free slots in the Lua stack */
237 else /* try to grow stack to a size with enough free slots */
238 return luaD_growstack(L, BASIC_STACK_SIZE, 0);
231} 239}
232 240
233 241
@@ -616,7 +624,7 @@ void luaD_poscall (lua_State *L, CallInfo *ci, int nres) {
616 624
617 625
618 626
619#define next_ci(L) (L->ci->next ? L->ci->next : luaE_extendCI(L)) 627#define next_ci(L) (L->ci->next ? L->ci->next : luaE_extendCI(L, 1))
620 628
621 629
622/* 630/*
diff --git a/lgc.c b/lgc.c
index f1d9a7ce..0f89451c 100644
--- a/lgc.c
+++ b/lgc.c
@@ -1293,7 +1293,7 @@ static void finishgencycle (lua_State *L, global_State *g) {
1293 correctgraylists(g); 1293 correctgraylists(g);
1294 checkSizes(L, g); 1294 checkSizes(L, g);
1295 g->gcstate = GCSpropagate; /* skip restart */ 1295 g->gcstate = GCSpropagate; /* skip restart */
1296 if (!g->gcemergency && luaD_checkminstack(L)) 1296 if (g->tobefnz != NULL && !g->gcemergency && luaD_checkminstack(L))
1297 callallpendingfinalizers(L); 1297 callallpendingfinalizers(L);
1298} 1298}
1299 1299
diff --git a/lstate.c b/lstate.c
index 70a11aae..7d341991 100644
--- a/lstate.c
+++ b/lstate.c
@@ -68,14 +68,19 @@ void luaE_setdebt (global_State *g, l_mem debt) {
68} 68}
69 69
70 70
71CallInfo *luaE_extendCI (lua_State *L) { 71CallInfo *luaE_extendCI (lua_State *L, int err) {
72 CallInfo *ci; 72 CallInfo *ci;
73 lua_assert(L->ci->next == NULL); 73 ci = luaM_reallocvector(L, NULL, 0, 1, CallInfo);
74 ci = luaM_new(L, CallInfo); 74 if (l_unlikely(ci == NULL)) { /* allocation failed? */
75 lua_assert(L->ci->next == NULL); 75 if (err)
76 L->ci->next = ci; 76 luaM_error(L); /* raise the error */
77 return NULL; /* else only report it */
78 }
79 ci->next = L->ci->next;
77 ci->previous = L->ci; 80 ci->previous = L->ci;
78 ci->next = NULL; 81 L->ci->next = ci;
82 if (ci->next)
83 ci->next->previous = ci;
79 ci->u.l.trap = 0; 84 ci->u.l.trap = 0;
80 L->nci++; 85 L->nci++;
81 return ci; 86 return ci;
diff --git a/lstate.h b/lstate.h
index 20dc4d24..01387283 100644
--- a/lstate.h
+++ b/lstate.h
@@ -438,7 +438,7 @@ union GCUnion {
438LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); 438LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt);
439LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); 439LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1);
440LUAI_FUNC lu_mem luaE_threadsize (lua_State *L); 440LUAI_FUNC lu_mem luaE_threadsize (lua_State *L);
441LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); 441LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L, int err);
442LUAI_FUNC void luaE_shrinkCI (lua_State *L); 442LUAI_FUNC void luaE_shrinkCI (lua_State *L);
443LUAI_FUNC void luaE_checkcstack (lua_State *L); 443LUAI_FUNC void luaE_checkcstack (lua_State *L);
444LUAI_FUNC void luaE_incCstack (lua_State *L); 444LUAI_FUNC void luaE_incCstack (lua_State *L);
diff --git a/ltests.c b/ltests.c
index c4905f94..ce2b20ca 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1106,6 +1106,27 @@ static int stacklevel (lua_State *L) {
1106} 1106}
1107 1107
1108 1108
1109static int resetCI (lua_State *L) {
1110 CallInfo *ci = L->ci;
1111 while (ci->next != NULL) {
1112 CallInfo *tofree = ci->next;
1113 ci->next = ci->next->next;
1114 luaM_free(L, tofree);
1115 L->nci--;
1116 }
1117 return 0;
1118}
1119
1120
1121static int reallocstack (lua_State *L) {
1122 int n = cast_int(luaL_checkinteger(L, 1));
1123 lua_lock(L);
1124 luaD_reallocstack(L, cast_int(L->top.p - L->stack.p) + n, 1);
1125 lua_unlock(L);
1126 return 0;
1127}
1128
1129
1109static int table_query (lua_State *L) { 1130static int table_query (lua_State *L) {
1110 const Table *t; 1131 const Table *t;
1111 int i = cast_int(luaL_optinteger(L, 2, -1)); 1132 int i = cast_int(luaL_optinteger(L, 2, -1));
@@ -2182,6 +2203,8 @@ static const struct luaL_Reg tests_funcs[] = {
2182 {"s2d", s2d}, 2203 {"s2d", s2d},
2183 {"sethook", sethook}, 2204 {"sethook", sethook},
2184 {"stacklevel", stacklevel}, 2205 {"stacklevel", stacklevel},
2206 {"resetCI", resetCI},
2207 {"reallocstack", reallocstack},
2185 {"sizes", get_sizes}, 2208 {"sizes", get_sizes},
2186 {"testC", testC}, 2209 {"testC", testC},
2187 {"makeCfunc", makeCfunc}, 2210 {"makeCfunc", makeCfunc},
diff --git a/testes/gc.lua b/testes/gc.lua
index 62713dac..e50d9029 100644
--- a/testes/gc.lua
+++ b/testes/gc.lua
@@ -707,4 +707,46 @@ end
707 707
708collectgarbage(oldmode) 708collectgarbage(oldmode)
709 709
710
711if T then
712 print("testing stack issues when calling finalizers")
713
714 local X
715 local obj
716
717 local function initobj ()
718 X = false
719 obj = setmetatable({}, {__gc = function () X = true end})
720 end
721
722 local function loop (n)
723 if n > 0 then loop(n - 1) end
724 end
725
726 -- should not try to call finalizer without a CallInfo available
727 initobj()
728 loop(20) -- ensure stack space
729 T.resetCI() -- remove extra CallInfos
730 T.alloccount(0) -- cannot allocate more CallInfos
731 obj = nil
732 collectgarbage() -- will not call finalizer
733 T.alloccount()
734 assert(X == false)
735 collectgarbage() -- now will call finalizer (it was still pending)
736 assert(X == true)
737
738 -- should not try to call finalizer without stack space available
739 initobj()
740 loop(5) -- ensure enough CallInfos
741 T.reallocstack(0) -- remove extra stack slots
742 T.alloccount(0) -- cannot reallocate stack
743 obj = nil
744 collectgarbage() -- will not call finalizer
745 T.alloccount()
746 assert(X == false)
747 collectgarbage() -- now will call finalizer (it was still pending)
748 assert(X == true)
749end
750
751
710print('OK') 752print('OK')
diff --git a/testes/memerr.lua b/testes/memerr.lua
index 9c940ca7..a55514a9 100644
--- a/testes/memerr.lua
+++ b/testes/memerr.lua
@@ -282,6 +282,25 @@ testamem("growing stack", function ()
282 return foo(100) 282 return foo(100)
283end) 283end)
284 284
285
286collectgarbage()
287collectgarbage()
288global io, T, setmetatable, collectgarbage, print
289
290local Count = 0
291testamem("finalizers", function ()
292 local X = false
293 local obj = setmetatable({}, {__gc = function () X = true end})
294 obj = nil
295 T.resetCI() -- remove extra CallInfos
296 T.reallocstack(18) -- remove extra stack slots
297 Count = Count + 1
298 io.stderr:write(Count, "\n")
299 T.trick(io)
300 collectgarbage()
301 return X
302end)
303
285-- }================================================================== 304-- }==================================================================
286 305
287 306
diff --git a/testes/tracegc.lua b/testes/tracegc.lua
index a8c929df..c1154f90 100644
--- a/testes/tracegc.lua
+++ b/testes/tracegc.lua
@@ -1,10 +1,15 @@
1-- track collections 1-- track collections
2 2
3
3local M = {} 4local M = {}
4 5
5-- import list 6-- import list
6local setmetatable, stderr, collectgarbage = 7local stderr, collectgarbage = io.stderr, collectgarbage
7 setmetatable, io.stderr, collectgarbage 8
9-- the debug version of setmetatable does not create any object (such as
10-- a '__metatable' string), and so it is more appropriate to be used in
11-- a finalizer
12local setmetatable = require"debug".setmetatable
8 13
9global none 14global none
10 15