Compare commits

..

22 Commits
v5.4.7 ... v5.4

Author SHA1 Message Date
Roberto I
312b9efaa1 Small corrections in the manual 2026-08-07 15:59:21 -03:00
Roberto I
03aabbc636 New copyright year (2026) 2026-08-05 15:04:44 -03:00
Roberto I
2f8fe031c7 New release number (5.4.9) 2026-07-24 11:39:24 -03:00
Roberto I
f4f4968fd3 Bug: 'luaL_newmetatable' used in a wrong way
The call to 'luaL_newmetatable' in 'newbox' can leave an incomplete
metatable in the registry, if 'luaL_setfuncs' raises a memory error.
2026-07-24 11:38:45 -03:00
Roberto I
e282da498c Bug: wrong initialization in result from 'gmatch'
Function returned by 'string.gmatch' can be left in an inconsistent
state after an error.
2026-07-24 11:21:24 -03:00
Roberto I
b18086ea34 Bug: Loading a binary chunk does not run the GC 2026-07-24 11:16:38 -03:00
Roberto I
af19891bce Bug: shift overflow in utf-8 decode
An initial byte \xFF will ask for 7 continuation bytes, and then the
shift by (count * 5) will try to shift 35 bits.
2026-07-22 15:07:52 -03:00
Roberto I
8511e90b7d Bug: GC checks stack space before running finalizer
If some stack does not have a 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.
2026-07-22 14:42:55 -03:00
Roberto I
0f781f836a Bug: Issues with write barrier for __newindex
In 'luaV_finishset', there is an update on a table that is a field on
another table. If the first table is the same as the one with the field
(e.g., after 't.__newindex = t'), the update can change the value on
that field (if the field being updated is '__newindex' itself). After
that, the barrier is called with the table stored in that field, which
is not the correct table anymore.
2026-07-13 15:55:36 -03:00
Roberto Ierusalimschy
934fdd481c Bug: Constructors with nils can overflow counters 2025-08-27 14:58:02 -03:00
Roberto Ierusalimschy
9ac9d23f41 Bug: error with option '--' without a script 2025-08-27 14:55:35 -03:00
Roberto Ierusalimschy
1b0f943da7 Bug: new metatable in weak table can fool the GC
All-weak tables are not being revisited after being visited during
propagation; if it gets a new metatable after that, the new metatable
may not be marked.
2025-06-16 16:33:02 -03:00
Roberto Ierusalimschy
6e22fedb74 New release number, updated copyright year 2025-05-21 13:52:56 -03:00
Roberto Ierusalimschy
267ef461d0 Bug: check for constructor overflow in [exp] fields
The check for constructor overflow was considering only fields with
explicit names, ignoring fields with syntax '[exp]=exp'.
2025-05-20 17:55:08 -03:00
Roberto Ierusalimschy
d1ee2a4deb Bug: Bad error message with fields of string _ENV
Errors with fields of the string _ENV (e.g., ("_ENV").u + 1) report
the error as comming from a global.
2025-05-15 15:14:56 -03:00
Roberto Ierusalimschy
3fe7be956f Bug: message handler can be overwritten
A __close metamethod can overwrite a message handler in the stack
when closing a thread or a state.
2025-03-17 16:14:17 -03:00
Roberto Ierusalimschy
983bc433e6 Bug: Use after free in 'luaV_finishset'
If a metatable is a weak table, its __newindex field could be collected
by an emergency collection while being used in 'luaV_finishset'. (This
bug has similarities with bug 5.3.2-1, fixed in commit a272fa66.)
2025-03-13 15:42:39 -03:00
Roberto Ierusalimschy
25da574fcb Bug: 'luaD_seterrorobj' should not raise errors
This function can be called unprotected, so it should not raise any
kind of errors. (It could raise a memory-allocation error when creating
a message).
2025-03-12 16:01:03 -03:00
Roberto Ierusalimschy
f5e55be2a0 Bug: Missing error status in panic function
'luaD_throw' may call 'luaE_resetthread', which returns an error code
but clears 'L->status'; so, 'luaD_throw' should set that status again.
2025-01-16 16:25:11 -03:00
Roberto Ierusalimschy
9f0c0fe0de Bug: Wrong limit for local variables in 16-bit systems
USHRT_MAX does not fit in an 'int' in 16-bit systems.
2024-09-10 11:34:42 -03:00
Roberto Ierusalimschy
782ef85b22 Bug: wrong code gen. for indices with comparisons
In function 'luaK_exp2val', used to generate code for indices: Macro
'hasjumps' does not consider the case when the whole expression is a
"jump" (a test). In all other of its uses, the surrounding code ensures
that the expression cannot be VJMP.
2024-08-17 12:37:04 -03:00
Roberto Ierusalimschy
30982bec96 Bug: Bad stack manipulation in 'multiline' (REPL)
'incomplete' was popping error message that should be used in case
there is no more lines to complete the input, that is, 'pushline'
returns NULL, due to end of file.
2024-07-05 14:31:07 -03:00
26 changed files with 220 additions and 66 deletions

3
lapi.c
View File

