diff options
| author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-12-13 10:41:17 -0300 |
|---|---|---|
| committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-12-13 10:41:17 -0300 |
| commit | 0bfc572e51d9035a615ef6e9523f736c9ffa8e57 (patch) | |
| tree | 218f2bb13a873becf8fa657a296c8863f7e0e466 | |
| parent | 1de95e97ef65632a88e08b6184bd9d1ceba7ec2f (diff) | |
| download | lua-0bfc572e51d9035a615ef6e9523f736c9ffa8e57.tar.gz lua-0bfc572e51d9035a615ef6e9523f736c9ffa8e57.tar.bz2 lua-0bfc572e51d9035a615ef6e9523f736c9ffa8e57.zip | |
Bug: GC is not reentrant
As the GC is not reentrant, finalizers should not be able to invoke it.
| -rw-r--r-- | lapi.c | 17 | ||||
| -rw-r--r-- | lbaselib.c | 19 | ||||
| -rw-r--r-- | lgc.c | 11 | ||||
| -rw-r--r-- | lgc.h | 9 | ||||
| -rw-r--r-- | lstate.c | 4 | ||||
| -rw-r--r-- | lstate.h | 2 | ||||
| -rw-r--r-- | manual/manual.of | 11 | ||||
| -rw-r--r-- | testes/api.lua | 5 | ||||
| -rw-r--r-- | testes/gc.lua | 6 |
9 files changed, 57 insertions, 27 deletions
| @@ -1136,18 +1136,19 @@ LUA_API int lua_status (lua_State *L) { | |||
| 1136 | LUA_API int lua_gc (lua_State *L, int what, ...) { | 1136 | LUA_API int lua_gc (lua_State *L, int what, ...) { |
| 1137 | va_list argp; | 1137 | va_list argp; |
| 1138 | int res = 0; | 1138 | int res = 0; |
| 1139 | global_State *g; | 1139 | global_State *g = G(L); |
| 1140 | if (g->gcstp & GCSTPGC) /* internal stop? */ | ||
| 1141 | return -1; /* all options are invalid when stopped */ | ||
| 1140 | lua_lock(L); | 1142 | lua_lock(L); |
| 1141 | g = G(L); | ||
| 1142 | va_start(argp, what); | 1143 | va_start(argp, what); |
| 1143 | switch (what) { | 1144 | switch (what) { |
| 1144 | case LUA_GCSTOP: { | 1145 | case LUA_GCSTOP: { |
| 1145 | g->gcrunning = 0; | 1146 | g->gcstp = GCSTPUSR; /* stopeed by the user */ |
| 1146 | break; | 1147 | break; |
| 1147 | } | 1148 | } |
| 1148 | case LUA_GCRESTART: { | 1149 | case LUA_GCRESTART: { |
| 1149 | luaE_setdebt(g, 0); | 1150 | luaE_setdebt(g, 0); |
| 1150 | g->gcrunning = 1; | 1151 | g->gcstp = 0; /* (GCSTPGC must be already zero here) */ |
| 1151 | break; | 1152 | break; |
| 1152 | } | 1153 | } |
| 1153 | case LUA_GCCOLLECT: { | 1154 | case LUA_GCCOLLECT: { |
| @@ -1166,8 +1167,8 @@ LUA_API int lua_gc (lua_State *L, int what, ...) { | |||
| 1166 | case LUA_GCSTEP: { | 1167 | case LUA_GCSTEP: { |
| 1167 | int data = va_arg(argp, int); | 1168 | int data = va_arg(argp, int); |
| 1168 | l_mem debt = 1; /* =1 to signal that it did an actual step */ | 1169 | l_mem debt = 1; /* =1 to signal that it did an actual step */ |
| 1169 | lu_byte oldrunning = g->gcrunning; | 1170 | lu_byte oldstp = g->gcstp; |
| 1170 | g->gcrunning = 1; /* allow GC to run */ | 1171 | g->gcstp = 0; /* allow GC to run (GCSTPGC must be zero here) */ |
| 1171 | if (data == 0) { | 1172 | if (data == 0) { |
| 1172 | luaE_setdebt(g, 0); /* do a basic step */ | 1173 | luaE_setdebt(g, 0); /* do a basic step */ |
| 1173 | luaC_step(L); | 1174 | luaC_step(L); |
| @@ -1177,7 +1178,7 @@ LUA_API int lua_gc (lua_State *L, int what, ...) { | |||
| 1177 | luaE_setdebt(g, debt); | 1178 | luaE_setdebt(g, debt); |
| 1178 | luaC_checkGC(L); | 1179 | luaC_checkGC(L); |
| 1179 | } | 1180 | } |
| 1180 | g->gcrunning = oldrunning; /* restore previous state */ | 1181 | g->gcstp = oldstp; /* restore previous state */ |
| 1181 | if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */ | 1182 | if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */ |
| 1182 | res = 1; /* signal it */ | 1183 | res = 1; /* signal it */ |
| 1183 | break; | 1184 | break; |
| @@ -1195,7 +1196,7 @@ LUA_API int lua_gc (lua_State *L, int what, ...) { | |||
| 1195 | break; | 1196 | break; |
| 1196 | } | 1197 | } |
| 1197 | case LUA_GCISRUNNING: { | 1198 | case LUA_GCISRUNNING: { |
| 1198 | res = g->gcrunning; | 1199 | res = gcrunning(g); |
| 1199 | break; | 1200 | break; |
| 1200 | } | 1201 | } |
| 1201 | case LUA_GCGEN: { | 1202 | case LUA_GCGEN: { |
| @@ -182,12 +182,20 @@ static int luaB_rawset (lua_State *L) { | |||
| 182 | 182 | ||
| 183 | 183 | ||
| 184 | static int pushmode (lua_State *L, int oldmode) { | 184 | static int pushmode (lua_State *L, int oldmode) { |
| 185 | lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental" | 185 | if (oldmode == -1) |
| 186 | : "generational"); | 186 | luaL_pushfail(L); /* invalid call to 'lua_gc' */ |
| 187 | else | ||
| 188 | lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental" | ||
| 189 | : "generational"); | ||
| 187 | return 1; | 190 | return 1; |
| 188 | } | 191 | } |
| 189 | 192 | ||
| 190 | 193 | ||
| 194 | /* | ||
| 195 | ** check whether call to 'lua_gc' was valid (not inside a finalizer) | ||
| 196 | */ | ||
| 197 | #define checkvalres(res) { if (res == -1) break; } | ||
| 198 | |||
| 191 | static int luaB_collectgarbage (lua_State *L) { | 199 | static int luaB_collectgarbage (lua_State *L) { |
| 192 | static const char *const opts[] = {"stop", "restart", "collect", | 200 | static const char *const opts[] = {"stop", "restart", "collect", |
| 193 | "count", "step", "setpause", "setstepmul", | 201 | "count", "step", "setpause", "setstepmul", |
| @@ -200,12 +208,14 @@ static int luaB_collectgarbage (lua_State *L) { | |||
| 200 | case LUA_GCCOUNT: { | 208 | case LUA_GCCOUNT: { |
| 201 | int k = lua_gc(L, o); | 209 | int k = lua_gc(L, o); |
| 202 | int b = lua_gc(L, LUA_GCCOUNTB); | 210 | int b = lua_gc(L, LUA_GCCOUNTB); |
| 211 | checkvalres(k); | ||
| 203 | lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024)); | 212 | lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024)); |
| 204 | return 1; | 213 | return 1; |
| 205 | } | 214 | } |
| 206 | case LUA_GCSTEP: { | 215 | case LUA_GCSTEP: { |
| 207 | int step = (int)luaL_optinteger(L, 2, 0); | 216 | int step = (int)luaL_optinteger(L, 2, 0); |
| 208 | int res = lua_gc(L, o, step); | 217 | int res = lua_gc(L, o, step); |
| 218 | checkvalres(res); | ||
| 209 | lua_pushboolean(L, res); | 219 | lua_pushboolean(L, res); |
| 210 | return 1; | 220 | return 1; |
| 211 | } | 221 | } |
| @@ -213,11 +223,13 @@ static int luaB_collectgarbage (lua_State *L) { | |||
| 213 | case LUA_GCSETSTEPMUL: { | 223 | case LUA_GCSETSTEPMUL: { |
| 214 | int p = (int)luaL_optinteger(L, 2, 0); | 224 | int p = (int)luaL_optinteger(L, 2, 0); |
| 215 | int previous = lua_gc(L, o, p); | 225 | int previous = lua_gc(L, o, p); |
| 226 | checkvalres(previous); | ||
| 216 | lua_pushinteger(L, previous); | 227 | lua_pushinteger(L, previous); |
| 217 | return 1; | 228 | return 1; |
| 218 | } | 229 | } |
| 219 | case LUA_GCISRUNNING: { | 230 | case LUA_GCISRUNNING: { |
| 220 | int res = lua_gc(L, o); | 231 | int res = lua_gc(L, o); |
| 232 | checkvalres(res); | ||
| 221 | lua_pushboolean(L, res); | 233 | lua_pushboolean(L, res); |
| 222 | return 1; | 234 | return 1; |
| 223 | } | 235 | } |
| @@ -234,10 +246,13 @@ static int luaB_collectgarbage (lua_State *L) { | |||
| 234 | } | 246 | } |
| 235 | default: { | 247 | default: { |
| 236 | int res = lua_gc(L, o); | 248 | int res = lua_gc(L, o); |
| 249 | checkvalres(res); | ||
| 237 | lua_pushinteger(L, res); | 250 | lua_pushinteger(L, res); |
| 238 | return 1; | 251 | return 1; |
| 239 | } | 252 | } |
| 240 | } | 253 | } |
| 254 | luaL_pushfail(L); /* invalid call (inside a finalizer) */ | ||
| 255 | return 1; | ||
| 241 | } | 256 | } |
| 242 | 257 | ||
| 243 | 258 | ||
| @@ -906,16 +906,16 @@ static void GCTM (lua_State *L) { | |||
| 906 | if (!notm(tm)) { /* is there a finalizer? */ | 906 | if (!notm(tm)) { /* is there a finalizer? */ |
| 907 | int status; | 907 | int status; |
| 908 | lu_byte oldah = L->allowhook; | 908 | lu_byte oldah = L->allowhook; |
| 909 | int running = g->gcrunning; | 909 | int oldgcstp = g->gcstp; |
| 910 | g->gcstp = GCSTPGC; /* avoid GC steps */ | ||
| 910 | L->allowhook = 0; /* stop debug hooks during GC metamethod */ | 911 | L->allowhook = 0; /* stop debug hooks during GC metamethod */ |
| 911 | g->gcrunning = 0; /* avoid GC steps */ | ||
| 912 | setobj2s(L, L->top++, tm); /* push finalizer... */ | 912 | setobj2s(L, L->top++, tm); /* push finalizer... */ |
| 913 | setobj2s(L, L->top++, &v); /* ... and its argument */ | 913 | setobj2s(L, L->top++, &v); /* ... and its argument */ |
| 914 | L->ci->callstatus |= CIST_FIN; /* will run a finalizer */ | 914 | L->ci->callstatus |= CIST_FIN; /* will run a finalizer */ |
| 915 | status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0); | 915 | status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0); |
| 916 | L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */ | 916 | L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */ |
| 917 | L->allowhook = oldah; /* restore hooks */ | 917 | L->allowhook = oldah; /* restore hooks */ |
| 918 | g->gcrunning = running; /* restore state */ | 918 | g->gcstp = oldgcstp; /* restore state */ |
| 919 | if (l_unlikely(status != LUA_OK)) { /* error while running __gc? */ | 919 | if (l_unlikely(status != LUA_OK)) { /* error while running __gc? */ |
| 920 | luaE_warnerror(L, "__gc metamethod"); | 920 | luaE_warnerror(L, "__gc metamethod"); |
| 921 | L->top--; /* pops error object */ | 921 | L->top--; /* pops error object */ |
| @@ -1502,9 +1502,11 @@ static void deletelist (lua_State *L, GCObject *p, GCObject *limit) { | |||
| 1502 | */ | 1502 | */ |
| 1503 | void luaC_freeallobjects (lua_State *L) { | 1503 | void luaC_freeallobjects (lua_State *L) { |
| 1504 | global_State *g = G(L); | 1504 | global_State *g = G(L); |
| 1505 | g->gcstp = GCSTPGC; | ||
| 1505 | luaC_changemode(L, KGC_INC); | 1506 | luaC_changemode(L, KGC_INC); |
| 1506 | separatetobefnz(g, 1); /* separate all objects with finalizers */ | 1507 | separatetobefnz(g, 1); /* separate all objects with finalizers */ |
| 1507 | lua_assert(g->finobj == NULL); | 1508 | lua_assert(g->finobj == NULL); |
| 1509 | g->gcstp = 0; | ||
| 1508 | callallpendingfinalizers(L); | 1510 | callallpendingfinalizers(L); |
| 1509 | deletelist(L, g->allgc, obj2gco(g->mainthread)); | 1511 | deletelist(L, g->allgc, obj2gco(g->mainthread)); |
| 1510 | deletelist(L, g->finobj, NULL); | 1512 | deletelist(L, g->finobj, NULL); |
| @@ -1647,6 +1649,7 @@ void luaC_runtilstate (lua_State *L, int statesmask) { | |||
| 1647 | } | 1649 | } |
| 1648 | 1650 | ||
| 1649 | 1651 | ||
| 1652 | |||
| 1650 | /* | 1653 | /* |
| 1651 | ** Performs a basic incremental step. The debt and step size are | 1654 | ** Performs a basic incremental step. The debt and step size are |
| 1652 | ** converted from bytes to "units of work"; then the function loops | 1655 | ** converted from bytes to "units of work"; then the function loops |
| @@ -1678,7 +1681,7 @@ static void incstep (lua_State *L, global_State *g) { | |||
| 1678 | void luaC_step (lua_State *L) { | 1681 | void luaC_step (lua_State *L) { |
| 1679 | global_State *g = G(L); | 1682 | global_State *g = G(L); |
| 1680 | lua_assert(!g->gcemergency); | 1683 | lua_assert(!g->gcemergency); |
| 1681 | if (g->gcrunning) { /* running? */ | 1684 | if (gcrunning(g)) { /* running? */ |
| 1682 | if(isdecGCmodegen(g)) | 1685 | if(isdecGCmodegen(g)) |
| 1683 | genstep(L, g); | 1686 | genstep(L, g); |
| 1684 | else | 1687 | else |
| @@ -148,6 +148,15 @@ | |||
| 148 | */ | 148 | */ |
| 149 | #define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0) | 149 | #define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0) |
| 150 | 150 | ||
| 151 | |||
| 152 | /* | ||
| 153 | ** Control when GC is running: | ||
| 154 | */ | ||
| 155 | #define GCSTPUSR 1 /* bit true when GC stopped by user */ | ||
| 156 | #define GCSTPGC 2 /* bit true when GC stopped by itself */ | ||
| 157 | #define gcrunning(g) ((g)->gcstp == 0) | ||
| 158 | |||
| 159 | |||
| 151 | /* | 160 | /* |
| 152 | ** Does one step of collection when debt becomes positive. 'pre'/'pos' | 161 | ** Does one step of collection when debt becomes positive. 'pre'/'pos' |
| 153 | ** allows some adjustments to be done only when needed. macro | 162 | ** allows some adjustments to be done only when needed. macro |
| @@ -236,7 +236,7 @@ static void f_luaopen (lua_State *L, void *ud) { | |||
| 236 | luaS_init(L); | 236 | luaS_init(L); |
| 237 | luaT_init(L); | 237 | luaT_init(L); |
| 238 | luaX_init(L); | 238 | luaX_init(L); |
| 239 | g->gcrunning = 1; /* allow gc */ | 239 | g->gcstp = 0; /* allow gc */ |
| 240 | setnilvalue(&g->nilvalue); /* now state is complete */ | 240 | setnilvalue(&g->nilvalue); /* now state is complete */ |
| 241 | luai_userstateopen(L); | 241 | luai_userstateopen(L); |
| 242 | } | 242 | } |
| @@ -373,7 +373,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { | |||
| 373 | g->ud_warn = NULL; | 373 | g->ud_warn = NULL; |
| 374 | g->mainthread = L; | 374 | g->mainthread = L; |
| 375 | g->seed = luai_makeseed(L); | 375 | g->seed = luai_makeseed(L); |
| 376 | g->gcrunning = 0; /* no GC while building state */ | 376 | g->gcstp = GCSTPGC; /* no GC while building state */ |
| 377 | g->strt.size = g->strt.nuse = 0; | 377 | g->strt.size = g->strt.nuse = 0; |
| 378 | g->strt.hash = NULL; | 378 | g->strt.hash = NULL; |
| 379 | setnilvalue(&g->l_registry); | 379 | setnilvalue(&g->l_registry); |
| @@ -263,7 +263,7 @@ typedef struct global_State { | |||
| 263 | lu_byte gcstopem; /* stops emergency collections */ | 263 | lu_byte gcstopem; /* stops emergency collections */ |
| 264 | lu_byte genminormul; /* control for minor generational collections */ | 264 | lu_byte genminormul; /* control for minor generational collections */ |
| 265 | lu_byte genmajormul; /* control for major generational collections */ | 265 | lu_byte genmajormul; /* control for major generational collections */ |
| 266 | lu_byte gcrunning; /* true if GC is running */ | 266 | lu_byte gcstp; /* control whether GC is running */ |
| 267 | lu_byte gcemergency; /* true if this is an emergency collection */ | 267 | lu_byte gcemergency; /* true if this is an emergency collection */ |
| 268 | lu_byte gcpause; /* size of pause between successive GCs */ | 268 | lu_byte gcpause; /* size of pause between successive GCs */ |
| 269 | lu_byte gcstepmul; /* GC "speed" */ | 269 | lu_byte gcstepmul; /* GC "speed" */ |
diff --git a/manual/manual.of b/manual/manual.of index c9e62b49..c660215c 100644 --- a/manual/manual.of +++ b/manual/manual.of | |||
| @@ -787,11 +787,8 @@ following the reverse order that they were marked. | |||
| 787 | If any finalizer marks objects for collection during that phase, | 787 | If any finalizer marks objects for collection during that phase, |
| 788 | these marks have no effect. | 788 | these marks have no effect. |
| 789 | 789 | ||
| 790 | Finalizers cannot yield. | 790 | Finalizers cannot yield nor run the garbage collector. |
| 791 | Except for that, they can do anything, | 791 | Because they can run in unpredictable times, |
| 792 | such as raise errors, create new objects, | ||
| 793 | or even run the garbage collector. | ||
| 794 | However, because they can run in unpredictable times, | ||
| 795 | it is good practice to restrict each finalizer | 792 | it is good practice to restrict each finalizer |
| 796 | to the minimum necessary to properly release | 793 | to the minimum necessary to properly release |
| 797 | its associated resource. | 794 | its associated resource. |
| @@ -3276,6 +3273,8 @@ Returns the previous mode (@id{LUA_GCGEN} or @id{LUA_GCINC}). | |||
| 3276 | For more details about these options, | 3273 | For more details about these options, |
| 3277 | see @Lid{collectgarbage}. | 3274 | see @Lid{collectgarbage}. |
| 3278 | 3275 | ||
| 3276 | This function should not be called by a finalizer. | ||
| 3277 | |||
| 3279 | } | 3278 | } |
| 3280 | 3279 | ||
| 3281 | @APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);| | 3280 | @APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);| |
| @@ -6233,6 +6232,8 @@ A zero means to not change that value. | |||
| 6233 | See @See{GC} for more details about garbage collection | 6232 | See @See{GC} for more details about garbage collection |
| 6234 | and some of these options. | 6233 | and some of these options. |
| 6235 | 6234 | ||
| 6235 | This function should not be called by a finalizer. | ||
| 6236 | |||
| 6236 | } | 6237 | } |
| 6237 | 6238 | ||
| 6238 | @LibEntry{dofile ([filename])| | 6239 | @LibEntry{dofile ([filename])| |
diff --git a/testes/api.lua b/testes/api.lua index c1bcb4b7..bd85a923 100644 --- a/testes/api.lua +++ b/testes/api.lua | |||
| @@ -804,15 +804,14 @@ F = function (x) | |||
| 804 | d = nil | 804 | d = nil |
| 805 | assert(debug.getmetatable(x).__gc == F) | 805 | assert(debug.getmetatable(x).__gc == F) |
| 806 | assert(load("table.insert({}, {})"))() -- create more garbage | 806 | assert(load("table.insert({}, {})"))() -- create more garbage |
| 807 | collectgarbage() -- force a GC during GC | 807 | assert(not collectgarbage()) -- GC during GC (no op) |
| 808 | assert(debug.getmetatable(x).__gc == F) -- previous GC did not mess this? | ||
| 809 | local dummy = {} -- create more garbage during GC | 808 | local dummy = {} -- create more garbage during GC |
| 810 | if A ~= nil then | 809 | if A ~= nil then |
| 811 | assert(type(A) == "userdata") | 810 | assert(type(A) == "userdata") |
| 812 | assert(T.udataval(A) == B) | 811 | assert(T.udataval(A) == B) |
| 813 | debug.getmetatable(A) -- just access it | 812 | debug.getmetatable(A) -- just access it |
| 814 | end | 813 | end |
| 815 | A = x -- ressucita userdata | 814 | A = x -- ressurect userdata |
| 816 | B = udval | 815 | B = udval |
| 817 | return 1,2,3 | 816 | return 1,2,3 |
| 818 | end | 817 | end |
diff --git a/testes/gc.lua b/testes/gc.lua index 2332c939..d865cb28 100644 --- a/testes/gc.lua +++ b/testes/gc.lua | |||
| @@ -676,11 +676,13 @@ end | |||
| 676 | -- just to make sure | 676 | -- just to make sure |
| 677 | assert(collectgarbage'isrunning') | 677 | assert(collectgarbage'isrunning') |
| 678 | 678 | ||
| 679 | do -- check that the collector is reentrant in incremental mode | 679 | do -- check that the collector is not reentrant in incremental mode |
| 680 | local res = true | ||
| 680 | setmetatable({}, {__gc = function () | 681 | setmetatable({}, {__gc = function () |
| 681 | collectgarbage() | 682 | res = collectgarbage() |
| 682 | end}) | 683 | end}) |
| 683 | collectgarbage() | 684 | collectgarbage() |
| 685 | assert(not res) | ||
| 684 | end | 686 | end |
| 685 | 687 | ||
| 686 | 688 | ||
