aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Ierusalimschy <roberto@inf.puc-rio.br>2021-12-13 10:41:17 -0300
committerRoberto Ierusalimschy <roberto@inf.puc-rio.br>2021-12-13 10:41:17 -0300
commit0bfc572e51d9035a615ef6e9523f736c9ffa8e57 (patch)
tree218f2bb13a873becf8fa657a296c8863f7e0e466
parent1de95e97ef65632a88e08b6184bd9d1ceba7ec2f (diff)
downloadlua-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.c17
-rw-r--r--lbaselib.c19
-rw-r--r--lgc.c11
-rw-r--r--lgc.h9
-rw-r--r--lstate.c4
-rw-r--r--lstate.h2
-rw-r--r--manual/manual.of11
-rw-r--r--testes/api.lua5
-rw-r--r--testes/gc.lua6
9 files changed, 57 insertions, 27 deletions
diff --git a/lapi.c b/lapi.c
index 071a06f3..3585ac43 100644
--- a/lapi.c
+++ b/lapi.c
@@ -1136,18 +1136,19 @@ LUA_API int lua_status (lua_State *L) {
1136LUA_API int lua_gc (lua_State *L, int what, ...) { 1136LUA_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: {
diff --git a/lbaselib.c b/lbaselib.c
index 912c4cc6..1d60c9de 100644
--- a/lbaselib.c
+++ b/lbaselib.c
@@ -182,12 +182,20 @@ static int luaB_rawset (lua_State *L) {
182 182
183 183
184static int pushmode (lua_State *L, int oldmode) { 184static 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
191static int luaB_collectgarbage (lua_State *L) { 199static 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
diff --git a/lgc.c b/lgc.c
index b360eed0..7d0b5e4f 100644
--- a/lgc.c
+++ b/lgc.c
@@ -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*/
1503void luaC_freeallobjects (lua_State *L) { 1503void 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) {
1678void luaC_step (lua_State *L) { 1681void 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
diff --git a/lgc.h b/lgc.h
index 073e2a40..024a4328 100644
--- a/lgc.h
+++ b/lgc.h
@@ -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
diff --git a/lstate.c b/lstate.c
index 547a7a01..1ffe1a0f 100644
--- a/lstate.c
+++ b/lstate.c
@@ -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);
diff --git a/lstate.h b/lstate.h
index 44cf939c..7886d891 100644
--- a/lstate.h
+++ b/lstate.h
@@ -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.
787If any finalizer marks objects for collection during that phase, 787If any finalizer marks objects for collection during that phase,
788these marks have no effect. 788these marks have no effect.
789 789
790Finalizers cannot yield. 790Finalizers cannot yield nor run the garbage collector.
791Except for that, they can do anything, 791Because they can run in unpredictable times,
792such as raise errors, create new objects,
793or even run the garbage collector.
794However, because they can run in unpredictable times,
795it is good practice to restrict each finalizer 792it is good practice to restrict each finalizer
796to the minimum necessary to properly release 793to the minimum necessary to properly release
797its associated resource. 794its associated resource.
@@ -3276,6 +3273,8 @@ Returns the previous mode (@id{LUA_GCGEN} or @id{LUA_GCINC}).
3276For more details about these options, 3273For more details about these options,
3277see @Lid{collectgarbage}. 3274see @Lid{collectgarbage}.
3278 3275
3276This 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.
6233See @See{GC} for more details about garbage collection 6232See @See{GC} for more details about garbage collection
6234and some of these options. 6233and some of these options.
6235 6234
6235This 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
818end 817end
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
677assert(collectgarbage'isrunning') 677assert(collectgarbage'isrunning')
678 678
679do -- check that the collector is reentrant in incremental mode 679do -- 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)
684end 686end
685 687
686 688