@@ -1089,6 +1089,7 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
ZIO z; ZIO z;
int status; int status;
lua_lock(L); lua_lock(L);
luaC_checkGC(L);
if (!chunkname) chunkname = "?"; if (!chunkname) chunkname = "?";
luaZ_init(L, &z, reader, data); luaZ_init(L, &z, reader, data);
status = luaD_protectedparser(L, &z, chunkname, mode); status = luaD_protectedparser(L, &z, chunkname, mode);
@@ -1343,7 +1344,7 @@ void lua_warning (lua_State *L, const char *msg, int tocont) {
LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) { LUA_API void *lua_newuserdatauv (lua_State *L, size_t size, int nuvalue) {
Udata *u; Udata *u;
lua_lock(L); lua_lock(L);
api_check(L, 0 <= nuvalue && nuvalue < USHRT_MAX, "invalid value"); api_check(L, 0 <= nuvalue && nuvalue < SHRT_MAX, "invalid value");
u = luaS_newudata(L, size, nuvalue); u = luaS_newudata(L, size, nuvalue);
setuvalue(L, s2v(L->top.p), u); setuvalue(L, s2v(L->top.p), u);
api_incr_top(L); api_incr_top(L);

View File

@@ -501,12 +501,25 @@ static const luaL_Reg boxmt[] = { /* box metamethods */
}; };
/*
** Get/create metatable (MT) for boxes
*/
static void getBoxMT (lua_State *L) {
const char *BOXMT = "_UBOX*"; /* key for the metatable */
if (luaL_getmetatable(L, BOXMT) == LUA_TNIL) { /* MT not created yet? */
luaL_newlibtable(L, boxmt); /* create it */
luaL_setfuncs(L, boxmt, 0); /* initialize it */
lua_copy(L, -1, -2); /* change stack from nil,MT to MT,MT */
lua_setfield(L, LUA_REGISTRYINDEX, BOXMT); /* store MT in the registry */
}
}
static void newbox (lua_State *L) { static void newbox (lua_State *L) {
UBox *box = (UBox *)lua_newuserdatauv(L, sizeof(UBox), 0); UBox *box = (UBox *)lua_newuserdatauv(L, sizeof(UBox), 0);
box->box = NULL; box->box = NULL;
box->bsize = 0; box->bsize = 0;
if (luaL_newmetatable(L, "_UBOX*")) /* creating metatable? */ getBoxMT(L);
luaL_setfuncs(L, boxmt, 0); /* set its metamethods */
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
} }

View File

@@ -35,6 +35,7 @@
#define MAXREGS 255 #define MAXREGS 255
/* (note that expressions VJMP also have jumps.) */
#define hasjumps(e) ((e)->t != (e)->f) #define hasjumps(e) ((e)->t != (e)->f)
@@ -985,7 +986,7 @@ void luaK_exp2anyregup (FuncState *fs, expdesc *e) {
** or it is a constant. ** or it is a constant.
*/ */
void luaK_exp2val (FuncState *fs, expdesc *e) { void luaK_exp2val (FuncState *fs, expdesc *e) {
if (hasjumps(e)) if (e->k == VJMP || hasjumps(e))
luaK_exp2anyreg(fs, e); luaK_exp2anyreg(fs, e);
else else
luaK_dischargevars(fs, e); luaK_dischargevars(fs, e);

View File

@@ -37,6 +37,9 @@
static const char *funcnamefromcall (lua_State *L, CallInfo *ci, static const char *funcnamefromcall (lua_State *L, CallInfo *ci,
const char **name); const char **name);
static const char strlocal[] = "local";
static const char strupval[] = "upvalue";
static int currentpc (CallInfo *ci) { static int currentpc (CallInfo *ci) {
lua_assert(isLua(ci)); lua_assert(isLua(ci));
@@ -497,7 +500,7 @@ static const char *basicgetobjname (const Proto *p, int *ppc, int reg,
int pc = *ppc; int pc = *ppc;
*name = luaF_getlocalname(p, reg + 1, pc); *name = luaF_getlocalname(p, reg + 1, pc);
if (*name) /* is a local? */ if (*name) /* is a local? */
return "local"; return strlocal;
/* else try symbolic execution */ /* else try symbolic execution */
*ppc = pc = findsetreg(p, pc, reg); *ppc = pc = findsetreg(p, pc, reg);
if (pc != -1) { /* could find instruction? */ if (pc != -1) { /* could find instruction? */
@@ -512,7 +515,7 @@ static const char *basicgetobjname (const Proto *p, int *ppc, int reg,
} }
case OP_GETUPVAL: { case OP_GETUPVAL: {
*name = upvalname(p, GETARG_B(i)); *name = upvalname(p, GETARG_B(i));
return "upvalue"; return strupval;
} }
case OP_LOADK: return kname(p, GETARG_Bx(i), name); case OP_LOADK: return kname(p, GETARG_Bx(i), name);
case OP_LOADKX: return kname(p, GETARG_Ax(p->code[pc + 1]), name); case OP_LOADKX: return kname(p, GETARG_Ax(p->code[pc + 1]), name);
@@ -547,15 +550,21 @@ static void rkname (const Proto *p, int pc, Instruction i, const char **name) {
/* /*
** Check whether table being indexed by instruction 'i' is the ** Check whether table being indexed by instruction 'i' is the
** environment '_ENV' ** environment '_ENV'. If the table is an upvalue, get its name;
** otherwise, find some "name" for the table and check whether
** that name is the name of a local variable (and not, for instance,
** a string). Then check that, if there is a name, it is '_ENV'.
*/ */
static const char *isEnv (const Proto *p, int pc, Instruction i, int isup) { static const char *isEnv (const Proto *p, int pc, Instruction i, int isup) {
int t = GETARG_B(i); /* table index */ int t = GETARG_B(i); /* table index */
const char *name; /* name of indexed variable */ const char *name; /* name of indexed variable */
if (isup) /* is 't' an upvalue? */ if (isup) /* is 't' an upvalue? */
name = upvalname(p, t); name = upvalname(p, t);
else /* 't' is a register */ else { /* 't' is a register */
basicgetobjname(p, &pc, t, &name); const char *what = basicgetobjname(p, &pc, t, &name);
if (what != strlocal && what != strupval)
name = NULL; /* cannot be the variable _ENV */
}
return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field"; return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field";
} }
@@ -701,7 +710,7 @@ static const char *getupvalname (CallInfo *ci, const TValue *o,
for (i = 0; i < c->nupvalues; i++) { for (i = 0; i < c->nupvalues; i++) {
if (c->upvals[i]->v.p == o) { if (c->upvals[i]->v.p == o) {
*name = upvalname(c->p, i); *name = upvalname(c->p, i);
return "upvalue"; return strupval;
} }
} }
return NULL; return NULL;

38
ldo.c
View File

@@ -94,10 +94,6 @@ void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */
break; break;
} }
case LUA_ERRERR: {
setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
break;
}
case LUA_OK: { /* special case only for closing upvalues */ case LUA_OK: { /* special case only for closing upvalues */
setnilvalue(s2v(oldtop)); /* no error message */ setnilvalue(s2v(oldtop)); /* no error message */
break; break;
@@ -120,6 +116,7 @@ l_noret luaD_throw (lua_State *L, int errcode) {
else { /* thread has no error handler */ else { /* thread has no error handler */
global_State *g = G(L); global_State *g = G(L);
errcode = luaE_resetthread(L, errcode); /* close all upvalues */ errcode = luaE_resetthread(L, errcode); /* close all upvalues */
L->status = errcode;
if (g->mainthread->errorJmp) { /* main thread has a handler? */ if (g->mainthread->errorJmp) { /* main thread has a handler? */
setobjs2s(L, g->mainthread->top.p++, L->top.p - 1); /* copy error obj. */ setobjs2s(L, g->mainthread->top.p++, L->top.p - 1); /* copy error obj. */
luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ luaD_throw(g->mainthread, errcode); /* re-throw in main thread */
@@ -198,6 +195,35 @@ static void correctstack (lua_State *L) {
/* some space for error handling */ /* some space for error handling */
#define ERRORSTACKSIZE (LUAI_MAXSTACK + 200) #define ERRORSTACKSIZE (LUAI_MAXSTACK + 200)
/* raise an error while running the message handler */
l_noret luaD_errerr (lua_State *L) {
TString *msg = luaS_newliteral(L, "error in error handling");
setsvalue2s(L, L->top.p, msg);
L->top.p++; /* assume EXTRA_STACK */
luaD_throw(L, LUA_ERRERR);
}
/*
** 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);
}
/* /*
** Reallocate the stack to a new size, correcting all pointers into it. ** Reallocate the stack to a new size, correcting all pointers into it.
** In ISO C, any pointer use after the pointer has been deallocated is ** In ISO C, any pointer use after the pointer has been deallocated is
@@ -247,7 +273,7 @@ int luaD_growstack (lua_State *L, int n, int raiseerror) {
a stack error; cannot grow further than that. */ a stack error; cannot grow further than that. */
lua_assert(stacksize(L) == ERRORSTACKSIZE); lua_assert(stacksize(L) == ERRORSTACKSIZE);
if (raiseerror) if (raiseerror)
luaD_throw(L, LUA_ERRERR); /* error inside message handler */ luaD_errerr(L); /* error inside message handler */
return 0; /* if not 'raiseerror', just signal it */ return 0; /* if not 'raiseerror', just signal it */
} }
else if (n < LUAI_MAXSTACK) { /* avoids arithmetic overflows */ else if (n < LUAI_MAXSTACK) { /* avoids arithmetic overflows */
@@ -496,7 +522,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))
l_sinline CallInfo *prepCallInfo (lua_State *L, StkId func, int nret, l_sinline CallInfo *prepCallInfo (lua_State *L, StkId func, int nret,

2
ldo.h
View File

@@ -60,6 +60,7 @@
/* type of protected functions, to be ran by 'runprotected' */ /* type of protected functions, to be ran by 'runprotected' */
typedef void (*Pfunc) (lua_State *L, void *ud); typedef void (*Pfunc) (lua_State *L, void *ud);
LUAI_FUNC l_noret luaD_errerr (lua_State *L);
LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop);
LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name,
const char *mode); const char *mode);
@@ -79,6 +80,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 int luaD_growstack (lua_State *L, int n, int raiseerror);
LUAI_FUNC void luaD_shrinkstack (lua_State *L); LUAI_FUNC void luaD_shrinkstack (lua_State *L);
LUAI_FUNC void luaD_inctop (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, int errcode); LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode);
LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);

15
lgc.c
View File

@@ -553,8 +553,12 @@ static lu_mem traversetable (global_State *g, Table *h) {
traverseweakvalue(g, h); traverseweakvalue(g, h);
else if (!weakvalue) /* strong values? */ else if (!weakvalue) /* strong values? */
traverseephemeron(g, h, 0); traverseephemeron(g, h, 0);
else /* all weak */ else { /* all weak */
linkgclist(h, g->allweak); /* nothing to traverse now */ if (g->gcstate == GCSpropagate)
linkgclist(h, g->grayagain); /* must visit again its metatable */
else
linkgclist(h, g->allweak); /* must clear collected entries */
}
} }
else /* not weak */ else /* not weak */
traversestrongtable(g, h); traversestrongtable(g, h);
@@ -1238,7 +1242,7 @@ static void finishgencycle (lua_State *L, global_State *g) {
correctgraylists(g); correctgraylists(g);
checkSizes(L, g); checkSizes(L, g);
g->gcstate = GCSpropagate; /* skip restart */ g->gcstate = GCSpropagate; /* skip restart */
if (!g->gcemergency) if (g->tobefnz != NULL && !g->gcemergency && luaD_checkminstack(L))
callallpendingfinalizers(L); callallpendingfinalizers(L);
} }
@@ -1628,11 +1632,12 @@ static lu_mem singlestep (lua_State *L) {
break; break;
} }
case GCScallfin: { /* call remaining finalizers */ case GCScallfin: { /* call remaining finalizers */
if (g->tobefnz && !g->gcemergency) { if (g->tobefnz && !g->gcemergency && luaD_checkminstack(L)) {
g->gcstopem = 0; /* ok collections during finalizers */ g->gcstopem = 0; /* ok collections during finalizers */
work = runafewfinalizers(L, GCFINMAX) * GCFINALIZECOST; work = runafewfinalizers(L, GCFINMAX) * GCFINALIZECOST;
} }
else { /* emergency mode or no more finalizers */ else { /* no more finalizers or emergency mode or no enough stack
to run finalizers */
g->gcstate = GCSpause; /* finish collection */ g->gcstate = GCSpause; /* finish collection */
work = 0; work = 0;
} }

View File

@@ -198,7 +198,7 @@ static int new_localvar (LexState *ls, TString *name) {
checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal,
MAXVARS, "local variables"); MAXVARS, "local variables");
luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1, luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1,
dyd->actvar.size, Vardesc, USHRT_MAX, "local variables"); dyd->actvar.size, Vardesc, SHRT_MAX, "local variables");
var = &dyd->actvar.arr[dyd->actvar.n++]; var = &dyd->actvar.arr[dyd->actvar.n++];
var->vd.kind = VDKREG; /* default */ var->vd.kind = VDKREG; /* default */
var->vd.name = name; var->vd.name = name;
@@ -849,12 +849,11 @@ static void recfield (LexState *ls, ConsControl *cc) {
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
int reg = ls->fs->freereg; int reg = ls->fs->freereg;
expdesc tab, key, val; expdesc tab, key, val;
if (ls->t.token == TK_NAME) { if (ls->t.token == TK_NAME)
checklimit(fs, cc->nh, MAX_INT, "items in a constructor");
codename(ls, &key); codename(ls, &key);
}
else /* ls->t.token == '[' */ else /* ls->t.token == '[' */
yindex(ls, &key); yindex(ls, &key);
checklimit(fs, cc->nh, MAX_INT, "items in a constructor");
cc->nh++; cc->nh++;
checknext(ls, '='); checknext(ls, '=');
tab = *cc->t; tab = *cc->t;
@@ -941,6 +940,8 @@ static void constructor (LexState *ls, expdesc *t) {
if (ls->t.token == '}') break; if (ls->t.token == '}') break;
closelistfield(fs, &cc); closelistfield(fs, &cc);
field(ls, &cc); field(ls, &cc);
checklimit(fs, cc.tostore + cc.na + cc.nh, INT_MAX/2,
"items in a constructor");
} while (testnext(ls, ',') || testnext(ls, ';')); } while (testnext(ls, ',') || testnext(ls, ';'));
check_match(ls, '}', '{', line); check_match(ls, '}', '{', line);
lastlistfield(fs, &cc); lastlistfield(fs, &cc);

View File

@@ -102,14 +102,19 @@ LUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) {
} }
CallInfo *luaE_extendCI (lua_State *L) { CallInfo *luaE_extendCI (lua_State *L, int err) {
CallInfo *ci; CallInfo *ci;
lua_assert(L->ci->next == NULL); ci = luaM_reallocvector(L, NULL, 0, 1, CallInfo);
ci = luaM_new(L, CallInfo); if (l_unlikely(ci == NULL)) { /* allocation failed? */
lua_assert(L->ci->next == NULL); if (err)
L->ci->next = ci; luaM_error(L); /* raise the error */
return NULL; /* else only report it */
}
ci->next = L->ci->next;
ci->previous = L->ci; ci->previous = L->ci;
ci->next = NULL; L->ci->next = ci;
if (ci->next)
ci->next->previous = ci;
ci->u.l.trap = 0; ci->u.l.trap = 0;
L->nci++; L->nci++;
return ci; return ci;
@@ -166,7 +171,7 @@ void luaE_checkcstack (lua_State *L) {
if (getCcalls(L) == LUAI_MAXCCALLS) if (getCcalls(L) == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow"); luaG_runerror(L, "C stack overflow");
else if (getCcalls(L) >= (LUAI_MAXCCALLS / 10 * 11)) else if (getCcalls(L) >= (LUAI_MAXCCALLS / 10 * 11))
luaD_throw(L, LUA_ERRERR); /* error while handling stack error */ luaD_errerr(L); /* error while handling stack error */
} }
@@ -272,7 +277,9 @@ static void close_state (lua_State *L) {
luaC_freeallobjects(L); /* just collect its objects */ luaC_freeallobjects(L); /* just collect its objects */
else { /* closing a fully built state */ else { /* closing a fully built state */
L->ci = &L->base_ci; /* unwind CallInfo list */ L->ci = &L->base_ci; /* unwind CallInfo list */
L->errfunc = 0; /* stack unwind can "throw away" the error function */
luaD_closeprotected(L, 1, LUA_OK); /* close all upvalues */ luaD_closeprotected(L, 1, LUA_OK); /* close all upvalues */
L->top.p = L->stack.p + 1; /* empty the stack to run finalizers */
luaC_freeallobjects(L); /* collect all objects */ luaC_freeallobjects(L); /* collect all objects */
luai_userstateclose(L); luai_userstateclose(L);
} }
@@ -328,6 +335,7 @@ int luaE_resetthread (lua_State *L, int status) {
if (status == LUA_YIELD) if (status == LUA_YIELD)
status = LUA_OK; status = LUA_OK;
L->status = LUA_OK; /* so it can run __close metamethods */ L->status = LUA_OK; /* so it can run __close metamethods */
L->errfunc = 0; /* stack unwind can "throw away" the error function */
status = luaD_closeprotected(L, 1, status); status = luaD_closeprotected(L, 1, status);
if (status != LUA_OK) /* errors? */ if (status != LUA_OK) /* errors? */
luaD_seterrorobj(L, status, L->stack.p + 1); luaD_seterrorobj(L, status, L->stack.p + 1);

View File

@@ -395,7 +395,7 @@ union GCUnion {
LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt); LUAI_FUNC void luaE_setdebt (global_State *g, l_mem debt);
LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1);
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_shrinkCI (lua_State *L);
LUAI_FUNC void luaE_checkcstack (lua_State *L); LUAI_FUNC void luaE_checkcstack (lua_State *L);
LUAI_FUNC void luaE_incCstack (lua_State *L); LUAI_FUNC void luaE_incCstack (lua_State *L);

View File

@@ -757,7 +757,6 @@ static int nospecials (const char *p, size_t l) {
static void prepstate (MatchState *ms, lua_State *L, static void prepstate (MatchState *ms, lua_State *L,
const char *s, size_t ls, const char *p, size_t lp) { const char *s, size_t ls, const char *p, size_t lp) {
ms->L = L; ms->L = L;
ms->matchdepth = MAXCCALLS;
ms->src_init = s; ms->src_init = s;
ms->src_end = s + ls; ms->src_end = s + ls;
ms->p_end = p + lp; ms->p_end = p + lp;
@@ -765,8 +764,8 @@ static void prepstate (MatchState *ms, lua_State *L,
static void reprepstate (MatchState *ms) { static void reprepstate (MatchState *ms) {
ms->matchdepth = MAXCCALLS;
ms->level = 0; ms->level = 0;
lua_assert(ms->matchdepth == MAXCCALLS);
} }

View File

@@ -1655,6 +1655,9 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
int level = getnum; int level = getnum;
luaL_traceback(L1, L1, msg, level); luaL_traceback(L1, L1, msg, level);
} }
else if EQ("threadstatus") {
lua_pushstring(L1, statcodes[lua_status(L1)]);
}
else if EQ("return") { else if EQ("return") {
int n = getnum; int n = getnum;
if (L1 != L) { if (L1 != L) {

14
lua.c
View File

@@ -302,7 +302,8 @@ static int collectargs (char **argv, int *first) {
case '-': /* '--' */ case '-': /* '--' */
if (argv[i][2] != '\0') /* extra characters after '--'? */ if (argv[i][2] != '\0') /* extra characters after '--'? */
return has_error; /* invalid option */ return has_error; /* invalid option */
*first = i + 1; /* if there is a script name, it comes after '--' */
*first = (argv[i + 1] != NULL) ? i + 1 : 0;
return args; return args;
case '\0': /* '-' */ case '\0': /* '-' */
return args; /* script "name" is '-' */ return args; /* script "name" is '-' */
@@ -490,10 +491,8 @@ static int incomplete (lua_State *L, int status) {
if (status == LUA_ERRSYNTAX) { if (status == LUA_ERRSYNTAX) {
size_t lmsg; size_t lmsg;
const char *msg = lua_tolstring(L, -1, &lmsg); const char *msg = lua_tolstring(L, -1, &lmsg);
if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) { if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0)
lua_pop(L, 1);
return 1; return 1;
}
} }
return 0; /* else... */ return 0; /* else... */
} }
@@ -508,9 +507,9 @@ static int pushline (lua_State *L, int firstline) {
size_t l; size_t l;
const char *prmt = get_prompt(L, firstline); const char *prmt = get_prompt(L, firstline);
int readstatus = lua_readline(L, b, prmt); int readstatus = lua_readline(L, b, prmt);
if (readstatus == 0)
return 0; /* no input (prompt will be popped by caller) */
lua_pop(L, 1); /* remove prompt */ lua_pop(L, 1); /* remove prompt */
if (readstatus == 0)
return 0; /* no input */
l = strlen(b); l = strlen(b);
if (l > 0 && b[l-1] == '\n') /* line ends with newline? */ if (l > 0 && b[l-1] == '\n') /* line ends with newline? */
b[--l] = '\0'; /* remove it */ b[--l] = '\0'; /* remove it */
@@ -552,8 +551,9 @@ static int multiline (lua_State *L) {
int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */ int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */
if (!incomplete(L, status) || !pushline(L, 0)) { if (!incomplete(L, status) || !pushline(L, 0)) {
lua_saveline(L, line); /* keep history */ lua_saveline(L, line); /* keep history */
return status; /* cannot or should not try to add continuation line */ return status; /* should not or cannot try to add continuation line */
} }
lua_remove(L, -2); /* remove error message (from incomplete line) */
lua_pushliteral(L, "\n"); /* add newline... */ lua_pushliteral(L, "\n"); /* add newline... */
lua_insert(L, -2); /* ...between the two lines */ lua_insert(L, -2); /* ...between the two lines */
lua_concat(L, 3); /* join them */ lua_concat(L, 3); /* join them */

8
lua.h
View File

@@ -18,14 +18,14 @@
#define LUA_VERSION_MAJOR "5" #define LUA_VERSION_MAJOR "5"
#define LUA_VERSION_MINOR "4" #define LUA_VERSION_MINOR "4"
#define LUA_VERSION_RELEASE "7" #define LUA_VERSION_RELEASE "9"
#define LUA_VERSION_NUM 504 #define LUA_VERSION_NUM 504
#define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 7) #define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + 9)
#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR #define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR
#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE #define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE
#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2024 Lua.org, PUC-Rio" #define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2026 Lua.org, PUC-Rio"
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes"
@@ -497,7 +497,7 @@ struct lua_Debug {
/****************************************************************************** /******************************************************************************
* Copyright (C) 1994-2024 Lua.org, PUC-Rio. * Copyright (C) 1994-2026 Lua.org, PUC-Rio.
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the

View File

@@ -65,6 +65,8 @@ static const char *utf8_decode (const char *s, utfint *val, int strict) {
utfint res = 0; /* final result */ utfint res = 0; /* final result */
if (c < 0x80) /* ascii? */ if (c < 0x80) /* ascii? */
res = c; res = c;
else if (c >= 0xfe) /* c >= 1111 1110b ? */
return NULL; /* would need six or more continuation bytes */
else { else {
int count = 0; /* to count number of continuation bytes */ int count = 0; /* to count number of continuation bytes */
for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */ for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */
@@ -74,7 +76,7 @@ static const char *utf8_decode (const char *s, utfint *val, int strict) {
res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */
} }
res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */ res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */
if (count > 5 || res > MAXUTF || res < limits[count]) if (res > MAXUTF || res < limits[count])
return NULL; /* invalid byte sequence */ return NULL; /* invalid byte sequence */
s += count; /* skip continuation bytes read */ s += count; /* skip continuation bytes read */
} }

10
lvm.c
View File

@@ -339,7 +339,10 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key,
lua_assert(isempty(slot)); /* slot must be empty */ lua_assert(isempty(slot)); /* slot must be empty */
tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get metamethod */ tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get metamethod */
if (tm == NULL) { /* no metamethod? */ if (tm == NULL) { /* no metamethod? */
sethvalue2s(L, L->top.p, h); /* anchor 't' */
L->top.p++; /* assume EXTRA_STACK */
luaH_finishset(L, h, key, slot, val); /* set new value */ luaH_finishset(L, h, key, slot, val); /* set new value */
L->top.p--;
invalidateTMcache(h); invalidateTMcache(h);
luaC_barrierback(L, obj2gco(h), val); luaC_barrierback(L, obj2gco(h), val);
return; return;
@@ -358,7 +361,12 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key,
} }
t = tm; /* else repeat assignment over 'tm' */ t = tm; /* else repeat assignment over 'tm' */
if (luaV_fastget(L, t, key, slot, luaH_get)) { if (luaV_fastget(L, t, key, slot, luaH_get)) {
luaV_finishfastset(L, t, slot, val); /* execute 'luaV_finishfastset', but preserving the original 't'
for the barrier. 't' and 'slot' can point to the same value,
and so the assignment can change 't' value */
GCObject *h = gcvalue(t);
setobj2t(L, cast(TValue *,slot), val);
luaC_barrierback(L, h, val);
return; /* done */ return; /* done */
} }
/* else 'return luaV_finishset(L, t, key, val, slot)' (loop) */ /* else 'return luaV_finishset(L, t, key, val, slot)' (loop) */

View File

@@ -30,7 +30,7 @@ by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes
<p> <p>
<small> <small>
<a href="http://www.lua.org/copyright.html">Copyright</a> <a href="http://www.lua.org/copyright.html">Copyright</a>
&copy; 2024 Lua.org, PUC-Rio. All rights reserved. &copy; 2026 Lua.org, PUC-Rio. All rights reserved.
</small> </small>
<hr> <hr>
@@ -58,7 +58,7 @@ end
local function compose (f,g) local function compose (f,g)
assert(f and g) assert(f and g)
return function (s) return g(f(s)) end return function (...) return g(f(...)) end
end end
local function concat (f, g) local function concat (f, g)
@@ -395,9 +395,10 @@ APIEntry = function (e)
local apiicmd, ne = string.match(e, "^(.-</span>)(.*)") local apiicmd, ne = string.match(e, "^(.-</span>)(.*)")
--io.stderr:write(e) --io.stderr:write(e)
if not apiicmd then if not apiicmd then
return antipara(Tag.hr() .. Tag.h3(a)) .. Tag.pre(h) .. e return antipara(Tag.hr() .. Tag.h3(a)) .. Tag.pre(h, {class="api"}) .. e
else else
return antipara(Tag.hr() .. Tag.h3(a)) .. apiicmd .. Tag.pre(h) .. ne return antipara(Tag.hr() .. Tag.h3(a)) .. apiicmd ..
Tag.pre(h, {class="api"}) .. ne
end end
end, end,

View File

@@ -107,7 +107,7 @@ for small machines and embedded systems.
Unless stated otherwise, Unless stated otherwise,
any overflow when manipulating integer values @def{wrap around}, 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, (In other words,
the actual result is the unique representable integer the actual result is the unique representable integer
that is equal modulo @M{2@sp{n}} to the mathematical result, that is equal modulo @M{2@sp{n}} to the mathematical result,
@@ -609,7 +609,7 @@ as soon as the collector can be sure the object
will not be accessed again in the normal execution of the program. will not be accessed again in the normal execution of the program.
(@Q{Normal execution} here excludes finalizers, (@Q{Normal execution} here excludes finalizers,
which can resurrect dead objects @see{finalizers}, which can resurrect dead objects @see{finalizers},
and excludes also operations using the debug library.) and it excludes also operations using the debug library.)
Note that the time when the collector can be sure that an object Note that the time when the collector can be sure that an object
is dead may not coincide with the programmer's expectations. is dead may not coincide with the programmer's expectations.
The only guarantees are that Lua will not collect an object The only guarantees are that Lua will not collect an object
@@ -1950,12 +1950,12 @@ Note that keys that are not positive integers
do not interfere with borders. do not interfere with borders.
A table with exactly one border is called a @def{sequence}. 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). 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. and therefore it is not a sequence.
(The @nil at index 4 is called a @emphx{hole}.) (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), has three borders (0, 3, and 6),
so it is not a sequence, too. so it is not a sequence, too.
The table @T{{}} is a sequence with border 0. The table @T{{}} is a sequence with border 0.
@@ -2318,7 +2318,7 @@ we recommend assigning the vararg expression
to a single variable and using that variable to a single variable and using that variable
in its place. 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 In all cases, when the construction needs
@Q{the n-th result} and there is no such result, @Q{the n-th result} and there is no such result,
it uses a @nil. it uses a @nil.
@@ -3471,9 +3471,9 @@ because a pseudo-index is not an actual stack position.
The type of integers in Lua. The type of integers in Lua.
By default this type is @id{long long}, 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} 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}.) (See @id{LUA_INT_TYPE} in @id{luaconf.h}.)
Lua also defines the constants Lua also defines the constants
@@ -3689,7 +3689,7 @@ passes to the allocator in every call.
@apii{0,1,m} @apii{0,1,m}
Creates a new empty table and pushes it onto the stack. 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)}.
} }

View File

@@ -287,7 +287,7 @@ print("final OK !!!")
--[[ --[[
***************************************************************************** *****************************************************************************
* Copyright (C) 1994-2016 Lua.org, PUC-Rio. * Copyright (C) 1994-2025 Lua.org, PUC-Rio.
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the

View File

@@ -399,6 +399,10 @@ do
-- trivial error -- trivial error
assert(T.checkpanic("pushstring hi; error") == "hi") assert(T.checkpanic("pushstring hi; error") == "hi")
-- thread status inside panic (bug in 5.4.4)
assert(T.checkpanic("pushstring hi; error", "threadstatus; return 2") ==
"ERRRUN")
-- using the stack inside panic -- using the stack inside panic
assert(T.checkpanic("pushstring hi; error;", assert(T.checkpanic("pushstring hi; error;",
[[checkstack 5 XX [[checkstack 5 XX

View File

@@ -3,6 +3,14 @@
print "testing closures" print "testing closures"
do -- bug in 5.4.7
_ENV[true] = 10
local function aux () return _ENV[1 < 2] end
assert(aux() == 10)
_ENV[true] = nil
end
local A,B = 0,{g=10} local A,B = 0,{g=10}
local function f(x) local function f(x)
local a = {} local a = {}

View File

@@ -493,6 +493,25 @@ assert(not pcall(a, a))
a = nil a = nil
do
-- bug in 5.4: thread can use message handler higher in the stack
-- than the variable being closed
local c = coroutine.create(function()
local clo <close> = setmetatable({}, {__close=function()
local x = 134 -- will overwrite message handler
error(x)
end})
-- yields coroutine but leaves a new message handler for it,
-- that would be used when closing the coroutine (except that it
-- will be overwritten)
xpcall(coroutine.yield, function() return "XXX" end)
end)
assert(coroutine.resume(c)) -- start coroutine
local st, msg = coroutine.close(c)
assert(not st and msg == 134)
end
-- access to locals of erroneous coroutines -- access to locals of erroneous coroutines
local x = coroutine.create (function () local x = coroutine.create (function ()
local a = 10 local a = 10

View File

@@ -137,6 +137,10 @@ checkmessage("aaa=(1)..{}", "a table value")
-- bug in 5.4.6 -- bug in 5.4.6
checkmessage("a = {_ENV = {}}; print(a._ENV.x + 1)", "field 'x'") checkmessage("a = {_ENV = {}}; print(a._ENV.x + 1)", "field 'x'")
-- a similar bug in 5.4.7, since 5.4.0
checkmessage("print(('_ENV').x + 1)", "field 'x'")
_G.aaa, _G.bbbb = nil _G.aaa, _G.bbbb = nil
-- calls -- calls

View File

@@ -370,6 +370,29 @@ x = 0 .."a".."b"..c..d.."e".."f".."g"
assert(x.val == "0abcdefg") assert(x.val == "0abcdefg")
do
-- bug since 5.4.1
local mt = setmetatable({__newindex={}}, {__mode='v'})
local t = setmetatable({}, mt)
if T then T.allocfailnext() end
-- seg. fault
for i=1, 10 do t[i] = 1 end
end
do -- bug in 5.4
local parent = {}
parent.__newindex = parent
collectgarbage()
local child = setmetatable({}, parent)
child.__newindex = {x = "hello"}
collectgarbage("step")
assert(parent.__newindex.x == "hello")
end
-- concat metamethod x numbers (bug in 5.1.1) -- concat metamethod x numbers (bug in 5.1.1)
c = {} c = {}
local x local x

View File

@@ -301,6 +301,16 @@ collectgarbage()
assert(next(a) == string.rep('$', 11)) assert(next(a) == string.rep('$', 11))
if T then -- bug since 5.3: all-weak tables are not being revisited
T.gcstate("propagate")
local t = setmetatable({}, {__mode = "kv"})
T.gcstate("atomic") -- 't' was visited
setmetatable(t, {__mode = "kv"})
T.gcstate("pause") -- its new metatable is not being visited
assert(getmetatable(t).__mode == "kv")
end
-- 'bug' in 5.1 -- 'bug' in 5.1
a = {} a = {}
local t = {x = 10} local t = {x = 10}

View File

@@ -90,7 +90,7 @@ prepfile[[
1, a 1, a
) )
]] ]]
RUN('lua - < %s > %s', prog, out) RUN('lua - -- < %s > %s', prog, out)
checkout("1\tnil\n") checkout("1\tnil\n")
RUN('echo "print(10)\nprint(2)\n" | lua > %s', out) RUN('echo "print(10)\nprint(2)\n" | lua > %s', out)
@@ -133,7 +133,7 @@ checkout("-h\n")
prepfile("print(package.path)") prepfile("print(package.path)")
-- test LUA_PATH -- test LUA_PATH
RUN('env LUA_INIT= LUA_PATH=x lua %s > %s', prog, out) RUN('env LUA_INIT= LUA_PATH=x lua -- %s > %s', prog, out)
checkout("x\n") checkout("x\n")
-- test LUA_PATH_version -- test LUA_PATH_version
@@ -346,9 +346,14 @@ RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out)
checkprogout("6\n10\n10\n\n") checkprogout("6\n10\n10\n\n")
prepfile("a = [[b\nc\nd\ne]]\n=a") prepfile("a = [[b\nc\nd\ne]]\n=a")
RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i -- < %s > %s]], prog, out)
checkprogout("b\nc\nd\ne\n\n") checkprogout("b\nc\nd\ne\n\n")
-- input interrupted in continuation line
prepfile("a.\n")
RUN([[lua -i < %s > /dev/null 2> %s]], prog, out)
checkprogout("near <eof>\n")
local prompt = "alo" local prompt = "alo"
prepfile[[ -- prepfile[[ --
a = 2 a = 2
@@ -473,12 +478,14 @@ assert(not os.remove(out))
-- invalid options -- invalid options
NoRun("unrecognized option '-h'", "lua -h") NoRun("unrecognized option '-h'", "lua -h")
NoRun("unrecognized option '---'", "lua ---") NoRun("unrecognized option '---'", "lua ---")
NoRun("unrecognized option '-Ex'", "lua -Ex") NoRun("unrecognized option '-Ex'", "lua -Ex --")
NoRun("unrecognized option '-vv'", "lua -vv") NoRun("unrecognized option '-vv'", "lua -vv")
NoRun("unrecognized option '-iv'", "lua -iv") NoRun("unrecognized option '-iv'", "lua -iv")
NoRun("'-e' needs argument", "lua -e") NoRun("'-e' needs argument", "lua -e")
NoRun("syntax error", "lua -e a") NoRun("syntax error", "lua -e a")
NoRun("'-l' needs argument", "lua -l") NoRun("'-l' needs argument", "lua -l")
NoRun("-i", "lua -- -i") -- handles -i as a script name
if T then -- test library? if T then -- test library?