Skip to content

Commit 0bfc572

Browse files
committed
Bug: GC is not reentrant
As the GC is not reentrant, finalizers should not be able to invoke it.
1 parent 1de95e9 commit 0bfc572

File tree

9 files changed

+57
-27
lines changed

9 files changed

+57
-27
lines changed

lapi.c

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,18 +1136,19 @@ LUA_API int lua_status (lua_State *L) {
11361136
LUA_API int lua_gc (lua_State *L, int what, ...) {
11371137
va_list argp;
11381138
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 */
11401142
lua_lock(L);
1141-
g = G(L);
11421143
va_start(argp, what);
11431144
switch (what) {
11441145
case LUA_GCSTOP: {
1145-
g->gcrunning = 0;
1146+
g->gcstp = GCSTPUSR; /* stopeed by the user */
11461147
break;
11471148
}
11481149
case LUA_GCRESTART: {
11491150
luaE_setdebt(g, 0);
1150-
g->gcrunning = 1;
1151+
g->gcstp = 0; /* (GCSTPGC must be already zero here) */
11511152
break;
11521153
}
11531154
case LUA_GCCOLLECT: {
@@ -1166,8 +1167,8 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
11661167
case LUA_GCSTEP: {
11671168
int data = va_arg(argp, int);
11681169
l_mem debt = 1; /* =1 to signal that it did an actual step */
1169-
lu_byte oldrunning = g->gcrunning;
1170-
g->gcrunning = 1; /* allow GC to run */
1170+
lu_byte oldstp = g->gcstp;
1171+
g->gcstp = 0; /* allow GC to run (GCSTPGC must be zero here) */
11711172
if (data == 0) {
11721173
luaE_setdebt(g, 0); /* do a basic step */
11731174
luaC_step(L);
@@ -1177,7 +1178,7 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
11771178
luaE_setdebt(g, debt);
11781179
luaC_checkGC(L);
11791180
}
1180-
g->gcrunning = oldrunning; /* restore previous state */
1181+
g->gcstp = oldstp; /* restore previous state */
11811182
if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */
11821183
res = 1; /* signal it */
11831184
break;
@@ -1195,7 +1196,7 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
11951196
break;
11961197
}
11971198
case LUA_GCISRUNNING: {
1198-
res = g->gcrunning;
1199+
res = gcrunning(g);
11991200
break;
12001201
}
12011202
case LUA_GCGEN: {

lbaselib.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,20 @@ static int luaB_rawset (lua_State *L) {
182182

183183

184184
static int pushmode (lua_State *L, int oldmode) {
185-
lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental"
186-
: "generational");
185+
if (oldmode == -1)
186+
luaL_pushfail(L); /* invalid call to 'lua_gc' */
187+
else
188+
lua_pushstring(L, (oldmode == LUA_GCINC) ? "incremental"
189+
: "generational");
187190
return 1;
188191
}
189192

190193

194+
/*
195+
** check whether call to 'lua_gc' was valid (not inside a finalizer)
196+
*/
197+
#define checkvalres(res) { if (res == -1) break; }
198+
191199
static int luaB_collectgarbage (lua_State *L) {
192200
static const char *const opts[] = {"stop", "restart", "collect",
193201
"count", "step", "setpause", "setstepmul",
@@ -200,24 +208,28 @@ static int luaB_collectgarbage (lua_State *L) {
200208
case LUA_GCCOUNT: {
201209
int k = lua_gc(L, o);
202210
int b = lua_gc(L, LUA_GCCOUNTB);
211+
checkvalres(k);
203212
lua_pushnumber(L, (lua_Number)k + ((lua_Number)b/1024));
204213
return 1;
205214
}
206215
case LUA_GCSTEP: {
207216
int step = (int)luaL_optinteger(L, 2, 0);
208217
int res = lua_gc(L, o, step);
218+
checkvalres(res);
209219
lua_pushboolean(L, res);
210220
return 1;
211221
}
212222
case LUA_GCSETPAUSE:
213223
case LUA_GCSETSTEPMUL: {
214224
int p = (int)luaL_optinteger(L, 2, 0);
215225
int previous = lua_gc(L, o, p);
226+
checkvalres(previous);
216227
lua_pushinteger(L, previous);
217228
return 1;
218229
}
219230
case LUA_GCISRUNNING: {
220231
int res = lua_gc(L, o);
232+
checkvalres(res);
221233
lua_pushboolean(L, res);
222234
return 1;
223235
}
@@ -234,10 +246,13 @@ static int luaB_collectgarbage (lua_State *L) {
234246
}
235247
default: {
236248
int res = lua_gc(L, o);
249+
checkvalres(res);
237250
lua_pushinteger(L, res);
238251
return 1;
239252
}
240253
}
254+
luaL_pushfail(L); /* invalid call (inside a finalizer) */
255+
return 1;
241256
}
242257

243258

lgc.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -906,16 +906,16 @@ static void GCTM (lua_State *L) {
906906
if (!notm(tm)) { /* is there a finalizer? */
907907
int status;
908908
lu_byte oldah = L->allowhook;
909-
int running = g->gcrunning;
909+
int oldgcstp = g->gcstp;
910+
g->gcstp = GCSTPGC; /* avoid GC steps */
910911
L->allowhook = 0; /* stop debug hooks during GC metamethod */
911-
g->gcrunning = 0; /* avoid GC steps */
912912
setobj2s(L, L->top++, tm); /* push finalizer... */
913913
setobj2s(L, L->top++, &v); /* ... and its argument */
914914
L->ci->callstatus |= CIST_FIN; /* will run a finalizer */
915915
status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0);
916916
L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */
917917
L->allowhook = oldah; /* restore hooks */
918-
g->gcrunning = running; /* restore state */
918+
g->gcstp = oldgcstp; /* restore state */
919919
if (l_unlikely(status != LUA_OK)) { /* error while running __gc? */
920920
luaE_warnerror(L, "__gc metamethod");
921921
L->top--; /* pops error object */
@@ -1502,9 +1502,11 @@ static void deletelist (lua_State *L, GCObject *p, GCObject *limit) {
15021502
*/
15031503
void luaC_freeallobjects (lua_State *L) {
15041504
global_State *g = G(L);
1505+
g->gcstp = GCSTPGC;
15051506
luaC_changemode(L, KGC_INC);
15061507
separatetobefnz(g, 1); /* separate all objects with finalizers */
15071508
lua_assert(g->finobj == NULL);
1509+
g->gcstp = 0;
15081510
callallpendingfinalizers(L);
15091511
deletelist(L, g->allgc, obj2gco(g->mainthread));
15101512
deletelist(L, g->finobj, NULL);
@@ -1647,6 +1649,7 @@ void luaC_runtilstate (lua_State *L, int statesmask) {
16471649
}
16481650

16491651

1652+
16501653
/*
16511654
** Performs a basic incremental step. The debt and step size are
16521655
** converted from bytes to "units of work"; then the function loops
@@ -1678,7 +1681,7 @@ static void incstep (lua_State *L, global_State *g) {
16781681
void luaC_step (lua_State *L) {
16791682
global_State *g = G(L);
16801683
lua_assert(!g->gcemergency);
1681-
if (g->gcrunning) { /* running? */
1684+
if (gcrunning(g)) { /* running? */
16821685
if(isdecGCmodegen(g))
16831686
genstep(L, g);
16841687
else

lgc.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@
148148
*/
149149
#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0)
150150

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+
151160
/*
152161
** Does one step of collection when debt becomes positive. 'pre'/'pos'
153162
** allows some adjustments to be done only when needed. macro

lstate.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ static void f_luaopen (lua_State *L, void *ud) {
236236
luaS_init(L);
237237
luaT_init(L);
238238
luaX_init(L);
239-
g->gcrunning = 1; /* allow gc */
239+
g->gcstp = 0; /* allow gc */
240240
setnilvalue(&g->nilvalue); /* now state is complete */
241241
luai_userstateopen(L);
242242
}
@@ -373,7 +373,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
373373
g->ud_warn = NULL;
374374
g->mainthread = L;
375375
g->seed = luai_makeseed(L);
376-
g->gcrunning = 0; /* no GC while building state */
376+
g->gcstp = GCSTPGC; /* no GC while building state */
377377
g->strt.size = g->strt.nuse = 0;
378378
g->strt.hash = NULL;
379379
setnilvalue(&g->l_registry);

lstate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ typedef struct global_State {
263263
lu_byte gcstopem; /* stops emergency collections */
264264
lu_byte genminormul; /* control for minor generational collections */
265265
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 */
267267
lu_byte gcemergency; /* true if this is an emergency collection */
268268
lu_byte gcpause; /* size of pause between successive GCs */
269269
lu_byte gcstepmul; /* GC "speed" */

manual/manual.of

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -787,11 +787,8 @@ following the reverse order that they were marked.
787787
If any finalizer marks objects for collection during that phase,
788788
these marks have no effect.
789789

790-
Finalizers cannot yield.
791-
Except for that, they can do anything,
792-
such as raise errors, create new objects,
793-
or even run the garbage collector.
794-
However, because they can run in unpredictable times,
790+
Finalizers cannot yield nor run the garbage collector.
791+
Because they can run in unpredictable times,
795792
it is good practice to restrict each finalizer
796793
to the minimum necessary to properly release
797794
its associated resource.
@@ -3276,6 +3273,8 @@ Returns the previous mode (@id{LUA_GCGEN} or @id{LUA_GCINC}).
32763273
For more details about these options,
32773274
see @Lid{collectgarbage}.
32783275

3276+
This function should not be called by a finalizer.
3277+
32793278
}
32803279

32813280
@APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);|
@@ -6233,6 +6232,8 @@ A zero means to not change that value.
62336232
See @See{GC} for more details about garbage collection
62346233
and some of these options.
62356234

6235+
This function should not be called by a finalizer.
6236+
62366237
}
62376238

62386239
@LibEntry{dofile ([filename])|

testes/api.lua

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -804,15 +804,14 @@ F = function (x)
804804
d = nil
805805
assert(debug.getmetatable(x).__gc == F)
806806
assert(load("table.insert({}, {})"))() -- create more garbage
807-
collectgarbage() -- force a GC during GC
808-
assert(debug.getmetatable(x).__gc == F) -- previous GC did not mess this?
807+
assert(not collectgarbage()) -- GC during GC (no op)
809808
local dummy = {} -- create more garbage during GC
810809
if A ~= nil then
811810
assert(type(A) == "userdata")
812811
assert(T.udataval(A) == B)
813812
debug.getmetatable(A) -- just access it
814813
end
815-
A = x -- ressucita userdata
814+
A = x -- ressurect userdata
816815
B = udval
817816
return 1,2,3
818817
end

testes/gc.lua

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,11 +676,13 @@ end
676676
-- just to make sure
677677
assert(collectgarbage'isrunning')
678678

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
680681
setmetatable({}, {__gc = function ()
681-
collectgarbage()
682+
res = collectgarbage()
682683
end})
683684
collectgarbage()
685+
assert(not res)
684686
end
685687

686688

0 commit comments

Comments
 (0)