Compare commits

..

12 Commits
rc ... master

Author SHA1 Message Date
Roberto I
2a7cf4f319 More effort in avoiding errors in finalizers
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.
2026-01-11 15:36:03 -03:00
Roberto I
5cfc725a8b Special case for 'string.rep' over an empty string 2026-01-04 16:39:22 -03:00
Roberto I
45c7ae5b1b BUG: Possible overflow in 'string.packsize'
'string.packsize' can overflow result in 32-bit machines using 64-bit
integers, as LUA_MAXINTEGER may not fit into size_t.
2026-01-04 16:31:17 -03:00
Roberto I
962f444a75 Details
In an assignment like 'a = &b', is looks suspicious if 'a' has a scope
larger than 'b'.
2026-01-04 16:27:54 -03:00
Roberto I
c4e2c91973 Details
Some comments still talked about bit 'isrealasize', which has been
removed.
2025-12-30 10:50:49 -03:00
Roberto I
632a71b24d BUG: Arithmetic overflow in 'collectgarbage"step"'
The computation of a new debt could overflow when we give a too large
step to 'collectgarbage"step"' and the current debt was already
negative. This is only an issue if your platform cares for it or if you
compile Lua with an option like '-fsanitize=undefined'.
2025-12-27 16:22:13 -03:00
Roberto I
578ae5745c Details
typo in comment + formatting + logical 'and' was written as a bitwise
operation (makes code more fragile)
2025-12-23 14:44:06 -03:00
Roberto I
a5522f06d2 GC checks stack space before running finalizer
If the stack does not have some minimum available space, the GC defers
calling a finalizer until the next cycle. That avoids errors while
running a finalizer that the programmer cannot control.
2025-12-13 16:16:59 -03:00
Roberto I
3d03ae5bd6 'luaL_newstate' starts state with warnings on
It is easier to forget to turn them on then to turn them off.
2025-12-13 11:00:30 -03:00
Roberto I
82d721a855 Format adjust in the manual
Lists in inline code don't get a space after commas. (That keeps the
code more compact and avoids line breaks in the middle of the code.)
2025-12-10 10:35:05 -03:00
Roberto I
104b0fc700 Details
- Avoid fixing name "_ENV" in the code
- Small improvements in the manual
2025-12-08 13:09:47 -03:00
Roberto I
8164d09338 Wrong assert in 'luaK_indexed' 2025-12-08 11:08:12 -03:00
19 changed files with 186 additions and 63 deletions

11
lapi.c
View File

