diff options
| author | Roberto I <roberto@inf.puc-rio.br> | 2026-01-11 15:36:03 -0300 |
|---|---|---|
| committer | Roberto I <roberto@inf.puc-rio.br> | 2026-01-11 15:36:03 -0300 |
| commit | 2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded (patch) | |
| tree | a99a8361664b0adda83186f04e2e9c98afd86b44 | |
| parent | 5cfc725a8b61a6f96c7324f60ac26739315095ba (diff) | |
| download | lua-2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded.tar.gz lua-2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded.tar.bz2 lua-2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded.zip | |
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.c | 20 | ||||
| -rw-r--r-- | lgc.c | 2 | ||||
| -rw-r--r-- | lstate.c | 17 | ||||
| -rw-r--r-- | lstate.h | 2 | ||||
| -rw-r--r-- | ltests.c | 23 | ||||
| -rw-r--r-- | testes/gc.lua | 42 | ||||
| -rw-r--r-- | testes/memerr.lua | 19 | ||||
| -rw-r--r-- | testes/tracegc.lua | 9 |
8 files changed, 118 insertions, 16 deletions
| @@ -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 | */ |
| 228 | int luaD_checkminstack (lua_State *L) { | 228 | int 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 | /* |
| @@ -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 | ||
| @@ -68,14 +68,19 @@ void luaE_setdebt (global_State *g, l_mem debt) { | |||
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | 70 | ||
| 71 | CallInfo *luaE_extendCI (lua_State *L) { | 71 | CallInfo *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; |
| @@ -438,7 +438,7 @@ union GCUnion { | |||
| 438 | LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); | 438 | LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); |
| 439 | LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); | 439 | LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); |
| 440 | LUAI_FUNC lu_mem luaE_threadsize (lua_State *L); | 440 | LUAI_FUNC lu_mem luaE_threadsize (lua_State *L); |
| 441 | LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); | 441 | LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L, int err); |
| 442 | LUAI_FUNC void luaE_shrinkCI (lua_State *L); | 442 | LUAI_FUNC void luaE_shrinkCI (lua_State *L); |
| 443 | LUAI_FUNC void luaE_checkcstack (lua_State *L); | 443 | LUAI_FUNC void luaE_checkcstack (lua_State *L); |
| 444 | LUAI_FUNC void luaE_incCstack (lua_State *L); | 444 | LUAI_FUNC void luaE_incCstack (lua_State *L); |
| @@ -1106,6 +1106,27 @@ static int stacklevel (lua_State *L) { | |||
| 1106 | } | 1106 | } |
| 1107 | 1107 | ||
| 1108 | 1108 | ||
| 1109 | static 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 | |||
| 1121 | static 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 | |||
| 1109 | static int table_query (lua_State *L) { | 1130 | static 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 | ||
| 708 | collectgarbage(oldmode) | 708 | collectgarbage(oldmode) |
| 709 | 709 | ||
| 710 | |||
| 711 | if 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) | ||
| 749 | end | ||
| 750 | |||
| 751 | |||
| 710 | print('OK') | 752 | print('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) |
| 283 | end) | 283 | end) |
| 284 | 284 | ||
| 285 | |||
| 286 | collectgarbage() | ||
| 287 | collectgarbage() | ||
| 288 | global io, T, setmetatable, collectgarbage, print | ||
| 289 | |||
| 290 | local Count = 0 | ||
| 291 | testamem("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 | ||
| 302 | end) | ||
| 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 | |||
| 3 | local M = {} | 4 | local M = {} |
| 4 | 5 | ||
| 5 | -- import list | 6 | -- import list |
| 6 | local setmetatable, stderr, collectgarbage = | 7 | local 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 | ||
| 12 | local setmetatable = require"debug".setmetatable | ||
| 8 | 13 | ||
| 9 | global none | 14 | global none |
| 10 | 15 | ||