@ -366,7 +366,7 @@ LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) {
}
LUA_API unsigned (lua_numbertocstring) (lua_State *L, int idx, char *buff) {
LUA_API unsigned lua_numbertocstring (lua_State *L, int idx, char *buff) {
const TValue *o = index2value(L, idx);
if (ttisnumber(o)) {
unsigned len = luaO_tostringbuff(o, buff);
@ -1201,11 +1201,16 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
case LUA_GCSTEP: {
lu_byte oldstp = g->gcstp;
l_mem n = cast(l_mem, va_arg(argp, size_t));
l_mem newdebt;
int work = 0; /* true if GC did some work */
g->gcstp = 0; /* allow GC to run (other bits must be zero here) */
if (n <= 0)
n = g->GCdebt; /* force to run one basic step */
luaE_setdebt(g, g->GCdebt - n);
newdebt = 0; /* force to run one basic step */
else if (g->GCdebt >= n - MAX_LMEM) /* no overflow? */
newdebt = g->GCdebt - n;
else /* overflow */
newdebt = -MAX_LMEM; /* set debt to miminum value */
luaE_setdebt(g, newdebt);
luaC_condGC(L, (void)0, work = 1);
if (work && g->gcstate == GCSpause) /* end of cycle? */
res = 1; /* signal it */

View File

@ -1185,7 +1185,7 @@ LUALIB_API lua_State *(luaL_newstate) (void) {
lua_State *L = lua_newstate(luaL_alloc, NULL, luaL_makeseed(NULL));
if (l_likely(L)) {
lua_atpanic(L, &panic);
lua_setwarnf(L, warnfoff, L); /* default is warnings off */
lua_setwarnf(L, warnfon, L);
}
return L;
}

View File

@ -81,8 +81,8 @@ LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def,
LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname);
LUALIB_API int (luaL_execresult) (lua_State *L, int stat);
LUALIB_API void *luaL_alloc (void *ud, void *ptr, size_t osize,
size_t nsize);
LUALIB_API void *(luaL_alloc) (void *ud, void *ptr, size_t osize,
size_t nsize);
/* predefined references */
@ -103,7 +103,7 @@ LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s);
LUALIB_API lua_State *(luaL_newstate) (void);
LUALIB_API unsigned luaL_makeseed (lua_State *L);
LUALIB_API unsigned (luaL_makeseed) (lua_State *L);
LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx);

View File

@ -1370,9 +1370,11 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) {
fillidxk(t, k->u.info, VINDEXUP); /* literal short string */
}
else if (t->k == VVARGVAR) { /* indexing the vararg parameter? */
lua_assert(t->u.ind.t == fs->f->numparams);
t->u.ind.t = cast_byte(t->u.var.ridx);
fillidxk(t, luaK_exp2anyreg(fs, k), VVARGIND); /* register */
int kreg = luaK_exp2anyreg(fs, k); /* put key in some register */
lu_byte vreg = cast_byte(t->u.var.ridx); /* register with vararg param. */
lua_assert(vreg == fs->f->numparams);
t->u.ind.t = vreg; /* (avoid a direct assignment; values may overlap) */
fillidxk(t, kreg, VVARGIND); /* 't' represents 'vararg[k]' */
}
else {
/* register index of the table */

21
ldo.c
View File

@ -220,6 +220,25 @@ l_noret luaD_errerr (lua_State *L) {
}
/*
** Check whether stacks have enough space to run a simple function (such
** as a finalizer): At least BASIC_STACK_SIZE in the Lua stack, two
** available CallInfos, and two "slots" in the C stack.
*/
int luaD_checkminstack (lua_State *L) {
if (getCcalls(L) >= LUAI_MAXCCALLS - 2)
return 0; /* not enough C-stack slots */
if (L->ci->next == NULL && luaE_extendCI(L, 0) == NULL)
return 0; /* unable to allocate first ci */
if (L->ci->next->next == NULL && luaE_extendCI(L, 0) == NULL)
return 0; /* unable to allocate second ci */
if (L->stack_last.p - L->top.p >= BASIC_STACK_SIZE)
return 1; /* enough (BASIC_STACK_SIZE) free slots in the Lua stack */
else /* try to grow stack to a size with enough free slots */
return luaD_growstack(L, BASIC_STACK_SIZE, 0);
}
/*
** In ISO C, any pointer use after the pointer has been deallocated is
** undefined behavior. So, before a stack reallocation, all pointers
@ -605,7 +624,7 @@ void luaD_poscall (lua_State *L, CallInfo *ci, int nres) {
#define next_ci(L) (L->ci->next ? L->ci->next : luaE_extendCI(L))
#define next_ci(L) (L->ci->next ? L->ci->next : luaE_extendCI(L, 1))
/*

1
ldo.h
View File

@ -89,6 +89,7 @@ LUAI_FUNC int luaD_reallocstack (lua_State *L, int newsize, int raiseerror);
LUAI_FUNC int luaD_growstack (lua_State *L, int n, int raiseerror);
LUAI_FUNC void luaD_shrinkstack (lua_State *L);
LUAI_FUNC void luaD_inctop (lua_State *L);
LUAI_FUNC int luaD_checkminstack (lua_State *L);
LUAI_FUNC l_noret luaD_throw (lua_State *L, TStatus errcode);
LUAI_FUNC l_noret luaD_throwbaselevel (lua_State *L, TStatus errcode);

7
lgc.c
View File

@ -1293,7 +1293,7 @@ static void finishgencycle (lua_State *L, global_State *g) {
correctgraylists(g);
checkSizes(L, g);
g->gcstate = GCSpropagate; /* skip restart */
if (!g->gcemergency)
if (g->tobefnz != NULL && !g->gcemergency && luaD_checkminstack(L))
callallpendingfinalizers(L);
}
@ -1667,12 +1667,13 @@ static l_mem singlestep (lua_State *L, int fast) {
break;
}
case GCScallfin: { /* call finalizers */
if (g->tobefnz && !g->gcemergency) {
if (g->tobefnz && !g->gcemergency && luaD_checkminstack(L)) {
g->gcstopem = 0; /* ok collections during finalizers */
GCTM(L); /* call one finalizer */
stepresult = CWUFIN;
}
else { /* emergency mode or no more finalizers */
else { /* no more finalizers or emergency mode or not enough stack
to run finalizers */
g->gcstate = GCSpause; /* finish collection */
stepresult = step2pause;
}

View File

@ -505,8 +505,8 @@ static void buildglobal (LexState *ls, TString *varname, expdesc *var) {
init_exp(var, VGLOBAL, -1); /* global by default */
singlevaraux(fs, ls->envn, var, 1); /* get environment variable */
if (var->k == VGLOBAL)
luaK_semerror(ls, "_ENV is global when accessing variable '%s'",
getstr(varname));
luaK_semerror(ls, "%s is global when accessing variable '%s'",
LUA_ENV, getstr(varname));
luaK_exp2anyregup(fs, var); /* _ENV could be a constant */
codestring(&key, varname); /* key is variable name */
luaK_indexed(fs, var, &key); /* 'var' represents _ENV[varname] */

View File

@ -68,14 +68,19 @@ void luaE_setdebt (global_State *g, l_mem debt) {
}
CallInfo *luaE_extendCI (lua_State *L) {
CallInfo *luaE_extendCI (lua_State *L, int err) {
CallInfo *ci;
lua_assert(L->ci->next == NULL);
ci = luaM_new(L, CallInfo);
lua_assert(L->ci->next == NULL);
L->ci->next = ci;
ci = luaM_reallocvector(L, NULL, 0, 1, CallInfo);
if (l_unlikely(ci == NULL)) { /* allocation failed? */
if (err)
luaM_error(L); /* raise the error */
return NULL; /* else only report it */
}
ci->next = L->ci->next;
ci->previous = L->ci;
ci->next = NULL;
L->ci->next = ci;
if (ci->next)
ci->next->previous = ci;
ci->u.l.trap = 0;
L->nci++;
return ci;

View File

@ -438,7 +438,7 @@ union GCUnion {
LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt);
LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1);
LUAI_FUNC lu_mem luaE_threadsize (lua_State *L);
LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L);
LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L, int err);
LUAI_FUNC void luaE_shrinkCI (lua_State *L);
LUAI_FUNC void luaE_checkcstack (lua_State *L);
LUAI_FUNC void luaE_incCstack (lua_State *L);

View File

@ -141,8 +141,8 @@ static int str_rep (lua_State *L) {
const char *s = luaL_checklstring(L, 1, &len);
lua_Integer n = luaL_checkinteger(L, 2);
const char *sep = luaL_optlstring(L, 3, "", &lsep);
if (n <= 0)
lua_pushliteral(L, "");
if (n <= 0 || (len | lsep) == 0)
lua_pushliteral(L, ""); /* no repetitions or both strings empty */
else if (l_unlikely(len > MAX_SIZE - lsep ||
cast_st2S(len + lsep) > cast_st2S(MAX_SIZE) / n))
return luaL_error(L, "resulting string too large");
@ -968,7 +968,7 @@ static int str_gsub (lua_State *L) {
reprepstate(&ms); /* (re)prepare state for new match */
if ((e = match(&ms, src, p)) != NULL && e != lastmatch) { /* match? */
n++;
changed = add_value(&ms, &b, src, e, tr) | changed;
changed = add_value(&ms, &b, src, e, tr) || changed;
src = lastmatch = e;
}
else if (src < ms.src_end) /* otherwise, skip one character */
@ -1726,7 +1726,7 @@ static int str_packsize (lua_State *L) {
luaL_argcheck(L, opt != Kstring && opt != Kzstr, 1,
"variable-length format");
size += ntoalign; /* total space used by option */
luaL_argcheck(L, totalsize <= LUA_MAXINTEGER - size,
luaL_argcheck(L, totalsize <= MAX_SIZE - size,
1, "format result too large");
totalsize += size;
}

View File

@ -651,10 +651,9 @@ static void reinserthash (lua_State *L, Table *ot, Table *t) {
/*
** Exchange the hash part of 't1' and 't2'. (In 'flags', only the
** dummy bit must be exchanged: The 'isrealasize' is not related
** to the hash part, and the metamethod bits do not change during
** a resize, so the "real" table can keep their values.)
** Exchange the hash part of 't1' and 't2'. (In 'flags', only the dummy
** bit must be exchanged: The metamethod bits do not change during a
** resize, so the "real" table can keep their values.)
*/
static void exchangehashpart (Table *t1, Table *t2) {
lu_byte lsizenode = t1->lsizenode;
@ -1156,14 +1155,15 @@ void luaH_finishset (lua_State *L, Table *t, const TValue *key,
lua_assert(hres != HOK);
if (hres == HNOTFOUND) {
TValue aux;
const TValue *actk = key; /* actual key to insert */
if (l_unlikely(ttisnil(key)))
luaG_runerror(L, "table index is nil");
else if (ttisfloat(key)) {
lua_Number f = fltvalue(key);
lua_Integer k;
if (luaV_flttointeger(f, &k, F2Ieq)) {
setivalue(&aux, k); /* key is equal to an integer */
key = &aux; /* insert it as an integer */
if (luaV_flttointeger(f, &k, F2Ieq)) { /* is key equal to an integer? */
setivalue(&aux, k);
actk = &aux; /* use the integer as the key */
}
else if (l_unlikely(luai_numisnan(f)))
luaG_runerror(L, "table index is NaN");
@ -1176,7 +1176,7 @@ void luaH_finishset (lua_State *L, Table *t, const TValue *key,
L->top.p--;
return;
}
luaH_newkey(L, t, key, value);
luaH_newkey(L, t, actk, value);
}
else if (hres > 0) { /* regular Node? */
setobj2t(L, gval(gnode(t, hres - HFIRSTNODE)), value);

View File

@ -1106,6 +1106,27 @@ static int stacklevel (lua_State *L) {
}
static int resetCI (lua_State *L) {
CallInfo *ci = L->ci;
while (ci->next != NULL) {
CallInfo *tofree = ci->next;
ci->next = ci->next->next;
luaM_free(L, tofree);
L->nci--;
}
return 0;
}
static int reallocstack (lua_State *L) {
int n = cast_int(luaL_checkinteger(L, 1));
lua_lock(L);
luaD_reallocstack(L, cast_int(L->top.p - L->stack.p) + n, 1);
lua_unlock(L);
return 0;
}
static int table_query (lua_State *L) {
const Table *t;
int i = cast_int(luaL_optinteger(L, 2, -1));
@ -2182,6 +2203,8 @@ static const struct luaL_Reg tests_funcs[] = {
{"s2d", s2d},
{"sethook", sethook},
{"stacklevel", stacklevel},
{"resetCI", resetCI},
{"reallocstack", reallocstack},
{"sizes", get_sizes},
{"testC", testC},
{"makeCfunc", makeCfunc},

2
ltm.h
View File

@ -49,7 +49,7 @@ typedef enum {
** Mask with 1 in all fast-access methods. A 1 in any of these bits
** in the flag of a (meta)table means the metatable does not have the
** corresponding metamethod field. (Bit 6 of the flag indicates that
** the table is using the dummy node; bit 7 is used for 'isrealasize'.)
** the table is using the dummy node.)
*/
#define maskflags cast_byte(~(~0u << (TM_EQ + 1)))

3
lua.c
View File

@ -349,6 +349,7 @@ static int collectargs (char **argv, int *first) {
*/
static int runargs (lua_State *L, char **argv, int n) {
int i;
lua_warning(L, "@off", 0); /* by default, Lua stand-alone has warnings off */
for (i = 1; i < n; i++) {
int option = argv[i][1];
lua_assert(argv[i][0] == '-'); /* already checked */
@ -725,7 +726,7 @@ static int pmain (lua_State *L) {
if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */
return 0; /* error running LUA_INIT */
}
if (!runargs(L, argv, optlim)) /* execute arguments -e and -l */
if (!runargs(L, argv, optlim)) /* execute arguments -e, -l, and -W */
return 0; /* something failed */
if (script > 0) { /* execute main script (if there is one) */
if (handle_script(L, argv + script) != LUA_OK)

View File

@ -107,7 +107,7 @@ for small machines and embedded systems.
Unless stated otherwise,
any overflow when manipulating integer values @def{wrap around},
according to the usual rules of two-complement arithmetic.
according to the usual rules of two's complement arithmetic.
(In other words,
the actual result is the unique representable integer
that is equal modulo @M{2@sp{n}} to the mathematical result,
@ -2091,12 +2091,12 @@ Note that keys that are not positive integers
do not interfere with borders.
A table with exactly one border is called a @def{sequence}.
For instance, the table @T{{10, 20, 30, 40, 50}} is a sequence,
For instance, the table @T{{10,20,30,40,50}} is a sequence,
as it has only one border (5).
The table @T{{10, 20, 30, nil, 50}} has two borders (3 and 5),
The table @T{{10,20,30,nil,50}} has two borders (3 and 5),
and therefore it is not a sequence.
(The @nil at index 4 is called a @emphx{hole}.)
The table @T{{nil, 20, 30, nil, nil, 60, nil}}
The table @T{{nil,20,30,nil,nil,60,nil}}
has three borders (0, 3, and 6),
so it is not a sequence, too.
The table @T{{}} is a sequence with border 0.
@ -2449,22 +2449,22 @@ These are the places where Lua expects a list of expressions:
@description{
@item{A @rw{return} statement,
for instance @T{return e1, e2, e3} @see{control}.}
for instance @T{return e1,e2,e3} @see{control}.}
@item{A table constructor,
for instance @T{{e1, e2, e3}} @see{tableconstructor}.}
for instance @T{{e1,e2,e3}} @see{tableconstructor}.}
@item{The arguments of a function call,
for instance @T{foo(e1, e2, e3)} @see{functioncall}.}
for instance @T{foo(e1,e2,e3)} @see{functioncall}.}
@item{A multiple assignment,
for instance @T{a , b, c = e1, e2, e3} @see{assignment}.}
for instance @T{a,b,c = e1,e2,e3} @see{assignment}.}
@item{A local or global declaration,
which is similar to a multiple assignment.}
@item{The initial values in a generic @rw{for} loop,
for instance @T{for k in e1, e2, e3 do ... end} @see{for}.}
for instance @T{for k in e1,e2,e3 do ... end} @see{for}.}
}
In the last four cases,
@ -2501,7 +2501,7 @@ we recommend assigning the vararg expression
to a single variable and using that variable
in its place.
Here are some examples of uses of mutlres expressions.
Here are some examples of uses of multires expressions.
In all cases, when the construction needs
@Q{the n-th result} and there is no such result,
it uses a @nil.
@ -3107,7 +3107,7 @@ void *luaL_alloc (void *ud, void *ptr, size_t osize,
}
Note that @N{ISO C} ensures
that @T{free(NULL)} has no effect and that
@T{realloc(NULL, size)} is equivalent to @T{malloc(size)}.
@T{realloc(NULL,size)} is equivalent to @T{malloc(size)}.
}
@ -3640,9 +3640,9 @@ because a pseudo-index is not an actual stack position.
The type of integers in Lua.
By default this type is @id{long long},
(usually a 64-bit two-complement integer),
(usually a 64-bit two's complement integer),
but that can be changed to @id{long} or @id{int}
(usually a 32-bit two-complement integer).
(usually a 32-bit two's complement integer).
(See @id{LUA_INT_TYPE} in @id{luaconf.h}.)
Lua also defines the constants
@ -3879,7 +3879,7 @@ is a seed for the hashing of strings.
@apii{0,1,m}
Creates a new empty table and pushes it onto the stack.
It is equivalent to @T{lua_createtable(L, 0, 0)}.
It is equivalent to @T{lua_createtable(L,0,0)}.
}
@ -5439,7 +5439,7 @@ the auxiliary library provides higher-level functions for some
common tasks.
All functions and types from the auxiliary library
are defined in header file @id{lauxlib.h} and
are defined in the header file @id{lauxlib.h} and
have a prefix @id{luaL_}.
All functions in the auxiliary library are built on
@ -5583,7 +5583,7 @@ Its pattern of use is as follows:
@item{First declare a variable @id{b} of type @Lid{luaL_Buffer}.}
@item{Then initialize it with a call @T{luaL_buffinit(L, &b)}.}
@item{Then initialize it with a call @T{luaL_buffinit(L,&b)}.}
@item{
Then add string pieces to the buffer calling any of
@ -5604,12 +5604,12 @@ you can use the buffer like this:
@item{First declare a variable @id{b} of type @Lid{luaL_Buffer}.}
@item{Then initialize it and preallocate a space of
size @id{sz} with a call @T{luaL_buffinitsize(L, &b, sz)}.}
size @id{sz} with a call @T{luaL_buffinitsize(L,&b,sz)}.}
@item{Then produce the string into that space.}
@item{
Finish by calling @T{luaL_pushresultsize(&b, sz)},
Finish by calling @T{luaL_pushresultsize(&b,sz)},
where @id{sz} is the total size of the resulting string
copied into that space (which may be less than or
equal to the preallocated size).
@ -6214,7 +6214,7 @@ You should not manually set integer keys in the table
after the first use of @Lid{luaL_ref}.
You can retrieve an object referred by the reference @id{r}
by calling @T{lua_rawgeti(L, t, r)} or @T{lua_geti(L, t, r)}.
by calling @T{lua_rawgeti(L,t,r)} or @T{lua_geti(L,t,r)}.
The function @Lid{luaL_unref} frees a reference.
If the object on the top of the stack is @nil,
@ -6492,7 +6492,7 @@ the host program can call the function @Lid{luaL_openlibs}.
Alternatively,
the host can select which libraries to open,
by using @Lid{luaL_openselectedlibs}.
Both functions are defined in the header file @id{lualib.h}.
Both functions are declared in the header file @id{lualib.h}.
@index{lualib.h}
The stand-alone interpreter @id{lua} @see{lua-sa}
@ -7744,7 +7744,7 @@ If @id{j} is absent, then it is assumed to be equal to @num{-1}
In particular,
the call @T{string.sub(s,1,j)} returns a prefix of @id{s}
with length @id{j},
and @T{string.sub(s, -i)} (for a positive @id{i})
and @T{string.sub(s,-i)} (for a positive @id{i})
returns a suffix of @id{s}
with length @id{i}.
@ -8180,7 +8180,7 @@ the function returns @fail.
A negative @id{n} gets characters before position @id{i}.
The default for @id{i} is 1 when @id{n} is non-negative
and @T{#s + 1} otherwise,
so that @T{utf8.offset(s, -n)} gets the offset of the
so that @T{utf8.offset(s,-n)} gets the offset of the
@id{n}-th character from the end of the string.
As a special case,
@ -8233,7 +8233,7 @@ the table will have; its default is zero.
Inserts element @id{value} at position @id{pos} in @id{list},
shifting up the elements
@T{list[pos], list[pos+1], @Cdots, list[#list]}.
@T{list[pos],list[pos+1],@Cdots,list[#list]}.
The default value for @id{pos} is @T{#list+1},
so that a call @T{table.insert(t,x)} inserts @id{x} at the end
of the list @id{t}.
@ -8271,7 +8271,7 @@ Removes from @id{list} the element at position @id{pos},
returning the value of the removed element.
When @id{pos} is an integer between 1 and @T{#list},
it shifts down the elements
@T{list[pos+1], list[pos+2], @Cdots, list[#list]}
@T{list[pos+1],list[pos+2],@Cdots,list[#list]}
and erases element @T{list[#list]};
The index @id{pos} can also be 0 when @T{#list} is 0,
or @T{#list + 1}.

View File

@ -707,4 +707,46 @@ end
collectgarbage(oldmode)
if T then
print("testing stack issues when calling finalizers")
local X
local obj
local function initobj ()
X = false
obj = setmetatable({}, {__gc = function () X = true end})
end
local function loop (n)
if n > 0 then loop(n - 1) end
end
-- should not try to call finalizer without a CallInfo available
initobj()
loop(20) -- ensure stack space
T.resetCI() -- remove extra CallInfos
T.alloccount(0) -- cannot allocate more CallInfos
obj = nil
collectgarbage() -- will not call finalizer
T.alloccount()
assert(X == false)
collectgarbage() -- now will call finalizer (it was still pending)
assert(X == true)
-- should not try to call finalizer without stack space available
initobj()
loop(5) -- ensure enough CallInfos
T.reallocstack(0) -- remove extra stack slots
T.alloccount(0) -- cannot reallocate stack
obj = nil
collectgarbage() -- will not call finalizer
T.alloccount()
assert(X == false)
collectgarbage() -- now will call finalizer (it was still pending)
assert(X == true)
end
print('OK')

View File

@ -282,6 +282,25 @@ testamem("growing stack", function ()
return foo(100)
end)
collectgarbage()
collectgarbage()
global io, T, setmetatable, collectgarbage, print
local Count = 0
testamem("finalizers", function ()
local X = false
local obj = setmetatable({}, {__gc = function () X = true end})
obj = nil
T.resetCI() -- remove extra CallInfos
T.reallocstack(18) -- remove extra stack slots
Count = Count + 1
io.stderr:write(Count, "\n")
T.trick(io)
collectgarbage()
return X
end)
-- }==================================================================

View File

@ -1,10 +1,15 @@
-- track collections
local M = {}
-- import list
local setmetatable, stderr, collectgarbage =
setmetatable, io.stderr, collectgarbage
local stderr, collectgarbage = io.stderr, collectgarbage
-- the debug version of setmetatable does not create any object (such as
-- a '__metatable' string), and so it is more appropriate to be used in
-- a finalizer
local setmetatable = require"debug".setmetatable
global none