From 25a491fe349fc52b69ece2ecbcb0b0189decb36f Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 11 Dec 2024 13:56:03 -0300 Subject: [PATCH 001/165] OP_SELF restricted to constant short strings Optimize this opcode for the common case. For long names or method calls after too many constants, operation can be coded as a move followed by 'gettable'. --- lcode.c | 43 +++++++++++++++++++++++++++---------------- ldebug.c | 15 ++------------- lopcodes.h | 2 +- lvm.c | 6 +++--- testes/errors.lua | 3 ++- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/lcode.c b/lcode.c index 4267079495..6c124ff64b 100644 --- a/lcode.c +++ b/lcode.c @@ -1085,22 +1085,6 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { } -/* -** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). -*/ -void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { - int ereg; - luaK_exp2anyreg(fs, e); - ereg = e->u.info; /* register where 'e' was placed */ - freeexp(fs, e); - e->u.info = fs->freereg; /* base register for op_self */ - e->k = VNONRELOC; /* self expression has a fixed register */ - luaK_reserveregs(fs, 2); /* function and 'self' produced by op_self */ - codeABRK(fs, OP_SELF, e->u.info, ereg, key); - freeexp(fs, key); -} - - /* ** Negate condition 'e' (where 'e' is a comparison). */ @@ -1275,6 +1259,33 @@ static int isSCnumber (expdesc *e, int *pi, int *isfloat) { } +/* +** Emit SELF instruction or equivalent: the code will convert +** expression 'e' into 'e.key(e,'. +*/ +void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { + int ereg, base; + luaK_exp2anyreg(fs, e); + ereg = e->u.info; /* register where 'e' (the receiver) was placed */ + freeexp(fs, e); + base = e->u.info = fs->freereg; /* base register for op_self */ + e->k = VNONRELOC; /* self expression has a fixed register */ + luaK_reserveregs(fs, 2); /* method and 'self' produced by op_self */ + lua_assert(key->k == VKSTR); + /* is method name a short string in a valid K index? */ + if (strisshr(key->u.strval) && luaK_exp2K(fs, key)) { + /* can use 'self' opcode */ + luaK_codeABCk(fs, OP_SELF, base, ereg, key->u.info, 0); + } + else { /* cannot use 'self' opcode; use move+gettable */ + luaK_exp2anyreg(fs, key); /* put method name in a register */ + luaK_codeABC(fs, OP_MOVE, base + 1, ereg, 0); /* copy self to base+1 */ + luaK_codeABC(fs, OP_GETTABLE, base, ereg, key->u.info); /* get method */ + } + freeexp(fs, key); +} + + /* ** Create expression 't[k]'. 't' must have its final result already in a ** register or upvalue. Upvalues can only be indexed by literal strings. diff --git a/ldebug.c b/ldebug.c index ee3ac17fb5..09ec197c42 100644 --- a/ldebug.c +++ b/ldebug.c @@ -541,18 +541,6 @@ static void rname (const Proto *p, int pc, int c, const char **name) { } -/* -** Find a "name" for a 'C' value in an RK instruction. -*/ -static void rkname (const Proto *p, int pc, Instruction i, const char **name) { - int c = GETARG_C(i); /* key index */ - if (GETARG_k(i)) /* is 'c' a constant? */ - kname(p, c, name); - else /* 'c' is a register */ - rname(p, pc, c, name); -} - - /* ** Check whether table being indexed by instruction 'i' is the ** environment '_ENV' @@ -600,7 +588,8 @@ static const char *getobjname (const Proto *p, int lastpc, int reg, return isEnv(p, lastpc, i, 0); } case OP_SELF: { - rkname(p, lastpc, i, name); + int k = GETARG_C(i); /* key index */ + kname(p, k, name); return "method"; } default: break; /* go through to return NULL */ diff --git a/lopcodes.h b/lopcodes.h index 31f6fac01b..7511eb2237 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -256,7 +256,7 @@ OP_SETFIELD,/* A B C R[A][K[B]:shortstring] := RK(C) */ OP_NEWTABLE,/* A B C k R[A] := {} */ -OP_SELF,/* A B C R[A+1] := R[B]; R[A] := R[B][RK(C):string] */ +OP_SELF,/* A B C R[A+1] := R[B]; R[A] := R[B][K[C]:shortstring] */ OP_ADDI,/* A B sC R[A] := R[B] + sC */ diff --git a/lvm.c b/lvm.c index 1c564a713d..b6b18a69aa 100644 --- a/lvm.c +++ b/lvm.c @@ -1382,10 +1382,10 @@ void luaV_execute (lua_State *L, CallInfo *ci) { StkId ra = RA(i); lu_byte tag; TValue *rb = vRB(i); - TValue *rc = RKC(i); - TString *key = tsvalue(rc); /* key must be a string */ + TValue *rc = KC(i); + TString *key = tsvalue(rc); /* key must be a short string */ setobj2s(L, ra + 1, rb); - luaV_fastget(rb, key, s2v(ra), luaH_getstr, tag); + luaV_fastget(rb, key, s2v(ra), luaH_getshortstr, tag); if (tagisempty(tag)) Protect(luaV_finishget(L, rb, rc, ra, tag)); vmbreak; diff --git a/testes/errors.lua b/testes/errors.lua index 0925fe582a..027e1b03af 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -321,7 +321,8 @@ t = nil checkmessage(s.."; aaa = bbb + 1", "global 'bbb'") checkmessage("local _ENV=_ENV;"..s.."; aaa = bbb + 1", "global 'bbb'") checkmessage(s.."; local t = {}; aaa = t.bbb + 1", "field 'bbb'") -checkmessage(s.."; local t = {}; t:bbb()", "method 'bbb'") +-- cannot use 'self' opcode +checkmessage(s.."; local t = {}; t:bbb()", "field 'bbb'") checkmessage([[aaa=9 repeat until 3==3 From 412e9a4d952d47631feddfa4ec25a520ec75b103 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 11 Dec 2024 15:32:43 -0300 Subject: [PATCH 002/165] 'luaH_fastseti' uses 'checknoTM' The extra check in checknoTM (versus only checking whether there is a metatable) is cheap, and it is not that uncommon for a table to have a metatable without a __newindex metafield. --- ltable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ltable.h b/ltable.h index 9c4eb937bc..e4aa98f047 100644 --- a/ltable.h +++ b/ltable.h @@ -58,7 +58,7 @@ { Table *h = t; lua_Unsigned u = l_castS2U(k) - 1u; \ if ((u < h->asize)) { \ lu_byte *tag = getArrTag(h, u); \ - if (h->metatable == NULL || !tagisempty(*tag)) \ + if (checknoTM(h->metatable, TM_NEWINDEX) || !tagisempty(*tag)) \ { fval2arr(h, u, tag, val); hres = HOK; } \ else hres = ~cast_int(u); } \ else { hres = luaH_psetint(h, k, val); }} From 7538f3886dfa091d661c56e48ebb1578ced8e467 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 16 Dec 2024 14:13:49 -0300 Subject: [PATCH 003/165] 'addk' broken in two functions --- lcode.c | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/lcode.c b/lcode.c index 6c124ff64b..e6a98bb649 100644 --- a/lcode.c +++ b/lcode.c @@ -537,6 +537,22 @@ static void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) { /* ** Add constant 'v' to prototype's list of constants (field 'k'). +*/ +static int addk (FuncState *fs, Proto *f, TValue *v) { + lua_State *L = fs->ls->L; + int oldsize = f->sizek; + int k = fs->nk; + luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); + while (oldsize < f->sizek) + setnilvalue(&f->k[oldsize++]); + setobj(L, &f->k[k], v); + fs->nk++; + luaC_barrier(L, f, v); + return k; +} + + +/* ** Use scanner's table to cache position of constants in constant list ** and try to reuse constants. Because some values should not be used ** as keys (nil cannot be a key, integer keys can collapse with float @@ -544,12 +560,11 @@ static void freeexps (FuncState *fs, expdesc *e1, expdesc *e2) { ** Note that all functions share the same table, so entering or exiting ** a function can make some indices wrong. */ -static int addk (FuncState *fs, TValue *key, TValue *v) { +static int k2proto (FuncState *fs, TValue *key, TValue *v) { TValue val; - lua_State *L = fs->ls->L; Proto *f = fs->f; int tag = luaH_get(fs->ls->h, key, &val); /* query scanner table */ - int k, oldsize; + int k; if (tag == LUA_VNUMINT) { /* is there an index there? */ k = cast_int(ivalue(&val)); /* correct value? (warning: must distinguish floats from integers!) */ @@ -558,17 +573,11 @@ static int addk (FuncState *fs, TValue *key, TValue *v) { return k; /* reuse index */ } /* constant not found; create a new entry */ - oldsize = f->sizek; - k = fs->nk; - /* numerical value does not need GC barrier; + k = addk(fs, f, v); + /* cache for reuse; numerical value does not need GC barrier; table has no metatable, so it does not need to invalidate cache */ setivalue(&val, k); - luaH_set(L, fs->ls->h, key, &val); - luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); - while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); - setobj(L, &f->k[k], v); - fs->nk++; - luaC_barrier(L, f, v); + luaH_set(fs->ls->L, fs->ls->h, key, &val); return k; } @@ -579,7 +588,7 @@ static int addk (FuncState *fs, TValue *key, TValue *v) { static int stringK (FuncState *fs, TString *s) { TValue o; setsvalue(fs->ls->L, &o, s); - return addk(fs, &o, &o); /* use string itself as key */ + return k2proto(fs, &o, &o); /* use string itself as key */ } @@ -589,7 +598,7 @@ static int stringK (FuncState *fs, TString *s) { static int luaK_intK (FuncState *fs, lua_Integer n) { TValue o; setivalue(&o, n); - return addk(fs, &o, &o); /* use integer itself as key */ + return k2proto(fs, &o, &o); /* use integer itself as key */ } /* @@ -608,7 +617,7 @@ static int luaK_numberK (FuncState *fs, lua_Number r) { lua_Integer ik; setfltvalue(&o, r); if (!luaV_flttointeger(r, &ik, F2Ieq)) /* not an integral value? */ - return addk(fs, &o, &o); /* use number itself as key */ + return k2proto(fs, &o, &o); /* use number itself as key */ else { /* must build an alternative key */ const int nbm = l_floatatt(MANT_DIG); const lua_Number q = l_mathop(ldexp)(l_mathop(1.0), -nbm + 1); @@ -618,7 +627,7 @@ static int luaK_numberK (FuncState *fs, lua_Number r) { /* result is not an integral value, unless value is too large */ lua_assert(!luaV_flttointeger(k, &ik, F2Ieq) || l_mathop(fabs)(r) >= l_mathop(1e6)); - return addk(fs, &kv, &o); + return k2proto(fs, &kv, &o); } } @@ -629,7 +638,7 @@ static int luaK_numberK (FuncState *fs, lua_Number r) { static int boolF (FuncState *fs) { TValue o; setbfvalue(&o); - return addk(fs, &o, &o); /* use boolean itself as key */ + return k2proto(fs, &o, &o); /* use boolean itself as key */ } @@ -639,7 +648,7 @@ static int boolF (FuncState *fs) { static int boolT (FuncState *fs) { TValue o; setbtvalue(&o); - return addk(fs, &o, &o); /* use boolean itself as key */ + return k2proto(fs, &o, &o); /* use boolean itself as key */ } @@ -651,7 +660,7 @@ static int nilK (FuncState *fs) { setnilvalue(&v); /* cannot use nil as key; instead use table itself to represent nil */ sethvalue(fs->ls->L, &k, fs->ls->h); - return addk(fs, &k, &v); + return k2proto(fs, &k, &v); } From 1c40ff9faafed620aa0458b397bcbfbe19e0f663 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 17 Dec 2024 11:23:22 -0300 Subject: [PATCH 004/165] Scanner and parser use different tables for constants Moreover, each function being parsed has its own table. The code is cleaner when each table is used for one specific purpose: The scanner uses its table to anchor and unify strings, mapping strings to themselves; the parser uses it to reuse constants in the code, mapping constants to their indices in the constant table. A different table for each task avoids false collisions. --- lcode.c | 12 ++++++------ llex.c | 15 ++++++--------- lparser.c | 7 ++++++- lparser.h | 1 + ltable.c | 11 +---------- ltable.h | 2 -- 6 files changed, 20 insertions(+), 28 deletions(-) diff --git a/lcode.c b/lcode.c index e6a98bb649..8f08302eb5 100644 --- a/lcode.c +++ b/lcode.c @@ -563,13 +563,13 @@ static int addk (FuncState *fs, Proto *f, TValue *v) { static int k2proto (FuncState *fs, TValue *key, TValue *v) { TValue val; Proto *f = fs->f; - int tag = luaH_get(fs->ls->h, key, &val); /* query scanner table */ + int tag = luaH_get(fs->kcache, key, &val); /* query scanner table */ int k; - if (tag == LUA_VNUMINT) { /* is there an index there? */ + if (!tagisempty(tag)) { /* is there an index there? */ k = cast_int(ivalue(&val)); + lua_assert(k < fs->nk); /* correct value? (warning: must distinguish floats from integers!) */ - if (k < fs->nk && ttypetag(&f->k[k]) == ttypetag(v) && - luaV_rawequalobj(&f->k[k], v)) + if (ttypetag(&f->k[k]) == ttypetag(v) && luaV_rawequalobj(&f->k[k], v)) return k; /* reuse index */ } /* constant not found; create a new entry */ @@ -577,7 +577,7 @@ static int k2proto (FuncState *fs, TValue *key, TValue *v) { /* cache for reuse; numerical value does not need GC barrier; table has no metatable, so it does not need to invalidate cache */ setivalue(&val, k); - luaH_set(fs->ls->L, fs->ls->h, key, &val); + luaH_set(fs->ls->L, fs->kcache, key, &val); return k; } @@ -659,7 +659,7 @@ static int nilK (FuncState *fs) { TValue k, v; setnilvalue(&v); /* cannot use nil as key; instead use table itself to represent nil */ - sethvalue(fs->ls->L, &k, fs->ls->h); + sethvalue(fs->ls->L, &k, fs->kcache); return k2proto(fs, &k, &v); } diff --git a/llex.c b/llex.c index b2e77c9c87..d913db1754 100644 --- a/llex.c +++ b/llex.c @@ -130,18 +130,15 @@ l_noret luaX_syntaxerror (LexState *ls, const char *msg) { ** Creates a new string and anchors it in scanner's table so that it ** will not be collected until the end of the compilation; by that time ** it should be anchored somewhere. It also internalizes long strings, -** ensuring there is only one copy of each unique string. The table -** here is used as a set: the string enters as the key, while its value -** is irrelevant. We use the string itself as the value only because it -** is a TValue readily available. Later, the code generation can change -** this value. +** ensuring there is only one copy of each unique string. */ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { lua_State *L = ls->L; TString *ts = luaS_newlstr(L, str, l); /* create new string */ - TString *oldts = luaH_getstrkey(ls->h, ts); - if (oldts != NULL) /* string already present? */ - return oldts; /* use it */ + TValue oldts; + int tag = luaH_getstr(ls->h, ts, &oldts); + if (!tagisempty(tag)) /* string already present? */ + return tsvalue(&oldts); /* use stored value */ else { /* create a new entry */ TValue *stv = s2v(L->top.p++); /* reserve stack space for string */ setsvalue(L, stv, ts); /* temporarily anchor the string */ @@ -149,8 +146,8 @@ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { /* table is not a metatable, so it does not need to invalidate cache */ luaC_checkGC(L); L->top.p--; /* remove string from stack */ + return ts; } - return ts; } diff --git a/lparser.c b/lparser.c index 3db7df4cc5..642e43b7ea 100644 --- a/lparser.c +++ b/lparser.c @@ -737,6 +737,7 @@ static void codeclosure (LexState *ls, expdesc *v) { static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { + lua_State *L = ls->L; Proto *f = fs->f; fs->prev = ls->fs; /* linked list of funcstates */ fs->ls = ls; @@ -757,8 +758,11 @@ static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { fs->firstlabel = ls->dyd->label.n; fs->bl = NULL; f->source = ls->source; - luaC_objbarrier(ls->L, f, f->source); + luaC_objbarrier(L, f, f->source); f->maxstacksize = 2; /* registers 0/1 are always valid */ + fs->kcache = luaH_new(L); /* create table for function */ + sethvalue2s(L, L->top.p, fs->kcache); /* anchor it */ + luaD_inctop(L); enterblock(fs, bl, 0); } @@ -780,6 +784,7 @@ static void close_func (LexState *ls) { luaM_shrinkvector(L, f->locvars, f->sizelocvars, fs->ndebugvars, LocVar); luaM_shrinkvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc); ls->fs = fs->prev; + L->top.p--; /* pop kcache table */ luaC_checkGC(L); } diff --git a/lparser.h b/lparser.h index 8a87776d67..589befdb76 100644 --- a/lparser.h +++ b/lparser.h @@ -146,6 +146,7 @@ typedef struct FuncState { struct FuncState *prev; /* enclosing function */ struct LexState *ls; /* lexical state */ struct BlockCnt *bl; /* chain of current blocks */ + Table *kcache; /* cache for reusing constants */ int pc; /* next position to code (equivalent to 'ncode') */ int lasttarget; /* 'label' of last 'jump label' */ int previousline; /* last line that was saved in 'lineinfo' */ diff --git a/ltable.c b/ltable.c index 052e005e52..eb5abf9f3d 100644 --- a/ltable.c +++ b/ltable.c @@ -962,7 +962,7 @@ lu_byte luaH_getint (Table *t, lua_Integer key, TValue *res) { */ const TValue *luaH_Hgetshortstr (Table *t, TString *key) { Node *n = hashstr(t, key); - lua_assert(key->tt == LUA_VSHRSTR); + lua_assert(strisshr(key)); for (;;) { /* check whether 'key' is somewhere in the chain */ if (keyisshrstr(n) && eqshrstr(keystrval(n), key)) return gval(n); /* that's it */ @@ -997,15 +997,6 @@ lu_byte luaH_getstr (Table *t, TString *key, TValue *res) { } -TString *luaH_getstrkey (Table *t, TString *key) { - const TValue *o = Hgetstr(t, key); - if (!isabstkey(o)) /* string already present? */ - return keystrval(nodefromval(o)); /* get saved copy */ - else - return NULL; -} - - /* ** main search function */ diff --git a/ltable.h b/ltable.h index e4aa98f047..ca21e69202 100644 --- a/ltable.h +++ b/ltable.h @@ -154,8 +154,6 @@ LUAI_FUNC lu_byte luaH_getint (Table *t, lua_Integer key, TValue *res); /* Special get for metamethods */ LUAI_FUNC const TValue *luaH_Hgetshortstr (Table *t, TString *key); -LUAI_FUNC TString *luaH_getstrkey (Table *t, TString *key); - LUAI_FUNC int luaH_psetint (Table *t, lua_Integer key, TValue *val); LUAI_FUNC int luaH_psetshortstr (Table *t, TString *key, TValue *val); LUAI_FUNC int luaH_psetstr (Table *t, TString *key, TValue *val); From f81d0bbd4f940399eb4b68845802bc3fe1cad73a Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 17 Dec 2024 13:36:12 -0300 Subject: [PATCH 005/165] Detail in 'luaD_inctop' Protect stack top before possible stack reallocation. (In the current implementation, a stack reallocation cannot call an emergency collection, so there is no bug, but it is safer not to depend on that.) --- ldo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ldo.c b/ldo.c index 9459391f9f..f825d95950 100644 --- a/ldo.c +++ b/ldo.c @@ -373,8 +373,8 @@ void luaD_shrinkstack (lua_State *L) { void luaD_inctop (lua_State *L) { - luaD_checkstack(L, 1); L->top.p++; + luaD_checkstack(L, 1); } /* }================================================================== */ From 2a307f898be2716638e7ac30d119f7cd9bbbe096 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Sat, 28 Dec 2024 15:03:48 -0300 Subject: [PATCH 006/165] When parser reuses constants, only floats can collide Ensure that float constants never use integer keys, so that only floats can collide in 'k2proto'. --- lcode.c | 54 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/lcode.c b/lcode.c index 8f08302eb5..641f0d09cc 100644 --- a/lcode.c +++ b/lcode.c @@ -557,8 +557,6 @@ static int addk (FuncState *fs, Proto *f, TValue *v) { ** and try to reuse constants. Because some values should not be used ** as keys (nil cannot be a key, integer keys can collapse with float ** keys), the caller must provide a useful 'key' for indexing the cache. -** Note that all functions share the same table, so entering or exiting -** a function can make some indices wrong. */ static int k2proto (FuncState *fs, TValue *key, TValue *v) { TValue val; @@ -567,15 +565,14 @@ static int k2proto (FuncState *fs, TValue *key, TValue *v) { int k; if (!tagisempty(tag)) { /* is there an index there? */ k = cast_int(ivalue(&val)); - lua_assert(k < fs->nk); - /* correct value? (warning: must distinguish floats from integers!) */ - if (ttypetag(&f->k[k]) == ttypetag(v) && luaV_rawequalobj(&f->k[k], v)) - return k; /* reuse index */ + /* collisions can happen only for float keys */ + lua_assert(ttisfloat(key) || luaV_rawequalobj(&f->k[k], v)); + return k; /* reuse index */ } /* constant not found; create a new entry */ k = addk(fs, f, v); - /* cache for reuse; numerical value does not need GC barrier; - table has no metatable, so it does not need to invalidate cache */ + /* cache it for reuse; numerical value does not need GC barrier; + table is not a metatable, so it does not need to invalidate cache */ setivalue(&val, k); luaH_set(fs->ls->L, fs->kcache, key, &val); return k; @@ -607,27 +604,32 @@ static int luaK_intK (FuncState *fs, lua_Integer n) { ** with actual integers. To that, we add to the number its smaller ** power-of-two fraction that is still significant in its scale. ** For doubles, that would be 1/2^52. -** (This method is not bulletproof: there may be another float -** with that value, and for floats larger than 2^53 the result is -** still an integer. At worst, this only wastes an entry with -** a duplicate.) +** This method is not bulletproof: different numbers may generate the +** same key (e.g., very large numbers will overflow to 'inf') and for +** floats larger than 2^53 the result is still an integer. At worst, +** this only wastes an entry with a duplicate. */ static int luaK_numberK (FuncState *fs, lua_Number r) { - TValue o; - lua_Integer ik; - setfltvalue(&o, r); - if (!luaV_flttointeger(r, &ik, F2Ieq)) /* not an integral value? */ - return k2proto(fs, &o, &o); /* use number itself as key */ - else { /* must build an alternative key */ + TValue o, kv; + setfltvalue(&o, r); /* value as a TValue */ + if (r == 0) { /* handle zero as a special case */ + setpvalue(&kv, fs); /* use FuncState as index */ + return k2proto(fs, &kv, &o); /* cannot collide */ + } + else { const int nbm = l_floatatt(MANT_DIG); const lua_Number q = l_mathop(ldexp)(l_mathop(1.0), -nbm + 1); - const lua_Number k = (ik == 0) ? q : r + r*q; /* new key */ - TValue kv; - setfltvalue(&kv, k); - /* result is not an integral value, unless value is too large */ - lua_assert(!luaV_flttointeger(k, &ik, F2Ieq) || - l_mathop(fabs)(r) >= l_mathop(1e6)); - return k2proto(fs, &kv, &o); + const lua_Number k = r * (1 + q); /* key */ + lua_Integer ik; + setfltvalue(&kv, k); /* key as a TValue */ + if (!luaV_flttointeger(k, &ik, F2Ieq)) { /* not an integral value? */ + int n = k2proto(fs, &kv, &o); /* use key */ + if (luaV_rawequalobj(&fs->f->k[n], &o)) /* correct value? */ + return n; + } + /* else, either key is still an integer or there was a collision; + anyway, do not try to reuse constant; instead, create a new one */ + return addk(fs, fs->f, &o); } } @@ -658,7 +660,7 @@ static int boolT (FuncState *fs) { static int nilK (FuncState *fs) { TValue k, v; setnilvalue(&v); - /* cannot use nil as key; instead use table itself to represent nil */ + /* cannot use nil as key; instead use table itself */ sethvalue(fs->ls->L, &k, fs->kcache); return k2proto(fs, &k, &v); } From abf8b1cd4a798fada026b4046e9dbc08791963f2 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Sat, 28 Dec 2024 15:05:01 -0300 Subject: [PATCH 007/165] Small optimization in 'luaH_psetshortstr' Do not optimize only for table updates (key already present). Creation of new short keys in new tables can be quite common in programs that create lots of small tables, for instance with constructors like {x=e1,y=e2}. --- ltable.c | 81 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/ltable.c b/ltable.c index eb5abf9f3d..f67853675e 100644 --- a/ltable.c +++ b/ltable.c @@ -981,14 +981,19 @@ lu_byte luaH_getshortstr (Table *t, TString *key, TValue *res) { } +static const TValue *Hgetlongstr (Table *t, TString *key) { + TValue ko; + lua_assert(!strisshr(key)); + setsvalue(cast(lua_State *, NULL), &ko, key); + return getgeneric(t, &ko, 0); /* for long strings, use generic case */ +} + + static const TValue *Hgetstr (Table *t, TString *key) { - if (key->tt == LUA_VSHRSTR) + if (strisshr(key)) return luaH_Hgetshortstr(t, key); - else { /* for long strings, use generic case */ - TValue ko; - setsvalue(cast(lua_State *, NULL), &ko, key); - return getgeneric(t, &ko, 0); - } + else + return Hgetlongstr(t, key); } @@ -1025,15 +1030,25 @@ lu_byte luaH_get (Table *t, const TValue *key, TValue *res) { } +/* +** When a 'pset' cannot be completed, this function returns an encoding +** of its result, to be used by 'luaH_finishset'. +*/ +static int retpsetcode (Table *t, const TValue *slot) { + if (isabstkey(slot)) + return HNOTFOUND; /* no slot with that key */ + else /* return node encoded */ + return cast_int((cast(Node*, slot) - t->node)) + HFIRSTNODE; +} + + static int finishnodeset (Table *t, const TValue *slot, TValue *val) { if (!ttisnil(slot)) { setobj(((lua_State*)NULL), cast(TValue*, slot), val); return HOK; /* success */ } - else if (isabstkey(slot)) - return HNOTFOUND; /* no slot with that key */ - else /* return node encoded */ - return cast_int((cast(Node*, slot) - t->node)) + HFIRSTNODE; + else + return retpsetcode(t, slot); } @@ -1060,13 +1075,45 @@ static int psetint (Table *t, lua_Integer key, TValue *val) { } +/* +** This function could be just this: +** return finishnodeset(t, luaH_Hgetshortstr(t, key), val); +** However, it optimizes the common case created by constructors (e.g., +** {x=1, y=2}), which creates a key in a table that has no metatable, +** it is not old/black, and it already has space for the key. +*/ + int luaH_psetshortstr (Table *t, TString *key, TValue *val) { - return finishnodeset(t, luaH_Hgetshortstr(t, key), val); + const TValue *slot = luaH_Hgetshortstr(t, key); + if (!ttisnil(slot)) { /* key already has a value? (all too common) */ + setobj(((lua_State*)NULL), cast(TValue*, slot), val); /* update it */ + return HOK; /* done */ + } + else if (checknoTM(t->metatable, TM_NEWINDEX)) { /* no metamethod? */ + if (ttisnil(val)) /* new value is nil? */ + return HOK; /* done (value is already nil/absent) */ + if (isabstkey(slot) && /* key is absent? */ + !(isblack(t) && iswhite(key))) { /* and don't need barrier? */ + TValue tk; /* key as a TValue */ + setsvalue(cast(lua_State *, NULL), &tk, key); + if (insertkey(t, &tk, val)) { /* insert key, if there is space */ + invalidateTMcache(t); + return HOK; + } + } + } + /* Else, either table has new-index metamethod, or it needs barrier, + or it needs to rehash for the new key. In any of these cases, the + operation cannot be completed here. Return a code for the caller. */ + return retpsetcode(t, slot); } int luaH_psetstr (Table *t, TString *key, TValue *val) { - return finishnodeset(t, Hgetstr(t, key), val); + if (strisshr(key)) + return luaH_psetshortstr(t, key, val); + else + return finishnodeset(t, Hgetlongstr(t, key), val); } @@ -1087,13 +1134,11 @@ int luaH_pset (Table *t, const TValue *key, TValue *val) { } /* -** Finish a raw "set table" operation, where 'slot' is where the value -** should have been (the result of a previous "get table"). -** Beware: when using this function you probably need to check a GC -** barrier and invalidate the TM cache. +** Finish a raw "set table" operation, where 'hres' encodes where the +** value should have been (the result of a previous 'pset' operation). +** Beware: when using this function the caller probably need to check a +** GC barrier and invalidate the TM cache. */ - - void luaH_finishset (lua_State *L, Table *t, const TValue *key, TValue *value, int hres) { lua_assert(hres != HOK); From 5894ca7b95d7fb05f1e93ee77e849a8d816d1c6d Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 30 Dec 2024 16:53:51 -0300 Subject: [PATCH 008/165] Scanner doesn't need to anchor reserved words --- llex.c | 30 +++++++++++++++++++----------- lstring.h | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/llex.c b/llex.c index d913db1754..3518f0dab6 100644 --- a/llex.c +++ b/llex.c @@ -127,21 +127,20 @@ l_noret luaX_syntaxerror (LexState *ls, const char *msg) { /* -** Creates a new string and anchors it in scanner's table so that it -** will not be collected until the end of the compilation; by that time -** it should be anchored somewhere. It also internalizes long strings, -** ensuring there is only one copy of each unique string. +** Anchors a string in scanner's table so that it will not be collected +** until the end of the compilation; by that time it should be anchored +** somewhere. It also internalizes long strings, ensuring there is only +** one copy of each unique string. */ -TString *luaX_newstring (LexState *ls, const char *str, size_t l) { +static TString *anchorstr (LexState *ls, TString *ts) { lua_State *L = ls->L; - TString *ts = luaS_newlstr(L, str, l); /* create new string */ TValue oldts; int tag = luaH_getstr(ls->h, ts, &oldts); if (!tagisempty(tag)) /* string already present? */ return tsvalue(&oldts); /* use stored value */ else { /* create a new entry */ TValue *stv = s2v(L->top.p++); /* reserve stack space for string */ - setsvalue(L, stv, ts); /* temporarily anchor the string */ + setsvalue(L, stv, ts); /* push (anchor) the string on the stack */ luaH_set(L, ls->h, stv, stv); /* t[string] = string */ /* table is not a metatable, so it does not need to invalidate cache */ luaC_checkGC(L); @@ -151,6 +150,14 @@ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { } +/* +** Creates a new string and anchors it in scanner's table. +*/ +TString *luaX_newstring (LexState *ls, const char *str, size_t l) { + return anchorstr(ls, luaS_newlstr(ls->L, str, l)); +} + + /* ** increment line number and skips newline sequence (any of ** \n, \r, \n\r, or \r\n) @@ -544,12 +551,13 @@ static int llex (LexState *ls, SemInfo *seminfo) { do { save_and_next(ls); } while (lislalnum(ls->current)); - ts = luaX_newstring(ls, luaZ_buffer(ls->buff), - luaZ_bufflen(ls->buff)); - seminfo->ts = ts; - if (isreserved(ts)) /* reserved word? */ + /* find or create string */ + ts = luaS_newlstr(ls->L, luaZ_buffer(ls->buff), + luaZ_bufflen(ls->buff)); + if (isreserved(ts)) /* reserved word? */ return ts->extra - 1 + FIRST_RESERVED; else { + seminfo->ts = anchorstr(ls, ts); return TK_NAME; } } diff --git a/lstring.h b/lstring.h index 26f4b8e1f3..1751e0434e 100644 --- a/lstring.h +++ b/lstring.h @@ -45,7 +45,7 @@ /* ** test whether a string is a reserved word */ -#define isreserved(s) ((s)->tt == LUA_VSHRSTR && (s)->extra > 0) +#define isreserved(s) (strisshr(s) && (s)->extra > 0) /* From 1ec251e091302515e54aa81d965840a5de4be0a1 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 6 Jan 2025 12:41:39 -0300 Subject: [PATCH 009/165] Detail (debugging aid) When compiling with option HARDMEMTESTS, every creation of a new key in a table forces an emergency GC. --- lgc.h | 8 ++++---- ltable.c | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lgc.h b/lgc.h index 339cc17622..ee0541793b 100644 --- a/lgc.h +++ b/lgc.h @@ -224,15 +224,15 @@ */ #if !defined(HARDMEMTESTS) -#define condchangemem(L,pre,pos) ((void)0) +#define condchangemem(L,pre,pos,emg) ((void)0) #else -#define condchangemem(L,pre,pos) \ - { if (gcrunning(G(L))) { pre; luaC_fullgc(L, 0); pos; } } +#define condchangemem(L,pre,pos,emg) \ + { if (gcrunning(G(L))) { pre; luaC_fullgc(L, emg); pos; } } #endif #define luaC_condGC(L,pre,pos) \ { if (G(L)->GCdebt <= 0) { pre; luaC_step(L); pos;}; \ - condchangemem(L,pre,pos); } + condchangemem(L,pre,pos,0); } /* more often than not, 'pre'/'pos' are empty */ #define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0) diff --git a/ltable.c b/ltable.c index f67853675e..b6b1fa1a96 100644 --- a/ltable.c +++ b/ltable.c @@ -910,6 +910,8 @@ static void luaH_newkey (lua_State *L, Table *t, const TValue *key, newcheckedkey(t, key, value); /* insert key in grown table */ } luaC_barrierback(L, obj2gco(t), key); + /* for debugging only: any new key may force an emergency collection */ + condchangemem(L, (void)0, (void)0, 1); } } From 8a3a49250ce4a7e46ec9e90810a61d9f97aece3d Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 6 Jan 2025 14:44:06 -0300 Subject: [PATCH 010/165] Detail Small improvement in line-tracing for internal debugging. --- lvm.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lvm.c b/lvm.c index b6b18a69aa..73d7ee4341 100644 --- a/lvm.c +++ b/lvm.c @@ -1175,8 +1175,12 @@ void luaV_execute (lua_State *L, CallInfo *ci) { Instruction i; /* instruction being executed */ vmfetch(); #if 0 - /* low-level line tracing for debugging Lua */ - printf("line: %d\n", luaG_getfuncline(cl->p, pcRel(pc, cl->p))); + { /* low-level line tracing for debugging Lua */ + #include "lopnames.h" + int pcrel = pcRel(pc, cl->p); + printf("line: %d; %s (%d)\n", luaG_getfuncline(cl->p, pcrel), + opnames[GET_OPCODE(i)], pcrel); + } #endif lua_assert(base == ci->func.p + 1); lua_assert(base <= L->top.p && L->top.p <= L->stack_last.p); From 7ca3c40b50b385ead6b8bc4c54de97b61d11a12a Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 10 Jan 2025 13:54:51 -0300 Subject: [PATCH 011/165] Another way to compile goto's The compilation of a goto or a label just create an entry and generate boilerplate code for the gotos. As we don't know yet whether it needs a CLOSE, we code a jump followed by a CLOSE, which is then dead code. When a block ends (and then we know for sure whether there are variables that need to be closed), we check the goto's against the labels of that block. When closing a goto against a label, if it needs a CLOSE, the compiler swaps the order of the jump and the CLOSE, making the CLOSE active. --- lparser.c | 187 +++++++++++++++++++++--------------------------- lparser.h | 2 +- lvm.c | 1 + testes/code.lua | 13 +++- testes/db.lua | 2 +- testes/goto.lua | 35 ++++++--- 6 files changed, 119 insertions(+), 121 deletions(-) diff --git a/lparser.c b/lparser.c index 642e43b7ea..6022b38eb0 100644 --- a/lparser.c +++ b/lparser.c @@ -530,18 +530,31 @@ static l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) { /* -** Solves the goto at index 'g' to given 'label' and removes it +** Closes the goto at index 'g' to given 'label' and removes it ** from the list of pending gotos. ** If it jumps into the scope of some variable, raises an error. +** The goto needs a CLOSE if it jumps out of a block with upvalues, +** or out of the scope of some variable and the block has upvalues +** (signaled by parameter 'bup'). */ -static void solvegoto (LexState *ls, int g, Labeldesc *label) { +static void closegoto (LexState *ls, int g, Labeldesc *label, int bup) { int i; + FuncState *fs = ls->fs; Labellist *gl = &ls->dyd->gt; /* list of gotos */ Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */ lua_assert(eqstr(gt->name, label->name)); if (l_unlikely(gt->nactvar < label->nactvar)) /* enter some scope? */ jumpscopeerror(ls, gt); - luaK_patchlist(ls->fs, gt->pc, label->pc); + if (gt->close || + (label->nactvar < gt->nactvar && bup)) { /* needs close? */ + lu_byte stklevel = reglevel(fs, label->nactvar); + /* move jump to CLOSE position */ + fs->f->code[gt->pc + 1] = fs->f->code[gt->pc]; + /* put CLOSE instruction at original position */ + fs->f->code[gt->pc] = CREATE_ABCk(OP_CLOSE, stklevel, 0, 0, 0); + gt->pc++; /* must point to jump instruction */ + } + luaK_patchlist(ls->fs, gt->pc, label->pc); /* goto jumps to label */ for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */ gl->arr[i] = gl->arr[i + 1]; gl->n--; @@ -549,14 +562,14 @@ static void solvegoto (LexState *ls, int g, Labeldesc *label) { /* -** Search for an active label with the given name. +** Search for an active label with the given name, starting at +** index 'ilb' (so that it can searh for all labels in current block +** or all labels in current function). */ -static Labeldesc *findlabel (LexState *ls, TString *name) { - int i; +static Labeldesc *findlabel (LexState *ls, TString *name, int ilb) { Dyndata *dyd = ls->dyd; - /* check labels in current function for a match */ - for (i = ls->fs->firstlabel; i < dyd->label.n; i++) { - Labeldesc *lb = &dyd->label.arr[i]; + for (; ilb < dyd->label.n; ilb++) { + Labeldesc *lb = &dyd->label.arr[ilb]; if (eqstr(lb->name, name)) /* correct label? */ return lb; } @@ -582,29 +595,19 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name, } -static int newgotoentry (LexState *ls, TString *name, int line, int pc) { - return newlabelentry(ls, &ls->dyd->gt, name, line, pc); -} - - /* -** Solves forward jumps. Check whether new label 'lb' matches any -** pending gotos in current block and solves them. Return true -** if any of the gotos need to close upvalues. +** Create an entry for the goto and the code for it. As it is not known +** at this point whether the goto may need a CLOSE, the code has a jump +** followed by an CLOSE. (As the CLOSE comes after the jump, it is a +** dead instruction; it works as a placeholder.) When the goto is closed +** against a label, if it needs a CLOSE, the two instructions swap +** positions, so that the CLOSE comes before the jump. */ -static int solvegotos (LexState *ls, Labeldesc *lb) { - Labellist *gl = &ls->dyd->gt; - int i = ls->fs->bl->firstgoto; - int needsclose = 0; - while (i < gl->n) { - if (eqstr(gl->arr[i].name, lb->name)) { - needsclose |= gl->arr[i].close; - solvegoto(ls, i, lb); /* will remove 'i' from the list */ - } - else - i++; - } - return needsclose; +static int newgotoentry (LexState *ls, TString *name, int line) { + FuncState *fs = ls->fs; + int pc = luaK_jump(fs); /* create jump */ + luaK_codeABC(fs, OP_CLOSE, 0, 1, 0); /* spaceholder, marked as dead */ + return newlabelentry(ls, &ls->dyd->gt, name, line, pc); } @@ -615,8 +618,7 @@ static int solvegotos (LexState *ls, Labeldesc *lb) { ** a close instruction if necessary. ** Returns true iff it added a close instruction. */ -static int createlabel (LexState *ls, TString *name, int line, - int last) { +static void createlabel (LexState *ls, TString *name, int line, int last) { FuncState *fs = ls->fs; Labellist *ll = &ls->dyd->label; int l = newlabelentry(ls, ll, name, line, luaK_getlabel(fs)); @@ -624,28 +626,37 @@ static int createlabel (LexState *ls, TString *name, int line, /* assume that locals are already out of scope */ ll->arr[l].nactvar = fs->bl->nactvar; } - if (solvegotos(ls, &ll->arr[l])) { /* need close? */ - luaK_codeABC(fs, OP_CLOSE, luaY_nvarstack(fs), 0, 0); - return 1; - } - return 0; } /* -** Adjust pending gotos to outer level of a block. +** Traverse the pending goto's of the finishing block checking whether +** each match some label of that block. Those that do not match are +** "exported" to the outer block, to be solved there. In particular, +** its 'nactvar' is updated with the level of the inner block, +** as the variables of the inner block are now out of scope. */ -static void movegotosout (FuncState *fs, BlockCnt *bl) { - int i; - Labellist *gl = &fs->ls->dyd->gt; - /* correct pending gotos to current block */ - for (i = bl->firstgoto; i < gl->n; i++) { /* for each pending goto */ - Labeldesc *gt = &gl->arr[i]; - /* leaving a variable scope? */ - if (reglevel(fs, gt->nactvar) > reglevel(fs, bl->nactvar)) - gt->close |= bl->upval; /* jump may need a close */ - gt->nactvar = bl->nactvar; /* update goto level */ +static void solvegotos (FuncState *fs, BlockCnt *bl) { + LexState *ls = fs->ls; + Labellist *gl = &ls->dyd->gt; + int outlevel = reglevel(fs, bl->nactvar); /* level outside the block */ + int igt = bl->firstgoto; /* first goto in the finishing block */ + while (igt < gl->n) { /* for each pending goto */ + Labeldesc *gt = &gl->arr[igt]; + /* search for a matching label in the current block */ + Labeldesc *lb = findlabel(ls, gt->name, bl->firstlabel); + if (lb != NULL) /* found a match? */ + closegoto(ls, igt, lb, bl->upval); /* close and remove goto */ + else { /* adjust 'goto' for outer block */ + /* block has variables to be closed and goto escapes the scope of + some variable? */ + if (bl->upval && reglevel(fs, gt->nactvar) > outlevel) + gt->close = 1; /* jump may need a close */ + gt->nactvar = bl->nactvar; /* correct level for outer block */ + igt++; /* go to next goto */ + } } + ls->dyd->label.n = bl->firstlabel; /* remove local labels */ } @@ -682,23 +693,20 @@ static l_noret undefgoto (LexState *ls, Labeldesc *gt) { static void leaveblock (FuncState *fs) { BlockCnt *bl = fs->bl; LexState *ls = fs->ls; - int hasclose = 0; - lu_byte stklevel = reglevel(fs, bl->nactvar); /* level outside the block */ + lu_byte stklevel = reglevel(fs, bl->nactvar); /* level outside block */ + if (bl->previous && bl->upval) /* need a 'close'? */ + luaK_codeABC(fs, OP_CLOSE, stklevel, 0, 0); + fs->freereg = stklevel; /* free registers */ removevars(fs, bl->nactvar); /* remove block locals */ lua_assert(bl->nactvar == fs->nactvar); /* back to level on entry */ if (bl->isloop) /* has to fix pending breaks? */ - hasclose = createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0); - if (!hasclose && bl->previous && bl->upval) /* still need a 'close'? */ - luaK_codeABC(fs, OP_CLOSE, stklevel, 0, 0); - fs->freereg = stklevel; /* free registers */ - ls->dyd->label.n = bl->firstlabel; /* remove local labels */ - fs->bl = bl->previous; /* current block now is previous one */ - if (bl->previous) /* was it a nested block? */ - movegotosout(fs, bl); /* update pending gotos to enclosing block */ - else { + createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0); + solvegotos(fs, bl); + if (bl->previous == NULL) { /* was it the last block? */ if (bl->firstgoto < ls->dyd->gt.n) /* still pending gotos? */ undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */ } + fs->bl = bl->previous; /* current block now is previous one */ } @@ -1446,40 +1454,27 @@ static int cond (LexState *ls) { } -static void gotostat (LexState *ls) { - FuncState *fs = ls->fs; - int line = ls->linenumber; +static void gotostat (LexState *ls, int line) { TString *name = str_checkname(ls); /* label's name */ - Labeldesc *lb = findlabel(ls, name); - if (lb == NULL) /* no label? */ - /* forward jump; will be resolved when the label is declared */ - newgotoentry(ls, name, line, luaK_jump(fs)); - else { /* found a label */ - /* backward jump; will be resolved here */ - int lblevel = reglevel(fs, lb->nactvar); /* label level */ - if (luaY_nvarstack(fs) > lblevel) /* leaving the scope of a variable? */ - luaK_codeABC(fs, OP_CLOSE, lblevel, 0, 0); - /* create jump and link it to the label */ - luaK_patchlist(fs, luaK_jump(fs), lb->pc); - } + newgotoentry(ls, name, line); } /* ** Break statement. Semantically equivalent to "goto break". */ -static void breakstat (LexState *ls) { - int line = ls->linenumber; +static void breakstat (LexState *ls, int line) { luaX_next(ls); /* skip break */ - newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, luaK_jump(ls->fs)); + newgotoentry(ls, luaS_newliteral(ls->L, "break"), line); } /* -** Check whether there is already a label with the given 'name'. +** Check whether there is already a label with the given 'name' at +** current function. */ static void checkrepeated (LexState *ls, TString *name) { - Labeldesc *lb = findlabel(ls, name); + Labeldesc *lb = findlabel(ls, name, ls->fs->firstlabel); if (l_unlikely(lb != NULL)) { /* already defined? */ const char *msg = "label '%s' already defined on line %d"; msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line); @@ -1669,38 +1664,16 @@ static void forstat (LexState *ls, int line) { static void test_then_block (LexState *ls, int *escapelist) { /* test_then_block -> [IF | ELSEIF] cond THEN block */ - BlockCnt bl; FuncState *fs = ls->fs; - expdesc v; - int jf; /* instruction to skip 'then' code (if condition is false) */ + int condtrue; luaX_next(ls); /* skip IF or ELSEIF */ - expr(ls, &v); /* read condition */ + condtrue = cond(ls); /* read condition */ checknext(ls, TK_THEN); - if (ls->t.token == TK_BREAK) { /* 'if x then break' ? */ - int line = ls->linenumber; - luaK_goiffalse(ls->fs, &v); /* will jump if condition is true */ - luaX_next(ls); /* skip 'break' */ - enterblock(fs, &bl, 0); /* must enter block before 'goto' */ - newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, v.t); - while (testnext(ls, ';')) {} /* skip semicolons */ - if (block_follow(ls, 0)) { /* jump is the entire block? */ - leaveblock(fs); - return; /* and that is it */ - } - else /* must skip over 'then' part if condition is false */ - jf = luaK_jump(fs); - } - else { /* regular case (not a break) */ - luaK_goiftrue(ls->fs, &v); /* skip over block if condition is false */ - enterblock(fs, &bl, 0); - jf = v.f; - } - statlist(ls); /* 'then' part */ - leaveblock(fs); + block(ls); /* 'then' part */ if (ls->t.token == TK_ELSE || ls->t.token == TK_ELSEIF) /* followed by 'else'/'elseif'? */ luaK_concat(fs, escapelist, luaK_jump(fs)); /* must jump over it */ - luaK_patchtohere(fs, jf); + luaK_patchtohere(fs, condtrue); } @@ -1928,12 +1901,12 @@ static void statement (LexState *ls) { break; } case TK_BREAK: { /* stat -> breakstat */ - breakstat(ls); + breakstat(ls, line); break; } case TK_GOTO: { /* stat -> 'goto' NAME */ luaX_next(ls); /* skip 'goto' */ - gotostat(ls); + gotostat(ls, line); break; } default: { /* stat -> func | assignment */ diff --git a/lparser.h b/lparser.h index 589befdb76..a8004fa0ce 100644 --- a/lparser.h +++ b/lparser.h @@ -112,7 +112,7 @@ typedef struct Labeldesc { int pc; /* position in code */ int line; /* line where it appeared */ lu_byte nactvar; /* number of active variables in that position */ - lu_byte close; /* goto that escapes upvalues */ + lu_byte close; /* true for goto that escapes upvalues */ } Labeldesc; diff --git a/lvm.c b/lvm.c index 73d7ee4341..074ee718ec 100644 --- a/lvm.c +++ b/lvm.c @@ -1590,6 +1590,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { } vmcase(OP_CLOSE) { StkId ra = RA(i); + lua_assert(!GETARG_B(i)); /* 'close must be alive */ Protect(luaF_close(L, ra, LUA_OK, 1)); vmbreak; } diff --git a/testes/code.lua b/testes/code.lua index 08b3e23faa..50ce7392f3 100644 --- a/testes/code.lua +++ b/testes/code.lua @@ -412,13 +412,22 @@ checkequal(function (l) local a; return 0 <= a and a <= l end, function (l) local a; return not (not(a >= 0) or not(a <= l)) end) --- if-break optimizations check(function (a, b) while a do if b then break else a = a + 1 end end end, -'TEST', 'JMP', 'TEST', 'JMP', 'ADDI', 'MMBINI', 'JMP', 'RETURN0') +'TEST', 'JMP', 'TEST', 'JMP', 'JMP', 'CLOSE', 'JMP', 'ADDI', 'MMBINI', 'JMP', 'RETURN0') + +check(function () + do + goto exit -- don't need to close + local x = nil + goto exit -- must close + end + ::exit:: + end, 'JMP', 'CLOSE', 'LOADNIL', 'TBC', + 'CLOSE', 'JMP', 'CLOSE', 'RETURN') checkequal(function () return 6 or true or nil end, function () return k6 or kTrue or kNil end) diff --git a/testes/db.lua b/testes/db.lua index fc0db9eae0..75730d27b0 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -128,7 +128,7 @@ then else a=2 end -]], {2,3,4,7}) +]], {2,4,7}) test([[ diff --git a/testes/goto.lua b/testes/goto.lua index 4ac6d7d089..103cccef52 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -250,21 +250,36 @@ assert(testG(3) == "3") assert(testG(4) == 5) assert(testG(5) == 10) -do - -- if x back goto out of scope of upvalue - local X +do -- test goto's around to-be-closed variable + + -- set 'var' and return an object that will reset 'var' when + -- it goes out of scope + local function newobj (var) + _ENV[var] = true + return setmetatable({}, {__close = function () + _ENV[var] = nil + end}) + end + goto L1 - ::L2:: goto L3 + ::L4:: assert(not X); goto L5 -- varX dead here - ::L1:: do - local a = setmetatable({}, {__close = function () X = true end}) - assert(X == nil) - if a then goto L2 end -- jumping back out of scope of 'a' - end + ::L1:: + local varX = newobj("X") + assert(X); goto L2 -- varX alive here - ::L3:: assert(X == true) -- checks that 'a' was correctly closed + ::L3:: + assert(X); goto L4 -- varX alive here + + ::L2:: assert(X); goto L3 -- varX alive here + + ::L5:: -- return end + + + +foo() -------------------------------------------------------------------------------- From 915c29f8bd0d4b0435a4b51a6c7913f5e170d09e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 10 Jan 2025 15:11:54 -0300 Subject: [PATCH 012/165] Improvements in the manual Plus details --- lapi.c | 3 +-- ldo.c | 2 +- lstate.h | 2 +- manual/manual.of | 34 ++++++++++++++++++++-------------- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lapi.c b/lapi.c index 01abfc15ad..4411cb2962 100644 --- a/lapi.c +++ b/lapi.c @@ -671,9 +671,8 @@ static int auxgetstr (lua_State *L, const TValue *t, const char *k) { lu_byte tag; TString *str = luaS_new(L, k); luaV_fastget(t, str, s2v(L->top.p), luaH_getstr, tag); - if (!tagisempty(tag)) { + if (!tagisempty(tag)) api_incr_top(L); - } else { setsvalue2s(L, L->top.p, str); api_incr_top(L); diff --git a/ldo.c b/ldo.c index f825d95950..009bf47ad2 100644 --- a/ldo.c +++ b/ldo.c @@ -367,7 +367,7 @@ void luaD_shrinkstack (lua_State *L) { luaD_reallocstack(L, nsize, 0); /* ok if that fails */ } else /* don't change stack */ - condmovestack(L,{},{}); /* (change only for debugging) */ + condmovestack(L,(void)0,(void)0); /* (change only for debugging) */ luaE_shrinkCI(L); /* shrink CI list */ } diff --git a/lstate.h b/lstate.h index 1c81b6edc9..e95c72880e 100644 --- a/lstate.h +++ b/lstate.h @@ -186,7 +186,7 @@ typedef struct stringtable { */ struct CallInfo { StkIdRel func; /* function index in the stack */ - StkIdRel top; /* top for this function */ + StkIdRel top; /* top for this function */ struct CallInfo *previous, *next; /* dynamic call link */ union { struct { /* only for Lua functions */ diff --git a/manual/manual.of b/manual/manual.of index a441cea13a..bb95148ad6 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1428,7 +1428,7 @@ except inside nested functions. A goto can jump to any visible label as long as it does not enter into the scope of a local variable. A label should not be declared -where a label with the same name is visible, +where a previous label with the same name is visible, even if this other label has been declared in an enclosing block. The @Rw{break} statement terminates the execution of a @@ -3835,7 +3835,7 @@ This macro may evaluate its arguments more than once. Converts the number at acceptable index @id{idx} to a string and puts the result in @id{buff}. -The buffer must have a size of at least @Lid{LUA_N2SBUFFSZ} bytes. +The buffer must have a size of at least @defid{LUA_N2SBUFFSZ} bytes. The conversion follows a non-specified format @see{coercion}. The function returns the number of bytes written to the buffer (including the final zero), @@ -3997,25 +3997,22 @@ Lua will call @id{falloc} before raising the error. Pushes onto the stack a formatted string and returns a pointer to this string @see{constchar}. -It is similar to the @ANSI{sprintf}, -but has two important differences. -First, -you do not have to allocate space for the result; -the result is a Lua string and Lua takes care of memory allocation -(and deallocation, through garbage collection). -Second, -the conversion specifiers are quite restricted. -There are no flags, widths, or precisions. -The conversion specifiers can only be +The result is a copy of @id{fmt} with +each @emph{conversion specifier} replaced by its respective +extra argument. +A conversion specifier can be @Char{%%} (inserts the character @Char{%}), @Char{%s} (inserts a zero-terminated string, with no size restrictions), @Char{%f} (inserts a @Lid{lua_Number}), @Char{%I} (inserts a @Lid{lua_Integer}), -@Char{%p} (inserts a pointer), +@Char{%p} (inserts a void pointer), @Char{%d} (inserts an @T{int}), @Char{%c} (inserts an @T{int} as a one-byte character), and @Char{%U} (inserts an @T{unsigned long} as a @x{UTF-8} byte sequence). +Every occurrence of @Char{%} in the string @id{fmt} +must form a valid conversion specifier. + } @APIEntry{void lua_pushglobaltable (lua_State *L);| @@ -4413,7 +4410,7 @@ for the @Q{newindex} event @see{metatable}. @APIEntry{void lua_settop (lua_State *L, int index);| @apii{?,?,e} -Accepts any index, @N{or 0}, +Receives any acceptable stack index, @N{or 0}, and sets the stack top to this index. If the new top is greater than the old one, then the new elements are filled with @nil. @@ -9427,6 +9424,15 @@ Moreover, there were some changes in the parameters themselves. @itemize{ +@item{ +In @Lid{lua_call} and related functions, +the maximum value for the number of required results +(@id{nresults}) is 250. +If you really need a larger value, +use @Lid{LUA_MULTRET} and then adjust the stack size. +Previously, this limit was unspecified. +} + @item{ @Lid{lua_newstate} has a third parameter, a seed for the hashing of strings. From 429761d7d29226dd0c220de9fdc7c28ea54d95c0 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 10 Jan 2025 15:14:01 -0300 Subject: [PATCH 013/165] New optimization option for testing Using gcc's option '-Og' (instead of '-O0') for testing/debugging. --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index e3f8cf698c..64dee501ec 100644 --- a/makefile +++ b/makefile @@ -63,7 +63,7 @@ CWARNS= $(CWARNSCPP) $(CWARNSC) $(CWARNGCC) # ASAN_OPTIONS="detect_invalid_pointer_pairs=2". # -fsanitize=undefined # -fsanitize=pointer-subtract -fsanitize=address -fsanitize=pointer-compare -# TESTS= -DLUA_USER_H='"ltests.h"' -O0 -g +# TESTS= -DLUA_USER_H='"ltests.h"' -Og -g LOCAL = $(TESTS) $(CWARNS) From 4b107a87760ee5f85185a904c9088ca476b94be5 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 10 Jan 2025 15:27:17 -0300 Subject: [PATCH 014/165] Details in lparser.c Added comments so that all braces pair correctly. (The parser has several instances of unmatched braces as characters ('{' or '}'), which hinders matching regular braces in the code.) --- lparser.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lparser.c b/lparser.c index 6022b38eb0..036f133ba7 100644 --- a/lparser.c +++ b/lparser.c @@ -797,10 +797,11 @@ static void close_func (LexState *ls) { } - -/*============================================================*/ -/* GRAMMAR RULES */ -/*============================================================*/ +/* +** {====================================================================== +** GRAMMAR RULES +** ======================================================================= +*/ /* @@ -974,15 +975,15 @@ static void constructor (LexState *ls, expdesc *t) { init_exp(t, VNONRELOC, fs->freereg); /* table will be at stack top */ luaK_reserveregs(fs, 1); init_exp(&cc.v, VVOID, 0); /* no value (yet) */ - checknext(ls, '{'); + checknext(ls, '{' /*}*/); cc.maxtostore = maxtostore(fs); do { lua_assert(cc.v.k == VVOID || cc.tostore > 0); - if (ls->t.token == '}') break; + if (ls->t.token == /*{*/ '}') break; closelistfield(fs, &cc); field(ls, &cc); } while (testnext(ls, ',') || testnext(ls, ';')); - check_match(ls, '}', '{', line); + check_match(ls, /*{*/ '}', '{' /*}*/, line); lastlistfield(fs, &cc); luaK_settablesize(fs, pc, t->u.info, cc.na, cc.nh); } @@ -1080,7 +1081,7 @@ static void funcargs (LexState *ls, expdesc *f) { check_match(ls, ')', '(', line); break; } - case '{': { /* funcargs -> constructor */ + case '{' /*}*/: { /* funcargs -> constructor */ constructor(ls, &args); break; } @@ -1167,7 +1168,7 @@ static void suffixedexp (LexState *ls, expdesc *v) { funcargs(ls, v); break; } - case '(': case TK_STRING: case '{': { /* funcargs */ + case '(': case TK_STRING: case '{' /*}*/: { /* funcargs */ luaK_exp2nextreg(fs, v); funcargs(ls, v); break; @@ -1215,7 +1216,7 @@ static void simpleexp (LexState *ls, expdesc *v) { init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 0, 1)); break; } - case '{': { /* constructor */ + case '{' /*}*/: { /* constructor */ constructor(ls, v); return; } @@ -1922,6 +1923,8 @@ static void statement (LexState *ls) { /* }====================================================================== */ +/* }====================================================================== */ + /* ** compiles the main function, which is a regular vararg function with an From 10e931da82268a9d190c17a9bdb9b1a4b48c2947 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 13 Jan 2025 11:23:07 -0300 Subject: [PATCH 015/165] Error "break outside loop" made a syntax error Syntax errors are easier to handle than semantic errors. --- lparser.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lparser.c b/lparser.c index 036f133ba7..83e341ed94 100644 --- a/lparser.c +++ b/lparser.c @@ -52,7 +52,7 @@ typedef struct BlockCnt { int firstgoto; /* index of first pending goto in this block */ lu_byte nactvar; /* # active locals outside the block */ lu_byte upval; /* true if some variable in the block is an upvalue */ - lu_byte isloop; /* true if 'block' is a loop */ + lu_byte isloop; /* 1 if 'block' is a loop; 2 if it has pending breaks */ lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ } BlockCnt; @@ -677,15 +677,10 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { ** generates an error for an undefined 'goto'. */ static l_noret undefgoto (LexState *ls, Labeldesc *gt) { - const char *msg; - if (eqstr(gt->name, luaS_newliteral(ls->L, "break"))) { - msg = "break outside loop at line %d"; - msg = luaO_pushfstring(ls->L, msg, gt->line); - } - else { - msg = "no visible label '%s' for at line %d"; - msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); - } + const char *msg = "no visible label '%s' for at line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); + /* breaks are checked when created, cannot be undefined */ + lua_assert(!eqstr(gt->name, luaS_newliteral(ls->L, "break"))); luaK_semerror(ls, msg); } @@ -699,7 +694,7 @@ static void leaveblock (FuncState *fs) { fs->freereg = stklevel; /* free registers */ removevars(fs, bl->nactvar); /* remove block locals */ lua_assert(bl->nactvar == fs->nactvar); /* back to level on entry */ - if (bl->isloop) /* has to fix pending breaks? */ + if (bl->isloop == 2) /* has to fix pending breaks? */ createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0); solvegotos(fs, bl); if (bl->previous == NULL) { /* was it the last block? */ @@ -1465,6 +1460,14 @@ static void gotostat (LexState *ls, int line) { ** Break statement. Semantically equivalent to "goto break". */ static void breakstat (LexState *ls, int line) { + BlockCnt *bl; /* to look for an enclosing loop */ + for (bl = ls->fs->bl; bl != NULL; bl = bl->previous) { + if (bl->isloop) /* found one? */ + goto ok; + } + luaX_syntaxerror(ls, "break outside loop"); + ok: + bl->isloop = 2; /* signal that block has pending breaks */ luaX_next(ls); /* skip break */ newgotoentry(ls, luaS_newliteral(ls->L, "break"), line); } From 3cdd49c94a8feed94853ba3a6adaa556fb34fd8d Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 14 Jan 2025 16:24:46 -0300 Subject: [PATCH 016/165] Fixed conversion warnings from clang Plus some other details. (Option '-Wuninitialized' was removed from the makefile because it is already enabled by -Wall.) --- lcode.c | 2 +- llex.c | 7 ++++++- lmem.h | 4 ++-- lobject.c | 1 + lparser.c | 2 +- ltable.c | 4 ++-- makefile | 1 - manual/manual.of | 7 +++---- testes/libs/makefile | 2 +- 9 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lcode.c b/lcode.c index 641f0d09cc..8c04d8ab16 100644 --- a/lcode.c +++ b/lcode.c @@ -1439,7 +1439,7 @@ static void finishbinexpval (FuncState *fs, expdesc *e1, expdesc *e2, e1->u.info = pc; e1->k = VRELOC; /* all those operations are relocatable */ luaK_fixline(fs, line); - luaK_codeABCk(fs, mmop, v1, v2, event, flip); /* to call metamethod */ + luaK_codeABCk(fs, mmop, v1, v2, cast_int(event), flip); /* metamethod */ luaK_fixline(fs, line); } diff --git a/llex.c b/llex.c index 3518f0dab6..1c4227ca4c 100644 --- a/llex.c +++ b/llex.c @@ -349,9 +349,14 @@ static int readhexaesc (LexState *ls) { } +/* +** When reading a UTF-8 escape sequence, save everything to the buffer +** for error reporting in case of errors; 'i' counts the number of +** saved characters, so that they can be removed if case of success. +*/ static unsigned long readutf8esc (LexState *ls) { unsigned long r; - int i = 4; /* chars to be removed: '\', 'u', '{', and first digit */ + int i = 4; /* number of chars to be removed: start with #"\u{X" */ save_and_next(ls); /* skip 'u' */ esccheck(ls, ls->current == '{', "missing '{'"); r = cast_ulong(gethexa(ls)); /* must have at least one digit */ diff --git a/lmem.h b/lmem.h index 204ce3bcae..083585920d 100644 --- a/lmem.h +++ b/lmem.h @@ -39,11 +39,11 @@ ** Computes the minimum between 'n' and 'MAX_SIZET/sizeof(t)', so that ** the result is not larger than 'n' and cannot overflow a 'size_t' ** when multiplied by the size of type 't'. (Assumes that 'n' is an -** 'int' or 'unsigned int' and that 'int' is not larger than 'size_t'.) +** 'int' and that 'int' is not larger than 'size_t'.) */ #define luaM_limitN(n,t) \ ((cast_sizet(n) <= MAX_SIZET/sizeof(t)) ? (n) : \ - cast_uint((MAX_SIZET/sizeof(t)))) + cast_int((MAX_SIZET/sizeof(t)))) /* diff --git a/lobject.c b/lobject.c index 97dacaf514..c0fd182f13 100644 --- a/lobject.c +++ b/lobject.c @@ -194,6 +194,7 @@ void luaO_arith (lua_State *L, int op, const TValue *p1, const TValue *p2, lu_byte luaO_hexavalue (int c) { + lua_assert(lisxdigit(c)); if (lisdigit(c)) return cast_byte(c - '0'); else return cast_byte((ltolower(c) - 'a') + 10); } diff --git a/lparser.c b/lparser.c index 83e341ed94..380e45f58c 100644 --- a/lparser.c +++ b/lparser.c @@ -405,7 +405,7 @@ static int searchvar (FuncState *fs, TString *n, expdesc *var) { init_exp(var, VCONST, fs->firstlocal + i); else /* real variable */ init_var(fs, var, i); - return var->k; + return cast_int(var->k); } } return -1; /* not found */ diff --git a/ltable.c b/ltable.c index b6b1fa1a96..122b7f1756 100644 --- a/ltable.c +++ b/ltable.c @@ -96,7 +96,7 @@ typedef union { ** between 2^MAXHBITS and the maximum size such that, measured in bytes, ** it fits in a 'size_t'. */ -#define MAXHSIZE luaM_limitN(1u << MAXHBITS, Node) +#define MAXHSIZE luaM_limitN(1 << MAXHBITS, Node) /* @@ -598,7 +598,7 @@ static void setnodevector (lua_State *L, Table *t, unsigned size) { else { int i; int lsize = luaO_ceillog2(size); - if (lsize > MAXHBITS || (1u << lsize) > MAXHSIZE) + if (lsize > MAXHBITS || (1 << lsize) > MAXHSIZE) luaG_runerror(L, "table overflow"); size = twoto(lsize); if (lsize < LIMFORLAST) /* no 'lastfree' field? */ diff --git a/makefile b/makefile index 64dee501ec..8506e93c20 100644 --- a/makefile +++ b/makefile @@ -15,7 +15,6 @@ CWARNSCPP= \ -Wdouble-promotion \ -Wmissing-declarations \ -Wconversion \ - -Wuninitialized \ -Wstrict-overflow=2 \ # the next warnings might be useful sometimes, # but usually they generate too much noise diff --git a/manual/manual.of b/manual/manual.of index bb95148ad6..77e37de383 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -3848,7 +3848,6 @@ or zero if the value at @id{idx} is not a number. Calls a function (or a callable object) in protected mode. - Both @id{nargs} and @id{nresults} have the same meaning as in @Lid{lua_call}. If there are no errors during the call, @@ -3998,9 +3997,9 @@ Lua will call @id{falloc} before raising the error. Pushes onto the stack a formatted string and returns a pointer to this string @see{constchar}. The result is a copy of @id{fmt} with -each @emph{conversion specifier} replaced by its respective -extra argument. -A conversion specifier can be +each @emph{conversion specifier} replaced by a string representation +of its respective extra argument. +A conversion specifier (and its corresponding extra argument) can be @Char{%%} (inserts the character @Char{%}), @Char{%s} (inserts a zero-terminated string, with no size restrictions), @Char{%f} (inserts a @Lid{lua_Number}), diff --git a/testes/libs/makefile b/testes/libs/makefile index 9c0c4e3f7f..4e7f965e99 100644 --- a/testes/libs/makefile +++ b/testes/libs/makefile @@ -5,7 +5,7 @@ LUA_DIR = ../../ CC = gcc # compilation should generate Dynamic-Link Libraries -CFLAGS = -Wall -std=gnu99 -O2 -I$(LUA_DIR) -fPIC -shared +CFLAGS = -Wall -std=c99 -O2 -I$(LUA_DIR) -fPIC -shared # libraries used by the tests all: lib1.so lib11.so lib2.so lib21.so lib2-v2.so From 2d8d5c74b5ef3d333314feede0165df7c3d13811 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 16 Jan 2025 11:51:16 -0300 Subject: [PATCH 017/165] Details New year (2024->2025), typos in comments --- ldebug.c | 2 +- ldo.c | 4 ++-- lgc.c | 4 ++-- llex.h | 2 +- llimits.h | 2 +- lmathlib.c | 2 +- lparser.h | 2 +- ltable.c | 8 ++++---- lua.c | 2 +- lua.h | 4 ++-- lundump.c | 2 +- lvm.c | 4 ++-- lvm.h | 2 +- manual/2html | 2 +- testes/all.lua | 7 +++---- 15 files changed, 24 insertions(+), 25 deletions(-) diff --git a/ldebug.c b/ldebug.c index 09ec197c42..af3b758334 100644 --- a/ldebug.c +++ b/ldebug.c @@ -898,7 +898,7 @@ int luaG_tracecall (lua_State *L) { if (ci->u.l.savedpc == p->code) { /* first instruction (not resuming)? */ if (p->flag & PF_ISVARARG) return 0; /* hooks will start at VARARGPREP instruction */ - else if (!(ci->callstatus & CIST_HOOKYIELD)) /* not yieded? */ + else if (!(ci->callstatus & CIST_HOOKYIELD)) /* not yielded? */ luaD_hookcall(L, ci); /* check 'call' hook */ } return 1; /* keep 'trap' on */ diff --git a/ldo.c b/ldo.c index 009bf47ad2..fb9df5d392 100644 --- a/ldo.c +++ b/ldo.c @@ -236,7 +236,7 @@ static void correctstack (lua_State *L, StkId oldstack) { #else /* -** Alternatively, we can use the old address after the dealocation. +** Alternatively, we can use the old address after the deallocation. ** That is not strict ISO C, but seems to work fine everywhere. */ @@ -485,7 +485,7 @@ static unsigned tryfuncTM (lua_State *L, StkId func, unsigned status) { } -/* Generic case for 'moveresult */ +/* Generic case for 'moveresult' */ l_sinline void genmoveresults (lua_State *L, StkId res, int nres, int wanted) { StkId firstresult = L->top.p - nres; /* index of first result */ diff --git a/lgc.c b/lgc.c index 3cdfd0064c..1e9f75698c 100644 --- a/lgc.c +++ b/lgc.c @@ -242,7 +242,7 @@ static int iscleared (global_State *g, const GCObject *o) { ** incremental sweep phase, it clears the black object to white (sweep ** it) to avoid other barrier calls for this same object. (That cannot ** be done is generational mode, as its sweep does not distinguish -** whites from deads.) +** white from dead.) */ void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) { global_State *g = G(L); @@ -1089,7 +1089,7 @@ void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) { ** GCmarked: number of bytes that became old since last major collection. ** GCmajorminor: number of bytes marked in last major collection. ** * KGC_GENMAJOR -** GCmarked: number of bytes that became old sinse last major collection. +** GCmarked: number of bytes that became old since last major collection. ** GCmajorminor: number of bytes marked in last major collection. */ diff --git a/llex.h b/llex.h index 389d2f8635..c3500ef6a8 100644 --- a/llex.h +++ b/llex.h @@ -59,7 +59,7 @@ typedef struct Token { } Token; -/* state of the lexer plus state of the parser when shared by all +/* state of the scanner plus state of the parser when shared by all functions */ typedef struct LexState { int current; /* current character (charint) */ diff --git a/llimits.h b/llimits.h index 6cf35e0cf7..d98171ae6b 100644 --- a/llimits.h +++ b/llimits.h @@ -159,7 +159,7 @@ typedef LUAI_UACINT l_uacInt; #define cast_st2S(sz) ((lua_Integer)(sz)) /* Cast a ptrdiff_t to size_t, when it is known that the minuend -** comes from the subtraend (the base) +** comes from the subtrahend (the base) */ #define ct_diff2sz(df) ((size_t)(df)) diff --git a/lmathlib.c b/lmathlib.c index f8b24d1d01..c7418e69ec 100644 --- a/lmathlib.c +++ b/lmathlib.c @@ -106,7 +106,7 @@ static int math_floor (lua_State *L) { static int math_ceil (lua_State *L) { if (lua_isinteger(L, 1)) - lua_settop(L, 1); /* integer is its own ceil */ + lua_settop(L, 1); /* integer is its own ceiling */ else { lua_Number d = l_mathop(ceil)(luaL_checknumber(L, 1)); pushnumint(L, d); diff --git a/lparser.h b/lparser.h index a8004fa0ce..a3063569c7 100644 --- a/lparser.h +++ b/lparser.h @@ -32,7 +32,7 @@ typedef enum { VKFLT, /* floating constant; nval = numerical float value */ VKINT, /* integer constant; ival = numerical integer value */ VKSTR, /* string constant; strval = TString address; - (string is fixed by the lexer) */ + (string is fixed by the scanner) */ VNONRELOC, /* expression has its value in a fixed register; info = result register */ VLOCAL, /* local variable; var.ridx = register index; diff --git a/ltable.c b/ltable.c index 122b7f1756..8df9a4fbfe 100644 --- a/ltable.c +++ b/ltable.c @@ -123,7 +123,7 @@ typedef union { /* ** Common hash part for tables with empty hash parts. That allows all -** tables to have a hash part, avoding an extra check ("is there a hash +** tables to have a hash part, avoiding an extra check ("is there a hash ** part?") when indexing. Its sole node has an empty value and a key ** (DEADKEY, NULL) that is different from any valid TValue. */ @@ -699,7 +699,7 @@ static void clearNewSlice (Table *t, unsigned oldasize, unsigned newasize) { ** into the table, initializes the new part of the array (if any) with ** nils and reinserts the elements of the old hash back into the new ** parts of the table. -** Note that if the new size for the arry part ('newasize') is equal to +** Note that if the new size for the array part ('newasize') is equal to ** the old one ('oldasize'), this function will do nothing with that ** part. */ @@ -774,7 +774,7 @@ static void rehash (lua_State *L, Table *t, const TValue *ek) { nsize = ct.total - ct.na; if (ct.deleted) { /* table has deleted entries? */ /* insertion-deletion-insertion: give hash some extra size to - avoid constant resizings */ + avoid repeated resizings */ nsize += nsize >> 2; } /* resize the table to new computed sizes */ @@ -1300,7 +1300,7 @@ lua_Unsigned luaH_getn (Table *t) { return newhint(t, binsearch(t, limit, asize)); } } - /* last element non empty; set a hint to speed up findind that again */ + /* last element non empty; set a hint to speed up finding that again */ /* (keys in the hash part cannot be hints) */ *lenhint(t) = asize; } diff --git a/lua.c b/lua.c index ea6141bb33..64d391609a 100644 --- a/lua.c +++ b/lua.c @@ -497,7 +497,7 @@ static void lua_freeline (char *line) { static void lua_initreadline (lua_State *L) { void *lib = dlopen(LUA_READLINELIB, RTLD_NOW | RTLD_LOCAL); if (lib == NULL) - lua_warning(L, "library '" LUA_READLINELIB "'not found", 0); + lua_warning(L, "library '" LUA_READLINELIB "' not found", 0); else { const char **name = cast(const char**, dlsym(lib, "rl_readline_name")); if (name != NULL) diff --git a/lua.h b/lua.h index aefa3b8c3d..76068fdcc7 100644 --- a/lua.h +++ b/lua.h @@ -13,7 +13,7 @@ #include -#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2024 Lua.org, PUC-Rio" +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2025 Lua.org, PUC-Rio" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" @@ -528,7 +528,7 @@ struct lua_Debug { /****************************************************************************** -* Copyright (C) 1994-2024 Lua.org, PUC-Rio. +* Copyright (C) 1994-2025 Lua.org, PUC-Rio. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/lundump.c b/lundump.c index 4d6e8bd2fd..fd5a2ca6eb 100644 --- a/lundump.c +++ b/lundump.c @@ -63,7 +63,7 @@ static void loadBlock (LoadState *S, void *b, size_t size) { static void loadAlign (LoadState *S, unsigned align) { unsigned padding = align - cast_uint(S->offset % align); - if (padding < align) { /* apd == align means no padding */ + if (padding < align) { /* (padding == align) means no padding */ lua_Integer paddingContent; loadBlock(S, &paddingContent, padding); lua_assert(S->offset % align == 0); diff --git a/lvm.c b/lvm.c index 074ee718ec..f0e73f9bb6 100644 --- a/lvm.c +++ b/lvm.c @@ -127,8 +127,8 @@ int luaV_flttointeger (lua_Number n, lua_Integer *p, F2Imod mode) { lua_Number f = l_floor(n); if (n != f) { /* not an integral value? */ if (mode == F2Ieq) return 0; /* fails if mode demands integral value */ - else if (mode == F2Iceil) /* needs ceil? */ - f += 1; /* convert floor to ceil (remember: n != f) */ + else if (mode == F2Iceil) /* needs ceiling? */ + f += 1; /* convert floor to ceiling (remember: n != f) */ } return lua_numbertointeger(f, p); } diff --git a/lvm.h b/lvm.h index c88985599a..be7b9cb0ea 100644 --- a/lvm.h +++ b/lvm.h @@ -43,7 +43,7 @@ typedef enum { F2Ieq, /* no rounding; accepts only integral values */ F2Ifloor, /* takes the floor of the number */ - F2Iceil /* takes the ceil of the number */ + F2Iceil /* takes the ceiling of the number */ } F2Imod; diff --git a/manual/2html b/manual/2html index 59bb4578a4..ac5ea04351 100755 --- a/manual/2html +++ b/manual/2html @@ -30,7 +30,7 @@ by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes

Copyright -© 2024 Lua.org, PUC-Rio. All rights reserved. +© 2025 Lua.org, PUC-Rio. All rights reserved.


diff --git a/testes/all.lua b/testes/all.lua index 3c1ff5c71a..4ffa9efee1 100644 --- a/testes/all.lua +++ b/testes/all.lua @@ -28,10 +28,9 @@ _nomsg = rawget(_G, "_nomsg") or false local usertests = rawget(_G, "_U") if usertests then - -- tests for sissies ;) Avoid problems - _soft = true - _port = true - _nomsg = true + _soft = true -- avoid tests that take too long + _port = true -- avoid non-portable tests + _nomsg = true -- avoid messages about tests not performed end -- tests should require debug when needed From 664bda02ba4bd167728a2acbe658cc4a9dd9b0b5 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 16 Jan 2025 16:07:39 -0300 Subject: [PATCH 018/165] fixing 'lua_status' in panic. 'luaD_throw' may call 'luaE_resetthread', which returns an error code but clears 'L->status'; so, 'luaD_throw' should set that status again. --- ldo.c | 1 + ltests.c | 10 ++++++++-- testes/api.lua | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ldo.c b/ldo.c index fb9df5d392..994ad6f0fb 100644 --- a/ldo.c +++ b/ldo.c @@ -133,6 +133,7 @@ l_noret luaD_throw (lua_State *L, int errcode) { else { /* thread has no error handler */ global_State *g = G(L); errcode = luaE_resetthread(L, errcode); /* close all upvalues */ + L->status = cast_byte(errcode); if (g->mainthread->errorJmp) { /* main thread has a handler? */ setobjs2s(L, g->mainthread->top.p++, L->top.p - 1); /* copy error obj. */ luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ diff --git a/ltests.c b/ltests.c index 6c77703c4f..83d08ac880 100644 --- a/ltests.c +++ b/ltests.c @@ -1367,7 +1367,7 @@ static int checkpanic (lua_State *L) { b.L = L; L1 = lua_newstate(f, ud, 0); /* create new state */ if (L1 == NULL) { /* error? */ - lua_pushnil(L); + lua_pushstring(L, MEMERRMSG); return 1; } lua_atpanic(L1, panicback); /* set its panic function */ @@ -1507,7 +1507,7 @@ static int getindex_aux (lua_State *L, lua_State *L1, const char **pc) { static const char *const statcodes[] = {"OK", "YIELD", "ERRRUN", - "ERRSYNTAX", MEMERRMSG, "ERRGCMM", "ERRERR"}; + "ERRSYNTAX", MEMERRMSG, "ERRERR"}; /* ** Avoid these stat codes from being collected, to avoid possible @@ -1806,6 +1806,12 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) { int level = getnum; luaL_traceback(L1, L1, msg, level); } + else if EQ("threadstatus") { + lua_pushstring(L1, statcodes[lua_status(L1)]); + } + else if EQ("alloccount") { + l_memcontrol.countlimit = cast_uint(getnum); + } else if EQ("return") { int n = getnum; if (L1 != L) { diff --git a/testes/api.lua b/testes/api.lua index b7e34f7fc5..21f703fd17 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -416,6 +416,10 @@ do -- trivial error 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 assert(T.checkpanic("pushstring hi; error;", [[checkstack 5 XX @@ -433,6 +437,21 @@ do assert(T.checkpanic("newuserdata 20000") == MEMERRMSG) T.totalmem(0) -- restore high limit + -- memory error + thread status + local x = T.checkpanic( + [[ alloccount 0 # force a memory error in next line + newtable + ]], + [[ + alloccount -1 # allow free allocations again + pushstring XX + threadstatus + concat 2 # to make sure message came from here + return 1 + ]]) + T.alloccount() + assert(x == "XX" .. "not enough memory") + -- stack error if not _soft then local msg = T.checkpanic[[ From 724012d3d07f43f03451bb05d2bd9f55dff1d116 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 16 Jan 2025 16:11:49 -0300 Subject: [PATCH 019/165] Small change in macro 'isvalid' The "faster way" to check whether a value is not 'nilvalue' is not faster. (Both forms entail one memory access.) --- lapi.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lapi.c b/lapi.c index 4411cb2962..cf73324b6b 100644 --- a/lapi.c +++ b/lapi.c @@ -40,10 +40,8 @@ const char lua_ident[] = /* ** Test for a valid index (one that is not the 'nilvalue'). -** '!ttisnil(o)' implies 'o != &G(L)->nilvalue', so it is not needed. -** However, it covers the most common cases in a faster way. */ -#define isvalid(L, o) (!ttisnil(o) || o != &G(L)->nilvalue) +#define isvalid(L, o) ((o) != &G(L)->nilvalue) /* test for pseudo index */ From 7d7ae8781e64e2b3b212d5c7b7c1b98b694df5ef Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 21 Jan 2025 13:33:59 -0300 Subject: [PATCH 020/165] Parameters for 'lua_createtable' back to int Tables don't accept sizes larger than int. --- lapi.c | 4 ++-- ltablib.c | 8 ++++---- ltests.c | 6 +++--- lua.c | 2 +- lua.h | 2 +- manual/manual.of | 6 +++--- testes/sort.lua | 6 ++++-- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lapi.c b/lapi.c index cf73324b6b..47d328ca93 100644 --- a/lapi.c +++ b/lapi.c @@ -782,14 +782,14 @@ LUA_API int lua_rawgetp (lua_State *L, int idx, const void *p) { } -LUA_API void lua_createtable (lua_State *L, unsigned narray, unsigned nrec) { +LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { Table *t; lua_lock(L); t = luaH_new(L); sethvalue2s(L, L->top.p, t); api_incr_top(L); if (narray > 0 || nrec > 0) - luaH_resize(L, t, narray, nrec); + luaH_resize(L, t, cast_uint(narray), cast_uint(nrec)); luaC_checkGC(L); lua_unlock(L); } diff --git a/ltablib.c b/ltablib.c index baa7111e2c..46ecb5e024 100644 --- a/ltablib.c +++ b/ltablib.c @@ -62,9 +62,9 @@ static void checktab (lua_State *L, int arg, int what) { static int tcreate (lua_State *L) { lua_Unsigned sizeseq = (lua_Unsigned)luaL_checkinteger(L, 1); lua_Unsigned sizerest = (lua_Unsigned)luaL_optinteger(L, 2, 0); - luaL_argcheck(L, sizeseq <= UINT_MAX, 1, "out of range"); - luaL_argcheck(L, sizerest <= UINT_MAX, 2, "out of range"); - lua_createtable(L, (unsigned)sizeseq, (unsigned)sizerest); + luaL_argcheck(L, sizeseq <= cast_uint(INT_MAX), 1, "out of range"); + luaL_argcheck(L, sizerest <= cast_uint(INT_MAX), 2, "out of range"); + lua_createtable(L, cast_int(sizeseq), cast_int(sizerest)); return 1; } @@ -192,7 +192,7 @@ static int tconcat (lua_State *L) { static int tpack (lua_State *L) { int i; int n = lua_gettop(L); /* number of elements to pack */ - lua_createtable(L, cast_uint(n), 1); /* create result table */ + lua_createtable(L, n, 1); /* create result table */ lua_insert(L, 1); /* put it at index 1 */ for (i = n; i >= 1; i--) /* assign elements */ lua_seti(L, 1, i); diff --git a/ltests.c b/ltests.c index 83d08ac880..eaf3b251f4 100644 --- a/ltests.c +++ b/ltests.c @@ -809,7 +809,7 @@ static int listk (lua_State *L) { luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1, "Lua function expected"); p = getproto(obj_at(L, 1)); - lua_createtable(L, cast_uint(p->sizek), 0); + lua_createtable(L, p->sizek, 0); for (i=0; isizek; i++) { pushobject(L, p->k+i); lua_rawseti(L, -2, i+1); @@ -825,7 +825,7 @@ static int listabslineinfo (lua_State *L) { 1, "Lua function expected"); p = getproto(obj_at(L, 1)); luaL_argcheck(L, p->abslineinfo != NULL, 1, "function has no debug info"); - lua_createtable(L, 2u * cast_uint(p->sizeabslineinfo), 0); + lua_createtable(L, 2 * p->sizeabslineinfo, 0); for (i=0; i < p->sizeabslineinfo; i++) { lua_pushinteger(L, p->abslineinfo[i].pc); lua_rawseti(L, -2, 2 * i + 1); @@ -867,7 +867,7 @@ void lua_printstack (lua_State *L) { static int get_limits (lua_State *L) { - lua_createtable(L, 0, 6); + lua_createtable(L, 0, 5); setnameval(L, "IS32INT", LUAI_IS32INT); setnameval(L, "MAXARG_Ax", MAXARG_Ax); setnameval(L, "MAXARG_Bx", MAXARG_Bx); diff --git a/lua.c b/lua.c index 64d391609a..b611cbcace 100644 --- a/lua.c +++ b/lua.c @@ -185,7 +185,7 @@ static void print_version (void) { static void createargtable (lua_State *L, char **argv, int argc, int script) { int i, narg; narg = argc - (script + 1); /* number of positive indices */ - lua_createtable(L, cast_uint(narg), cast_uint(script + 1)); + lua_createtable(L, narg, script + 1); for (i = 0; i < argc; i++) { lua_pushstring(L, argv[i]); lua_rawseti(L, -2, i - script); diff --git a/lua.h b/lua.h index 76068fdcc7..e82fc255c8 100644 --- a/lua.h +++ b/lua.h @@ -267,7 +267,7 @@ LUA_API int (lua_rawget) (lua_State *L, int idx); LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); -LUA_API void (lua_createtable) (lua_State *L, unsigned narr, unsigned nrec); +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); LUA_API void *(lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue); LUA_API int (lua_getmetatable) (lua_State *L, int objindex); LUA_API int (lua_getiuservalue) (lua_State *L, int idx, int n); diff --git a/manual/manual.of b/manual/manual.of index 77e37de383..150315d41d 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -3238,7 +3238,7 @@ Values at other positions are not affected. } -@APIEntry{void lua_createtable (lua_State *L, unsigned nseq, unsigned nrec);| +@APIEntry{void lua_createtable (lua_State *L, int nseq, int nrec);| @apii{0,1,m} Creates a new empty table and pushes it onto the stack. @@ -3249,7 +3249,7 @@ the table will have. Lua may use these hints to preallocate memory for the new table. This preallocation may help performance when you know in advance how many elements the table will have. -Otherwise you can use the function @Lid{lua_newtable}. +Otherwise you should use the function @Lid{lua_newtable}. } @@ -3351,7 +3351,7 @@ Returns the previous mode (@id{LUA_GCGEN} or @id{LUA_GCINC}). @item{@defid{LUA_GCPARAM} (int param, int val)| Changes and/or returns the value of a parameter of the collector. -If @id{val} is negative, the call only returns the current value. +If @id{val} is -1, the call only returns the current value. The argument @id{param} must have one of the following values: @description{ @item{@defid{LUA_GCPMINORMUL}| The minor multiplier. } diff --git a/testes/sort.lua b/testes/sort.lua index 442b3129e4..965e153482 100644 --- a/testes/sort.lua +++ b/testes/sort.lua @@ -35,8 +35,10 @@ do print "testing 'table.create'" assert(memdiff > 1024 * 12) assert(not T or select(2, T.querytab(t)) == 1024) - checkerror("table overflow", table.create, (1<<31) + 1) - checkerror("table overflow", table.create, 0, (1<<31) + 1) + local maxint1 = 1 << (string.packsize("i") * 8 - 1) + checkerror("out of range", table.create, maxint1) + checkerror("out of range", table.create, 0, maxint1) + checkerror("table overflow", table.create, 0, maxint1 - 1) end From c4e7cdb541d89142056927ebdfd8f97017d38f45 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 27 Jan 2025 16:09:55 -0300 Subject: [PATCH 021/165] Renaming two new functions 'lua_numbertostrbuff' -> 'lua_numbertocstring' 'lua_pushextlstring' -> 'lua_pushexternalstring' --- lapi.c | 4 ++-- lauxlib.c | 6 +++--- liolib.c | 2 +- loadlib.c | 2 +- ltests.c | 4 ++-- lua.h | 4 ++-- manual/manual.of | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lapi.c b/lapi.c index 47d328ca93..c0fd1a1b19 100644 --- a/lapi.c +++ b/lapi.c @@ -366,7 +366,7 @@ LUA_API int lua_compare (lua_State *L, int index1, int index2, int op) { } -LUA_API unsigned (lua_numbertostrbuff) (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); @@ -546,7 +546,7 @@ LUA_API const char *lua_pushlstring (lua_State *L, const char *s, size_t len) { } -LUA_API const char *lua_pushextlstring (lua_State *L, +LUA_API const char *lua_pushexternalstring (lua_State *L, const char *s, size_t len, lua_Alloc falloc, void *ud) { TString *ts; lua_lock(L); diff --git a/lauxlib.c b/lauxlib.c index d37d2f8c3f..5bca18166d 100644 --- a/lauxlib.c +++ b/lauxlib.c @@ -622,9 +622,9 @@ LUALIB_API void luaL_pushresult (luaL_Buffer *B) { resizebox(L, -1, len + 1); /* adjust box size to content size */ s = (char*)box->box; /* final buffer address */ s[len] = '\0'; /* add ending zero */ - /* clear box, as 'lua_pushextlstring' will take control over buffer */ + /* clear box, as Lua will take control of the buffer */ box->bsize = 0; box->box = NULL; - lua_pushextlstring(L, s, len, allocf, ud); + lua_pushexternalstring(L, s, len, allocf, ud); lua_closeslot(L, -2); /* close the box */ lua_gc(L, LUA_GCSTEP, len); } @@ -929,7 +929,7 @@ LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { switch (lua_type(L, idx)) { case LUA_TNUMBER: { char buff[LUA_N2SBUFFSZ]; - lua_numbertostrbuff(L, idx, buff); + lua_numbertocstring(L, idx, buff); lua_pushstring(L, buff); break; } diff --git a/liolib.c b/liolib.c index 98ff3d0dfb..a0988db06a 100644 --- a/liolib.c +++ b/liolib.c @@ -667,7 +667,7 @@ static int g_write (lua_State *L, FILE *f, int arg) { for (; nargs--; arg++) { char buff[LUA_N2SBUFFSZ]; const char *s; - size_t len = lua_numbertostrbuff(L, arg, buff); /* try as a number */ + size_t len = lua_numbertocstring(L, arg, buff); /* try as a number */ if (len > 0) { /* did conversion work (value was a number)? */ s = buff; len--; diff --git a/loadlib.c b/loadlib.c index e5ed135286..5f0c170296 100644 --- a/loadlib.c +++ b/loadlib.c @@ -280,7 +280,7 @@ static void setpath (lua_State *L, const char *fieldname, if (path == NULL) /* no versioned environment variable? */ path = getenv(envname); /* try unversioned name */ if (path == NULL || noenv(L)) /* no environment variable? */ - lua_pushextlstring(L, dft, strlen(dft), NULL, NULL); /* use default */ + lua_pushexternalstring(L, dft, strlen(dft), NULL, NULL); /* use default */ else if ((dftmark = strstr(path, LUA_PATH_SEP LUA_PATH_SEP)) == NULL) lua_pushstring(L, path); /* nothing to change */ else { /* path contains a ";;": insert default path in its place */ diff --git a/ltests.c b/ltests.c index eaf3b251f4..e3037be32b 100644 --- a/ltests.c +++ b/ltests.c @@ -1389,7 +1389,7 @@ static int checkpanic (lua_State *L) { static int externKstr (lua_State *L) { size_t len; const char *s = luaL_checklstring(L, 1, &len); - lua_pushextlstring(L, s, len, NULL, NULL); + lua_pushexternalstring(L, s, len, NULL, NULL); return 1; } @@ -1413,7 +1413,7 @@ static int externstr (lua_State *L) { /* copy string content to buffer, including ending 0 */ memcpy(buff, s, (len + 1) * sizeof(char)); /* create external string */ - lua_pushextlstring(L, buff, len, allocf, ud); + lua_pushexternalstring(L, buff, len, allocf, ud); return 1; } diff --git a/lua.h b/lua.h index e82fc255c8..95e0db321a 100644 --- a/lua.h +++ b/lua.h @@ -244,7 +244,7 @@ LUA_API void (lua_pushnil) (lua_State *L); LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); -LUA_API const char *(lua_pushextlstring) (lua_State *L, +LUA_API const char *(lua_pushexternalstring) (lua_State *L, const char *s, size_t len, lua_Alloc falloc, void *ud); LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, @@ -372,7 +372,7 @@ LUA_API void (lua_concat) (lua_State *L, int n); LUA_API void (lua_len) (lua_State *L, int idx); #define LUA_N2SBUFFSZ 64 -LUA_API unsigned (lua_numbertostrbuff) (lua_State *L, int idx, char *buff); +LUA_API unsigned (lua_numbertocstring) (lua_State *L, int idx, char *buff); LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); diff --git a/manual/manual.of b/manual/manual.of index 150315d41d..274799e3f1 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -3829,7 +3829,7 @@ This macro may evaluate its arguments more than once. } -@APIEntry{unsigned (lua_numbertostrbuff) (lua_State *L, int idx, +@APIEntry{unsigned (lua_numbertocstring) (lua_State *L, int idx, char *buff);| @apii{0,0,-} @@ -3955,7 +3955,7 @@ This function is equivalent to @Lid{lua_pushcclosure} with no upvalues. } -@APIEntry{const char *(lua_pushextlstring) (lua_State *L, +@APIEntry{const char *(lua_pushexternalstring) (lua_State *L, const char *s, size_t len, lua_Alloc falloc, void *ud);| @apii{0,1,m} From 39a14ea7d7b14172595c61619e8f35c2614b2606 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 28 Jan 2025 11:45:45 -0300 Subject: [PATCH 022/165] CallInfo bit CIST_CLSRET broken in two Since commit f407b3c4a, it was being used for two distinct (and incompatible) meanings: A: Function has TBC variables (now bit CIST_TBC) B: Interpreter is closing TBC variables (original bit CIST_CLSRET) B implies A, but A does not imply B. --- lapi.c | 6 +++--- ldo.c | 12 +++++++----- lstate.h | 4 +++- testes/coroutine.lua | 37 +++++++++++++++++++++++++++++++------ 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/lapi.c b/lapi.c index c0fd1a1b19..7b30617f7e 100644 --- a/lapi.c +++ b/lapi.c @@ -195,7 +195,7 @@ LUA_API void lua_settop (lua_State *L, int idx) { } newtop = L->top.p + diff; if (diff < 0 && L->tbclist.p >= newtop) { - lua_assert(ci->callstatus & CIST_CLSRET); + lua_assert(ci->callstatus & CIST_TBC); newtop = luaF_close(L, newtop, CLOSEKTOP, 0); } L->top.p = newtop; /* correct top only after closing any upvalue */ @@ -207,7 +207,7 @@ LUA_API void lua_closeslot (lua_State *L, int idx) { StkId level; lua_lock(L); level = index2stack(L, idx); - api_check(L, (L->ci->callstatus & CIST_CLSRET) && L->tbclist.p == level, + api_check(L, (L->ci->callstatus & CIST_TBC) && (L->tbclist.p == level), "no variable to close at given level"); level = luaF_close(L, level, CLOSEKTOP, 0); setnilvalue(s2v(level)); @@ -1280,7 +1280,7 @@ LUA_API void lua_toclose (lua_State *L, int idx) { o = index2stack(L, idx); api_check(L, L->tbclist.p < o, "given index below or equal a marked one"); luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */ - L->ci->callstatus |= CIST_CLSRET; /* mark that function has TBC slots */ + L->ci->callstatus |= CIST_TBC; /* mark that function has TBC slots */ lua_unlock(L); } diff --git a/ldo.c b/ldo.c index 994ad6f0fb..31c00a2169 100644 --- a/ldo.c +++ b/ldo.c @@ -505,7 +505,7 @@ l_sinline void genmoveresults (lua_State *L, StkId res, int nres, ** Given 'nres' results at 'firstResult', move 'fwanted-1' of them ** to 'res'. Handle most typical cases (zero results for commands, ** one result for expressions, multiple results for tail calls/single -** parameters) separated. The flag CIST_CLSRET in 'fwanted', if set, +** parameters) separated. The flag CIST_TBC in 'fwanted', if set, ** forces the swicth to go to the default case. */ l_sinline void moveresults (lua_State *L, StkId res, int nres, @@ -526,8 +526,9 @@ l_sinline void moveresults (lua_State *L, StkId res, int nres, break; default: { /* two/more results and/or to-be-closed variables */ int wanted = get_nresults(fwanted); - if (fwanted & CIST_CLSRET) { /* to-be-closed variables? */ + if (fwanted & CIST_TBC) { /* to-be-closed variables? */ L->ci->u2.nres = nres; + L->ci->callstatus |= CIST_CLSRET; /* in case of yields */ res = luaF_close(L, res, CLOSEKTOP, 1); L->ci->callstatus &= ~CIST_CLSRET; if (L->hookmask) { /* if needed, call hook after '__close's */ @@ -552,8 +553,8 @@ l_sinline void moveresults (lua_State *L, StkId res, int nres, ** that. */ void luaD_poscall (lua_State *L, CallInfo *ci, int nres) { - l_uint32 fwanted = ci->callstatus & (CIST_CLSRET | CIST_NRESULTS); - if (l_unlikely(L->hookmask) && !(fwanted & CIST_CLSRET)) + l_uint32 fwanted = ci->callstatus & (CIST_TBC | CIST_NRESULTS); + if (l_unlikely(L->hookmask) && !(fwanted & CIST_TBC)) rethook(L, ci, nres); /* move results to proper place */ moveresults(L, ci->func.p, nres, fwanted); @@ -785,7 +786,8 @@ static int finishpcallk (lua_State *L, CallInfo *ci) { */ static void finishCcall (lua_State *L, CallInfo *ci) { int n; /* actual number of results from C function */ - if (ci->callstatus & CIST_CLSRET) { /* was returning? */ + if (ci->callstatus & CIST_CLSRET) { /* was closing TBC variable? */ + lua_assert(ci->callstatus & CIST_TBC); n = ci->u2.nres; /* just redo 'luaD_poscall' */ /* don't need to reset CIST_CLSRET, as it will be set again anyway */ } diff --git a/lstate.h b/lstate.h index e95c72880e..635f41d2ec 100644 --- a/lstate.h +++ b/lstate.h @@ -235,8 +235,10 @@ struct CallInfo { #define CIST_FRESH cast(l_uint32, CIST_C << 1) /* function is closing tbc variables */ #define CIST_CLSRET (CIST_FRESH << 1) +/* function has tbc variables to close */ +#define CIST_TBC (CIST_CLSRET << 1) /* original value of 'allowhook' */ -#define CIST_OAH (CIST_CLSRET << 1) +#define CIST_OAH (CIST_TBC << 1) /* call is running a debug hook */ #define CIST_HOOKED (CIST_OAH << 1) /* doing a yieldable protected call */ diff --git a/testes/coroutine.lua b/testes/coroutine.lua index c1252ab89f..78b9bdca19 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -515,7 +515,7 @@ else print "testing yields inside hooks" local turn - + local function fact (t, x) assert(turn == t) if x == 0 then return 1 @@ -642,7 +642,7 @@ else print "testing coroutine API" - + -- reusing a thread assert(T.testC([[ newthread # create thread @@ -920,7 +920,7 @@ do -- a few more tests for comparison operators until res ~= 10 return res end - + local function test () local a1 = setmetatable({x=1}, mt1) local a2 = setmetatable({x=2}, mt2) @@ -932,7 +932,7 @@ do -- a few more tests for comparison operators assert(2 >= a2) return true end - + run(test) end @@ -1037,6 +1037,31 @@ f = T.makeCfunc([[ return * ]], 23, "huu") + +do -- testing bug introduced in commit f407b3c4a + local X = false -- flag "to be closed" + local coro = coroutine.wrap(T.testC) + -- runs it until 'pcallk' (that yields) + -- 4th argument (at index 4): object to be closed + local res1, res2 = coro( + [[ + toclose 3 # this could break the next 'pcallk' + pushvalue 2 # push function 'yield' to call it + pushint 22; pushint 33 # arguments to yield + # calls 'yield' (2 args; 2 results; continuation function at index 4) + pcallk 2 2 4 + invalid command (should not arrive here) + ]], -- 1st argument (at index 1): code; + coroutine.yield, -- (at index 2): function to be called + func2close(function () X = true end), -- (index 3): TBC slot + "pushint 43; return 3" -- (index 4): code for continuation function + ) + + assert(res1 == 22 and res2 == 33 and not X) + local res1, res2, res3 = coro(34, "hi") -- runs continuation function + assert(res1 == 34 and res2 == "hi" and res3 == 43 and X) +end + x = coroutine.wrap(f) assert(x() == 102) eqtab({x()}, {23, "huu"}) @@ -1094,11 +1119,11 @@ co = coroutine.wrap(function (...) return cannot be here! ]], [[ # 1st continuation - yieldk 0 3 + yieldk 0 3 cannot be here! ]], [[ # 2nd continuation - yieldk 0 4 + yieldk 0 4 cannot be here! ]], [[ # 3th continuation From f7439112a5469078ac4f444106242cf1c1d3fe8a Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 29 Jan 2025 14:47:06 -0300 Subject: [PATCH 023/165] Details (in test library) - Added support for negative stack indices in the "C interpreter" - Improved format when printing values --- ltests.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ltests.c b/ltests.c index e3037be32b..6b5dc27643 100644 --- a/ltests.c +++ b/ltests.c @@ -343,13 +343,19 @@ void lua_printvalue (TValue *v) { printf("%s", (!l_isfalse(v) ? "true" : "false")); break; } + case LUA_TLIGHTUSERDATA: { + printf("light udata: %p", pvalue(v)); + break; + } case LUA_TNIL: { printf("nil"); break; } default: { - void *p = iscollectable(v) ? gcvalue(v) : NULL; - printf("%s: %p", ttypename(ttype(v)), p); + if (ttislcf(v)) + printf("light C function: %p", fvalue(v)); + else /* must be collectable */ + printf("%s: %p", ttypename(ttype(v)), gcvalue(v)); break; } } @@ -1499,9 +1505,14 @@ static int getindex_aux (lua_State *L, lua_State *L1, const char **pc) { skip(pc); switch (*(*pc)++) { case 'R': return LUA_REGISTRYINDEX; - case 'G': return luaL_error(L, "deprecated index 'G'"); case 'U': return lua_upvalueindex(getnum_aux(L, L1, pc)); - default: (*pc)--; return getnum_aux(L, L1, pc); + default: { + int n; + (*pc)--; /* to read again */ + n = getnum_aux(L, L1, pc); + if (n == 0) return 0; + else return lua_absindex(L1, n); + } } } @@ -1550,7 +1561,7 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) { const char *inst = getstring; if EQ("") return 0; else if EQ("absindex") { - lua_pushinteger(L1, lua_absindex(L1, getindex)); + lua_pushinteger(L1, getindex); } else if EQ("append") { int t = getindex; From d1e677c52be3b107a7a29fdc482158f6d9251e79 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 30 Jan 2025 11:41:39 -0300 Subject: [PATCH 024/165] New type 'TStatus' for thread status/error codes --- lapi.c | 10 +++++----- ldo.c | 45 +++++++++++++++++++++++---------------------- ldo.h | 14 ++++++++------ lfunc.c | 5 +++-- lfunc.h | 4 ++-- lgc.c | 2 +- llimits.h | 6 ++++++ lstate.c | 6 +++--- lstate.h | 8 ++++---- lstring.c | 2 +- 10 files changed, 56 insertions(+), 46 deletions(-) diff --git a/lapi.c b/lapi.c index 7b30617f7e..b3062072ab 100644 --- a/lapi.c +++ b/lapi.c @@ -1070,7 +1070,7 @@ static void f_call (lua_State *L, void *ud) { LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, lua_KContext ctx, lua_KFunction k) { struct CallS c; - int status; + TStatus status; ptrdiff_t func; lua_lock(L); api_check(L, k == NULL || !isLua(L->ci), @@ -1107,14 +1107,14 @@ LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, } adjustresults(L, nresults); lua_unlock(L); - return status; + return APIstatus(status); } LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char *chunkname, const char *mode) { ZIO z; - int status; + TStatus status; lua_lock(L); if (!chunkname) chunkname = "?"; luaZ_init(L, &z, reader, data); @@ -1131,7 +1131,7 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, } } lua_unlock(L); - return status; + return APIstatus(status); } @@ -1154,7 +1154,7 @@ LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data, int strip) { LUA_API int lua_status (lua_State *L) { - return L->status; + return APIstatus(L->status); } diff --git a/ldo.c b/ldo.c index 31c00a2169..3f9c8b7dfb 100644 --- a/ldo.c +++ b/ldo.c @@ -97,11 +97,11 @@ struct lua_longjmp { struct lua_longjmp *previous; luai_jmpbuf b; - volatile int status; /* error code */ + volatile TStatus status; /* error code */ }; -void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { +void luaD_seterrorobj (lua_State *L, TStatus errcode, StkId oldtop) { switch (errcode) { case LUA_ERRMEM: { /* memory error? */ setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ @@ -125,7 +125,7 @@ void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { } -l_noret luaD_throw (lua_State *L, int errcode) { +l_noret luaD_throw (lua_State *L, TStatus errcode) { if (L->errorJmp) { /* thread has an error handler? */ L->errorJmp->status = errcode; /* set status */ LUAI_THROW(L, L->errorJmp); /* jump to it */ @@ -133,7 +133,7 @@ l_noret luaD_throw (lua_State *L, int errcode) { else { /* thread has no error handler */ global_State *g = G(L); errcode = luaE_resetthread(L, errcode); /* close all upvalues */ - L->status = cast_byte(errcode); + L->status = errcode; if (g->mainthread->errorJmp) { /* main thread has a handler? */ setobjs2s(L, g->mainthread->top.p++, L->top.p - 1); /* copy error obj. */ luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ @@ -149,7 +149,7 @@ l_noret luaD_throw (lua_State *L, int errcode) { } -int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { +TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { l_uint32 oldnCcalls = L->nCcalls; struct lua_longjmp lj; lj.status = LUA_OK; @@ -751,8 +751,8 @@ void luaD_callnoyield (lua_State *L, StkId func, int nResults) { ** particular, field CIST_RECST preserves the error status across these ** multiple runs, changing only if there is a new error. */ -static int finishpcallk (lua_State *L, CallInfo *ci) { - int status = getcistrecst(ci); /* get original status */ +static TStatus finishpcallk (lua_State *L, CallInfo *ci) { + TStatus status = getcistrecst(ci); /* get original status */ if (l_likely(status == LUA_OK)) /* no error? */ status = LUA_YIELD; /* was interrupted by an yield */ else { /* error */ @@ -792,14 +792,15 @@ static void finishCcall (lua_State *L, CallInfo *ci) { /* don't need to reset CIST_CLSRET, as it will be set again anyway */ } else { - int status = LUA_YIELD; /* default if there were no errors */ + TStatus status = LUA_YIELD; /* default if there were no errors */ + lua_KFunction kf = ci->u.c.k; /* continuation function */ /* must have a continuation and must be able to call it */ - lua_assert(ci->u.c.k != NULL && yieldable(L)); + lua_assert(kf != NULL && yieldable(L)); if (ci->callstatus & CIST_YPCALL) /* was inside a 'lua_pcallk'? */ status = finishpcallk(L, ci); /* finish it */ adjustresults(L, LUA_MULTRET); /* finish 'lua_callk' */ lua_unlock(L); - n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation */ + n = (*kf)(L, APIstatus(status), ci->u.c.ctx); /* call continuation */ lua_lock(L); api_checknelems(L, n); } @@ -901,7 +902,7 @@ static void resume (lua_State *L, void *ud) { ** (status == LUA_YIELD), or an unprotected error ('findpcall' doesn't ** find a recover point). */ -static int precover (lua_State *L, int status) { +static TStatus precover (lua_State *L, TStatus status) { CallInfo *ci; while (errorstatus(status) && (ci = findpcall(L)) != NULL) { L->ci = ci; /* go down to recovery functions */ @@ -914,7 +915,7 @@ static int precover (lua_State *L, int status) { LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, int *nresults) { - int status; + TStatus status; lua_lock(L); if (L->status == LUA_OK) { /* may be starting a coroutine */ if (L->ci != &L->base_ci) /* not in base level? */ @@ -936,14 +937,14 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, if (l_likely(!errorstatus(status))) lua_assert(status == L->status); /* normal end or yield */ else { /* unrecoverable error */ - L->status = cast_byte(status); /* mark thread as 'dead' */ + L->status = status; /* mark thread as 'dead' */ luaD_seterrorobj(L, status, L->top.p); /* push error message */ L->ci->top.p = L->top.p; } *nresults = (status == LUA_YIELD) ? L->ci->u2.nyield : cast_int(L->top.p - (L->ci->func.p + 1)); lua_unlock(L); - return status; + return APIstatus(status); } @@ -988,7 +989,7 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, */ struct CloseP { StkId level; - int status; + TStatus status; }; @@ -1005,7 +1006,7 @@ static void closepaux (lua_State *L, void *ud) { ** Calls 'luaF_close' in protected mode. Return the original status ** or, in case of errors, the new status. */ -int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status) { +TStatus luaD_closeprotected (lua_State *L, ptrdiff_t level, TStatus status) { CallInfo *old_ci = L->ci; lu_byte old_allowhooks = L->allowhook; for (;;) { /* keep closing upvalues until no more errors */ @@ -1027,9 +1028,9 @@ int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status) { ** thread information ('allowhook', etc.) and in particular ** its stack level in case of errors. */ -int luaD_pcall (lua_State *L, Pfunc func, void *u, - ptrdiff_t old_top, ptrdiff_t ef) { - int status; +TStatus luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, + ptrdiff_t ef) { + TStatus status; CallInfo *old_ci = L->ci; lu_byte old_allowhooks = L->allowhook; ptrdiff_t old_errfunc = L->errfunc; @@ -1091,10 +1092,10 @@ static void f_parser (lua_State *L, void *ud) { } -int luaD_protectedparser (lua_State *L, ZIO *z, const char *name, - const char *mode) { +TStatus luaD_protectedparser (lua_State *L, ZIO *z, const char *name, + const char *mode) { struct SParser p; - int status; + TStatus status; incnny(L); /* cannot yield during parsing */ p.z = z; p.name = name; p.mode = mode; p.dyd.actvar.arr = NULL; p.dyd.actvar.size = 0; diff --git a/ldo.h b/ldo.h index b52a353fda..ea1655e1fa 100644 --- a/ldo.h +++ b/ldo.h @@ -67,8 +67,9 @@ /* type of protected functions, to be ran by 'runprotected' */ typedef void (*Pfunc) (lua_State *L, void *ud); -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 void luaD_seterrorobj (lua_State *L, TStatus errcode, StkId oldtop); +LUAI_FUNC TStatus luaD_protectedparser (lua_State *L, ZIO *z, + const char *name, const char *mode); LUAI_FUNC void luaD_hook (lua_State *L, int event, int line, int fTransfer, int nTransfer); @@ -78,8 +79,9 @@ LUAI_FUNC int luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, LUAI_FUNC CallInfo *luaD_precall (lua_State *L, StkId func, int nResults); LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults); -LUAI_FUNC int luaD_closeprotected (lua_State *L, ptrdiff_t level, int status); -LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, +LUAI_FUNC TStatus luaD_closeprotected (lua_State *L, ptrdiff_t level, + TStatus status); +LUAI_FUNC TStatus luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); LUAI_FUNC void luaD_poscall (lua_State *L, CallInfo *ci, int nres); LUAI_FUNC int luaD_reallocstack (lua_State *L, int newsize, int raiseerror); @@ -87,8 +89,8 @@ 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 l_noret luaD_throw (lua_State *L, int errcode); -LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); +LUAI_FUNC l_noret luaD_throw (lua_State *L, TStatus errcode); +LUAI_FUNC TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); #endif diff --git a/lfunc.c b/lfunc.c index 0ea05e009a..d6853ff82f 100644 --- a/lfunc.c +++ b/lfunc.c @@ -140,7 +140,8 @@ static void checkclosemth (lua_State *L, StkId level) { ** the 'level' of the upvalue being closed, as everything after that ** won't be used again. */ -static void prepcallclosemth (lua_State *L, StkId level, int status, int yy) { +static void prepcallclosemth (lua_State *L, StkId level, TStatus status, + int yy) { TValue *uv = s2v(level); /* value being closed */ TValue *errobj; if (status == CLOSEKTOP) @@ -224,7 +225,7 @@ static void poptbclist (lua_State *L) { ** Close all upvalues and to-be-closed variables up to the given stack ** level. Return restored 'level'. */ -StkId luaF_close (lua_State *L, StkId level, int status, int yy) { +StkId luaF_close (lua_State *L, StkId level, TStatus status, int yy) { ptrdiff_t levelrel = savestack(L, level); luaF_closeupval(L, level); /* first, close the upvalues */ while (L->tbclist.p >= level) { /* traverse tbc's down to that level */ diff --git a/lfunc.h b/lfunc.h index 342389e48a..d6aad3a6df 100644 --- a/lfunc.h +++ b/lfunc.h @@ -44,7 +44,7 @@ /* special status to close upvalues preserving the top of the stack */ -#define CLOSEKTOP (-1) +#define CLOSEKTOP (LUA_ERRERR + 1) LUAI_FUNC Proto *luaF_newproto (lua_State *L); @@ -54,7 +54,7 @@ LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level); LUAI_FUNC void luaF_closeupval (lua_State *L, StkId level); -LUAI_FUNC StkId luaF_close (lua_State *L, StkId level, int status, int yy); +LUAI_FUNC StkId luaF_close (lua_State *L, StkId level, TStatus status, int yy); LUAI_FUNC void luaF_unlinkupval (UpVal *uv); LUAI_FUNC lu_mem luaF_protosize (Proto *p); LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); diff --git a/lgc.c b/lgc.c index 1e9f75698c..8a82b6d990 100644 --- a/lgc.c +++ b/lgc.c @@ -953,7 +953,7 @@ static void GCTM (lua_State *L) { setgcovalue(L, &v, udata2finalize(g)); tm = luaT_gettmbyobj(L, &v, TM_GC); if (!notm(tm)) { /* is there a finalizer? */ - int status; + TStatus status; lu_byte oldah = L->allowhook; lu_byte oldgcstp = g->gcstp; g->gcstp |= GCSTPGC; /* avoid GC steps */ diff --git a/llimits.h b/llimits.h index d98171ae6b..d206e9e1cd 100644 --- a/llimits.h +++ b/llimits.h @@ -41,6 +41,12 @@ typedef unsigned char lu_byte; typedef signed char ls_byte; +/* Type for thread status/error codes */ +typedef lu_byte TStatus; + +/* The C API still uses 'int' for status/error codes */ +#define APIstatus(st) cast_int(st) + /* maximum value for size_t */ #define MAX_SIZET ((size_t)(~(size_t)0)) diff --git a/lstate.c b/lstate.c index 0e1cb01ebb..18ab4900ba 100644 --- a/lstate.c +++ b/lstate.c @@ -320,7 +320,7 @@ void luaE_freethread (lua_State *L, lua_State *L1) { } -int luaE_resetthread (lua_State *L, int status) { +TStatus luaE_resetthread (lua_State *L, TStatus status) { CallInfo *ci = L->ci = &L->base_ci; /* unwind CallInfo list */ setnilvalue(s2v(L->stack.p)); /* 'function' entry for basic 'ci' */ ci->func.p = L->stack.p; @@ -340,12 +340,12 @@ int luaE_resetthread (lua_State *L, int status) { LUA_API int lua_closethread (lua_State *L, lua_State *from) { - int status; + TStatus status; lua_lock(L); L->nCcalls = (from) ? getCcalls(from) : 0; status = luaE_resetthread(L, L->status); lua_unlock(L); - return status; + return APIstatus(status); } diff --git a/lstate.h b/lstate.h index 635f41d2ec..b47a4e9b31 100644 --- a/lstate.h +++ b/lstate.h @@ -339,8 +339,8 @@ typedef struct global_State { */ struct lua_State { CommonHeader; - lu_byte status; lu_byte allowhook; + TStatus status; unsigned short nci; /* number of items in 'ci' list */ StkIdRel top; /* first free slot in the stack */ global_State *l_G; @@ -352,10 +352,10 @@ struct lua_State { GCObject *gclist; struct lua_State *twups; /* list of threads with open upvalues */ struct lua_longjmp *errorJmp; /* current error recover point */ - CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ + CallInfo base_ci; /* CallInfo for first level (C host) */ volatile lua_Hook hook; ptrdiff_t errfunc; /* current error handling function (stack index) */ - l_uint32 nCcalls; /* number of nested (non-yieldable | C) calls */ + l_uint32 nCcalls; /* number of nested non-yieldable or C calls */ int oldpc; /* last pc traced */ int basehookcount; int hookcount; @@ -438,7 +438,7 @@ LUAI_FUNC void luaE_checkcstack (lua_State *L); LUAI_FUNC void luaE_incCstack (lua_State *L); LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont); LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where); -LUAI_FUNC int luaE_resetthread (lua_State *L, int status); +LUAI_FUNC TStatus luaE_resetthread (lua_State *L, TStatus status); #endif diff --git a/lstring.c b/lstring.c index 0c89a51b07..b5c8f89f02 100644 --- a/lstring.c +++ b/lstring.c @@ -329,7 +329,7 @@ TString *luaS_newextlstr (lua_State *L, if (!falloc) f_pintern(L, &ne); /* just internalize string */ else { - int status = luaD_rawrunprotected(L, f_pintern, &ne); + TStatus status = luaD_rawrunprotected(L, f_pintern, &ne); (*falloc)(ud, cast_voidp(s), len + 1, 0); /* free external string */ if (status != LUA_OK) /* memory error? */ luaM_error(L); /* re-raise memory error */ From fa1382b5cd504bdfc5fc3f5c447ed09a4c9804fd Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 31 Jan 2025 13:51:38 -0300 Subject: [PATCH 025/165] Main thread is a regular field of global_State They were already allocated as a single block, so there is no need for the global_State to point to its main thread. --- lapi.c | 2 +- ldo.c | 9 ++++--- lgc.c | 8 +++--- lstate.c | 39 +++++++--------------------- lstate.h | 78 ++++++++++++++++++++++++++++++++------------------------ ltests.c | 4 +-- 6 files changed, 65 insertions(+), 75 deletions(-) diff --git a/lapi.c b/lapi.c index b3062072ab..a5e94507ac 100644 --- a/lapi.c +++ b/lapi.c @@ -655,7 +655,7 @@ LUA_API int lua_pushthread (lua_State *L) { setthvalue(L, s2v(L->top.p), L); api_incr_top(L); lua_unlock(L); - return (G(L)->mainthread == L); + return (mainthread(G(L)) == L); } diff --git a/ldo.c b/ldo.c index 3f9c8b7dfb..65252e07e9 100644 --- a/ldo.c +++ b/ldo.c @@ -132,11 +132,12 @@ l_noret luaD_throw (lua_State *L, TStatus errcode) { } else { /* thread has no error handler */ global_State *g = G(L); + lua_State *mainth = mainthread(g); errcode = luaE_resetthread(L, errcode); /* close all upvalues */ L->status = errcode; - if (g->mainthread->errorJmp) { /* main thread has a handler? */ - setobjs2s(L, g->mainthread->top.p++, L->top.p - 1); /* copy error obj. */ - luaD_throw(g->mainthread, errcode); /* re-throw in main thread */ + if (mainth->errorJmp) { /* main thread has a handler? */ + setobjs2s(L, mainth->top.p++, L->top.p - 1); /* copy error obj. */ + luaD_throw(mainth, errcode); /* re-throw in main thread */ } else { /* no handler at all; abort */ if (g->panic) { /* panic function? */ @@ -961,7 +962,7 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, lua_KContext ctx, ci = L->ci; api_checkpop(L, nresults); if (l_unlikely(!yieldable(L))) { - if (L != G(L)->mainthread) + if (L != mainthread(G(L))) luaG_runerror(L, "attempt to yield across a C-call boundary"); else luaG_runerror(L, "attempt to yield from outside a coroutine"); diff --git a/lgc.c b/lgc.c index 8a82b6d990..e853305211 100644 --- a/lgc.c +++ b/lgc.c @@ -78,7 +78,7 @@ ((*getArrTag(t,i) & BIT_ISCOLLECTABLE) ? getArrVal(t,i)->gc : NULL) -#define markvalue(g,o) { checkliveness(g->mainthread,o); \ +#define markvalue(g,o) { checkliveness(mainthread(g),o); \ if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); } #define markkey(g, n) { if keyiswhite(n) reallymarkobject(g,gckey(n)); } @@ -441,7 +441,7 @@ static void cleargraylists (global_State *g) { static void restartcollection (global_State *g) { cleargraylists(g); g->GCmarked = 0; - markobject(g, g->mainthread); + markobject(g, mainthread(g)); markvalue(g, &g->l_registry); markmt(g); markbeingfnz(g); /* mark any finalizing object left from previous cycle */ @@ -1513,7 +1513,7 @@ void luaC_freeallobjects (lua_State *L) { separatetobefnz(g, 1); /* separate all objects with finalizers */ lua_assert(g->finobj == NULL); callallpendingfinalizers(L); - deletelist(L, g->allgc, obj2gco(g->mainthread)); + deletelist(L, g->allgc, obj2gco(mainthread(g))); lua_assert(g->finobj == NULL); /* no new finalizers */ deletelist(L, g->fixedgc, NULL); /* collect fixed objects */ lua_assert(g->strt.nuse == 0); @@ -1526,7 +1526,7 @@ static void atomic (lua_State *L) { GCObject *grayagain = g->grayagain; /* save original list */ g->grayagain = NULL; lua_assert(g->ephemeron == NULL && g->weak == NULL); - lua_assert(!iswhite(g->mainthread)); + lua_assert(!iswhite(mainthread(g))); g->gcstate = GCSatomic; markobject(g, L); /* mark running thread */ /* registry and global metatables may be changed by API */ diff --git a/lstate.c b/lstate.c index 18ab4900ba..69ddef40ef 100644 --- a/lstate.c +++ b/lstate.c @@ -29,25 +29,6 @@ -/* -** thread state + extra space -*/ -typedef struct LX { - lu_byte extra_[LUA_EXTRASPACE]; - lua_State l; -} LX; - - -/* -** Main thread combines a thread state and the global state -*/ -typedef struct LG { - LX l; - global_State g; -} LG; - - - #define fromstate(L) (cast(LX *, cast(lu_byte *, (L)) - offsetof(LX, l))) @@ -278,8 +259,8 @@ static void close_state (lua_State *L) { } luaM_freearray(L, G(L)->strt.hash, cast_sizet(G(L)->strt.size)); freestack(L); - lua_assert(gettotalbytes(g) == sizeof(LG)); - (*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */ + lua_assert(gettotalbytes(g) == sizeof(global_State)); + (*g->frealloc)(g->ud, g, sizeof(global_State), 0); /* free main block */ } @@ -301,7 +282,7 @@ LUA_API lua_State *lua_newthread (lua_State *L) { L1->hook = L->hook; resethookcount(L1); /* initialize L1 extra space */ - memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread), + memcpy(lua_getextraspace(L1), lua_getextraspace(mainthread(g)), LUA_EXTRASPACE); luai_userstatethread(L, L1); stack_init(L1, L); /* init stack */ @@ -352,11 +333,10 @@ LUA_API int lua_closethread (lua_State *L, lua_State *from) { LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud, unsigned seed) { int i; lua_State *L; - global_State *g; - LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); - if (l == NULL) return NULL; - L = &l->l.l; - g = &l->g; + global_State *g = cast(global_State*, + (*f)(ud, NULL, LUA_TTHREAD, sizeof(global_State))); + if (g == NULL) return NULL; + L = &g->mainth.l; L->tt = LUA_VTHREAD; g->currentwhite = bitmask(WHITE0BIT); L->marked = luaC_white(g); @@ -368,7 +348,6 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud, unsigned seed) { g->ud = ud; g->warnf = NULL; g->ud_warn = NULL; - g->mainthread = L; g->seed = seed; g->gcstp = GCSTPGC; /* no GC while building state */ g->strt.size = g->strt.nuse = 0; @@ -386,7 +365,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud, unsigned seed) { g->gray = g->grayagain = NULL; g->weak = g->ephemeron = g->allweak = NULL; g->twups = NULL; - g->GCtotalbytes = sizeof(LG); + g->GCtotalbytes = sizeof(global_State); g->GCmarked = 0; g->GCdebt = 0; setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */ @@ -408,7 +387,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud, unsigned seed) { LUA_API void lua_close (lua_State *L) { lua_lock(L); - L = G(L)->mainthread; /* only the main thread can be closed */ + L = mainthread(G(L)); /* only the main thread can be closed */ close_state(L); } diff --git a/lstate.h b/lstate.h index b47a4e9b31..050fc35f52 100644 --- a/lstate.h +++ b/lstate.h @@ -283,6 +283,48 @@ struct CallInfo { #define getoah(ci) (((ci)->callstatus & CIST_OAH) ? 1 : 0) +/* +** 'per thread' state +*/ +struct lua_State { + CommonHeader; + lu_byte allowhook; + TStatus status; + unsigned short nci; /* number of items in 'ci' list */ + StkIdRel top; /* first free slot in the stack */ + struct global_State *l_G; + CallInfo *ci; /* call info for current function */ + StkIdRel stack_last; /* end of stack (last element + 1) */ + StkIdRel stack; /* stack base */ + UpVal *openupval; /* list of open upvalues in this stack */ + StkIdRel tbclist; /* list of to-be-closed variables */ + GCObject *gclist; + struct lua_State *twups; /* list of threads with open upvalues */ + struct lua_longjmp *errorJmp; /* current error recover point */ + CallInfo base_ci; /* CallInfo for first level (C host) */ + volatile lua_Hook hook; + ptrdiff_t errfunc; /* current error handling function (stack index) */ + l_uint32 nCcalls; /* number of nested non-yieldable or C calls */ + int oldpc; /* last pc traced */ + int basehookcount; + int hookcount; + volatile l_signalT hookmask; + struct { /* info about transferred values (for call/return hooks) */ + int ftransfer; /* offset of first value transferred */ + int ntransfer; /* number of values transferred */ + } transferinfo; +}; + + +/* +** thread state + extra space +*/ +typedef struct LX { + lu_byte extra_[LUA_EXTRASPACE]; + lua_State l; +} LX; + + /* ** 'global state', shared by all threads of this state */ @@ -324,50 +366,18 @@ typedef struct global_State { GCObject *finobjrold; /* list of really old objects with finalizers */ struct lua_State *twups; /* list of threads with open upvalues */ lua_CFunction panic; /* to be called in unprotected errors */ - struct lua_State *mainthread; TString *memerrmsg; /* message for memory-allocation errors */ TString *tmname[TM_N]; /* array with tag-method names */ struct Table *mt[LUA_NUMTYPES]; /* metatables for basic types */ TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ lua_WarnFunction warnf; /* warning function */ void *ud_warn; /* auxiliary data to 'warnf' */ + LX mainth; /* main thread of this state */ } global_State; -/* -** 'per thread' state -*/ -struct lua_State { - CommonHeader; - lu_byte allowhook; - TStatus status; - unsigned short nci; /* number of items in 'ci' list */ - StkIdRel top; /* first free slot in the stack */ - global_State *l_G; - CallInfo *ci; /* call info for current function */ - StkIdRel stack_last; /* end of stack (last element + 1) */ - StkIdRel stack; /* stack base */ - UpVal *openupval; /* list of open upvalues in this stack */ - StkIdRel tbclist; /* list of to-be-closed variables */ - GCObject *gclist; - struct lua_State *twups; /* list of threads with open upvalues */ - struct lua_longjmp *errorJmp; /* current error recover point */ - CallInfo base_ci; /* CallInfo for first level (C host) */ - volatile lua_Hook hook; - ptrdiff_t errfunc; /* current error handling function (stack index) */ - l_uint32 nCcalls; /* number of nested non-yieldable or C calls */ - int oldpc; /* last pc traced */ - int basehookcount; - int hookcount; - volatile l_signalT hookmask; - struct { /* info about transferred values (for call/return hooks) */ - int ftransfer; /* offset of first value transferred */ - int ntransfer; /* number of values transferred */ - } transferinfo; -}; - - #define G(L) (L->l_G) +#define mainthread(G) (&(G)->mainth.l) /* ** 'g->nilvalue' being a nil value flags that the state was completely diff --git a/ltests.c b/ltests.c index 6b5dc27643..f4855fea95 100644 --- a/ltests.c +++ b/ltests.c @@ -408,7 +408,7 @@ static void checktable (global_State *g, Table *h) { for (n = gnode(h, 0); n < limit; n++) { if (!isempty(gval(n))) { TValue k; - getnodekey(g->mainthread, &k, n); + getnodekey(mainthread(g), &k, n); assert(!keyisnil(n)); checkvalref(g, hgc, &k); checkvalref(g, hgc, gval(n)); @@ -672,7 +672,7 @@ int lua_checkmemory (lua_State *L) { l_mem totalin; /* total of objects that are in gray lists */ l_mem totalshould; /* total of objects that should be in gray lists */ if (keepinvariant(g)) { - assert(!iswhite(g->mainthread)); + assert(!iswhite(mainthread(g))); assert(!iswhite(gcvalue(&g->l_registry))); } assert(!isdead(g, gcvalue(&g->l_registry))); From cd38fe8cf3b0f54dcc1d4a21a7a9cb585c46a43e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 18 Feb 2025 17:02:32 -0300 Subject: [PATCH 026/165] Added macro LUAI_STRICT_ADDRESS By default, the code assumes it is safe to use a dealocated pointer as long as the code does not access it. --- ldo.c | 28 ++++++++++++++++++---------- ltests.h | 4 ++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/ldo.c b/ldo.c index 65252e07e9..4705b26c17 100644 --- a/ldo.c +++ b/ldo.c @@ -192,14 +192,19 @@ TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { /* ** In ISO C, any pointer use after the pointer has been deallocated is -** undefined behavior. So, before a stack reallocation, all pointers are -** changed to offsets, and after the reallocation they are changed back -** to pointers. As during the reallocation the pointers are invalid, the -** reallocation cannot run emergency collections. -** +** undefined behavior. So, before a stack reallocation, all pointers +** should be changed to offsets, and after the reallocation they should +** be changed back to pointers. As during the reallocation the pointers +** are invalid, the reallocation cannot run emergency collections. +** Alternatively, we can use the old address after the deallocation. +** That is not strict ISO C, but seems to work fine everywhere. +** The following macro chooses how strict is the code. */ +#if !defined(LUAI_STRICT_ADDRESS) +#define LUAI_STRICT_ADDRESS 0 +#endif -#if 1 +#if LUAI_STRICT_ADDRESS /* ** Change all pointers to the stack into offsets. */ @@ -238,12 +243,16 @@ static void correctstack (lua_State *L, StkId oldstack) { #else /* -** Alternatively, we can use the old address after the deallocation. -** That is not strict ISO C, but seems to work fine everywhere. +** Assume that it is fine to use an address after its deallocation, +** as long as we do not dereference it. */ -static void relstack (lua_State *L) { UNUSED(L); } +static void relstack (lua_State *L) { UNUSED(L); } /* do nothing */ + +/* +** Correct pointers into 'oldstack' to point into 'L->stack'. +*/ static void correctstack (lua_State *L, StkId oldstack) { CallInfo *ci; UpVal *up; @@ -261,7 +270,6 @@ static void correctstack (lua_State *L, StkId oldstack) { ci->u.l.trap = 1; /* signal to update 'trap' in 'luaV_execute' */ } } - #endif diff --git a/ltests.h b/ltests.h index 543b0d553a..df72307a04 100644 --- a/ltests.h +++ b/ltests.h @@ -44,6 +44,10 @@ #define LUA_RAND32 +/* test stack reallocation with strict address use */ +#define LUAI_STRICT_ADDRESS 1 + + /* memory-allocator control variables */ typedef struct Memcontrol { int failnext; From e5f4927a0b97015d4c22bc22fbf80fb2c11ca7cc Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 20 Feb 2025 10:09:04 -0300 Subject: [PATCH 027/165] Array sizes in undump changed from unsigned to int Array sizes are always int and are dumped as int, so there is no reason to read them back as unsigned. --- lmem.h | 3 ++- lundump.c | 57 ++++++++++++++++++++++++------------------------------- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/lmem.h b/lmem.h index 083585920d..dc714fb2e4 100644 --- a/lmem.h +++ b/lmem.h @@ -57,7 +57,8 @@ #define luaM_freearray(L, b, n) luaM_free_(L, (b), (n)*sizeof(*(b))) #define luaM_new(L,t) cast(t*, luaM_malloc_(L, sizeof(t), 0)) -#define luaM_newvector(L,n,t) cast(t*, luaM_malloc_(L, (n)*sizeof(t), 0)) +#define luaM_newvector(L,n,t) \ + cast(t*, luaM_malloc_(L, cast_sizet(n)*sizeof(t), 0)) #define luaM_newvectorchecked(L,n,t) \ (luaM_checksize(L,n,sizeof(t)), luaM_newvector(L,n,t)) diff --git a/lundump.c b/lundump.c index fd5a2ca6eb..d074a0734e 100644 --- a/lundump.c +++ b/lundump.c @@ -52,7 +52,7 @@ static l_noret error (LoadState *S, const char *why) { ** All high-level loads go through loadVector; you can change it to ** adapt to the endianness of the input */ -#define loadVector(S,b,n) loadBlock(S,b,(n)*sizeof((b)[0])) +#define loadVector(S,b,n) loadBlock(S,b,cast_sizet(n)*sizeof((b)[0])) static void loadBlock (LoadState *S, void *b, size_t size) { if (luaZ_read(S->Z, b, size) != 0) @@ -71,7 +71,7 @@ static void loadAlign (LoadState *S, unsigned align) { } -#define getaddr(S,n,t) cast(t *, getaddr_(S,(n) * sizeof(t))) +#define getaddr(S,n,t) cast(t *, getaddr_(S,cast_sizet(n) * sizeof(t))) static const void *getaddr_ (LoadState *S, size_t size) { const void *block = luaZ_getaddr(S->Z, size); @@ -113,13 +113,6 @@ static size_t loadSize (LoadState *S) { } -/* -** Read an non-negative int */ -static unsigned loadUint (LoadState *S) { - return cast_uint(loadVarint(S, cast_sizet(INT_MAX))); -} - - static int loadInt (LoadState *S) { return cast_int(loadVarint(S, cast_sizet(INT_MAX))); } @@ -188,15 +181,15 @@ static void loadString (LoadState *S, Proto *p, TString **sl) { static void loadCode (LoadState *S, Proto *f) { - unsigned n = loadUint(S); + int n = loadInt(S); loadAlign(S, sizeof(f->code[0])); if (S->fixed) { f->code = getaddr(S, n, Instruction); - f->sizecode = cast_int(n); + f->sizecode = n; } else { f->code = luaM_newvectorchecked(S->L, n, Instruction); - f->sizecode = cast_int(n); + f->sizecode = n; loadVector(S, f->code, n); } } @@ -206,10 +199,10 @@ static void loadFunction(LoadState *S, Proto *f); static void loadConstants (LoadState *S, Proto *f) { - unsigned i; - unsigned n = loadUint(S); + int i; + int n = loadInt(S); f->k = luaM_newvectorchecked(S->L, n, TValue); - f->sizek = cast_int(n); + f->sizek = n; for (i = 0; i < n; i++) setnilvalue(&f->k[i]); for (i = 0; i < n; i++) { @@ -248,10 +241,10 @@ static void loadConstants (LoadState *S, Proto *f) { static void loadProtos (LoadState *S, Proto *f) { - unsigned i; - unsigned n = loadUint(S); + int i; + int n = loadInt(S); f->p = luaM_newvectorchecked(S->L, n, Proto *); - f->sizep = cast_int(n); + f->sizep = n; for (i = 0; i < n; i++) f->p[i] = NULL; for (i = 0; i < n; i++) { @@ -269,10 +262,10 @@ static void loadProtos (LoadState *S, Proto *f) { ** in that case all prototypes must be consistent for the GC. */ static void loadUpvalues (LoadState *S, Proto *f) { - unsigned i; - unsigned n = loadUint(S); + int i; + int n = loadInt(S); f->upvalues = luaM_newvectorchecked(S->L, n, Upvaldesc); - f->sizeupvalues = cast_int(n); + f->sizeupvalues = n; for (i = 0; i < n; i++) /* make array valid for GC */ f->upvalues[i].name = NULL; for (i = 0; i < n; i++) { /* following calls can raise errors */ @@ -284,33 +277,33 @@ static void loadUpvalues (LoadState *S, Proto *f) { static void loadDebug (LoadState *S, Proto *f) { - unsigned i; - unsigned n = loadUint(S); + int i; + int n = loadInt(S); if (S->fixed) { f->lineinfo = getaddr(S, n, ls_byte); - f->sizelineinfo = cast_int(n); + f->sizelineinfo = n; } else { f->lineinfo = luaM_newvectorchecked(S->L, n, ls_byte); - f->sizelineinfo = cast_int(n); + f->sizelineinfo = n; loadVector(S, f->lineinfo, n); } - n = loadUint(S); + n = loadInt(S); if (n > 0) { loadAlign(S, sizeof(int)); if (S->fixed) { f->abslineinfo = getaddr(S, n, AbsLineInfo); - f->sizeabslineinfo = cast_int(n); + f->sizeabslineinfo = n; } else { f->abslineinfo = luaM_newvectorchecked(S->L, n, AbsLineInfo); - f->sizeabslineinfo = cast_int(n); + f->sizeabslineinfo = n; loadVector(S, f->abslineinfo, n); } } - n = loadUint(S); + n = loadInt(S); f->locvars = luaM_newvectorchecked(S->L, n, LocVar); - f->sizelocvars = cast_int(n); + f->sizelocvars = n; for (i = 0; i < n; i++) f->locvars[i].varname = NULL; for (i = 0; i < n; i++) { @@ -318,9 +311,9 @@ static void loadDebug (LoadState *S, Proto *f) { f->locvars[i].startpc = loadInt(S); f->locvars[i].endpc = loadInt(S); } - n = loadUint(S); + n = loadInt(S); if (n != 0) /* does it have debug information? */ - n = cast_uint(f->sizeupvalues); /* must be this many */ + n = f->sizeupvalues; /* must be this many */ for (i = 0; i < n; i++) loadString(S, f, &f->upvalues[i].name); } From ceac82f78be8baeddfa8536472d8b08df2eb7d49 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 26 Feb 2025 11:29:54 -0300 Subject: [PATCH 028/165] Details Comments, small changes in the manual, an extra test for errors in error handling, small changes in tests. --- lobject.c | 8 ++++---- manual/manual.of | 2 +- testes/errors.lua | 25 +++++++++++++++++++++++-- testes/main.lua | 5 ++++- testes/sort.lua | 2 +- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/lobject.c b/lobject.c index c0fd182f13..68566a2bad 100644 --- a/lobject.c +++ b/lobject.c @@ -247,7 +247,7 @@ static lua_Number lua_strx2number (const char *s, char **endptr) { nosigdig++; else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */ r = (r * l_mathop(16.0)) + luaO_hexavalue(*s); - else e++; /* too many digits; ignore, but still count for exponent */ + else e++; /* too many digits; ignore, but still count for exponent */ if (hasdot) e--; /* decimal digit? correct exponent */ } else break; /* neither a dot nor a digit */ @@ -512,18 +512,18 @@ static void initbuff (lua_State *L, BuffFS *buff) { static void pushbuff (lua_State *L, void *ud) { BuffFS *buff = cast(BuffFS*, ud); switch (buff->err) { - case 1: + case 1: /* memory error */ luaD_throw(L, LUA_ERRMEM); break; case 2: /* length overflow: Add "..." at the end of result */ if (buff->buffsize - buff->blen < 3) - strcpy(buff->b + buff->blen - 3, "..."); /* 'blen' must be > 3 */ + strcpy(buff->b + buff->blen - 3, "..."); /* 'blen' must be > 3 */ else { /* there is enough space left for the "..." */ strcpy(buff->b + buff->blen, "..."); buff->blen += 3; } /* FALLTHROUGH */ - default: { /* no errors */ + default: { /* no errors, but it can raise one creating the new string */ TString *ts = luaS_newlstr(L, buff->b, buff->blen); setsvalue2s(L, L->top.p, ts); L->top.p++; diff --git a/manual/manual.of b/manual/manual.of index 274799e3f1..a55c7b49cb 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -6347,7 +6347,7 @@ Opens all standard Lua libraries into the given state. @APIEntry{void luaL_openselectedlibs (lua_State *L, int load, int preload);| @apii{0,0,e} -Opens (loads) and preloads selected libraries into the state @id{L}. +Opens (loads) and preloads selected standard libraries into the state @id{L}. (To @emph{preload} means to add the library loader into the table @Lid{package.preload}, so that the library can be required later by the program. diff --git a/testes/errors.lua b/testes/errors.lua index 027e1b03af..adc111fd3f 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -45,7 +45,7 @@ end -- test error message with no extra info assert(doit("error('hi', 0)") == 'hi') --- test error message with no info +-- test nil error message assert(doit("error()") == nil) @@ -555,7 +555,7 @@ if not _soft then -- error in error handling local res, msg = xpcall(error, error) - assert(not res and type(msg) == 'string') + assert(not res and msg == 'error in error handling') print('+') local function f (x) @@ -586,6 +586,27 @@ if not _soft then end +do -- errors in error handle that not necessarily go forever + local function err (n) -- function to be used as message handler + -- generate an error unless n is zero, so that there is a limited + -- loop of errors + if type(n) ~= "number" then -- some other error? + return n -- report it + elseif n == 0 then + return "END" -- that will be the final message + else error(n - 1) -- does the loop + end + end + + local res, msg = xpcall(error, err, 170) + assert(not res and msg == "END") + + -- too many levels + local res, msg = xpcall(error, err, 300) + assert(not res and msg == "C stack overflow") +end + + do -- non string messages local t = {} diff --git a/testes/main.lua b/testes/main.lua index 1aa7b21771..e0e9cbe8a4 100644 --- a/testes/main.lua +++ b/testes/main.lua @@ -310,8 +310,11 @@ checkprogout("ZYX)\nXYZ)\n") -- bug since 5.2: finalizer called when closing a state could -- subvert finalization order prepfile[[ --- should be called last +-- ensure tables will be collected only at the end of the program +collectgarbage"stop" + print("creating 1") +-- this finalizer should be called last setmetatable({}, {__gc = function () print(1) end}) print("creating 2") diff --git a/testes/sort.lua b/testes/sort.lua index 965e153482..290b199ee5 100644 --- a/testes/sort.lua +++ b/testes/sort.lua @@ -199,7 +199,7 @@ do __index = function (_,k) pos1 = k end, __newindex = function (_,k) pos2 = k; error() end, }) local st, msg = pcall(table.move, a, f, e, t) - assert(not st and not msg and pos1 == x and pos2 == y) + assert(not st and pos1 == x and pos2 == y) end checkmove(1, maxI, 0, 1, 0) checkmove(0, maxI - 1, 1, maxI - 1, maxI) From f9e35627ed26dff4114a1d01ff113d8b4cc91ab5 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 26 Feb 2025 11:31:10 -0300 Subject: [PATCH 029/165] 'lua_State.nci' must be an integer Lua can easily overflow an unsigned short counting nested calls. (The limit to this value is the maximum stack size, LUAI_MAXSTACK, which is currently 1e6.) --- lstate.h | 2 +- ltests.h | 7 +++++-- testes/coroutine.lua | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lstate.h b/lstate.h index 050fc35f52..f841c2321e 100644 --- a/lstate.h +++ b/lstate.h @@ -290,7 +290,6 @@ struct lua_State { CommonHeader; lu_byte allowhook; TStatus status; - unsigned short nci; /* number of items in 'ci' list */ StkIdRel top; /* first free slot in the stack */ struct global_State *l_G; CallInfo *ci; /* call info for current function */ @@ -306,6 +305,7 @@ struct lua_State { ptrdiff_t errfunc; /* current error handling function (stack index) */ l_uint32 nCcalls; /* number of nested non-yieldable or C calls */ int oldpc; /* last pc traced */ + int nci; /* number of items in 'ci' list */ int basehookcount; int hookcount; volatile l_signalT hookmask; diff --git a/ltests.h b/ltests.h index df72307a04..cc372b8f4b 100644 --- a/ltests.h +++ b/ltests.h @@ -152,9 +152,12 @@ LUA_API void *debug_realloc (void *ud, void *block, */ -/* make stack-overflow tests run faster */ +/* +** Reduce maximum stack size to make stack-overflow tests run faster. +** (But value is still large enough to overflow smaller integers.) +*/ #undef LUAI_MAXSTACK -#define LUAI_MAXSTACK 50000 +#define LUAI_MAXSTACK 68000 /* test mode uses more stack space */ diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 78b9bdca19..abc08039d2 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -127,6 +127,18 @@ assert(#a == 22 and a[#a] == 79) x, a = nil +do -- "bug" in 5.4.2 + local function foo () foo () end -- just create a stack overflow + local co = coroutine.create(foo) + -- running this coroutine would overflow the unsigned short 'nci', the + -- counter of CallInfo structures available to the thread. + -- (The issue only manifests in an 'assert'.) + local st, msg = coroutine.resume(co) + assert(string.find(msg, "stack overflow")) + assert(coroutine.status(co) == "dead") +end + + print("to-be-closed variables in coroutines") local function func2close (f) From 127a8e80fe0d74efd26994b3877cdc77b712ea56 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 28 Feb 2025 10:10:27 -0300 Subject: [PATCH 030/165] '__close' gets no error object if there is no error Instead of receiving nil as a second argument, __close metamethods are called with just one argument when there are no errors. --- ldo.c | 4 ---- lfunc.c | 32 ++++++++++++++++++++------------ manual/manual.of | 7 ++++--- testes/locals.lua | 44 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/ldo.c b/ldo.c index 4705b26c17..3ddc5a4c04 100644 --- a/ldo.c +++ b/ldo.c @@ -111,10 +111,6 @@ void luaD_seterrorobj (lua_State *L, TStatus errcode, StkId oldtop) { setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); break; } - case LUA_OK: { /* special case only for closing upvalues */ - setnilvalue(s2v(oldtop)); /* no error message */ - break; - } default: { lua_assert(errorstatus(errcode)); /* real error */ setobjs2s(L, oldtop, L->top.p - 1); /* error message on current top */ diff --git a/lfunc.c b/lfunc.c index d6853ff82f..c62a5d2395 100644 --- a/lfunc.c +++ b/lfunc.c @@ -100,21 +100,23 @@ UpVal *luaF_findupval (lua_State *L, StkId level) { /* -** Call closing method for object 'obj' with error message 'err'. The +** Call closing method for object 'obj' with error object 'err'. The ** boolean 'yy' controls whether the call is yieldable. ** (This function assumes EXTRA_STACK.) */ static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) { StkId top = L->top.p; + StkId func = top; const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); - setobj2s(L, top, tm); /* will call metamethod... */ - setobj2s(L, top + 1, obj); /* with 'self' as the 1st argument */ - setobj2s(L, top + 2, err); /* and error msg. as 2nd argument */ - L->top.p = top + 3; /* add function and arguments */ + setobj2s(L, top++, tm); /* will call metamethod... */ + setobj2s(L, top++, obj); /* with 'self' as the 1st argument */ + if (err != NULL) /* if there was an error... */ + setobj2s(L, top++, err); /* then error object will be 2nd argument */ + L->top.p = top; /* add function and arguments */ if (yy) - luaD_call(L, top, 0); + luaD_call(L, func, 0); else - luaD_callnoyield(L, top, 0); + luaD_callnoyield(L, func, 0); } @@ -144,11 +146,17 @@ static void prepcallclosemth (lua_State *L, StkId level, TStatus status, int yy) { TValue *uv = s2v(level); /* value being closed */ TValue *errobj; - if (status == CLOSEKTOP) - errobj = &G(L)->nilvalue; /* error object is nil */ - else { /* 'luaD_seterrorobj' will set top to level + 2 */ - errobj = s2v(level + 1); /* error object goes after 'uv' */ - luaD_seterrorobj(L, status, level + 1); /* set error object */ + switch (status) { + case LUA_OK: + L->top.p = level + 1; /* call will be at this level */ + /* FALLTHROUGH */ + case CLOSEKTOP: /* don't need to change top */ + errobj = NULL; /* no error object */ + break; + default: /* 'luaD_seterrorobj' will set top to level + 2 */ + errobj = s2v(level + 1); /* error object goes after 'uv' */ + luaD_seterrorobj(L, status, level + 1); /* set error object */ + break; } callclosemethod(L, uv, errobj, yy); } diff --git a/manual/manual.of b/manual/manual.of index a55c7b49cb..ff4e79feb9 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1612,10 +1612,11 @@ or exiting by an error. Here, to @emph{close} a value means to call its @idx{__close} metamethod. When calling the metamethod, -the value itself is passed as the first argument -and the error object that caused the exit (if any) +the value itself is passed as the first argument. +If there was an error, +the error object that caused the exit is passed as a second argument; -if there was no error, the second argument is @nil. +otherwise, there is no second argument. The value assigned to a to-be-closed variable must have a @idx{__close} metamethod diff --git a/testes/locals.lua b/testes/locals.lua index 090d846bef..910deb8a52 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -280,6 +280,32 @@ do end +do -- testing presence of second argument + local function foo (howtoclose, obj, n) + local ca -- copy of 'a' visible inside its close metamethod + do + local a = func2close(function (...) + local t = table.pack(...) + assert(select("#", ...) == n) + assert(t.n == n and t[1] == ca and (t.n < 2 or t[2] == obj)) + ca = 15 -- final value to be returned if howtoclose=="scope" + end) + ca = a + if howtoclose == "ret" then return obj -- 'a' closed by return + elseif howtoclose == "err" then error(obj) -- 'a' closed by error + end + end -- 'a' closed by end of scope + return ca -- ca now should be 15 + end + -- with no errors, closing methods receive no extra argument + assert(foo("scope", nil, 1) == 15) -- close by end of scope + assert(foo("ret", 32, 1) == 32) -- close by return + -- with errors, they do + local st, msg = pcall(foo, "err", 23, 2) -- close by error + assert(not st and msg == 23) +end + + -- testing to-be-closed x compile-time constants -- (there were some bugs here in Lua 5.4-rc3, due to a confusion -- between compile levels and stack levels of variables) @@ -865,8 +891,10 @@ do if extra then extrares = co() -- runs until first (extra) yield end - local res = table.pack(co()) -- runs until yield inside '__close' - assert(res.n == 2 and res[2] == nil) + local res = table.pack(co()) -- runs until "regular" yield + -- regular yield will yield all values passed to the close function; + -- without errors, that is only the object being closed. + assert(res.n == 1 and type(res[1]) == "table") local res2 = table.pack(co()) -- runs until end of function assert(res2.n == t.n) for i = 1, #t do @@ -879,10 +907,10 @@ do end local function foo () - local x = func2close(coroutine.yield) + local x = func2close(coroutine.yield) -- "regular" yield local extra = func2close(function (self) assert(self == extrares) - coroutine.yield(100) + coroutine.yield(100) -- first (extra) yield end) extrares = extra return table.unpack{10, x, 30} @@ -891,21 +919,21 @@ do assert(extrares == 100) local function foo () - local x = func2close(coroutine.yield) + local x = func2close(coroutine.yield) -- "regular" yield return end check(foo, false) local function foo () - local x = func2close(coroutine.yield) + local x = func2close(coroutine.yield) -- "regular" yield local y, z = 20, 30 return x end check(foo, false, "x") local function foo () - local x = func2close(coroutine.yield) - local extra = func2close(coroutine.yield) + local x = func2close(coroutine.yield) -- "regular" yield + local extra = func2close(coroutine.yield) -- extra yield return table.unpack({}, 1, 100) -- 100 nils end check(foo, true, table.unpack({}, 1, 100)) From ee99452158de5e2fa804bd10de7669848f3b3952 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 28 Feb 2025 14:53:58 -0300 Subject: [PATCH 031/165] Error object cannot be nil Lua will change a nil as error object to a string message, so that it never reports an error with nil as the error object. --- ldebug.c | 4 +++- ldo.c | 10 +++++++--- manual/manual.of | 11 ++++++++++- testes/errors.lua | 6 +++--- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/ldebug.c b/ldebug.c index af3b758334..18bdc5959c 100644 --- a/ldebug.c +++ b/ldebug.c @@ -844,7 +844,9 @@ l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { va_start(argp, fmt); msg = luaO_pushvfstring(L, fmt, argp); /* format message */ va_end(argp); - if (msg != NULL && isLua(ci)) { /* Lua function? (and no error) */ + if (msg == NULL) /* no memory to format message? */ + luaD_throw(L, LUA_ERRMEM); + else if (isLua(ci)) { /* Lua function? */ /* add source:line information */ luaG_addinfo(L, msg, ci_func(ci)->p->source, getcurrentline(ci)); setobjs2s(L, L->top.p - 2, L->top.p - 1); /* remove 'msg' */ diff --git a/ldo.c b/ldo.c index 3ddc5a4c04..84f7bbb2c4 100644 --- a/ldo.c +++ b/ldo.c @@ -112,12 +112,16 @@ void luaD_seterrorobj (lua_State *L, TStatus errcode, StkId oldtop) { break; } default: { - lua_assert(errorstatus(errcode)); /* real error */ - setobjs2s(L, oldtop, L->top.p - 1); /* error message on current top */ + lua_assert(errorstatus(errcode)); /* must be a real error */ + if (!ttisnil(s2v(L->top.p - 1))) { /* error object is not nil? */ + setobjs2s(L, oldtop, L->top.p - 1); /* move it to 'oldtop' */ + } + else /* change it to a proper message */ + setsvalue2s(L, oldtop, luaS_newliteral(L, "")); break; } } - L->top.p = oldtop + 1; + L->top.p = oldtop + 1; /* top goes back to old top plus error object */ } diff --git a/manual/manual.of b/manual/manual.of index ff4e79feb9..b34e1e9cb7 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -290,7 +290,9 @@ an @def{error object} is propagated with information about the error. Lua itself only generates errors whose error object is a string, but programs can generate errors with -any value as the error object. +any value as the error object, +except @nil. +(Lua will change a @nil as error object to a string message.) It is up to the Lua program or its host to handle such error objects. For historical reasons, an error object is often called an @def{error message}, @@ -8082,6 +8084,8 @@ multiple assignment: The default for @id{a2} is @id{a1}. The destination range can overlap with the source range. The number of elements to be moved must fit in a Lua integer. +If @id{f} is larger than @id{e}, +nothing is moved. Returns the destination table @id{a2}. @@ -9402,6 +9406,11 @@ declare a local variable with the same name in the loop body. A chain of @id{__call} metamethods can have at most 15 objects. } +@item{ +In an error, a @nil as the error object is replaced by a +string message. +} + } } diff --git a/testes/errors.lua b/testes/errors.lua index adc111fd3f..5fdb772263 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -46,7 +46,7 @@ end assert(doit("error('hi', 0)") == 'hi') -- test nil error message -assert(doit("error()") == nil) +assert(doit("error()") == "") -- test common errors/errors that crashed in the past @@ -614,7 +614,7 @@ do assert(not res and msg == t) res, msg = pcall(function () error(nil) end) - assert(not res and msg == nil) + assert(not res and msg == "") local function f() error{msg='x'} end res, msg = xpcall(f, function (r) return {msg=r.msg..'y'} end) @@ -634,7 +634,7 @@ do assert(not res and msg == t) res, msg = pcall(assert, nil, nil) - assert(not res and msg == nil) + assert(not res and type(msg) == "string") -- 'assert' without arguments res, msg = pcall(assert) From cb88c1cd5d22fe7c56f4f374ded7c16f7cf14bf3 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 28 Feb 2025 15:48:45 -0300 Subject: [PATCH 032/165] Detail Added macro LUA_FAILISFALSE to make easier to change the fail value from nil to false. --- lauxlib.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lauxlib.h b/lauxlib.h index 4be008b90d..d8522098a7 100644 --- a/lauxlib.h +++ b/lauxlib.h @@ -165,7 +165,11 @@ LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, /* push the value used to represent failure/error */ +#if defined(LUA_FAILISFALSE) +#define luaL_pushfail(L) lua_pushboolean(L, 0) +#else #define luaL_pushfail(L) lua_pushnil(L) +#endif From b5b1995f2925b2f9be4a48304ac97a38f8608648 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 10 Mar 2025 15:21:32 -0300 Subject: [PATCH 033/165] Checks for type 'int' added to binary header The structure 'AbsLineInfo' is hard-dumped into binary chunks, and it comprises two 'int' fields. --- ldump.c | 13 ++++++++----- lundump.c | 38 ++++++++++++++++++++++++++------------ lundump.h | 5 +++-- testes/calls.lua | 47 ++++++++++++++++++++++++++++++----------------- 4 files changed, 67 insertions(+), 36 deletions(-) diff --git a/ldump.c b/ldump.c index 71d9a5b1c9..54f96674e1 100644 --- a/ldump.c +++ b/ldump.c @@ -253,16 +253,19 @@ static void dumpFunction (DumpState *D, const Proto *f) { } +#define dumpNumInfo(D, tvar, value) \ + { tvar i = value; dumpByte(D, sizeof(tvar)); dumpVar(D, i); } + + static void dumpHeader (DumpState *D) { dumpLiteral(D, LUA_SIGNATURE); dumpByte(D, LUAC_VERSION); dumpByte(D, LUAC_FORMAT); dumpLiteral(D, LUAC_DATA); - dumpByte(D, sizeof(Instruction)); - dumpByte(D, sizeof(lua_Integer)); - dumpByte(D, sizeof(lua_Number)); - dumpInteger(D, LUAC_INT); - dumpNumber(D, LUAC_NUM); + dumpNumInfo(D, int, LUAC_INT); + dumpNumInfo(D, Instruction, LUAC_INST); + dumpNumInfo(D, lua_Integer, LUAC_INT); + dumpNumInfo(D, lua_Number, LUAC_NUM); } diff --git a/lundump.c b/lundump.c index d074a0734e..d53bfc9a99 100644 --- a/lundump.c +++ b/lundump.c @@ -345,13 +345,29 @@ static void checkliteral (LoadState *S, const char *s, const char *msg) { } -static void fchecksize (LoadState *S, size_t size, const char *tname) { - if (loadByte(S) != size) - error(S, luaO_pushfstring(S->L, "%s size mismatch", tname)); +static l_noret numerror (LoadState *S, const char *what, const char *tname) { + const char *msg = luaO_pushfstring(S->L, "%s %s mismatch", tname, what); + error(S, msg); } -#define checksize(S,t) fchecksize(S,sizeof(t),#t) +static void checknumsize (LoadState *S, int size, const char *tname) { + if (size != loadByte(S)) + numerror(S, "size", tname); +} + + +static void checknumformat (LoadState *S, int eq, const char *tname) { + if (!eq) + numerror(S, "format", tname); +} + + +#define checknum(S,tvar,value,tname) \ + { tvar i; checknumsize(S, sizeof(i), tname); \ + loadVar(S, i); \ + checknumformat(S, i == value, tname); } + static void checkHeader (LoadState *S) { /* skip 1st char (already read and checked) */ @@ -361,13 +377,10 @@ static void checkHeader (LoadState *S) { if (loadByte(S) != LUAC_FORMAT) error(S, "format mismatch"); checkliteral(S, LUAC_DATA, "corrupted chunk"); - checksize(S, Instruction); - checksize(S, lua_Integer); - checksize(S, lua_Number); - if (loadInteger(S) != LUAC_INT) - error(S, "integer format mismatch"); - if (loadNumber(S) != LUAC_NUM) - error(S, "float format mismatch"); + checknum(S, int, LUAC_INT, "int"); + checknum(S, Instruction, LUAC_INST, "instruction"); + checknum(S, lua_Integer, LUAC_INT, "Lua integer"); + checknum(S, lua_Number, LUAC_NUM, "Lua number"); } @@ -398,7 +411,8 @@ LClosure *luaU_undump (lua_State *L, ZIO *Z, const char *name, int fixed) { cl->p = luaF_newproto(L); luaC_objbarrier(L, cl, cl->p); loadFunction(&S, cl->p); - lua_assert(cl->nupvalues == cl->p->sizeupvalues); + if (cl->nupvalues != cl->p->sizeupvalues) + error(&S, "corrupted chunk"); luai_verifycode(L, cl->p); L->top.p--; /* pop table */ return cl; diff --git a/lundump.h b/lundump.h index 1d6e50ea84..c4e06f9ebd 100644 --- a/lundump.h +++ b/lundump.h @@ -17,8 +17,9 @@ /* data to catch conversion errors */ #define LUAC_DATA "\x19\x93\r\n\x1a\n" -#define LUAC_INT 0x5678 -#define LUAC_NUM cast_num(370.5) +#define LUAC_INT -0x5678 +#define LUAC_INST 0x12345678 +#define LUAC_NUM cast_num(-370.5) /* ** Encode major-minor version in one byte, one nibble for each diff --git a/testes/calls.lua b/testes/calls.lua index 310282157d..8b35595768 100644 --- a/testes/calls.lua +++ b/testes/calls.lua @@ -480,15 +480,22 @@ assert((function (a) return a end)() == nil) print("testing binary chunks") do - local header = string.pack("c4BBc6BBB", - "\27Lua", -- signature - 0x55, -- version 5.5 (0x55) - 0, -- format - "\x19\x93\r\n\x1a\n", -- data - 4, -- size of instruction - string.packsize("j"), -- sizeof(lua integer) - string.packsize("n") -- sizeof(lua number) - ) + local headformat = "c4BBc6BiBI4BjBn" + local header = { -- header components + "\27Lua", -- signature + 0x55, -- version 5.5 (0x55) + 0, -- format + "\x19\x93\r\n\x1a\n", -- a binary string + string.packsize("i"), -- size of an int + -0x5678, -- an int + 4, -- size of an instruction + 0x12345678, -- an instruction (4 bytes) + string.packsize("j"), -- size of a Lua integer + -0x5678, -- a Lua integer + string.packsize("n"), -- size of a Lua float + -370.5, -- a Lua float + } + local c = string.dump(function () local a = 1; local b = 3; local f = function () return a + b + _ENV.c; end -- upvalues @@ -500,17 +507,23 @@ do assert(assert(load(c))() == 10) -- check header - assert(string.sub(c, 1, #header) == header) - -- check LUAC_INT and LUAC_NUM - local ci, cn = string.unpack("jn", c, #header + 1) - assert(ci == 0x5678 and cn == 370.5) - - -- corrupted header + local t = {string.unpack(headformat, c)} for i = 1, #header do + assert(t[i] == header[i]) + end + + -- Testing corrupted header. + -- A single wrong byte in the head invalidates the chunk, + -- except for the Lua float check. (If numbers are long double, + -- the representation may need padding, and changing that padding + -- will not invalidate the chunk.) + local headlen = string.packsize(headformat) + headlen = headlen - string.packsize("n") -- remove float check + for i = 1, headlen do local s = string.sub(c, 1, i - 1) .. - string.char(string.byte(string.sub(c, i, i)) + 1) .. + string.char((string.byte(string.sub(c, i, i)) + 1) & 0xFF) .. string.sub(c, i + 1, -1) - assert(#s == #c) + assert(#s == #c and s ~= c) assert(not load(s)) end From 808976bb59d91a031d9832b5482a9fb5a41faee3 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 12 Mar 2025 12:35:36 -0300 Subject: [PATCH 034/165] Small correction in 'traverseweakvalue' After a weak table is traversed in the atomic phase, if it does not have white values ('hasclears') it does not need to be retraversed again. (Comments were correct, but code did not agree with them.) --- lgc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lgc.c b/lgc.c index e853305211..cada07d9a1 100644 --- a/lgc.c +++ b/lgc.c @@ -497,10 +497,10 @@ static void traverseweakvalue (global_State *g, Table *h) { hasclears = 1; /* table will have to be cleared */ } } - if (g->gcstate == GCSatomic && hasclears) - linkgclist(h, g->weak); /* has to be cleared later */ - else + if (g->gcstate == GCSpropagate) linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ + else if (hasclears) + linkgclist(h, g->weak); /* has to be cleared later */ } From 4398e488e678decd06a5ca48a27751d509361405 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 12 Mar 2025 13:52:35 -0300 Subject: [PATCH 035/165] New test file 'memerr.lua' Tests for memory-allocation errors moved from 'api.lua' to this new file, as 'api.lua' was already too big. (Besides, these tests have nothing to do with the API.) --- testes/all.lua | 1 + testes/api.lua | 243 ------------------------------------------ testes/memerr.lua | 266 ++++++++++++++++++++++++++++++++++++++++++++++ testes/packtests | 1 + 4 files changed, 268 insertions(+), 243 deletions(-) create mode 100644 testes/memerr.lua diff --git a/testes/all.lua b/testes/all.lua index 4ffa9efee1..c3fdac957c 100644 --- a/testes/all.lua +++ b/testes/all.lua @@ -182,6 +182,7 @@ dofile('nextvar.lua') dofile('pm.lua') dofile('utf8.lua') dofile('api.lua') +dofile('memerr.lua') assert(dofile('events.lua') == 12) dofile('vararg.lua') dofile('closure.lua') diff --git a/testes/api.lua b/testes/api.lua index 21f703fd17..ee2de98bdb 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -11,9 +11,6 @@ local debug = require "debug" local pack = table.pack --- standard error message for memory errors -local MEMERRMSG = "not enough memory" - local function tcheck (t1, t2) assert(t1.n == (t2.n or #t2) + 1) for i = 2, t1.n do assert(t1[i] == t2[i - 1]) end @@ -432,11 +429,6 @@ do "bad argument #4 (string expected, got no value)") - -- memory error - T.totalmem(T.totalmem()+10000) -- set low memory limit (+10k) - assert(T.checkpanic("newuserdata 20000") == MEMERRMSG) - T.totalmem(0) -- restore high limit - -- memory error + thread status local x = T.checkpanic( [[ alloccount 0 # force a memory error in next line @@ -1306,241 +1298,6 @@ do end ---[[ -** {================================================================== -** Testing memory limits -** =================================================================== ---]] - -print("memory-allocation errors") - -checkerr("block too big", T.newuserdata, math.maxinteger) -collectgarbage() -local f = load"local a={}; for i=1,100000 do a[i]=i end" -T.alloccount(10) -checkerr(MEMERRMSG, f) -T.alloccount() -- remove limit - - --- test memory errors; increase limit for maximum memory by steps, --- o that we get memory errors in all allocations of a given --- task, until there is enough memory to complete the task without --- errors. -local function testbytes (s, f) - collectgarbage() - local M = T.totalmem() - local oldM = M - local a,b = nil - while true do - collectgarbage(); collectgarbage() - T.totalmem(M) - a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) - T.totalmem(0) -- remove limit - if a and b == "OK" then break end -- stop when no more errors - if b ~= "OK" and b ~= MEMERRMSG then -- not a memory error? - error(a, 0) -- propagate it - end - M = M + 7 -- increase memory limit - end - print(string.format("minimum memory for %s: %d bytes", s, M - oldM)) - return a -end - --- test memory errors; increase limit for number of allocations one --- by one, so that we get memory errors in all allocations of a given --- task, until there is enough allocations to complete the task without --- errors. - -local function testalloc (s, f) - collectgarbage() - local M = 0 - local a,b = nil - while true do - collectgarbage(); collectgarbage() - T.alloccount(M) - a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) - T.alloccount() -- remove limit - if a and b == "OK" then break end -- stop when no more errors - if b ~= "OK" and b ~= MEMERRMSG then -- not a memory error? - error(a, 0) -- propagate it - end - M = M + 1 -- increase allocation limit - end - print(string.format("minimum allocations for %s: %d allocations", s, M)) - return a -end - - -local function testamem (s, f) - testalloc(s, f) - return testbytes(s, f) -end - - --- doing nothing -b = testamem("doing nothing", function () return 10 end) -assert(b == 10) - --- testing memory errors when creating a new state - -testamem("state creation", function () - local st = T.newstate() - if st then T.closestate(st) end -- close new state - return st -end) - -testamem("empty-table creation", function () - return {} -end) - -testamem("string creation", function () - return "XXX" .. "YYY" -end) - -testamem("coroutine creation", function() - return coroutine.create(print) -end) - - --- testing to-be-closed variables -testamem("to-be-closed variables", function() - local flag - do - local x = - setmetatable({}, {__close = function () flag = true end}) - flag = false - local x = {} - end - return flag -end) - - --- testing threads - --- get main thread from registry -local mt = T.testC("rawgeti R !M; return 1") -assert(type(mt) == "thread" and coroutine.running() == mt) - - - -local function expand (n,s) - if n==0 then return "" end - local e = string.rep("=", n) - return string.format("T.doonnewstack([%s[ %s;\n collectgarbage(); %s]%s])\n", - e, s, expand(n-1,s), e) -end - -G=0; collectgarbage(); a =collectgarbage("count") -load(expand(20,"G=G+1"))() -assert(G==20); collectgarbage(); -- assert(gcinfo() <= a+1) -G = nil - -testamem("running code on new thread", function () - return T.doonnewstack("local x=1") == 0 -- try to create thread -end) - - --- testing memory x compiler - -testamem("loadstring", function () - return load("x=1") -- try to do load a string -end) - - -local testprog = [[ -local function foo () return end -local t = {"x"} -AA = "aaa" -for i = 1, #t do AA = AA .. t[i] end -return true -]] - --- testing memory x dofile -_G.AA = nil -local t =os.tmpname() -local f = assert(io.open(t, "w")) -f:write(testprog) -f:close() -testamem("dofile", function () - local a = loadfile(t) - return a and a() -end) -assert(os.remove(t)) -assert(_G.AA == "aaax") - - --- other generic tests - -testamem("gsub", function () - local a, b = string.gsub("alo alo", "(a)", function (x) return x..'b' end) - return (a == 'ablo ablo') -end) - -testamem("dump/undump", function () - local a = load(testprog) - local b = a and string.dump(a) - a = b and load(b) - return a and a() -end) - -_G.AA = nil - -local t = os.tmpname() -testamem("file creation", function () - local f = assert(io.open(t, 'w')) - assert (not io.open"nomenaoexistente") - io.close(f); - return not loadfile'nomenaoexistente' -end) -assert(os.remove(t)) - -testamem("table creation", function () - local a, lim = {}, 10 - for i=1,lim do a[i] = i; a[i..'a'] = {} end - return (type(a[lim..'a']) == 'table' and a[lim] == lim) -end) - -testamem("constructors", function () - local a = {10, 20, 30, 40, 50; a=1, b=2, c=3, d=4, e=5} - return (type(a) == 'table' and a.e == 5) -end) - -local a = 1 -local close = nil -testamem("closure creation", function () - function close (b) - return function (x) return b + x end - end - return (close(2)(4) == 6) -end) - -testamem("using coroutines", function () - local a = coroutine.wrap(function () - coroutine.yield(string.rep("a", 10)) - return {} - end) - assert(string.len(a()) == 10) - return a() -end) - -do -- auxiliary buffer - local lim = 100 - local a = {}; for i = 1, lim do a[i] = "01234567890123456789" end - testamem("auxiliary buffer", function () - return (#table.concat(a, ",") == 20*lim + lim - 1) - end) -end - -testamem("growing stack", function () - local function foo (n) - if n == 0 then return 1 else return 1 + foo(n - 1) end - end - return foo(100) -end) - --- }================================================================== - - do -- testing failing in 'lua_checkstack' local res = T.testC([[rawcheckstack 500000; return 1]]) assert(res == false) diff --git a/testes/memerr.lua b/testes/memerr.lua new file mode 100644 index 0000000000..cb236eb976 --- /dev/null +++ b/testes/memerr.lua @@ -0,0 +1,266 @@ +-- $Id: testes/memerr.lua $ +-- See Copyright Notice in file all.lua + + +local function checkerr (msg, f, ...) + local stat, err = pcall(f, ...) + assert(not stat and string.find(err, msg)) +end + +if T==nil then + (Message or print) + ('\n >>> testC not active: skipping memory error tests <<<\n') + return +end + +print("testing memory-allocation errors") + +local debug = require "debug" + +local pack = table.pack + +-- standard error message for memory errors +local MEMERRMSG = "not enough memory" + + +-- memory error in panic function +T.totalmem(T.totalmem()+10000) -- set low memory limit (+10k) +assert(T.checkpanic("newuserdata 20000") == MEMERRMSG) +T.totalmem(0) -- restore high limit + + + +-- {================================================================== +-- Testing memory limits +-- =================================================================== + +checkerr("block too big", T.newuserdata, math.maxinteger) +collectgarbage() +local f = load"local a={}; for i=1,100000 do a[i]=i end" +T.alloccount(10) +checkerr(MEMERRMSG, f) +T.alloccount() -- remove limit + + +-- test memory errors; increase limit for maximum memory by steps, +-- o that we get memory errors in all allocations of a given +-- task, until there is enough memory to complete the task without +-- errors. +local function testbytes (s, f) + collectgarbage() + local M = T.totalmem() + local oldM = M + local a,b = nil + while true do + collectgarbage(); collectgarbage() + T.totalmem(M) + a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) + T.totalmem(0) -- remove limit + if a and b == "OK" then break end -- stop when no more errors + if b ~= "OK" and b ~= MEMERRMSG then -- not a memory error? + error(a, 0) -- propagate it + end + M = M + 7 -- increase memory limit + end + print(string.format("minimum memory for %s: %d bytes", s, M - oldM)) + return a +end + +-- test memory errors; increase limit for number of allocations one +-- by one, so that we get memory errors in all allocations of a given +-- task, until there is enough allocations to complete the task without +-- errors. + +local function testalloc (s, f) + collectgarbage() + local M = 0 + local a,b = nil + while true do + collectgarbage(); collectgarbage() + T.alloccount(M) + a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) + T.alloccount() -- remove limit + if a and b == "OK" then break end -- stop when no more errors + if b ~= "OK" and b ~= MEMERRMSG then -- not a memory error? + error(a, 0) -- propagate it + end + M = M + 1 -- increase allocation limit + end + print(string.format("minimum allocations for %s: %d allocations", s, M)) + return a +end + + +local function testamem (s, f) + testalloc(s, f) + return testbytes(s, f) +end + + +-- doing nothing +b = testamem("doing nothing", function () return 10 end) +assert(b == 10) + +-- testing memory errors when creating a new state + +testamem("state creation", function () + local st = T.newstate() + if st then T.closestate(st) end -- close new state + return st +end) + +testamem("empty-table creation", function () + return {} +end) + +testamem("string creation", function () + return "XXX" .. "YYY" +end) + +testamem("coroutine creation", function() + return coroutine.create(print) +end) + + +-- testing to-be-closed variables +testamem("to-be-closed variables", function() + local flag + do + local x = + setmetatable({}, {__close = function () flag = true end}) + flag = false + local x = {} + end + return flag +end) + + +-- testing threads + +-- get main thread from registry +local mt = T.testC("rawgeti R !M; return 1") +assert(type(mt) == "thread" and coroutine.running() == mt) + + + +local function expand (n,s) + if n==0 then return "" end + local e = string.rep("=", n) + return string.format("T.doonnewstack([%s[ %s;\n collectgarbage(); %s]%s])\n", + e, s, expand(n-1,s), e) +end + +G=0; collectgarbage(); a =collectgarbage("count") +load(expand(20,"G=G+1"))() +assert(G==20); collectgarbage(); -- assert(gcinfo() <= a+1) +G = nil + +testamem("running code on new thread", function () + return T.doonnewstack("local x=1") == 0 -- try to create thread +end) + + +-- testing memory x compiler + +testamem("loadstring", function () + return load("x=1") -- try to do load a string +end) + + +local testprog = [[ +local function foo () return end +local t = {"x"} +AA = "aaa" +for i = 1, #t do AA = AA .. t[i] end +return true +]] + +-- testing memory x dofile +_G.AA = nil +local t =os.tmpname() +local f = assert(io.open(t, "w")) +f:write(testprog) +f:close() +testamem("dofile", function () + local a = loadfile(t) + return a and a() +end) +assert(os.remove(t)) +assert(_G.AA == "aaax") + + +-- other generic tests + +testamem("gsub", function () + local a, b = string.gsub("alo alo", "(a)", function (x) return x..'b' end) + return (a == 'ablo ablo') +end) + +testamem("dump/undump", function () + local a = load(testprog) + local b = a and string.dump(a) + a = b and load(b) + return a and a() +end) + +_G.AA = nil + +local t = os.tmpname() +testamem("file creation", function () + local f = assert(io.open(t, 'w')) + assert (not io.open"nomenaoexistente") + io.close(f); + return not loadfile'nomenaoexistente' +end) +assert(os.remove(t)) + +testamem("table creation", function () + local a, lim = {}, 10 + for i=1,lim do a[i] = i; a[i..'a'] = {} end + return (type(a[lim..'a']) == 'table' and a[lim] == lim) +end) + +testamem("constructors", function () + local a = {10, 20, 30, 40, 50; a=1, b=2, c=3, d=4, e=5} + return (type(a) == 'table' and a.e == 5) +end) + +local a = 1 +local close = nil +testamem("closure creation", function () + function close (b) + return function (x) return b + x end + end + return (close(2)(4) == 6) +end) + +testamem("using coroutines", function () + local a = coroutine.wrap(function () + coroutine.yield(string.rep("a", 10)) + return {} + end) + assert(string.len(a()) == 10) + return a() +end) + +do -- auxiliary buffer + local lim = 100 + local a = {}; for i = 1, lim do a[i] = "01234567890123456789" end + testamem("auxiliary buffer", function () + return (#table.concat(a, ",") == 20*lim + lim - 1) + end) +end + +testamem("growing stack", function () + local function foo (n) + if n == 0 then return 1 else return 1 + foo(n - 1) end + end + return foo(100) +end) + +-- }================================================================== + + +print "Ok" + + diff --git a/testes/packtests b/testes/packtests index 0dbb92fe5d..855c054a0c 100755 --- a/testes/packtests +++ b/testes/packtests @@ -28,6 +28,7 @@ $NAME/literals.lua \ $NAME/locals.lua \ $NAME/main.lua \ $NAME/math.lua \ +$NAME/memerr.lua \ $NAME/nextvar.lua \ $NAME/pm.lua \ $NAME/sort.lua \ From ab66652b3270b95222dea134b5e47bb3afc434cc Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 12 Mar 2025 14:00:58 -0300 Subject: [PATCH 036/165] Removed copyright notice from 'testes/all.lua' All test files refer to the main copyright notice in 'lua.h'. --- testes/all.lua | 29 +---------------------------- testes/api.lua | 2 +- testes/attrib.lua | 2 +- testes/big.lua | 2 +- testes/bitwise.lua | 2 +- testes/calls.lua | 2 +- testes/closure.lua | 2 +- testes/code.lua | 2 +- testes/constructs.lua | 2 +- testes/coroutine.lua | 2 +- testes/cstack.lua | 2 +- testes/db.lua | 2 +- testes/errors.lua | 2 +- testes/events.lua | 2 +- testes/files.lua | 2 +- testes/gc.lua | 2 +- testes/gengc.lua | 2 +- testes/goto.lua | 2 +- testes/heavy.lua | 4 ++-- testes/literals.lua | 2 +- testes/locals.lua | 2 +- testes/main.lua | 2 +- testes/math.lua | 2 +- testes/memerr.lua | 2 +- testes/nextvar.lua | 2 +- testes/pm.lua | 2 +- testes/sort.lua | 2 +- testes/strings.lua | 2 +- testes/tpack.lua | 2 +- testes/utf8.lua | 2 +- testes/vararg.lua | 2 +- testes/verybig.lua | 2 +- 32 files changed, 33 insertions(+), 60 deletions(-) diff --git a/testes/all.lua b/testes/all.lua index c3fdac957c..5c7ebfa5bf 100644 --- a/testes/all.lua +++ b/testes/all.lua @@ -1,6 +1,6 @@ #!../lua -- $Id: testes/all.lua $ --- See Copyright Notice at the end of this file +-- See Copyright Notice in file lua.h local version = "Lua 5.5" @@ -283,30 +283,3 @@ end print("final OK !!!") - - ---[[ -***************************************************************************** -* Copyright (C) 1994-2016 Lua.org, PUC-Rio. -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -***************************************************************************** -]] - diff --git a/testes/api.lua b/testes/api.lua index ee2de98bdb..49e3f9b987 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -1,5 +1,5 @@ -- $Id: testes/api.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h if T==nil then (Message or print)('\n >>> testC not active: skipping API tests <<<\n') diff --git a/testes/attrib.lua b/testes/attrib.lua index 9054e0b64d..d8b6e0f3f2 100644 --- a/testes/attrib.lua +++ b/testes/attrib.lua @@ -1,5 +1,5 @@ -- $Id: testes/attrib.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print "testing require" diff --git a/testes/big.lua b/testes/big.lua index 46fd846674..119caa6c32 100644 --- a/testes/big.lua +++ b/testes/big.lua @@ -1,5 +1,5 @@ -- $Id: testes/big.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h if _soft then return 'a' diff --git a/testes/bitwise.lua b/testes/bitwise.lua index dd0a1a9a39..10afff432f 100644 --- a/testes/bitwise.lua +++ b/testes/bitwise.lua @@ -1,5 +1,5 @@ -- $Id: testes/bitwise.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print("testing bitwise operations") diff --git a/testes/calls.lua b/testes/calls.lua index 8b35595768..942fad72e0 100644 --- a/testes/calls.lua +++ b/testes/calls.lua @@ -1,5 +1,5 @@ -- $Id: testes/calls.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print("testing functions and calls") diff --git a/testes/closure.lua b/testes/closure.lua index 07149ef36a..d3b9f6216a 100644 --- a/testes/closure.lua +++ b/testes/closure.lua @@ -1,5 +1,5 @@ -- $Id: testes/closure.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print "testing closures" diff --git a/testes/code.lua b/testes/code.lua index 50ce7392f3..111717cefe 100644 --- a/testes/code.lua +++ b/testes/code.lua @@ -1,5 +1,5 @@ -- $Id: testes/code.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h if T==nil then (Message or print)('\n >>> testC not active: skipping opcode tests <<<\n') diff --git a/testes/constructs.lua b/testes/constructs.lua index 6ac6816671..3f6d506f0a 100644 --- a/testes/constructs.lua +++ b/testes/constructs.lua @@ -1,5 +1,5 @@ -- $Id: testes/constructs.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h ;;print "testing syntax";; diff --git a/testes/coroutine.lua b/testes/coroutine.lua index abc08039d2..680fc6058d 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -1,5 +1,5 @@ -- $Id: testes/coroutine.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print "testing coroutines" diff --git a/testes/cstack.lua b/testes/cstack.lua index 97afe9fd03..0a68a30ac2 100644 --- a/testes/cstack.lua +++ b/testes/cstack.lua @@ -1,5 +1,5 @@ -- $Id: testes/cstack.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h local tracegc = require"tracegc" diff --git a/testes/db.lua b/testes/db.lua index 75730d27b0..3c821ab7d5 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -1,5 +1,5 @@ -- $Id: testes/db.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h -- testing debug library diff --git a/testes/errors.lua b/testes/errors.lua index 5fdb772263..8ef267579a 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -1,5 +1,5 @@ -- $Id: testes/errors.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print("testing errors") diff --git a/testes/events.lua b/testes/events.lua index 5360ac301c..2500fbd554 100644 --- a/testes/events.lua +++ b/testes/events.lua @@ -1,5 +1,5 @@ -- $Id: testes/events.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print('testing metatables') diff --git a/testes/files.lua b/testes/files.lua index 9bdf04d09a..05fae49b44 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -1,5 +1,5 @@ -- $Id: testes/files.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h local debug = require "debug" diff --git a/testes/gc.lua b/testes/gc.lua index 09bfe09ab3..96eadad816 100644 --- a/testes/gc.lua +++ b/testes/gc.lua @@ -1,5 +1,5 @@ -- $Id: testes/gc.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print('testing incremental garbage collection') diff --git a/testes/gengc.lua b/testes/gengc.lua index c4f6ca1b71..ea99bdc43a 100644 --- a/testes/gengc.lua +++ b/testes/gengc.lua @@ -1,5 +1,5 @@ -- $Id: testes/gengc.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print('testing generational garbage collection') diff --git a/testes/goto.lua b/testes/goto.lua index 103cccef52..eca6851689 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -1,5 +1,5 @@ -- $Id: testes/goto.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h collectgarbage() diff --git a/testes/heavy.lua b/testes/heavy.lua index 4731c7472f..3b4e4ce352 100644 --- a/testes/heavy.lua +++ b/testes/heavy.lua @@ -1,5 +1,5 @@ --- $Id: heavy.lua,v 1.7 2017/12/29 15:42:15 roberto Exp $ --- See Copyright Notice in file all.lua +-- $Id: testes/heavy.lua,v $ +-- See Copyright Notice in file lua.h local function teststring () print("creating a string too long") diff --git a/testes/literals.lua b/testes/literals.lua index 30ab9ab115..28995718b7 100644 --- a/testes/literals.lua +++ b/testes/literals.lua @@ -1,5 +1,5 @@ -- $Id: testes/literals.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print('testing scanner') diff --git a/testes/locals.lua b/testes/locals.lua index 910deb8a52..ccea0a1422 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -1,5 +1,5 @@ -- $Id: testes/locals.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print('testing local variables and environments') diff --git a/testes/main.lua b/testes/main.lua index e0e9cbe8a4..bf3c898eed 100644 --- a/testes/main.lua +++ b/testes/main.lua @@ -1,6 +1,6 @@ # testing special comment on first line -- $Id: testes/main.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h -- most (all?) tests here assume a reasonable "Unix-like" shell if _port then return end diff --git a/testes/math.lua b/testes/math.lua index 3937b9ce56..bad8bc5e71 100644 --- a/testes/math.lua +++ b/testes/math.lua @@ -1,5 +1,5 @@ -- $Id: testes/math.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print("testing numbers and math lib") diff --git a/testes/memerr.lua b/testes/memerr.lua index cb236eb976..77cb47cb1e 100644 --- a/testes/memerr.lua +++ b/testes/memerr.lua @@ -1,5 +1,5 @@ -- $Id: testes/memerr.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h local function checkerr (msg, f, ...) diff --git a/testes/nextvar.lua b/testes/nextvar.lua index d1da3ceeb3..031ad3fd99 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua @@ -1,5 +1,5 @@ -- $Id: testes/nextvar.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print('testing tables, next, and for') diff --git a/testes/pm.lua b/testes/pm.lua index f5889fcd07..2a0cfb0bb5 100644 --- a/testes/pm.lua +++ b/testes/pm.lua @@ -1,5 +1,5 @@ -- $Id: testes/pm.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h -- UTF-8 file diff --git a/testes/sort.lua b/testes/sort.lua index 290b199ee5..b012766057 100644 --- a/testes/sort.lua +++ b/testes/sort.lua @@ -1,5 +1,5 @@ -- $Id: testes/sort.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print "testing (parts of) table library" diff --git a/testes/strings.lua b/testes/strings.lua index 9bb52b35dd..ce28e4c560 100644 --- a/testes/strings.lua +++ b/testes/strings.lua @@ -1,5 +1,5 @@ -- $Id: testes/strings.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h -- ISO Latin encoding diff --git a/testes/tpack.lua b/testes/tpack.lua index 4b32efb59b..70386178c4 100644 --- a/testes/tpack.lua +++ b/testes/tpack.lua @@ -1,5 +1,5 @@ -- $Id: testes/tpack.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h local pack = string.pack local packsize = string.packsize diff --git a/testes/utf8.lua b/testes/utf8.lua index dc0f2f09d5..0704782c12 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -1,5 +1,5 @@ -- $Id: testes/utf8.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h -- UTF-8 file diff --git a/testes/vararg.lua b/testes/vararg.lua index 1b02510244..10553de2af 100644 --- a/testes/vararg.lua +++ b/testes/vararg.lua @@ -1,5 +1,5 @@ -- $Id: testes/vararg.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print('testing vararg') diff --git a/testes/verybig.lua b/testes/verybig.lua index 250ea79501..8163802c1d 100644 --- a/testes/verybig.lua +++ b/testes/verybig.lua @@ -1,5 +1,5 @@ -- $Id: testes/verybig.lua $ --- See Copyright Notice in file all.lua +-- See Copyright Notice in file lua.h print "testing RK" From d9e0f64a5de699a620771af299ea22f522c72f19 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 12 Mar 2025 15:45:39 -0300 Subject: [PATCH 037/165] Small changes in the manual --- manual/manual.of | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/manual/manual.of b/manual/manual.of index b34e1e9cb7..b698672a08 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2222,10 +2222,10 @@ f(3, 4, 5) a=3, b=4 f(r(), 10) a=1, b=10 f(r()) a=1, b=2 -g(3) a=3, b=nil, ... --> (nothing) -g(3, 4) a=3, b=4, ... --> (nothing) -g(3, 4, 5, 8) a=3, b=4, ... --> 5 8 -g(5, r()) a=5, b=1, ... --> 2 3 +g(3) a=3, b=nil, ... -> (nothing) +g(3, 4) a=3, b=4, ... -> (nothing) +g(3, 4, 5, 8) a=3, b=4, ... -> 5 8 +g(5, r()) a=5, b=1, ... -> 2 3 } Results are returned using the @Rw{return} statement @see{control}. @@ -7477,25 +7477,25 @@ then there is no replacement Here are some examples: @verbatim{ x = string.gsub("hello world", "(%w+)", "%1 %1") ---> x="hello hello world world" +-- x="hello hello world world" x = string.gsub("hello world", "%w+", "%0 %0", 1) ---> x="hello hello world" +-- x="hello hello world" x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") ---> x="world hello Lua from" +-- x="world hello Lua from" x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv) ---> x="home = /home/roberto, user = roberto" +-- x="home = /home/roberto, user = roberto" x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) return load(s)() end) ---> x="4+5 = 9" +-- x="4+5 = 9" local t = {name="lua", version="5.4"} x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) ---> x="lua-5.4.tar.gz" +-- x="lua-5.4.tar.gz" } } @@ -9299,15 +9299,21 @@ the interpreter waits for its completion by issuing a different prompt. Note that, as each complete line is read as a new chunk, -local variables do not outlive lines: +local variables do not outlive lines. +To steer clear of confusion, +the interpreter gives a warning if a line starts with the +reserved word @Rw{local}: @verbatim{ -> x = 20 -> local x = 10; print(x) --> 10 -> print(x) --> 20 -- global 'x' -> do -- incomplete line +> x = 20 -- global 'x' +> local x = 10; print(x) + --> warning: locals do not survive across lines in interactive mode + --> 10 +> print(x) -- back to global 'x' + --> 20 +> do -- incomplete chunk >> local x = 10; print(x) -- '>>' prompts for line completion >> print(x) ->> end -- line completed; Lua will run it as a single chunk +>> end -- chunk completed --> 10 --> 10 } From c931d86e98da320c71da70c16d44aa28e9755520 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 12 Mar 2025 15:51:16 -0300 Subject: [PATCH 038/165] '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). --- ldebug.c | 4 ++++ ldo.c | 36 +++++++++++++++++------------------- ldo.h | 1 + lstate.c | 2 +- testes/errors.lua | 4 ++-- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/ldebug.c b/ldebug.c index 18bdc5959c..a32029815b 100644 --- a/ldebug.c +++ b/ldebug.c @@ -832,6 +832,10 @@ l_noret luaG_errormsg (lua_State *L) { L->top.p++; /* assume EXTRA_STACK */ luaD_callnoyield(L, L->top.p - 2, 1); /* call it */ } + if (ttisnil(s2v(L->top.p - 1))) { /* error object is nil? */ + /* change it to a proper message */ + setsvalue2s(L, L->top.p - 1, luaS_newliteral(L, "")); + } luaD_throw(L, LUA_ERRRUN); } diff --git a/ldo.c b/ldo.c index 84f7bbb2c4..b0d37bf7f3 100644 --- a/ldo.c +++ b/ldo.c @@ -102,24 +102,13 @@ struct lua_longjmp { void luaD_seterrorobj (lua_State *L, TStatus errcode, StkId oldtop) { - switch (errcode) { - case LUA_ERRMEM: { /* memory error? */ - setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ - break; - } - case LUA_ERRERR: { - setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); - break; - } - default: { - lua_assert(errorstatus(errcode)); /* must be a real error */ - if (!ttisnil(s2v(L->top.p - 1))) { /* error object is not nil? */ - setobjs2s(L, oldtop, L->top.p - 1); /* move it to 'oldtop' */ - } - else /* change it to a proper message */ - setsvalue2s(L, oldtop, luaS_newliteral(L, "")); - break; - } + if (errcode == LUA_ERRMEM) { /* memory error? */ + setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ + } + else { + lua_assert(errorstatus(errcode)); /* must be a real error */ + lua_assert(!ttisnil(s2v(L->top.p - 1))); /* with a non-nil object */ + setobjs2s(L, oldtop, L->top.p - 1); /* move it to 'oldtop' */ } L->top.p = oldtop + 1; /* top goes back to old top plus error object */ } @@ -190,6 +179,15 @@ TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { #define ERRORSTACKSIZE (MAXSTACK + STACKERRSPACE) +/* 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); +} + + /* ** In ISO C, any pointer use after the pointer has been deallocated is ** undefined behavior. So, before a stack reallocation, all pointers @@ -317,7 +315,7 @@ int luaD_growstack (lua_State *L, int n, int raiseerror) { a stack error; cannot grow further than that. */ lua_assert(stacksize(L) == ERRORSTACKSIZE); 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 */ } else if (n < MAXSTACK) { /* avoids arithmetic overflows */ diff --git a/ldo.h b/ldo.h index ea1655e1fa..465f4fb8d8 100644 --- a/ldo.h +++ b/ldo.h @@ -67,6 +67,7 @@ /* type of protected functions, to be ran by 'runprotected' */ 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, TStatus errcode, StkId oldtop); LUAI_FUNC TStatus luaD_protectedparser (lua_State *L, ZIO *z, const char *name, diff --git a/lstate.c b/lstate.c index 69ddef40ef..ed5ccaaa32 100644 --- a/lstate.c +++ b/lstate.c @@ -132,7 +132,7 @@ void luaE_checkcstack (lua_State *L) { if (getCcalls(L) == LUAI_MAXCCALLS) luaG_runerror(L, "C stack overflow"); 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 */ } diff --git a/testes/errors.lua b/testes/errors.lua index 8ef267579a..d83e6023b1 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -46,7 +46,7 @@ end assert(doit("error('hi', 0)") == 'hi') -- test nil error message -assert(doit("error()") == "") +assert(doit("error()") == "") -- test common errors/errors that crashed in the past @@ -614,7 +614,7 @@ do assert(not res and msg == t) res, msg = pcall(function () error(nil) end) - assert(not res and msg == "") + assert(not res and msg == "") local function f() error{msg='x'} end res, msg = xpcall(f, function (r) return {msg=r.msg..'y'} end) From 22974326ca0d4f893849ce722cc1d65b3e228f42 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 13 Mar 2025 15:30:52 -0300 Subject: [PATCH 039/165] 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.) --- lapi.c | 5 +++++ lvm.c | 8 ++++++++ testes/events.lua | 13 ++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lapi.c b/lapi.c index a5e94507ac..eab12cac0f 100644 --- a/lapi.c +++ b/lapi.c @@ -681,6 +681,11 @@ static int auxgetstr (lua_State *L, const TValue *t, const char *k) { } +/* +** The following function assumes that the registry cannot be a weak +** table, so that en mergency collection while using the global table +** cannot collect it. +*/ static void getGlobalTable (lua_State *L, TValue *gt) { Table *registry = hvalue(&G(L)->l_registry); lu_byte tag = luaH_getint(registry, LUA_RIDX_GLOBALS, gt); diff --git a/lvm.c b/lvm.c index f0e73f9bb6..af048d8130 100644 --- a/lvm.c +++ b/lvm.c @@ -325,6 +325,11 @@ lu_byte luaV_finishget (lua_State *L, const TValue *t, TValue *key, /* ** Finish a table assignment 't[key] = val'. +** About anchoring the table before the call to 'luaH_finishset': +** This call may trigger an emergency collection. When loop>0, +** the table being acessed is a field in some metatable. If this +** metatable is weak and the table is not anchored, this collection +** could collect that table while it is being updated. */ void luaV_finishset (lua_State *L, const TValue *t, TValue *key, TValue *val, int hres) { @@ -335,7 +340,10 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key, Table *h = hvalue(t); /* save 't' table */ tm = fasttm(L, h->metatable, TM_NEWINDEX); /* get 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, val, hres); /* set new value */ + L->top.p--; invalidateTMcache(h); luaC_barrierback(L, obj2gco(h), val); return; diff --git a/testes/events.lua b/testes/events.lua index 2500fbd554..7e434b1f6f 100644 --- a/testes/events.lua +++ b/testes/events.lua @@ -379,6 +379,17 @@ x = 0 .."a".."b"..c..d.."e".."f".."g" assert(x.val == "0abcdefg") +do + -- bug since 5.4.1 (test needs T) + 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 + -- concat metamethod x numbers (bug in 5.1.1) c = {} local x @@ -481,7 +492,7 @@ assert(not pcall(function (a,b) return a[b] end, a, 10)) assert(not pcall(function (a,b,c) a[b] = c end, a, 10, true)) -- bug in 5.1 -T, K, V = nil +local T, K, V = nil grandparent = {} grandparent.__newindex = function(t,k,v) T=t; K=k; V=v end From c2dc6e8e947ed0c7b18d452592f722f56ee1f96a Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 14 Mar 2025 12:37:19 -0300 Subject: [PATCH 040/165] Missing GC barrier in 'luaV_finishset' --- lapi.c | 3 +-- lvm.c | 4 +++- testes/gc.lua | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lapi.c b/lapi.c index eab12cac0f..f59430a7f3 100644 --- a/lapi.c +++ b/lapi.c @@ -888,9 +888,8 @@ LUA_API void lua_settable (lua_State *L, int idx) { api_checkpop(L, 2); t = index2value(L, idx); luaV_fastset(t, s2v(L->top.p - 2), s2v(L->top.p - 1), hres, luaH_pset); - if (hres == HOK) { + if (hres == HOK) luaV_finishfastset(L, t, s2v(L->top.p - 1)); - } else luaV_finishset(L, t, s2v(L->top.p - 2), s2v(L->top.p - 1), hres); L->top.p -= 2; /* pop index and value */ diff --git a/lvm.c b/lvm.c index af048d8130..f14397d46e 100644 --- a/lvm.c +++ b/lvm.c @@ -362,8 +362,10 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key, } t = tm; /* else repeat assignment over 'tm' */ luaV_fastset(t, key, val, hres, luaH_pset); - if (hres == HOK) + if (hres == HOK) { + luaV_finishfastset(L, t, val); return; /* done */ + } /* else 'return luaV_finishset(L, t, key, val, slot)' (loop) */ } luaG_runerror(L, "'__newindex' chain too long; possible loop"); diff --git a/testes/gc.lua b/testes/gc.lua index 96eadad816..0693837c35 100644 --- a/testes/gc.lua +++ b/testes/gc.lua @@ -600,6 +600,21 @@ if T then end +if T then + collectgarbage("stop") + T.gcstate("pause") + local sup = {x = 0} + local a = setmetatable({}, {__newindex = sup}) + T.gcstate("enteratomic") + assert(T.gccolor(sup) == "black") + a.x = {} -- should not break the invariant + assert(not (T.gccolor(sup) == "black" and T.gccolor(sup.x) == "white")) + T.gcstate("pause") -- complete the GC cycle + sup.x.y = 10 + collectgarbage("restart") +end + + if T then print("emergency collections") collectgarbage() From 94d38560c3095190fa2c868cbf7bcf39ca444568 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 14 Mar 2025 15:16:09 -0300 Subject: [PATCH 041/165] Wrong error message when using "_ENV" fields The string "_ENV" is erroneously identified as a variable _ENV, so that results from a field is classified as a global. --- ldebug.c | 17 ++++++++++++----- ltests.c | 2 +- testes/errors.lua | 3 +++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ldebug.c b/ldebug.c index a32029815b..258a439478 100644 --- a/ldebug.c +++ b/ldebug.c @@ -33,6 +33,8 @@ #define LuaClosure(f) ((f) != NULL && (f)->c.tt == LUA_VLCL) +static const char strlocal[] = "local"; +static const char strupval[] = "upvalue"; static const char *funcnamefromcall (lua_State *L, CallInfo *ci, const char **name); @@ -505,7 +507,7 @@ static const char *basicgetobjname (const Proto *p, int *ppc, int reg, int pc = *ppc; *name = luaF_getlocalname(p, reg + 1, pc); if (*name) /* is a local? */ - return "local"; + return strlocal; /* else try symbolic execution */ *ppc = pc = findsetreg(p, pc, reg); if (pc != -1) { /* could find instruction? */ @@ -520,7 +522,7 @@ static const char *basicgetobjname (const Proto *p, int *ppc, int reg, } case OP_GETUPVAL: { *name = upvalname(p, GETARG_B(i)); - return "upvalue"; + return strupval; } case OP_LOADK: return kname(p, GETARG_Bx(i), name); case OP_LOADKX: return kname(p, GETARG_Ax(p->code[pc + 1]), name); @@ -550,8 +552,13 @@ static const char *isEnv (const Proto *p, int pc, Instruction i, int isup) { const char *name; /* name of indexed variable */ if (isup) /* is 't' an upvalue? */ name = upvalname(p, t); - else /* 't' is a register */ - basicgetobjname(p, &pc, t, &name); + else { /* 't' is a register */ + const char *what = basicgetobjname(p, &pc, t, &name); + /* 'name' must be the name of a local variable (at the current + level or an upvalue) */ + if (what != strlocal && what != strupval) + name = NULL; /* cannot be the variable _ENV */ + } return (name && strcmp(name, LUA_ENV) == 0) ? "global" : "field"; } @@ -698,7 +705,7 @@ static const char *getupvalname (CallInfo *ci, const TValue *o, for (i = 0; i < c->nupvalues; i++) { if (c->upvals[i]->v.p == o) { *name = upvalname(c->p, i); - return "upvalue"; + return strupval; } } return NULL; diff --git a/ltests.c b/ltests.c index f4855fea95..d3509862d9 100644 --- a/ltests.c +++ b/ltests.c @@ -982,7 +982,7 @@ static int gc_printobj (lua_State *L) { } -static const char *statenames[] = { +static const char *const statenames[] = { "propagate", "enteratomic", "atomic", "sweepallgc", "sweepfinobj", "sweeptobefnz", "sweepend", "callfin", "pause", ""}; diff --git a/testes/errors.lua b/testes/errors.lua index d83e6023b1..c1c40fecdf 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -162,6 +162,9 @@ checkmessage("aaa=(1)..{}", "a table value") -- bug in 5.4.6 checkmessage("a = {_ENV = {}}; print(a._ENV.x + 1)", "field 'x'") +-- a similar bug, since 5.4.0 +checkmessage("print(('_ENV').x + 1)", "field 'x'") + _G.aaa, _G.bbbb = nil -- calls From 205f9aa67b43b3d9b5059769cfc1ed0265341586 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 17 Mar 2025 14:30:43 -0300 Subject: [PATCH 042/165] New function 'printallstack' in test library --- ltests.c | 23 +++++++++++++++++++++++ ltests.h | 1 + 2 files changed, 24 insertions(+) diff --git a/ltests.c b/ltests.c index d3509862d9..5b4a600037 100644 --- a/ltests.c +++ b/ltests.c @@ -872,6 +872,28 @@ void lua_printstack (lua_State *L) { } +int lua_printallstack (lua_State *L) { + StkId p; + int i = 1; + CallInfo *ci = &L->base_ci; + printf("stack: >>\n"); + for (p = L->stack.p; p < L->top.p; p++) { + if (ci != NULL && p == ci->func.p) { + printf(" ---\n"); + if (ci == L->ci) + ci = NULL; /* printed last frame */ + else + ci = ci->next; + } + printf("%3d: ", i++); + lua_printvalue(s2v(p)); + printf("\n"); + } + printf("<<\n"); + return 0; +} + + static int get_limits (lua_State *L) { lua_createtable(L, 0, 5); setnameval(L, "IS32INT", LUAI_IS32INT); @@ -2102,6 +2124,7 @@ static const struct luaL_Reg tests_funcs[] = { {"limits", get_limits}, {"listcode", listcode}, {"printcode", printcode}, + {"printallstack", lua_printallstack}, {"listk", listk}, {"listabslineinfo", listabslineinfo}, {"listlocals", listlocals}, diff --git a/ltests.h b/ltests.h index cc372b8f4b..af5641ba8b 100644 --- a/ltests.h +++ b/ltests.h @@ -94,6 +94,7 @@ LUAI_FUNC void lua_printvalue (struct TValue *v); ** Function to print the stack */ LUAI_FUNC void lua_printstack (lua_State *L); +LUAI_FUNC int lua_printallstack (lua_State *L); /* test for lock/unlock */ From 921832be8d7f687d2cd891654c8680c6e9d6584c Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 17 Mar 2025 14:32:08 -0300 Subject: [PATCH 043/165] New function 'resetCI' New function 'resetCI' resets the CallInfo list of a thread, ensuring a proper state when creating a new thread, closing a thread, or closing a state, so that we can run code after that. (When closing a thread, we need to run its __close metamethods; when closing a state, we need to run its __close metamethods and its finalizers.) --- lstate.c | 39 ++++++++++++++++++++------------------- testes/coroutine.lua | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/lstate.c b/lstate.c index ed5ccaaa32..20ed838f42 100644 --- a/lstate.c +++ b/lstate.c @@ -143,25 +143,29 @@ LUAI_FUNC void luaE_incCstack (lua_State *L) { } +static void resetCI (lua_State *L) { + CallInfo *ci = L->ci = &L->base_ci; + ci->func.p = L->stack.p; + setnilvalue(s2v(ci->func.p)); /* 'function' entry for basic 'ci' */ + ci->top.p = ci->func.p + 1 + LUA_MINSTACK; /* +1 for 'function' entry */ + ci->u.c.k = NULL; + ci->callstatus = CIST_C; + L->status = LUA_OK; + L->errfunc = 0; /* stack unwind can "throw away" the error function */ +} + + static void stack_init (lua_State *L1, lua_State *L) { - int i; CallInfo *ci; + int i; /* initialize stack array */ L1->stack.p = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, StackValue); L1->tbclist.p = L1->stack.p; for (i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) setnilvalue(s2v(L1->stack.p + i)); /* erase new stack */ - L1->top.p = L1->stack.p; L1->stack_last.p = L1->stack.p + BASIC_STACK_SIZE; /* initialize first ci */ - ci = &L1->base_ci; - ci->next = ci->previous = NULL; - ci->callstatus = CIST_C; - ci->func.p = L1->top.p; - ci->u.c.k = NULL; - setnilvalue(s2v(L1->top.p)); /* 'function' entry for this 'ci' */ - L1->top.p++; - ci->top.p = L1->top.p + LUA_MINSTACK; - L1->ci = ci; + resetCI(L1); + L1->top.p = L1->stack.p + 1; /* +1 for 'function' entry */ } @@ -235,6 +239,7 @@ static void preinit_thread (lua_State *L, global_State *g) { L->status = LUA_OK; L->errfunc = 0; L->oldpc = 0; + L->base_ci.previous = L->base_ci.next = NULL; } @@ -252,8 +257,9 @@ static void close_state (lua_State *L) { if (!completestate(g)) /* closing a partially built state? */ luaC_freeallobjects(L); /* just collect its objects */ else { /* closing a fully built state */ - L->ci = &L->base_ci; /* unwind CallInfo list */ + resetCI(L); 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 */ luai_userstateclose(L); } @@ -302,20 +308,15 @@ void luaE_freethread (lua_State *L, lua_State *L1) { TStatus luaE_resetthread (lua_State *L, TStatus status) { - CallInfo *ci = L->ci = &L->base_ci; /* unwind CallInfo list */ - setnilvalue(s2v(L->stack.p)); /* 'function' entry for basic 'ci' */ - ci->func.p = L->stack.p; - ci->callstatus = CIST_C; + resetCI(L); if (status == LUA_YIELD) status = LUA_OK; - L->status = LUA_OK; /* so it can run __close metamethods */ status = luaD_closeprotected(L, 1, status); if (status != LUA_OK) /* errors? */ luaD_seterrorobj(L, status, L->stack.p + 1); else L->top.p = L->stack.p + 1; - ci->top.p = L->top.p + LUA_MINSTACK; - luaD_reallocstack(L, cast_int(ci->top.p - L->stack.p), 0); + luaD_reallocstack(L, cast_int(L->ci->top.p - L->stack.p), 0); return status; } diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 680fc6058d..17f6cebaae 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -505,6 +505,25 @@ assert(not pcall(a, a)) 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 = 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 local x = coroutine.create (function () local a = 10 From fdbb4c23414cef141602a45ae8464f0553085e02 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 17 Mar 2025 16:05:47 -0300 Subject: [PATCH 044/165] Detail in the manual --- manual/manual.of | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/manual/manual.of b/manual/manual.of index b698672a08..c1a9c138d7 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -6230,15 +6230,17 @@ Returns the name of the type of the value at the given index. @APIEntry{void luaL_unref (lua_State *L, int t, int ref);| @apii{0,0,-} -Releases the reference @id{ref} from the table at index @id{t} -@seeC{luaL_ref}. -The entry is removed from the table, +Releases a reference @see{luaL_ref}. +The integer @id{ref} must be either +@Lid{LUA_NOREF}, @Lid{LUA_REFNIL}, +or a reference previously returned by @Lid{luaL_ref} +and not already released. +If @id{ref} is either @Lid{LUA_NOREF} or @Lid{LUA_REFNIL} +this function does nothing. +Otherwise, the entry is removed from the table, so that the referred object can be collected and the reference @id{ref} can be used again by @Lid{luaL_ref}. -If @id{ref} is @Lid{LUA_NOREF} or @Lid{LUA_REFNIL}, -@Lid{luaL_unref} does nothing. - } @APIEntry{void luaL_where (lua_State *L, int lvl);| From cad5a4fdbb0f0843ec67596d1e472187decf1c88 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 24 Mar 2025 15:23:55 -0300 Subject: [PATCH 045/165] Details Small changes in test library: - execute mode added to 'all.lua'; - more information about subtypes (tags) when printing a stack. --- ltests.c | 53 ++++++++++++++++++++++++++------------------------ testes/all.lua | 0 2 files changed, 28 insertions(+), 25 deletions(-) mode change 100644 => 100755 testes/all.lua diff --git a/ltests.c b/ltests.c index 5b4a600037..6a638d051b 100644 --- a/ltests.c +++ b/ltests.c @@ -327,37 +327,40 @@ void lua_printobj (lua_State *L, struct GCObject *o) { void lua_printvalue (TValue *v) { - switch (ttype(v)) { - case LUA_TNUMBER: { + switch (ttypetag(v)) { + case LUA_VNUMINT: case LUA_VNUMFLT: { char buff[LUA_N2SBUFFSZ]; unsigned len = luaO_tostringbuff(v, buff); buff[len] = '\0'; printf("%s", buff); break; } - case LUA_TSTRING: { - printf("'%s'", getstr(tsvalue(v))); - break; - } - case LUA_TBOOLEAN: { - printf("%s", (!l_isfalse(v) ? "true" : "false")); - break; - } - case LUA_TLIGHTUSERDATA: { - printf("light udata: %p", pvalue(v)); - break; - } - case LUA_TNIL: { - printf("nil"); - break; - } - default: { - if (ttislcf(v)) - printf("light C function: %p", fvalue(v)); - else /* must be collectable */ - printf("%s: %p", ttypename(ttype(v)), gcvalue(v)); - break; - } + case LUA_VSHRSTR: + printf("'%s'", getstr(tsvalue(v))); break; + case LUA_VLNGSTR: + printf("'%.30s...'", getstr(tsvalue(v))); break; + case LUA_VFALSE: + printf("%s", "false"); break; + case LUA_VTRUE: + printf("%s", "true"); break; + case LUA_VLIGHTUSERDATA: + printf("light udata: %p", pvalue(v)); break; + case LUA_VUSERDATA: + printf("full udata: %p", uvalue(v)); break; + case LUA_VNIL: + printf("nil"); break; + case LUA_VLCF: + printf("light C function: %p", fvalue(v)); break; + case LUA_VCCL: + printf("C closure: %p", clCvalue(v)); break; + case LUA_VLCL: + printf("Lua function: %p", clLvalue(v)); break; + case LUA_VTHREAD: + printf("thread: %p", thvalue(v)); break; + case LUA_VTABLE: + printf("table: %p", hvalue(v)); break; + default: + lua_assert(0); } } diff --git a/testes/all.lua b/testes/all.lua old mode 100644 new mode 100755 From b0f3df16a495745cf16657a48dde6845ec85c732 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 25 Mar 2025 11:43:03 -0300 Subject: [PATCH 046/165] Addition in math.random can overflow To avoid complains from some tools, the addition when computing math.random(n,m), which is computed as n + random(0, m - n), should use unsigned integers. --- lmathlib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lmathlib.c b/lmathlib.c index c7418e69ec..a098177235 100644 --- a/lmathlib.c +++ b/lmathlib.c @@ -593,8 +593,8 @@ static int math_random (lua_State *L) { /* random integer in the interval [low, up] */ luaL_argcheck(L, low <= up, 1, "interval is empty"); /* project random integer into the interval [0, up - low] */ - p = project(I2UInt(rv), (lua_Unsigned)up - (lua_Unsigned)low, state); - lua_pushinteger(L, l_castU2S(p) + low); + p = project(I2UInt(rv), l_castS2U(up) - l_castS2U(low), state); + lua_pushinteger(L, l_castU2S(p + l_castS2U(low))); return 1; } From ef5d171cc89b19ac1fea905b99d819b5f97cba00 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 27 Mar 2025 12:38:29 -0300 Subject: [PATCH 047/165] New macro 'l_numbits' --- ldump.c | 2 +- lfunc.c | 9 ++------- llimits.h | 6 ++++-- ltable.c | 2 +- ltests.h | 3 --- lvm.c | 2 +- 6 files changed, 9 insertions(+), 15 deletions(-) diff --git a/ldump.c b/ldump.c index 54f96674e1..d8fca317f0 100644 --- a/ldump.c +++ b/ldump.c @@ -87,7 +87,7 @@ static void dumpByte (DumpState *D, int y) { ** size for 'dumpVarint' buffer: each byte can store up to 7 bits. ** (The "+6" rounds up the division.) */ -#define DIBS ((sizeof(size_t) * CHAR_BIT + 6) / 7) +#define DIBS ((l_numbits(size_t) + 6) / 7) /* ** Dumps an unsigned integer using the MSB Varint encoding diff --git a/lfunc.c b/lfunc.c index c62a5d2395..da7c623974 100644 --- a/lfunc.c +++ b/lfunc.c @@ -162,13 +162,8 @@ static void prepcallclosemth (lua_State *L, StkId level, TStatus status, } -/* -** Maximum value for deltas in 'tbclist', dependent on the type -** of delta. (This macro assumes that an 'L' is in scope where it -** is used.) -*/ -#define MAXDELTA \ - ((256ul << ((sizeof(L->stack.p->tbclist.delta) - 1) * 8)) - 1) +/* Maximum value for deltas in 'tbclist' */ +#define MAXDELTA USHRT_MAX /* diff --git a/llimits.h b/llimits.h index d206e9e1cd..710dc1b440 100644 --- a/llimits.h +++ b/llimits.h @@ -15,6 +15,8 @@ #include "lua.h" +#define l_numbits(t) cast_int(sizeof(t) * CHAR_BIT) + /* ** 'l_mem' is a signed integer big enough to count the total memory ** used by Lua. (It is signed due to the use of debt in several @@ -33,7 +35,7 @@ typedef unsigned long lu_mem; #endif /* } */ #define MAX_LMEM \ - cast(l_mem, (cast(lu_mem, 1) << (sizeof(l_mem) * 8 - 1)) - 1) + cast(l_mem, (cast(lu_mem, 1) << (l_numbits(l_mem) - 1)) - 1) /* chars used as small naturals (so that 'char' is reserved for characters) */ @@ -61,7 +63,7 @@ typedef lu_byte TStatus; ** floor of the log2 of the maximum signed value for integral type 't'. ** (That is, maximum 'n' such that '2^n' fits in the given signed type.) */ -#define log2maxs(t) cast_int(sizeof(t) * 8 - 2) +#define log2maxs(t) (l_numbits(t) - 2) /* diff --git a/ltable.c b/ltable.c index 8df9a4fbfe..0b3ec1762c 100644 --- a/ltable.c +++ b/ltable.c @@ -67,7 +67,7 @@ typedef union { ** MAXABITS is the largest integer such that 2^MAXABITS fits in an ** unsigned int. */ -#define MAXABITS cast_int(sizeof(int) * CHAR_BIT - 1) +#define MAXABITS (l_numbits(int) - 1) /* diff --git a/ltests.h b/ltests.h index af5641ba8b..3420516763 100644 --- a/ltests.h +++ b/ltests.h @@ -142,9 +142,6 @@ LUA_API void *debug_realloc (void *ud, void *block, #define STRCACHE_N 23 #define STRCACHE_M 5 -#undef LUAI_USER_ALIGNMENT_T -#define LUAI_USER_ALIGNMENT_T union { char b[sizeof(void*) * 8]; } - /* ** This one is not compatible with tests for opcode optimizations, diff --git a/lvm.c b/lvm.c index f14397d46e..e2c36ef577 100644 --- a/lvm.c +++ b/lvm.c @@ -774,7 +774,7 @@ lua_Number luaV_modf (lua_State *L, lua_Number m, lua_Number n) { /* number of bits in an integer */ -#define NBITS cast_int(sizeof(lua_Integer) * CHAR_BIT) +#define NBITS l_numbits(lua_Integer) /* From 37a1b72706b6e55e60b8d73bcbe269921976825e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 27 Mar 2025 15:22:40 -0300 Subject: [PATCH 048/165] Small optimization in 'project' from math.random When computing the Mersenne number, instead of spreading 1's a fixed number of times (with shifts of 1, 2, 4, 8, 16, and 32), spread only until the number becomes a Mersenne number. --- lmathlib.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/lmathlib.c b/lmathlib.c index a098177235..bd34c88860 100644 --- a/lmathlib.c +++ b/lmathlib.c @@ -533,7 +533,7 @@ typedef struct { ** Project the random integer 'ran' into the interval [0, n]. ** Because 'ran' has 2^B possible values, the projection can only be ** uniform when the size of the interval is a power of 2 (exact -** division). Otherwise, to get a uniform projection into [0, n], we +** division). So, to get a uniform projection into [0, n], we ** first compute 'lim', the smallest Mersenne number not smaller than ** 'n'. We then project 'ran' into the interval [0, lim]. If the result ** is inside [0, n], we are done. Otherwise, we try with another 'ran', @@ -541,26 +541,14 @@ typedef struct { */ static lua_Unsigned project (lua_Unsigned ran, lua_Unsigned n, RanState *state) { - if ((n & (n + 1)) == 0) /* is 'n + 1' a power of 2? */ - return ran & n; /* no bias */ - else { - lua_Unsigned lim = n; - /* compute the smallest (2^b - 1) not smaller than 'n' */ - lim |= (lim >> 1); - lim |= (lim >> 2); - lim |= (lim >> 4); - lim |= (lim >> 8); - lim |= (lim >> 16); -#if (LUA_MAXUNSIGNED >> 31) >= 3 - lim |= (lim >> 32); /* integer type has more than 32 bits */ -#endif - lua_assert((lim & (lim + 1)) == 0 /* 'lim + 1' is a power of 2, */ - && lim >= n /* not smaller than 'n', */ - && (lim >> 1) < n); /* and it is the smallest one */ - while ((ran &= lim) > n) /* project 'ran' into [0..lim] */ - ran = I2UInt(nextrand(state->s)); /* not inside [0..n]? try again */ - return ran; - } + lua_Unsigned lim = n; /* to compute the Mersenne number */ + int sh; /* how much to spread bits to the right in 'lim' */ + /* spread '1' bits in 'lim' until it becomes a Mersenne number */ + for (sh = 1; (lim & (lim + 1)) != 0; sh *= 2) + lim |= (lim >> sh); /* spread '1's to the right */ + while ((ran &= lim) > n) /* project 'ran' into [0..lim] and test */ + ran = I2UInt(nextrand(state->s)); /* not inside [0..n]? try again */ + return ran; } From f4123b2fc2a662c08e3d7edc721241c251a22c4b Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 31 Mar 2025 13:44:41 -0300 Subject: [PATCH 049/165] Growth factor of 1.5 for stack and lexical buffer --- lauxlib.c | 14 +++++++------- ldo.c | 2 +- llex.c | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lauxlib.c b/lauxlib.c index 5bca18166d..7c9ad53b2b 100644 --- a/lauxlib.c +++ b/lauxlib.c @@ -541,17 +541,17 @@ static void newbox (lua_State *L) { /* ** Compute new size for buffer 'B', enough to accommodate extra 'sz' -** bytes plus one for a terminating zero. (The test for "not big enough" -** also gets the case when the computation of 'newsize' overflows.) +** bytes plus one for a terminating zero. */ static size_t newbuffsize (luaL_Buffer *B, size_t sz) { - size_t newsize = (B->size / 2) * 3; /* buffer size * 1.5 */ - if (l_unlikely(sz > MAX_SIZE - B->n - 1)) + size_t newsize = B->size; + if (l_unlikely(sz >= MAX_SIZE - B->n)) return cast_sizet(luaL_error(B->L, "resulting string too large")); - if (newsize < B->n + sz + 1 || newsize > MAX_SIZE) { - /* newsize was not big enough or too big */ + /* else B->n + sz + 1 <= MAX_SIZE */ + if (newsize <= MAX_SIZE/3 * 2) /* no overflow? */ + newsize += (newsize >> 1); /* new size *= 1.5 */ + if (newsize < B->n + sz + 1) /* not big enough? */ newsize = B->n + sz + 1; - } return newsize; } diff --git a/ldo.c b/ldo.c index b0d37bf7f3..6824a21fa6 100644 --- a/ldo.c +++ b/ldo.c @@ -319,7 +319,7 @@ int luaD_growstack (lua_State *L, int n, int raiseerror) { return 0; /* if not 'raiseerror', just signal it */ } else if (n < MAXSTACK) { /* avoids arithmetic overflows */ - int newsize = 2 * size; /* tentative new size */ + int newsize = size + (size >> 1); /* tentative new size (size * 1.5) */ int needed = cast_int(L->top.p - L->stack.p) + n; if (newsize > MAXSTACK) /* cannot cross the limit */ newsize = MAXSTACK; diff --git a/llex.c b/llex.c index 1c4227ca4c..4b5a1f7509 100644 --- a/llex.c +++ b/llex.c @@ -62,10 +62,10 @@ static l_noret lexerror (LexState *ls, const char *msg, int token); static void save (LexState *ls, int c) { Mbuffer *b = ls->buff; if (luaZ_bufflen(b) + 1 > luaZ_sizebuffer(b)) { - size_t newsize; - if (luaZ_sizebuffer(b) >= MAX_SIZE/2) + size_t newsize = luaZ_sizebuffer(b); /* get old size */; + if (newsize >= (MAX_SIZE/3 * 2)) /* larger than MAX_SIZE/1.5 ? */ lexerror(ls, "lexical element too long", 0); - newsize = luaZ_sizebuffer(b) * 2; + newsize += (newsize >> 1); /* new size is 1.5 times the old one */ luaZ_resizebuffer(ls->L, b, newsize); } b->buffer[luaZ_bufflen(b)++] = cast_char(c); From 93e347b51923a3f0b993aac37c74e1489c02f3b5 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 1 Apr 2025 10:41:25 -0300 Subject: [PATCH 050/165] Corrections of stack addresses back to strict mode It can be a little slower, but only for quite large stacks and moreover stack reallocation is not a common operation. With no strong contrary reason, it is better to follow the standard. --- ldo.c | 2 +- ltests.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ldo.c b/ldo.c index 6824a21fa6..3e5c7504f4 100644 --- a/ldo.c +++ b/ldo.c @@ -199,7 +199,7 @@ l_noret luaD_errerr (lua_State *L) { ** The following macro chooses how strict is the code. */ #if !defined(LUAI_STRICT_ADDRESS) -#define LUAI_STRICT_ADDRESS 0 +#define LUAI_STRICT_ADDRESS 1 #endif #if LUAI_STRICT_ADDRESS diff --git a/ltests.h b/ltests.h index 3420516763..7f0ce4041d 100644 --- a/ltests.h +++ b/ltests.h @@ -44,8 +44,8 @@ #define LUA_RAND32 -/* test stack reallocation with strict address use */ -#define LUAI_STRICT_ADDRESS 1 +/* test stack reallocation without strict address use */ +#define LUAI_STRICT_ADDRESS 0 /* memory-allocator control variables */ From 3f4f28010aa5065456f1edf97de1ab268cc49944 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 3 Apr 2025 11:32:49 -0300 Subject: [PATCH 051/165] io.write returns number of written bytes on error --- liolib.c | 18 +++++++++++------- ltests.c | 20 ++++++++++++++++++++ manual/manual.of | 3 +++ testes/files.lua | 31 +++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/liolib.c b/liolib.c index a0988db06a..3225ed5f2c 100644 --- a/liolib.c +++ b/liolib.c @@ -662,11 +662,12 @@ static int io_readline (lua_State *L) { static int g_write (lua_State *L, FILE *f, int arg) { int nargs = lua_gettop(L) - arg; - int status = 1; + size_t totalbytes = 0; /* total number of bytes written */ errno = 0; - for (; nargs--; arg++) { + for (; nargs--; arg++) { /* for each argument */ char buff[LUA_N2SBUFFSZ]; const char *s; + size_t numbytes; /* bytes written in one call to 'fwrite' */ size_t len = lua_numbertocstring(L, arg, buff); /* try as a number */ if (len > 0) { /* did conversion work (value was a number)? */ s = buff; @@ -674,12 +675,15 @@ static int g_write (lua_State *L, FILE *f, int arg) { } else /* must be a string */ s = luaL_checklstring(L, arg, &len); - status = status && (fwrite(s, sizeof(char), len, f) == len); + numbytes = fwrite(s, sizeof(char), len, f); + totalbytes += numbytes; + if (numbytes < len) { /* write error? */ + int n = luaL_fileresult(L, 0, NULL); + lua_pushinteger(L, cast_st2S(totalbytes)); + return n + 1; /* return fail, error msg., error code, and counter */ + } } - if (l_likely(status)) - return 1; /* file handle already on stack top */ - else - return luaL_fileresult(L, status, NULL); + return 1; /* no errors; file handle already on stack top */ } diff --git a/ltests.c b/ltests.c index 6a638d051b..1517aa88f6 100644 --- a/ltests.c +++ b/ltests.c @@ -2106,6 +2106,25 @@ static int coresume (lua_State *L) { } } +#if !defined(LUA_USE_POSIX) + +#define nonblock NULL + +#else + +#include +#include + +static int nonblock (lua_State *L) { + FILE *f = cast(luaL_Stream*, luaL_checkudata(L, 1, LUA_FILEHANDLE))->f; + int fd = fileno(f); + int flags = fcntl(fd, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(fd, F_SETFL, flags); + return 0; +} +#endif + /* }====================================================== */ @@ -2159,6 +2178,7 @@ static const struct luaL_Reg tests_funcs[] = { {"upvalue", upvalue}, {"externKstr", externKstr}, {"externstr", externstr}, + {"nonblock", nonblock}, {NULL, NULL} }; diff --git a/manual/manual.of b/manual/manual.of index c1a9c138d7..7cd0d4dbde 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -8699,6 +8699,9 @@ Writes the value of each of its arguments to @id{file}. The arguments must be strings or numbers. In case of success, this function returns @id{file}. +Otherwise, it returns four values: +@fail, the error message, the error code, +and the number of bytes it was able to write. } diff --git a/testes/files.lua b/testes/files.lua index 05fae49b44..2c802047df 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -696,6 +696,37 @@ do end +if T and T.nonblock then + print("testing failed write") + + -- unable to write anything to /dev/full + local f = io.open("/dev/full", "w") + assert(f:setvbuf("no")) + local _, _, err, count = f:write("abcd") + assert(err > 0 and count == 0) + assert(f:close()) + + -- receiver will read a "few" bytes (enough to empty a large buffer) + local receiver = [[ + lua -e 'assert(io.stdin:setvbuf("no")); assert(#io.read(1e4) == 1e4)' ]] + + local f = io.popen(receiver, "w") + assert(f:setvbuf("no")) + T.nonblock(f) + + -- able to write a few bytes + assert(f:write(string.rep("a", 1e2))) + + -- Unable to write more bytes than the pipe buffer supports. + -- (In Linux, the pipe buffer size is 64K (2^16). Posix requires at + -- least 512 bytes.) + local _, _, err, count = f:write("abcd", string.rep("a", 2^17)) + assert(err > 0 and count >= 512 and count < 2^17) + + assert(f:close()) +end + + if not _soft then print("testing large files (> BUFSIZ)") io.output(file) From 620f49a7aae8a5c982b21f0accbf2ff9019a55f6 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 3 Apr 2025 12:56:52 -0300 Subject: [PATCH 052/165] Tiny refactoring in io.flush --- liolib.c | 13 +++++++------ testes/files.lua | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/liolib.c b/liolib.c index 3225ed5f2c..c8f165cb05 100644 --- a/liolib.c +++ b/liolib.c @@ -732,18 +732,19 @@ static int f_setvbuf (lua_State *L) { } - -static int io_flush (lua_State *L) { - FILE *f = getiofile(L, IO_OUTPUT); +static int aux_flush (lua_State *L, FILE *f) { errno = 0; return luaL_fileresult(L, fflush(f) == 0, NULL); } static int f_flush (lua_State *L) { - FILE *f = tofile(L); - errno = 0; - return luaL_fileresult(L, fflush(f) == 0, NULL); + return aux_flush(L, tofile(L)); +} + + +static int io_flush (lua_State *L) { + return aux_flush(L, getiofile(L, IO_OUTPUT)); } diff --git a/testes/files.lua b/testes/files.lua index 2c802047df..53edf3145f 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -347,7 +347,7 @@ collectgarbage() assert(io.write(' ' .. t .. ' ')) assert(io.write(';', 'end of file\n')) -f:flush(); io.flush() +assert(f:flush()); assert(io.flush()) f:close() print('+') @@ -461,6 +461,23 @@ do -- testing closing file in line iteration end +do print("testing flush") + local f = io.output("/dev/null") + assert(f:write("abcd")) -- write to buffer + assert(f:flush()) -- write to device + assert(f:write("abcd")) -- write to buffer + assert(io.flush()) -- write to device + assert(f:close()) + + local f = io.output("/dev/full") + assert(f:write("abcd")) -- write to buffer + assert(not f:flush()) -- cannot write to device + assert(f:write("abcd")) -- write to buffer + assert(not io.flush()) -- cannot write to device + assert(f:close()) +end + + -- test for multipe arguments in 'lines' io.output(file); io.write"0123456789\n":close() for a,b in io.lines(file, 1, 1) do From 3dd8ea54daa77345a8f193e871f6792722d8e131 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 3 Apr 2025 15:31:22 -0300 Subject: [PATCH 053/165] Order change in 'pushfuncname' 'pushglobalfuncname' can be quite slow (as it traverses all globals and all loaded modules), so try first to get a name from the code. --- lauxlib.c | 10 +++++----- testes/db.lua | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lauxlib.c b/lauxlib.c index 7c9ad53b2b..7f33f0addb 100644 --- a/lauxlib.c +++ b/lauxlib.c @@ -94,14 +94,14 @@ static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { static void pushfuncname (lua_State *L, lua_Debug *ar) { - if (pushglobalfuncname(L, ar)) { /* try first a global name */ - lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); - lua_remove(L, -2); /* remove name */ - } - else if (*ar->namewhat != '\0') /* is there a name from code? */ + if (*ar->namewhat != '\0') /* is there a name from code? */ lua_pushfstring(L, "%s '%s'", ar->namewhat, ar->name); /* use it */ else if (*ar->what == 'm') /* main? */ lua_pushliteral(L, "main chunk"); + else if (pushglobalfuncname(L, ar)) { /* try a global name */ + lua_pushfstring(L, "function '%s'", lua_tostring(L, -1)); + lua_remove(L, -2); /* remove name */ + } else if (*ar->what != 'C') /* for Lua functions, use */ lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined); else /* nothing left... */ diff --git a/testes/db.lua b/testes/db.lua index 3c821ab7d5..8e13373c2d 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -701,7 +701,7 @@ assert(debug.traceback(print, 4) == print) assert(string.find(debug.traceback("hi", 4), "^hi\n")) assert(string.find(debug.traceback("hi"), "^hi\n")) assert(not string.find(debug.traceback("hi"), "'debug.traceback'")) -assert(string.find(debug.traceback("hi", 0), "'debug.traceback'")) +assert(string.find(debug.traceback("hi", 0), "'traceback'")) assert(string.find(debug.traceback(), "^stack traceback:\n")) do -- C-function names in traceback @@ -829,7 +829,7 @@ end co = coroutine.create(function (x) f(x) end) a, b = coroutine.resume(co, 3) -t = {"'coroutine.yield'", "'f'", "in function <"} +t = {"'yield'", "'f'", "in function <"} while coroutine.status(co) == "suspended" do checktraceback(co, t) a, b = coroutine.resume(co) From 3dbb1a4b894c0744a331d4319d8d1704dc4ad943 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 15 Apr 2025 17:00:30 -0300 Subject: [PATCH 054/165] In gen. GC, some gray objects stay in gray lists In generational collection, objects marked as touched1 stay in gray lists between collections. This commit fixes a bug introduced in commit 808976bb59. --- lgc.c | 7 ++++++- lstrlib.c | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lgc.c b/lgc.c index cada07d9a1..c0d68377b9 100644 --- a/lgc.c +++ b/lgc.c @@ -465,6 +465,8 @@ static void restartcollection (global_State *g) { ** TOUCHED1 objects need to be in the list. TOUCHED2 doesn't need to go ** back to a gray list, but then it must become OLD. (That is what ** 'correctgraylist' does when it finds a TOUCHED2 object.) +** This function is a no-op in incremental mode, as objects cannot be +** marked as touched in that mode. */ static void genlink (global_State *g, GCObject *o) { lua_assert(isblack(o)); @@ -480,7 +482,8 @@ static void genlink (global_State *g, GCObject *o) { ** Traverse a table with weak values and link it to proper list. During ** propagate phase, keep it in 'grayagain' list, to be revisited in the ** atomic phase. In the atomic phase, if table has any white value, -** put it in 'weak' list, to be cleared. +** put it in 'weak' list, to be cleared; otherwise, call 'genlink' +** to check table age in generational mode. */ static void traverseweakvalue (global_State *g, Table *h) { Node *n, *limit = gnodelast(h); @@ -501,6 +504,8 @@ static void traverseweakvalue (global_State *g, Table *h) { linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */ else if (hasclears) linkgclist(h, g->weak); /* has to be cleared later */ + else + genlink(g, obj2gco(h)); } diff --git a/lstrlib.c b/lstrlib.c index 321d6a0b0a..306cd0bfeb 100644 --- a/lstrlib.c +++ b/lstrlib.c @@ -1544,8 +1544,10 @@ static KOption getdetails (Header *h, size_t totalsize, const char **fmt, else { if (align > h->maxalign) /* enforce maximum alignment */ align = h->maxalign; - if (l_unlikely(!ispow2(align))) /* not a power of 2? */ + if (l_unlikely(!ispow2(align))) { /* not a power of 2? */ + *ntoalign = 0; /* to avoid warnings */ luaL_argerror(h->L, 1, "format asks for alignment not power of 2"); + } else { /* 'szmoda' = totalsize % align */ unsigned szmoda = cast_uint(totalsize & (align - 1)); From 50fd8d03c33bbe52ac5b34c4eb748197b349cedd Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 17 Apr 2025 14:58:55 -0300 Subject: [PATCH 055/165] Function 'luaK_semerror' made vararg All calls to 'luaK_semerror' were using 'luaO_pushfstring' to create the error messages. --- lcode.c | 9 ++++++++- lcode.h | 2 +- lparser.c | 30 ++++++++++++------------------ 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lcode.c b/lcode.c index 8c04d8ab16..d22a081aed 100644 --- a/lcode.c +++ b/lcode.c @@ -40,7 +40,14 @@ static int codesJ (FuncState *fs, OpCode o, int sj, int k); /* semantic error */ -l_noret luaK_semerror (LexState *ls, const char *msg) { +l_noret luaK_semerror (LexState *ls, const char *fmt, ...) { + const char *msg; + va_list argp; + va_start(argp, fmt); + msg = luaO_pushvfstring(ls->L, fmt, argp); + va_end(argp); + if (msg == NULL) /* error? */ + luaD_throw(ls->L, LUA_ERRMEM); ls->t.token = 0; /* remove "near " from final message */ luaX_syntaxerror(ls, msg); } diff --git a/lcode.h b/lcode.h index 414ebe3999..94fc2417dd 100644 --- a/lcode.h +++ b/lcode.h @@ -97,7 +97,7 @@ LUAI_FUNC void luaK_settablesize (FuncState *fs, int pc, int ra, int asize, int hsize); LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore); LUAI_FUNC void luaK_finish (FuncState *fs); -LUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *msg); +LUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *fmt, ...); #endif diff --git a/lparser.c b/lparser.c index 380e45f58c..e71d721211 100644 --- a/lparser.c +++ b/lparser.c @@ -306,11 +306,9 @@ static void check_readonly (LexState *ls, expdesc *e) { default: return; /* other cases cannot be read-only */ } - if (varname) { - const char *msg = luaO_pushfstring(ls->L, - "attempt to assign to const variable '%s'", getstr(varname)); - luaK_semerror(ls, msg); /* error */ - } + if (varname) + luaK_semerror(ls, "attempt to assign to const variable '%s'", + getstr(varname)); } @@ -523,9 +521,9 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { static l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) { TString *tsname = getlocalvardesc(ls->fs, gt->nactvar)->vd.name; const char *varname = getstr(tsname); - const char *msg = " at line %d jumps into the scope of local '%s'"; - msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line, varname); - luaK_semerror(ls, msg); /* raise the error */ + luaK_semerror(ls, + " at line %d jumps into the scope of local '%s'", + getstr(gt->name), gt->line, varname); /* raise the error */ } @@ -677,11 +675,10 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { ** generates an error for an undefined 'goto'. */ static l_noret undefgoto (LexState *ls, Labeldesc *gt) { - const char *msg = "no visible label '%s' for at line %d"; - msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line); /* breaks are checked when created, cannot be undefined */ lua_assert(!eqstr(gt->name, luaS_newliteral(ls->L, "break"))); - luaK_semerror(ls, msg); + luaK_semerror(ls, "no visible label '%s' for at line %d", + getstr(gt->name), gt->line); } @@ -1479,11 +1476,9 @@ static void breakstat (LexState *ls, int line) { */ static void checkrepeated (LexState *ls, TString *name) { Labeldesc *lb = findlabel(ls, name, ls->fs->firstlabel); - if (l_unlikely(lb != NULL)) { /* already defined? */ - const char *msg = "label '%s' already defined on line %d"; - msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line); - luaK_semerror(ls, msg); /* error */ - } + if (l_unlikely(lb != NULL)) /* already defined? */ + luaK_semerror(ls, "label '%s' already defined on line %d", + getstr(name), lb->line); /* error */ } @@ -1718,8 +1713,7 @@ static lu_byte getlocalattribute (LexState *ls) { else if (strcmp(attr, "close") == 0) return RDKTOCLOSE; /* to-be-closed variable */ else - luaK_semerror(ls, - luaO_pushfstring(ls->L, "unknown attribute '%s'", attr)); + luaK_semerror(ls, "unknown attribute '%s'", attr); } return VDKREG; /* regular variable */ } From 9b014d4bcd4ebadb523f1c1a1d38148d8526e5ed Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 23 Apr 2025 11:36:09 -0300 Subject: [PATCH 056/165] Details (typos in comments) --- ldo.c | 2 +- lgc.c | 1 - lparser.c | 2 +- lvm.c | 2 +- testes/api.lua | 6 +++--- testes/constructs.lua | 2 +- testes/db.lua | 12 ++++++------ testes/errors.lua | 2 +- testes/files.lua | 2 +- testes/gc.lua | 10 +++++----- testes/locals.lua | 2 +- testes/main.lua | 4 ++-- testes/math.lua | 2 +- testes/nextvar.lua | 4 ++-- testes/pm.lua | 4 ++-- testes/utf8.lua | 2 +- 16 files changed, 29 insertions(+), 30 deletions(-) diff --git a/ldo.c b/ldo.c index 3e5c7504f4..820b5a9ad0 100644 --- a/ldo.c +++ b/ldo.c @@ -513,7 +513,7 @@ l_sinline void genmoveresults (lua_State *L, StkId res, int nres, ** to 'res'. Handle most typical cases (zero results for commands, ** one result for expressions, multiple results for tail calls/single ** parameters) separated. The flag CIST_TBC in 'fwanted', if set, -** forces the swicth to go to the default case. +** forces the switch to go to the default case. */ l_sinline void moveresults (lua_State *L, StkId res, int nres, l_uint32 fwanted) { diff --git a/lgc.c b/lgc.c index c0d68377b9..f0045dd6f2 100644 --- a/lgc.c +++ b/lgc.c @@ -126,7 +126,6 @@ static l_mem objsize (GCObject *o) { CClosure *cl = gco2ccl(o); res = sizeCclosure(cl->nupvalues); break; - break; } case LUA_VUSERDATA: { Udata *u = gco2u(o); diff --git a/lparser.c b/lparser.c index e71d721211..e7e05f480e 100644 --- a/lparser.c +++ b/lparser.c @@ -561,7 +561,7 @@ static void closegoto (LexState *ls, int g, Labeldesc *label, int bup) { /* ** Search for an active label with the given name, starting at -** index 'ilb' (so that it can searh for all labels in current block +** index 'ilb' (so that it can search for all labels in current block ** or all labels in current function). */ static Labeldesc *findlabel (LexState *ls, TString *name, int ilb) { diff --git a/lvm.c b/lvm.c index e2c36ef577..97dfe5ee62 100644 --- a/lvm.c +++ b/lvm.c @@ -327,7 +327,7 @@ lu_byte luaV_finishget (lua_State *L, const TValue *t, TValue *key, ** Finish a table assignment 't[key] = val'. ** About anchoring the table before the call to 'luaH_finishset': ** This call may trigger an emergency collection. When loop>0, -** the table being acessed is a field in some metatable. If this +** the table being accessed is a field in some metatable. If this ** metatable is weak and the table is not anchored, this collection ** could collect that table while it is being updated. */ diff --git a/testes/api.lua b/testes/api.lua index 49e3f9b987..b3791654d4 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -114,7 +114,7 @@ end -- testing warnings T.testC([[ - warningC "#This shold be a" + warningC "#This should be a" warningC " single " warning "warning" warningC "#This should be " @@ -162,7 +162,7 @@ do -- test returning more results than fit in the caller stack end -do -- testing multipe returns +do -- testing multiple returns local function foo (n) if n > 0 then return n, foo(n - 1) end end @@ -902,7 +902,7 @@ F = function (x) assert(T.udataval(A) == B) debug.getmetatable(A) -- just access it end - A = x -- ressurect userdata + A = x -- resurrect userdata B = udval return 1,2,3 end diff --git a/testes/constructs.lua b/testes/constructs.lua index 3f6d506f0a..94f670c7a5 100644 --- a/testes/constructs.lua +++ b/testes/constructs.lua @@ -60,7 +60,7 @@ assert((x>y) and x or y == 2); assert(1234567890 == tonumber('1234567890') and 1234567890+1 == 1234567891) -do -- testing operators with diffent kinds of constants +do -- testing operators with different kinds of constants -- operands to consider: -- * fit in register -- * constant doesn't fit in register diff --git a/testes/db.lua b/testes/db.lua index 8e13373c2d..e4982c207a 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -431,7 +431,7 @@ do assert(a == nil and not b) end --- testing iteraction between multiple values x hooks +-- testing interaction between multiple values x hooks do local function f(...) return 3, ... end local count = 0 @@ -587,7 +587,7 @@ t = getupvalues(foo2) assert(t.a == 1 and t.b == 2 and t.c == 3) assert(debug.setupvalue(foo1, 1, "xuxu") == "b") assert(({debug.getupvalue(foo2, 3)})[2] == "xuxu") --- upvalues of C functions are allways "called" "" (the empty string) +-- upvalues of C functions are always named "" (the empty string) assert(debug.getupvalue(string.gmatch("x", "x"), 1) == "") @@ -839,7 +839,7 @@ t[1] = "'error'" checktraceback(co, t) --- test acessing line numbers of a coroutine from a resume inside +-- test accessing line numbers of a coroutine from a resume inside -- a C function (this is a known bug in Lua 5.0) local function g(x) @@ -966,9 +966,9 @@ local debug = require'debug' local a = 12 -- a local variable local n, v = debug.getlocal(1, 1) -assert(n == "(temporary)" and v == debug) -- unkown name but known value +assert(n == "(temporary)" and v == debug) -- unknown name but known value n, v = debug.getlocal(1, 2) -assert(n == "(temporary)" and v == 12) -- unkown name but known value +assert(n == "(temporary)" and v == 12) -- unknown name but known value -- a function with an upvalue local f = function () local x; return a end @@ -1018,7 +1018,7 @@ do -- bug in 5.4.0: line hooks in stripped code line = l end, "l") assert(s() == 2); debug.sethook(nil) - assert(line == nil) -- hook called withoug debug info for 1st instruction + assert(line == nil) -- hook called without debug info for 1st instruction end do -- tests for 'source' in binary dumps diff --git a/testes/errors.lua b/testes/errors.lua index c1c40fecdf..c80051fc7a 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -507,7 +507,7 @@ end if not _soft then - -- several tests that exaust the Lua stack + -- several tests that exhaust the Lua stack collectgarbage() print"testing stack overflow" local C = 0 diff --git a/testes/files.lua b/testes/files.lua index 53edf3145f..a0ae661c40 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -478,7 +478,7 @@ do print("testing flush") end --- test for multipe arguments in 'lines' +-- test for multiple arguments in 'lines' io.output(file); io.write"0123456789\n":close() for a,b in io.lines(file, 1, 1) do if a == "\n" then assert(not b) diff --git a/testes/gc.lua b/testes/gc.lua index 0693837c35..ca8aa1bc51 100644 --- a/testes/gc.lua +++ b/testes/gc.lua @@ -446,8 +446,8 @@ do -- tests for string keys in weak tables local m = collectgarbage("count") -- current memory local a = setmetatable({}, {__mode = "kv"}) a[string.rep("a", 2^22)] = 25 -- long string key -> number value - a[string.rep("b", 2^22)] = {} -- long string key -> colectable value - a[{}] = 14 -- colectable key + a[string.rep("b", 2^22)] = {} -- long string key -> collectable value + a[{}] = 14 -- collectable key collectgarbage() local k, v = next(a) -- string key with number value preserved assert(k == string.rep("a", 2^22) and v == 25) @@ -459,7 +459,7 @@ do -- tests for string keys in weak tables assert(next(a) == nil) -- make sure will not try to compare with dead key assert(a[string.rep("b", 100)] == undef) - assert(collectgarbage("count") <= m + 1) -- eveything collected + assert(collectgarbage("count") <= m + 1) -- everything collected end @@ -524,7 +524,7 @@ do local co = coroutine.create(f) assert(coroutine.resume(co, co)) end - -- Now, thread and closure are not reacheable any more. + -- Now, thread and closure are not reachable any more. collectgarbage() assert(collected) collectgarbage("restart") @@ -644,7 +644,7 @@ do assert(getmetatable(o) == tt) -- create new objects during GC local a = 'xuxu'..(10+3)..'joao', {} - ___Glob = o -- ressurrect object! + ___Glob = o -- resurrect object! setmetatable({}, tt) -- creates a new one with same metatable print(">>> closing state " .. "<<<\n") end diff --git a/testes/locals.lua b/testes/locals.lua index ccea0a1422..eeeb4338fe 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -1162,7 +1162,7 @@ do local function open (x) numopen = numopen + 1 return - function () -- iteraction function + function () -- iteration function x = x - 1 if x > 0 then return x end end, diff --git a/testes/main.lua b/testes/main.lua index bf3c898eed..eb63d58859 100644 --- a/testes/main.lua +++ b/testes/main.lua @@ -226,7 +226,7 @@ RUN("lua -l 'str=string' '-lm=math' -e 'print(m.sin(0))' %s > %s", prog, out) checkout("0.0\nALO ALO\t20\n") --- test module names with version sufix ("libs/lib2-v2") +-- test module names with version suffix ("libs/lib2-v2") RUN("env LUA_CPATH='./libs/?.so' lua -l lib2-v2 -e 'print(lib2.id())' > %s", out) checkout("true\n") @@ -347,7 +347,7 @@ checkout("a\n") RUN([[lua "-eprint(1)" -ea=3 -e "print(a)" > %s]], out) checkout("1\n3\n") --- test iteractive mode +-- test interactive mode prepfile[[ (6*2-6) -- === a = diff --git a/testes/math.lua b/testes/math.lua index bad8bc5e71..88a57ce75a 100644 --- a/testes/math.lua +++ b/testes/math.lua @@ -1071,7 +1071,7 @@ do assert(x == tonumber(tostring(x))) end - -- different numbers shold print differently. + -- different numbers should print differently. -- check pairs of floats with minimum detectable difference local p = floatbits - 1 for i = 1, maxexp - 1 do diff --git a/testes/nextvar.lua b/testes/nextvar.lua index 031ad3fd99..679cb1e4ac 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua @@ -227,7 +227,7 @@ for i = 1,lim do end --- insert and delete elements until a rehash occurr. Caller must ensure +-- insert and delete elements until a rehash occur. Caller must ensure -- that a rehash will change the shape of the table. Must repeat because -- the insertion may collide with the deleted element, and then there is -- no rehash. @@ -349,7 +349,7 @@ a,b,c = 1,2,3 a,b,c = nil --- next uses always the same iteraction function +-- next uses always the same iteration function assert(next{} == next{}) local function find (name) diff --git a/testes/pm.lua b/testes/pm.lua index 2a0cfb0bb5..ab19eb5db8 100644 --- a/testes/pm.lua +++ b/testes/pm.lua @@ -23,9 +23,9 @@ a,b = string.find('alo', '') assert(a == 1 and b == 0) a,b = string.find('a\0o a\0o a\0o', 'a', 1) -- first position assert(a == 1 and b == 1) -a,b = string.find('a\0o a\0o a\0o', 'a\0o', 2) -- starts in the midle +a,b = string.find('a\0o a\0o a\0o', 'a\0o', 2) -- starts in the middle assert(a == 5 and b == 7) -a,b = string.find('a\0o a\0o a\0o', 'a\0o', 9) -- starts in the midle +a,b = string.find('a\0o a\0o a\0o', 'a\0o', 9) -- starts in the middle assert(a == 9 and b == 11) a,b = string.find('a\0a\0a\0a\0\0ab', '\0ab', 2); -- finds at the end assert(a == 9 and b == 11); diff --git a/testes/utf8.lua b/testes/utf8.lua index 0704782c12..d0c0184d34 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -134,7 +134,7 @@ do errorcodes("\xbfinvalid") errorcodes("αλφ\xBFα") - -- calling interation function with invalid arguments + -- calling iteration function with invalid arguments local f = utf8.codes("") assert(f("", 2) == nil) assert(f("", -1) == nil) From e05590591410a5e007a1e3f1691f6c1cf9d8fe45 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 23 Apr 2025 11:55:04 -0300 Subject: [PATCH 057/165] New macro 'pushvfstring' Helps to ensure that 'luaO_pushvfstring' is being called correctly, with an error check after closing the vararg list with 'va_end'. --- lapi.c | 6 +----- lcode.c | 6 +----- ldebug.c | 8 ++------ lobject.h | 9 +++++++++ 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/lapi.c b/lapi.c index f59430a7f3..769eba13b6 100644 --- a/lapi.c +++ b/lapi.c @@ -593,12 +593,8 @@ LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { const char *ret; va_list argp; lua_lock(L); - va_start(argp, fmt); - ret = luaO_pushvfstring(L, fmt, argp); - va_end(argp); + pushvfstring(L, argp, fmt, ret); luaC_checkGC(L); - if (ret == NULL) /* error? */ - luaD_throw(L, LUA_ERRMEM); lua_unlock(L); return ret; } diff --git a/lcode.c b/lcode.c index d22a081aed..e8b9bb5dbd 100644 --- a/lcode.c +++ b/lcode.c @@ -43,11 +43,7 @@ static int codesJ (FuncState *fs, OpCode o, int sj, int k); l_noret luaK_semerror (LexState *ls, const char *fmt, ...) { const char *msg; va_list argp; - va_start(argp, fmt); - msg = luaO_pushvfstring(ls->L, fmt, argp); - va_end(argp); - if (msg == NULL) /* error? */ - luaD_throw(ls->L, LUA_ERRMEM); + pushvfstring(ls->L, argp, fmt, msg); ls->t.token = 0; /* remove "near " from final message */ luaX_syntaxerror(ls, msg); } diff --git a/ldebug.c b/ldebug.c index 258a439478..f4bb0a08a9 100644 --- a/ldebug.c +++ b/ldebug.c @@ -852,12 +852,8 @@ l_noret luaG_runerror (lua_State *L, const char *fmt, ...) { const char *msg; va_list argp; luaC_checkGC(L); /* error message uses memory */ - va_start(argp, fmt); - msg = luaO_pushvfstring(L, fmt, argp); /* format message */ - va_end(argp); - if (msg == NULL) /* no memory to format message? */ - luaD_throw(L, LUA_ERRMEM); - else if (isLua(ci)) { /* Lua function? */ + pushvfstring(L, argp, fmt, msg); + if (isLua(ci)) { /* Lua function? */ /* add source:line information */ luaG_addinfo(L, msg, ci_func(ci)->p->source, getcurrentline(ci)); setobjs2s(L, L->top.p - 2, L->top.p - 1); /* remove 'msg' */ diff --git a/lobject.h b/lobject.h index 8c06a224cc..b5ca36680d 100644 --- a/lobject.h +++ b/lobject.h @@ -822,6 +822,15 @@ typedef struct Table { /* size of buffer for 'luaO_utf8esc' function */ #define UTF8BUFFSZ 8 + +/* macro to call 'luaO_pushvfstring' correctly */ +#define pushvfstring(L, argp, fmt, msg) \ + { va_start(argp, fmt); \ + msg = luaO_pushvfstring(L, fmt, argp); \ + va_end(argp); \ + if (msg == NULL) luaD_throw(L, LUA_ERRMEM); /* only after 'va_end' */ } + + LUAI_FUNC int luaO_utf8esc (char *buff, unsigned long x); LUAI_FUNC lu_byte luaO_ceillog2 (unsigned int x); LUAI_FUNC lu_byte luaO_codeparam (unsigned int p); From be8120906304a8658fab998587b969e0e42f5650 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 5 May 2025 16:24:59 -0300 Subject: [PATCH 058/165] First implementation of global declarations --- llex.c | 7 +- llex.h | 4 +- lparser.c | 111 +++++++++++++++++++--------- lparser.h | 15 +++- ltests.h | 1 + luaconf.h | 6 ++ manual/manual.of | 184 ++++++++++++++++++++++++++++------------------- testes/db.lua | 1 + testes/goto.lua | 44 +++++++++++- testes/math.lua | 16 ++++- 10 files changed, 272 insertions(+), 117 deletions(-) diff --git a/llex.c b/llex.c index 4b5a1f7509..9d93224f28 100644 --- a/llex.c +++ b/llex.c @@ -40,11 +40,16 @@ #define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') +#if defined(LUA_COMPAT_GLOBAL) +#define GLOBALLEX ".g" /* not recognizable by the scanner */ +#else +#define GLOBALLEX "global" +#endif /* ORDER RESERVED */ static const char *const luaX_tokens [] = { "and", "break", "do", "else", "elseif", - "end", "false", "for", "function", "goto", "if", + "end", "false", "for", "function", GLOBALLEX, "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "//", "..", "...", "==", ">=", "<=", "~=", diff --git a/llex.h b/llex.h index c3500ef6a8..078e4d31e5 100644 --- a/llex.h +++ b/llex.h @@ -33,8 +33,8 @@ enum RESERVED { /* terminal symbols denoted by reserved words */ TK_AND = FIRST_RESERVED, TK_BREAK, TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, - TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, - TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, + TK_GLOBAL, TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, + TK_REPEAT, TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, /* other terminal symbols */ TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_SHL, TK_SHR, diff --git a/lparser.c b/lparser.c index e7e05f480e..1c5fdca6f3 100644 --- a/lparser.c +++ b/lparser.c @@ -30,8 +30,8 @@ -/* maximum number of local variables per function (must be smaller - than 250, due to the bytecode format) */ +/* maximum number of variable declarationss per function (must be + smaller than 250, due to the bytecode format) */ #define MAXVARS 200 @@ -54,6 +54,7 @@ typedef struct BlockCnt { lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isloop; /* 1 if 'block' is a loop; 2 if it has pending breaks */ lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ + lu_byte globdec; /* true if inside the scope of any global declaration */ } BlockCnt; @@ -188,10 +189,10 @@ static short registerlocalvar (LexState *ls, FuncState *fs, /* -** Create a new local variable with the given 'name' and given 'kind'. +** Create a new variable with the given 'name' and given 'kind'. ** Return its index in the function. */ -static int new_localvarkind (LexState *ls, TString *name, lu_byte kind) { +static int new_varkind (LexState *ls, TString *name, lu_byte kind) { lua_State *L = ls->L; FuncState *fs = ls->fs; Dyndata *dyd = ls->dyd; @@ -211,7 +212,7 @@ static int new_localvarkind (LexState *ls, TString *name, lu_byte kind) { ** Create a new local variable with the given 'name' and regular kind. */ static int new_localvar (LexState *ls, TString *name) { - return new_localvarkind(ls, name, VDKREG); + return new_varkind(ls, name, VDKREG); } #define new_localvarliteral(ls,v) \ @@ -238,7 +239,7 @@ static Vardesc *getlocalvardesc (FuncState *fs, int vidx) { static lu_byte reglevel (FuncState *fs, int nvar) { while (nvar-- > 0) { Vardesc *vd = getlocalvardesc(fs, nvar); /* get previous variable */ - if (vd->vd.kind != RDKCTC) /* is in a register? */ + if (varinreg(vd)) /* is in a register? */ return cast_byte(vd->vd.ridx + 1); } return 0; /* no variables in registers */ @@ -259,7 +260,7 @@ lu_byte luaY_nvarstack (FuncState *fs) { */ static LocVar *localdebuginfo (FuncState *fs, int vidx) { Vardesc *vd = getlocalvardesc(fs, vidx); - if (vd->vd.kind == RDKCTC) + if (!varinreg(vd)) return NULL; /* no debug info. for constants */ else { int idx = vd->vd.pidx; @@ -401,7 +402,9 @@ static int searchvar (FuncState *fs, TString *n, expdesc *var) { if (eqstr(n, vd->vd.name)) { /* found? */ if (vd->vd.kind == RDKCTC) /* compile-time constant? */ init_exp(var, VCONST, fs->firstlocal + i); - else /* real variable */ + else if (vd->vd.kind == GDKREG || vd->vd.kind == GDKCONST) + init_exp(var, VGLOBAL, i); + else /* local variable */ init_var(fs, var, i); return cast_int(var->k); } @@ -440,25 +443,24 @@ static void marktobeclosed (FuncState *fs) { ** 'var' as 'void' as a flag. */ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { - if (fs == NULL) /* no more levels? */ - init_exp(var, VVOID, 0); /* default is global */ - else { - int v = searchvar(fs, n, var); /* look up locals at current level */ - if (v >= 0) { /* found? */ - if (v == VLOCAL && !base) - markupval(fs, var->u.var.vidx); /* local will be used as an upval */ - } - else { /* not found as local at current level; try upvalues */ - int idx = searchupvalue(fs, n); /* try existing upvalues */ - if (idx < 0) { /* not found? */ + int v = searchvar(fs, n, var); /* look up locals at current level */ + if (v >= 0) { /* found? */ + if (v == VLOCAL && !base) + markupval(fs, var->u.var.vidx); /* local will be used as an upval */ + } + else { /* not found as local at current level; try upvalues */ + int idx = searchupvalue(fs, n); /* try existing upvalues */ + if (idx < 0) { /* not found? */ + if (fs->prev != NULL) /* more levels? */ singlevaraux(fs->prev, n, var, 0); /* try upper levels */ - if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */ - idx = newupvalue(fs, n, var); /* will be a new upvalue */ - else /* it is a global or a constant */ - return; /* don't need to do anything at this level */ - } - init_exp(var, VUPVAL, idx); /* new or old upvalue */ + else /* no more levels */ + init_exp(var, VGLOBAL, -1); /* global by default */ + if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */ + idx = newupvalue(fs, n, var); /* will be a new upvalue */ + else /* it is a global or a constant */ + return; /* don't need to do anything at this level */ } + init_exp(var, VUPVAL, idx); /* new or old upvalue */ } } @@ -471,10 +473,15 @@ static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; singlevaraux(fs, varname, var, 1); - if (var->k == VVOID) { /* global name? */ + if (var->k == VGLOBAL) { /* global name? */ expdesc key; + /* global by default in the scope of a global declaration? */ + if (var->u.info == -1 && fs->bl->globdec) + luaK_semerror(ls, "variable '%s' not declared", getstr(varname)); singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ - lua_assert(var->k != VVOID); /* this one must exist */ + if (var->k == VGLOBAL) + luaK_semerror(ls, "_ENV is global when accessing variable '%s'", + getstr(varname)); luaK_exp2anyregup(fs, var); /* but could be a constant */ codestring(&key, varname); /* key is variable name */ luaK_indexed(fs, var, &key); /* env[varname] */ @@ -664,8 +671,13 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { bl->firstlabel = fs->ls->dyd->label.n; bl->firstgoto = fs->ls->dyd->gt.n; bl->upval = 0; + /* inherit 'insidetbc' from enclosing block */ bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc); - bl->previous = fs->bl; + /* inherit 'globdec' from enclosing block or enclosing function */ + bl->globdec = fs->bl != NULL ? fs->bl->globdec + : fs->prev != NULL ? fs->prev->bl->globdec + : 0; /* chunk's first block */ + bl->previous = fs->bl; /* link block in function's block list */ fs->bl = bl; lua_assert(fs->freereg == luaY_nvarstack(fs)); } @@ -1600,7 +1612,7 @@ static void fornum (LexState *ls, TString *varname, int line) { int base = fs->freereg; new_localvarliteral(ls, "(for state)"); new_localvarliteral(ls, "(for state)"); - new_localvarkind(ls, varname, RDKCONST); /* control variable */ + new_varkind(ls, varname, RDKCONST); /* control variable */ checknext(ls, '='); exp1(ls); /* initial value */ checknext(ls, ','); @@ -1627,7 +1639,7 @@ static void forlist (LexState *ls, TString *indexname) { new_localvarliteral(ls, "(for state)"); /* iterator function */ new_localvarliteral(ls, "(for state)"); /* state */ new_localvarliteral(ls, "(for state)"); /* closing var. (after swap) */ - new_localvarkind(ls, indexname, RDKCONST); /* control variable */ + new_varkind(ls, indexname, RDKCONST); /* control variable */ /* other declared variables */ while (testnext(ls, ',')) { new_localvar(ls, str_checkname(ls)); @@ -1702,7 +1714,7 @@ static void localfunc (LexState *ls) { } -static lu_byte getlocalattribute (LexState *ls) { +static lu_byte getvarattribute (LexState *ls) { /* ATTRIB -> ['<' Name '>'] */ if (testnext(ls, '<')) { TString *ts = str_checkname(ls); @@ -1738,8 +1750,8 @@ static void localstat (LexState *ls) { expdesc e; do { TString *vname = str_checkname(ls); - lu_byte kind = getlocalattribute(ls); - vidx = new_localvarkind(ls, vname, kind); + lu_byte kind = getvarattribute(ls); + vidx = new_varkind(ls, vname, kind); if (kind == RDKTOCLOSE) { /* to-be-closed? */ if (toclose != -1) /* one already present? */ luaK_semerror(ls, "multiple to-be-closed variables in local list"); @@ -1769,6 +1781,24 @@ static void localstat (LexState *ls) { } +static void globalstat (LexState *ls) { + FuncState *fs = ls->fs; + luaX_next(ls); /* skip 'global' */ + do { + TString *vname = str_checkname(ls); + lu_byte kind = getvarattribute(ls); + if (kind == RDKTOCLOSE) + luaK_semerror(ls, "global variable ('%s') cannot be to-be-closed", + getstr(vname)); + /* adjust kind for global variable */ + kind = (kind == VDKREG) ? GDKREG : GDKCONST; + new_varkind(ls, vname, kind); + fs->nactvar++; /* activate declaration */ + } while (testnext(ls, ',')); + fs->bl->globdec = 1; /* code is in the scope of a global declaration */ +} + + static int funcname (LexState *ls, expdesc *v) { /* funcname -> NAME {fieldsel} [':' NAME] */ int ismethod = 0; @@ -1888,6 +1918,10 @@ static void statement (LexState *ls) { localstat(ls); break; } + case TK_GLOBAL: { /* stat -> globalstat */ + globalstat(ls); + break; + } case TK_DBCOLON: { /* stat -> label */ luaX_next(ls); /* skip double colon */ labelstat(ls, str_checkname(ls), line); @@ -1907,6 +1941,17 @@ static void statement (LexState *ls) { gotostat(ls, line); break; } + case TK_NAME: { + /* compatibility code to parse global keyword when "global" + is not reserved */ + if (eqstr(ls->t.seminfo.ts, luaS_newliteral(ls->L, "global"))) { + int lk = luaX_lookahead(ls); + if (lk == TK_NAME) { /* 'global name'? */ + globalstat(ls); + break; + } + } /* else... */ + } /* FALLTHROUGH */ default: { /* stat -> func | assignment */ exprstat(ls); break; diff --git a/lparser.h b/lparser.h index a3063569c7..3cd0ba77a4 100644 --- a/lparser.h +++ b/lparser.h @@ -37,6 +37,9 @@ typedef enum { info = result register */ VLOCAL, /* local variable; var.ridx = register index; var.vidx = relative index in 'actvar.arr' */ + VGLOBAL, /* global variable; + info = relative index in 'actvar.arr' (or -1 for + implicit declaration) */ VUPVAL, /* upvalue variable; info = index of upvalue in 'upvalues' */ VCONST, /* compile-time variable; info = absolute index in 'actvar.arr' */ @@ -87,10 +90,16 @@ typedef struct expdesc { /* kinds of variables */ -#define VDKREG 0 /* regular */ -#define RDKCONST 1 /* constant */ +#define VDKREG 0 /* regular local */ +#define RDKCONST 1 /* local constant */ #define RDKTOCLOSE 2 /* to-be-closed */ -#define RDKCTC 3 /* compile-time constant */ +#define RDKCTC 3 /* local compile-time constant */ +#define GDKREG 4 /* regular global */ +#define GDKCONST 5 /* global constant */ + +/* variables that live in registers */ +#define varinreg(v) ((v)->vd.kind <= RDKTOCLOSE) + /* description of an active local variable */ typedef union Vardesc { diff --git a/ltests.h b/ltests.h index 7f0ce4041d..43f08162cd 100644 --- a/ltests.h +++ b/ltests.h @@ -14,6 +14,7 @@ /* test Lua with compatibility code */ #define LUA_COMPAT_MATHLIB #define LUA_COMPAT_LT_LE +#undef LUA_COMPAT_GLOBAL #define LUA_DEBUG diff --git a/luaconf.h b/luaconf.h index bd39465052..51e77547be 100644 --- a/luaconf.h +++ b/luaconf.h @@ -355,6 +355,12 @@ ** =================================================================== */ +/* +@@ LUA_COMPAT_GLOBAL avoids 'global' being a reserved word +*/ +#define LUA_COMPAT_GLOBAL + + /* @@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3. ** You can define it to get all options, or change specific options diff --git a/manual/manual.of b/manual/manual.of index 7cd0d4dbde..ace5d37513 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -213,11 +213,88 @@ of a given value @seeF{type}. } -@sect2{globalenv| @title{Environments and the Global Environment} +@sect2{globalenv| @title{Scopes, Variables, and Environments} +@index{visibility} + +A variable name refers to a global or a local variable according +to the declaration that is in context at that point of the code. +(For the purposes of this discussion, +a function's formal parameter is equivalent to a local variable.) + +All chunks start with an implicit declaration @T{global *}, +which declares all free names as global variables; +this implicit declaration becomes void inside the scope of any other +@Rw{global} declaration, regardless of the names being declared. +@verbatim{ +X = 1 -- Ok, global by default +do + global Y -- voids implicit initial declaration + X = 1 -- ERROR, X not declared + Y = 1 -- Ok, Y declared as global +end +X = 2 -- Ok, global by default again +} +So, outside any global declaration, +Lua works as @x{global-by-default}. +Inside any global declaration, +Lua works without a default: +All variables must be declared. + +Lua is a lexically scoped language. +The scope of a variable declaration begins at the first statement after +the declaration and lasts until the last non-void statement +of the innermost block that includes the declaration. +(@emph{Void statements} are labels and empty statements.) + +A declaration shadows any declaration for the same name that +is in context at the point of the declaration. Inside this +shadow, any outer declaration for that name is void. +See the next example: +@verbatim{ +global print, x +x = 10 -- global variable +do -- new block + local x = x -- new 'x', with value 10 + print(x) --> 10 + x = x+1 + do -- another block + local x = x+1 -- another 'x' + print(x) --> 12 + end + print(x) --> 11 +end +print(x) --> 10 (the global one) +} + +Notice that, in a declaration like @T{local x = x}, +the new @id{x} being declared is not in scope yet, +and so the @id{x} in the left-hand side refers to the outside variable. + +Because of the @x{lexical scoping} rules, +local variables can be freely accessed by functions +defined inside their scope. +A local variable used by an inner function is called an @def{upvalue} +(or @emphx{external local variable}, or simply @emphx{external variable}) +inside the inner function. + +Notice that each execution of a @Rw{local} statement +defines new local variables. +Consider the following example: +@verbatim{ +a = {} +local x = 20 +for i = 1, 10 do + local y = 0 + a[i] = function () y = y + 1; return x + y end +end +} +The loop creates ten closures +(that is, ten instances of the anonymous function). +Each of these closures uses a different @id{y} variable, +while all of them share the same @id{x}. As we will discuss further in @refsec{variables} and @refsec{assignment}, -any reference to a free name -(that is, a name not bound to any declaration) @id{var} +any reference to a global variable @id{var} is syntactically translated to @T{_ENV.var}. Moreover, every chunk is compiled in the scope of an external local variable named @id{_ENV} @see{chunks}, @@ -225,12 +302,14 @@ so @id{_ENV} itself is never a free name in a chunk. Despite the existence of this external @id{_ENV} variable and the translation of free names, -@id{_ENV} is a completely regular name. +@id{_ENV} is a regular name. In particular, you can define new variables and parameters with that name. -Each reference to a free name uses the @id{_ENV} that is -visible at that point in the program, -following the usual visibility rules of Lua @see{visibility}. +(However, you should not define @id{_ENV} as a global variable, +otherwise @T{_ENV.var} would translate to +@T{_ENV._ENV.var} and so on, in an infinite loop.) +Each reference to a global variable name uses the @id{_ENV} that is +visible at that point in the program. Any table used as the value of @id{_ENV} is called an @def{environment}. @@ -244,8 +323,8 @@ When Lua loads a chunk, the default value for its @id{_ENV} variable is the global environment @seeF{load}. Therefore, by default, -free names in Lua code refer to entries in the global environment -and, therefore, they are also called @def{global variables}. +global variables in Lua code refer to entries in the global environment +and, therefore, they act as conventional global variables. Moreover, all standard libraries are loaded in the global environment and some functions there operate on that environment. You can use @Lid{load} (or @Lid{loadfile}) @@ -1198,17 +1277,15 @@ global variables, local variables, and table fields. A single name can denote a global variable or a local variable (or a function's formal parameter, -which is a particular kind of local variable): +which is a particular kind of local variable) @see{globalenv}: @Produc{ @producname{var}@producbody{@bnfNter{Name}} } @bnfNter{Name} denotes identifiers @see{lexical}. -Any variable name is assumed to be global unless explicitly declared -as a local @see{localvar}. -@x{Local variables} are @emph{lexically scoped}: +Because variables are @emph{lexically scoped}, local variables can be freely accessed by functions -defined inside their scope @see{visibility}. +defined inside their scope @see{globalenv}. Before the first assignment to a variable, its value is @nil. @@ -1227,8 +1304,6 @@ The syntax @id{var.Name} is just syntactic sugar for An access to a global variable @id{x} is equivalent to @id{_ENV.x}. -Due to the way that chunks are compiled, -the variable @id{_ENV} itself is never global @see{globalenv}. } @@ -1571,17 +1646,18 @@ Function calls are explained in @See{functioncall}. } -@sect3{localvar| @title{Local Declarations} -@x{Local variables} can be declared anywhere inside a block. -The declaration can include an initialization: +@sect3{localvar| @title{Variable Declarations} +Local and global variables can be declared anywhere inside a block. +The declaration for locals can include an initialization: @Produc{ @producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} +@producname{stat}@producbody{@Rw{global} attnamelist} @producname{attnamelist}@producbody{ @bnfNter{Name} attrib @bnfrep{@bnfter{,} @bnfNter{Name} attrib}} } If present, an initial assignment has the same semantics of a multiple assignment @see{assignment}. -Otherwise, all variables are initialized with @nil. +Otherwise, all local variables are initialized with @nil. Each variable name may be postfixed by an attribute (a name between angle brackets): @@ -1595,11 +1671,22 @@ that is, a variable that cannot be assigned to after its initialization; and @id{close}, which declares a to-be-closed variable @see{to-be-closed}. A list of variables can contain at most one to-be-closed variable. +Only local variables can have the @id{close} attribute. + +Note that, for global variables, +the @emph{read-only} atribute is only a syntactical restriction: +@verbatim{ +global X +X = 1 -- ERROR +_ENV.X = 1 -- Ok +foo() -- 'foo' can freely change the global X +} A chunk is also a block @see{chunks}, -and so local variables can be declared in a chunk outside any explicit block. +and so variables can be declared in a chunk outside any explicit block. -The visibility rules for local variables are explained in @See{visibility}. +The visibility rules for variable declarations +are explained in @See{globalenv}. } @@ -2356,58 +2443,6 @@ return x,y,f() -- returns x, y, and all results from f(). } -@sect2{visibility| @title{Visibility Rules} - -@index{visibility} -Lua is a lexically scoped language. -The scope of a local variable begins at the first statement after -its declaration and lasts until the last non-void statement -of the innermost block that includes the declaration. -(@emph{Void statements} are labels and empty statements.) -Consider the following example: -@verbatim{ -x = 10 -- global variable -do -- new block - local x = x -- new 'x', with value 10 - print(x) --> 10 - x = x+1 - do -- another block - local x = x+1 -- another 'x' - print(x) --> 12 - end - print(x) --> 11 -end -print(x) --> 10 (the global one) -} - -Notice that, in a declaration like @T{local x = x}, -the new @id{x} being declared is not in scope yet, -and so the second @id{x} refers to the outside variable. - -Because of the @x{lexical scoping} rules, -local variables can be freely accessed by functions -defined inside their scope. -A local variable used by an inner function is called an @def{upvalue} -(or @emphx{external local variable}, or simply @emphx{external variable}) -inside the inner function. - -Notice that each execution of a @Rw{local} statement -defines new local variables. -Consider the following example: -@verbatim{ -a = {} -local x = 20 -for i = 1, 10 do - local y = 0 - a[i] = function () y = y + 1; return x + y end -end -} -The loop creates ten closures -(that is, ten instances of the anonymous function). -Each of these closures uses a different @id{y} variable, -while all of them share the same @id{x}. - -} } @@ -9535,6 +9570,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @OrNL @Rw{function} funcname funcbody @OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} +@OrNL @Rw{global} attnamelist } @producname{attnamelist}@producbody{ diff --git a/testes/db.lua b/testes/db.lua index e4982c207a..ae204c4176 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -349,6 +349,7 @@ end, "crl") function f(a,b) + global collectgarbage, assert, g, string collectgarbage() local _, x = debug.getlocal(1, 1) local _, y = debug.getlocal(1, 2) diff --git a/testes/goto.lua b/testes/goto.lua index eca6851689..fdfddb8570 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -1,6 +1,8 @@ -- $Id: testes/goto.lua $ -- See Copyright Notice in file lua.h +print("testing goto and global declarations") + collectgarbage() local function errmsg (code, m) @@ -280,7 +282,47 @@ end foo() --------------------------------------------------------------------------------- +-------------------------------------------------------------------------- +do + global print, load, T; global assert + global string + + local function checkerr (code, err) + local st, msg = load(code) + assert(not st and string.find(msg, err)) + end + + -- globals must be declared after a global declaration + checkerr("global none; X = 1", "variable 'X'") + + -- global variables cannot be to-be-closed + checkerr("global X", "cannot be") + + do + local X = 10 + do global X; X = 20 end + assert(X == 10) -- local X + end + assert(_ENV.X == 20) -- global X + + -- '_ENV' cannot be global + checkerr("global _ENV, a; a = 10", "variable 'a'") + + -- global declarations inside functions + checkerr([[ + global none + local function foo () XXX = 1 end --< ERROR]], "variable 'XXX'") + + if not T then -- when not in "test mode", "global" isn't reserved + assert(load("global = 1; return global")() == 1) + print " ('global' is not a reserved word)" + else + -- "global" reserved, cannot be used as a variable + assert(not load("global = 1; return global")) + end + +end print'OK' + diff --git a/testes/math.lua b/testes/math.lua index 88a57ce75a..242579b177 100644 --- a/testes/math.lua +++ b/testes/math.lua @@ -3,6 +3,14 @@ print("testing numbers and math lib") +local math = require "math" +local string = require "string" + +global none + +global print, assert, pcall, type, pairs, load +global tonumber, tostring, select + local minint = math.mininteger local maxint = math.maxinteger @@ -184,7 +192,7 @@ do for i = -3, 3 do -- variables avoid constant folding for j = -3, 3 do -- domain errors (0^(-n)) are not portable - if not _port or i ~= 0 or j > 0 then + if not _ENV._port or i ~= 0 or j > 0 then assert(eq(i^j, 1 / i^(-j))) end end @@ -430,7 +438,7 @@ for i = 2,36 do assert(tonumber('\t10000000000\t', i) == i10) end -if not _soft then +if not _ENV._soft then -- tests with very long numerals assert(tonumber("0x"..string.rep("f", 13)..".0") == 2.0^(4*13) - 1) assert(tonumber("0x"..string.rep("f", 150)..".0") == 2.0^(4*150) - 1) @@ -632,7 +640,7 @@ assert(maxint % -2 == -1) -- non-portable tests because Windows C library cannot compute -- fmod(1, huge) correctly -if not _port then +if not _ENV._port then local function anan (x) assert(isNaN(x)) end -- assert Not a Number anan(0.0 % 0) anan(1.3 % 0) @@ -779,6 +787,7 @@ assert(a == '10' and b == '20') do print("testing -0 and NaN") + global rawset, undef local mz = -0.0 local z = 0.0 assert(mz == z) @@ -1074,6 +1083,7 @@ do -- different numbers should print differently. -- check pairs of floats with minimum detectable difference local p = floatbits - 1 + global ipairs for i = 1, maxexp - 1 do for _, i in ipairs{-i, i} do local x = 2^i From 4365a45d681b4e71e3c39148489bb8eae538ccf7 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 6 May 2025 15:54:05 -0300 Subject: [PATCH 059/165] Checks for read-only globals --- lcode.c | 3 ++- lparser.c | 24 ++++++++++++++++++------ lparser.h | 5 +++-- testes/locals.lua | 10 ++++++++++ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/lcode.c b/lcode.c index e8b9bb5dbd..8f658500dc 100644 --- a/lcode.c +++ b/lcode.c @@ -1315,8 +1315,8 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { luaK_exp2anyreg(fs, t); /* put it in a register */ if (t->k == VUPVAL) { lu_byte temp = cast_byte(t->u.info); /* upvalue index */ - lua_assert(isKstr(fs, k)); t->u.ind.t = temp; /* (can't do a direct assignment; values overlap) */ + lua_assert(isKstr(fs, k)); t->u.ind.idx = cast(short, k->u.info); /* literal short string */ t->k = VINDEXUP; } @@ -1336,6 +1336,7 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { t->k = VINDEXED; } } + t->u.ind.vidx = -1; /* by default, not a declared global */ } diff --git a/lparser.c b/lparser.c index 1c5fdca6f3..61ce090840 100644 --- a/lparser.c +++ b/lparser.c @@ -200,7 +200,7 @@ static int new_varkind (LexState *ls, TString *name, lu_byte kind) { luaY_checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, MAXVARS, "local variables"); luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1, - dyd->actvar.size, Vardesc, SHRT_MAX, "local variables"); + dyd->actvar.size, Vardesc, SHRT_MAX, "variable declarationss"); var = &dyd->actvar.arr[dyd->actvar.n++]; var->vd.kind = kind; /* default */ var->vd.name = name; @@ -276,7 +276,7 @@ static LocVar *localdebuginfo (FuncState *fs, int vidx) { static void init_var (FuncState *fs, expdesc *e, int vidx) { e->f = e->t = NO_JUMP; e->k = VLOCAL; - e->u.var.vidx = cast(unsigned short, vidx); + e->u.var.vidx = cast(short, vidx); e->u.var.ridx = getlocalvardesc(fs, vidx)->vd.ridx; } @@ -304,8 +304,16 @@ static void check_readonly (LexState *ls, expdesc *e) { varname = up->name; break; } + case VINDEXUP: case VINDEXSTR: case VINDEXED: { + int vidx = e->u.ind.vidx; + /* is it a read-only declared global? */ + if (vidx != -1 && ls->dyd->actvar.arr[vidx].vd.kind == GDKCONST) + varname = ls->dyd->actvar.arr[vidx].vd.name; + break; + } default: - return; /* other cases cannot be read-only */ + lua_assert(e->k == VINDEXI); /* this one doesn't need any check */ + return; /* integer index cannot be read-only */ } if (varname) luaK_semerror(ls, "attempt to assign to const variable '%s'", @@ -391,7 +399,7 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) { /* -** Look for an active local variable with the name 'n' in the +** Look for an active variable with the name 'n' in the ** function 'fs'. If found, initialize 'var' with it and return ** its expression kind; otherwise return -1. */ @@ -403,7 +411,7 @@ static int searchvar (FuncState *fs, TString *n, expdesc *var) { if (vd->vd.kind == RDKCTC) /* compile-time constant? */ init_exp(var, VCONST, fs->firstlocal + i); else if (vd->vd.kind == GDKREG || vd->vd.kind == GDKCONST) - init_exp(var, VGLOBAL, i); + init_exp(var, VGLOBAL, fs->firstlocal + i); else /* local variable */ init_var(fs, var, i); return cast_int(var->k); @@ -475,8 +483,11 @@ static void singlevar (LexState *ls, expdesc *var) { singlevaraux(fs, varname, var, 1); if (var->k == VGLOBAL) { /* global name? */ expdesc key; + int info = var->u.info; + lua_assert(info == -1 || + eqstr(ls->dyd->actvar.arr[info].vd.name, varname)); /* global by default in the scope of a global declaration? */ - if (var->u.info == -1 && fs->bl->globdec) + if (info == -1 && fs->bl->globdec) luaK_semerror(ls, "variable '%s' not declared", getstr(varname)); singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ if (var->k == VGLOBAL) @@ -485,6 +496,7 @@ static void singlevar (LexState *ls, expdesc *var) { luaK_exp2anyregup(fs, var); /* but could be a constant */ codestring(&key, varname); /* key is variable name */ luaK_indexed(fs, var, &key); /* env[varname] */ + var->u.ind.vidx = cast(short, info); /* mark it as a declared global */ } } diff --git a/lparser.h b/lparser.h index 3cd0ba77a4..274fb1c469 100644 --- a/lparser.h +++ b/lparser.h @@ -77,11 +77,12 @@ typedef struct expdesc { int info; /* for generic use */ struct { /* for indexed variables */ short idx; /* index (R or "long" K) */ + short vidx; /* index in 'actvar.arr' or -1 if not a declared global */ lu_byte t; /* table (register or upvalue) */ } ind; struct { /* for local variables */ lu_byte ridx; /* register holding the variable */ - unsigned short vidx; /* compiler index (in 'actvar.arr') */ + short vidx; /* index in 'actvar.arr' */ } var; } u; int t; /* patch list of 'exit when true' */ @@ -101,7 +102,7 @@ typedef struct expdesc { #define varinreg(v) ((v)->vd.kind <= RDKTOCLOSE) -/* description of an active local variable */ +/* description of an active variable */ typedef union Vardesc { struct { TValuefields; /* constant value (if it is a compile-time constant) */ diff --git a/testes/locals.lua b/testes/locals.lua index eeeb4338fe..421595bb9b 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -178,6 +178,8 @@ A = nil do -- constants + global assert, load, string, X + X = 1 -- not a constant local a, b, c = 10, 20, 30 b = a + c + b -- 'b' is not constant assert(a == 10 and b == 60 and c == 30) @@ -191,6 +193,9 @@ do -- constants checkro("z", "local x , y, z = 10, 20, 30; y = 10; z = 11") checkro("foo", "local foo = 10; function foo() end") checkro("foo", "local foo = {}; function foo() end") + checkro("foo", "global foo ; function foo() end") + checkro("XX", "global XX ; XX = 10") + checkro("XX", "local _ENV; global XX ; XX = 10") checkro("z", [[ local a, z , b = 10; @@ -201,6 +206,11 @@ do -- constants local a, var1 = 10; function foo() a = 20; z = function () var1 = 12; end end ]]) + + checkro("var1", [[ + global a, var1 , z; + local function foo() a = 20; z = function () var1 = 12; end end + ]]) end From 3f0ea90aa8b8493485637f6e8d2a070a1ac0d5cb Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 8 May 2025 11:08:03 -0300 Subject: [PATCH 060/165] New syntax 'global function' --- lparser.c | 49 +++++++++++++++++++++++++++++++++++++---------- manual/manual.of | 15 +++++++++++++-- testes/errors.lua | 8 ++++++++ testes/goto.lua | 23 +++++++++++++++++++++- 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/lparser.c b/lparser.c index 61ce090840..a11f1dd34d 100644 --- a/lparser.c +++ b/lparser.c @@ -477,8 +477,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { ** Find a variable with the given name 'n', handling global variables ** too. */ -static void singlevar (LexState *ls, expdesc *var) { - TString *varname = str_checkname(ls); +static void buildvar (LexState *ls, TString *varname, expdesc *var) { FuncState *fs = ls->fs; singlevaraux(fs, varname, var, 1); if (var->k == VGLOBAL) { /* global name? */ @@ -501,6 +500,11 @@ static void singlevar (LexState *ls, expdesc *var) { } +static void singlevar (LexState *ls, expdesc *var) { + buildvar(ls, str_checkname(ls), var); +} + + /* ** Adjust the number of results from an expression list 'e' with 'nexps' ** expressions to 'nvars' values. @@ -1727,7 +1731,7 @@ static void localfunc (LexState *ls) { static lu_byte getvarattribute (LexState *ls) { - /* ATTRIB -> ['<' Name '>'] */ + /* attrib -> ['<' NAME '>'] */ if (testnext(ls, '<')) { TString *ts = str_checkname(ls); const char *attr = getstr(ts); @@ -1752,7 +1756,7 @@ static void checktoclose (FuncState *fs, int level) { static void localstat (LexState *ls) { - /* stat -> LOCAL NAME ATTRIB { ',' NAME ATTRIB } ['=' explist] */ + /* stat -> LOCAL NAME attrib { ',' NAME attrib } ['=' explist] */ FuncState *fs = ls->fs; int toclose = -1; /* index of to-be-closed variable (if any) */ Vardesc *var; /* last variable */ @@ -1794,8 +1798,8 @@ static void localstat (LexState *ls) { static void globalstat (LexState *ls) { + /* globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */ FuncState *fs = ls->fs; - luaX_next(ls); /* skip 'global' */ do { TString *vname = str_checkname(ls); lu_byte kind = getvarattribute(ls); @@ -1807,7 +1811,31 @@ static void globalstat (LexState *ls) { new_varkind(ls, vname, kind); fs->nactvar++; /* activate declaration */ } while (testnext(ls, ',')); - fs->bl->globdec = 1; /* code is in the scope of a global declaration */ +} + + +static void globalfunc (LexState *ls, int line) { + /* globalfunc -> (GLOBAL FUNCTION) NAME body */ + expdesc var, b; + FuncState *fs = ls->fs; + TString *fname = str_checkname(ls); + new_varkind(ls, fname, GDKREG); /* declare global variable */ + fs->nactvar++; /* enter its scope */ + buildvar(ls, fname, &var); + body(ls, &b, 0, ls->linenumber); /* compile and return closure in 'b' */ + luaK_storevar(fs, &var, &b); + luaK_fixline(fs, line); /* definition "happens" in the first line */ +} + + +static void globalstatfunc (LexState *ls, int line) { + /* stat -> GLOBAL globalfunc | GLOBAL globalstat */ + luaX_next(ls); /* skip 'global' */ + ls->fs->bl->globdec = 1; /* in the scope of a global declaration */ + if (testnext(ls, TK_FUNCTION)) + globalfunc(ls, line); + else + globalstat(ls); } @@ -1930,8 +1958,8 @@ static void statement (LexState *ls) { localstat(ls); break; } - case TK_GLOBAL: { /* stat -> globalstat */ - globalstat(ls); + case TK_GLOBAL: { /* stat -> globalstatfunc */ + globalstatfunc(ls, line); break; } case TK_DBCOLON: { /* stat -> label */ @@ -1958,8 +1986,9 @@ static void statement (LexState *ls) { is not reserved */ if (eqstr(ls->t.seminfo.ts, luaS_newliteral(ls->L, "global"))) { int lk = luaX_lookahead(ls); - if (lk == TK_NAME) { /* 'global name'? */ - globalstat(ls); + if (lk == TK_NAME || lk == TK_FUNCTION) { + /* 'global ' or 'global function' */ + globalstatfunc(ls, line); break; } } /* else... */ diff --git a/manual/manual.of b/manual/manual.of index ace5d37513..cc71aaada7 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2229,6 +2229,7 @@ The following syntactic sugar simplifies function definitions: @Produc{ @producname{stat}@producbody{@Rw{function} funcname funcbody} @producname{stat}@producbody{@Rw{local} @Rw{function} @bnfNter{Name} funcbody} +@producname{stat}@producbody{@Rw{global} @Rw{function} @bnfNter{Name} funcbody} @producname{funcname}@producbody{@bnfNter{Name} @bnfrep{@bnfter{.} @bnfNter{Name}} @bnfopt{@bnfter{:} @bnfNter{Name}}} } The statement @@ -2247,6 +2248,7 @@ translates to @verbatim{ t.a.b.c.f = function () @rep{body} end } + The statement @verbatim{ local function f () @rep{body} end @@ -2260,7 +2262,15 @@ not to local f = function () @rep{body} end } (This only makes a difference when the body of the function -contains references to @id{f}.) +contains recursive references to @id{f}.) +Similarly, the statement +@verbatim{ +global function f () @rep{body} end +} +translates to +@verbatim{ +global f; f = function () @rep{body} end +} A function definition is an executable expression, whose value has type @emph{function}. @@ -2323,7 +2333,7 @@ then the function returns with no results. @index{multiple return} There is a system-dependent limit on the number of values that a function may return. -This limit is guaranteed to be greater than 1000. +This limit is guaranteed to be at least 1000. The @emphx{colon} syntax is used to emulate @def{methods}, @@ -9569,6 +9579,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @OrNL @Rw{for} namelist @Rw{in} explist @Rw{do} block @Rw{end} @OrNL @Rw{function} funcname funcbody @OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody +@OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} @OrNL @Rw{global} attnamelist } diff --git a/testes/errors.lua b/testes/errors.lua index c80051fc7a..6c76a99a16 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -489,6 +489,14 @@ if not b then end end]], 5) +lineerror([[ +_ENV = 1 +global function foo () + local a = 10 + return a +end +]], 2) + -- bug in 5.4.0 lineerror([[ diff --git a/testes/goto.lua b/testes/goto.lua index fdfddb8570..b41399ffce 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -293,8 +293,9 @@ do assert(not st and string.find(msg, err)) end - -- globals must be declared after a global declaration + -- globals must be declared, after a global declaration checkerr("global none; X = 1", "variable 'X'") + checkerr("global none; function XX() end", "variable 'XX'") -- global variables cannot be to-be-closed checkerr("global X", "cannot be") @@ -321,6 +322,26 @@ do -- "global" reserved, cannot be used as a variable assert(not load("global = 1; return global")) end + + local foo = 20 + do + global function foo (x) + if x == 0 then return 1 else return 2 * foo(x - 1) end + end + assert(foo == _ENV.foo and foo(4) == 16) + end + assert(_ENV.foo(4) == 16) + assert(foo == 20) -- local one is in context here + + do + global foo; + function foo (x) return end -- Ok after declaration + end + + checkerr([[ + global foo ; + function foo (x) return end -- ERROR: foo is read-only + ]], "assign to const variable 'foo'") end From d827e96f33056bcc0daca0c04b3273604f9d5986 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 8 May 2025 12:49:39 -0300 Subject: [PATCH 061/165] Using 'l_uint32' for unicode codepoints in scanner 'l_uint32' is enough for unicode codepoints (versus unsigned long), and the utf-8 library already uses that type. --- llex.c | 6 +++--- llimits.h | 1 - lobject.c | 5 +++-- lobject.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/llex.c b/llex.c index 9d93224f28..edeb48feee 100644 --- a/llex.c +++ b/llex.c @@ -359,12 +359,12 @@ static int readhexaesc (LexState *ls) { ** for error reporting in case of errors; 'i' counts the number of ** saved characters, so that they can be removed if case of success. */ -static unsigned long readutf8esc (LexState *ls) { - unsigned long r; +static l_uint32 readutf8esc (LexState *ls) { + l_uint32 r; int i = 4; /* number of chars to be removed: start with #"\u{X" */ save_and_next(ls); /* skip 'u' */ esccheck(ls, ls->current == '{', "missing '{'"); - r = cast_ulong(gethexa(ls)); /* must have at least one digit */ + r = cast_uint(gethexa(ls)); /* must have at least one digit */ while (cast_void(save_and_next(ls)), lisxdigit(ls->current)) { i++; esccheck(ls, r <= (0x7FFFFFFFu >> 4), "UTF-8 value too large"); diff --git a/llimits.h b/llimits.h index 710dc1b440..b1fc384bfa 100644 --- a/llimits.h +++ b/llimits.h @@ -138,7 +138,6 @@ typedef LUAI_UACINT l_uacInt; #define cast_num(i) cast(lua_Number, (i)) #define cast_int(i) cast(int, (i)) #define cast_uint(i) cast(unsigned int, (i)) -#define cast_ulong(i) cast(unsigned long, (i)) #define cast_byte(i) cast(lu_byte, (i)) #define cast_uchar(i) cast(unsigned char, (i)) #define cast_char(i) cast(char, (i)) diff --git a/lobject.c b/lobject.c index 68566a2bad..57fc6a91a4 100644 --- a/lobject.c +++ b/lobject.c @@ -382,7 +382,7 @@ size_t luaO_str2num (const char *s, TValue *o) { } -int luaO_utf8esc (char *buff, unsigned long x) { +int luaO_utf8esc (char *buff, l_uint32 x) { int n = 1; /* number of bytes put in buffer (backwards) */ lua_assert(x <= 0x7FFFFFFFu); if (x < 0x80) /* ascii? */ @@ -637,7 +637,8 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { } case 'U': { /* an 'unsigned long' as a UTF-8 sequence */ char bf[UTF8BUFFSZ]; - int len = luaO_utf8esc(bf, va_arg(argp, unsigned long)); + unsigned long arg = va_arg(argp, unsigned long); + int len = luaO_utf8esc(bf, cast(l_uint32, arg)); addstr2buff(&buff, bf + UTF8BUFFSZ - len, cast_uint(len)); break; } diff --git a/lobject.h b/lobject.h index b5ca36680d..bc2f69ab4a 100644 --- a/lobject.h +++ b/lobject.h @@ -831,7 +831,7 @@ typedef struct Table { if (msg == NULL) luaD_throw(L, LUA_ERRMEM); /* only after 'va_end' */ } -LUAI_FUNC int luaO_utf8esc (char *buff, unsigned long x); +LUAI_FUNC int luaO_utf8esc (char *buff, l_uint32 x); LUAI_FUNC lu_byte luaO_ceillog2 (unsigned int x); LUAI_FUNC lu_byte luaO_codeparam (unsigned int p); LUAI_FUNC l_mem luaO_applyparam (lu_byte p, l_mem x); From 7ade1557627cf3f09c23c892ee227b7386f28414 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 8 May 2025 15:18:57 -0300 Subject: [PATCH 062/165] Janitorial work on casts --- lcode.c | 8 ++++---- ldump.c | 2 +- llimits.h | 3 +++ lobject.c | 2 +- lopcodes.h | 40 ++++++++++++++++++++-------------------- lparser.c | 4 ++-- ltests.c | 30 +++++++++++++++--------------- lundump.c | 2 +- 8 files changed, 47 insertions(+), 44 deletions(-) diff --git a/lcode.c b/lcode.c index 8f658500dc..119d91ab6e 100644 --- a/lcode.c +++ b/lcode.c @@ -1317,22 +1317,22 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { lu_byte temp = cast_byte(t->u.info); /* upvalue index */ t->u.ind.t = temp; /* (can't do a direct assignment; values overlap) */ lua_assert(isKstr(fs, k)); - t->u.ind.idx = cast(short, k->u.info); /* literal short string */ + t->u.ind.idx = cast_short(k->u.info); /* literal short string */ t->k = VINDEXUP; } else { /* register index of the table */ t->u.ind.t = cast_byte((t->k == VLOCAL) ? t->u.var.ridx: t->u.info); if (isKstr(fs, k)) { - t->u.ind.idx = cast(short, k->u.info); /* literal short string */ + t->u.ind.idx = cast_short(k->u.info); /* literal short string */ t->k = VINDEXSTR; } else if (isCint(k)) { /* int. constant in proper range? */ - t->u.ind.idx = cast(short, k->u.ival); + t->u.ind.idx = cast_short(k->u.ival); t->k = VINDEXI; } else { - t->u.ind.idx = cast(short, luaK_exp2anyreg(fs, k)); /* register */ + t->u.ind.idx = cast_short(luaK_exp2anyreg(fs, k)); /* register */ t->k = VINDEXED; } } diff --git a/ldump.c b/ldump.c index d8fca317f0..79bb1dc9a7 100644 --- a/ldump.c +++ b/ldump.c @@ -108,7 +108,7 @@ static void dumpSize (DumpState *D, size_t sz) { static void dumpInt (DumpState *D, int x) { lua_assert(x >= 0); - dumpVarint(D, cast(size_t, x)); + dumpVarint(D, cast_sizet(x)); } diff --git a/llimits.h b/llimits.h index b1fc384bfa..223b5e6c34 100644 --- a/llimits.h +++ b/llimits.h @@ -137,12 +137,15 @@ typedef LUAI_UACINT l_uacInt; #define cast_voidp(i) cast(void *, (i)) #define cast_num(i) cast(lua_Number, (i)) #define cast_int(i) cast(int, (i)) +#define cast_short(i) cast(short, (i)) #define cast_uint(i) cast(unsigned int, (i)) #define cast_byte(i) cast(lu_byte, (i)) #define cast_uchar(i) cast(unsigned char, (i)) #define cast_char(i) cast(char, (i)) #define cast_charp(i) cast(char *, (i)) #define cast_sizet(i) cast(size_t, (i)) +#define cast_Integer(i) cast(lua_Integer, (i)) +#define cast_Inst(i) cast(Instruction, (i)) /* cast a signed lua_Integer to lua_Unsigned */ diff --git a/lobject.c b/lobject.c index 57fc6a91a4..1c32ecf7a9 100644 --- a/lobject.c +++ b/lobject.c @@ -618,7 +618,7 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { } case 'I': { /* a 'lua_Integer' */ TValue num; - setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt))); + setivalue(&num, cast_Integer(va_arg(argp, l_uacInt))); addnum2buff(&buff, &num); break; } diff --git a/lopcodes.h b/lopcodes.h index 7511eb2237..e3ac9d0969 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -126,14 +126,14 @@ enum OpMode {iABC, ivABC, iABx, iAsBx, iAx, isJ}; #define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))) #define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ - ((cast(Instruction, o)<>(pos)) & MASK1(size,0))) #define setarg(i,v,pos,size) ((i) = (((i)&MASK0(size,pos)) | \ - ((cast(Instruction, v)<f = e->t = NO_JUMP; e->k = VLOCAL; - e->u.var.vidx = cast(short, vidx); + e->u.var.vidx = cast_short(vidx); e->u.var.ridx = getlocalvardesc(fs, vidx)->vd.ridx; } @@ -495,7 +495,7 @@ static void buildvar (LexState *ls, TString *varname, expdesc *var) { luaK_exp2anyregup(fs, var); /* but could be a constant */ codestring(&key, varname); /* key is variable name */ luaK_indexed(fs, var, &key); /* env[varname] */ - var->u.ind.vidx = cast(short, info); /* mark it as a declared global */ + var->u.ind.vidx = cast_short(info); /* mark it as a declared global */ } } diff --git a/ltests.c b/ltests.c index 1517aa88f6..e7bc66dd28 100644 --- a/ltests.c +++ b/ltests.c @@ -910,9 +910,9 @@ static int get_limits (lua_State *L) { static int mem_query (lua_State *L) { if (lua_isnone(L, 1)) { - lua_pushinteger(L, cast(lua_Integer, l_memcontrol.total)); - lua_pushinteger(L, cast(lua_Integer, l_memcontrol.numblocks)); - lua_pushinteger(L, cast(lua_Integer, l_memcontrol.maxmem)); + lua_pushinteger(L, cast_Integer(l_memcontrol.total)); + lua_pushinteger(L, cast_Integer(l_memcontrol.numblocks)); + lua_pushinteger(L, cast_Integer(l_memcontrol.maxmem)); return 3; } else if (lua_isnumber(L, 1)) { @@ -926,7 +926,7 @@ static int mem_query (lua_State *L) { int i; for (i = LUA_NUMTYPES - 1; i >= 0; i--) { if (strcmp(t, ttypename(i)) == 0) { - lua_pushinteger(L, cast(lua_Integer, l_memcontrol.objcount[i])); + lua_pushinteger(L, cast_Integer(l_memcontrol.objcount[i])); return 1; } } @@ -1074,7 +1074,7 @@ static int hash_query (lua_State *L) { Table *t; luaL_checktype(L, 2, LUA_TTABLE); t = hvalue(obj_at(L, 2)); - lua_pushinteger(L, cast(lua_Integer, luaH_mainposition(t, o) - t->node)); + lua_pushinteger(L, cast_Integer(luaH_mainposition(t, o) - t->node)); } return 1; } @@ -1082,9 +1082,9 @@ static int hash_query (lua_State *L) { static int stacklevel (lua_State *L) { int a = 0; - lua_pushinteger(L, cast(lua_Integer, L->top.p - L->stack.p)); + lua_pushinteger(L, cast_Integer(L->top.p - L->stack.p)); lua_pushinteger(L, stacksize(L)); - lua_pushinteger(L, cast(lua_Integer, L->nCcalls)); + lua_pushinteger(L, cast_Integer(L->nCcalls)); lua_pushinteger(L, L->nci); lua_pushinteger(L, (lua_Integer)(size_t)&a); return 5; @@ -1099,9 +1099,9 @@ static int table_query (lua_State *L) { t = hvalue(obj_at(L, 1)); asize = t->asize; if (i == -1) { - lua_pushinteger(L, cast(lua_Integer, asize)); - lua_pushinteger(L, cast(lua_Integer, allocsizenode(t))); - lua_pushinteger(L, cast(lua_Integer, asize > 0 ? *lenhint(t) : 0)); + lua_pushinteger(L, cast_Integer(asize)); + lua_pushinteger(L, cast_Integer(allocsizenode(t))); + lua_pushinteger(L, cast_Integer(asize > 0 ? *lenhint(t) : 0)); return 3; } else if (cast_uint(i) < asize) { @@ -1157,7 +1157,7 @@ static int test_codeparam (lua_State *L) { static int test_applyparam (lua_State *L) { lua_Integer p = luaL_checkinteger(L, 1); lua_Integer x = luaL_checkinteger(L, 2); - lua_pushinteger(L, cast(lua_Integer, luaO_applyparam(cast_byte(p), x))); + lua_pushinteger(L, cast_Integer(luaO_applyparam(cast_byte(p), x))); return 1; } @@ -1257,7 +1257,7 @@ static int pushuserdata (lua_State *L) { static int udataval (lua_State *L) { - lua_pushinteger(L, cast(lua_Integer, cast(size_t, lua_touserdata(L, 1)))); + lua_pushinteger(L, cast_st2S(cast_sizet(lua_touserdata(L, 1)))); return 1; } @@ -1294,7 +1294,7 @@ static int num2int (lua_State *L) { static int makeseed (lua_State *L) { - lua_pushinteger(L, cast(lua_Integer, luaL_makeseed(L))); + lua_pushinteger(L, cast_Integer(luaL_makeseed(L))); return 1; } @@ -1638,7 +1638,7 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) { } else if EQ("func2num") { lua_CFunction func = lua_tocfunction(L1, getindex); - lua_pushinteger(L1, cast(lua_Integer, cast(size_t, func))); + lua_pushinteger(L1, cast_st2S(cast_sizet(func))); } else if EQ("getfield") { int t = getindex; @@ -2011,7 +2011,7 @@ static int Cfunc (lua_State *L) { static int Cfunck (lua_State *L, int status, lua_KContext ctx) { lua_pushstring(L, statcodes[status]); lua_setglobal(L, "status"); - lua_pushinteger(L, cast(lua_Integer, ctx)); + lua_pushinteger(L, cast_Integer(ctx)); lua_setglobal(L, "ctx"); return runC(L, L, lua_tostring(L, cast_int(ctx))); } diff --git a/lundump.c b/lundump.c index d53bfc9a99..fccded7d79 100644 --- a/lundump.c +++ b/lundump.c @@ -149,7 +149,7 @@ static void loadString (LoadState *S, Proto *p, TString **sl) { return; } else if (size == 1) { /* previously saved string? */ - lua_Integer idx = cast(lua_Integer, loadSize(S)); /* get its index */ + lua_Integer idx = cast_st2S(loadSize(S)); /* get its index */ TValue stv; luaH_getint(S->h, idx, &stv); /* get its value */ *sl = ts = tsvalue(&stv); From 5b1ab8efdcb7b48cab8148a407266c467d57114c Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Sun, 11 May 2025 11:51:58 -0300 Subject: [PATCH 063/165] 'expdesc' doesn't depend on 'actvar' for var. info. In preparation for 'global *', the structure 'expdesc' does not point to 'actvar.arr' for information about global variables. --- lcode.c | 9 ++++++--- lparser.c | 15 +++++++-------- lparser.h | 16 ++++++++++------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lcode.c b/lcode.c index 119d91ab6e..7ca895f147 100644 --- a/lcode.c +++ b/lcode.c @@ -753,10 +753,11 @@ void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { /* ** Convert a VKSTR to a VK */ -static void str2K (FuncState *fs, expdesc *e) { +static int str2K (FuncState *fs, expdesc *e) { lua_assert(e->k == VKSTR); e->u.info = stringK(fs, e->u.strval); e->k = VK; + return e->u.info; } @@ -1307,8 +1308,9 @@ void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { ** values in registers. */ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { + int keystr = -1; if (k->k == VKSTR) - str2K(fs, k); + keystr = str2K(fs, k); lua_assert(!hasjumps(t) && (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL)); if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non 'Kstr'? */ @@ -1336,7 +1338,8 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { t->k = VINDEXED; } } - t->u.ind.vidx = -1; /* by default, not a declared global */ + t->u.ind.keystr = keystr; /* string index in 'k' */ + t->u.ind.ro = 0; /* by default, not read-only */ } diff --git a/lparser.c b/lparser.c index 6658bb2061..3c2f57ab5e 100644 --- a/lparser.c +++ b/lparser.c @@ -304,11 +304,9 @@ static void check_readonly (LexState *ls, expdesc *e) { varname = up->name; break; } - case VINDEXUP: case VINDEXSTR: case VINDEXED: { - int vidx = e->u.ind.vidx; - /* is it a read-only declared global? */ - if (vidx != -1 && ls->dyd->actvar.arr[vidx].vd.kind == GDKCONST) - varname = ls->dyd->actvar.arr[vidx].vd.name; + case VINDEXUP: case VINDEXSTR: case VINDEXED: { /* global variable */ + if (e->u.ind.ro) /* read-only? */ + varname = tsvalue(&fs->f->k[e->u.ind.keystr]); break; } default: @@ -483,8 +481,6 @@ static void buildvar (LexState *ls, TString *varname, expdesc *var) { if (var->k == VGLOBAL) { /* global name? */ expdesc key; int info = var->u.info; - lua_assert(info == -1 || - eqstr(ls->dyd->actvar.arr[info].vd.name, varname)); /* global by default in the scope of a global declaration? */ if (info == -1 && fs->bl->globdec) luaK_semerror(ls, "variable '%s' not declared", getstr(varname)); @@ -495,7 +491,10 @@ static void buildvar (LexState *ls, TString *varname, expdesc *var) { luaK_exp2anyregup(fs, var); /* but could be a constant */ codestring(&key, varname); /* key is variable name */ luaK_indexed(fs, var, &key); /* env[varname] */ - var->u.ind.vidx = cast_short(info); /* mark it as a declared global */ + if (info != -1 && ls->dyd->actvar.arr[info].vd.kind == GDKCONST) + var->u.ind.ro = 1; /* mark variable as read-only */ + else /* anyway must be a global */ + lua_assert(info == -1 || ls->dyd->actvar.arr[info].vd.kind == GDKREG); } } diff --git a/lparser.h b/lparser.h index 274fb1c469..524df6ea74 100644 --- a/lparser.h +++ b/lparser.h @@ -45,16 +45,19 @@ typedef enum { info = absolute index in 'actvar.arr' */ VINDEXED, /* indexed variable; ind.t = table register; - ind.idx = key's R index */ + ind.idx = key's R index; + ind.ro = true if it represents a read-only global; + ind.keystr = if key is a string, index in 'k' of that string; + -1 if key is not a string */ VINDEXUP, /* indexed upvalue; - ind.t = table upvalue; - ind.idx = key's K index */ + ind.idx = key's K index; + ind.* as in VINDEXED */ VINDEXI, /* indexed variable with constant integer; ind.t = table register; ind.idx = key's value */ VINDEXSTR, /* indexed variable with literal string; - ind.t = table register; - ind.idx = key's K index */ + ind.idx = key's K index; + ind.* as in VINDEXED */ VJMP, /* expression is a test/comparison; info = pc of corresponding jump instruction */ VRELOC, /* expression can put result in any register; @@ -77,8 +80,9 @@ typedef struct expdesc { int info; /* for generic use */ struct { /* for indexed variables */ short idx; /* index (R or "long" K) */ - short vidx; /* index in 'actvar.arr' or -1 if not a declared global */ lu_byte t; /* table (register or upvalue) */ + lu_byte ro; /* true if variable is read-only */ + int keystr; /* index in 'k' of string key, or -1 if not a string */ } ind; struct { /* for local variables */ lu_byte ridx; /* register holding the variable */ From 7dc6aae29057c9dc4588f780c7abd72a62ff4c8e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 12 May 2025 11:42:45 -0300 Subject: [PATCH 064/165] Correct line in error message for constant function --- lparser.c | 2 +- testes/goto.lua | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lparser.c b/lparser.c index 3c2f57ab5e..93991cb051 100644 --- a/lparser.c +++ b/lparser.c @@ -1858,8 +1858,8 @@ static void funcstat (LexState *ls, int line) { expdesc v, b; luaX_next(ls); /* skip FUNCTION */ ismethod = funcname(ls, &v); - body(ls, &b, ismethod, line); check_readonly(ls, &v); + body(ls, &b, ismethod, line); luaK_storevar(ls->fs, &v, &b); luaK_fixline(ls->fs, line); /* definition "happens" in the first line */ } diff --git a/testes/goto.lua b/testes/goto.lua index b41399ffce..59713dd78a 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -342,6 +342,13 @@ do global foo ; function foo (x) return end -- ERROR: foo is read-only ]], "assign to const variable 'foo'") + + checkerr([[ + global foo ; + function foo (x) -- ERROR: foo is read-only + return + end + ]], "%:2%:") -- correct line in error message end From 3b9dd52be02fd43c598f4adb6fa7844e6a573923 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 13 May 2025 11:43:10 -0300 Subject: [PATCH 065/165] Collective declaration for globals ('global *') --- lparser.c | 55 ++++++++++++++++++++------------ manual/manual.of | 76 ++++++++++++++++++++++++++++++++++----------- testes/all.lua | 8 +++-- testes/calls.lua | 15 +++++---- testes/closure.lua | 2 ++ testes/code.lua | 6 ++-- testes/files.lua | 6 ++-- testes/goto.lua | 24 ++++++++++---- testes/literals.lua | 2 ++ testes/locals.lua | 12 ++++--- testes/nextvar.lua | 7 ++--- testes/pm.lua | 2 ++ testes/strings.lua | 1 + testes/utf8.lua | 2 ++ 14 files changed, 155 insertions(+), 63 deletions(-) diff --git a/lparser.c b/lparser.c index 93991cb051..242bb0010b 100644 --- a/lparser.c +++ b/lparser.c @@ -405,7 +405,12 @@ static int searchvar (FuncState *fs, TString *n, expdesc *var) { int i; for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { Vardesc *vd = getlocalvardesc(fs, i); - if (eqstr(n, vd->vd.name)) { /* found? */ + if (vd->vd.name == NULL) { /* 'global *'? */ + if (var->u.info == -1) { /* no previous collective declaration? */ + var->u.info = fs->firstlocal + i; /* will use this one as default */ + } + } + else if (eqstr(n, vd->vd.name)) { /* found? */ if (vd->vd.kind == RDKCTC) /* compile-time constant? */ init_exp(var, VCONST, fs->firstlocal + i); else if (vd->vd.kind == GDKREG || vd->vd.kind == GDKCONST) @@ -449,18 +454,16 @@ static void marktobeclosed (FuncState *fs) { ** 'var' as 'void' as a flag. */ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { - int v = searchvar(fs, n, var); /* look up locals at current level */ + int v = searchvar(fs, n, var); /* look up variables at current level */ if (v >= 0) { /* found? */ if (v == VLOCAL && !base) markupval(fs, var->u.var.vidx); /* local will be used as an upval */ } - else { /* not found as local at current level; try upvalues */ + else { /* not found at current level; try upvalues */ int idx = searchupvalue(fs, n); /* try existing upvalues */ if (idx < 0) { /* not found? */ if (fs->prev != NULL) /* more levels? */ singlevaraux(fs->prev, n, var, 0); /* try upper levels */ - else /* no more levels */ - init_exp(var, VGLOBAL, -1); /* global by default */ if (var->k == VLOCAL || var->k == VUPVAL) /* local or upvalue? */ idx = newupvalue(fs, n, var); /* will be a new upvalue */ else /* it is a global or a constant */ @@ -477,6 +480,7 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { */ static void buildvar (LexState *ls, TString *varname, expdesc *var) { FuncState *fs = ls->fs; + init_exp(var, VGLOBAL, -1); /* global by default */ singlevaraux(fs, varname, var, 1); if (var->k == VGLOBAL) { /* global name? */ expdesc key; @@ -1796,20 +1800,33 @@ static void localstat (LexState *ls) { } +static lu_byte getglobalattribute (LexState *ls) { + lu_byte kind = getvarattribute(ls); + if (kind == RDKTOCLOSE) + luaK_semerror(ls, "global variables cannot be to-be-closed"); + /* adjust kind for global variable */ + return (kind == VDKREG) ? GDKREG : GDKCONST; +} + + static void globalstat (LexState *ls) { - /* globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */ + /* globalstat -> (GLOBAL) '*' attrib + globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */ FuncState *fs = ls->fs; - do { - TString *vname = str_checkname(ls); - lu_byte kind = getvarattribute(ls); - if (kind == RDKTOCLOSE) - luaK_semerror(ls, "global variable ('%s') cannot be to-be-closed", - getstr(vname)); - /* adjust kind for global variable */ - kind = (kind == VDKREG) ? GDKREG : GDKCONST; - new_varkind(ls, vname, kind); + if (testnext(ls, '*')) { + lu_byte kind = getglobalattribute(ls); + /* use NULL as name to represent '*' entries */ + new_varkind(ls, NULL, kind); fs->nactvar++; /* activate declaration */ - } while (testnext(ls, ',')); + } + else { + do { + TString *vname = str_checkname(ls); + lu_byte kind = getglobalattribute(ls); + new_varkind(ls, vname, kind); + fs->nactvar++; /* activate declaration */ + } while (testnext(ls, ',')); + } } @@ -1983,10 +2000,10 @@ static void statement (LexState *ls) { case TK_NAME: { /* compatibility code to parse global keyword when "global" is not reserved */ - if (eqstr(ls->t.seminfo.ts, luaS_newliteral(ls->L, "global"))) { + if (strcmp(getstr(ls->t.seminfo.ts), "global") == 0) { int lk = luaX_lookahead(ls); - if (lk == TK_NAME || lk == TK_FUNCTION) { - /* 'global ' or 'global function' */ + if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) { + /* 'global ' or 'global *' or 'global function' */ globalstatfunc(ls, line); break; } diff --git a/manual/manual.of b/manual/manual.of index cc71aaada7..effb95da15 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -223,14 +223,15 @@ a function's formal parameter is equivalent to a local variable.) All chunks start with an implicit declaration @T{global *}, which declares all free names as global variables; -this implicit declaration becomes void inside the scope of any other -@Rw{global} declaration, regardless of the names being declared. +this preambular declaration becomes void inside the scope of any other +@Rw{global} declaration, +as the following example illustrates: @verbatim{ X = 1 -- Ok, global by default do global Y -- voids implicit initial declaration - X = 1 -- ERROR, X not declared Y = 1 -- Ok, Y declared as global + X = 1 -- ERROR, X not declared end X = 2 -- Ok, global by default again } @@ -1110,9 +1111,9 @@ and cannot be used as names: @index{reserved words} @verbatim{ and break do else elseif end -false for function goto if in -local nil not or repeat return -then true until while +false for function global goto if +in local nil not or repeat +return then true until while } Lua is a case-sensitive language: @@ -1653,7 +1654,8 @@ The declaration for locals can include an initialization: @producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} @producname{stat}@producbody{@Rw{global} attnamelist} @producname{attnamelist}@producbody{ - @bnfNter{Name} attrib @bnfrep{@bnfter{,} @bnfNter{Name} attrib}} + @bnfNter{Name} @bnfopt{attrib} + @bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}} } If present, an initial assignment has the same semantics of a multiple assignment @see{assignment}. @@ -1662,24 +1664,55 @@ Otherwise, all local variables are initialized with @nil. Each variable name may be postfixed by an attribute (a name between angle brackets): @Produc{ -@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}} +@producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}} } There are two possible attributes: @id{const}, which declares a @emph{constant} or @emph{read-only} variable, @index{constant variable} -that is, a variable that cannot be assigned to -after its initialization; +that is, a variable that cannot be used as the left-hand side of an +assignment, and @id{close}, which declares a to-be-closed variable @see{to-be-closed}. A list of variables can contain at most one to-be-closed variable. Only local variables can have the @id{close} attribute. +Lua offers also a collective declaration for global variables: +@Produc{ +@producname{stat}@producbody{@Rw{global} @bnfter{*} @bnfopt{attrib}} +} +This special form implicitly declares +as globals all names not explicitly declared previously. +In particular, +@T{global * } implicitly declares +as read-only globals all names not explicitly declared previously; +see the following example: +@verbatim{ +global X +global * +print(math.pi) -- Ok, 'print' and 'math' are read-only +X = 1 -- Ok, declared as read-write +Y = 1 -- Error, Y is read-only +} + +As noted in @See{globalenv}, +all chunks start with an implicit declaration @T{global *}, +but this preambular declaration becomes void inside +the scope of any other @Rw{global} declaration. +Therefore, a program that does not use global declarations +or start with @T{global *} +has free read-write access to any global; +a program that starts with @T{global * } +has free read-only access to any global; +and a program that starts with any other global declaration +(e.g., @T{global none}) can only refer to declared variables. + Note that, for global variables, -the @emph{read-only} atribute is only a syntactical restriction: +the effect of any declaration is only syntactical: @verbatim{ -global X -X = 1 -- ERROR -_ENV.X = 1 -- Ok -foo() -- 'foo' can freely change the global X +global X , _G +X = 1 -- ERROR +_ENV.X = 1 -- Ok +_G.print(X) -- Ok +foo() -- 'foo' can freely change any global } A chunk is also a block @see{chunks}, @@ -9453,7 +9486,12 @@ change between versions. @itemize{ @item{ -The control variable in @Rw{for} loops are read only. +The word @Rw{global} is a reserved word. +Do not use it as a regular name. +} + +@item{ +The control variable in @Rw{for} loops is read only. If you need to change it, declare a local variable with the same name in the loop body. } @@ -9582,12 +9620,14 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} @OrNL @Rw{global} attnamelist +@OrNL @Rw{global} @bnfter{*} @bnfopt{attrib} } @producname{attnamelist}@producbody{ - @bnfNter{Name} attrib @bnfrep{@bnfter{,} @bnfNter{Name} attrib}} + @bnfNter{Name} @bnfopt{attrib} + @bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}} -@producname{attrib}@producbody{@bnfopt{@bnfter{<} @bnfNter{Name} @bnfter{>}}} +@producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}} @producname{retstat}@producbody{@Rw{return} @bnfopt{explist} @bnfopt{@bnfter{;}}} diff --git a/testes/all.lua b/testes/all.lua index 5c7ebfa5bf..499c100d76 100755 --- a/testes/all.lua +++ b/testes/all.lua @@ -2,6 +2,10 @@ -- $Id: testes/all.lua $ -- See Copyright Notice in file lua.h +global * + +global _soft, _port, _nomsg +global T local version = "Lua 5.5" if _VERSION ~= version then @@ -34,7 +38,7 @@ if usertests then end -- tests should require debug when needed -debug = nil +global debug; debug = nil if usertests then @@ -71,7 +75,7 @@ do -- ( -- track messages for tests not performed local msgs = {} -function Message (m) +global function Message (m) if not _nomsg then print(m) msgs[#msgs+1] = string.sub(m, 3, -3) diff --git a/testes/calls.lua b/testes/calls.lua index 942fad72e0..0ea1c4ab0d 100644 --- a/testes/calls.lua +++ b/testes/calls.lua @@ -1,6 +1,8 @@ -- $Id: testes/calls.lua $ -- See Copyright Notice in file lua.h +global * + print("testing functions and calls") local debug = require "debug" @@ -22,7 +24,7 @@ assert(not pcall(type)) -- testing local-function recursion -fact = false +global fact; fact = false do local res = 1 local function fact (n) @@ -63,7 +65,7 @@ a.b.c:f2('k', 12); assert(a.b.c.k == 12) print('+') -t = nil -- 'declare' t +global t; t = nil -- 'declare' t function f(a,b,c) local d = 'a'; t={a,b,c,d} end f( -- this line change must be valid @@ -75,7 +77,7 @@ assert(t[1] == 1 and t[2] == 2 and t[3] == 3 and t[4] == 'a') t = nil -- delete 't' -function fat(x) +global function fat(x) if x <= 1 then return 1 else return x*load("return fat(" .. x-1 .. ")", "")() end @@ -107,7 +109,7 @@ end _G.deep = nil -- "declaration" (used by 'all.lua') -function deep (n) +global function deep (n) if n>0 then deep(n-1) end end deep(10) @@ -352,7 +354,7 @@ assert(not load(function () return true end)) -- small bug local t = {nil, "return ", "3"} -f, msg = load(function () return table.remove(t, 1) end) +local f, msg = load(function () return table.remove(t, 1) end) assert(f() == nil) -- should read the empty chunk -- another small bug (in 5.2.1) @@ -388,7 +390,8 @@ assert(load("return _ENV", nil, nil, 123)() == 123) -- load when _ENV is not first upvalue -local x; XX = 123 +global XX; local x +XX = 123 local function h () local y=x -- use 'x', so that it becomes 1st upvalue return XX -- global name diff --git a/testes/closure.lua b/testes/closure.lua index d3b9f6216a..c55d15838f 100644 --- a/testes/closure.lua +++ b/testes/closure.lua @@ -1,6 +1,8 @@ -- $Id: testes/closure.lua $ -- See Copyright Notice in file lua.h +global * + print "testing closures" do -- bug in 5.4.7 diff --git a/testes/code.lua b/testes/code.lua index 111717cefe..b6ceb34cb3 100644 --- a/testes/code.lua +++ b/testes/code.lua @@ -1,6 +1,8 @@ -- $Id: testes/code.lua $ -- See Copyright Notice in file lua.h +global * + if T==nil then (Message or print)('\n >>> testC not active: skipping opcode tests <<<\n') return @@ -405,8 +407,8 @@ do -- tests for table access in upvalues end -- de morgan -checkequal(function () local a; if not (a or b) then b=a end end, - function () local a; if (not a and not b) then b=a end end) +checkequal(function () local a, b; if not (a or b) then b=a end end, + function () local a, b; if (not a and not b) then b=a end end) checkequal(function (l) local a; return 0 <= a and a <= l end, function (l) local a; return not (not(a >= 0) or not(a <= l)) end) diff --git a/testes/files.lua b/testes/files.lua index a0ae661c40..c2b355fb8d 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -1,6 +1,8 @@ -- $Id: testes/files.lua $ -- See Copyright Notice in file lua.h +global * + local debug = require "debug" local maxint = math.maxinteger @@ -838,13 +840,13 @@ assert(os.date("!\0\0") == "\0\0") local x = string.rep("a", 10000) assert(os.date(x) == x) local t = os.time() -D = os.date("*t", t) +global D; D = os.date("*t", t) assert(os.date(string.rep("%d", 1000), t) == string.rep(os.date("%d", t), 1000)) assert(os.date(string.rep("%", 200)) == string.rep("%", 100)) local function checkDateTable (t) - _G.D = os.date("*t", t) + D = os.date("*t", t) assert(os.time(D) == t) load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and D.hour==%H and D.min==%M and D.sec==%S and diff --git a/testes/goto.lua b/testes/goto.lua index 59713dd78a..3f1f6e6949 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -1,6 +1,10 @@ -- $Id: testes/goto.lua $ -- See Copyright Notice in file lua.h +global require +global print, load, assert, string, setmetatable +global collectgarbage, error + print("testing goto and global declarations") collectgarbage() @@ -254,6 +258,8 @@ assert(testG(5) == 10) do -- test goto's around to-be-closed variable + global * + -- set 'var' and return an object that will reset 'var' when -- it goes out of scope local function newobj (var) @@ -265,16 +271,16 @@ do -- test goto's around to-be-closed variable goto L1 - ::L4:: assert(not X); goto L5 -- varX dead here + ::L4:: assert(not varX); goto L5 -- varX dead here ::L1:: local varX = newobj("X") - assert(X); goto L2 -- varX alive here + assert(varX); goto L2 -- varX alive here ::L3:: - assert(X); goto L4 -- varX alive here + assert(varX); goto L4 -- varX alive here - ::L2:: assert(X); goto L3 -- varX alive here + ::L2:: assert(varX); goto L3 -- varX alive here ::L5:: -- return end @@ -285,8 +291,7 @@ foo() -------------------------------------------------------------------------- do - global print, load, T; global assert - global string + global T local function checkerr (code, err) local st, msg = load(code) @@ -299,6 +304,7 @@ do -- global variables cannot be to-be-closed checkerr("global X", "cannot be") + checkerr("global * ", "cannot be") do local X = 10 @@ -349,6 +355,12 @@ do return end ]], "%:2%:") -- correct line in error message + + checkerr([[ + global * ; + print(X) -- Ok to use + Y = 1 -- ERROR + ]], "assign to const variable 'Y'") end diff --git a/testes/literals.lua b/testes/literals.lua index 28995718b7..fecdd6d3b9 100644 --- a/testes/literals.lua +++ b/testes/literals.lua @@ -3,6 +3,8 @@ print('testing scanner') +global * + local debug = require "debug" diff --git a/testes/locals.lua b/testes/locals.lua index 421595bb9b..99ff9edc5e 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -1,6 +1,8 @@ -- $Id: testes/locals.lua $ -- See Copyright Notice in file lua.h +global * + print('testing local variables and environments') local debug = require"debug" @@ -39,9 +41,11 @@ f = nil local f local x = 1 -a = nil -load('local a = {}')() -assert(a == nil) +do + global a; a = nil + load('local a = {}')() + assert(a == nil) +end function f (a) local _1, _2, _3, _4, _5 @@ -154,7 +158,7 @@ local _ENV = (function (...) return ... end)(_G, dummy) -- { do local _ENV = {assert=assert}; assert(true) end local mt = {_G = _G} local foo,x -A = false -- "declare" A +global A; A = false -- "declare" A do local _ENV = mt function foo (x) A = x diff --git a/testes/nextvar.lua b/testes/nextvar.lua index 679cb1e4ac..e5a9717841 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua @@ -1,6 +1,8 @@ -- $Id: testes/nextvar.lua $ -- See Copyright Notice in file lua.h +global * + print('testing tables, next, and for') local function checkerror (msg, f, ...) @@ -345,9 +347,6 @@ end local nofind = {} -a,b,c = 1,2,3 -a,b,c = nil - -- next uses always the same iteration function assert(next{} == next{}) @@ -396,7 +395,7 @@ for i=0,10000 do end end -n = {n=0} +local n = {n=0} for i,v in pairs(a) do n.n = n.n+1 assert(i and v and a[i] == v) diff --git a/testes/pm.lua b/testes/pm.lua index ab19eb5db8..1700ca2c23 100644 --- a/testes/pm.lua +++ b/testes/pm.lua @@ -6,6 +6,8 @@ print('testing pattern matching') +global * + local function checkerror (msg, f, ...) local s, err = pcall(f, ...) assert(not s and string.find(err, msg)) diff --git a/testes/strings.lua b/testes/strings.lua index ce28e4c560..455398c3f5 100644 --- a/testes/strings.lua +++ b/testes/strings.lua @@ -3,6 +3,7 @@ -- ISO Latin encoding +global * print('testing strings and string library') diff --git a/testes/utf8.lua b/testes/utf8.lua index d0c0184d34..ec9b706ff7 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -3,6 +3,8 @@ -- UTF-8 file +global * + print "testing UTF-8 library" local utf8 = require'utf8' From fded0b4a844990b1a6d0cda1aba25df33eb5f46f Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 13 May 2025 11:50:43 -0300 Subject: [PATCH 066/165] Remove compat code in parser when not needed --- llex.c | 2 +- lparser.c | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/llex.c b/llex.c index edeb48feee..59d927d468 100644 --- a/llex.c +++ b/llex.c @@ -41,7 +41,7 @@ #define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') #if defined(LUA_COMPAT_GLOBAL) -#define GLOBALLEX ".g" /* not recognizable by the scanner */ +#define GLOBALLEX ".g" /* anything not recognizable as a name */ #else #define GLOBALLEX "global" #endif diff --git a/lparser.c b/lparser.c index 242bb0010b..27c8a92758 100644 --- a/lparser.c +++ b/lparser.c @@ -1997,6 +1997,7 @@ static void statement (LexState *ls) { gotostat(ls, line); break; } +#if defined(LUA_COMPAT_GLOBAL) case TK_NAME: { /* compatibility code to parse global keyword when "global" is not reserved */ @@ -2008,7 +2009,9 @@ static void statement (LexState *ls) { break; } } /* else... */ - } /* FALLTHROUGH */ + } +#endif + /* FALLTHROUGH */ default: { /* stat -> func | assignment */ exprstat(ls); break; From 3fb7a77731e6140674a6b13b73979256bfb95ce3 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 15 May 2025 12:43:37 -0300 Subject: [PATCH 067/165] Internalized string "break" kept by the parser The parser uses "break" as fake label to compile "break" as "goto break". To avoid producing this string at each use, it keeps it available in its state. --- llex.c | 3 +++ llex.h | 1 + lparser.c | 6 +++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/llex.c b/llex.c index 59d927d468..54e7f343d4 100644 --- a/llex.c +++ b/llex.c @@ -190,6 +190,9 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, ls->lastline = 1; ls->source = source; ls->envn = luaS_newliteral(L, LUA_ENV); /* get env name */ + ls->brkn = luaS_newliteral(L, "break"); /* get "break" name */ + /* "break" cannot be collected, as it is a reserved word" */ + lua_assert(isreserved(ls->brkn)); luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ } diff --git a/llex.h b/llex.h index 078e4d31e5..0dba9d6094 100644 --- a/llex.h +++ b/llex.h @@ -75,6 +75,7 @@ typedef struct LexState { struct Dyndata *dyd; /* dynamic structures used by the parser */ TString *source; /* current source name */ TString *envn; /* environment variable name */ + TString *brkn; /* "break" name (used as a label) */ } LexState; diff --git a/lparser.c b/lparser.c index 27c8a92758..29022bfdb6 100644 --- a/lparser.c +++ b/lparser.c @@ -707,7 +707,7 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { */ static l_noret undefgoto (LexState *ls, Labeldesc *gt) { /* breaks are checked when created, cannot be undefined */ - lua_assert(!eqstr(gt->name, luaS_newliteral(ls->L, "break"))); + lua_assert(!eqstr(gt->name, ls->brkn)); luaK_semerror(ls, "no visible label '%s' for at line %d", getstr(gt->name), gt->line); } @@ -723,7 +723,7 @@ static void leaveblock (FuncState *fs) { removevars(fs, bl->nactvar); /* remove block locals */ lua_assert(bl->nactvar == fs->nactvar); /* back to level on entry */ if (bl->isloop == 2) /* has to fix pending breaks? */ - createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0); + createlabel(ls, ls->brkn, 0, 0); solvegotos(fs, bl); if (bl->previous == NULL) { /* was it the last block? */ if (bl->firstgoto < ls->dyd->gt.n) /* still pending gotos? */ @@ -1497,7 +1497,7 @@ static void breakstat (LexState *ls, int line) { ok: bl->isloop = 2; /* signal that block has pending breaks */ luaX_next(ls); /* skip break */ - newgotoentry(ls, luaS_newliteral(ls->L, "break"), line); + newgotoentry(ls, ls->brkn, line); } From ded2ad2d86f44424c6b6e12bf1b75836cfa9e502 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 16 May 2025 14:51:07 -0300 Subject: [PATCH 068/165] Slightly faster way to check for "global" --- llex.c | 20 ++++++++++---------- llex.h | 1 + lparser.c | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/llex.c b/llex.c index 54e7f343d4..f8bb3ea4b4 100644 --- a/llex.c +++ b/llex.c @@ -40,16 +40,11 @@ #define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') -#if defined(LUA_COMPAT_GLOBAL) -#define GLOBALLEX ".g" /* anything not recognizable as a name */ -#else -#define GLOBALLEX "global" -#endif /* ORDER RESERVED */ static const char *const luaX_tokens [] = { "and", "break", "do", "else", "elseif", - "end", "false", "for", "function", GLOBALLEX, "goto", "if", + "end", "false", "for", "function", "global", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "//", "..", "...", "==", ">=", "<=", "~=", @@ -189,10 +184,15 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, ls->linenumber = 1; ls->lastline = 1; ls->source = source; - ls->envn = luaS_newliteral(L, LUA_ENV); /* get env name */ - ls->brkn = luaS_newliteral(L, "break"); /* get "break" name */ - /* "break" cannot be collected, as it is a reserved word" */ - lua_assert(isreserved(ls->brkn)); + /* all three strings here ("_ENV", "break", "global") were fixed, + so they cannot be collected */ + ls->envn = luaS_newliteral(L, LUA_ENV); /* get env string */ + ls->brkn = luaS_newliteral(L, "break"); /* get "break" string */ +#if defined(LUA_COMPAT_GLOBAL) + /* compatibility mode: "global" is not a reserved word */ + ls->glbn = luaS_newliteral(L, "global"); /* get "global" string */ + ls->glbn->extra = 0; /* mark it as not reserved */ +#endif luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ } diff --git a/llex.h b/llex.h index 0dba9d6094..37016e8a3f 100644 --- a/llex.h +++ b/llex.h @@ -76,6 +76,7 @@ typedef struct LexState { TString *source; /* current source name */ TString *envn; /* environment variable name */ TString *brkn; /* "break" name (used as a label) */ + TString *glbn; /* "global" name (when not a reserved word) */ } LexState; diff --git a/lparser.c b/lparser.c index 29022bfdb6..384ef690c9 100644 --- a/lparser.c +++ b/lparser.c @@ -2001,10 +2001,10 @@ static void statement (LexState *ls) { case TK_NAME: { /* compatibility code to parse global keyword when "global" is not reserved */ - if (strcmp(getstr(ls->t.seminfo.ts), "global") == 0) { + if (ls->t.seminfo.ts == ls->glbn) { /* current = "global"? */ int lk = luaX_lookahead(ls); if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) { - /* 'global ' or 'global *' or 'global function' */ + /* 'global name' or 'global *' or 'global function' */ globalstatfunc(ls, line); break; } From f2c1531e6cacb10926158d8def5fa5841a0f357e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 16 May 2025 15:20:32 -0300 Subject: [PATCH 069/165] Detail Reports errors with "?:?:" (instead of "?:-1:") when there is no debug information. --- ldebug.c | 11 +++++------ testes/errors.lua | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ldebug.c b/ldebug.c index f4bb0a08a9..9110f437bf 100644 --- a/ldebug.c +++ b/ldebug.c @@ -817,16 +817,15 @@ l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { /* add src:line information to 'msg' */ const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, int line) { - char buff[LUA_IDSIZE]; - if (src) { + if (src == NULL) /* no debug information? */ + return luaO_pushfstring(L, "?:?: %s", msg); + else { + char buff[LUA_IDSIZE]; size_t idlen; const char *id = getlstr(src, idlen); luaO_chunkid(buff, id, idlen); + return luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); } - else { /* no source available; use "?" instead */ - buff[0] = '?'; buff[1] = '\0'; - } - return luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); } diff --git a/testes/errors.lua b/testes/errors.lua index 6c76a99a16..a072891366 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -303,14 +303,14 @@ do local f = function (a) return a + 1 end f = assert(load(string.dump(f, true))) assert(f(3) == 4) - checkerr("^%?:%-1:", f, {}) + checkerr("^%?:%?:", f, {}) -- code with a move to a local var ('OP_MOV A B' with A Date: Sun, 18 May 2025 11:43:43 -0300 Subject: [PATCH 070/165] Variable attributes can prefix name list In this format, the attribute applies to all names in the list; e.g. "global print, require, math". --- lparser.c | 53 ++++++++++++++++++++++++++------------------- manual/manual.of | 30 ++++++++++++++----------- testes/all.lua | 2 +- testes/calls.lua | 2 +- testes/closure.lua | 2 +- testes/code.lua | 2 +- testes/files.lua | 2 +- testes/goto.lua | 12 +++++----- testes/literals.lua | 2 +- testes/locals.lua | 22 ++++++++++++++----- testes/math.lua | 7 +++--- testes/nextvar.lua | 2 +- testes/pm.lua | 2 +- testes/strings.lua | 2 +- testes/utf8.lua | 2 +- 15 files changed, 84 insertions(+), 60 deletions(-) diff --git a/lparser.c b/lparser.c index 384ef690c9..bad3592ade 100644 --- a/lparser.c +++ b/lparser.c @@ -1733,7 +1733,7 @@ static void localfunc (LexState *ls) { } -static lu_byte getvarattribute (LexState *ls) { +static lu_byte getvarattribute (LexState *ls, lu_byte df) { /* attrib -> ['<' NAME '>'] */ if (testnext(ls, '<')) { TString *ts = str_checkname(ls); @@ -1746,7 +1746,7 @@ static lu_byte getvarattribute (LexState *ls) { else luaK_semerror(ls, "unknown attribute '%s'", attr); } - return VDKREG; /* regular variable */ + return df; /* return default value */ } @@ -1767,10 +1767,12 @@ static void localstat (LexState *ls) { int nvars = 0; int nexps; expdesc e; - do { - TString *vname = str_checkname(ls); - lu_byte kind = getvarattribute(ls); - vidx = new_varkind(ls, vname, kind); + /* get prefixed attribute (if any); default is regular local variable */ + lu_byte defkind = getvarattribute(ls, VDKREG); + do { /* for each variable */ + TString *vname = str_checkname(ls); /* get its name */ + lu_byte kind = getvarattribute(ls, defkind); /* postfixed attribute */ + vidx = new_varkind(ls, vname, kind); /* predeclare it */ if (kind == RDKTOCLOSE) { /* to-be-closed? */ if (toclose != -1) /* one already present? */ luaK_semerror(ls, "multiple to-be-closed variables in local list"); @@ -1778,13 +1780,13 @@ static void localstat (LexState *ls) { } nvars++; } while (testnext(ls, ',')); - if (testnext(ls, '=')) + if (testnext(ls, '=')) /* initialization? */ nexps = explist(ls, &e); else { e.k = VVOID; nexps = 0; } - var = getlocalvardesc(fs, vidx); /* get last variable */ + var = getlocalvardesc(fs, vidx); /* retrieve last variable */ if (nvars == nexps && /* no adjustments? */ var->vd.kind == RDKCONST && /* last variable is const? */ luaK_exp2const(fs, &e, &var->k)) { /* compile-time constant? */ @@ -1800,29 +1802,35 @@ static void localstat (LexState *ls) { } -static lu_byte getglobalattribute (LexState *ls) { - lu_byte kind = getvarattribute(ls); - if (kind == RDKTOCLOSE) - luaK_semerror(ls, "global variables cannot be to-be-closed"); - /* adjust kind for global variable */ - return (kind == VDKREG) ? GDKREG : GDKCONST; +static lu_byte getglobalattribute (LexState *ls, lu_byte df) { + lu_byte kind = getvarattribute(ls, df); + switch (kind) { + case RDKTOCLOSE: + luaK_semerror(ls, "global variables cannot be to-be-closed"); + break; /* to avoid warnings */ + case RDKCONST: + return GDKCONST; /* adjust kind for global variable */ + default: + return kind; + } } static void globalstat (LexState *ls) { - /* globalstat -> (GLOBAL) '*' attrib - globalstat -> (GLOBAL) NAME attrib {',' NAME attrib} */ + /* globalstat -> (GLOBAL) attrib '*' + globalstat -> (GLOBAL) attrib NAME attrib {',' NAME attrib} */ FuncState *fs = ls->fs; + /* get prefixed attribute (if any); default is regular global variable */ + lu_byte defkind = getglobalattribute(ls, GDKREG); if (testnext(ls, '*')) { - lu_byte kind = getglobalattribute(ls); /* use NULL as name to represent '*' entries */ - new_varkind(ls, NULL, kind); + new_varkind(ls, NULL, defkind); fs->nactvar++; /* activate declaration */ } else { - do { + do { /* list of names */ TString *vname = str_checkname(ls); - lu_byte kind = getglobalattribute(ls); + lu_byte kind = getglobalattribute(ls, defkind); new_varkind(ls, vname, kind); fs->nactvar++; /* activate declaration */ } while (testnext(ls, ',')); @@ -2003,8 +2011,9 @@ static void statement (LexState *ls) { is not reserved */ if (ls->t.seminfo.ts == ls->glbn) { /* current = "global"? */ int lk = luaX_lookahead(ls); - if (lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) { - /* 'global name' or 'global *' or 'global function' */ + if (lk == '<' || lk == TK_NAME || lk == '*' || lk == TK_FUNCTION) { + /* 'global ' or 'global name' or 'global *' or + 'global function' */ globalstatfunc(ls, line); break; } diff --git a/manual/manual.of b/manual/manual.of index effb95da15..eb97e853d1 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1651,43 +1651,47 @@ Function calls are explained in @See{functioncall}. Local and global variables can be declared anywhere inside a block. The declaration for locals can include an initialization: @Produc{ -@producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} +@producname{stat}@producbody{@Rw{local} + attnamelist @bnfopt{@bnfter{=} explist}} @producname{stat}@producbody{@Rw{global} attnamelist} -@producname{attnamelist}@producbody{ - @bnfNter{Name} @bnfopt{attrib} - @bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}} } If present, an initial assignment has the same semantics of a multiple assignment @see{assignment}. Otherwise, all local variables are initialized with @nil. -Each variable name may be postfixed by an attribute -(a name between angle brackets): +The list of names may be prefixed by an attribute +(a name between angle brackets) +and each variable name may be postfixed by an attribute: @Produc{ +@producname{attnamelist}@producbody{ + @bnfopt{attrib} @bnfNter{Name} @bnfopt{attrib} + @bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}} @producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}} } +A prefixed attribute applies to all names in the list; +a postfixed attribute applies to its particular name. There are two possible attributes: @id{const}, which declares a @emph{constant} or @emph{read-only} variable, @index{constant variable} that is, a variable that cannot be used as the left-hand side of an assignment, and @id{close}, which declares a to-be-closed variable @see{to-be-closed}. -A list of variables can contain at most one to-be-closed variable. Only local variables can have the @id{close} attribute. +A list of variables can contain at most one to-be-closed variable. Lua offers also a collective declaration for global variables: @Produc{ -@producname{stat}@producbody{@Rw{global} @bnfter{*} @bnfopt{attrib}} +@producname{stat}@producbody{@Rw{global} @bnfopt{attrib} @bnfter{*}} } This special form implicitly declares as globals all names not explicitly declared previously. In particular, -@T{global * } implicitly declares +@T{global *} implicitly declares as read-only globals all names not explicitly declared previously; see the following example: @verbatim{ global X -global * +global * print(math.pi) -- Ok, 'print' and 'math' are read-only X = 1 -- Ok, declared as read-write Y = 1 -- Error, Y is read-only @@ -1700,7 +1704,7 @@ the scope of any other @Rw{global} declaration. Therefore, a program that does not use global declarations or start with @T{global *} has free read-write access to any global; -a program that starts with @T{global * } +a program that starts with @T{global *} has free read-only access to any global; and a program that starts with any other global declaration (e.g., @T{global none}) can only refer to declared variables. @@ -9620,11 +9624,11 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} @OrNL @Rw{global} attnamelist -@OrNL @Rw{global} @bnfter{*} @bnfopt{attrib} +@OrNL @Rw{global} @bnfopt{attrib} @bnfter{*} } @producname{attnamelist}@producbody{ - @bnfNter{Name} @bnfopt{attrib} + @bnfopt{attrib} @bnfNter{Name} @bnfopt{attrib} @bnfrep{@bnfter{,} @bnfNter{Name} @bnfopt{attrib}}} @producname{attrib}@producbody{@bnfter{<} @bnfNter{Name} @bnfter{>}} diff --git a/testes/all.lua b/testes/all.lua index 499c100d76..d3e2f12368 100755 --- a/testes/all.lua +++ b/testes/all.lua @@ -2,7 +2,7 @@ -- $Id: testes/all.lua $ -- See Copyright Notice in file lua.h -global * +global * global _soft, _port, _nomsg global T diff --git a/testes/calls.lua b/testes/calls.lua index 0ea1c4ab0d..214417014a 100644 --- a/testes/calls.lua +++ b/testes/calls.lua @@ -1,7 +1,7 @@ -- $Id: testes/calls.lua $ -- See Copyright Notice in file lua.h -global * +global * print("testing functions and calls") diff --git a/testes/closure.lua b/testes/closure.lua index c55d15838f..0c2e96c0f1 100644 --- a/testes/closure.lua +++ b/testes/closure.lua @@ -1,7 +1,7 @@ -- $Id: testes/closure.lua $ -- See Copyright Notice in file lua.h -global * +global * print "testing closures" diff --git a/testes/code.lua b/testes/code.lua index b6ceb34cb3..633f48969b 100644 --- a/testes/code.lua +++ b/testes/code.lua @@ -1,7 +1,7 @@ -- $Id: testes/code.lua $ -- See Copyright Notice in file lua.h -global * +global * if T==nil then (Message or print)('\n >>> testC not active: skipping opcode tests <<<\n') diff --git a/testes/files.lua b/testes/files.lua index c2b355fb8d..d4e327b71b 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -1,7 +1,7 @@ -- $Id: testes/files.lua $ -- See Copyright Notice in file lua.h -global * +global * local debug = require "debug" diff --git a/testes/goto.lua b/testes/goto.lua index 3f1f6e6949..44486e2029 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -1,9 +1,9 @@ -- $Id: testes/goto.lua $ -- See Copyright Notice in file lua.h -global require -global print, load, assert, string, setmetatable -global collectgarbage, error +global require +global print, load, assert, string, setmetatable +global collectgarbage, error print("testing goto and global declarations") @@ -304,7 +304,7 @@ do -- global variables cannot be to-be-closed checkerr("global X", "cannot be") - checkerr("global * ", "cannot be") + checkerr("global *", "cannot be") do local X = 10 @@ -345,7 +345,7 @@ do end checkerr([[ - global foo ; + global foo; function foo (x) return end -- ERROR: foo is read-only ]], "assign to const variable 'foo'") @@ -357,7 +357,7 @@ do ]], "%:2%:") -- correct line in error message checkerr([[ - global * ; + global *; print(X) -- Ok to use Y = 1 -- ERROR ]], "assign to const variable 'Y'") diff --git a/testes/literals.lua b/testes/literals.lua index fecdd6d3b9..336ef585c5 100644 --- a/testes/literals.lua +++ b/testes/literals.lua @@ -3,7 +3,7 @@ print('testing scanner') -global * +global * local debug = require "debug" diff --git a/testes/locals.lua b/testes/locals.lua index 99ff9edc5e..02f41980a8 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -1,7 +1,7 @@ -- $Id: testes/locals.lua $ -- See Copyright Notice in file lua.h -global * +global * print('testing local variables and environments') @@ -181,23 +181,25 @@ assert(x==20) A = nil -do -- constants +do print("testing local constants") global assert, load, string, X X = 1 -- not a constant local a, b, c = 10, 20, 30 b = a + c + b -- 'b' is not constant assert(a == 10 and b == 60 and c == 30) + local function checkro (name, code) local st, msg = load(code) local gab = string.format("attempt to assign to const variable '%s'", name) assert(not st and string.find(msg, gab)) end + checkro("y", "local x, y , z = 10, 20, 30; x = 11; y = 12") checkro("x", "local x , y, z = 10, 20, 30; x = 11") checkro("z", "local x , y, z = 10, 20, 30; y = 10; z = 11") - checkro("foo", "local foo = 10; function foo() end") - checkro("foo", "local foo = {}; function foo() end") - checkro("foo", "global foo ; function foo() end") + checkro("foo", "local foo = 10; function foo() end") + checkro("foo", "local foo = {}; function foo() end") + checkro("foo", "global foo ; function foo() end") checkro("XX", "global XX ; XX = 10") checkro("XX", "local _ENV; global XX ; XX = 10") @@ -218,8 +220,18 @@ do -- constants end + print"testing to-be-closed variables" + +do + local st, msg = load("local a, b") + assert(not st and string.find(msg, "multiple")) + + local st, msg = load("local a, b") + assert(not st and string.find(msg, "multiple")) +end + local function stack(n) n = ((n == 0) or stack(n - 1)) end local function func2close (f, x, y) diff --git a/testes/math.lua b/testes/math.lua index 242579b177..0d228d0988 100644 --- a/testes/math.lua +++ b/testes/math.lua @@ -8,11 +8,10 @@ local string = require "string" global none -global print, assert, pcall, type, pairs, load -global tonumber, tostring, select +global print, assert, pcall, type, pairs, load +global tonumber, tostring, select -local minint = math.mininteger -local maxint = math.maxinteger +local minint, maxint = math.mininteger, math.maxinteger local intbits = math.floor(math.log(maxint, 2) + 0.5) + 1 assert((1 << intbits) == 0) diff --git a/testes/nextvar.lua b/testes/nextvar.lua index e5a9717841..03810a8e41 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua @@ -1,7 +1,7 @@ -- $Id: testes/nextvar.lua $ -- See Copyright Notice in file lua.h -global * +global * print('testing tables, next, and for') diff --git a/testes/pm.lua b/testes/pm.lua index 1700ca2c23..720d2a3562 100644 --- a/testes/pm.lua +++ b/testes/pm.lua @@ -6,7 +6,7 @@ print('testing pattern matching') -global * +global * local function checkerror (msg, f, ...) local s, err = pcall(f, ...) diff --git a/testes/strings.lua b/testes/strings.lua index 455398c3f5..46912d4392 100644 --- a/testes/strings.lua +++ b/testes/strings.lua @@ -3,7 +3,7 @@ -- ISO Latin encoding -global * +global * print('testing strings and string library') diff --git a/testes/utf8.lua b/testes/utf8.lua index ec9b706ff7..143c6d3467 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -3,7 +3,7 @@ -- UTF-8 file -global * +global * print "testing UTF-8 library" From 6d53701c7a0dc4736d824fd891ee6f22265d0d68 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Sun, 18 May 2025 12:03:54 -0300 Subject: [PATCH 071/165] Proper error message when jumping into 'global *' A goto cannot jump into the scope of any variable declaration, including 'global *'. To report the error, it needs a "name" for the scope it is entering. --- lparser.c | 6 +++--- manual/manual.of | 2 +- testes/goto.lua | 13 ++++++++----- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lparser.c b/lparser.c index bad3592ade..c134b7a89c 100644 --- a/lparser.c +++ b/lparser.c @@ -542,13 +542,13 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { /* ** Generates an error that a goto jumps into the scope of some -** local variable. +** variable declaration. */ static l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) { TString *tsname = getlocalvardesc(ls->fs, gt->nactvar)->vd.name; - const char *varname = getstr(tsname); + const char *varname = (tsname != NULL) ? getstr(tsname) : "*"; luaK_semerror(ls, - " at line %d jumps into the scope of local '%s'", + " at line %d jumps into the scope of '%s'", getstr(gt->name), gt->line, varname); /* raise the error */ } diff --git a/manual/manual.of b/manual/manual.of index eb97e853d1..a6361fa25b 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1504,7 +1504,7 @@ labels in Lua are considered statements too: A label is visible in the entire block where it is defined, except inside nested functions. A goto can jump to any visible label as long as it does not -enter into the scope of a local variable. +enter into the scope of a variable declaration. A label should not be declared where a previous label with the same name is visible, even if this other label has been declared in an enclosing block. diff --git a/testes/goto.lua b/testes/goto.lua index 44486e2029..d773006102 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -23,15 +23,18 @@ errmsg([[ ::l1:: ::l1:: ]], "label 'l1'") errmsg([[ ::l1:: do ::l1:: end]], "label 'l1'") --- undefined label -errmsg([[ goto l1; local aa ::l1:: ::l2:: print(3) ]], "local 'aa'") --- jumping over variable definition +-- jumping over variable declaration +errmsg([[ goto l1; local aa ::l1:: ::l2:: print(3) ]], "scope of 'aa'") + +errmsg([[ goto l2; global *; ::l1:: ::l2:: print(3) ]], "scope of '*'") + errmsg([[ do local bb, cc; goto l1; end local aa ::l1:: print(3) -]], "local 'aa'") +]], "scope of 'aa'") + -- jumping into a block errmsg([[ do ::l1:: end goto l1 ]], "label 'l1'") @@ -44,7 +47,7 @@ errmsg([[ local xuxu = 10 ::cont:: until xuxu < x -]], "local 'xuxu'") +]], "scope of 'xuxu'") -- simple gotos local x From be05c444818989463dc307eed283503d391f93eb Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 20 May 2025 17:36:05 -0300 Subject: [PATCH 072/165] New way to control preambular declaration Validity of the preambular global declaration in controled together with all declarations, when checking variable names. --- lparser.c | 33 ++++++++++++++++++++------------- lparser.h | 3 +++ testes/db.lua | 6 +++++- testes/goto.lua | 18 +++++++++++++++++- 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/lparser.c b/lparser.c index c134b7a89c..e868e887da 100644 --- a/lparser.c +++ b/lparser.c @@ -54,7 +54,6 @@ typedef struct BlockCnt { lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isloop; /* 1 if 'block' is a loop; 2 if it has pending breaks */ lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ - lu_byte globdec; /* true if inside the scope of any global declaration */ } BlockCnt; @@ -399,22 +398,35 @@ static int newupvalue (FuncState *fs, TString *name, expdesc *v) { /* ** Look for an active variable with the name 'n' in the ** function 'fs'. If found, initialize 'var' with it and return -** its expression kind; otherwise return -1. +** its expression kind; otherwise return -1. While searching, +** var->u.info==-1 means that the preambular global declaration is +** active (the default while there is no other global declaration); +** var->u.info==-2 means there is no active collective declaration +** (some previous global declaration but no collective declaration); +** and var->u.info>=0 points to the inner-most (the first one found) +** collective declaration, if there is one. */ static int searchvar (FuncState *fs, TString *n, expdesc *var) { int i; for (i = cast_int(fs->nactvar) - 1; i >= 0; i--) { Vardesc *vd = getlocalvardesc(fs, i); - if (vd->vd.name == NULL) { /* 'global *'? */ - if (var->u.info == -1) { /* no previous collective declaration? */ - var->u.info = fs->firstlocal + i; /* will use this one as default */ + if (varglobal(vd)) { /* global declaration? */ + if (vd->vd.name == NULL) { /* collective declaration? */ + if (var->u.info < 0) /* no previous collective declaration? */ + var->u.info = fs->firstlocal + i; /* this is the first one */ + } + else { /* global name */ + if (eqstr(n, vd->vd.name)) { /* found? */ + init_exp(var, VGLOBAL, fs->firstlocal + i); + return VGLOBAL; + } + else if (var->u.info == -1) /* active preambular declaration? */ + var->u.info = -2; /* invalidate preambular declaration */ } } else if (eqstr(n, vd->vd.name)) { /* found? */ if (vd->vd.kind == RDKCTC) /* compile-time constant? */ init_exp(var, VCONST, fs->firstlocal + i); - else if (vd->vd.kind == GDKREG || vd->vd.kind == GDKCONST) - init_exp(var, VGLOBAL, fs->firstlocal + i); else /* local variable */ init_var(fs, var, i); return cast_int(var->k); @@ -486,7 +498,7 @@ static void buildvar (LexState *ls, TString *varname, expdesc *var) { expdesc key; int info = var->u.info; /* global by default in the scope of a global declaration? */ - if (info == -1 && fs->bl->globdec) + if (info == -2) luaK_semerror(ls, "variable '%s' not declared", getstr(varname)); singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ if (var->k == VGLOBAL) @@ -692,10 +704,6 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { bl->upval = 0; /* inherit 'insidetbc' from enclosing block */ bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc); - /* inherit 'globdec' from enclosing block or enclosing function */ - bl->globdec = fs->bl != NULL ? fs->bl->globdec - : fs->prev != NULL ? fs->prev->bl->globdec - : 0; /* chunk's first block */ bl->previous = fs->bl; /* link block in function's block list */ fs->bl = bl; lua_assert(fs->freereg == luaY_nvarstack(fs)); @@ -1855,7 +1863,6 @@ static void globalfunc (LexState *ls, int line) { static void globalstatfunc (LexState *ls, int line) { /* stat -> GLOBAL globalfunc | GLOBAL globalstat */ luaX_next(ls); /* skip 'global' */ - ls->fs->bl->globdec = 1; /* in the scope of a global declaration */ if (testnext(ls, TK_FUNCTION)) globalfunc(ls, line); else diff --git a/lparser.h b/lparser.h index 524df6ea74..b08008ce62 100644 --- a/lparser.h +++ b/lparser.h @@ -105,6 +105,9 @@ typedef struct expdesc { /* variables that live in registers */ #define varinreg(v) ((v)->vd.kind <= RDKTOCLOSE) +/* test for global variables */ +#define varglobal(v) ((v)->vd.kind >= GDKREG) + /* description of an active variable */ typedef union Vardesc { diff --git a/testes/db.lua b/testes/db.lua index ae204c4176..0f174f17f7 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -349,9 +349,11 @@ end, "crl") function f(a,b) - global collectgarbage, assert, g, string + -- declare some globals to check that they don't interfere with 'getlocal' + global collectgarbage collectgarbage() local _, x = debug.getlocal(1, 1) + global assert, g, string local _, y = debug.getlocal(1, 2) assert(x == a and y == b) assert(debug.setlocal(2, 3, "pera") == "AA".."AA") @@ -387,7 +389,9 @@ function g (...) f(AAAA,B) assert(AAAA == "pera" and B == "manga") do + global * local B = 13 + global assert local x,y = debug.getlocal(1,5) assert(x == 'B' and y == 13) end diff --git a/testes/goto.lua b/testes/goto.lua index d773006102..7e40fc4faf 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -364,7 +364,23 @@ do print(X) -- Ok to use Y = 1 -- ERROR ]], "assign to const variable 'Y'") - + + checkerr([[ + global *; + Y = X -- Ok to use + global *; + Y = 1 -- ERROR + ]], "assign to const variable 'Y'") + + global * + Y = 10 + assert(_ENV.Y == 10) + global * + local x = Y + global * + Y = x + Y + assert(_ENV.Y == 20) + end print'OK' From c15543b9afa31ab5dc564511ae11acd808405e8f Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 20 May 2025 17:50:56 -0300 Subject: [PATCH 073/165] 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'. --- lopcodes.h | 6 +++--- lparser.c | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lopcodes.h b/lopcodes.h index e3ac9d0969..9787003846 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -254,7 +254,7 @@ OP_SETTABLE,/* A B C R[A][R[B]] := RK(C) */ OP_SETI,/* A B C R[A][B] := RK(C) */ OP_SETFIELD,/* A B C R[A][K[B]:shortstring] := RK(C) */ -OP_NEWTABLE,/* A B C k R[A] := {} */ +OP_NEWTABLE,/* A vB vC k R[A] := {} */ OP_SELF,/* A B C R[A+1] := R[B]; R[A] := R[B][K[C]:shortstring] */ @@ -378,9 +378,9 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ real C = EXTRAARG _ C (the bits of EXTRAARG concatenated with the bits of C). - (*) In OP_NEWTABLE, B is log2 of the hash size (which is always a + (*) In OP_NEWTABLE, vB is log2 of the hash size (which is always a power of 2) plus 1, or zero for size zero. If not k, the array size - is C. Otherwise, the array size is EXTRAARG _ C. + is vC. Otherwise, the array size is EXTRAARG _ vC. (*) For comparisons, k specifies what condition the test should accept (true or false). diff --git a/lparser.c b/lparser.c index e868e887da..992d45bdf3 100644 --- a/lparser.c +++ b/lparser.c @@ -904,12 +904,11 @@ static void recfield (LexState *ls, ConsControl *cc) { FuncState *fs = ls->fs; lu_byte reg = ls->fs->freereg; expdesc tab, key, val; - if (ls->t.token == TK_NAME) { - luaY_checklimit(fs, cc->nh, INT_MAX / 2, "items in a constructor"); + if (ls->t.token == TK_NAME) codename(ls, &key); - } else /* ls->t.token == '[' */ yindex(ls, &key); + luaY_checklimit(fs, cc->nh, INT_MAX / 2, "items in a constructor"); cc->nh++; checknext(ls, '='); tab = *cc->t; From 519c57d597625f010d1bbb3f91bac5d193111060 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 4 Jun 2025 09:54:31 -0300 Subject: [PATCH 074/165] Removed uneeded check in parser In a constructor, each field generates at least one opcode, and the number of opcodes is limited by INT_MAX. Therefore, the counters for number of fields cannot exceed this limit. (The current limit for items in the hash part of a table has a limit smaller than INT_MAX. However, as long as there are no overflows, the logic for table resizing will handle that limit.) --- lparser.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lparser.c b/lparser.c index 992d45bdf3..9abaa374ba 100644 --- a/lparser.c +++ b/lparser.c @@ -908,7 +908,6 @@ static void recfield (LexState *ls, ConsControl *cc) { codename(ls, &key); else /* ls->t.token == '[' */ yindex(ls, &key); - luaY_checklimit(fs, cc->nh, INT_MAX / 2, "items in a constructor"); cc->nh++; checknext(ls, '='); tab = *cc->t; From d05fe48bfdd89956c0ebd115dca0fb115aa28dd6 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 4 Jun 2025 12:55:43 -0300 Subject: [PATCH 075/165] Loading a binary chunk should not break assertions Although the execution of a bad binary chunk can crash the interpreter, simply loading it should be safe. --- lundump.c | 2 +- manual/manual.of | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lundump.c b/lundump.c index fccded7d79..b69ec336c3 100644 --- a/lundump.c +++ b/lundump.c @@ -234,7 +234,7 @@ static void loadConstants (LoadState *S, Proto *f) { f->source = NULL; break; } - default: lua_assert(0); + default: error(S, "invalid constant"); } } } diff --git a/manual/manual.of b/manual/manual.of index a6361fa25b..0d473eed4f 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1402,6 +1402,9 @@ Chunks can also be precompiled into binary form; see the program @idx{luac} and the function @Lid{string.dump} for details. Programs in source and compiled forms are interchangeable; Lua automatically detects the file type and acts accordingly @seeF{load}. +Be aware that, unlike source code, +the execution of maliciously crafted +bytecode can crash the interpreter. } From fd897027f19288ce2cb0249cb8c1818e2f3f1c4c Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 12 Jun 2025 11:15:09 -0300 Subject: [PATCH 076/165] A coroutine can close itself A call to close itself will close all its to-be-closed variables and return to the resume that (re)started the coroutine. --- lcorolib.c | 13 ++++++++-- ldo.c | 10 +++++++ ldo.h | 1 + lstate.c | 2 ++ manual/manual.of | 36 ++++++++++++++++++------- testes/coroutine.lua | 62 +++++++++++++++++++++++++++++++++++++------- 6 files changed, 103 insertions(+), 21 deletions(-) diff --git a/lcorolib.c b/lcorolib.c index 3d95f8735a..5b9736f100 100644 --- a/lcorolib.c +++ b/lcorolib.c @@ -154,8 +154,13 @@ static int luaB_costatus (lua_State *L) { } +static lua_State *getoptco (lua_State *L) { + return (lua_isnone(L, 1) ? L : getco(L)); +} + + static int luaB_yieldable (lua_State *L) { - lua_State *co = lua_isnone(L, 1) ? L : getco(L); + lua_State *co = getoptco(L); lua_pushboolean(L, lua_isyieldable(co)); return 1; } @@ -169,7 +174,7 @@ static int luaB_corunning (lua_State *L) { static int luaB_close (lua_State *L) { - lua_State *co = getco(L); + lua_State *co = getoptco(L); int status = auxstatus(L, co); switch (status) { case COS_DEAD: case COS_YIELD: { @@ -184,6 +189,10 @@ static int luaB_close (lua_State *L) { return 2; } } + case COS_RUN: /* running coroutine? */ + lua_closethread(co, L); /* close itself */ + lua_assert(0); /* previous call does not return */ + return 0; default: /* normal or running coroutine */ return luaL_error(L, "cannot close a %s coroutine", statname[status]); } diff --git a/ldo.c b/ldo.c index 820b5a9ad0..776519dc9d 100644 --- a/ldo.c +++ b/ldo.c @@ -139,6 +139,16 @@ l_noret luaD_throw (lua_State *L, TStatus errcode) { } +l_noret luaD_throwbaselevel (lua_State *L, TStatus errcode) { + if (L->errorJmp) { + /* unroll error entries up to the first level */ + while (L->errorJmp->previous != NULL) + L->errorJmp = L->errorJmp->previous; + } + luaD_throw(L, errcode); +} + + TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { l_uint32 oldnCcalls = L->nCcalls; struct lua_longjmp lj; diff --git a/ldo.h b/ldo.h index 465f4fb8d8..2d4ca8be46 100644 --- a/ldo.h +++ b/ldo.h @@ -91,6 +91,7 @@ LUAI_FUNC void luaD_shrinkstack (lua_State *L); LUAI_FUNC void luaD_inctop (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); LUAI_FUNC TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); #endif diff --git a/lstate.c b/lstate.c index 20ed838f42..70a11aaec6 100644 --- a/lstate.c +++ b/lstate.c @@ -326,6 +326,8 @@ LUA_API int lua_closethread (lua_State *L, lua_State *from) { lua_lock(L); L->nCcalls = (from) ? getCcalls(from) : 0; status = luaE_resetthread(L, L->status); + if (L == from) /* closing itself? */ + luaD_throwbaselevel(L, status); lua_unlock(L); return APIstatus(status); } diff --git a/manual/manual.of b/manual/manual.of index 0d473eed4f..7c504d976e 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -3267,17 +3267,25 @@ when called through this function. Resets a thread, cleaning its call stack and closing all pending to-be-closed variables. -Returns a status code: +The parameter @id{from} represents the coroutine that is resetting @id{L}. +If there is no such coroutine, +this parameter can be @id{NULL}. + +Unless @id{L} is equal to @id{from}, +the call returns a status code: @Lid{LUA_OK} for no errors in the thread (either the original error that stopped the thread or errors in closing methods), or an error status otherwise. In case of error, -leaves the error object on the top of the stack. +the error object is put on the top of the stack. -The parameter @id{from} represents the coroutine that is resetting @id{L}. -If there is no such coroutine, -this parameter can be @id{NULL}. +If @id{L} is equal to @id{from}, +it corresponds to a thread closing itself. +In that case, +the call does not return; +instead, the resume or the protected call +that (re)started the thread returns. } @@ -6939,18 +6947,26 @@ which come inside the table @defid{coroutine}. See @See{coroutine} for a general description of coroutines. -@LibEntry{coroutine.close (co)| +@LibEntry{coroutine.close ([co])| Closes coroutine @id{co}, that is, closes all its pending to-be-closed variables and puts the coroutine in a dead state. -The given coroutine must be dead or suspended. -In case of error +The default for @id{co} is the running coroutine. + +The given coroutine must be dead, suspended, +or be the running coroutine. +For the running coroutine, +this function does not return. +Instead, the resume that (re)started the coroutine returns. + +For other coroutines, +in case of error (either the original error that stopped the coroutine or errors in closing methods), -returns @false plus the error object; -otherwise returns @true. +this function returns @false plus the error object; +otherwise ir returns @true. } diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 17f6cebaae..02536ee536 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -156,11 +156,6 @@ do st, msg = coroutine.close(co) assert(st and msg == nil) - - -- cannot close the running coroutine - local st, msg = pcall(coroutine.close, coroutine.running()) - assert(not st and string.find(msg, "running")) - local main = coroutine.running() -- cannot close a "normal" coroutine @@ -169,20 +164,19 @@ do assert(not st and string.find(msg, "normal")) end))() - -- cannot close a coroutine while closing it - do + do -- close a coroutine while closing it local co co = coroutine.create( function() local x = func2close(function() - coroutine.close(co) -- try to close it again + coroutine.close(co) -- close it again end) coroutine.yield(20) end) local st, msg = coroutine.resume(co) assert(st and msg == 20) st, msg = coroutine.close(co) - assert(not st and string.find(msg, "running coroutine")) + assert(st and msg == nil) end -- to-be-closed variables in coroutines @@ -289,6 +283,56 @@ do end +do print("coroutines closing itself") + global coroutine, string, os + global assert, error, pcall + + local X = nil + + local function new () + return coroutine.create(function (what) + + local var = func2close(function (t, err) + if what == "yield" then + coroutine.yield() + elseif what == "error" then + error(200) + else + X = "Ok" + return X + end + end) + + -- do an unprotected call so that coroutine becomes non-yieldable + string.gsub("a", "a", function () + assert(not coroutine.isyieldable()) + -- do protected calls while non-yieldable, to add recovery + -- entries (setjmp) to the stack + assert(pcall(pcall, function () + -- 'close' works even while non-yieldable + coroutine.close() -- close itself + os.exit(false) -- not reacheable + end)) + end) + end) + end + + local co = new() + local st, msg = coroutine.resume(co, "ret") + assert(st and msg == nil) + assert(X == "Ok") + + local co = new() + local st, msg = coroutine.resume(co, "error") + assert(not st and msg == 200) + + local co = new() + local st, msg = coroutine.resume(co, "yield") + assert(not st and string.find(msg, "attempt to yield")) + +end + + -- yielding across C boundaries local co = coroutine.wrap(function() From e657a48ea5698bbd9982d878eb65e6615ec94f7e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 13 Jun 2025 14:08:38 -0300 Subject: [PATCH 077/165] The main thread cannot be closed No thread started with pcall (instead of resume) can be closed, because coroutine.close would not respect the expected number of results from the protected call. --- lcorolib.c | 3 +++ manual/manual.of | 4 ++-- testes/coroutine.lua | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lcorolib.c b/lcorolib.c index 5b9736f100..23dd844156 100644 --- a/lcorolib.c +++ b/lcorolib.c @@ -190,6 +190,9 @@ static int luaB_close (lua_State *L) { } } case COS_RUN: /* running coroutine? */ + lua_geti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD); /* get main */ + if (lua_tothread(L, -1) == co) + return luaL_error(L, "cannot close main thread"); lua_closethread(co, L); /* close itself */ lua_assert(0); /* previous call does not return */ return 0; diff --git a/manual/manual.of b/manual/manual.of index 7c504d976e..baa33d88a6 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -3284,8 +3284,8 @@ If @id{L} is equal to @id{from}, it corresponds to a thread closing itself. In that case, the call does not return; -instead, the resume or the protected call -that (re)started the thread returns. +instead, the resume that (re)started the thread returns. +The thread must be running inside a resume. } diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 02536ee536..4881d96478 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -158,6 +158,11 @@ do local main = coroutine.running() + -- cannot close 'main' + local st, msg = pcall(coroutine.close, main); + assert(not st and string.find(msg, "main")) + + -- cannot close a "normal" coroutine ;(coroutine.wrap(function () local st, msg = pcall(coroutine.close, main) From 0cecf1ab6d76e6a7d200fb01bdd999b61835fe21 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 13 Jun 2025 14:14:50 -0300 Subject: [PATCH 078/165] Dump uses varints also for integer constants Unlike sizes, these constants can be negative, so it encodes those integers into unsigned integers in a way that keeps small numbers small. --- ldump.c | 27 ++++++++++++++++++--------- lundump.c | 21 ++++++++++++--------- testes/code.lua | 18 ++++++++++++++++++ 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/ldump.c b/ldump.c index 79bb1dc9a7..a75b20d247 100644 --- a/ldump.c +++ b/ldump.c @@ -31,7 +31,7 @@ typedef struct { int strip; int status; Table *h; /* table to track saved strings */ - lua_Integer nstr; /* counter for counting saved strings */ + lua_Unsigned nstr; /* counter for counting saved strings */ } DumpState; @@ -87,12 +87,12 @@ static void dumpByte (DumpState *D, int y) { ** size for 'dumpVarint' buffer: each byte can store up to 7 bits. ** (The "+6" rounds up the division.) */ -#define DIBS ((l_numbits(size_t) + 6) / 7) +#define DIBS ((l_numbits(lua_Unsigned) + 6) / 7) /* ** Dumps an unsigned integer using the MSB Varint encoding */ -static void dumpVarint (DumpState *D, size_t x) { +static void dumpVarint (DumpState *D, lua_Unsigned x) { lu_byte buff[DIBS]; unsigned n = 1; buff[DIBS - 1] = x & 0x7f; /* fill least-significant byte */ @@ -103,12 +103,13 @@ static void dumpVarint (DumpState *D, size_t x) { static void dumpSize (DumpState *D, size_t sz) { - dumpVarint(D, sz); + dumpVarint(D, cast(lua_Unsigned, sz)); } + static void dumpInt (DumpState *D, int x) { lua_assert(x >= 0); - dumpVarint(D, cast_sizet(x)); + dumpVarint(D, cast_uint(x)); } @@ -117,8 +118,16 @@ static void dumpNumber (DumpState *D, lua_Number x) { } +/* +** Signed integers are coded to keep small values small. (Coding -1 as +** 0xfff...fff would use too many bytes to save a quite common value.) +** A non-negative x is coded as 2x; a negative x is coded as -2x - 1. +** (0 => 0; -1 => 1; 1 => 2; -2 => 3; 2 => 4; ...) +*/ static void dumpInteger (DumpState *D, lua_Integer x) { - dumpVar(D, x); + lua_Unsigned cx = (x >= 0) ? 2u * l_castS2U(x) + : (2u * ~l_castS2U(x)) + 1; + dumpVarint(D, cx); } @@ -136,8 +145,8 @@ static void dumpString (DumpState *D, TString *ts) { TValue idx; int tag = luaH_getstr(D->h, ts, &idx); if (!tagisempty(tag)) { /* string already saved? */ - dumpSize(D, 1); /* reuse a saved string */ - dumpSize(D, cast_sizet(ivalue(&idx))); /* index of saved string */ + dumpVarint(D, 1); /* reuse a saved string */ + dumpVarint(D, l_castS2U(ivalue(&idx))); /* index of saved string */ } else { /* must write and save the string */ TValue key, value; /* to save the string in the hash */ @@ -147,7 +156,7 @@ static void dumpString (DumpState *D, TString *ts) { dumpVector(D, s, size + 1); /* include ending '\0' */ D->nstr++; /* one more saved string */ setsvalue(D->L, &key, ts); /* the string is the key */ - setivalue(&value, D->nstr); /* its index is the value */ + setivalue(&value, l_castU2S(D->nstr)); /* its index is the value */ luaH_set(D->L, D->h, &key, &value); /* h[ts] = nstr */ /* integer value does not need barrier */ } diff --git a/lundump.c b/lundump.c index b69ec336c3..10528987c0 100644 --- a/lundump.c +++ b/lundump.c @@ -37,7 +37,7 @@ typedef struct { const char *name; Table *h; /* list for string reuse */ size_t offset; /* current position relative to beginning of dump */ - lua_Integer nstr; /* number of strings in the list */ + lua_Unsigned nstr; /* number of strings in the list */ lu_byte fixed; /* dump is fixed in memory */ } LoadState; @@ -94,8 +94,8 @@ static lu_byte loadByte (LoadState *S) { } -static size_t loadVarint (LoadState *S, size_t limit) { - size_t x = 0; +static lua_Unsigned loadVarint (LoadState *S, lua_Unsigned limit) { + lua_Unsigned x = 0; int b; limit >>= 7; do { @@ -127,9 +127,12 @@ static lua_Number loadNumber (LoadState *S) { static lua_Integer loadInteger (LoadState *S) { - lua_Integer x; - loadVar(S, x); - return x; + lua_Unsigned cx = loadVarint(S, LUA_MAXUNSIGNED); + /* decode unsigned to signed */ + if ((cx & 1) != 0) + return l_castU2S(~(cx >> 1)); + else + return l_castU2S(cx >> 1); } @@ -149,9 +152,9 @@ static void loadString (LoadState *S, Proto *p, TString **sl) { return; } else if (size == 1) { /* previously saved string? */ - lua_Integer idx = cast_st2S(loadSize(S)); /* get its index */ + lua_Unsigned idx = loadVarint(S, LUA_MAXUNSIGNED); /* get its index */ TValue stv; - luaH_getint(S->h, idx, &stv); /* get its value */ + luaH_getint(S->h, l_castU2S(idx), &stv); /* get its value */ *sl = ts = tsvalue(&stv); luaC_objbarrier(L, p, ts); return; /* do not save it again */ @@ -175,7 +178,7 @@ static void loadString (LoadState *S, Proto *p, TString **sl) { /* add string to list of saved strings */ S->nstr++; setsvalue(L, &sv, ts); - luaH_setint(L, S->h, S->nstr, &sv); + luaH_setint(L, S->h, l_castU2S(S->nstr), &sv); luaC_objbarrierback(L, obj2gco(S->h), ts); } diff --git a/testes/code.lua b/testes/code.lua index 633f48969b..380ff70c1b 100644 --- a/testes/code.lua +++ b/testes/code.lua @@ -482,5 +482,23 @@ do -- basic check for SETLIST assert(count == 1) end + +do print("testing code for integer limits") + local function checkints (n) + local source = string.format( + "local a = {[true] = 0X%x}; return a[true]", n) + local f = assert(load(source)) + checkKlist(f, {n}) + assert(f() == n) + f = load(string.dump(f)) + assert(f() == n) + end + + checkints(math.maxinteger) + checkints(math.mininteger) + checkints(-1) + +end + print 'OK' From 8cd7ae7da06f54b97f95d6994d6bf47086e4e7eb Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 16 Jun 2025 15:50:12 -0300 Subject: [PATCH 079/165] Simpler code for 'traversetable' Check the mode in a separate function (getmode), instead of using comma expressions inside the 'if' condition. --- lgc.c | 39 ++++++++++++++++++++++++++------------- testes/gc.lua | 5 +++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lgc.c b/lgc.c index f0045dd6f2..e4cbcf0c0a 100644 --- a/lgc.c +++ b/lgc.c @@ -589,25 +589,38 @@ static void traversestrongtable (global_State *g, Table *h) { } -static l_mem traversetable (global_State *g, Table *h) { - const char *weakkey, *weakvalue; +/* +** (result & 1) iff weak values; (result & 2) iff weak keys. +*/ +static int getmode (global_State *g, Table *h) { const TValue *mode = gfasttm(g, h->metatable, TM_MODE); - TString *smode; + if (mode == NULL || !ttisshrstring(mode)) + return 0; /* ignore non-(short)string modes */ + else { + const char *smode = getshrstr(tsvalue(mode)); + const char *weakkey = strchr(smode, 'k'); + const char *weakvalue = strchr(smode, 'v'); + return ((weakkey != NULL) << 1) | (weakvalue != NULL); + } +} + + +static l_mem traversetable (global_State *g, Table *h) { markobjectN(g, h->metatable); - if (mode && ttisshrstring(mode) && /* is there a weak mode? */ - (cast_void(smode = tsvalue(mode)), - cast_void(weakkey = strchr(getshrstr(smode), 'k')), - cast_void(weakvalue = strchr(getshrstr(smode), 'v')), - (weakkey || weakvalue))) { /* is really weak? */ - if (!weakkey) /* strong keys? */ + switch (getmode(g, h)) { + case 0: /* not weak */ + traversestrongtable(g, h); + break; + case 1: /* weak values */ traverseweakvalue(g, h); - else if (!weakvalue) /* strong values? */ + break; + case 2: /* weak keys */ traverseephemeron(g, h, 0); - else /* all weak */ + break; + case 3: /* all weak */ linkgclist(h, g->allweak); /* nothing to traverse now */ + break; } - else /* not weak */ - traversestrongtable(g, h); return 1 + 2*sizenode(h) + h->asize; } diff --git a/testes/gc.lua b/testes/gc.lua index ca8aa1bc51..5d2b3085aa 100644 --- a/testes/gc.lua +++ b/testes/gc.lua @@ -288,6 +288,11 @@ x,y,z=nil collectgarbage() assert(next(a) == string.rep('$', 11)) +do -- invalid mode + local a = setmetatable({}, {__mode = 34}) + collectgarbage() +end + -- 'bug' in 5.1 a = {} From 9386e49a3173b68e8b5a7ba882c4c2faf557b61e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 16 Jun 2025 16:29:32 -0300 Subject: [PATCH 080/165] New metatable in an all-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. --- lgc.c | 7 +++++-- testes/gc.lua | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lgc.c b/lgc.c index e4cbcf0c0a..bbaa5ff7e1 100644 --- a/lgc.c +++ b/lgc.c @@ -617,8 +617,11 @@ static l_mem traversetable (global_State *g, Table *h) { case 2: /* weak keys */ traverseephemeron(g, h, 0); break; - case 3: /* all weak */ - linkgclist(h, g->allweak); /* nothing to traverse now */ + case 3: /* all weak; nothing to traverse */ + if (g->gcstate == GCSpropagate) + linkgclist(h, g->grayagain); /* must visit again its metatable */ + else + linkgclist(h, g->allweak); /* must clear collected entries */ break; } return 1 + 2*sizenode(h) + h->asize; diff --git a/testes/gc.lua b/testes/gc.lua index 5d2b3085aa..62713dac64 100644 --- a/testes/gc.lua +++ b/testes/gc.lua @@ -294,6 +294,16 @@ do -- invalid mode end +if T then -- bug since 5.3: all-weak tables are not being revisited + T.gcstate("propagate") + local t = setmetatable({}, {__mode = "kv"}) + T.gcstate("enteratomic") -- '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 a = {} local t = {x = 10} From f71156744851701b5d5fabdda5061b31e53f8f14 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 17 Jun 2025 11:40:49 -0300 Subject: [PATCH 081/165] Check string indices when loading binary chunk Lua is not religious about that, but it tries to avoid crashes when loading binary chunks. --- lundump.c | 12 ++++++------ manual/manual.of | 12 +++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lundump.c b/lundump.c index 10528987c0..ade4038426 100644 --- a/lundump.c +++ b/lundump.c @@ -154,8 +154,9 @@ static void loadString (LoadState *S, Proto *p, TString **sl) { else if (size == 1) { /* previously saved string? */ lua_Unsigned idx = loadVarint(S, LUA_MAXUNSIGNED); /* get its index */ TValue stv; - luaH_getint(S->h, l_castU2S(idx), &stv); /* get its value */ - *sl = ts = tsvalue(&stv); + if (novariant(luaH_getint(S->h, l_castU2S(idx), &stv)) != LUA_TSTRING) + error(S, "invalid string index"); + *sl = ts = tsvalue(&stv); /* get its value */ luaC_objbarrier(L, p, ts); return; /* do not save it again */ } @@ -394,11 +395,10 @@ LClosure *luaU_undump (lua_State *L, ZIO *Z, const char *name, int fixed) { LoadState S; LClosure *cl; if (*name == '@' || *name == '=') - S.name = name + 1; + name = name + 1; else if (*name == LUA_SIGNATURE[0]) - S.name = "binary string"; - else - S.name = name; + name = "binary string"; + S.name = name; S.L = L; S.Z = Z; S.fixed = cast_byte(fixed); diff --git a/manual/manual.of b/manual/manual.of index baa33d88a6..5bab781b4a 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1403,8 +1403,7 @@ see the program @idx{luac} and the function @Lid{string.dump} for details. Programs in source and compiled forms are interchangeable; Lua automatically detects the file type and acts accordingly @seeF{load}. Be aware that, unlike source code, -the execution of maliciously crafted -bytecode can crash the interpreter. +maliciously crafted binary chunks can crash the interpreter. } @@ -6694,11 +6693,10 @@ It may be the string @St{b} (only @x{binary chunk}s), or @St{bt} (both binary and text). The default is @St{bt}. -It is safe to load malformed binary chunks; -@id{load} signals an appropriate error. -However, -Lua does not check the consistency of the code inside binary chunks; -running maliciously crafted bytecode can crash the interpreter. +Lua does not check the consistency of binary chunks. +Maliciously crafted binary chunks can crash +the interpreter. +You can use the @id{mode} parameter to prevent loading binary chunks. } From 07b009c3712c062957593d0a4fa82e0fe9023024 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 18 Jun 2025 16:45:55 -0300 Subject: [PATCH 082/165] No need to limit variable declarations to 250 Only local variables, which use registers, need this low limit. --- lparser.c | 5 ++--- lparser.h | 4 ++-- testes/errors.lua | 2 +- testes/goto.lua | 38 +++++++++++++++++++++++++++++++++----- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lparser.c b/lparser.c index 9abaa374ba..201dbe8b6a 100644 --- a/lparser.c +++ b/lparser.c @@ -50,7 +50,7 @@ typedef struct BlockCnt { struct BlockCnt *previous; /* chain */ int firstlabel; /* index of first label in this block */ int firstgoto; /* index of first pending goto in this block */ - lu_byte nactvar; /* # active locals outside the block */ + short nactvar; /* number of active declarations at block entry */ lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isloop; /* 1 if 'block' is a loop; 2 if it has pending breaks */ lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ @@ -196,8 +196,6 @@ static int new_varkind (LexState *ls, TString *name, lu_byte kind) { FuncState *fs = ls->fs; Dyndata *dyd = ls->dyd; Vardesc *var; - luaY_checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal, - MAXVARS, "local variables"); luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1, dyd->actvar.size, Vardesc, SHRT_MAX, "variable declarationss"); var = &dyd->actvar.arr[dyd->actvar.n++]; @@ -330,6 +328,7 @@ static void adjustlocalvars (LexState *ls, int nvars) { Vardesc *var = getlocalvardesc(fs, vidx); var->vd.ridx = cast_byte(reglevel++); var->vd.pidx = registerlocalvar(ls, fs, var->vd.name); + luaY_checklimit(fs, reglevel, MAXVARS, "local variables"); } } diff --git a/lparser.h b/lparser.h index b08008ce62..fdbb9b8a0b 100644 --- a/lparser.h +++ b/lparser.h @@ -128,7 +128,7 @@ typedef struct Labeldesc { TString *name; /* label identifier */ int pc; /* position in code */ int line; /* line where it appeared */ - lu_byte nactvar; /* number of active variables in that position */ + short nactvar; /* number of active variables in that position */ lu_byte close; /* true for goto that escapes upvalues */ } Labeldesc; @@ -173,7 +173,7 @@ typedef struct FuncState { int firstlocal; /* index of first local var (in Dyndata array) */ int firstlabel; /* index of first label (in 'dyd->label->arr') */ short ndebugvars; /* number of elements in 'f->locvars' */ - lu_byte nactvar; /* number of active local variables */ + short nactvar; /* number of active variable declarations */ lu_byte nups; /* number of upvalues */ lu_byte freereg; /* first free register */ lu_byte iwthabs; /* instructions issued since last absolute line info */ diff --git a/testes/errors.lua b/testes/errors.lua index a072891366..4230a35249 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -742,7 +742,7 @@ assert(c > 255 and string.find(b, "too many upvalues") and -- local variables s = "\nfunction foo ()\n local " -for j = 1,300 do +for j = 1,200 do s = s.."a"..j..", " end s = s.."b\n" diff --git a/testes/goto.lua b/testes/goto.lua index 7e40fc4faf..3519e75d65 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -293,14 +293,14 @@ end foo() -------------------------------------------------------------------------- +local function checkerr (code, err) + local st, msg = load(code) + assert(not st and string.find(msg, err)) +end + do global T - local function checkerr (code, err) - local st, msg = load(code) - assert(not st and string.find(msg, err)) - end - -- globals must be declared, after a global declaration checkerr("global none; X = 1", "variable 'X'") checkerr("global none; function XX() end", "variable 'XX'") @@ -383,5 +383,33 @@ do end + +do -- Ok to declare hundreds of globals + global table + local code = {} + for i = 1, 1000 do + code[#code + 1] = ";global x" .. i + end + code[#code + 1] = "; return x990" + code = table.concat(code) + _ENV.x990 = 11 + assert(load(code)() == 11) + _ENV.x990 = nil +end + +do -- mixing lots of global/local declarations + global table + local code = {} + for i = 1, 200 do + code[#code + 1] = ";global x" .. i + code[#code + 1] = ";local y" .. i .. "=" .. (2*i) + end + code[#code + 1] = "; return x200 + y200" + code = table.concat(code) + _ENV.x200 = 11 + assert(assert(load(code))() == 2*200 + 11) + _ENV.x200 = nil +end + print'OK' From 30531c291b25243e05a9734033a6a023c18f13ac Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 23 Jun 2025 14:00:21 -0300 Subject: [PATCH 083/165] Refactoring in the use of 'readline' by 'lua.c' More common code for 'readline' loaded statically or dynamically (or not loaded). --- lua.c | 71 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/lua.c b/lua.c index b611cbcace..dfb98e3884 100644 --- a/lua.c +++ b/lua.c @@ -432,32 +432,30 @@ static int handle_luainit (lua_State *L) { /* -** lua_readline defines how to show a prompt and then read a line from -** the standard input. -** lua_saveline defines how to "save" a read line in a "history". -** lua_freeline defines how to free a line read by lua_readline. +** * lua_initreadline initializes the readline system. +** * lua_readline defines how to show a prompt and then read a line from +** the standard input. +** * lua_saveline defines how to "save" a read line in a "history". +** * lua_freeline defines how to free a line read by lua_readline. +** +** If lua_readline is defined, all of them should be defined. */ -#if defined(LUA_USE_READLINE) - -#include -#include -#define lua_initreadline(L) ((void)L, rl_readline_name="lua") -#define lua_readline(b,p) ((void)b, readline(p)) -#define lua_saveline(line) add_history(line) -#define lua_freeline(b) free(b) +#if !defined(lua_readline) /* { */ -#endif +/* Code to use the readline library, either statically or dynamically linked */ +/* pointer to 'readline' function (if any) */ +typedef char *(*l_readlineT) (const char *prompt); +static l_readlineT l_readline = NULL; -#if !defined(lua_readline) /* { */ +/* pointer to 'add_history' function (if any) */ +typedef void (*l_addhistT) (const char *string); +static l_addhistT l_addhist = NULL; -/* pointer to dynamically loaded 'readline' function (if any) */ -typedef char *(*l_readline_t) (const char *prompt); -static l_readline_t l_readline = NULL; static char *lua_readline (char *buff, const char *prompt) { - if (l_readline != NULL) /* is there a dynamic 'readline'? */ + if (l_readline != NULL) /* is there a 'readline'? */ return (*l_readline)(prompt); /* use it */ else { /* emulate 'readline' over 'buff' */ fputs(prompt, stdout); @@ -467,33 +465,38 @@ static char *lua_readline (char *buff, const char *prompt) { } -/* pointer to dynamically loaded 'add_history' function (if any) */ -typedef void (*l_addhist_t) (const char *string); -static l_addhist_t l_addhist = NULL; - static void lua_saveline (const char *line) { - if (l_addhist != NULL) /* is there a dynamic 'add_history'? */ + if (l_addhist != NULL) /* is there an 'add_history'? */ (*l_addhist)(line); /* use it */ /* else nothing to be done */ } static void lua_freeline (char *line) { - if (l_readline != NULL) /* is there a dynamic 'readline'? */ + if (l_readline != NULL) /* is there a 'readline'? */ free(line); /* free line created by it */ /* else 'lua_readline' used an automatic buffer; nothing to free */ } -#if !defined(LUA_USE_DLOPEN) || !defined(LUA_READLINELIB) +#if defined(LUA_USE_READLINE) /* { */ -#define lua_initreadline(L) ((void)L) +/* assume Lua will be linked with '-lreadline' */ +#include +#include + +static void lua_initreadline(lua_State *L) { + UNUSED(L); + rl_readline_name = "lua"; + l_readline = readline; + l_addhist = add_history; +} -#else /* { */ +#elif defined(LUA_USE_DLOPEN) && defined(LUA_READLINELIB) /* }{ */ +/* try to load 'readline' dynamically */ #include - static void lua_initreadline (lua_State *L) { void *lib = dlopen(LUA_READLINELIB, RTLD_NOW | RTLD_LOCAL); if (lib == NULL) @@ -502,14 +505,16 @@ static void lua_initreadline (lua_State *L) { const char **name = cast(const char**, dlsym(lib, "rl_readline_name")); if (name != NULL) *name = "Lua"; - l_readline = cast(l_readline_t, cast_func(dlsym(lib, "readline"))); - if (l_readline == NULL) - lua_warning(L, "unable to load 'readline'", 0); - else - l_addhist = cast(l_addhist_t, cast_func(dlsym(lib, "add_history"))); + l_readline = cast(l_readlineT, cast_func(dlsym(lib, "readline"))); + l_addhist = cast(l_addhistT, cast_func(dlsym(lib, "add_history"))); } } +#else /* }{ */ + +/* no readline; leave function pointers as NULL */ +#define lua_initreadline(L) cast(void, L) + #endif /* } */ #endif /* } */ From 270a58c0629dea1a376f05433e8df383756173a8 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 23 Jun 2025 14:36:32 -0300 Subject: [PATCH 084/165] Application name for 'readline' is "lua", not "Lua" --- lua.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua.c b/lua.c index dfb98e3884..3fe9b7da36 100644 --- a/lua.c +++ b/lua.c @@ -504,7 +504,7 @@ static void lua_initreadline (lua_State *L) { else { const char **name = cast(const char**, dlsym(lib, "rl_readline_name")); if (name != NULL) - *name = "Lua"; + *name = "lua"; l_readline = cast(l_readlineT, cast_func(dlsym(lib, "readline"))); l_addhist = cast(l_addhistT, cast_func(dlsym(lib, "add_history"))); } From f6c627af20e48ae96bd17f4392ca74ce0ae90f36 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 26 Jun 2025 11:45:42 -0300 Subject: [PATCH 085/165] Cast added to 'add_history' MacOS defines 'add_history' with a "wrong" type (it returns 'int' instead of 'void'). --- lua.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua.c b/lua.c index 3fe9b7da36..a90bf29b50 100644 --- a/lua.c +++ b/lua.c @@ -488,8 +488,8 @@ static void lua_freeline (char *line) { static void lua_initreadline(lua_State *L) { UNUSED(L); rl_readline_name = "lua"; - l_readline = readline; - l_addhist = add_history; + l_readline = cast(l_readlineT, readline); + l_addhist = cast(l_addhistT, add_history); } #elif defined(LUA_USE_DLOPEN) && defined(LUA_READLINELIB) /* }{ */ From 1da89da62fac7515937fb0f583b97dd50fdd0cbe Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 27 Jun 2025 14:46:41 -0300 Subject: [PATCH 086/165] Manual updated to version 5.5 --- manual/2html | 6 +++--- manual/manual.of | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/manual/2html b/manual/2html index ac5ea04351..b7afd2a6e4 100755 --- a/manual/2html +++ b/manual/2html @@ -8,11 +8,11 @@ --------------------------------------------------------------- header = [[ - + -Lua 5.4 Reference Manual +Lua 5.5 Reference Manual @@ -23,7 +23,7 @@ header = [[

[Lua logo] -Lua 5.4 Reference Manual +Lua 5.5 Reference Manual

by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes diff --git a/manual/manual.of b/manual/manual.of index 5bab781b4a..bcc8173b4f 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -6908,7 +6908,7 @@ and @St{userdata}. A global variable (not a function) that holds a string containing the running Lua version. -The current value of this variable is @St{Lua 5.4}. +The current value of this variable is @St{Lua 5.5}. } @@ -7154,7 +7154,7 @@ to search for a @N{C loader}. Lua initializes the @N{C path} @Lid{package.cpath} in the same way it initializes the Lua path @Lid{package.path}, -using the environment variable @defid{LUA_CPATH_5_4}, +using the environment variable @defid{LUA_CPATH_5_5}, or the environment variable @defid{LUA_CPATH}, or a default path defined in @id{luaconf.h}. @@ -7223,7 +7223,7 @@ A string with the path used by @Lid{require} to search for a Lua loader. At start-up, Lua initializes this variable with -the value of the environment variable @defid{LUA_PATH_5_4} or +the value of the environment variable @defid{LUA_PATH_5_5} or the environment variable @defid{LUA_PATH} or with a default path defined in @id{luaconf.h}, if those environment variables are not defined. @@ -7594,9 +7594,9 @@ x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) end) -- x="4+5 = 9" -local t = {name="lua", version="5.4"} +local t = {name="lua", version="5.5"} x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) --- x="lua-5.4.tar.gz" +-- x="lua-5.5.tar.gz" } } @@ -9332,7 +9332,7 @@ when the standard input (@id{stdin}) is a terminal, and as @T{lua -} otherwise. When called without the option @T{-E}, -the interpreter checks for an environment variable @defid{LUA_INIT_5_4} +the interpreter checks for an environment variable @defid{LUA_INIT_5_5} (or @defid{LUA_INIT} if the versioned name is not defined) before running any argument. If the variable content has the format @T{@At@rep{filename}}, From cfce6f4b20afe85ede2182b3df3ab2bfcdb0e692 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 27 Jun 2025 14:47:11 -0300 Subject: [PATCH 087/165] Warning in loslib.c (signed-unsigned comparison) --- loslib.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/loslib.c b/loslib.c index 4623ad5ecf..3f605028fe 100644 --- a/loslib.c +++ b/loslib.c @@ -273,7 +273,7 @@ static int getfield (lua_State *L, const char *key, int d, int delta) { static const char *checkoption (lua_State *L, const char *conv, - ptrdiff_t convlen, char *buff) { + size_t convlen, char *buff) { const char *option = LUA_STRFTIMEOPTIONS; unsigned oplen = 1; /* length of options being checked */ for (; *option != '\0' && oplen <= convlen; option += oplen) { @@ -333,7 +333,8 @@ static int os_date (lua_State *L) { size_t reslen; char *buff = luaL_prepbuffsize(&b, SIZETIMEFMT); s++; /* skip '%' */ - s = checkoption(L, s, se - s, cc + 1); /* copy specifier to 'cc' */ + /* copy specifier to 'cc' */ + s = checkoption(L, s, ct_diff2sz(se - s), cc + 1); reslen = strftime(buff, SIZETIMEFMT, cc, stm); luaL_addsize(&b, reslen); } From 59a1adf194efe43741c2bb2005d93d8320a19d14 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 1 Jul 2025 10:57:02 -0300 Subject: [PATCH 088/165] LUAI_MAXSTACK defined privately LUAI_MAXSTACK is limited to INT_MAX/2, so can use INT_MAX/2 to define pseudo-indices (LUA_REGISTRYINDEX) in 'lua.h'. A change in the maximum stack size does not need to change the Lua-C ABI. --- ldo.c | 14 ++++++++++++++ ltests.h | 1 - lua.h | 6 +++--- luaconf.h | 14 -------------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/ldo.c b/ldo.c index 776519dc9d..f232b588a9 100644 --- a/ldo.c +++ b/ldo.c @@ -174,6 +174,20 @@ TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { #define STACKERRSPACE 200 +/* +** LUAI_MAXSTACK limits the size of the Lua stack. +** It must fit into INT_MAX/2. +*/ + +#if !defined(LUAI_MAXSTACK) +#if 1000000 < (INT_MAX / 2) +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK (INT_MAX / 2u) +#endif +#endif + + /* maximum stack size that respects size_t */ #define MAXSTACK_BYSIZET ((MAX_SIZET / sizeof(StackValue)) - STACKERRSPACE) diff --git a/ltests.h b/ltests.h index 43f08162cd..d34e9d421d 100644 --- a/ltests.h +++ b/ltests.h @@ -155,7 +155,6 @@ LUA_API void *debug_realloc (void *ud, void *block, ** Reduce maximum stack size to make stack-overflow tests run faster. ** (But value is still large enough to overflow smaller integers.) */ -#undef LUAI_MAXSTACK #define LUAI_MAXSTACK 68000 diff --git a/lua.h b/lua.h index 95e0db321a..131a8fcb9f 100644 --- a/lua.h +++ b/lua.h @@ -37,10 +37,10 @@ /* ** Pseudo-indices -** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty -** space after that to help overflow detection) +** (The stack size is limited to INT_MAX/2; we keep some free empty +** space after that to help overflow detection.) */ -#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) +#define LUA_REGISTRYINDEX (-(INT_MAX/2 + 1000)) #define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) diff --git a/luaconf.h b/luaconf.h index 51e77547be..bc5fbe9f6f 100644 --- a/luaconf.h +++ b/luaconf.h @@ -763,20 +763,6 @@ ** ===================================================================== */ -/* -@@ LUAI_MAXSTACK limits the size of the Lua stack. -** CHANGE it if you need a different limit. This limit is arbitrary; -** its only purpose is to stop Lua from consuming unlimited stack -** space and to reserve some numbers for pseudo-indices. -** (It must fit into max(int)/2.) -*/ -#if 1000000 < (INT_MAX / 2) -#define LUAI_MAXSTACK 1000000 -#else -#define LUAI_MAXSTACK (INT_MAX / 2u) -#endif - - /* @@ LUA_EXTRASPACE defines the size of a raw memory area associated with ** a Lua state with very fast access. From 03bf7fdd4f3a588cd7ff0a8c51ed68c596d3d575 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 1 Jul 2025 16:07:03 -0300 Subject: [PATCH 089/165] Added missing casts from lua_Unsigned to size_t size_t can be smaller than lua_Usigned. --- lstrlib.c | 4 ++-- lundump.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lstrlib.c b/lstrlib.c index 306cd0bfeb..8056c6ff57 100644 --- a/lstrlib.c +++ b/lstrlib.c @@ -1811,8 +1811,8 @@ static int str_unpack (lua_State *L) { lua_Unsigned len = (lua_Unsigned)unpackint(L, data + pos, h.islittle, cast_int(size), 0); luaL_argcheck(L, len <= ld - pos - size, 2, "data string too short"); - lua_pushlstring(L, data + pos + size, len); - pos += len; /* skip string */ + lua_pushlstring(L, data + pos + size, cast_sizet(len)); + pos += cast_sizet(len); /* skip string */ break; } case Kzstr: { diff --git a/lundump.c b/lundump.c index ade4038426..76f0ddc11b 100644 --- a/lundump.c +++ b/lundump.c @@ -109,7 +109,7 @@ static lua_Unsigned loadVarint (LoadState *S, lua_Unsigned limit) { static size_t loadSize (LoadState *S) { - return loadVarint(S, MAX_SIZE); + return cast_sizet(loadVarint(S, MAX_SIZE)); } From 03d672a95cfd287855b373587f3975165eab9e02 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 7 Jul 2025 15:02:09 -0300 Subject: [PATCH 090/165] Details (comments) --- lapi.c | 2 +- lctype.c | 2 +- lobject.c | 2 +- luaconf.h | 6 +++--- lutf8lib.c | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lapi.c b/lapi.c index 769eba13b6..71405a2584 100644 --- a/lapi.c +++ b/lapi.c @@ -679,7 +679,7 @@ static int auxgetstr (lua_State *L, const TValue *t, const char *k) { /* ** The following function assumes that the registry cannot be a weak -** table, so that en mergency collection while using the global table +** table; so, an emergency collection while using the global table ** cannot collect it. */ static void getGlobalTable (lua_State *L, TValue *gt) { diff --git a/lctype.c b/lctype.c index 9542280942..b1a43e44b0 100644 --- a/lctype.c +++ b/lctype.c @@ -18,7 +18,7 @@ #if defined (LUA_UCID) /* accept UniCode IDentifiers? */ -/* consider all non-ascii codepoints to be alphabetic */ +/* consider all non-ASCII codepoints to be alphabetic */ #define NONA 0x01 #else #define NONA 0x00 /* default */ diff --git a/lobject.c b/lobject.c index 1c32ecf7a9..5c270b274b 100644 --- a/lobject.c +++ b/lobject.c @@ -385,7 +385,7 @@ size_t luaO_str2num (const char *s, TValue *o) { int luaO_utf8esc (char *buff, l_uint32 x) { int n = 1; /* number of bytes put in buffer (backwards) */ lua_assert(x <= 0x7FFFFFFFu); - if (x < 0x80) /* ascii? */ + if (x < 0x80) /* ASCII? */ buff[UTF8BUFFSZ - 1] = cast_char(x); else { /* need continuation bytes */ unsigned int mfb = 0x3f; /* maximum that fits in first byte */ diff --git a/luaconf.h b/luaconf.h index bc5fbe9f6f..0adc9c13f1 100644 --- a/luaconf.h +++ b/luaconf.h @@ -59,7 +59,7 @@ /* -** When Posix DLL ('LUA_USE_DLOPEN') is enabled, the Lua stand-alone +** When POSIX DLL ('LUA_USE_DLOPEN') is enabled, the Lua stand-alone ** application will try to dynamically link a 'readline' facility ** for its REPL. In that case, LUA_READLINELIB is the name of the ** library it will look for those facilities. If lua.c cannot open @@ -76,7 +76,7 @@ #if defined(LUA_USE_MACOSX) #define LUA_USE_POSIX -#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ +#define LUA_USE_DLOPEN /* macOS does not need -ldl */ #define LUA_READLINELIB "libedit.dylib" #endif @@ -88,7 +88,7 @@ #if defined(LUA_USE_C89) && defined(LUA_USE_POSIX) -#error "Posix is not compatible with C89" +#error "POSIX is not compatible with C89" #endif diff --git a/lutf8lib.c b/lutf8lib.c index 4c9784e093..be01613514 100644 --- a/lutf8lib.c +++ b/lutf8lib.c @@ -47,7 +47,7 @@ static lua_Integer u_posrelat (lua_Integer pos, size_t len) { ** Decode one UTF-8 sequence, returning NULL if byte sequence is ** invalid. The array 'limits' stores the minimum value for each ** sequence length, to check for overlong representations. Its first -** entry forces an error for non-ascii bytes with no continuation +** entry forces an error for non-ASCII bytes with no continuation ** bytes (count == 0). */ static const char *utf8_decode (const char *s, l_uint32 *val, int strict) { @@ -55,7 +55,7 @@ static const char *utf8_decode (const char *s, l_uint32 *val, int strict) { {~(l_uint32)0, 0x80, 0x800, 0x10000u, 0x200000u, 0x4000000u}; unsigned int c = (unsigned char)s[0]; l_uint32 res = 0; /* final result */ - if (c < 0x80) /* ascii? */ + if (c < 0x80) /* ASCII? */ res = c; else { int count = 0; /* to count number of continuation bytes */ From 848568790826b7e201f84682185b5b605c473016 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 7 Jul 2025 15:03:45 -0300 Subject: [PATCH 091/165] Correction in definition of CIST_FRESH The cast must be made before the shift. If int has 16 bits, the shift would zero the value and the cast would cast 0 to 0. --- lstate.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lstate.h b/lstate.h index f841c2321e..80df3b0a95 100644 --- a/lstate.h +++ b/lstate.h @@ -85,7 +85,7 @@ typedef struct CallInfo CallInfo; ** they must be visited again at the end of the cycle), but they are ** marked black because assignments to them must activate barriers (to ** move them back to TOUCHED1). -** - Open upvales are kept gray to avoid barriers, but they stay out +** - Open upvalues are kept gray to avoid barriers, but they stay out ** of gray lists. (They don't even have a 'gclist' field.) */ @@ -232,7 +232,7 @@ struct CallInfo { /* call is running a C function (still in first 16 bits) */ #define CIST_C (1u << (CIST_RECST + 3)) /* call is on a fresh "luaV_execute" frame */ -#define CIST_FRESH cast(l_uint32, CIST_C << 1) +#define CIST_FRESH (cast(l_uint32, CIST_C) << 1) /* function is closing tbc variables */ #define CIST_CLSRET (CIST_FRESH << 1) /* function has tbc variables to close */ From 942c10a5e33811a08a290ec15031c950a6d17c99 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 8 Jul 2025 13:33:57 -0300 Subject: [PATCH 092/165] Optional initialization for global declarations --- lparser.c | 81 +++++++++++++++++++++++++++++++------------ manual/manual.of | 18 +++++----- testes/bwcoercion.lua | 2 +- testes/calls.lua | 4 +-- testes/files.lua | 4 +-- testes/goto.lua | 23 +++++++++++- testes/tracegc.lua | 2 +- 7 files changed, 96 insertions(+), 38 deletions(-) diff --git a/lparser.c b/lparser.c index 201dbe8b6a..dde0b6d5e0 100644 --- a/lparser.c +++ b/lparser.c @@ -30,7 +30,7 @@ -/* maximum number of variable declarationss per function (must be +/* maximum number of variable declarations per function (must be smaller than 250, due to the bytecode format) */ #define MAXVARS 200 @@ -197,7 +197,7 @@ static int new_varkind (LexState *ls, TString *name, lu_byte kind) { Dyndata *dyd = ls->dyd; Vardesc *var; luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1, - dyd->actvar.size, Vardesc, SHRT_MAX, "variable declarationss"); + dyd->actvar.size, Vardesc, SHRT_MAX, "variable declarations"); var = &dyd->actvar.arr[dyd->actvar.n++]; var->vd.kind = kind; /* default */ var->vd.name = name; @@ -485,6 +485,20 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { } +static void buildglobal (LexState *ls, TString *varname, expdesc *var) { + FuncState *fs = ls->fs; + expdesc key; + 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_exp2anyregup(fs, var); /* _ENV could be a constant */ + codestring(&key, varname); /* key is variable name */ + luaK_indexed(fs, var, &key); /* 'var' represents _ENV[varname] */ +} + + /* ** Find a variable with the given name 'n', handling global variables ** too. @@ -494,18 +508,11 @@ static void buildvar (LexState *ls, TString *varname, expdesc *var) { init_exp(var, VGLOBAL, -1); /* global by default */ singlevaraux(fs, varname, var, 1); if (var->k == VGLOBAL) { /* global name? */ - expdesc key; int info = var->u.info; /* global by default in the scope of a global declaration? */ if (info == -2) luaK_semerror(ls, "variable '%s' not declared", getstr(varname)); - 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_exp2anyregup(fs, var); /* but could be a constant */ - codestring(&key, varname); /* key is variable name */ - luaK_indexed(fs, var, &key); /* env[varname] */ + buildglobal(ls, varname, var); if (info != -1 && ls->dyd->actvar.arr[info].vd.kind == GDKCONST) var->u.ind.ro = 1; /* mark variable as read-only */ else /* anyway must be a global */ @@ -665,7 +672,7 @@ static void createlabel (LexState *ls, TString *name, int line, int last) { /* -** Traverse the pending goto's of the finishing block checking whether +** Traverse the pending gotos of the finishing block checking whether ** each match some label of that block. Those that do not match are ** "exported" to the outer block, to be solved there. In particular, ** its 'nactvar' is updated with the level of the inner block, @@ -1435,6 +1442,15 @@ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { } } + +/* Create code to store the "top" register in 'var' */ +static void storevartop (FuncState *fs, expdesc *var) { + expdesc e; + init_exp(&e, VNONRELOC, fs->freereg - 1); + luaK_storevar(fs, var, &e); /* will also free the top register */ +} + + /* ** Parse and compile a multiple assignment. The first "variable" ** (a 'suffixedexp') was already read by the caller. @@ -1468,8 +1484,7 @@ static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) { return; /* avoid default */ } } - init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ - luaK_storevar(ls->fs, &lh->v, &e); + storevartop(ls->fs, &lh->v); /* default assignment */ } @@ -1821,25 +1836,45 @@ static lu_byte getglobalattribute (LexState *ls, lu_byte df) { } +static void globalnames (LexState *ls, lu_byte defkind) { + FuncState *fs = ls->fs; + int nvars = 0; + int lastidx; /* index of last registered variable */ + do { /* for each name */ + TString *vname = str_checkname(ls); + lu_byte kind = getglobalattribute(ls, defkind); + lastidx = new_varkind(ls, vname, kind); + nvars++; + } while (testnext(ls, ',')); + if (testnext(ls, '=')) { /* initialization? */ + expdesc e; + int i; + int nexps = explist(ls, &e); /* read list of expressions */ + adjust_assign(ls, nvars, nexps, &e); + for (i = 0; i < nvars; i++) { /* for each variable */ + expdesc var; + TString *varname = getlocalvardesc(fs, lastidx - i)->vd.name; + buildglobal(ls, varname, &var); /* create global variable in 'var' */ + storevartop(fs, &var); + } + } + fs->nactvar = cast_short(fs->nactvar + nvars); /* activate declaration */ +} + + static void globalstat (LexState *ls) { /* globalstat -> (GLOBAL) attrib '*' globalstat -> (GLOBAL) attrib NAME attrib {',' NAME attrib} */ FuncState *fs = ls->fs; /* get prefixed attribute (if any); default is regular global variable */ lu_byte defkind = getglobalattribute(ls, GDKREG); - if (testnext(ls, '*')) { + if (!testnext(ls, '*')) + globalnames(ls, defkind); + else { /* use NULL as name to represent '*' entries */ new_varkind(ls, NULL, defkind); fs->nactvar++; /* activate declaration */ } - else { - do { /* list of names */ - TString *vname = str_checkname(ls); - lu_byte kind = getglobalattribute(ls, defkind); - new_varkind(ls, vname, kind); - fs->nactvar++; /* activate declaration */ - } while (testnext(ls, ',')); - } } @@ -1850,7 +1885,7 @@ static void globalfunc (LexState *ls, int line) { TString *fname = str_checkname(ls); new_varkind(ls, fname, GDKREG); /* declare global variable */ fs->nactvar++; /* enter its scope */ - buildvar(ls, fname, &var); + buildglobal(ls, fname, &var); body(ls, &b, 0, ls->linenumber); /* compile and return closure in 'b' */ luaK_storevar(fs, &var, &b); luaK_fixline(fs, line); /* definition "happens" in the first line */ diff --git a/manual/manual.of b/manual/manual.of index bcc8173b4f..8f90f942ee 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -229,7 +229,7 @@ as the following example illustrates: @verbatim{ X = 1 -- Ok, global by default do - global Y -- voids implicit initial declaration + global Y -- voids the implicit initial declaration Y = 1 -- Ok, Y declared as global X = 1 -- ERROR, X not declared end @@ -269,7 +269,7 @@ print(x) --> 10 (the global one) Notice that, in a declaration like @T{local x = x}, the new @id{x} being declared is not in scope yet, -and so the @id{x} in the left-hand side refers to the outside variable. +and so the @id{x} in the right-hand side refers to the outside variable. Because of the @x{lexical scoping} rules, local variables can be freely accessed by functions @@ -1651,11 +1651,12 @@ Function calls are explained in @See{functioncall}. @sect3{localvar| @title{Variable Declarations} Local and global variables can be declared anywhere inside a block. -The declaration for locals can include an initialization: +The declaration can include an initialization: @Produc{ @producname{stat}@producbody{@Rw{local} attnamelist @bnfopt{@bnfter{=} explist}} -@producname{stat}@producbody{@Rw{global} attnamelist} +@producname{stat}@producbody{@Rw{global} + attnamelist @bnfopt{@bnfter{=} explist}} } If present, an initial assignment has the same semantics of a multiple assignment @see{assignment}. @@ -1712,7 +1713,8 @@ and a program that starts with any other global declaration (e.g., @T{global none}) can only refer to declared variables. Note that, for global variables, -the effect of any declaration is only syntactical: +the effect of any declaration is only syntactical +(except for the optional assignment): @verbatim{ global X , _G X = 1 -- ERROR @@ -3924,8 +3926,8 @@ This macro may evaluate its arguments more than once. } -@APIEntry{unsigned (lua_numbertocstring) (lua_State *L, int idx, - char *buff);| +@APIEntry{unsigned lua_numbertocstring (lua_State *L, int idx, + char *buff);| @apii{0,0,-} Converts the number at acceptable index @id{idx} to a string @@ -4050,7 +4052,7 @@ This function is equivalent to @Lid{lua_pushcclosure} with no upvalues. } -@APIEntry{const char *(lua_pushexternalstring) (lua_State *L, +@APIEntry{const char *lua_pushexternalstring (lua_State *L, const char *s, size_t len, lua_Alloc falloc, void *ud);| @apii{0,1,m} diff --git a/testes/bwcoercion.lua b/testes/bwcoercion.lua index cd735ab0b6..0544944d84 100644 --- a/testes/bwcoercion.lua +++ b/testes/bwcoercion.lua @@ -4,7 +4,7 @@ local strsub = string.sub local print = print -_ENV = nil +global none -- Try to convert a value to an integer, without assuming any coercion. local function toint (x) diff --git a/testes/calls.lua b/testes/calls.lua index 214417014a..0dacb85ab7 100644 --- a/testes/calls.lua +++ b/testes/calls.lua @@ -24,7 +24,7 @@ assert(not pcall(type)) -- testing local-function recursion -global fact; fact = false +global fact = false do local res = 1 local function fact (n) @@ -65,7 +65,7 @@ a.b.c:f2('k', 12); assert(a.b.c.k == 12) print('+') -global t; t = nil -- 'declare' t +global t = nil -- 'declare' t function f(a,b,c) local d = 'a'; t={a,b,c,d} end f( -- this line change must be valid diff --git a/testes/files.lua b/testes/files.lua index d4e327b71b..7146ac7ca2 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -715,7 +715,7 @@ do end -if T and T.nonblock then +if T and T.nonblock and not _port then print("testing failed write") -- unable to write anything to /dev/full @@ -840,7 +840,7 @@ assert(os.date("!\0\0") == "\0\0") local x = string.rep("a", 10000) assert(os.date(x) == x) local t = os.time() -global D; D = os.date("*t", t) +global D = os.date("*t", t) assert(os.date(string.rep("%d", 1000), t) == string.rep(os.date("%d", t), 1000)) assert(os.date(string.rep("%", 200)) == string.rep("%", 100)) diff --git a/testes/goto.lua b/testes/goto.lua index 3519e75d65..3310314d8a 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -380,7 +380,7 @@ do global * Y = x + Y assert(_ENV.Y == 20) - + Y = nil end @@ -411,5 +411,26 @@ do -- mixing lots of global/local declarations _ENV.x200 = nil end +do print "testing initialization in global declarations" + global a, b, c = 10, 20, 30 + assert(_ENV.a == 10 and b == 20 and c == 30) + + global a, b, c = 10 + assert(_ENV.a == 10 and b == nil and c == nil) + + global table + global a, b, c, d = table.unpack{1, 2, 3, 6, 5} + assert(_ENV.a == 1 and b == 2 and c == 3 and d == 6) + + local a, b = 100, 200 + do + global a, b = a, b + end + assert(_ENV.a == 100 and _ENV.b == 200) + + + _ENV.a, _ENV.b, _ENV.c, _ENV.d = nil -- erase these globals +end + print'OK' diff --git a/testes/tracegc.lua b/testes/tracegc.lua index 9c5c1b3f51..a8c929dffd 100644 --- a/testes/tracegc.lua +++ b/testes/tracegc.lua @@ -6,7 +6,7 @@ local M = {} local setmetatable, stderr, collectgarbage = setmetatable, io.stderr, collectgarbage -_ENV = nil +global none local active = false From f65d1f9e02d891733d4ff1cf8d4bc91291e0098e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 8 Jul 2025 15:40:59 -0300 Subject: [PATCH 093/165] lua option '--' may not be followed by script --- lua.c | 3 ++- testes/main.lua | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lua.c b/lua.c index a90bf29b50..66fb74b7b5 100644 --- a/lua.c +++ b/lua.c @@ -303,7 +303,8 @@ static int collectargs (char **argv, int *first) { case '-': /* '--' */ if (argv[i][2] != '\0') /* extra characters after '--'? */ 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; case '\0': /* '-' */ return args; /* script "name" is '-' */ diff --git a/testes/main.lua b/testes/main.lua index eb63d58859..dc48dc485f 100644 --- a/testes/main.lua +++ b/testes/main.lua @@ -90,7 +90,7 @@ prepfile[[ 1, a ) ]] -RUN('lua - < %s > %s', prog, out) +RUN('lua - -- < %s > %s', prog, out) checkout("1\tnil\n") RUN('echo "print(10)\nprint(2)\n" | lua > %s', out) @@ -133,7 +133,7 @@ checkout("-h\n") prepfile("print(package.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") -- test LUA_PATH_version @@ -358,7 +358,7 @@ RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) checkprogout("6\n10\n10\n\n") prepfile("a = [[b\nc\nd\ne]]\na") -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") -- input interrupted in continuation line @@ -488,12 +488,13 @@ assert(not os.remove(out)) -- invalid options NoRun("unrecognized option '-h'", "lua -h") 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 '-iv'", "lua -iv") NoRun("'-e' needs argument", "lua -e") NoRun("syntax error", "lua -e a") NoRun("'-l' needs argument", "lua -l") +NoRun("-i", "lua -- -i") -- handles -i as a script name if T then -- test library? From 85a3c1699c9587a9e952b4ab75b1c6c310ebebea Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 9 Jul 2025 14:40:36 -0300 Subject: [PATCH 094/165] New method to unload DLLs External strings created by DLLs may need the DLL code to be deallocated. This implies that a DLL can only be unloaded after all its strings were deallocated, which happen only after the run of all finalizers. To ensure that order, we create a 'library string' to represent each DLL and keep it locked. When this string is deallocated (after the deallocation of any string created by the DLL) it closes its corresponding DLL. --- loadlib.c | 75 ++++++++++++++++++++++++--------------------- testes/attrib.lua | 8 ++++- testes/libs/lib22.c | 51 ++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 36 deletions(-) diff --git a/loadlib.c b/loadlib.c index 5f0c170296..2cd95ca308 100644 --- a/loadlib.c +++ b/loadlib.c @@ -306,6 +306,16 @@ static void setpath (lua_State *L, const char *fieldname, /* }================================================================== */ +/* +** External strings created by DLLs may need the DLL code to be +** deallocated. This implies that a DLL can only be unloaded after all +** its strings were deallocated. To ensure that, we create a 'library +** string' to represent each DLL, and when this string is deallocated +** it closes its corresponding DLL. +** (The string itself is irrelevant; its userdata is the DLL pointer.) +*/ + + /* ** return registry.CLIBS[path] */ @@ -320,34 +330,41 @@ static void *checkclib (lua_State *L, const char *path) { /* -** registry.CLIBS[path] = plib -- for queries -** registry.CLIBS[#CLIBS + 1] = plib -- also keep a list of all libraries +** Deallocate function for library strings. +** Unload the DLL associated with the string being deallocated. */ -static void addtoclib (lua_State *L, const char *path, void *plib) { - lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); - lua_pushlightuserdata(L, plib); - lua_pushvalue(L, -1); - lua_setfield(L, -3, path); /* CLIBS[path] = plib */ - lua_rawseti(L, -2, luaL_len(L, -2) + 1); /* CLIBS[#CLIBS + 1] = plib */ - lua_pop(L, 1); /* pop CLIBS table */ +static void *freelib (void *ud, void *ptr, size_t osize, size_t nsize) { + /* string itself is irrelevant and static */ + (void)ptr; (void)osize; (void)nsize; + lsys_unloadlib(ud); /* unload library represented by the string */ + return NULL; } /* -** __gc tag method for CLIBS table: calls 'lsys_unloadlib' for all lib -** handles in list CLIBS +** Create a library string that, when deallocated, will unload 'plib' */ -static int gctm (lua_State *L) { - lua_Integer n = luaL_len(L, 1); - for (; n >= 1; n--) { /* for each handle, in reverse order */ - lua_rawgeti(L, 1, n); /* get handle CLIBS[n] */ - lsys_unloadlib(lua_touserdata(L, -1)); - lua_pop(L, 1); /* pop handle */ - } - return 0; +static void createlibstr (lua_State *L, void *plib) { + static const char dummy[] = /* common long body for all library strings */ + "01234567890123456789012345678901234567890123456789"; + lua_pushexternalstring(L, dummy, sizeof(dummy) - 1, freelib, plib); } +/* +** registry.CLIBS[path] = plib -- for queries. +** Also create a reference to strlib, so that the library string will +** only be collected when registry.CLIBS is collected. +*/ +static void addtoclib (lua_State *L, const char *path, void *plib) { + lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); + lua_pushlightuserdata(L, plib); + lua_setfield(L, -2, path); /* CLIBS[path] = plib */ + createlibstr(L, plib); + luaL_ref(L, -2); /* keep library string in CLIBS */ + lua_pop(L, 1); /* pop CLIBS table */ +} + /* error codes for 'lookforfunc' */ #define ERRLIB 1 @@ -361,8 +378,8 @@ static int gctm (lua_State *L) { ** Then, if 'sym' is '*', return true (as library has been loaded). ** Otherwise, look for symbol 'sym' in the library and push a ** C function with that symbol. -** Return 0 and 'true' or a function in the stack; in case of -** errors, return an error code and an error message in the stack. +** Return 0 with 'true' or a function in the stack; in case of +** errors, return an error code with an error message in the stack. */ static int lookforfunc (lua_State *L, const char *path, const char *sym) { void *reg = checkclib(L, path); /* check loaded C libraries */ @@ -704,21 +721,9 @@ static void createsearcherstable (lua_State *L) { } -/* -** create table CLIBS to keep track of loaded C libraries, -** setting a finalizer to close all libraries when closing state. -*/ -static void createclibstable (lua_State *L) { - luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); /* create CLIBS table */ - lua_createtable(L, 0, 1); /* create metatable for CLIBS */ - lua_pushcfunction(L, gctm); - lua_setfield(L, -2, "__gc"); /* set finalizer for CLIBS table */ - lua_setmetatable(L, -2); -} - - LUAMOD_API int luaopen_package (lua_State *L) { - createclibstable(L); + luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); /* create CLIBS table */ + lua_pop(L, 1); /* will not use it now */ luaL_newlib(L, pk_funcs); /* create 'package' table */ createsearcherstable(L); /* set paths */ diff --git a/testes/attrib.lua b/testes/attrib.lua index d8b6e0f3f2..8a3462ea9d 100644 --- a/testes/attrib.lua +++ b/testes/attrib.lua @@ -300,6 +300,12 @@ else assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2") assert(lib2.id("x") == true) -- a different "id" implementation + for _, len in ipairs{0, 10, 39, 40, 41, 1000} do + local str = string.rep("a", len) + local str1 = lib2.newstr(str) + assert(str == str1) + end + -- test C submodules local fs, ext = require"lib1.sub" assert(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1") @@ -447,7 +453,7 @@ do end --- test of large float/integer indices +-- test of large float/integer indices -- compute maximum integer where all bits fit in a float local maxint = math.maxinteger diff --git a/testes/libs/lib22.c b/testes/libs/lib22.c index 8e6565022e..b377cce520 100644 --- a/testes/libs/lib22.c +++ b/testes/libs/lib22.c @@ -1,3 +1,7 @@ +/* implementation for lib2-v2 */ + +#include + #include "lua.h" #include "lauxlib.h" @@ -8,8 +12,54 @@ static int id (lua_State *L) { } +struct STR { + void *ud; + lua_Alloc allocf; +}; + + +static void *t_freestr (void *ud, void *ptr, size_t osize, size_t nsize) { + struct STR *blk = (struct STR*)ptr - 1; + blk->allocf(blk->ud, blk, sizeof(struct STR) + osize, 0); + return NULL; +} + + +static int newstr (lua_State *L) { + size_t len; + const char *str = luaL_checklstring(L, 1, &len); + void *ud; + lua_Alloc allocf = lua_getallocf(L, &ud); + struct STR *blk = (struct STR*)allocf(ud, NULL, 0, + len + 1 + sizeof(struct STR)); + if (blk == NULL) { /* allocation error? */ + lua_pushliteral(L, "not enough memory"); + lua_error(L); /* raise a memory error */ + } + blk->ud = ud; blk->allocf = allocf; + memcpy(blk + 1, str, len + 1); + lua_pushexternalstring(L, (char *)(blk + 1), len, t_freestr, L); + return 1; +} + + +/* +** Create an external string and keep it in the registry, so that it +** will test that the library code is still available (to deallocate +** this string) when closing the state. +*/ +static void initstr (lua_State *L) { + lua_pushcfunction(L, newstr); + lua_pushstring(L, + "012345678901234567890123456789012345678901234567890123456789"); + lua_call(L, 1, 1); /* call newstr("0123...") */ + luaL_ref(L, LUA_REGISTRYINDEX); /* keep string in the registry */ +} + + static const struct luaL_Reg funcs[] = { {"id", id}, + {"newstr", newstr}, {NULL, NULL} }; @@ -18,6 +68,7 @@ LUAMOD_API int luaopen_lib2 (lua_State *L) { lua_settop(L, 2); lua_setglobal(L, "y"); /* y gets 2nd parameter */ lua_setglobal(L, "x"); /* x gets 1st parameter */ + initstr(L); luaL_newlib(L, funcs); return 1; } From c612685d4b9ecdf0525b4d4410efa9f70d4b4518 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 9 Jul 2025 14:43:31 -0300 Subject: [PATCH 095/165] lua.c doesn't use function pointers with LUA_READLINE Bugs in macOS prevent assigning 'add_history' to 'l_addhist' without a warning. --- lua.c | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/lua.c b/lua.c index 66fb74b7b5..b2967a447d 100644 --- a/lua.c +++ b/lua.c @@ -438,13 +438,24 @@ static int handle_luainit (lua_State *L) { ** the standard input. ** * lua_saveline defines how to "save" a read line in a "history". ** * lua_freeline defines how to free a line read by lua_readline. -** -** If lua_readline is defined, all of them should be defined. */ #if !defined(lua_readline) /* { */ +/* Otherwise, all previously listed functions should be defined. */ -/* Code to use the readline library, either statically or dynamically linked */ +#if defined(LUA_USE_READLINE) /* { */ +/* Lua will be linked with '-lreadline' */ + +#include +#include + +#define lua_initreadline(L) ((void)L, rl_readline_name="lua") +#define lua_readline(buff,prompt) ((void)buff, readline(prompt)) +#define lua_saveline(line) add_history(line) +#define lua_freeline(line) free(line) + +#else /* }{ */ +/* use dynamically loaded readline (or nothing) */ /* pointer to 'readline' function (if any) */ typedef char *(*l_readlineT) (const char *prompt); @@ -480,22 +491,9 @@ static void lua_freeline (char *line) { } -#if defined(LUA_USE_READLINE) /* { */ - -/* assume Lua will be linked with '-lreadline' */ -#include -#include - -static void lua_initreadline(lua_State *L) { - UNUSED(L); - rl_readline_name = "lua"; - l_readline = cast(l_readlineT, readline); - l_addhist = cast(l_addhistT, add_history); -} - -#elif defined(LUA_USE_DLOPEN) && defined(LUA_READLINELIB) /* }{ */ - +#if defined(LUA_USE_DLOPEN) && defined(LUA_READLINELIB) /* { */ /* try to load 'readline' dynamically */ + #include static void lua_initreadline (lua_State *L) { @@ -508,15 +506,20 @@ static void lua_initreadline (lua_State *L) { *name = "lua"; l_readline = cast(l_readlineT, cast_func(dlsym(lib, "readline"))); l_addhist = cast(l_addhistT, cast_func(dlsym(lib, "add_history"))); + if (l_readline == NULL) + lua_warning(L, "unable to load 'readline'", 0); } } -#else /* }{ */ +#else /* }{ */ +/* no dlopen or LUA_READLINELIB undefined */ -/* no readline; leave function pointers as NULL */ -#define lua_initreadline(L) cast(void, L) +/* Leave pointers with NULL */ +#define lua_initreadline(L) ((void)L) -#endif /* } */ +#endif /* } */ + +#endif /* } */ #endif /* } */ From 60b6599e8322dd93e3b33c9496ff035a1c45552f Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 15 Jul 2025 14:40:27 -0300 Subject: [PATCH 096/165] Short strings can be external, too That complicates a little object equality (and therefore table access for long strings), but the old behavior was somewhat weird. (Short strings, a concept otherwise absent from the manual, could not be external.) --- loadlib.c | 4 +- lobject.h | 1 + lstring.c | 46 +++++++++----------- lstring.h | 3 +- ltable.c | 82 +++++++++++++++++++++-------------- ltests.c | 6 ++- lvm.c | 106 ++++++++++++++++++++++++++++------------------ manual/manual.of | 12 ++---- testes/attrib.lua | 28 +++++++++--- 9 files changed, 168 insertions(+), 120 deletions(-) diff --git a/loadlib.c b/loadlib.c index 2cd95ca308..8d2e68e261 100644 --- a/loadlib.c +++ b/loadlib.c @@ -345,8 +345,8 @@ static void *freelib (void *ud, void *ptr, size_t osize, size_t nsize) { ** Create a library string that, when deallocated, will unload 'plib' */ static void createlibstr (lua_State *L, void *plib) { - static const char dummy[] = /* common long body for all library strings */ - "01234567890123456789012345678901234567890123456789"; + /* common content for all library strings */ + static const char dummy[] = "01234567890"; lua_pushexternalstring(L, dummy, sizeof(dummy) - 1, freelib, plib); } diff --git a/lobject.h b/lobject.h index bc2f69ab4a..cc3dd370d0 100644 --- a/lobject.h +++ b/lobject.h @@ -418,6 +418,7 @@ typedef struct TString { #define strisshr(ts) ((ts)->shrlen >= 0) +#define isextstr(ts) (ttislngstring(ts) && tsvalue(ts)->shrlen != LSTRREG) /* diff --git a/lstring.c b/lstring.c index b5c8f89f02..17c6fd8f51 100644 --- a/lstring.c +++ b/lstring.c @@ -39,14 +39,14 @@ /* -** equality for long strings +** generic equality for strings */ -int luaS_eqlngstr (TString *a, TString *b) { - size_t len = a->u.lnglen; - lua_assert(a->tt == LUA_VLNGSTR && b->tt == LUA_VLNGSTR); - return (a == b) || /* same instance or... */ - ((len == b->u.lnglen) && /* equal length and ... */ - (memcmp(getlngstr(a), getlngstr(b), len) == 0)); /* equal contents */ +int luaS_eqstr (TString *a, TString *b) { + size_t len1, len2; + const char *s1 = getlstr(a, len1); + const char *s2 = getlstr(b, len2); + return ((len1 == len2) && /* equal length and ... */ + (memcmp(s1, s2, len1) == 0)); /* equal contents */ } @@ -315,28 +315,9 @@ static void f_newext (lua_State *L, void *ud) { } -static void f_pintern (lua_State *L, void *ud) { - struct NewExt *ne = cast(struct NewExt *, ud); - ne->ts = internshrstr(L, ne->s, ne->len); -} - - TString *luaS_newextlstr (lua_State *L, const char *s, size_t len, lua_Alloc falloc, void *ud) { struct NewExt ne; - if (len <= LUAI_MAXSHORTLEN) { /* short string? */ - ne.s = s; ne.len = len; - if (!falloc) - f_pintern(L, &ne); /* just internalize string */ - else { - TStatus status = luaD_rawrunprotected(L, f_pintern, &ne); - (*falloc)(ud, cast_voidp(s), len + 1, 0); /* free external string */ - if (status != LUA_OK) /* memory error? */ - luaM_error(L); /* re-raise memory error */ - } - return ne.ts; - } - /* "normal" case: long strings */ if (!falloc) { ne.kind = LSTRFIX; f_newext(L, &ne); /* just create header */ @@ -357,3 +338,16 @@ TString *luaS_newextlstr (lua_State *L, } +/* +** Normalize an external string: If it is short, internalize it. +*/ +TString *luaS_normstr (lua_State *L, TString *ts) { + size_t len = ts->u.lnglen; + if (len > LUAI_MAXSHORTLEN) + return ts; /* long string; keep the original */ + else { + const char *str = getlngstr(ts); + return internshrstr(L, str, len); + } +} + diff --git a/lstring.h b/lstring.h index 1751e0434e..2eac222b08 100644 --- a/lstring.h +++ b/lstring.h @@ -56,7 +56,7 @@ LUAI_FUNC unsigned luaS_hash (const char *str, size_t l, unsigned seed); LUAI_FUNC unsigned luaS_hashlongstr (TString *ts); -LUAI_FUNC int luaS_eqlngstr (TString *a, TString *b); +LUAI_FUNC int luaS_eqstr (TString *a, TString *b); LUAI_FUNC void luaS_resize (lua_State *L, int newsize); LUAI_FUNC void luaS_clearcache (global_State *g); LUAI_FUNC void luaS_init (lua_State *L); @@ -69,5 +69,6 @@ LUAI_FUNC TString *luaS_createlngstrobj (lua_State *L, size_t l); LUAI_FUNC TString *luaS_newextlstr (lua_State *L, const char *s, size_t len, lua_Alloc falloc, void *ud); LUAI_FUNC size_t luaS_sizelngstr (size_t len, int kind); +LUAI_FUNC TString *luaS_normstr (lua_State *L, TString *ts); #endif diff --git a/ltable.c b/ltable.c index 0b3ec1762c..1bea7affe8 100644 --- a/ltable.c +++ b/ltable.c @@ -234,41 +234,51 @@ l_sinline Node *mainpositionfromnode (const Table *t, Node *nd) { ** Check whether key 'k1' is equal to the key in node 'n2'. This ** equality is raw, so there are no metamethods. Floats with integer ** values have been normalized, so integers cannot be equal to -** floats. It is assumed that 'eqshrstr' is simply pointer equality, so -** that short strings are handled in the default case. -** A true 'deadok' means to accept dead keys as equal to their original -** values. All dead keys are compared in the default case, by pointer -** identity. (Only collectable objects can produce dead keys.) Note that -** dead long strings are also compared by identity. -** Once a key is dead, its corresponding value may be collected, and -** then another value can be created with the same address. If this -** other value is given to 'next', 'equalkey' will signal a false -** positive. In a regular traversal, this situation should never happen, -** as all keys given to 'next' came from the table itself, and therefore -** could not have been collected. Outside a regular traversal, we -** have garbage in, garbage out. What is relevant is that this false -** positive does not break anything. (In particular, 'next' will return -** some other valid item on the table or nil.) +** floats. It is assumed that 'eqshrstr' is simply pointer equality, +** so that short strings are handled in the default case. The flag +** 'deadok' means to accept dead keys as equal to their original values. +** (Only collectable objects can produce dead keys.) Note that dead +** long strings are also compared by identity. Once a key is dead, +** its corresponding value may be collected, and then another value +** can be created with the same address. If this other value is given +** to 'next', 'equalkey' will signal a false positive. In a regular +** traversal, this situation should never happen, as all keys given to +** 'next' came from the table itself, and therefore could not have been +** collected. Outside a regular traversal, we have garbage in, garbage +** out. What is relevant is that this false positive does not break +** anything. (In particular, 'next' will return some other valid item +** on the table or nil.) */ static int equalkey (const TValue *k1, const Node *n2, int deadok) { - if ((rawtt(k1) != keytt(n2)) && /* not the same variants? */ - !(deadok && keyisdead(n2) && iscollectable(k1))) - return 0; /* cannot be same key */ - switch (keytt(n2)) { - case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: - return 1; - case LUA_VNUMINT: - return (ivalue(k1) == keyival(n2)); - case LUA_VNUMFLT: - return luai_numeq(fltvalue(k1), fltvalueraw(keyval(n2))); - case LUA_VLIGHTUSERDATA: - return pvalue(k1) == pvalueraw(keyval(n2)); - case LUA_VLCF: - return fvalue(k1) == fvalueraw(keyval(n2)); - case ctb(LUA_VLNGSTR): - return luaS_eqlngstr(tsvalue(k1), keystrval(n2)); - default: + if (rawtt(k1) != keytt(n2)) { /* not the same variants? */ + if (keyisshrstr(n2) && ttislngstring(k1)) { + /* an external string can be equal to a short-string key */ + return luaS_eqstr(tsvalue(k1), keystrval(n2)); + } + else if (deadok && keyisdead(n2) && iscollectable(k1)) { + /* a collectable value can be equal to a dead key */ return gcvalue(k1) == gcvalueraw(keyval(n2)); + } + else + return 0; /* otherwise, different variants cannot be equal */ + } + else { /* equal variants */ + switch (keytt(n2)) { + case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: + return 1; + case LUA_VNUMINT: + return (ivalue(k1) == keyival(n2)); + case LUA_VNUMFLT: + return luai_numeq(fltvalue(k1), fltvalueraw(keyval(n2))); + case LUA_VLIGHTUSERDATA: + return pvalue(k1) == pvalueraw(keyval(n2)); + case LUA_VLCF: + return fvalue(k1) == fvalueraw(keyval(n2)); + case ctb(LUA_VLNGSTR): + return luaS_eqstr(tsvalue(k1), keystrval(n2)); + default: + return gcvalue(k1) == gcvalueraw(keyval(n2)); + } } } @@ -1158,6 +1168,14 @@ void luaH_finishset (lua_State *L, Table *t, const TValue *key, else if (l_unlikely(luai_numisnan(f))) luaG_runerror(L, "table index is NaN"); } + else if (isextstr(key)) { /* external string? */ + /* If string is short, must internalize it to be used as table key */ + TString *ts = luaS_normstr(L, tsvalue(key)); + setsvalue2s(L, L->top.p++, ts); /* anchor 'ts' (EXTRA_STACK) */ + luaH_newkey(L, t, s2v(L->top.p - 1), value); + L->top.p--; + return; + } luaH_newkey(L, t, key, value); } else if (hres > 0) { /* regular Node? */ diff --git a/ltests.c b/ltests.c index e7bc66dd28..d92cd6c56d 100644 --- a/ltests.c +++ b/ltests.c @@ -1066,8 +1066,12 @@ static int tracegc (lua_State *L) { static int hash_query (lua_State *L) { if (lua_isnone(L, 2)) { + TString *ts; luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "string expected"); - lua_pushinteger(L, cast_int(tsvalue(obj_at(L, 1))->hash)); + ts = tsvalue(obj_at(L, 1)); + if (ts->tt == LUA_VLNGSTR) + luaS_hashlongstr(ts); /* make sure long string has a hash */ + lua_pushinteger(L, cast_int(ts->hash)); } else { TValue *o = obj_at(L, 1); diff --git a/lvm.c b/lvm.c index 97dfe5ee62..7456688826 100644 --- a/lvm.c +++ b/lvm.c @@ -573,52 +573,74 @@ int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) { */ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { const TValue *tm; - if (ttypetag(t1) != ttypetag(t2)) { /* not the same variant? */ - if (ttype(t1) != ttype(t2) || ttype(t1) != LUA_TNUMBER) - return 0; /* only numbers can be equal with different variants */ - else { /* two numbers with different variants */ - /* One of them is an integer. If the other does not have an - integer value, they cannot be equal; otherwise, compare their - integer values. */ - lua_Integer i1, i2; - return (luaV_tointegerns(t1, &i1, F2Ieq) && - luaV_tointegerns(t2, &i2, F2Ieq) && - i1 == i2); + if (ttype(t1) != ttype(t2)) /* not the same type? */ + return 0; + else if (ttypetag(t1) != ttypetag(t2)) { + switch (ttypetag(t1)) { + case LUA_VNUMINT: { /* integer == float? */ + /* integer and float can only be equal if float has an integer + value equal to the integer */ + lua_Integer i2; + return (luaV_flttointeger(fltvalue(t2), &i2, F2Ieq) && + ivalue(t1) == i2); + } + case LUA_VNUMFLT: { /* float == integer? */ + lua_Integer i1; /* see comment in previous case */ + return (luaV_flttointeger(fltvalue(t1), &i1, F2Ieq) && + i1 == ivalue(t2)); + } + case LUA_VSHRSTR: case LUA_VLNGSTR: { + /* compare two strings with different variants: they can be + equal when one string is a short string and the other is + an external string */ + return luaS_eqstr(tsvalue(t1), tsvalue(t2)); + } + default: + /* only numbers (integer/float) and strings (long/short) can have + equal values with different variants */ + return 0; } } - /* values have same type and same variant */ - switch (ttypetag(t1)) { - case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: return 1; - case LUA_VNUMINT: return (ivalue(t1) == ivalue(t2)); - case LUA_VNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); - case LUA_VLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); - case LUA_VLCF: return fvalue(t1) == fvalue(t2); - case LUA_VSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2)); - case LUA_VLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2)); - case LUA_VUSERDATA: { - if (uvalue(t1) == uvalue(t2)) return 1; - else if (L == NULL) return 0; - tm = fasttm(L, uvalue(t1)->metatable, TM_EQ); - if (tm == NULL) - tm = fasttm(L, uvalue(t2)->metatable, TM_EQ); - break; /* will try TM */ + else { /* equal variants */ + switch (ttypetag(t1)) { + case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: + return 1; + case LUA_VNUMINT: + return (ivalue(t1) == ivalue(t2)); + case LUA_VNUMFLT: + return (fltvalue(t1) == fltvalue(t2)); + case LUA_VLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); + case LUA_VSHRSTR: + return eqshrstr(tsvalue(t1), tsvalue(t2)); + case LUA_VLNGSTR: + return luaS_eqstr(tsvalue(t1), tsvalue(t2)); + case LUA_VUSERDATA: { + if (uvalue(t1) == uvalue(t2)) return 1; + else if (L == NULL) return 0; + tm = fasttm(L, uvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, uvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + case LUA_VTABLE: { + if (hvalue(t1) == hvalue(t2)) return 1; + else if (L == NULL) return 0; + tm = fasttm(L, hvalue(t1)->metatable, TM_EQ); + if (tm == NULL) + tm = fasttm(L, hvalue(t2)->metatable, TM_EQ); + break; /* will try TM */ + } + case LUA_VLCF: + return (fvalue(t1) == fvalue(t2)); + default: /* functions and threads */ + return (gcvalue(t1) == gcvalue(t2)); } - case LUA_VTABLE: { - if (hvalue(t1) == hvalue(t2)) return 1; - else if (L == NULL) return 0; - tm = fasttm(L, hvalue(t1)->metatable, TM_EQ); - if (tm == NULL) - tm = fasttm(L, hvalue(t2)->metatable, TM_EQ); - break; /* will try TM */ + if (tm == NULL) /* no TM? */ + return 0; /* objects are different */ + else { + int tag = luaT_callTMres(L, tm, t1, t2, L->top.p); /* call TM */ + return !tagisfalse(tag); } - default: - return gcvalue(t1) == gcvalue(t2); - } - if (tm == NULL) /* no TM? */ - return 0; /* objects are different */ - else { - int tag = luaT_callTMres(L, tm, t1, t2, L->top.p); /* call TM */ - return !tagisfalse(tag); } } diff --git a/manual/manual.of b/manual/manual.of index 8f90f942ee..b276577430 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2419,8 +2419,8 @@ for instance @T{foo(e1, e2, e3)} @see{functioncall}.} @item{A multiple assignment, for instance @T{a , b, c = e1, e2, e3} @see{assignment}.} -@item{A local declaration, -for instance @T{local a , b, c = e1, e2, e3} @see{localvar}.} +@item{A local or global declaration, +which is a special case of 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}.} @@ -2431,8 +2431,7 @@ the list of values from the list of expressions must be @emph{adjusted} to a specific length: the number of parameters in a call to a non-variadic function @see{func-def}, -the number of variables in a multiple assignment or -a local declaration, +the number of variables in a multiple assignment or a declaration, and exactly four values for a generic @rw{for} loop. The @def{adjustment} follows these rules: If there are more values than needed, @@ -4075,11 +4074,6 @@ the string @id{s} as the block, the length plus one (to account for the ending zero) as the old size, and 0 as the new size. -Lua always @x{internalizes} strings with lengths up to 40 characters. -So, for strings in that range, -this function will immediately internalize the string -and call @id{falloc} to free the buffer. - Even when using an external buffer, Lua still has to allocate a header for the string. In case of a memory-allocation error, diff --git a/testes/attrib.lua b/testes/attrib.lua index 8a3462ea9d..f415608699 100644 --- a/testes/attrib.lua +++ b/testes/attrib.lua @@ -300,12 +300,6 @@ else assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2") assert(lib2.id("x") == true) -- a different "id" implementation - for _, len in ipairs{0, 10, 39, 40, 41, 1000} do - local str = string.rep("a", len) - local str1 = lib2.newstr(str) - assert(str == str1) - end - -- test C submodules local fs, ext = require"lib1.sub" assert(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1") @@ -314,11 +308,11 @@ else _ENV.x, _ENV.y = nil end + _ENV = _G -- testing preload - do local p = package package = {} @@ -337,6 +331,26 @@ do assert(type(package.path) == "string") end + +do print("testing external strings") + package.cpath = DC"?" + local lib2 = require"lib2-v2" + local t = {} + for _, len in ipairs{0, 10, 39, 40, 41, 1000} do + local str = string.rep("a", len) + local str1 = lib2.newstr(str) + assert(str == str1) + assert(not T or T.hash(str) == T.hash(str1)) + t[str1] = 20; assert(t[str] == 20 and t[str1] == 20) + t[str] = 10; assert(t[str1] == 10) + local tt = {[str1] = str1} + assert(next(tt) == str1 and next(tt, str1) == nil) + assert(tt[str] == str) + local str2 = lib2.newstr(str1) + assert(str == str2 and t[str2] == 10 and tt[str2] == str) + end +end + print('+') end --] From ccb8b307f11c7497e61f617b12f3a7f0a697256c Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 18 Jul 2025 16:10:28 -0300 Subject: [PATCH 097/165] Correction in utf8.offset Wrong utf-8 character may have no continuation bytes. --- lutf8lib.c | 7 ++++--- testes/utf8.lua | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lutf8lib.c b/lutf8lib.c index be01613514..df49c901d6 100644 --- a/lutf8lib.c +++ b/lutf8lib.c @@ -215,9 +215,10 @@ static int byteoffset (lua_State *L) { } lua_pushinteger(L, posi + 1); /* initial position */ if ((s[posi] & 0x80) != 0) { /* multi-byte character? */ - do { - posi++; - } while (iscontp(s + posi + 1)); /* skip to final byte */ + if (iscont(s[posi])) + return luaL_error(L, "initial position is a continuation byte"); + while (iscontp(s + posi + 1)) + posi++; /* skip to last continuation byte */ } /* else one-byte character: final position is the initial one */ lua_pushinteger(L, posi + 1); /* 'posi' now is the final position */ diff --git a/testes/utf8.lua b/testes/utf8.lua index 143c6d3467..028995a478 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -152,11 +152,20 @@ checkerror("position out of bounds", utf8.offset, "", 1, -1) checkerror("continuation byte", utf8.offset, "𦧺", 1, 2) checkerror("continuation byte", utf8.offset, "𦧺", 1, 2) checkerror("continuation byte", utf8.offset, "\x80", 1) +checkerror("continuation byte", utf8.offset, "\x9c", -1) -- error in indices for len checkerror("out of bounds", utf8.len, "abc", 0, 2) checkerror("out of bounds", utf8.len, "abc", 1, 4) +do -- missing continuation bytes + -- get what is available + local p, e = utf8.offset("\xE0", 1) + assert(p == 1 and e == 1) + local p, e = utf8.offset("\xE0\x9e", -1) + assert(p == 1 and e == 2) +end + local s = "hello World" local t = {string.byte(s, 1, -1)} From 303f4155593721dfd57dadc6e56122e465ce9efb Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 18 Jul 2025 16:18:30 -0300 Subject: [PATCH 098/165] Randomness added to table length computation A bad actor could fill only a few entries in a table (power of twos in decreasing order, see tests) and produce a small table with a huge length. If your program builds a table with external data and iterates over its length, this behavior could be an issue. --- lapi.c | 2 +- lobject.c | 3 ++- ltable.c | 50 ++++++++++++++++++++++++++++------------------ ltable.h | 2 +- lvm.c | 2 +- testes/nextvar.lua | 12 +++++++++++ 6 files changed, 48 insertions(+), 23 deletions(-) diff --git a/lapi.c b/lapi.c index 71405a2584..0c88751a2e 100644 --- a/lapi.c +++ b/lapi.c @@ -440,7 +440,7 @@ LUA_API lua_Unsigned lua_rawlen (lua_State *L, int idx) { case LUA_VSHRSTR: return cast(lua_Unsigned, tsvalue(o)->shrlen); case LUA_VLNGSTR: return cast(lua_Unsigned, tsvalue(o)->u.lnglen); case LUA_VUSERDATA: return cast(lua_Unsigned, uvalue(o)->len); - case LUA_VTABLE: return luaH_getn(hvalue(o)); + case LUA_VTABLE: return luaH_getn(L, hvalue(o)); default: return 0; } } diff --git a/lobject.c b/lobject.c index 5c270b274b..b558cfe0d2 100644 --- a/lobject.c +++ b/lobject.c @@ -31,7 +31,8 @@ /* -** Computes ceil(log2(x)) +** Computes ceil(log2(x)), which is the smallest integer n such that +** x <= (1 << n). */ lu_byte luaO_ceillog2 (unsigned int x) { static const lu_byte log_2[256] = { /* log_2[i - 1] = ceil(log2(i)) */ diff --git a/ltable.c b/ltable.c index 1bea7affe8..4017d8c7d0 100644 --- a/ltable.c +++ b/ltable.c @@ -1220,24 +1220,36 @@ void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) { /* ** Try to find a boundary in the hash part of table 't'. From the -** caller, we know that 'j' is zero or present and that 'j + 1' is -** present. We want to find a larger key that is absent from the -** table, so that we can do a binary search between the two keys to -** find a boundary. We keep doubling 'j' until we get an absent index. -** If the doubling would overflow, we try LUA_MAXINTEGER. If it is -** absent, we are ready for the binary search. ('j', being max integer, -** is larger or equal to 'i', but it cannot be equal because it is -** absent while 'i' is present; so 'j > i'.) Otherwise, 'j' is a -** boundary. ('j + 1' cannot be a present integer key because it is -** not a valid integer in Lua.) +** caller, we know that 'asize + 1' is present. We want to find a larger +** key that is absent from the table, so that we can do a binary search +** between the two keys to find a boundary. We keep doubling 'j' until +** we get an absent index. If the doubling would overflow, we try +** LUA_MAXINTEGER. If it is absent, we are ready for the binary search. +** ('j', being max integer, is larger or equal to 'i', but it cannot be +** equal because it is absent while 'i' is present.) Otherwise, 'j' is a +** boundary. ('j + 1' cannot be a present integer key because it is not +** a valid integer in Lua.) +** About 'rnd': If we used a fixed algorithm, a bad actor could fill +** a table with only the keys that would be probed, in such a way that +** a small table could result in a huge length. To avoid that, we use +** the state's seed as a source of randomness. For the first probe, +** we "randomly double" 'i' by adding to it a random number roughly its +** width. */ -static lua_Unsigned hash_search (Table *t, lua_Unsigned j) { - lua_Unsigned i; - if (j == 0) j++; /* the caller ensures 'j + 1' is present */ - do { +static lua_Unsigned hash_search (lua_State *L, Table *t, unsigned asize) { + lua_Unsigned i = asize + 1; /* caller ensures t[i] is present */ + unsigned rnd = G(L)->seed; + int n = (asize > 0) ? luaO_ceillog2(asize) : 0; /* width of 'asize' */ + unsigned mask = (1u << n) - 1; /* 11...111 with the width of 'asize' */ + unsigned incr = (rnd & mask) + 1; /* first increment (at least 1) */ + lua_Unsigned j = (incr <= l_castS2U(LUA_MAXINTEGER) - i) ? i + incr : i + 1; + rnd >>= n; /* used 'n' bits from 'rnd' */ + while (!hashkeyisempty(t, j)) { /* repeat until an absent t[j] */ i = j; /* 'i' is a present index */ - if (j <= l_castS2U(LUA_MAXINTEGER) / 2) - j *= 2; + if (j <= l_castS2U(LUA_MAXINTEGER)/2 - 1) { + j = j*2 + (rnd & 1); /* try again with 2j or 2j+1 */ + rnd >>= 1; + } else { j = LUA_MAXINTEGER; if (hashkeyisempty(t, j)) /* t[j] not present? */ @@ -1245,7 +1257,7 @@ static lua_Unsigned hash_search (Table *t, lua_Unsigned j) { else /* weird case */ return j; /* well, max integer is a boundary... */ } - } while (!hashkeyisempty(t, j)); /* repeat until an absent t[j] */ + } /* i < j && t[i] present && t[j] absent */ while (j - i > 1u) { /* do a binary search between them */ lua_Unsigned m = (i + j) / 2; @@ -1286,7 +1298,7 @@ static lua_Unsigned newhint (Table *t, unsigned hint) { ** If there is no array part, or its last element is non empty, the ** border may be in the hash part. */ -lua_Unsigned luaH_getn (Table *t) { +lua_Unsigned luaH_getn (lua_State *L, Table *t) { unsigned asize = t->asize; if (asize > 0) { /* is there an array part? */ const unsigned maxvicinity = 4; @@ -1327,7 +1339,7 @@ lua_Unsigned luaH_getn (Table *t) { if (isdummy(t) || hashkeyisempty(t, asize + 1)) return asize; /* 'asize + 1' is empty */ else /* 'asize + 1' is also non empty */ - return hash_search(t, asize); + return hash_search(L, t, asize); } diff --git a/ltable.h b/ltable.h index ca21e69202..f3b7bc7e7e 100644 --- a/ltable.h +++ b/ltable.h @@ -173,7 +173,7 @@ LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, unsigned nasize); LUAI_FUNC lu_mem luaH_size (Table *t); LUAI_FUNC void luaH_free (lua_State *L, Table *t); LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); -LUAI_FUNC lua_Unsigned luaH_getn (Table *t); +LUAI_FUNC lua_Unsigned luaH_getn (lua_State *L, Table *t); #if defined(LUA_DEBUG) diff --git a/lvm.c b/lvm.c index 7456688826..cfdcf97a55 100644 --- a/lvm.c +++ b/lvm.c @@ -722,7 +722,7 @@ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { Table *h = hvalue(rb); tm = fasttm(L, h->metatable, TM_LEN); if (tm) break; /* metamethod? break switch to call it */ - setivalue(s2v(ra), l_castU2S(luaH_getn(h))); /* else primitive len */ + setivalue(s2v(ra), l_castU2S(luaH_getn(L, h))); /* else primitive len */ return; } case LUA_VSHRSTR: { diff --git a/testes/nextvar.lua b/testes/nextvar.lua index 03810a8e41..7e5bed5685 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua @@ -345,6 +345,18 @@ do end end + +do print("testing attack on table length") + local t = {} + local lim = math.floor(math.log(math.maxinteger, 2)) - 1 + for i = lim, 0, -1 do + t[2^i] = true + end + assert(t[1 << lim]) + -- next loop should not take forever + for i = 1, #t do end +end + local nofind = {} From e3716ee161bb5416b5eb846eff6039d61954cfbd Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 23 Jul 2025 18:12:53 -0300 Subject: [PATCH 099/165] Fix in string.rep The cast of n (number of repetitions) to size_t may truncate its value, causing a buffer overflow later. Better to check the buffer size using lua_Integer, as all string lengths must fit in a lua_Integer and n already is a lua_Integer. If everything fits in MAX_SIZE, then we can safely convert n to size_t and compute the buffer size as a size_t. As a corner case, n can be larger than size_t if the strings being repeated have length zero, but in this case it will be multiplied by zero, so an overflow in the cast is irrelevant. --- lstrlib.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lstrlib.c b/lstrlib.c index 8056c6ff57..d9735903d4 100644 --- a/lstrlib.c +++ b/lstrlib.c @@ -132,27 +132,31 @@ static int str_upper (lua_State *L) { } +/* +** MAX_SIZE is limited both by size_t and lua_Integer. +** When x <= MAX_SIZE, x can be safely cast to size_t or lua_Integer. +*/ static int str_rep (lua_State *L) { - size_t l, lsep; - const char *s = luaL_checklstring(L, 1, &l); + size_t len, lsep; + 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, ""); - else if (l_unlikely(l + lsep < l || l + lsep > MAX_SIZE / cast_sizet(n))) + 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"); else { - size_t totallen = ((size_t)n * (l + lsep)) - lsep; + size_t totallen = (cast_sizet(n) * (len + lsep)) - lsep; luaL_Buffer b; char *p = luaL_buffinitsize(L, &b, totallen); while (n-- > 1) { /* first n-1 copies (followed by separator) */ - memcpy(p, s, l * sizeof(char)); p += l; + memcpy(p, s, len * sizeof(char)); p += len; if (lsep > 0) { /* empty 'memcpy' is not that cheap */ - memcpy(p, sep, lsep * sizeof(char)); - p += lsep; + memcpy(p, sep, lsep * sizeof(char)); p += lsep; } } - memcpy(p, s, l * sizeof(char)); /* last copy (not followed by separator) */ + memcpy(p, s, len * sizeof(char)); /* last copy without separator */ luaL_pushresultsize(&b, totallen); } return 1; From c33bb08ffe04f24e09571b59eed3c9b59b622d91 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 29 Jul 2025 11:50:20 -0300 Subject: [PATCH 100/165] Added some casts for 32-bit machines When both 'int' and 'l_obj' have 32 bits, an unsigned int needs a cast to be assigned to 'l_obj'. (As long as 'l_obj' can count the total memory used by the system, these casts should be safe.) --- lgc.c | 2 +- llimits.h | 4 ++-- lobject.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lgc.c b/lgc.c index bbaa5ff7e1..a775b6e510 100644 --- a/lgc.c +++ b/lgc.c @@ -624,7 +624,7 @@ static l_mem traversetable (global_State *g, Table *h) { linkgclist(h, g->allweak); /* must clear collected entries */ break; } - return 1 + 2*sizenode(h) + h->asize; + return cast(l_mem, 1 + 2*sizenode(h) + h->asize); } diff --git a/llimits.h b/llimits.h index 223b5e6c34..c4719a1507 100644 --- a/llimits.h +++ b/llimits.h @@ -20,8 +20,8 @@ /* ** 'l_mem' is a signed integer big enough to count the total memory ** used by Lua. (It is signed due to the use of debt in several -** computations.) Usually, 'ptrdiff_t' should work, but we use 'long' -** for 16-bit machines. +** computations.) 'lu_mem' is a corresponding unsigned type. Usually, +** 'ptrdiff_t' should work, but we use 'long' for 16-bit machines. */ #if defined(LUAI_MEM) /* { external definitions? */ typedef LUAI_MEM l_mem; diff --git a/lobject.c b/lobject.c index b558cfe0d2..763b484609 100644 --- a/lobject.c +++ b/lobject.c @@ -87,7 +87,7 @@ lu_byte luaO_codeparam (unsigned int p) { ** overflow, so we check which order is best. */ l_mem luaO_applyparam (lu_byte p, l_mem x) { - unsigned int m = p & 0xF; /* mantissa */ + int m = p & 0xF; /* mantissa */ int e = (p >> 4); /* exponent */ if (e > 0) { /* normalized? */ e--; /* correct exponent */ From 8fddca81e7d4137512e92f398ca638d61b935dbd Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 29 Jul 2025 14:35:04 -0300 Subject: [PATCH 101/165] 'onelua' can use the test library Just add -DLUA_USER_H='"ltests.h"' when compiling it. --- lauxlib.c | 6 +++++- ltests.c | 32 ++++++++++++++++++++++---------- ltests.h | 4 ++-- onelua.c | 5 +++++ 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/lauxlib.c b/lauxlib.c index 7f33f0addb..adb3851e82 100644 --- a/lauxlib.c +++ b/lauxlib.c @@ -1177,7 +1177,11 @@ LUALIB_API unsigned int luaL_makeseed (lua_State *L) { } -LUALIB_API lua_State *luaL_newstate (void) { +/* +** Use the name with parentheses so that headers can redefine it +** as a macro. +*/ +LUALIB_API lua_State *(luaL_newstate) (void) { lua_State *L = lua_newstate(l_alloc, NULL, luai_makeseed()); if (l_likely(L)) { lua_atpanic(L, &panic); diff --git a/ltests.c b/ltests.c index d92cd6c56d..c4905f9487 100644 --- a/ltests.c +++ b/ltests.c @@ -164,13 +164,13 @@ static void warnf (void *ud, const char *msg, int tocont) { #define MARK 0x55 /* 01010101 (a nice pattern) */ -typedef union Header { +typedef union memHeader { LUAI_MAXALIGN; struct { size_t size; int type; } d; -} Header; +} memHeader; #if !defined(EXTERNMEMCHECK) @@ -193,14 +193,14 @@ Memcontrol l_memcontrol = {0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL}}; -static void freeblock (Memcontrol *mc, Header *block) { +static void freeblock (Memcontrol *mc, memHeader *block) { if (block) { size_t size = block->d.size; int i; for (i = 0; i < MARKSIZE; i++) /* check marks after block */ lua_assert(*(cast_charp(block + 1) + size + i) == MARK); mc->objcount[block->d.type]--; - fillmem(block, sizeof(Header) + size + MARKSIZE); /* erase block */ + fillmem(block, sizeof(memHeader) + size + MARKSIZE); /* erase block */ free(block); /* actually free block */ mc->numblocks--; /* update counts */ mc->total -= size; @@ -210,7 +210,7 @@ static void freeblock (Memcontrol *mc, Header *block) { void *debug_realloc (void *ud, void *b, size_t oldsize, size_t size) { Memcontrol *mc = cast(Memcontrol *, ud); - Header *block = cast(Header *, b); + memHeader *block = cast(memHeader *, b); int type; if (mc->memlimit == 0) { /* first time? */ char *limit = getenv("MEMLIMIT"); /* initialize memory limit */ @@ -241,12 +241,12 @@ void *debug_realloc (void *ud, void *b, size_t oldsize, size_t size) { if (size > oldsize && mc->total+size-oldsize > mc->memlimit) return NULL; /* fake a memory allocation error */ else { - Header *newblock; + memHeader *newblock; int i; size_t commonsize = (oldsize < size) ? oldsize : size; - size_t realsize = sizeof(Header) + size + MARKSIZE; + size_t realsize = sizeof(memHeader) + size + MARKSIZE; if (realsize < size) return NULL; /* arithmetic overflow! */ - newblock = cast(Header *, malloc(realsize)); /* alloc a new block */ + newblock = cast(memHeader *, malloc(realsize)); /* alloc a new block */ if (newblock == NULL) return NULL; /* really out of memory? */ if (block) { @@ -480,7 +480,7 @@ static int lua_checkpc (CallInfo *ci) { } -static void checkstack (global_State *g, lua_State *L1) { +static void check_stack (global_State *g, lua_State *L1) { StkId o; CallInfo *ci; UpVal *uv; @@ -517,7 +517,7 @@ static void checkrefs (global_State *g, GCObject *o) { break; } case LUA_VTHREAD: { - checkstack(g, gco2th(o)); + check_stack(g, gco2th(o)); break; } case LUA_VLCL: { @@ -908,6 +908,17 @@ static int get_limits (lua_State *L) { } +static int get_sizes (lua_State *L) { + lua_newtable(L); + setnameval(L, "Lua state", sizeof(lua_State)); + setnameval(L, "global state", sizeof(global_State)); + setnameval(L, "TValue", sizeof(TValue)); + setnameval(L, "Node", sizeof(Node)); + setnameval(L, "stack Value", sizeof(StackValue)); + return 1; +} + + static int mem_query (lua_State *L) { if (lua_isnone(L, 1)) { lua_pushinteger(L, cast_Integer(l_memcontrol.total)); @@ -2171,6 +2182,7 @@ static const struct luaL_Reg tests_funcs[] = { {"s2d", s2d}, {"sethook", sethook}, {"stacklevel", stacklevel}, + {"sizes", get_sizes}, {"testC", testC}, {"makeCfunc", makeCfunc}, {"totalmem", mem_query}, diff --git a/ltests.h b/ltests.h index d34e9d421d..c825bdcfc1 100644 --- a/ltests.h +++ b/ltests.h @@ -122,14 +122,14 @@ LUA_API int luaB_opentests (lua_State *L); LUA_API void *debug_realloc (void *ud, void *block, size_t osize, size_t nsize); -#if defined(lua_c) + #define luaL_newstate() \ lua_newstate(debug_realloc, &l_memcontrol, luaL_makeseed(NULL)) #define luai_openlibs(L) \ { luaL_openlibs(L); \ luaL_requiref(L, "T", luaB_opentests, 1); \ lua_pop(L, 1); } -#endif + diff --git a/onelua.c b/onelua.c index 2a43496124..cc639494cc 100644 --- a/onelua.c +++ b/onelua.c @@ -110,6 +110,11 @@ #include "linit.c" #endif +/* test library -- used only for internal development */ +#if defined(LUA_DEBUG) +#include "ltests.c" +#endif + /* lua */ #ifdef MAKE_LUA #include "lua.c" From 5b179eaf6a78af5f000d76147af94669d04487b2 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 9 Aug 2025 15:08:53 -0300 Subject: [PATCH 102/165] Details --- lcode.c | 29 +++++++++++++++-------------- lparser.c | 2 +- ltable.c | 2 +- manual/manual.of | 32 ++++++++++++++++++++------------ testes/gengc.lua | 14 ++++++++++++++ 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/lcode.c b/lcode.c index 7ca895f147..cafe265e73 100644 --- a/lcode.c +++ b/lcode.c @@ -565,20 +565,20 @@ static int k2proto (FuncState *fs, TValue *key, TValue *v) { TValue val; Proto *f = fs->f; int tag = luaH_get(fs->kcache, key, &val); /* query scanner table */ - int k; if (!tagisempty(tag)) { /* is there an index there? */ - k = cast_int(ivalue(&val)); + int k = cast_int(ivalue(&val)); /* collisions can happen only for float keys */ lua_assert(ttisfloat(key) || luaV_rawequalobj(&f->k[k], v)); return k; /* reuse index */ } - /* constant not found; create a new entry */ - k = addk(fs, f, v); - /* cache it for reuse; numerical value does not need GC barrier; - table is not a metatable, so it does not need to invalidate cache */ - setivalue(&val, k); - luaH_set(fs->ls->L, fs->kcache, key, &val); - return k; + else { /* constant not found; create a new entry */ + int k = addk(fs, f, v); + /* cache it for reuse; numerical value does not need GC barrier; + table is not a metatable, so it does not need to invalidate cache */ + setivalue(&val, k); + luaH_set(fs->ls->L, fs->kcache, key, &val); + return k; + } } @@ -604,13 +604,14 @@ static int luaK_intK (FuncState *fs, lua_Integer n) { /* ** Add a float to list of constants and return its index. Floats ** with integral values need a different key, to avoid collision -** with actual integers. To that, we add to the number its smaller +** with actual integers. To that end, we add to the number its smaller ** power-of-two fraction that is still significant in its scale. -** For doubles, that would be 1/2^52. +** (For doubles, the fraction would be 2^-52). ** This method is not bulletproof: different numbers may generate the ** same key (e.g., very large numbers will overflow to 'inf') and for -** floats larger than 2^53 the result is still an integer. At worst, -** this only wastes an entry with a duplicate. +** floats larger than 2^53 the result is still an integer. For those +** cases, just generate a new entry. At worst, this only wastes an entry +** with a duplicate. */ static int luaK_numberK (FuncState *fs, lua_Number r) { TValue o, kv; @@ -625,7 +626,7 @@ static int luaK_numberK (FuncState *fs, lua_Number r) { const lua_Number k = r * (1 + q); /* key */ lua_Integer ik; setfltvalue(&kv, k); /* key as a TValue */ - if (!luaV_flttointeger(k, &ik, F2Ieq)) { /* not an integral value? */ + if (!luaV_flttointeger(k, &ik, F2Ieq)) { /* not an integer value? */ int n = k2proto(fs, &kv, &o); /* use key */ if (luaV_rawequalobj(&fs->f->k[n], &o)) /* correct value? */ return n; diff --git a/lparser.c b/lparser.c index dde0b6d5e0..73dad6d77d 100644 --- a/lparser.c +++ b/lparser.c @@ -1827,7 +1827,7 @@ static lu_byte getglobalattribute (LexState *ls, lu_byte df) { switch (kind) { case RDKTOCLOSE: luaK_semerror(ls, "global variables cannot be to-be-closed"); - break; /* to avoid warnings */ + return kind; /* to avoid warnings */ case RDKCONST: return GDKCONST; /* adjust kind for global variable */ default: diff --git a/ltable.c b/ltable.c index 4017d8c7d0..b7f88f6ffe 100644 --- a/ltable.c +++ b/ltable.c @@ -156,7 +156,7 @@ static Node *hashint (const Table *t, lua_Integer i) { ** The main computation should be just ** n = frexp(n, &i); return (n * INT_MAX) + i ** but there are some numerical subtleties. -** In a two-complement representation, INT_MAX does not has an exact +** In a two-complement representation, INT_MAX may not have an exact ** representation as a float, but INT_MIN does; because the absolute ** value of 'frexp' is smaller than 1 (unless 'n' is inf/NaN), the ** absolute value of the product 'frexp * -INT_MIN' is smaller or equal diff --git a/manual/manual.of b/manual/manual.of index b276577430..61dd42f248 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -127,7 +127,8 @@ strings can contain any 8-bit value, including @x{embedded zeros} (@Char{\0}). Lua is also encoding-agnostic; it makes no assumptions about the contents of a string. -The length of any string in Lua must fit in a Lua integer. +The length of any string in Lua must fit in a Lua integer, +and the string plus a small header must fit in @id{size_t}. Lua can call (and manipulate) functions written in Lua and functions written in C @see{functioncall}. @@ -1555,7 +1556,8 @@ It has the following syntax: exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}} } The given identifier (@bnfNter{Name}) defines the control variable, -which is a new read-only variable local to the loop body (@emph{block}). +which is a new read-only (@id{const}) variable local to the loop body +(@emph{block}). The loop starts by evaluating once the three control expressions. Their values are called respectively @@ -1610,7 +1612,7 @@ works as follows. The names @rep{var_i} declare loop variables local to the loop body. The first of these variables is the @emph{control variable}, -which is a read-only variable. +which is a read-only (@id{const}) variable. The loop starts by evaluating @rep{explist} to produce four values: @@ -4083,7 +4085,7 @@ Lua will call @id{falloc} before raising the error. @APIEntry{const char *lua_pushfstring (lua_State *L, const char *fmt, ...);| -@apii{0,1,m} +@apii{0,1,v} Pushes onto the stack a formatted string and returns a pointer to this string @see{constchar}. @@ -4103,6 +4105,9 @@ A conversion specifier (and its corresponding extra argument) can be Every occurrence of @Char{%} in the string @id{fmt} must form a valid conversion specifier. +Besides memory allocation errors, +this function may raise an error if the resulting string is too large. + } @APIEntry{void lua_pushglobaltable (lua_State *L);| @@ -4135,7 +4140,7 @@ light userdata with the same @N{C address}. } @APIEntry{const char *lua_pushliteral (lua_State *L, const char *s);| -@apii{0,1,m} +@apii{0,1,v} This macro is equivalent to @Lid{lua_pushstring}, but should be used only when @id{s} is a literal string. @@ -4144,7 +4149,7 @@ but should be used only when @id{s} is a literal string. } @APIEntry{const char *lua_pushlstring (lua_State *L, const char *s, size_t len);| -@apii{0,1,m} +@apii{0,1,v} Pushes the string pointed to by @id{s} with size @id{len} onto the stack. @@ -4156,6 +4161,9 @@ including @x{embedded zeros}. Returns a pointer to the internal copy of the string @see{constchar}. +Besides memory allocation errors, +this function may raise an error if the string is too large. + } @APIEntry{void lua_pushnil (lua_State *L);| @@ -5015,8 +5023,8 @@ then @id{name} is set to @id{NULL}. @item{@id{namewhat}| explains the @T{name} field. The value of @T{namewhat} can be -@T{"global"}, @T{"local"}, @T{"method"}, -@T{"field"}, @T{"upvalue"}, or @T{""} (the empty string), +@T{"global"}, @T{"local"}, @T{"upvalue"}, +@T{"field"}, @T{""} (the empty string), plus some other options, according to how the function was called. (Lua uses the empty string when no other option seems to apply.) } @@ -6571,7 +6579,7 @@ The call always returns the previous value of the parameter. If the call does not give a new value, the value is left unchanged. -Lua rounds these values before storing them; +Lua stores these values in a compressed format, so, the value returned as the previous value may not be exactly the last value set. } @@ -6585,10 +6593,10 @@ This function should not be called by a finalizer. } @LibEntry{dofile ([filename])| -Opens the named file and executes its content as a Lua chunk. +Opens the named file and executes its content as a Lua chunk, +returning all values returned by the chunk. When called without arguments, @id{dofile} executes the content of the standard input (@id{stdin}). -Returns all values returned by the chunk. In case of errors, @id{dofile} propagates the error to its caller. (That is, @id{dofile} does not run in protected mode.) @@ -6960,7 +6968,7 @@ in case of error (either the original error that stopped the coroutine or errors in closing methods), this function returns @false plus the error object; -otherwise ir returns @true. +otherwise it returns @true. } diff --git a/testes/gengc.lua b/testes/gengc.lua index ea99bdc43a..6509e39d8a 100644 --- a/testes/gengc.lua +++ b/testes/gengc.lua @@ -176,6 +176,20 @@ do print"testing stop-the-world collection" assert(collectgarbage("param", "stepsize") == step) end + +if T then -- test GC parameter codification + for _, percentage in ipairs{5, 10, 12, 20, 50, 100, 200, 500} do + local param = T.codeparam(percentage) -- codify percentage + for _, value in ipairs{1, 2, 10, 100, 257, 1023, 6500, 100000} do + local exact = value*percentage // 100 + local aprox = T.applyparam(param, value) -- apply percentage + -- difference is at most 10% (+1 compensates difference due to + -- rounding to integers) + assert(math.abs(aprox - exact) <= exact/10 + 1) + end + end +end + collectgarbage(oldmode) print('OK') From 53dc5a3bbadac166a8b40904790f91b351e55dd9 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 9 Aug 2025 15:15:20 -0300 Subject: [PATCH 103/165] Functions 'frexp'-'ldexp' back to the math library They are basic for anything that handles the representation of floating numbers. --- lmathlib.c | 32 ++++++++++++++++---------------- manual/manual.of | 19 ++++++++++++++++++- testes/math.lua | 12 ++++++++++++ 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/lmathlib.c b/lmathlib.c index bd34c88860..b030f97ead 100644 --- a/lmathlib.c +++ b/lmathlib.c @@ -203,6 +203,20 @@ static int math_rad (lua_State *L) { return 1; } +static int math_frexp (lua_State *L) { + int e; + lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); + lua_pushinteger(L, e); + return 2; +} + +static int math_ldexp (lua_State *L) { + lua_Number x = luaL_checknumber(L, 1); + int ep = (int)luaL_checkinteger(L, 2); + lua_pushnumber(L, l_mathop(ldexp)(x, ep)); + return 1; +} + static int math_min (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ @@ -666,20 +680,6 @@ static int math_pow (lua_State *L) { return 1; } -static int math_frexp (lua_State *L) { - int e; - lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); - lua_pushinteger(L, e); - return 2; -} - -static int math_ldexp (lua_State *L) { - lua_Number x = luaL_checknumber(L, 1); - int ep = (int)luaL_checkinteger(L, 2); - lua_pushnumber(L, l_mathop(ldexp)(x, ep)); - return 1; -} - static int math_log10 (lua_State *L) { lua_pushnumber(L, l_mathop(log10)(luaL_checknumber(L, 1))); return 1; @@ -702,7 +702,9 @@ static const luaL_Reg mathlib[] = { {"tointeger", math_toint}, {"floor", math_floor}, {"fmod", math_fmod}, + {"frexp", math_frexp}, {"ult", math_ult}, + {"ldexp", math_ldexp}, {"log", math_log}, {"max", math_max}, {"min", math_min}, @@ -718,8 +720,6 @@ static const luaL_Reg mathlib[] = { {"sinh", math_sinh}, {"tanh", math_tanh}, {"pow", math_pow}, - {"frexp", math_frexp}, - {"ldexp", math_ldexp}, {"log10", math_log10}, #endif /* placeholders */ diff --git a/manual/manual.of b/manual/manual.of index 61dd42f248..89e9b8f440 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -8341,6 +8341,17 @@ that rounds the quotient towards zero. (integer/float) } +@LibEntry{math.frexp (x)| + +Returns two numbers @id{m} and @id{e} such that @M{x = m2@sp{e}}, +where @id{e} is an integer. +When @id{x} is zero, NaN, +inf, or -inf, +@id{m} is equal to @id{x}; +otherwise, the absolute value of @id{m} +is in the range @C{(} @M{[0.5, 1)} @C{]}. + +} + @LibEntry{math.huge| The float value @idx{HUGE_VAL}, @@ -8348,6 +8359,12 @@ a value greater than any other numeric value. } +@LibEntry{math.ldexp(m, e)| + +Returns @M{m2@sp{e}}, where @id{e} is an integer. + +} + @LibEntry{math.log (x [, base])| Returns the logarithm of @id{x} in the given base. @@ -8403,7 +8420,7 @@ Converts the angle @id{x} from degrees to radians. When called without arguments, returns a pseudo-random float with uniform distribution -in the range @C{(} @M{[0,1)}. @C{]} +in the range @C{(} @M{[0, 1)}. @C{]} When called with two integers @id{m} and @id{n}, @id{math.random} returns a pseudo-random integer with uniform distribution in the range @M{[m, n]}. diff --git a/testes/math.lua b/testes/math.lua index 0d228d0988..54d19c4075 100644 --- a/testes/math.lua +++ b/testes/math.lua @@ -685,6 +685,18 @@ assert(eq(math.exp(0), 1)) assert(eq(math.sin(10), math.sin(10%(2*math.pi)))) +do print("testing ldexp/frexp") + global ipairs + for _, x in ipairs{0, 10, 32, -math.pi, 1e10, 1e-10, math.huge, -math.huge} do + local m, p = math.frexp(x) + assert(math.ldexp(m, p) == x) + local am = math.abs(m) + assert(m == x or (0.5 <= am and am < 1)) + end + +end + + assert(tonumber(' 1.3e-2 ') == 1.3e-2) assert(tonumber(' -1.00000000000001 ') == -1.00000000000001) From dd095677e38a104d0fd073f31530e08c9f5286fc Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 20 Aug 2025 13:59:08 -0300 Subject: [PATCH 104/165] Small cleaning services --- lfunc.c | 3 +-- lmathlib.c | 19 ++++++++++++++++--- lvm.c | 54 +++++++++++++++++++++++++++++------------------------- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/lfunc.c b/lfunc.c index da7c623974..b6fd9ceb55 100644 --- a/lfunc.c +++ b/lfunc.c @@ -196,8 +196,7 @@ void luaF_unlinkupval (UpVal *uv) { */ void luaF_closeupval (lua_State *L, StkId level) { UpVal *uv; - StkId upl; /* stack index pointed by 'uv' */ - while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) { + while ((uv = L->openupval) != NULL && uplevel(uv) >= level) { TValue *slot = &uv->u.value; /* new position for value */ lua_assert(uplevel(uv) < L->top.p); luaF_unlinkupval(uv); /* remove upvalue from 'openupval' list */ diff --git a/lmathlib.c b/lmathlib.c index b030f97ead..2f0f3d1bee 100644 --- a/lmathlib.c +++ b/lmathlib.c @@ -38,31 +38,37 @@ static int math_abs (lua_State *L) { return 1; } + static int math_sin (lua_State *L) { lua_pushnumber(L, l_mathop(sin)(luaL_checknumber(L, 1))); return 1; } + static int math_cos (lua_State *L) { lua_pushnumber(L, l_mathop(cos)(luaL_checknumber(L, 1))); return 1; } + static int math_tan (lua_State *L) { lua_pushnumber(L, l_mathop(tan)(luaL_checknumber(L, 1))); return 1; } + static int math_asin (lua_State *L) { lua_pushnumber(L, l_mathop(asin)(luaL_checknumber(L, 1))); return 1; } + static int math_acos (lua_State *L) { lua_pushnumber(L, l_mathop(acos)(luaL_checknumber(L, 1))); return 1; } + static int math_atan (lua_State *L) { lua_Number y = luaL_checknumber(L, 1); lua_Number x = luaL_optnumber(L, 2, 1); @@ -167,6 +173,7 @@ static int math_ult (lua_State *L) { return 1; } + static int math_log (lua_State *L) { lua_Number x = luaL_checknumber(L, 1); lua_Number res; @@ -188,28 +195,34 @@ static int math_log (lua_State *L) { return 1; } + static int math_exp (lua_State *L) { lua_pushnumber(L, l_mathop(exp)(luaL_checknumber(L, 1))); return 1; } + static int math_deg (lua_State *L) { lua_pushnumber(L, luaL_checknumber(L, 1) * (l_mathop(180.0) / PI)); return 1; } + static int math_rad (lua_State *L) { lua_pushnumber(L, luaL_checknumber(L, 1) * (PI / l_mathop(180.0))); return 1; } + static int math_frexp (lua_State *L) { - int e; - lua_pushnumber(L, l_mathop(frexp)(luaL_checknumber(L, 1), &e)); - lua_pushinteger(L, e); + lua_Number x = luaL_checknumber(L, 1); + int ep; + lua_pushnumber(L, l_mathop(frexp)(x, &ep)); + lua_pushinteger(L, ep); return 2; } + static int math_ldexp (lua_State *L) { lua_Number x = luaL_checknumber(L, 1); int ep = (int)luaL_checkinteger(L, 2); diff --git a/lvm.c b/lvm.c index cfdcf97a55..bde850ea16 100644 --- a/lvm.c +++ b/lvm.c @@ -910,6 +910,10 @@ void luaV_finishOp (lua_State *L) { /* ** {================================================================== ** Macros for arithmetic/bitwise/comparison opcodes in 'luaV_execute' +** +** All these macros are to be used exclusively inside the main +** iterpreter loop (function luaV_execute) and may access directly +** the local variables of that function (L, i, pc, ci, etc.). ** =================================================================== */ @@ -931,17 +935,17 @@ void luaV_finishOp (lua_State *L) { ** operation, 'fop' is the float operation. */ #define op_arithI(L,iop,fop) { \ - StkId ra = RA(i); \ + TValue *ra = vRA(i); \ TValue *v1 = vRB(i); \ int imm = GETARG_sC(i); \ if (ttisinteger(v1)) { \ lua_Integer iv1 = ivalue(v1); \ - pc++; setivalue(s2v(ra), iop(L, iv1, imm)); \ + pc++; setivalue(ra, iop(L, iv1, imm)); \ } \ else if (ttisfloat(v1)) { \ lua_Number nb = fltvalue(v1); \ lua_Number fimm = cast_num(imm); \ - pc++; setfltvalue(s2v(ra), fop(L, nb, fimm)); \ + pc++; setfltvalue(ra, fop(L, nb, fimm)); \ }} @@ -952,6 +956,7 @@ void luaV_finishOp (lua_State *L) { #define op_arithf_aux(L,v1,v2,fop) { \ lua_Number n1; lua_Number n2; \ if (tonumberns(v1, n1) && tonumberns(v2, n2)) { \ + StkId ra = RA(i); \ pc++; setfltvalue(s2v(ra), fop(L, n1, n2)); \ }} @@ -960,7 +965,6 @@ void luaV_finishOp (lua_State *L) { ** Arithmetic operations over floats and others with register operands. */ #define op_arithf(L,fop) { \ - StkId ra = RA(i); \ TValue *v1 = vRB(i); \ TValue *v2 = vRC(i); \ op_arithf_aux(L, v1, v2, fop); } @@ -970,7 +974,6 @@ void luaV_finishOp (lua_State *L) { ** Arithmetic operations with K operands for floats. */ #define op_arithfK(L,fop) { \ - StkId ra = RA(i); \ TValue *v1 = vRB(i); \ TValue *v2 = KC(i); lua_assert(ttisnumber(v2)); \ op_arithf_aux(L, v1, v2, fop); } @@ -980,8 +983,8 @@ void luaV_finishOp (lua_State *L) { ** Arithmetic operations over integers and floats. */ #define op_arith_aux(L,v1,v2,iop,fop) { \ - StkId ra = RA(i); \ if (ttisinteger(v1) && ttisinteger(v2)) { \ + StkId ra = RA(i); \ lua_Integer i1 = ivalue(v1); lua_Integer i2 = ivalue(v2); \ pc++; setivalue(s2v(ra), iop(L, i1, i2)); \ } \ @@ -1010,12 +1013,12 @@ void luaV_finishOp (lua_State *L) { ** Bitwise operations with constant operand. */ #define op_bitwiseK(L,op) { \ - StkId ra = RA(i); \ TValue *v1 = vRB(i); \ TValue *v2 = KC(i); \ lua_Integer i1; \ lua_Integer i2 = ivalue(v2); \ if (tointegerns(v1, &i1)) { \ + StkId ra = RA(i); \ pc++; setivalue(s2v(ra), op(i1, i2)); \ }} @@ -1024,11 +1027,11 @@ void luaV_finishOp (lua_State *L) { ** Bitwise operations with register operands. */ #define op_bitwise(L,op) { \ - StkId ra = RA(i); \ TValue *v1 = vRB(i); \ TValue *v2 = vRC(i); \ lua_Integer i1; lua_Integer i2; \ if (tointegerns(v1, &i1) && tointegerns(v2, &i2)) { \ + StkId ra = RA(i); \ pc++; setivalue(s2v(ra), op(i1, i2)); \ }} @@ -1039,18 +1042,18 @@ void luaV_finishOp (lua_State *L) { ** integers. */ #define op_order(L,opi,opn,other) { \ - StkId ra = RA(i); \ + TValue *ra = vRA(i); \ int cond; \ TValue *rb = vRB(i); \ - if (ttisinteger(s2v(ra)) && ttisinteger(rb)) { \ - lua_Integer ia = ivalue(s2v(ra)); \ + if (ttisinteger(ra) && ttisinteger(rb)) { \ + lua_Integer ia = ivalue(ra); \ lua_Integer ib = ivalue(rb); \ cond = opi(ia, ib); \ } \ - else if (ttisnumber(s2v(ra)) && ttisnumber(rb)) \ - cond = opn(s2v(ra), rb); \ + else if (ttisnumber(ra) && ttisnumber(rb)) \ + cond = opn(ra, rb); \ else \ - Protect(cond = other(L, s2v(ra), rb)); \ + Protect(cond = other(L, ra, rb)); \ docondjump(); } @@ -1059,19 +1062,19 @@ void luaV_finishOp (lua_State *L) { ** always small enough to have an exact representation as a float.) */ #define op_orderI(L,opi,opf,inv,tm) { \ - StkId ra = RA(i); \ + TValue *ra = vRA(i); \ int cond; \ int im = GETARG_sB(i); \ - if (ttisinteger(s2v(ra))) \ - cond = opi(ivalue(s2v(ra)), im); \ - else if (ttisfloat(s2v(ra))) { \ - lua_Number fa = fltvalue(s2v(ra)); \ + if (ttisinteger(ra)) \ + cond = opi(ivalue(ra), im); \ + else if (ttisfloat(ra)) { \ + lua_Number fa = fltvalue(ra); \ lua_Number fim = cast_num(im); \ cond = opf(fa, fim); \ } \ else { \ int isf = GETARG_C(i); \ - Protect(cond = luaT_callorderiTM(L, s2v(ra), im, inv, isf, tm)); \ + Protect(cond = luaT_callorderiTM(L, ra, im, inv, isf, tm)); \ } \ docondjump(); } @@ -1090,6 +1093,7 @@ void luaV_finishOp (lua_State *L) { #define RA(i) (base+GETARG_A(i)) +#define vRA(i) s2v(RA(i)) #define RB(i) (base+GETARG_B(i)) #define vRB(i) s2v(RB(i)) #define KB(i) (k+GETARG_B(i)) @@ -1130,14 +1134,14 @@ void luaV_finishOp (lua_State *L) { /* ** Correct global 'pc'. */ -#define savepc(L) (ci->u.l.savedpc = pc) +#define savepc(ci) (ci->u.l.savedpc = pc) /* ** Whenever code can raise errors, the global 'pc' and the global ** 'top' must be correct to report occasional errors. */ -#define savestate(L,ci) (savepc(L), L->top.p = ci->top.p) +#define savestate(L,ci) (savepc(ci), L->top.p = ci->top.p) /* @@ -1147,7 +1151,7 @@ void luaV_finishOp (lua_State *L) { #define Protect(exp) (savestate(L,ci), (exp), updatetrap(ci)) /* special version that does not change the top */ -#define ProtectNT(exp) (savepc(L), (exp), updatetrap(ci)) +#define ProtectNT(exp) (savepc(ci), (exp), updatetrap(ci)) /* ** Protect code that can only raise errors. (That is, it cannot change @@ -1165,7 +1169,7 @@ void luaV_finishOp (lua_State *L) { /* 'c' is the limit of live values in the stack */ #define checkGC(L,c) \ - { luaC_condGC(L, (savepc(L), L->top.p = (c)), \ + { luaC_condGC(L, (savepc(ci), L->top.p = (c)), \ updatetrap(ci)); \ luai_threadyield(L); } @@ -1714,7 +1718,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { if (b != 0) /* fixed number of arguments? */ L->top.p = ra + b; /* top signals number of arguments */ /* else previous instruction set top */ - savepc(L); /* in case of errors */ + savepc(ci); /* in case of errors */ if ((newci = luaD_precall(L, ra, nresults)) == NULL) updatetrap(ci); /* C call; nothing else to be done */ else { /* Lua call: run function in this same C frame */ From c688b00f73205273c46d7cf5656ff5a27e90ce36 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 20 Aug 2025 14:18:12 -0300 Subject: [PATCH 105/165] Detail in 'obj2gco' Its check should use the type of the object, not its tag. (Change only relevant in test mode.) --- lstate.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lstate.h b/lstate.h index 80df3b0a95..a4d5570c78 100644 --- a/lstate.h +++ b/lstate.h @@ -430,9 +430,9 @@ union GCUnion { /* ** macro to convert a Lua object into a GCObject -** (The access to 'tt' tries to ensure that 'v' is actually a Lua object.) */ -#define obj2gco(v) check_exp((v)->tt >= LUA_TSTRING, &(cast_u(v)->gc)) +#define obj2gco(v) \ + check_exp(novariant((v)->tt) >= LUA_TSTRING, &(cast_u(v)->gc)) /* actual number of total memory allocated */ From 88aa4049ad3e638571bfffcf5fd8b6a8e07c6aaf Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 20 Aug 2025 14:31:07 -0300 Subject: [PATCH 106/165] Keep the order left-right in shifts Opcodes OP_SHLI-OP_SHRI and the cases for opcodes OP_SHL-OP_SHR were out of order. --- ljumptab.h | 2 +- lopcodes.c | 2 +- lopcodes.h | 2 +- lopnames.h | 2 +- lvm.c | 16 ++++++++-------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ljumptab.h b/ljumptab.h index 8306f250cc..a24828bb5a 100644 --- a/ljumptab.h +++ b/ljumptab.h @@ -57,8 +57,8 @@ static const void *const disptab[NUM_OPCODES] = { &&L_OP_BANDK, &&L_OP_BORK, &&L_OP_BXORK, -&&L_OP_SHRI, &&L_OP_SHLI, +&&L_OP_SHRI, &&L_OP_ADD, &&L_OP_SUB, &&L_OP_MUL, diff --git a/lopcodes.c b/lopcodes.c index 092c390206..79ffbe2590 100644 --- a/lopcodes.c +++ b/lopcodes.c @@ -53,8 +53,8 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BANDK */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BORK */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_BXORK */ - ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHRI */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHLI */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SHRI */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_ADD */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SUB */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_MUL */ diff --git a/lopcodes.h b/lopcodes.h index 9787003846..9ad21021ea 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -272,8 +272,8 @@ OP_BANDK,/* A B C R[A] := R[B] & K[C]:integer */ OP_BORK,/* A B C R[A] := R[B] | K[C]:integer */ OP_BXORK,/* A B C R[A] := R[B] ~ K[C]:integer */ -OP_SHRI,/* A B sC R[A] := R[B] >> sC */ OP_SHLI,/* A B sC R[A] := sC << R[B] */ +OP_SHRI,/* A B sC R[A] := R[B] >> sC */ OP_ADD,/* A B C R[A] := R[B] + R[C] */ OP_SUB,/* A B C R[A] := R[B] - R[C] */ diff --git a/lopnames.h b/lopnames.h index 965cec9bf2..39df332f5e 100644 --- a/lopnames.h +++ b/lopnames.h @@ -45,8 +45,8 @@ static const char *const opnames[] = { "BANDK", "BORK", "BXORK", - "SHRI", "SHLI", + "SHRI", "ADD", "SUB", "MUL", diff --git a/lvm.c b/lvm.c index bde850ea16..8ad4344a39 100644 --- a/lvm.c +++ b/lvm.c @@ -1476,23 +1476,23 @@ void luaV_execute (lua_State *L, CallInfo *ci) { op_bitwiseK(L, l_bxor); vmbreak; } - vmcase(OP_SHRI) { + vmcase(OP_SHLI) { StkId ra = RA(i); TValue *rb = vRB(i); int ic = GETARG_sC(i); lua_Integer ib; if (tointegerns(rb, &ib)) { - pc++; setivalue(s2v(ra), luaV_shiftl(ib, -ic)); + pc++; setivalue(s2v(ra), luaV_shiftl(ic, ib)); } vmbreak; } - vmcase(OP_SHLI) { + vmcase(OP_SHRI) { StkId ra = RA(i); TValue *rb = vRB(i); int ic = GETARG_sC(i); lua_Integer ib; if (tointegerns(rb, &ib)) { - pc++; setivalue(s2v(ra), luaV_shiftl(ic, ib)); + pc++; setivalue(s2v(ra), luaV_shiftl(ib, -ic)); } vmbreak; } @@ -1538,14 +1538,14 @@ void luaV_execute (lua_State *L, CallInfo *ci) { op_bitwise(L, l_bxor); vmbreak; } - vmcase(OP_SHR) { - op_bitwise(L, luaV_shiftr); - vmbreak; - } vmcase(OP_SHL) { op_bitwise(L, luaV_shiftl); vmbreak; } + vmcase(OP_SHR) { + op_bitwise(L, luaV_shiftr); + vmbreak; + } vmcase(OP_MMBIN) { StkId ra = RA(i); Instruction pi = *(pc - 2); /* original arith. expression */ From 907d172c1114a2d61e85e1ca7aba50ef1fc4ffe3 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 20 Aug 2025 15:00:53 -0300 Subject: [PATCH 107/165] Added lock/unlock to API function 'lua_rawlen' The call to 'luaH_getn' can change the "field" 'lenhint' of a table. --- lapi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lapi.c b/lapi.c index 0c88751a2e..55e371dcc9 100644 --- a/lapi.c +++ b/lapi.c @@ -440,7 +440,13 @@ LUA_API lua_Unsigned lua_rawlen (lua_State *L, int idx) { case LUA_VSHRSTR: return cast(lua_Unsigned, tsvalue(o)->shrlen); case LUA_VLNGSTR: return cast(lua_Unsigned, tsvalue(o)->u.lnglen); case LUA_VUSERDATA: return cast(lua_Unsigned, uvalue(o)->len); - case LUA_VTABLE: return luaH_getn(L, hvalue(o)); + case LUA_VTABLE: { + lua_Unsigned res; + lua_lock(L); + res = luaH_getn(L, hvalue(o)); + lua_unlock(L); + return res; + } default: return 0; } } From c345877e4c2588324d9a1e5655e8f48200ba2e5e Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 20 Aug 2025 15:29:46 -0300 Subject: [PATCH 108/165] Better documentation for LUA_ERRERR Not all errors in a message handler generate a LUA_ERRERR. --- ldo.c | 4 ++-- manual/manual.of | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ldo.c b/ldo.c index f232b588a9..dff9488e96 100644 --- a/ldo.c +++ b/ldo.c @@ -203,7 +203,7 @@ TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { #define ERRORSTACKSIZE (MAXSTACK + STACKERRSPACE) -/* raise an error while running the message handler */ +/* raise a stack 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); @@ -339,7 +339,7 @@ int luaD_growstack (lua_State *L, int n, int raiseerror) { a stack error; cannot grow further than that. */ lua_assert(stacksize(L) == ERRORSTACKSIZE); if (raiseerror) - luaD_errerr(L); /* error inside message handler */ + luaD_errerr(L); /* stack error inside message handler */ return 0; /* if not 'raiseerror', just signal it */ } else if (n < MAXSTACK) { /* avoids arithmetic overflows */ diff --git a/manual/manual.of b/manual/manual.of index 89e9b8f440..3c7041182a 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -270,7 +270,7 @@ print(x) --> 10 (the global one) Notice that, in a declaration like @T{local x = x}, the new @id{x} being declared is not in scope yet, -and so the @id{x} in the right-hand side refers to the outside variable. +and so the @id{x} on the right-hand side refers to the outside variable. Because of the @x{lexical scoping} rules, local variables can be freely accessed by functions @@ -2826,7 +2826,16 @@ status codes to indicate different kinds of errors or other conditions: For such errors, Lua does not call the @x{message handler}. } -@item{@defid{LUA_ERRERR}| error while running the @x{message handler}.} +@item{@defid{LUA_ERRERR}| +stack overflow while running the @x{message handler} +due to another stack overflow. +More often than not, +this error is the result of some other error while running +a message handler. +An error in a message handler will call the handler again, +which will generate the error again, and so on, +until this loop exhausts the stack and cause this error. +} @item{@defid{LUA_ERRSYNTAX}| syntax error during precompilation or format error in a binary chunk.} From 06c5d3825f03eafc90b56d43f70f70048b785bc8 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 20 Aug 2025 16:10:54 -0300 Subject: [PATCH 109/165] Removed code for compatibility with version 5.3 --- lstate.h | 4 ---- ltests.h | 1 - ltm.c | 16 ---------------- lua.h | 7 ------- luaconf.h | 27 +-------------------------- lvm.c | 6 ------ testes/api.lua | 3 ++- 7 files changed, 3 insertions(+), 61 deletions(-) diff --git a/lstate.h b/lstate.h index a4d5570c78..20dc4d24f0 100644 --- a/lstate.h +++ b/lstate.h @@ -249,10 +249,6 @@ struct CallInfo { #define CIST_HOOKYIELD (CIST_TAIL << 1) /* function "called" a finalizer */ #define CIST_FIN (CIST_HOOKYIELD << 1) -#if defined(LUA_COMPAT_LT_LE) -/* using __lt for __le */ -#define CIST_LEQ (CIST_FIN << 1) -#endif #define get_nresults(cs) (cast_int((cs) & CIST_NRESULTS) - 1) diff --git a/ltests.h b/ltests.h index c825bdcfc1..305f561976 100644 --- a/ltests.h +++ b/ltests.h @@ -13,7 +13,6 @@ /* test Lua with compatibility code */ #define LUA_COMPAT_MATHLIB -#define LUA_COMPAT_LT_LE #undef LUA_COMPAT_GLOBAL diff --git a/ltm.c b/ltm.c index 8eca2d6e1f..d1a61a6250 100644 --- a/ltm.c +++ b/ltm.c @@ -196,28 +196,12 @@ void luaT_trybiniTM (lua_State *L, const TValue *p1, lua_Integer i2, /* ** Calls an order tag method. -** For lessequal, LUA_COMPAT_LT_LE keeps compatibility with old -** behavior: if there is no '__le', try '__lt', based on l <= r iff -** !(r < l) (assuming a total order). If the metamethod yields during -** this substitution, the continuation has to know about it (to negate -** the result of rtop.p, event); /* try original event */ if (tag >= 0) /* found tag method? */ return !tagisfalse(tag); -#if defined(LUA_COMPAT_LT_LE) - else if (event == TM_LE) { - /* try '!(p2 < p1)' for '(p1 <= p2)' */ - L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ - tag = callbinTM(L, p2, p1, L->top.p, TM_LT); - L->ci->callstatus ^= CIST_LEQ; /* clear mark */ - if (tag >= 0) /* found tag method? */ - return tagisfalse(tag); - } -#endif luaG_ordererror(L, p1, p2); /* no metamethod found */ return 0; /* to avoid warnings */ } diff --git a/lua.h b/lua.h index 131a8fcb9f..ab473dc3e4 100644 --- a/lua.h +++ b/lua.h @@ -432,13 +432,6 @@ LUA_API void (lua_closeslot) (lua_State *L, int idx); ** compatibility macros ** =============================================================== */ -#if defined(LUA_COMPAT_APIINTCASTS) - -#define lua_pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) -#define lua_tounsignedx(L,i,is) ((lua_Unsigned)lua_tointegerx(L,i,is)) -#define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) - -#endif #define lua_newuserdata(L,s) lua_newuserdatauv(L,s,1) #define lua_getuservalue(L,idx) lua_getiuservalue(L,idx,1) diff --git a/luaconf.h b/luaconf.h index 0adc9c13f1..56d2916519 100644 --- a/luaconf.h +++ b/luaconf.h @@ -361,36 +361,13 @@ #define LUA_COMPAT_GLOBAL -/* -@@ LUA_COMPAT_5_3 controls other macros for compatibility with Lua 5.3. -** You can define it to get all options, or change specific options -** to fit your specific needs. -*/ -#if defined(LUA_COMPAT_5_3) /* { */ - /* @@ LUA_COMPAT_MATHLIB controls the presence of several deprecated ** functions in the mathematical library. ** (These functions were already officially removed in 5.3; ** nevertheless they are still available here.) */ -#define LUA_COMPAT_MATHLIB - -/* -@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for -** manipulating other integer types (lua_pushunsigned, lua_tounsigned, -** luaL_checkint, luaL_checklong, etc.) -** (These macros were also officially removed in 5.3, but they are still -** available here.) -*/ -#define LUA_COMPAT_APIINTCASTS - - -/* -@@ LUA_COMPAT_LT_LE controls the emulation of the '__le' metamethod -** using '__lt'. -*/ -#define LUA_COMPAT_LT_LE +/* #define LUA_COMPAT_MATHLIB */ /* @@ -407,8 +384,6 @@ #define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) #define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT) -#endif /* } */ - /* }================================================================== */ diff --git a/lvm.c b/lvm.c index 8ad4344a39..a9de5cbccc 100644 --- a/lvm.c +++ b/lvm.c @@ -861,12 +861,6 @@ void luaV_finishOp (lua_State *L) { case OP_EQ: { /* note that 'OP_EQI'/'OP_EQK' cannot yield */ int res = !l_isfalse(s2v(L->top.p - 1)); L->top.p--; -#if defined(LUA_COMPAT_LT_LE) - if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ - ci->callstatus ^= CIST_LEQ; /* clear mark */ - res = !res; /* negate result */ - } -#endif lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); if (res != GETARG_k(inst)) /* condition failed? */ ci->u.l.savedpc++; /* skip jump instruction */ diff --git a/testes/api.lua b/testes/api.lua index b3791654d4..9855f5411d 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -246,7 +246,8 @@ assert(not T.testC("compare LT 1 4, return 1")) assert(not T.testC("compare LE 9 1, return 1")) assert(not T.testC("compare EQ 9 9, return 1")) -local b = {__lt = function (a,b) return a[1] < b[1] end} +local b = {__lt = function (a,b) return a[1] < b[1] end, + __le = function (a,b) return a[1] <= b[1] end} local a1,a3,a4 = setmetatable({1}, b), setmetatable({3}, b), setmetatable({4}, b) From 13d2326a23a6cde050c17c5b856f962e93e06f3d Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 21 Aug 2025 10:51:17 -0300 Subject: [PATCH 110/165] Some definitions moved from luaconf.h to llimits.h These definitions were in luaconf.h only because the standard libraries need them. Now that llimits.h is included by the libraries, it offers a more private place for these definitions. --- llimits.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ luaconf.h | 51 +-------------------------------------------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/llimits.h b/llimits.h index c4719a1507..d115496f7a 100644 --- a/llimits.h +++ b/llimits.h @@ -287,6 +287,55 @@ typedef unsigned long l_uint32; #endif + +/* +** lua_numbertointeger converts a float number with an integral value +** to an integer, or returns 0 if the float is not within the range of +** a lua_Integer. (The range comparisons are tricky because of +** rounding. The tests here assume a two-complement representation, +** where MININTEGER always has an exact representation as a float; +** MAXINTEGER may not have one, and therefore its conversion to float +** may have an ill-defined value.) +*/ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + + +/* +** LUAI_FUNC is a mark for all extern functions that are not to be +** exported to outside modules. +** LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables, +** none of which to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). +** Elf/gcc (versions 3.2 and later) mark them as "hidden" to optimize +** access when Lua is compiled as a shared library. Not all elf targets +** support this attribute. Unfortunately, gcc does not offer a way to +** check whether the target offers that support, and those without +** support give a warning about it. To avoid these warnings, change to +** the default definition. +*/ +#if !defined(LUAI_FUNC) + +#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("internal"))) extern +#else /* }{ */ +#define LUAI_FUNC extern +#endif /* } */ + +#define LUAI_DDEC(dec) LUAI_FUNC dec +#define LUAI_DDEF /* empty */ + +#endif + + +/* Give these macros simpler names for internal use */ +#define l_likely(x) luai_likely(x) +#define l_unlikely(x) luai_unlikely(x) + /* ** {================================================================== ** "Abstraction Layer" for basic report of messages and errors diff --git a/luaconf.h b/luaconf.h index 56d2916519..b42da518fc 100644 --- a/luaconf.h +++ b/luaconf.h @@ -321,31 +321,6 @@ #define LUALIB_API LUA_API #define LUAMOD_API LUA_API - -/* -@@ LUAI_FUNC is a mark for all extern functions that are not to be -** exported to outside modules. -@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables, -** none of which to be exported to outside modules (LUAI_DDEF for -** definitions and LUAI_DDEC for declarations). -** CHANGE them if you need to mark them in some special way. Elf/gcc -** (versions 3.2 and later) mark them as "hidden" to optimize access -** when Lua is compiled as a shared library. Not all elf targets support -** this attribute. Unfortunately, gcc does not offer a way to check -** whether the target offers that support, and those without support -** give a warning about it. To avoid these warnings, change to the -** default definition. -*/ -#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ - defined(__ELF__) /* { */ -#define LUAI_FUNC __attribute__((visibility("internal"))) extern -#else /* }{ */ -#define LUAI_FUNC extern -#endif /* } */ - -#define LUAI_DDEC(dec) LUAI_FUNC dec -#define LUAI_DDEF /* empty */ - /* }================================================================== */ @@ -415,26 +390,11 @@ */ -/* The following definitions are good for most cases here */ +/* The following definition is good for most cases here */ #define l_floor(x) (l_mathop(floor)(x)) -/* -@@ lua_numbertointeger converts a float number with an integral value -** to an integer, or returns 0 if float is not within the range of -** a lua_Integer. (The range comparisons are tricky because of -** rounding. The tests here assume a two-complement representation, -** where MININTEGER always has an exact representation as a float; -** MAXINTEGER may not have one, and therefore its conversion to float -** may have an ill-defined value.) -*/ -#define lua_numbertointeger(n,p) \ - ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ - (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ - (*(p) = (LUA_INTEGER)(n), 1)) - - /* now the variable definitions */ #if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ @@ -694,13 +654,6 @@ #endif -#if defined(LUA_CORE) || defined(LUA_LIB) -/* shorter names for Lua's own use */ -#define l_likely(x) luai_likely(x) -#define l_unlikely(x) luai_unlikely(x) -#endif - - /* }================================================================== */ @@ -782,7 +735,5 @@ - - #endif From 711890624fae4508d1cb5b4d358817bf4ebcfcb2 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 26 Aug 2025 12:30:05 -0300 Subject: [PATCH 111/165] update in 'onelua.c' --- onelua.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/onelua.c b/onelua.c index cc639494cc..e717121391 100644 --- a/onelua.c +++ b/onelua.c @@ -5,10 +5,14 @@ ** ** $ gcc -O2 -std=c99 -o lua onelua.c -lm ** -** or +** or (for C89) ** ** $ gcc -O2 -std=c89 -DLUA_USE_C89 -o lua onelua.c -lm ** +** or (for Linux) +** +** gcc -O2 -o lua -DLUA_USE_LINUX -Wl,-E onelua.c -lm -ldl +** */ /* default is to build the full interpreter */ @@ -30,7 +34,15 @@ #define LUA_USE_LINUX #define LUA_USE_MACOSX #define LUA_USE_POSIX -#define LUA_ANSI +#endif + + +/* +** Other specific features +*/ +#if 0 +#define LUA_32BITS +#define LUA_USE_C89 #endif @@ -54,12 +66,10 @@ #include #include - /* setup for luaconf.h */ #define LUA_CORE #define LUA_LIB -#define ltable_c -#define lvm_c + #include "luaconf.h" /* do not export internal symbols */ From 9a3940380a2a1540dc500593a6de0c1c5e6feb69 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 26 Aug 2025 12:30:34 -0300 Subject: [PATCH 112/165] New compile option LUA_USE_OFF_T Allows non-Posix systems to use off_t and related functions for file offsets. --- liolib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liolib.c b/liolib.c index c8f165cb05..57615e6f32 100644 --- a/liolib.c +++ b/liolib.c @@ -114,7 +114,7 @@ static int l_checkmode (const char *mode) { #if !defined(l_fseek) /* { */ -#if defined(LUA_USE_POSIX) /* { */ +#if defined(LUA_USE_POSIX) || defined(LUA_USE_OFF_T) /* { */ #include From 03a3473687ef0df86fc6d31de7d46158a0436666 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 27 Aug 2025 10:28:31 -0300 Subject: [PATCH 113/165] 'ltests.h' should not use LUAI_FUNC LUAI_FUNC is now defined in llimits.h. --- ltests.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ltests.h b/ltests.h index 305f561976..26ffed83de 100644 --- a/ltests.h +++ b/ltests.h @@ -63,7 +63,7 @@ LUA_API Memcontrol l_memcontrol; #define luai_tracegc(L,f) luai_tracegctest(L, f) -LUAI_FUNC void luai_tracegctest (lua_State *L, int first); +extern void luai_tracegctest (lua_State *L, int first); /* @@ -75,26 +75,26 @@ extern void *l_Trick; /* ** Function to traverse and check all memory used by Lua */ -LUAI_FUNC int lua_checkmemory (lua_State *L); +extern int lua_checkmemory (lua_State *L); /* ** Function to print an object GC-friendly */ struct GCObject; -LUAI_FUNC void lua_printobj (lua_State *L, struct GCObject *o); +extern void lua_printobj (lua_State *L, struct GCObject *o); /* ** Function to print a value */ struct TValue; -LUAI_FUNC void lua_printvalue (struct TValue *v); +extern void lua_printvalue (struct TValue *v); /* ** Function to print the stack */ -LUAI_FUNC void lua_printstack (lua_State *L); -LUAI_FUNC int lua_printallstack (lua_State *L); +extern void lua_printstack (lua_State *L); +extern int lua_printallstack (lua_State *L); /* test for lock/unlock */ From f87416f1a3e47aa69ed8d27e7406ec6b7848da9a Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 27 Aug 2025 10:30:54 -0300 Subject: [PATCH 114/165] Added limit to number of elements in a constructor The reasoning in commit 519c57d5 is wrong: A sequence of nils generates several fields with just one OP_LOADNIL. --- lparser.c | 21 ++++++++++++++++++--- lvm.c | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lparser.c b/lparser.c index 73dad6d77d..5abcd407cb 100644 --- a/lparser.c +++ b/lparser.c @@ -905,6 +905,19 @@ typedef struct ConsControl { } ConsControl; +/* +** Maximum number of elements in a constructor, to control the following: +** * counter overflows; +** * overflows in 'extra' for OP_NEWTABLE and OP_SETLIST; +** * overflows when adding multiple returns in OP_SETLIST. +*/ +#define MAX_CNST (INT_MAX/2) +#if MAX_CNST/(MAXARG_vC + 1) > MAXARG_Ax +#undef MAX_CNST +#define MAX_CNST (MAXARG_Ax * (MAXARG_vC + 1)) +#endif + + static void recfield (LexState *ls, ConsControl *cc) { /* recfield -> (NAME | '['exp']') = exp */ FuncState *fs = ls->fs; @@ -925,7 +938,7 @@ static void recfield (LexState *ls, ConsControl *cc) { static void closelistfield (FuncState *fs, ConsControl *cc) { - if (cc->v.k == VVOID) return; /* there is no list item */ + lua_assert(cc->tostore > 0); luaK_exp2nextreg(fs, &cc->v); cc->v.k = VVOID; if (cc->tostore >= cc->maxtostore) { @@ -1013,10 +1026,12 @@ static void constructor (LexState *ls, expdesc *t) { checknext(ls, '{' /*}*/); cc.maxtostore = maxtostore(fs); do { - lua_assert(cc.v.k == VVOID || cc.tostore > 0); if (ls->t.token == /*{*/ '}') break; - closelistfield(fs, &cc); + if (cc.v.k != VVOID) /* is there a previous list item? */ + closelistfield(fs, &cc); /* close it */ field(ls, &cc); + luaY_checklimit(fs, cc.tostore + cc.na + cc.nh, MAX_CNST, + "items in a constructor"); } while (testnext(ls, ',') || testnext(ls, ';')); check_match(ls, /*{*/ '}', '{' /*}*/, line); lastlistfield(fs, &cc); diff --git a/lvm.c b/lvm.c index a9de5cbccc..d0a1c05d0b 100644 --- a/lvm.c +++ b/lvm.c @@ -1888,7 +1888,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { vmcase(OP_SETLIST) { StkId ra = RA(i); unsigned n = cast_uint(GETARG_vB(i)); - unsigned int last = cast_uint(GETARG_vC(i)); + unsigned last = cast_uint(GETARG_vC(i)); Table *h = hvalue(s2v(ra)); if (n == 0) n = cast_uint(L->top.p - ra) - 1; /* get up to the top */ From 0b73ed8f083c99b5ff88e0822532db7ad8785881 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Sat, 30 Aug 2025 16:16:02 -0300 Subject: [PATCH 115/165] Allows LUA_32BITS to be defined externally An external definition for LUA_32BITS can change the API, but libraries check number-format compatibility when loading. So, any incompatible modules will report a clear error. --- luaconf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/luaconf.h b/luaconf.h index b42da518fc..1ac64328e6 100644 --- a/luaconf.h +++ b/luaconf.h @@ -138,7 +138,7 @@ /* @@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. */ -#define LUA_32BITS 0 +/* #define LUA_32BITS */ /* @@ -153,7 +153,7 @@ #endif -#if LUA_32BITS /* { */ +#if defined(LUA_32BITS) /* { */ /* ** 32-bit integers and 'float' */ From ffbcadfb4197213d55222bca3ecc52606cd980f4 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 5 Sep 2025 15:29:15 -0300 Subject: [PATCH 116/165] In C++, 'throw' must go to the correct handler. In C, we may have several "setjmp" nested, and the "longjmp" will go to the one given by the corresponding "jmp_buf". In C++, a "throw" will always go to the inner "catch". So, the "catch" must check whether it is the recipient of the "throw" and, if not, rethrow the exception to the outer level. --- ldo.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/ldo.c b/ldo.c index dff9488e96..44937068f8 100644 --- a/ldo.c +++ b/ldo.c @@ -57,10 +57,18 @@ ** ======================================================= */ +/* chained list of long jump buffers */ +typedef struct lua_longjmp { + struct lua_longjmp *previous; + jmp_buf b; + volatile TStatus status; /* error code */ +} lua_longjmp; + + /* ** LUAI_THROW/LUAI_TRY define how Lua does exception handling. By ** default, Lua handles errors with exceptions when compiling as -** C++ code, with _longjmp/_setjmp when asked to use them, and with +** C++ code, with _longjmp/_setjmp when available (POSIX), and with ** longjmp/setjmp otherwise. */ #if !defined(LUAI_THROW) /* { */ @@ -69,38 +77,38 @@ /* C++ exceptions */ #define LUAI_THROW(L,c) throw(c) -#define LUAI_TRY(L,c,f,ud) \ - try { (f)(L, ud); } catch(...) { if ((c)->status == 0) (c)->status = -1; } -#define luai_jmpbuf int /* dummy field */ + +static void LUAI_TRY (lua_State *L, lua_longjmp *c, Pfunc f, void *ud) { + try { + f(L, ud); /* call function protected */ + } + catch (lua_longjmp *c1) { /* Lua error */ + if (c1 != c) /* not the correct level? */ + throw; /* rethrow to upper level */ + } + catch (...) { /* non-Lua exception */ + c->status = -1; /* create some error code */ + } +} + #elif defined(LUA_USE_POSIX) /* }{ */ -/* in POSIX, try _longjmp/_setjmp (more efficient) */ +/* in POSIX, use _longjmp/_setjmp (more efficient) */ #define LUAI_THROW(L,c) _longjmp((c)->b, 1) #define LUAI_TRY(L,c,f,ud) if (_setjmp((c)->b) == 0) ((f)(L, ud)) -#define luai_jmpbuf jmp_buf #else /* }{ */ /* ISO C handling with long jumps */ #define LUAI_THROW(L,c) longjmp((c)->b, 1) #define LUAI_TRY(L,c,f,ud) if (setjmp((c)->b) == 0) ((f)(L, ud)) -#define luai_jmpbuf jmp_buf #endif /* } */ #endif /* } */ - -/* chain list of long jump buffers */ -struct lua_longjmp { - struct lua_longjmp *previous; - luai_jmpbuf b; - volatile TStatus status; /* error code */ -}; - - void luaD_seterrorobj (lua_State *L, TStatus errcode, StkId oldtop) { if (errcode == LUA_ERRMEM) { /* memory error? */ setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ @@ -151,7 +159,7 @@ l_noret luaD_throwbaselevel (lua_State *L, TStatus errcode) { TStatus luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { l_uint32 oldnCcalls = L->nCcalls; - struct lua_longjmp lj; + lua_longjmp lj; lj.status = LUA_OK; lj.previous = L->errorJmp; /* chain new error handler */ L->errorJmp = &lj; From 9ea06e61f20ae34974226074fc6123dbb54a07c2 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 5 Sep 2025 15:36:47 -0300 Subject: [PATCH 117/165] Details - LUAMOD_API defined as 'extern "C"' in C++. - "ANSI C" is in fact "ISO C" (comments) - Removed option -std from makefile in testes/libs. (Easier to change to C++ for tests). --- lapi.c | 2 +- lmathlib.c | 2 +- loslib.c | 2 +- luaconf.h | 6 ++++++ testes/libs/lib11.c | 2 +- testes/libs/makefile | 2 +- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lapi.c b/lapi.c index 55e371dcc9..27fa524797 100644 --- a/lapi.c +++ b/lapi.c @@ -484,7 +484,7 @@ LUA_API lua_State *lua_tothread (lua_State *L, int idx) { /* ** Returns a pointer to the internal representation of an object. -** Note that ANSI C does not allow the conversion of a pointer to +** Note that ISO C does not allow the conversion of a pointer to ** function to a 'void*', so the conversion here goes through ** a 'size_t'. (As the returned pointer is only informative, this ** conversion should not be a problem.) diff --git a/lmathlib.c b/lmathlib.c index 2f0f3d1bee..a6b13f969c 100644 --- a/lmathlib.c +++ b/lmathlib.c @@ -278,7 +278,7 @@ static int math_type (lua_State *L) { */ /* -** This code uses lots of shifts. ANSI C does not allow shifts greater +** This code uses lots of shifts. ISO C does not allow shifts greater ** than or equal to the width of the type being shifted, so some shifts ** are written in convoluted ways to match that restriction. For ** preprocessor tests, it assumes a width of 32 bits, so the maximum diff --git a/loslib.c b/loslib.c index 3f605028fe..b7a2b0d15f 100644 --- a/loslib.c +++ b/loslib.c @@ -34,7 +34,7 @@ #if defined(LUA_USE_WINDOWS) #define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYzZ%" \ "||" "#c#x#d#H#I#j#m#M#S#U#w#W#y#Y" /* two-char options */ -#elif defined(LUA_USE_C89) /* ANSI C 89 (only 1-char options) */ +#elif defined(LUA_USE_C89) /* C89 (only 1-char options) */ #define LUA_STRFTIMEOPTIONS "aAbBcdHIjmMpSUwWxXyYZ%" #else /* C99 specification */ #define LUA_STRFTIMEOPTIONS "aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%" \ diff --git a/luaconf.h b/luaconf.h index 1ac64328e6..96a77802b9 100644 --- a/luaconf.h +++ b/luaconf.h @@ -319,7 +319,13 @@ ** More often than not the libs go together with the core. */ #define LUALIB_API LUA_API + +#if defined(__cplusplus) +/* Lua uses the "C name" when calling open functions */ +#define LUAMOD_API extern "C" +#else #define LUAMOD_API LUA_API +#endif /* }================================================================== */ diff --git a/testes/libs/lib11.c b/testes/libs/lib11.c index 377d0c484f..6a85f4d621 100644 --- a/testes/libs/lib11.c +++ b/testes/libs/lib11.c @@ -1,7 +1,7 @@ #include "lua.h" /* function from lib1.c */ -int lib1_export (lua_State *L); +LUAMOD_API int lib1_export (lua_State *L); LUAMOD_API int luaopen_lib11 (lua_State *L) { return lib1_export(L); diff --git a/testes/libs/makefile b/testes/libs/makefile index 4e7f965e99..cf4c688152 100644 --- a/testes/libs/makefile +++ b/testes/libs/makefile @@ -5,7 +5,7 @@ LUA_DIR = ../../ CC = gcc # compilation should generate Dynamic-Link Libraries -CFLAGS = -Wall -std=c99 -O2 -I$(LUA_DIR) -fPIC -shared +CFLAGS = -Wall -O2 -I$(LUA_DIR) -fPIC -shared # libraries used by the tests all: lib1.so lib11.so lib2.so lib21.so lib2-v2.so From 140b672e2ee2ac842661ece4b48e1a64f0cd11ea Mon Sep 17 00:00:00 2001 From: Roberto I Date: Tue, 16 Sep 2025 13:26:24 -0300 Subject: [PATCH 118/165] Vararg table Not yet optimized nor documented. --- lobject.h | 6 ++++-- lopcodes.h | 2 +- lparser.c | 29 +++++++++++++++++++---------- lparser.h | 9 +++++---- ltm.c | 45 +++++++++++++++++++++++++++++++++++++++------ ltm.h | 4 ++-- lundump.c | 3 ++- lvm.c | 2 +- testes/vararg.lua | 11 +++++++---- 9 files changed, 80 insertions(+), 31 deletions(-) diff --git a/lobject.h b/lobject.h index cc3dd370d0..a805dcbffb 100644 --- a/lobject.h +++ b/lobject.h @@ -583,8 +583,10 @@ typedef struct AbsLineInfo { /* ** Flags in Prototypes */ -#define PF_ISVARARG 1 -#define PF_FIXED 2 /* prototype has parts in fixed memory */ +#define PF_ISVARARG 1 /* function is vararg */ +#define PF_VATAB 2 /* function is vararg with table */ +#define PF_VAPTAB 4 /* function is vararg with pseudo-table */ +#define PF_FIXED 8 /* prototype has parts in fixed memory */ /* diff --git a/lopcodes.h b/lopcodes.h index 9ad21021ea..c3f7f64d69 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -338,7 +338,7 @@ OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */ OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */ -OP_VARARGPREP,/*A (adjust vararg parameters) */ +OP_VARARGPREP,/* (adjust vararg parameters) */ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ } OpCode; diff --git a/lparser.c b/lparser.c index 5abcd407cb..f7e787936f 100644 --- a/lparser.c +++ b/lparser.c @@ -1041,9 +1041,10 @@ static void constructor (LexState *ls, expdesc *t) { /* }====================================================================== */ -static void setvararg (FuncState *fs, int nparams) { - fs->f->flag |= PF_ISVARARG; - luaK_codeABC(fs, OP_VARARGPREP, nparams, 0, 0); +static void setvararg (FuncState *fs, int kind) { + lua_assert(kind & PF_ISVARARG); + fs->f->flag |= cast_byte(kind); + luaK_codeABC(fs, OP_VARARGPREP, 0, 0, 0); } @@ -1052,7 +1053,7 @@ static void parlist (LexState *ls) { FuncState *fs = ls->fs; Proto *f = fs->f; int nparams = 0; - int isvararg = 0; + int varargk = 0; if (ls->t.token != ')') { /* is 'parlist' not empty? */ do { switch (ls->t.token) { @@ -1062,19 +1063,27 @@ static void parlist (LexState *ls) { break; } case TK_DOTS: { + varargk |= PF_ISVARARG; luaX_next(ls); - isvararg = 1; + if (testnext(ls, '=')) { + new_varkind(ls, str_checkname(ls), RDKVATAB); + varargk |= PF_VATAB; + } break; } default: luaX_syntaxerror(ls, " or '...' expected"); } - } while (!isvararg && testnext(ls, ',')); + } while (!varargk && testnext(ls, ',')); } adjustlocalvars(ls, nparams); f->numparams = cast_byte(fs->nactvar); - if (isvararg) - setvararg(fs, f->numparams); /* declared vararg */ - luaK_reserveregs(fs, fs->nactvar); /* reserve registers for parameters */ + if (varargk != 0) { + setvararg(fs, varargk); /* declared vararg */ + if (varargk & PF_VATAB) + adjustlocalvars(ls, 1); /* vararg table */ + } + /* reserve registers for parameters (and vararg variable, if present) */ + luaK_reserveregs(fs, fs->nactvar); } @@ -2099,7 +2108,7 @@ static void mainfunc (LexState *ls, FuncState *fs) { BlockCnt bl; Upvaldesc *env; open_func(ls, fs, &bl); - setvararg(fs, 0); /* main function is always declared vararg */ + setvararg(fs, PF_ISVARARG); /* main function is always vararg */ env = allocupvalue(fs); /* ...set environment upvalue */ env->instack = 1; env->idx = 0; diff --git a/lparser.h b/lparser.h index fdbb9b8a0b..e479905efd 100644 --- a/lparser.h +++ b/lparser.h @@ -97,10 +97,11 @@ typedef struct expdesc { /* kinds of variables */ #define VDKREG 0 /* regular local */ #define RDKCONST 1 /* local constant */ -#define RDKTOCLOSE 2 /* to-be-closed */ -#define RDKCTC 3 /* local compile-time constant */ -#define GDKREG 4 /* regular global */ -#define GDKCONST 5 /* global constant */ +#define RDKVATAB 2 /* vararg table */ +#define RDKTOCLOSE 3 /* to-be-closed */ +#define RDKCTC 4 /* local compile-time constant */ +#define GDKREG 5 /* regular global */ +#define GDKCONST 6 /* global constant */ /* variables that live in registers */ #define varinreg(v) ((v)->vd.kind <= RDKTOCLOSE) diff --git a/ltm.c b/ltm.c index d1a61a6250..619be59e93 100644 --- a/ltm.c +++ b/ltm.c @@ -224,11 +224,38 @@ int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, } -void luaT_adjustvarargs (lua_State *L, int nfixparams, CallInfo *ci, - const Proto *p) { +/* +** Create a vararg table at the top of the stack, with 'n' elements +** starting at 'f'. +*/ +static void createvarargtab (lua_State *L, StkId f, int n) { int i; - int actual = cast_int(L->top.p - ci->func.p) - 1; /* number of arguments */ - int nextra = actual - nfixparams; /* number of extra arguments */ + TValue key, value; + Table *t = luaH_new(L); + sethvalue(L, s2v(L->top.p), t); + L->top.p++; + luaH_resize(L, t, cast_uint(n), 1); + setsvalue(L, &key, luaS_new(L, "n")); /* key is "n" */ + setivalue(&value, n); /* value is n */ + /* No need to anchor the key: Due to the resize, the next operation + cannot trigger a garbage collection */ + luaH_set(L, t, &key, &value); /* t.n = n */ + for (i = 0; i < n; i++) + luaH_setint(L, t, i + 1, s2v(f + i)); +} + + +/* +** initial stack: func arg1 ... argn extra1 ... +** ^ ci->func ^ L->top +** final stack: func nil ... nil extra1 ... func arg1 ... argn +** ^ ci->func ^ L->top +*/ +void luaT_adjustvarargs (lua_State *L, CallInfo *ci, const Proto *p) { + int i; + int totalargs = cast_int(L->top.p - ci->func.p) - 1; + int nfixparams = p->numparams; + int nextra = totalargs - nfixparams; /* number of extra arguments */ ci->u.l.nextraargs = nextra; luaD_checkstack(L, p->maxstacksize + 1); /* copy function to the top of the stack */ @@ -238,8 +265,14 @@ void luaT_adjustvarargs (lua_State *L, int nfixparams, CallInfo *ci, setobjs2s(L, L->top.p++, ci->func.p + i); setnilvalue(s2v(ci->func.p + i)); /* erase original parameter (for GC) */ } - ci->func.p += actual + 1; - ci->top.p += actual + 1; + if (p->flag & (PF_VAPTAB | PF_VATAB)) { /* is there a vararg table? */ + if (p->flag & PF_VAPTAB) /* is vararg table fake? */ + setnilvalue(s2v(L->top.p)); /* initialize it */ + else + createvarargtab(L, ci->func.p + nfixparams + 1, nextra); + } + ci->func.p += totalargs + 1; + ci->top.p += totalargs + 1; lua_assert(L->top.p <= ci->top.p && ci->top.p <= L->stack_last.p); } diff --git a/ltm.h b/ltm.h index ba2e47606e..ed479bb4cd 100644 --- a/ltm.h +++ b/ltm.h @@ -95,8 +95,8 @@ LUAI_FUNC int luaT_callorderTM (lua_State *L, const TValue *p1, LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, int inv, int isfloat, TMS event); -LUAI_FUNC void luaT_adjustvarargs (lua_State *L, int nfixparams, - struct CallInfo *ci, const Proto *p); +LUAI_FUNC void luaT_adjustvarargs (lua_State *L, struct CallInfo *ci, + const Proto *p); LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci, StkId where, int wanted); diff --git a/lundump.c b/lundump.c index 76f0ddc11b..74839af8bd 100644 --- a/lundump.c +++ b/lundump.c @@ -327,7 +327,8 @@ static void loadFunction (LoadState *S, Proto *f) { f->linedefined = loadInt(S); f->lastlinedefined = loadInt(S); f->numparams = loadByte(S); - f->flag = loadByte(S) & PF_ISVARARG; /* get only the meaningful flags */ + /* get only the meaningful flags */ + f->flag = cast_byte(loadByte(S) & ~PF_FIXED); if (S->fixed) f->flag |= PF_FIXED; /* signal that code is fixed */ f->maxstacksize = loadByte(S); diff --git a/lvm.c b/lvm.c index d0a1c05d0b..d88a80d19f 100644 --- a/lvm.c +++ b/lvm.c @@ -1927,7 +1927,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { vmbreak; } vmcase(OP_VARARGPREP) { - ProtectNT(luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p)); + ProtectNT(luaT_adjustvarargs(L, ci, cl->p)); if (l_unlikely(trap)) { /* previous "Protect" updated trap */ luaD_hookcall(L, ci); L->oldpc = 1; /* next opcode will be seen as a "new" line */ diff --git a/testes/vararg.lua b/testes/vararg.lua index 10553de2af..4320684e1c 100644 --- a/testes/vararg.lua +++ b/testes/vararg.lua @@ -3,9 +3,12 @@ print('testing vararg') -local function f (a, ...) +local function f (a, ...=t) local x = {n = select('#', ...), ...} - for i = 1, x.n do assert(a[i] == x[i]) end + assert(x.n == t.n) + for i = 1, x.n do + assert(a[i] == x[i] and x[i] == t[i]) + end return x.n end @@ -17,7 +20,7 @@ local function c12 (...) return res, 2 end -local function vararg (...) return {n = select('#', ...), ...} end +local function vararg (...=t) return t end local call = function (f, args) return f(table.unpack(args, 1, args.n)) end @@ -99,7 +102,7 @@ assert(a==nil and b==nil and c==nil and d==nil and e==nil) -- varargs for main chunks -local f = load[[ return {...} ]] +local f = assert(load[[ return {...} ]]) local x = f(2,3) assert(x[1] == 2 and x[2] == 3 and x[3] == undef) From 8fb1af0e33cd8688f57cd0e3ab86420a8cfe99bd Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 17 Sep 2025 16:07:48 -0300 Subject: [PATCH 119/165] Varag parameter is a new kind of variable To allow some optimizations on its use. --- lcode.c | 12 ++++++++++++ lcode.h | 1 + lobject.h | 4 ++-- lparser.c | 26 +++++++++++++++++--------- lparser.h | 4 +++- ltm.c | 8 ++++---- testes/vararg.lua | 26 ++++++++++++++++++++++++++ 7 files changed, 65 insertions(+), 16 deletions(-) diff --git a/lcode.c b/lcode.c index cafe265e73..f74223eb66 100644 --- a/lcode.c +++ b/lcode.c @@ -785,6 +785,15 @@ void luaK_setoneret (FuncState *fs, expdesc *e) { } } +/* +** Change a vararg parameter into a regular local variable +*/ +void luaK_vapar2local (FuncState *fs, expdesc *var) { + fs->f->flag |= PF_VATAB; /* function will need a vararg table */ + /* now a vararg parameter is equivalent to a regular local variable */ + var->k = VLOCAL; +} + /* ** Ensure that expression 'e' is not a variable (nor a ). @@ -796,6 +805,9 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) { const2exp(const2val(fs, e), e); break; } + case VVARGVAR: { + luaK_vapar2local(fs, e); /* turn it into a local variable */ + } /* FALLTHROUGH */ case VLOCAL: { /* already in a register */ int temp = e->u.var.ridx; e->u.info = temp; /* (can't do a direct assignment; values overlap) */ diff --git a/lcode.h b/lcode.h index 94fc2417dd..8c27bc92fd 100644 --- a/lcode.h +++ b/lcode.h @@ -71,6 +71,7 @@ LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); LUAI_FUNC void luaK_int (FuncState *fs, int reg, lua_Integer n); +LUAI_FUNC void luaK_vapar2local (FuncState *fs, expdesc *var); LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2anyregup (FuncState *fs, expdesc *e); diff --git a/lobject.h b/lobject.h index a805dcbffb..841ab5b9c3 100644 --- a/lobject.h +++ b/lobject.h @@ -584,8 +584,8 @@ typedef struct AbsLineInfo { ** Flags in Prototypes */ #define PF_ISVARARG 1 /* function is vararg */ -#define PF_VATAB 2 /* function is vararg with table */ -#define PF_VAPTAB 4 /* function is vararg with pseudo-table */ +#define PF_VAVAR 2 /* function has vararg parameter */ +#define PF_VATAB 4 /* function has vararg table */ #define PF_FIXED 8 /* prototype has parts in fixed memory */ diff --git a/lparser.c b/lparser.c index f7e787936f..8b909f3d44 100644 --- a/lparser.c +++ b/lparser.c @@ -289,7 +289,7 @@ static void check_readonly (LexState *ls, expdesc *e) { varname = ls->dyd->actvar.arr[e->u.info].vd.name; break; } - case VLOCAL: { + case VLOCAL: case VVARGVAR: { Vardesc *vardesc = getlocalvardesc(fs, e->u.var.vidx); if (vardesc->vd.kind != VDKREG) /* not a regular variable? */ varname = vardesc->vd.name; @@ -426,8 +426,11 @@ static int searchvar (FuncState *fs, TString *n, expdesc *var) { else if (eqstr(n, vd->vd.name)) { /* found? */ if (vd->vd.kind == RDKCTC) /* compile-time constant? */ init_exp(var, VCONST, fs->firstlocal + i); - else /* local variable */ + else { /* local variable */ init_var(fs, var, i); + if (vd->vd.kind == RDKVAVAR) /* vararg parameter? */ + var->k = VVARGVAR; + } return cast_int(var->k); } } @@ -467,8 +470,13 @@ static void marktobeclosed (FuncState *fs) { static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { int v = searchvar(fs, n, var); /* look up variables at current level */ if (v >= 0) { /* found? */ - if (v == VLOCAL && !base) - markupval(fs, var->u.var.vidx); /* local will be used as an upval */ + if (!base) { + if (var->k == VVARGVAR) /* vararg parameter? */ + luaK_vapar2local(fs, var); /* change it to a regular local */ + if (var->k == VLOCAL) + markupval(fs, var->u.var.vidx); /* will be used as an upvalue */ + } + /* else nothing else to be done */ } else { /* not found at current level; try upvalues */ int idx = searchupvalue(fs, n); /* try existing upvalues */ @@ -1066,8 +1074,8 @@ static void parlist (LexState *ls) { varargk |= PF_ISVARARG; luaX_next(ls); if (testnext(ls, '=')) { - new_varkind(ls, str_checkname(ls), RDKVATAB); - varargk |= PF_VATAB; + new_varkind(ls, str_checkname(ls), RDKVAVAR); + varargk |= PF_VAVAR; } break; } @@ -1079,10 +1087,10 @@ static void parlist (LexState *ls) { f->numparams = cast_byte(fs->nactvar); if (varargk != 0) { setvararg(fs, varargk); /* declared vararg */ - if (varargk & PF_VATAB) - adjustlocalvars(ls, 1); /* vararg table */ + if (varargk & PF_VAVAR) + adjustlocalvars(ls, 1); /* vararg parameter */ } - /* reserve registers for parameters (and vararg variable, if present) */ + /* reserve registers for parameters (plus vararg parameter, if present) */ luaK_reserveregs(fs, fs->nactvar); } diff --git a/lparser.h b/lparser.h index e479905efd..327170e3b1 100644 --- a/lparser.h +++ b/lparser.h @@ -37,6 +37,8 @@ typedef enum { info = result register */ VLOCAL, /* local variable; var.ridx = register index; var.vidx = relative index in 'actvar.arr' */ + VVARGVAR, /* vararg parameter; var.ridx = register index; + var.vidx = relative index in 'actvar.arr' */ VGLOBAL, /* global variable; info = relative index in 'actvar.arr' (or -1 for implicit declaration) */ @@ -97,7 +99,7 @@ typedef struct expdesc { /* kinds of variables */ #define VDKREG 0 /* regular local */ #define RDKCONST 1 /* local constant */ -#define RDKVATAB 2 /* vararg table */ +#define RDKVAVAR 2 /* vararg parameter */ #define RDKTOCLOSE 3 /* to-be-closed */ #define RDKCTC 4 /* local compile-time constant */ #define GDKREG 5 /* regular global */ diff --git a/ltm.c b/ltm.c index 619be59e93..cc812e6234 100644 --- a/ltm.c +++ b/ltm.c @@ -265,11 +265,11 @@ void luaT_adjustvarargs (lua_State *L, CallInfo *ci, const Proto *p) { setobjs2s(L, L->top.p++, ci->func.p + i); setnilvalue(s2v(ci->func.p + i)); /* erase original parameter (for GC) */ } - if (p->flag & (PF_VAPTAB | PF_VATAB)) { /* is there a vararg table? */ - if (p->flag & PF_VAPTAB) /* is vararg table fake? */ - setnilvalue(s2v(L->top.p)); /* initialize it */ - else + if (p->flag & PF_VAVAR) { /* is there a vararg parameter? */ + if (p->flag & PF_VATAB) /* does it need a vararg table? */ createvarargtab(L, ci->func.p + nfixparams + 1, nextra); + else /* no table; set parameter to nil */ + setnilvalue(s2v(L->top.p)); } ci->func.p += totalargs + 1; ci->top.p += totalargs + 1; diff --git a/testes/vararg.lua b/testes/vararg.lua index 4320684e1c..92f720cb6d 100644 --- a/testes/vararg.lua +++ b/testes/vararg.lua @@ -150,5 +150,31 @@ do local a, b = g() assert(a == nil and b == 2) end + + +do -- vararg parameter used in nested functions + local function foo (... = tab1) + return function (... = tab2) + return {tab1, tab2} + end + end + local f = foo(10, 20, 30) + local t = f("a", "b") + assert(t[1].n == 3 and t[1][1] == 10) + assert(t[2].n == 2 and t[2][1] == "a") +end + +do -- vararg parameter is read-only + local st, msg = load("return function (... = t) t = 10 end") + assert(string.find(msg, "const variable 't'")) + + local st, msg = load[[ + local function foo (... = extra) + return function (...) extra = nil end + end + ]] + assert(string.find(msg, "const variable 'extra'")) +end + print('OK') From 0cc3c9447cca9abae9738ee77c24d88801c3916c Mon Sep 17 00:00:00 2001 From: Roberto I Date: Thu, 18 Sep 2025 11:03:55 -0300 Subject: [PATCH 120/165] Small tweaks in makefile --- makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/makefile b/makefile index 8506e93c20..2a8ca48969 100644 --- a/makefile +++ b/makefile @@ -15,9 +15,9 @@ CWARNSCPP= \ -Wdouble-promotion \ -Wmissing-declarations \ -Wconversion \ - -Wstrict-overflow=2 \ # the next warnings might be useful sometimes, # but usually they generate too much noise + # -Wstrict-overflow=2 \ # -Werror \ # -pedantic # warns if we use jump tables \ # -Wformat=2 \ @@ -166,8 +166,7 @@ ldump.o: ldump.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h \ lfunc.o: lfunc.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h \ llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lgc.o: lgc.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h \ - llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h llex.h lstring.h \ - ltable.h + llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h linit.o: linit.c lprefix.h lua.h luaconf.h lualib.h lauxlib.h llimits.h liolib.o: liolib.c lprefix.h lua.h luaconf.h lauxlib.h lualib.h llimits.h llex.o: llex.c lprefix.h lua.h luaconf.h lctype.h llimits.h ldebug.h \ From 25c54fe60e22d05cdfaa48c64372d354efa59547 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 24 Sep 2025 18:33:08 -0300 Subject: [PATCH 121/165] Optimization for vararg tables A vararg table can be virtual. If the vararg table is used only as a base in indexing expressions, the code does not need to create an actual table for it. Instead, it compiles the indexing expressions into direct accesses to the internal vararg data. --- lcode.c | 57 ++++++++++++++++++----------- ljumptab.h | 3 +- lopcodes.c | 1 + lopcodes.h | 2 ++ lopnames.h | 1 + lparser.c | 10 ++++-- lparser.h | 2 ++ ltm.c | 22 ++++++++++++ ltm.h | 1 + lvm.c | 6 ++++ manual/manual.of | 91 +++++++++++++++++++++++++++++++---------------- testes/locals.lua | 6 ++-- testes/vararg.lua | 47 ++++++++++++++++++++---- 13 files changed, 186 insertions(+), 63 deletions(-) diff --git a/lcode.c b/lcode.c index f74223eb66..f7c2334ca7 100644 --- a/lcode.c +++ b/lcode.c @@ -842,6 +842,12 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) { e->k = VRELOC; break; } + case VVARGIND: { + freeregs(fs, e->u.ind.t, e->u.ind.idx); + e->u.info = luaK_codeABC(fs, OP_GETVARG, 0, e->u.ind.t, e->u.ind.idx); + e->k = VRELOC; + break; + } case VVARARG: case VCALL: { luaK_setoneret(fs, e); break; @@ -1004,11 +1010,11 @@ int luaK_exp2anyreg (FuncState *fs, expdesc *e) { /* -** Ensures final expression result is either in a register -** or in an upvalue. +** Ensures final expression result is either in a register, +** in an upvalue, or it is the vararg parameter. */ void luaK_exp2anyregup (FuncState *fs, expdesc *e) { - if (e->k != VUPVAL || hasjumps(e)) + if ((e->k != VUPVAL && e->k != VVARGVAR) || hasjumps(e)) luaK_exp2anyreg(fs, e); } @@ -1314,6 +1320,13 @@ void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { } +/* auxiliary function to define indexing expressions */ +static void fillidxk (expdesc *t, int idx, expkind k) { + t->u.ind.idx = cast_byte(idx); + t->k = k; +} + + /* ** Create expression 't[k]'. 't' must have its final result already in a ** register or upvalue. Upvalues can only be indexed by literal strings. @@ -1325,31 +1338,30 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { if (k->k == VKSTR) keystr = str2K(fs, k); lua_assert(!hasjumps(t) && - (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL)); + (t->k == VLOCAL || t->k == VVARGVAR || + t->k == VNONRELOC || t->k == VUPVAL)); if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non 'Kstr'? */ luaK_exp2anyreg(fs, t); /* put it in a register */ if (t->k == VUPVAL) { lu_byte temp = cast_byte(t->u.info); /* upvalue index */ t->u.ind.t = temp; /* (can't do a direct assignment; values overlap) */ lua_assert(isKstr(fs, k)); - t->u.ind.idx = cast_short(k->u.info); /* literal short string */ - t->k = VINDEXUP; + 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 */ } else { /* register index of the table */ t->u.ind.t = cast_byte((t->k == VLOCAL) ? t->u.var.ridx: t->u.info); - if (isKstr(fs, k)) { - t->u.ind.idx = cast_short(k->u.info); /* literal short string */ - t->k = VINDEXSTR; - } - else if (isCint(k)) { /* int. constant in proper range? */ - t->u.ind.idx = cast_short(k->u.ival); - t->k = VINDEXI; - } - else { - t->u.ind.idx = cast_short(luaK_exp2anyreg(fs, k)); /* register */ - t->k = VINDEXED; - } + if (isKstr(fs, k)) + fillidxk(t, k->u.info, VINDEXSTR); /* literal short string */ + else if (isCint(k)) /* int. constant in proper range? */ + fillidxk(t, cast_int(k->u.ival), VINDEXI); + else + fillidxk(t, luaK_exp2anyreg(fs, k), VINDEXED); /* register */ } t->u.ind.keystr = keystr; /* string index in 'k' */ t->u.ind.ro = 0; /* by default, not read-only */ @@ -1913,9 +1925,14 @@ void luaK_finish (FuncState *fs) { SETARG_C(*pc, p->numparams + 1); /* signal that it is vararg */ break; } - case OP_JMP: { + case OP_GETVARG: { + if (p->flag & PF_VATAB) /* function has a vararg table? */ + SET_OPCODE(*pc, OP_GETTABLE); /* must get vararg there */ + break; + } + case OP_JMP: { /* to optimize jumps to jumps */ int target = finaltarget(p->code, i); - fixjump(fs, i, target); + fixjump(fs, i, target); /* jump directly to final target */ break; } default: break; diff --git a/ljumptab.h b/ljumptab.h index a24828bb5a..f896b6585b 100644 --- a/ljumptab.h +++ b/ljumptab.h @@ -21,7 +21,7 @@ static const void *const disptab[NUM_OPCODES] = { #if 0 ** you can update the following list with this command: ** -** sed -n '/^OP_/\!d; s/OP_/\&\&L_OP_/ ; s/,.*/,/ ; s/\/.*// ; p' lopcodes.h +** sed -n '/^OP_/!d; s/OP_/\&\&L_OP_/ ; s/,.*/,/ ; s/\/.*// ; p' lopcodes.h ** #endif @@ -106,6 +106,7 @@ static const void *const disptab[NUM_OPCODES] = { &&L_OP_SETLIST, &&L_OP_CLOSURE, &&L_OP_VARARG, +&&L_OP_GETVARG, &&L_OP_VARARGPREP, &&L_OP_EXTRAARG diff --git a/lopcodes.c b/lopcodes.c index 79ffbe2590..47458e40ca 100644 --- a/lopcodes.c +++ b/lopcodes.c @@ -102,6 +102,7 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { ,opmode(0, 0, 1, 0, 0, ivABC) /* OP_SETLIST */ ,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */ ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETVARG */ ,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */ ,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */ }; diff --git a/lopcodes.h b/lopcodes.h index c3f7f64d69..82bba721ff 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -338,6 +338,8 @@ OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */ OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */ +OP_GETVARG, /* A B C R[A] := R[B][R[C]], R[B] is vararg parameter */ + OP_VARARGPREP,/* (adjust vararg parameters) */ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ diff --git a/lopnames.h b/lopnames.h index 39df332f5e..aa7bea77c9 100644 --- a/lopnames.h +++ b/lopnames.h @@ -94,6 +94,7 @@ static const char *const opnames[] = { "SETLIST", "CLOSURE", "VARARG", + "GETVARG", "VARARGPREP", "EXTRAARG", NULL diff --git a/lparser.c b/lparser.c index 8b909f3d44..408b8e216d 100644 --- a/lparser.c +++ b/lparser.c @@ -279,7 +279,9 @@ static void init_var (FuncState *fs, expdesc *e, int vidx) { /* -** Raises an error if variable described by 'e' is read only +** Raises an error if variable described by 'e' is read only; moreover, +** if 'e' is t[exp] where t is the vararg parameter, change it to index +** a real table. (Virtual vararg tables cannot be changed.) */ static void check_readonly (LexState *ls, expdesc *e) { FuncState *fs = ls->fs; @@ -301,6 +303,10 @@ static void check_readonly (LexState *ls, expdesc *e) { varname = up->name; break; } + case VVARGIND: { + fs->f->flag |= PF_VATAB; /* function will need a vararg table */ + e->k = VINDEXED; + } /* FALLTHROUGH */ case VINDEXUP: case VINDEXSTR: case VINDEXED: { /* global variable */ if (e->u.ind.ro) /* read-only? */ varname = tsvalue(&fs->f->k[e->u.ind.keystr]); @@ -1073,7 +1079,7 @@ static void parlist (LexState *ls) { case TK_DOTS: { varargk |= PF_ISVARARG; luaX_next(ls); - if (testnext(ls, '=')) { + if (testnext(ls, '|')) { new_varkind(ls, str_checkname(ls), RDKVAVAR); varargk |= PF_VAVAR; } diff --git a/lparser.h b/lparser.h index 327170e3b1..a30df04f77 100644 --- a/lparser.h +++ b/lparser.h @@ -51,6 +51,8 @@ typedef enum { ind.ro = true if it represents a read-only global; ind.keystr = if key is a string, index in 'k' of that string; -1 if key is not a string */ + VVARGIND, /* indexed vararg parameter; + ind.* as in VINDEXED */ VINDEXUP, /* indexed upvalue; ind.idx = key's K index; ind.* as in VINDEXED */ diff --git a/ltm.c b/ltm.c index cc812e6234..92a03e71be 100644 --- a/ltm.c +++ b/ltm.c @@ -277,6 +277,28 @@ void luaT_adjustvarargs (lua_State *L, CallInfo *ci, const Proto *p) { } +void luaT_getvararg (CallInfo *ci, StkId ra, TValue *rc) { + int nextra = ci->u.l.nextraargs; + lua_Integer n; + if (tointegerns(rc, &n)) { /* integral value? */ + if (l_castS2U(n) - 1 < cast_uint(nextra)) { + StkId slot = ci->func.p - nextra + cast_int(n) - 1; + setobjs2s(((lua_State*)NULL), ra, slot); + return; + } + } + else if (ttisshrstring(rc)) { /* short-string value? */ + size_t len; + const char *s = getlstr(tsvalue(rc), len); + if (len == 1 && s[0] == 'n') { /* key is "n"? */ + setivalue(s2v(ra), nextra); + return; + } + } + setnilvalue(s2v(ra)); /* else produce nil */ +} + + void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) { int i; int nextra = ci->u.l.nextraargs; diff --git a/ltm.h b/ltm.h index ed479bb4cd..86f457ebce 100644 --- a/ltm.h +++ b/ltm.h @@ -97,6 +97,7 @@ LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, LUAI_FUNC void luaT_adjustvarargs (lua_State *L, struct CallInfo *ci, const Proto *p); +LUAI_FUNC void luaT_getvararg (CallInfo *ci, StkId ra, TValue *rc); LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci, StkId where, int wanted); diff --git a/lvm.c b/lvm.c index d88a80d19f..3ce7e87f8f 100644 --- a/lvm.c +++ b/lvm.c @@ -1926,6 +1926,12 @@ void luaV_execute (lua_State *L, CallInfo *ci) { Protect(luaT_getvarargs(L, ci, ra, n)); vmbreak; } + vmcase(OP_GETVARG) { + StkId ra = RA(i); + TValue *rc = vRC(i); + luaT_getvararg(ci, ra, rc); + vmbreak; + } vmcase(OP_VARARGPREP) { ProtectNT(luaT_adjustvarargs(L, ci, cl->p)); if (l_unlikely(trap)) { /* previous "Protect" updated trap */ diff --git a/manual/manual.of b/manual/manual.of index 3c7041182a..beea41f96a 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2262,7 +2262,7 @@ return x or f(x) -- results adjusted to 1 @sect3{func-def| @title{Function Definitions} -The syntax for function definition is +The syntax for a function definition is @Produc{ @producname{functiondef}@producbody{@Rw{function} funcbody} @producname{funcbody}@producbody{@bnfter{(} @bnfopt{parlist} @bnfter{)} block @Rw{end}} @@ -2315,6 +2315,18 @@ translates to global f; f = function () @rep{body} end } +The @emphx{colon} syntax +is used to emulate @def{methods}, +adding an implicit extra parameter @idx{self} to the function. +Thus, the statement +@verbatim{ +function t.a.b.c:f (@rep{params}) @rep{body} end +} +is syntactic sugar for +@verbatim{ +t.a.b.c.f = function (self, @rep{params}) @rep{body} end +} + A function definition is an executable expression, whose value has type @emph{function}. When Lua precompiles a chunk, @@ -2325,11 +2337,25 @@ the function is @emph{instantiated} (or @emph{closed}). This function instance, or @emphx{closure}, is the final value of the expression. +Results are returned using the @Rw{return} statement @see{control}. +If control reaches the end of a function +without encountering a @Rw{return} statement, +then the function returns with no results. + +@index{multiple return} +There is a system-dependent limit on the number of values +that a function may return. +This limit is guaranteed to be at least 1000. + +@sect4{@title{Parameters} + Parameters act as local variables that are initialized with the argument values: @Produc{ -@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} @bnfter{...}} @Or - @bnfter{...}} +@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} varargparam} @Or + varargparam} +@producname{varargparam}@producbody{@bnfter{...} + @bnfopt{@bnfter{|} @bnfNter{Name}}} } When a Lua function is called, it adjusts its list of @x{arguments} to @@ -2339,11 +2365,12 @@ which is indicated by three dots (@Char{...}) at the end of its parameter list. A variadic function does not adjust its argument list; instead, it collects all extra arguments and supplies them -to the function through a @def{vararg expression}, -which is also written as three dots. -The value of this expression is a list of all actual extra arguments, -similar to a function with multiple results @see{multires}. +to the function through a @def{vararg expression} and, +if present, a @def{vararg table}. +A vararg expression is also written as three dots, +and its value is a list of all actual extra arguments, +similar to a function with multiple results @see{multires}. As an example, consider the following definitions: @verbatim{ @@ -2368,26 +2395,27 @@ g(3, 4, 5, 8) a=3, b=4, ... -> 5 8 g(5, r()) a=5, b=1, ... -> 2 3 } -Results are returned using the @Rw{return} statement @see{control}. -If control reaches the end of a function -without encountering a @Rw{return} statement, -then the function returns with no results. - -@index{multiple return} -There is a system-dependent limit on the number of values -that a function may return. -This limit is guaranteed to be at least 1000. - -The @emphx{colon} syntax -is used to emulate @def{methods}, -adding an implicit extra parameter @idx{self} to the function. -Thus, the statement +The presence of a varag table in a variadic function is indicated +by the @T{|name} syntax after the three dots. +When present, +a vararg table behaves like a read-only local variable +with the given name that is initialized with a table. +In that table, +the values at indices 1, 2, etc. are the extra arguments, +and the value at index @St{n} is the number of extra arguments. +In other words, the code behaves as if the function started with +the following statement, +assuming the standard behavior of @Lid{table.pack}: @verbatim{ -function t.a.b.c:f (@rep{params}) @rep{body} end +local name = table.pack(...) } -is syntactic sugar for -@verbatim{ -t.a.b.c.f = function (self, @rep{params}) @rep{body} end + +As an optimization, +if the vararg table is used only as a base in indexing expressions +(the @T{t} in @T{t[exp]} or @T{t.id}) and it is not an upvalue, +the code does not create an actual table and instead translates +the indexing expressions into accesses to the internal vararg data. + } } @@ -2422,7 +2450,7 @@ for instance @T{foo(e1, e2, e3)} @see{functioncall}.} for instance @T{a , b, c = e1, e2, e3} @see{assignment}.} @item{A local or global declaration, -which is a special case of multiple assignment.} +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}.} @@ -3016,7 +3044,7 @@ typedef void * (*lua_Alloc) (void *ud, size_t osize, size_t nsize);| -The type of the @x{memory-allocation function} used by Lua states. +The type of the @x{memory-allocator function} used by Lua states. The allocator function must provide a functionality similar to @id{realloc}, but not exactly the same. @@ -3482,7 +3510,7 @@ This function should not be called by a finalizer. @APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);| @apii{0,0,-} -Returns the @x{memory-allocation function} of a given state. +Returns the @x{memory-allocator function} of a given state. If @id{ud} is not @id{NULL}, Lua stores in @T{*ud} the opaque pointer given when the memory-allocator function was set. @@ -9732,8 +9760,11 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @producname{funcbody}@producbody{@bnfter{(} @bnfopt{parlist} @bnfter{)} block @Rw{end}} -@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} @bnfter{...}} - @Or @bnfter{...}} +@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} varargparam} @Or + varargparam} + +@producname{varargparam}@producbody{@bnfter{...} + @bnfopt{@bnfter{|} @bnfNter{Name}}} @producname{tableconstructor}@producbody{@bnfter{@Open} @bnfopt{fieldlist} @bnfter{@Close}} diff --git a/testes/locals.lua b/testes/locals.lua index 02f41980a8..5222802f44 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -310,8 +310,7 @@ do -- testing presence of second argument local function foo (howtoclose, obj, n) local ca -- copy of 'a' visible inside its close metamethod do - local a = func2close(function (...) - local t = table.pack(...) + local a = func2close(function (... | t) assert(select("#", ...) == n) assert(t.n == n and t[1] == ca and (t.n < 2 or t[2] == obj)) ca = 15 -- final value to be returned if howtoclose=="scope" @@ -911,8 +910,7 @@ do local extrares -- result from extra yield (if any) - local function check (body, extra, ...) - local t = table.pack(...) -- expected returns + local function check (body, extra, ...|t) local co = coroutine.wrap(body) if extra then extrares = co() -- runs until first (extra) yield diff --git a/testes/vararg.lua b/testes/vararg.lua index 92f720cb6d..5711f78b84 100644 --- a/testes/vararg.lua +++ b/testes/vararg.lua @@ -3,7 +3,7 @@ print('testing vararg') -local function f (a, ...=t) +local function f (a, ...|t) local x = {n = select('#', ...), ...} assert(x.n == t.n) for i = 1, x.n do @@ -20,7 +20,7 @@ local function c12 (...) return res, 2 end -local function vararg (...=t) return t end +local function vararg (... | t) return t end local call = function (f, args) return f(table.unpack(args, 1, args.n)) end @@ -153,8 +153,8 @@ end do -- vararg parameter used in nested functions - local function foo (... = tab1) - return function (... = tab2) + local function foo (... | tab1) + return function (... | tab2) return {tab1, tab2} end end @@ -165,16 +165,51 @@ do -- vararg parameter used in nested functions end do -- vararg parameter is read-only - local st, msg = load("return function (... = t) t = 10 end") + local st, msg = load("return function (... | t) t = 10 end") assert(string.find(msg, "const variable 't'")) local st, msg = load[[ - local function foo (... = extra) + local function foo (... | extra) return function (...) extra = nil end end ]] assert(string.find(msg, "const variable 'extra'")) end + +do -- _ENV as vararg parameter + local st, msg = load[[ + local function aux (... | _ENV) + global a + a = 10 + end ]] + assert(string.find(msg, "const variable 'a'")) +end + + +do -- access to vararg parameter + local function notab (keys, t, ... | v) + for _, k in pairs(keys) do + assert(t[k] == v[k]) + end + assert(t.n == v.n) + end + + local t = table.pack(10, 20, 30) + local keys = {-1, 0, 1, t.n, t.n + 1, 1.0, 1.1, "n", print, "k", "1"} + notab(keys, t, 10, 20, 30) -- ensure stack space + local m = collectgarbage"count" + notab(keys, t, 10, 20, 30) + -- 'notab' does not create any table/object + assert(m == collectgarbage"count") + + -- writing to the vararg table + local function foo (... | t) + t[1] = t[1] + 10 + return t[1] + end + assert(foo(10, 30) == 20) +end + print('OK') From 3347c9d32d4d91b6139bff475c78cf0c4796e2a7 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 10 Oct 2025 13:22:19 -0300 Subject: [PATCH 122/165] Initialization of too many locals break assertion The check for limit of local variables is made after generating code to initialize them. If there are too many local variables not initialized, the coding of instruction OP_LOADNIL could overflow an argument. --- lparser.c | 1 + testes/errors.lua | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lparser.c b/lparser.c index 408b8e216d..dc646fea99 100644 --- a/lparser.c +++ b/lparser.c @@ -547,6 +547,7 @@ static void singlevar (LexState *ls, expdesc *var) { static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { FuncState *fs = ls->fs; int needed = nvars - nexps; /* extra values needed */ + luaK_checkstack(fs, needed); if (hasmultret(e->k)) { /* last expression has multiple returns? */ int extra = needed + 1; /* discount last expression itself */ if (extra < 0) diff --git a/testes/errors.lua b/testes/errors.lua index 4230a35249..00a43fc69b 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -689,21 +689,26 @@ end -- testing syntax limits local function testrep (init, rep, close, repc, finalresult) - local s = init .. string.rep(rep, 100) .. close .. string.rep(repc, 100) - local res, msg = load(s) - assert(res) -- 100 levels is OK + local function gencode (n) + return init .. string.rep(rep, n) .. close .. string.rep(repc, n) + end + local res, msg = load(gencode(100)) -- 100 levels is OK + assert(res) if (finalresult) then assert(res() == finalresult) end - s = init .. string.rep(rep, 500) - local res, msg = load(s) -- 500 levels not ok + local res, msg = load(gencode(500)) -- 500 levels not ok assert(not res and (string.find(msg, "too many") or string.find(msg, "overflow"))) end +testrep("local a", ",a", ";", "") -- local variables +testrep("local a", ",a", "= 1", ",1") -- local variables initialized +testrep("local a", ",a", "= f()", "") -- local variables initialized testrep("local a; a", ",a", "= 1", ",1") -- multiple assignment -testrep("local a; a=", "{", "0", "}") -testrep("return ", "(", "2", ")", 2) +testrep("local a; a=", "{", "0", "}") -- constructors +testrep("return ", "(", "2", ")", 2) -- parentheses +-- nested calls (a(a(a(a(...))))) testrep("local function a (x) return x end; return ", "a(", "2.2", ")", 2.2) testrep("", "do ", "", " end") testrep("", "while a do ", "", " end") From 7a92f3f99a26d9e51be40b744ed4fab0b50ecaa5 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 10 Oct 2025 15:28:41 -0300 Subject: [PATCH 123/165] Change in dumping of NULL strings When dumping a string, adding 2 to its size may overflow a size_t for external strings, which may not have a header. (Adding 1 is Ok, because all strings end with a '\0' not included in their size.) The new method for saving NULL strings code them as a repeated string, using the reserved index 0. --- ldump.c | 22 +++++++++++++--------- lundump.c | 12 ++++++------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/ldump.c b/ldump.c index a75b20d247..5795788922 100644 --- a/ldump.c +++ b/ldump.c @@ -132,27 +132,31 @@ static void dumpInteger (DumpState *D, lua_Integer x) { /* -** Dump a String. First dump its "size": size==0 means NULL; -** size==1 is followed by an index and means "reuse saved string with -** that index"; size>=2 is followed by the string contents with real -** size==size-2 and means that string, which will be saved with -** the next available index. +** Dump a String. First dump its "size": +** size==0 is followed by an index and means "reuse saved string with +** that index"; index==0 means NULL. +** size>=1 is followed by the string contents with real size==size-1 and +** means that string, which will be saved with the next available index. +** The real size does not include the ending '\0' (which is not dumped), +** so adding 1 to it cannot overflow a size_t. */ static void dumpString (DumpState *D, TString *ts) { - if (ts == NULL) - dumpSize(D, 0); + if (ts == NULL) { + dumpVarint(D, 0); /* will "reuse" NULL */ + dumpVarint(D, 0); /* special index for NULL */ + } else { TValue idx; int tag = luaH_getstr(D->h, ts, &idx); if (!tagisempty(tag)) { /* string already saved? */ - dumpVarint(D, 1); /* reuse a saved string */ + dumpVarint(D, 0); /* reuse a saved string */ dumpVarint(D, l_castS2U(ivalue(&idx))); /* index of saved string */ } else { /* must write and save the string */ TValue key, value; /* to save the string in the hash */ size_t size; const char *s = getlstr(ts, size); - dumpSize(D, size + 2); + dumpSize(D, size + 1); dumpVector(D, s, size + 1); /* include ending '\0' */ D->nstr++; /* one more saved string */ setsvalue(D->L, &key, ts); /* the string is the key */ diff --git a/lundump.c b/lundump.c index 74839af8bd..3b61cc8cbb 100644 --- a/lundump.c +++ b/lundump.c @@ -147,20 +147,20 @@ static void loadString (LoadState *S, Proto *p, TString **sl) { TString *ts; TValue sv; size_t size = loadSize(S); - if (size == 0) { /* no string? */ - lua_assert(*sl == NULL); /* must be prefilled */ - return; - } - else if (size == 1) { /* previously saved string? */ + if (size == 0) { /* previously saved string? */ lua_Unsigned idx = loadVarint(S, LUA_MAXUNSIGNED); /* get its index */ TValue stv; + if (idx == 0) { /* no string? */ + lua_assert(*sl == NULL); /* must be prefilled */ + return; + } if (novariant(luaH_getint(S->h, l_castU2S(idx), &stv)) != LUA_TSTRING) error(S, "invalid string index"); *sl = ts = tsvalue(&stv); /* get its value */ luaC_objbarrier(L, p, ts); return; /* do not save it again */ } - else if ((size -= 2) <= LUAI_MAXSHORTLEN) { /* short string? */ + else if ((size -= 1) <= LUAI_MAXSHORTLEN) { /* short string? */ char buff[LUAI_MAXSHORTLEN + 1]; /* extra space for '\0' */ loadVector(S, buff, size + 1); /* load string into buffer */ *sl = ts = luaS_newlstr(L, buff, size); /* create string */ From 30a7b93439f72570cd3315c201b140df3c07e106 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sun, 12 Oct 2025 15:13:28 -0300 Subject: [PATCH 124/165] Two new memory tests For external strings and for vararg tables. --- testes/memerr.lua | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/testes/memerr.lua b/testes/memerr.lua index 77cb47cb1e..69d2ef8550 100644 --- a/testes/memerr.lua +++ b/testes/memerr.lua @@ -42,8 +42,12 @@ checkerr(MEMERRMSG, f) T.alloccount() -- remove limit +-- preallocate stack space +local function deep (n) if n > 0 then deep(n - 1) end end + + -- test memory errors; increase limit for maximum memory by steps, --- o that we get memory errors in all allocations of a given +-- so that we get memory errors in all allocations of a given -- task, until there is enough memory to complete the task without -- errors. local function testbytes (s, f) @@ -53,6 +57,7 @@ local function testbytes (s, f) local a,b = nil while true do collectgarbage(); collectgarbage() + deep(4) T.totalmem(M) a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) T.totalmem(0) -- remove limit @@ -77,6 +82,7 @@ local function testalloc (s, f) local a,b = nil while true do collectgarbage(); collectgarbage() + deep(4) T.alloccount(M) a, b = T.testC("pcall 0 1 0; pushstatus; return 2", f) T.alloccount() -- remove limit @@ -87,21 +93,19 @@ local function testalloc (s, f) M = M + 1 -- increase allocation limit end print(string.format("minimum allocations for %s: %d allocations", s, M)) - return a + return M end local function testamem (s, f) - testalloc(s, f) - return testbytes(s, f) + local aloc = testalloc(s, f) + local res = testbytes(s, f) + return {aloc = aloc, res = res} end --- doing nothing -b = testamem("doing nothing", function () return 10 end) -assert(b == 10) - --- testing memory errors when creating a new state +local b = testamem("function call", function () return 10 end) +assert(b.res == 10 and b.aloc == 0) testamem("state creation", function () local st = T.newstate() @@ -121,6 +125,18 @@ testamem("coroutine creation", function() return coroutine.create(print) end) +do -- vararg tables + local function pack (... | t) return t end + local b = testamem("vararg table", function () + return pack(10, 20, 30, 40, "hello") + end) + assert(b.aloc == 3) -- new table uses three memory blocks + -- table optimized away + local function sel (n, ...|arg) return arg[n] + arg.n end + local b = testamem("optimized vararg table", + function () return sel(2.0, 20, 30) end) + assert(b.res == 32 and b.aloc == 0) -- no memory needed for this case +end -- testing to-be-closed variables testamem("to-be-closed variables", function() @@ -160,6 +176,14 @@ testamem("running code on new thread", function () end) +do -- external strings + local str = string.rep("a", 100) + testamem("creating external strings", function () + return T.externstr(str) + end) +end + + -- testing memory x compiler testamem("loadstring", function () From 9c66903cc55388006a833f0f3911ea81fa86edea Mon Sep 17 00:00:00 2001 From: Roberto I Date: Tue, 14 Oct 2025 13:50:24 -0300 Subject: [PATCH 125/165] Details - Functions luaK_goiffalse, luaS_hash made private. - Removed unused macro log2maxs. --- lcode.c | 2 +- lcode.h | 1 - llimits.h | 7 ------- lstring.c | 2 +- lstring.h | 1 - 5 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lcode.c b/lcode.c index f7c2334ca7..429d4f8020 100644 --- a/lcode.c +++ b/lcode.c @@ -1181,7 +1181,7 @@ void luaK_goiftrue (FuncState *fs, expdesc *e) { /* ** Emit code to go through if 'e' is false, jump otherwise. */ -void luaK_goiffalse (FuncState *fs, expdesc *e) { +static void luaK_goiffalse (FuncState *fs, expdesc *e) { int pc; /* pc of new jump */ luaK_dischargevars(fs, e); switch (e->k) { diff --git a/lcode.h b/lcode.h index 8c27bc92fd..f6397a3cde 100644 --- a/lcode.h +++ b/lcode.h @@ -80,7 +80,6 @@ LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); -LUAI_FUNC void luaK_goiffalse (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e); LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults); LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e); diff --git a/llimits.h b/llimits.h index d115496f7a..2163254378 100644 --- a/llimits.h +++ b/llimits.h @@ -59,13 +59,6 @@ typedef lu_byte TStatus; #define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \ : cast_sizet(LUA_MAXINTEGER)) -/* -** floor of the log2 of the maximum signed value for integral type 't'. -** (That is, maximum 'n' such that '2^n' fits in the given signed type.) -*/ -#define log2maxs(t) (l_numbits(t) - 2) - - /* ** test whether an unsigned value is a power of 2 (or zero) */ diff --git a/lstring.c b/lstring.c index 17c6fd8f51..75635142e9 100644 --- a/lstring.c +++ b/lstring.c @@ -50,7 +50,7 @@ int luaS_eqstr (TString *a, TString *b) { } -unsigned luaS_hash (const char *str, size_t l, unsigned seed) { +static unsigned luaS_hash (const char *str, size_t l, unsigned seed) { unsigned int h = seed ^ cast_uint(l); for (; l > 0; l--) h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1])); diff --git a/lstring.h b/lstring.h index 2eac222b08..1643c3d82b 100644 --- a/lstring.h +++ b/lstring.h @@ -54,7 +54,6 @@ #define eqshrstr(a,b) check_exp((a)->tt == LUA_VSHRSTR, (a) == (b)) -LUAI_FUNC unsigned luaS_hash (const char *str, size_t l, unsigned seed); LUAI_FUNC unsigned luaS_hashlongstr (TString *ts); LUAI_FUNC int luaS_eqstr (TString *a, TString *b); LUAI_FUNC void luaS_resize (lua_State *L, int newsize); From b352217b8498a5ed8f6c954b3da365fcbb89751f Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 17 Oct 2025 13:53:35 -0300 Subject: [PATCH 126/165] Standard allocator function added to the API That makes easier to redefine luaL_newstate. --- lauxlib.c | 12 ++++++------ lauxlib.h | 3 +++ manual/manual.of | 28 +++++++++++++++++++--------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/lauxlib.c b/lauxlib.c index adb3851e82..1bb41bb1da 100644 --- a/lauxlib.c +++ b/lauxlib.c @@ -742,7 +742,7 @@ typedef struct LoadF { static const char *getF (lua_State *L, void *ud, size_t *size) { LoadF *lf = (LoadF *)ud; - (void)L; /* not used */ + UNUSED(L); if (lf->n > 0) { /* are there pre-read characters to be read? */ *size = lf->n; /* return them (chars already in buffer) */ lf->n = 0; /* no more pre-read characters */ @@ -856,7 +856,7 @@ typedef struct LoadS { static const char *getS (lua_State *L, void *ud, size_t *size) { LoadS *ls = (LoadS *)ud; - (void)L; /* not used */ + UNUSED(L); if (ls->size == 0) return NULL; *size = ls->size; ls->size = 0; @@ -1046,8 +1046,8 @@ LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, } -static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { - (void)ud; (void)osize; /* not used */ +void *luaL_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { + UNUSED(ud); UNUSED(osize); if (nsize == 0) { free(ptr); return NULL; @@ -1172,7 +1172,7 @@ static unsigned int luai_makeseed (void) { LUALIB_API unsigned int luaL_makeseed (lua_State *L) { - (void)L; /* unused */ + UNUSED(L); return luai_makeseed(); } @@ -1182,7 +1182,7 @@ LUALIB_API unsigned int luaL_makeseed (lua_State *L) { ** as a macro. */ LUALIB_API lua_State *(luaL_newstate) (void) { - lua_State *L = lua_newstate(l_alloc, NULL, luai_makeseed()); + 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 */ diff --git a/lauxlib.h b/lauxlib.h index d8522098a7..7f1d3ca195 100644 --- a/lauxlib.h +++ b/lauxlib.h @@ -81,6 +81,9 @@ 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); + /* predefined references */ #define LUA_NOREF (-2) diff --git a/manual/manual.of b/manual/manual.of index beea41f96a..ad273d625d 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -3079,11 +3079,12 @@ the allocator must behave like @id{realloc}. In particular, the allocator returns @id{NULL} if and only if it cannot fulfill the request. -Here is a simple implementation for the @x{allocator function}. -It is used in the auxiliary library by @Lid{luaL_newstate}. +Here is a simple implementation for the @x{allocator function}, +corresponding to the function @Lid{luaL_alloc} from the +auxiliary library. @verbatim{ -static void *l_alloc (void *ud, void *ptr, size_t osize, - size_t nsize) { +void *luaL_alloc (void *ud, void *ptr, size_t osize, + size_t nsize) { (void)ud; (void)osize; /* not used */ if (nsize == 0) { free(ptr); @@ -5988,9 +5989,8 @@ it does not run it. @apii{0,0,-} Returns a value with a weak attempt for randomness. -(It produces that value based on the current date and time -and the address of an internal variable, -in case the machine has Address Space Layout Randomization.) +The parameter @id{L} can be @id{NULL} +if there is no Lua state available. } @@ -6046,8 +6046,9 @@ with @id{tname} in the registry. @apii{0,0,-} Creates a new Lua state. -It calls @Lid{lua_newstate} with an -allocator based on the @N{ISO C} allocation functions +It calls @Lid{lua_newstate} with @Lid{luaL_alloc} as +the allocator function and the result of @T{luaL_makeseed(NULL)} +as the seed, and then sets a warning function and a panic function @see{C-error} that print messages to the standard error output. @@ -6271,6 +6272,15 @@ in the registry @seeC{luaL_newmetatable}. } +@APIEntry{ +void *luaL_alloc (void *ud, void *ptr, size_t osize, size_t nsize);| + +A standard allocator function for Lua @seeF{lua_Alloc}, +built on top of the C functions @id{realloc} and @id{free}. + +} + + @APIEntry{ typedef struct luaL_Stream { FILE *f; From 26755cad99cd2a362a3f149114a2e7f05928db0a Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 18 Oct 2025 10:30:12 -0300 Subject: [PATCH 127/165] Added "attribute internal" to __MACH__ platforms Also, makefile does not add compiling options (LOCAL) to linker flags (MYLDFLAGS). --- llimits.h | 18 +++++++++--------- makefile | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/llimits.h b/llimits.h index 2163254378..fc5cb276f6 100644 --- a/llimits.h +++ b/llimits.h @@ -303,21 +303,21 @@ typedef unsigned long l_uint32; ** LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables, ** none of which to be exported to outside modules (LUAI_DDEF for ** definitions and LUAI_DDEC for declarations). -** Elf/gcc (versions 3.2 and later) mark them as "hidden" to optimize -** access when Lua is compiled as a shared library. Not all elf targets -** support this attribute. Unfortunately, gcc does not offer a way to -** check whether the target offers that support, and those without -** support give a warning about it. To avoid these warnings, change to -** the default definition. +** Elf and MACH/gcc (versions 3.2 and later) mark them as "hidden" to +** optimize access when Lua is compiled as a shared library. Not all elf +** targets support this attribute. Unfortunately, gcc does not offer +** a way to check whether the target offers that support, and those +** without support give a warning about it. To avoid these warnings, +** change to the default definition. */ #if !defined(LUAI_FUNC) #if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ - defined(__ELF__) /* { */ + (defined(__ELF__) || defined(__MACH__)) #define LUAI_FUNC __attribute__((visibility("internal"))) extern -#else /* }{ */ +#else #define LUAI_FUNC extern -#endif /* } */ +#endif #define LUAI_DDEC(dec) LUAI_FUNC dec #define LUAI_DDEF /* empty */ diff --git a/makefile b/makefile index 2a8ca48969..8674519f5f 100644 --- a/makefile +++ b/makefile @@ -72,7 +72,7 @@ LOCAL = $(TESTS) $(CWARNS) # For C89, "-std=c89 -DLUA_USE_C89" # Note that Linux/Posix options are not compatible with C89 MYCFLAGS= $(LOCAL) -std=c99 -DLUA_USE_LINUX -MYLDFLAGS= $(LOCAL) -Wl,-E +MYLDFLAGS= -Wl,-E MYLIBS= -ldl From fca974486d12aa29bb6d731fdb5b25055157ece8 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 18 Oct 2025 10:34:42 -0300 Subject: [PATCH 128/165] Small change in 'trymt' Operation name can be diferent from metamethod name. --- lstrlib.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lstrlib.c b/lstrlib.c index d9735903d4..23df839ea0 100644 --- a/lstrlib.c +++ b/lstrlib.c @@ -269,11 +269,18 @@ static int tonum (lua_State *L, int arg) { } -static void trymt (lua_State *L, const char *mtname) { +/* +** To be here, either the first operand was a string or the first +** operand didn't have a corresponding metamethod. (Otherwise, that +** other metamethod would have been called.) So, if this metamethod +** doesn't work, the only other option would be for the second +** operand to have a different metamethod. +*/ +static void trymt (lua_State *L, const char *mtkey, const char *opname) { lua_settop(L, 2); /* back to the original arguments */ if (l_unlikely(lua_type(L, 2) == LUA_TSTRING || - !luaL_getmetafield(L, 2, mtname))) - luaL_error(L, "attempt to %s a '%s' with a '%s'", mtname + 2, + !luaL_getmetafield(L, 2, mtkey))) + luaL_error(L, "attempt to %s a '%s' with a '%s'", opname, luaL_typename(L, -2), luaL_typename(L, -1)); lua_insert(L, -3); /* put metamethod before arguments */ lua_call(L, 2, 1); /* call metamethod */ @@ -284,7 +291,7 @@ static int arith (lua_State *L, int op, const char *mtname) { if (tonum(L, 1) && tonum(L, 2)) lua_arith(L, op); /* result will be on the top */ else - trymt(L, mtname); + trymt(L, mtname, mtname + 2); return 1; } From d4eff00234dc55dac4cb86b6187f5607c1254f9b Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 29 Oct 2025 13:14:48 -0300 Subject: [PATCH 129/165] Fixed initialization of global variables When calling 'luaK_storevar', the 'expdesc' for the variable must be created before the one for the expression, to satisfy the assumptions for register allocation. So, in a statement like 'global a = exp', where 'a' is actually '_ENV.a', this variable must be handled before the initializing expression 'exp'. --- lcode.c | 2 +- lparser.c | 41 +++++++++++++++++++++++++++++------------ testes/goto.lua | 22 ++++++++++++++++++++++ 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/lcode.c b/lcode.c index 429d4f8020..7c63abb2eb 100644 --- a/lcode.c +++ b/lcode.c @@ -1242,7 +1242,7 @@ static void codenot (FuncState *fs, expdesc *e) { ** Check whether expression 'e' is a short literal string */ static int isKstr (FuncState *fs, expdesc *e) { - return (e->k == VK && !hasjumps(e) && e->u.info <= MAXARG_B && + return (e->k == VK && !hasjumps(e) && e->u.info <= MAXINDEXRK && ttisshrstring(&fs->f->k[e->u.info])); } diff --git a/lparser.c b/lparser.c index dc646fea99..e3538c16f5 100644 --- a/lparser.c +++ b/lparser.c @@ -1875,6 +1875,33 @@ static lu_byte getglobalattribute (LexState *ls, lu_byte df) { } +/* +** Recursively traverse list of globals to be initalized. When +** going, generate table description for the global. In the end, +** after all indices have been generated, read list of initializing +** expressions. When returning, generate the assignment of the value on +** the stack to the corresponding table description. 'n' is the variable +** being handled, range [0, nvars - 1]. +*/ +static void initglobal (LexState *ls, int nvars, int firstidx, int n) { + if (n == nvars) { /* traversed all variables? */ + expdesc e; + int nexps = explist(ls, &e); /* read list of expressions */ + adjust_assign(ls, nvars, nexps, &e); + } + else { /* handle variable 'n' */ + FuncState *fs = ls->fs; + expdesc var; + TString *varname = getlocalvardesc(fs, firstidx + n)->vd.name; + buildglobal(ls, varname, &var); /* create global variable in 'var' */ + enterlevel(ls); /* control recursion depth */ + initglobal(ls, nvars, firstidx, n + 1); + leavelevel(ls); + storevartop(fs, &var); + } +} + + static void globalnames (LexState *ls, lu_byte defkind) { FuncState *fs = ls->fs; int nvars = 0; @@ -1885,18 +1912,8 @@ static void globalnames (LexState *ls, lu_byte defkind) { lastidx = new_varkind(ls, vname, kind); nvars++; } while (testnext(ls, ',')); - if (testnext(ls, '=')) { /* initialization? */ - expdesc e; - int i; - int nexps = explist(ls, &e); /* read list of expressions */ - adjust_assign(ls, nvars, nexps, &e); - for (i = 0; i < nvars; i++) { /* for each variable */ - expdesc var; - TString *varname = getlocalvardesc(fs, lastidx - i)->vd.name; - buildglobal(ls, varname, &var); /* create global variable in 'var' */ - storevartop(fs, &var); - } - } + if (testnext(ls, '=')) /* initialization? */ + initglobal(ls, nvars, lastidx - nvars + 1, 0); fs->nactvar = cast_short(fs->nactvar + nvars); /* activate declaration */ } diff --git a/testes/goto.lua b/testes/goto.lua index 3310314d8a..a692a623b4 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -432,5 +432,27 @@ do print "testing initialization in global declarations" _ENV.a, _ENV.b, _ENV.c, _ENV.d = nil -- erase these globals end +do + global table, string + -- global initialization when names don't fit in K + + -- to fill constant table + local code = {} + for i = 1, 300 do code[i] = "'" .. i .. "'" end + code = table.concat(code, ",") + code = string.format([[ + return function (_ENV) + local dummy = {%s} -- fill initial positions in constant table, + -- so that initialization must use registers for global names + global a, b, c = 10, 20, 30 + end]], code) + + local fun = assert(load(code))() + + local env = {} + fun(env) + assert(env.a == 10 and env.b == 20 and env.c == 30) +end + print'OK' From 0149b781d438091ce086449101a916e9b4456b4e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 30 Oct 2025 10:39:55 -0300 Subject: [PATCH 130/165] Case VVARGIND added to luaK_storevar In a global initialization, the variable does not pass through 'check_readonly', and therefore a VVARGIND is not normalized to a VINDEXED. --- lcode.c | 4 ++++ testes/vararg.lua | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/lcode.c b/lcode.c index 7c63abb2eb..f09edb5f88 100644 --- a/lcode.c +++ b/lcode.c @@ -1109,6 +1109,10 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { codeABRK(fs, OP_SETFIELD, var->u.ind.t, var->u.ind.idx, ex); break; } + case VVARGIND: { + fs->f->flag |= PF_VATAB; /* function will need a vararg table */ + /* now, assignment is to a regular table */ + } /* FALLTHROUGH */ case VINDEXED: { codeABRK(fs, OP_SETTABLE, var->u.ind.t, var->u.ind.idx, ex); break; diff --git a/testes/vararg.lua b/testes/vararg.lua index 5711f78b84..840c3eee49 100644 --- a/testes/vararg.lua +++ b/testes/vararg.lua @@ -184,6 +184,18 @@ do -- _ENV as vararg parameter a = 10 end ]] assert(string.find(msg, "const variable 'a'")) + + local function aux (... | _ENV) + global a; a = 10 + return a + end + assert(aux() == 10) + + local function aux (... | _ENV) + global a = 10 + return a + end + assert(aux() == 10) end From d342328e5b24c9b3c6c5b33bfcf9f8534210b8e6 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Thu, 30 Oct 2025 11:07:01 -0300 Subject: [PATCH 131/165] Vertical bar removed from syntax of vararg table The syntax 'function foo (a, b, ...arg)' is already used by JavaScript for this same semantics, so it seems natural to use the same notation in Lua. --- lparser.c | 4 ++-- manual/manual.of | 8 +++----- testes/locals.lua | 4 ++-- testes/memerr.lua | 4 ++-- testes/vararg.lua | 22 +++++++++++----------- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/lparser.c b/lparser.c index e3538c16f5..40a30ff685 100644 --- a/lparser.c +++ b/lparser.c @@ -1079,8 +1079,8 @@ static void parlist (LexState *ls) { } case TK_DOTS: { varargk |= PF_ISVARARG; - luaX_next(ls); - if (testnext(ls, '|')) { + luaX_next(ls); /* skip '...' */ + if (ls->t.token == TK_NAME) { new_varkind(ls, str_checkname(ls), RDKVAVAR); varargk |= PF_VAVAR; } diff --git a/manual/manual.of b/manual/manual.of index ad273d625d..0127df0276 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2354,8 +2354,7 @@ initialized with the argument values: @Produc{ @producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} varargparam} @Or varargparam} -@producname{varargparam}@producbody{@bnfter{...} - @bnfopt{@bnfter{|} @bnfNter{Name}}} +@producname{varargparam}@producbody{@bnfter{...} @bnfopt{@bnfNter{Name}}} } When a Lua function is called, it adjusts its list of @x{arguments} to @@ -2396,7 +2395,7 @@ g(5, r()) a=5, b=1, ... -> 2 3 } The presence of a varag table in a variadic function is indicated -by the @T{|name} syntax after the three dots. +by a name after the three dots. When present, a vararg table behaves like a read-only local variable with the given name that is initialized with a table. @@ -9773,8 +9772,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} varargparam} @Or varargparam} -@producname{varargparam}@producbody{@bnfter{...} - @bnfopt{@bnfter{|} @bnfNter{Name}}} +@producname{varargparam}@producbody{@bnfter{...} @bnfopt{@bnfNter{Name}}} @producname{tableconstructor}@producbody{@bnfter{@Open} @bnfopt{fieldlist} @bnfter{@Close}} diff --git a/testes/locals.lua b/testes/locals.lua index 5222802f44..6cd1054764 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -310,7 +310,7 @@ do -- testing presence of second argument local function foo (howtoclose, obj, n) local ca -- copy of 'a' visible inside its close metamethod do - local a = func2close(function (... | t) + local a = func2close(function (...t) assert(select("#", ...) == n) assert(t.n == n and t[1] == ca and (t.n < 2 or t[2] == obj)) ca = 15 -- final value to be returned if howtoclose=="scope" @@ -910,7 +910,7 @@ do local extrares -- result from extra yield (if any) - local function check (body, extra, ...|t) + local function check (body, extra, ...t) local co = coroutine.wrap(body) if extra then extrares = co() -- runs until first (extra) yield diff --git a/testes/memerr.lua b/testes/memerr.lua index 69d2ef8550..2cc8f48173 100644 --- a/testes/memerr.lua +++ b/testes/memerr.lua @@ -126,13 +126,13 @@ testamem("coroutine creation", function() end) do -- vararg tables - local function pack (... | t) return t end + local function pack (...t) return t end local b = testamem("vararg table", function () return pack(10, 20, 30, 40, "hello") end) assert(b.aloc == 3) -- new table uses three memory blocks -- table optimized away - local function sel (n, ...|arg) return arg[n] + arg.n end + local function sel (n, ...arg) return arg[n] + arg.n end local b = testamem("optimized vararg table", function () return sel(2.0, 20, 30) end) assert(b.res == 32 and b.aloc == 0) -- no memory needed for this case diff --git a/testes/vararg.lua b/testes/vararg.lua index 840c3eee49..a01598ff3b 100644 --- a/testes/vararg.lua +++ b/testes/vararg.lua @@ -3,7 +3,7 @@ print('testing vararg') -local function f (a, ...|t) +local function f (a, ...t) local x = {n = select('#', ...), ...} assert(x.n == t.n) for i = 1, x.n do @@ -20,7 +20,7 @@ local function c12 (...) return res, 2 end -local function vararg (... | t) return t end +local function vararg (... t) return t end local call = function (f, args) return f(table.unpack(args, 1, args.n)) end @@ -153,8 +153,8 @@ end do -- vararg parameter used in nested functions - local function foo (... | tab1) - return function (... | tab2) + local function foo (...tab1) + return function (...tab2) return {tab1, tab2} end end @@ -165,11 +165,11 @@ do -- vararg parameter used in nested functions end do -- vararg parameter is read-only - local st, msg = load("return function (... | t) t = 10 end") + local st, msg = load("return function (... t) t = 10 end") assert(string.find(msg, "const variable 't'")) local st, msg = load[[ - local function foo (... | extra) + local function foo (...extra) return function (...) extra = nil end end ]] @@ -179,19 +179,19 @@ end do -- _ENV as vararg parameter local st, msg = load[[ - local function aux (... | _ENV) + local function aux (... _ENV) global a a = 10 end ]] assert(string.find(msg, "const variable 'a'")) - local function aux (... | _ENV) + local function aux (..._ENV) global a; a = 10 return a end assert(aux() == 10) - local function aux (... | _ENV) + local function aux (... _ENV) global a = 10 return a end @@ -200,7 +200,7 @@ end do -- access to vararg parameter - local function notab (keys, t, ... | v) + local function notab (keys, t, ...v) for _, k in pairs(keys) do assert(t[k] == v[k]) end @@ -216,7 +216,7 @@ do -- access to vararg parameter assert(m == collectgarbage"count") -- writing to the vararg table - local function foo (... | t) + local function foo (...t) t[1] = t[1] + 10 return t[1] end From f791bb69061c15f73395c5a95958ac18af5ef764 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 31 Oct 2025 14:48:55 -0300 Subject: [PATCH 132/165] Details - New macro l_strcoll to ease changing 'strcoll' to something else. - MAXINDEXRK==1 in 'ltests.h' is enough to run test 'code.lua'. - Removed unused '#include' in 'lutf8lib.c'. --- ltests.h | 7 +------ lutf8lib.c | 1 - lvm.c | 10 +++++++++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ltests.h b/ltests.h index 26ffed83de..93096da810 100644 --- a/ltests.h +++ b/ltests.h @@ -142,12 +142,7 @@ LUA_API void *debug_realloc (void *ud, void *block, #define STRCACHE_N 23 #define STRCACHE_M 5 - -/* -** This one is not compatible with tests for opcode optimizations, -** as it blocks some optimizations -#define MAXINDEXRK 0 -*/ +#define MAXINDEXRK 1 /* diff --git a/lutf8lib.c b/lutf8lib.c index df49c901d6..b7f3fe1e16 100644 --- a/lutf8lib.c +++ b/lutf8lib.c @@ -10,7 +10,6 @@ #include "lprefix.h" -#include #include #include #include diff --git a/lvm.c b/lvm.c index 3ce7e87f8f..efb0db289e 100644 --- a/lvm.c +++ b/lvm.c @@ -372,6 +372,14 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key, } +/* +** Function to be used for 0-terminated string order comparison +*/ +#if !defined(l_strcoll) +#define l_strcoll strcoll +#endif + + /* ** Compare two strings 'ts1' x 'ts2', returning an integer less-equal- ** -greater than zero if 'ts1' is less-equal-greater than 'ts2'. @@ -386,7 +394,7 @@ static int l_strcmp (const TString *ts1, const TString *ts2) { size_t rl2; const char *s2 = getlstr(ts2, rl2); for (;;) { /* for each segment */ - int temp = strcoll(s1, s2); + int temp = l_strcoll(s1, s2); if (temp != 0) /* not equal? */ return temp; /* done */ else { /* strings are equal up to a '\0' */ From e44f3a2ffc7ced5e75cca7657aaa60ef27da89aa Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 8 Nov 2025 11:43:42 -0300 Subject: [PATCH 133/165] Global initialization checks name conflict Initialization "global a = 10" raises an error if global 'a' is already defined, that is, it has a non-nil value. --- lcode.c | 16 ++++++++++++++++ lcode.h | 2 ++ ldebug.c | 8 ++++++++ ldebug.h | 1 + ljumptab.h | 1 + lopcodes.c | 1 + lopcodes.h | 2 ++ lopnames.h | 1 + lparser.c | 19 ++++++++++++++++--- lvm.c | 6 ++++++ manual/manual.of | 14 +++++++++++--- testes/goto.lua | 21 ++++++++++++++++++++- testes/memerr.lua | 4 ++-- 13 files changed, 87 insertions(+), 9 deletions(-) diff --git a/lcode.c b/lcode.c index f09edb5f88..d82f8263e1 100644 --- a/lcode.c +++ b/lcode.c @@ -705,6 +705,22 @@ static void luaK_float (FuncState *fs, int reg, lua_Number f) { } +/* +** Get the value of 'var' in a register and generate an opcode to check +** whether that register is nil. 'k' is the index of the variable name +** in the list of constants. If its value cannot be encoded in Bx, a 0 +** will use '?' for the name. +*/ +void luaK_codecheckglobal (FuncState *fs, expdesc *var, int k, int line) { + luaK_exp2anyreg(fs, var); + luaK_fixline(fs, line); + k = (k >= MAXARG_Bx) ? 0 : k + 1; + luaK_codeABx(fs, OP_ERRNNIL, var->u.info, k); + luaK_fixline(fs, line); + freeexp(fs, var); +} + + /* ** Convert a constant in 'v' into an expression description 'e' */ diff --git a/lcode.h b/lcode.h index f6397a3cde..09e5c802b0 100644 --- a/lcode.h +++ b/lcode.h @@ -68,6 +68,8 @@ LUAI_FUNC int luaK_codevABCk (FuncState *fs, OpCode o, int A, int B, int C, LUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v); LUAI_FUNC void luaK_fixline (FuncState *fs, int line); LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); +LUAI_FUNC void luaK_codecheckglobal (FuncState *fs, expdesc *var, int k, + int line); LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); LUAI_FUNC void luaK_int (FuncState *fs, int reg, lua_Integer n); diff --git a/ldebug.c b/ldebug.c index 9110f437bf..abead91ce6 100644 --- a/ldebug.c +++ b/ldebug.c @@ -814,6 +814,14 @@ l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { } +l_noret luaG_errnnil (lua_State *L, LClosure *cl, int k) { + const char *globalname = "?"; /* default name if k == 0 */ + if (k > 0) + kname(cl->p, k - 1, &globalname); + luaG_runerror(L, "global '%s' already defined", globalname); +} + + /* add src:line information to 'msg' */ const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, int line) { diff --git a/ldebug.h b/ldebug.h index 2bfce3cb5e..20d07818b4 100644 --- a/ldebug.h +++ b/ldebug.h @@ -53,6 +53,7 @@ LUAI_FUNC l_noret luaG_tointerror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2); +LUAI_FUNC l_noret luaG_errnnil (lua_State *L, LClosure *cl, int k); LUAI_FUNC l_noret luaG_runerror (lua_State *L, const char *fmt, ...); LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, int line); diff --git a/ljumptab.h b/ljumptab.h index f896b6585b..52fa6d746e 100644 --- a/ljumptab.h +++ b/ljumptab.h @@ -107,6 +107,7 @@ static const void *const disptab[NUM_OPCODES] = { &&L_OP_CLOSURE, &&L_OP_VARARG, &&L_OP_GETVARG, +&&L_OP_ERRNNIL, &&L_OP_VARARGPREP, &&L_OP_EXTRAARG diff --git a/lopcodes.c b/lopcodes.c index 47458e40ca..7e182315bc 100644 --- a/lopcodes.c +++ b/lopcodes.c @@ -103,6 +103,7 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { ,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */ ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETVARG */ + ,opmode(0, 0, 0, 0, 0, iABx) /* OP_ERRNNIL */ ,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */ ,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */ }; diff --git a/lopcodes.h b/lopcodes.h index 82bba721ff..f7bded2cc1 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -340,6 +340,8 @@ OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */ OP_GETVARG, /* A B C R[A] := R[B][R[C]], R[B] is vararg parameter */ +OP_ERRNNIL,/* A Bx raise error if R[A] ~= nil (K[Bx] is global name)*/ + OP_VARARGPREP,/* (adjust vararg parameters) */ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ diff --git a/lopnames.h b/lopnames.h index aa7bea77c9..0554a2e9a1 100644 --- a/lopnames.h +++ b/lopnames.h @@ -95,6 +95,7 @@ static const char *const opnames[] = { "CLOSURE", "VARARG", "GETVARG", + "ERRNNIL", "VARARGPREP", "EXTRAARG", NULL diff --git a/lparser.c b/lparser.c index 40a30ff685..77141e79f9 100644 --- a/lparser.c +++ b/lparser.c @@ -1875,6 +1875,16 @@ static lu_byte getglobalattribute (LexState *ls, lu_byte df) { } +static void checkglobal (LexState *ls, TString *varname, int line) { + FuncState *fs = ls->fs; + expdesc var; + int k; + buildglobal(ls, varname, &var); /* create global variable in 'var' */ + k = var.u.ind.keystr; /* index of global name in 'k' */ + luaK_codecheckglobal(fs, &var, k, line); +} + + /* ** Recursively traverse list of globals to be initalized. When ** going, generate table description for the global. In the end, @@ -1883,7 +1893,8 @@ static lu_byte getglobalattribute (LexState *ls, lu_byte df) { ** the stack to the corresponding table description. 'n' is the variable ** being handled, range [0, nvars - 1]. */ -static void initglobal (LexState *ls, int nvars, int firstidx, int n) { +static void initglobal (LexState *ls, int nvars, int firstidx, int n, + int line) { if (n == nvars) { /* traversed all variables? */ expdesc e; int nexps = explist(ls, &e); /* read list of expressions */ @@ -1895,8 +1906,9 @@ static void initglobal (LexState *ls, int nvars, int firstidx, int n) { TString *varname = getlocalvardesc(fs, firstidx + n)->vd.name; buildglobal(ls, varname, &var); /* create global variable in 'var' */ enterlevel(ls); /* control recursion depth */ - initglobal(ls, nvars, firstidx, n + 1); + initglobal(ls, nvars, firstidx, n + 1, line); leavelevel(ls); + checkglobal(ls, varname, line); storevartop(fs, &var); } } @@ -1913,7 +1925,7 @@ static void globalnames (LexState *ls, lu_byte defkind) { nvars++; } while (testnext(ls, ',')); if (testnext(ls, '=')) /* initialization? */ - initglobal(ls, nvars, lastidx - nvars + 1, 0); + initglobal(ls, nvars, lastidx - nvars + 1, 0, ls->linenumber); fs->nactvar = cast_short(fs->nactvar + nvars); /* activate declaration */ } @@ -1943,6 +1955,7 @@ static void globalfunc (LexState *ls, int line) { fs->nactvar++; /* enter its scope */ buildglobal(ls, fname, &var); body(ls, &b, 0, ls->linenumber); /* compile and return closure in 'b' */ + checkglobal(ls, fname, line); luaK_storevar(fs, &var, &b); luaK_fixline(fs, line); /* definition "happens" in the first line */ } diff --git a/lvm.c b/lvm.c index efb0db289e..2a9fb67a7e 100644 --- a/lvm.c +++ b/lvm.c @@ -1940,6 +1940,12 @@ void luaV_execute (lua_State *L, CallInfo *ci) { luaT_getvararg(ci, ra, rc); vmbreak; } + vmcase(OP_ERRNNIL) { + TValue *ra = vRA(i); + if (!ttisnil(ra)) + halfProtect(luaG_errnnil(L, cl, GETARG_Bx(i))); + vmbreak; + } vmcase(OP_VARARGPREP) { ProtectNT(luaT_adjustvarargs(L, ci, cl->p)); if (l_unlikely(trap)) { /* previous "Protect" updated trap */ diff --git a/manual/manual.of b/manual/manual.of index 0127df0276..eaf0ce7890 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -1660,9 +1660,15 @@ The declaration can include an initialization: @producname{stat}@producbody{@Rw{global} attnamelist @bnfopt{@bnfter{=} explist}} } -If present, an initial assignment has the same semantics +If there is no initialization, +local variables are initialized with @nil; +global variables are left unchanged. +Otherwise, the initialization gets the same adjustment of a multiple assignment @see{assignment}. -Otherwise, all local variables are initialized with @nil. +Moreover, for global variables, +the initialization will raise a runtime error +if the variable is already defined, +that is, it has a non-nil value. The list of names may be prefixed by an attribute (a name between angle brackets) @@ -2312,8 +2318,10 @@ global function f () @rep{body} end } translates to @verbatim{ -global f; f = function () @rep{body} end +global f; global f = function () @rep{body} end } +The second @Rw{global} makes the assignment an initialization, +which will raise an error if that global is already defined. The @emphx{colon} syntax is used to emulate @def{methods}, diff --git a/testes/goto.lua b/testes/goto.lua index a692a623b4..906208b553 100644 --- a/testes/goto.lua +++ b/testes/goto.lua @@ -293,6 +293,7 @@ end foo() -------------------------------------------------------------------------- +-- check for compilation errors local function checkerr (code, err) local st, msg = load(code) assert(not st and string.find(msg, err)) @@ -414,22 +415,26 @@ end do print "testing initialization in global declarations" global a, b, c = 10, 20, 30 assert(_ENV.a == 10 and b == 20 and c == 30) + _ENV.a = nil; _ENV.b = nil; _ENV.c = nil; global a, b, c = 10 assert(_ENV.a == 10 and b == nil and c == nil) + _ENV.a = nil; _ENV.b = nil; _ENV.c = nil; global table global a, b, c, d = table.unpack{1, 2, 3, 6, 5} assert(_ENV.a == 1 and b == 2 and c == 3 and d == 6) + a = nil; b = nil; c = nil; d = nil local a, b = 100, 200 do global a, b = a, b end assert(_ENV.a == 100 and _ENV.b == 200) + _ENV.a = nil; _ENV.b = nil - _ENV.a, _ENV.b, _ENV.c, _ENV.d = nil -- erase these globals + assert(_ENV.a == nil and _ENV.b == nil and _ENV.c == nil and _ENV.d == nil) end do @@ -454,5 +459,19 @@ do assert(env.a == 10 and env.b == 20 and env.c == 30) end + +do -- testing global redefinitions + -- cannot use 'checkerr' as errors are not compile time + global pcall + local f = assert(load("global print = 10")) + local st, msg = pcall(f) + assert(string.find(msg, "global 'print' already defined")) + + local f = assert(load("local _ENV = {AA = false}; global AA = 10")) + local st, msg = pcall(f) + assert(string.find(msg, "global 'AA' already defined")) + +end + print'OK' diff --git a/testes/memerr.lua b/testes/memerr.lua index 2cc8f48173..9c940ca79a 100644 --- a/testes/memerr.lua +++ b/testes/memerr.lua @@ -166,9 +166,9 @@ local function expand (n,s) e, s, expand(n-1,s), e) end -G=0; collectgarbage(); a =collectgarbage("count") +G=0; collectgarbage() load(expand(20,"G=G+1"))() -assert(G==20); collectgarbage(); -- assert(gcinfo() <= a+1) +assert(G==20); collectgarbage() G = nil testamem("running code on new thread", function () From 81f4def54f440e045b1401f11ef78b65b56b7abe Mon Sep 17 00:00:00 2001 From: Roberto I Date: Tue, 11 Nov 2025 14:36:16 -0300 Subject: [PATCH 134/165] Correction in line info for semantic errors Semantic errors should refer the last used token, not the next one. --- lcode.c | 1 + testes/errors.lua | 64 +++++++++++++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/lcode.c b/lcode.c index d82f8263e1..95ef900cfd 100644 --- a/lcode.c +++ b/lcode.c @@ -45,6 +45,7 @@ l_noret luaK_semerror (LexState *ls, const char *fmt, ...) { va_list argp; pushvfstring(ls->L, argp, fmt, msg); ls->t.token = 0; /* remove "near " from final message */ + ls->linenumber = ls->lastline; /* back to line of last used token */ luaX_syntaxerror(ls, msg); } diff --git a/testes/errors.lua b/testes/errors.lua index 00a43fc69b..c9d850994b 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -418,28 +418,28 @@ end -- testing line error -local function lineerror (s, l) +local function lineerror (s, l, w) local err,msg = pcall(load(s)) local line = tonumber(string.match(msg, ":(%d+):")) - assert(line == l or (not line and not l)) + assert((line == l or (not line and not l)) and string.find(msg, w)) end -lineerror("local a\n for i=1,'a' do \n print(i) \n end", 2) -lineerror("\n local a \n for k,v in 3 \n do \n print(k) \n end", 3) -lineerror("\n\n for k,v in \n 3 \n do \n print(k) \n end", 4) -lineerror("function a.x.y ()\na=a+1\nend", 1) +lineerror("local a\n for i=1,'a' do \n print(i) \n end", 2, "limit") +lineerror("\n local a \n for k,v in 3 \n do \n print(k) \n end", 3, "to call") +lineerror("\n\n for k,v in \n 3 \n do \n print(k) \n end", 4, "to call") +lineerror("function a.x.y ()\na=a+1\nend", 1, "index") -lineerror("a = \na\n+\n{}", 3) -lineerror("a = \n3\n+\n(\n4\n/\nprint)", 6) -lineerror("a = \nprint\n+\n(\n4\n/\n7)", 3) +lineerror("a = \na\n+\n{}", 3, "arithmetic") +lineerror("a = \n3\n+\n(\n4\n/\nprint)", 6, "arithmetic") +lineerror("a = \nprint\n+\n(\n4\n/\n7)", 3, "arithmetic") -lineerror("a\n=\n-\n\nprint\n;", 3) +lineerror("a\n=\n-\n\nprint\n;", 3, "arithmetic") lineerror([[ a ( -- << 23) -]], 2) +]], 2, "call") lineerror([[ local a = {x = 13} @@ -449,7 +449,7 @@ x ( -- << 23 ) -]], 5) +]], 5, "call") lineerror([[ local a = {x = 13} @@ -459,17 +459,17 @@ x ( 23 + a ) -]], 6) +]], 6, "arithmetic") local p = [[ function g() f() end function f(x) error('a', XX) end g() ]] -XX=3;lineerror((p), 3) -XX=0;lineerror((p), false) -XX=1;lineerror((p), 2) -XX=2;lineerror((p), 1) +XX=3;lineerror((p), 3, "a") +XX=0;lineerror((p), false, "a") +XX=1;lineerror((p), 2, "a") +XX=2;lineerror((p), 1, "a") _G.XX, _G.g, _G.f = nil @@ -477,7 +477,7 @@ lineerror([[ local b = false if not b then error 'test' -end]], 3) +end]], 3, "test") lineerror([[ local b = false @@ -487,7 +487,7 @@ if not b then error 'test' end end -end]], 5) +end]], 5, "test") lineerror([[ _ENV = 1 @@ -495,7 +495,7 @@ global function foo () local a = 10 return a end -]], 2) +]], 2, "index") -- bug in 5.4.0 @@ -503,17 +503,37 @@ lineerror([[ local a = 0 local b = 1 local c = b % a -]], 3) +]], 3, "perform") do -- Force a negative estimate for base line. Error in instruction 2 -- (after VARARGPREP, GETGLOBAL), with first absolute line information -- (forced by too many lines) in instruction 0. local s = string.format("%s return __A.x", string.rep("\n", 300)) - lineerror(s, 301) + lineerror(s, 301, "index") end +local function stxlineerror (s, l, w) + local err,msg = load(s) + local line = tonumber(string.match(msg, ":(%d+):")) + assert((line == l or (not line and not l)) and string.find(msg, w, 1, true)) +end + +stxlineerror([[ +::L1:: +::L1:: +]], 2, "already defined") + +stxlineerror([[ +global none +local x = b +]], 2, "not declared") + +stxlineerror([[ +local a, b +]], 1, "multiple") + if not _soft then -- several tests that exhaust the Lua stack collectgarbage() From 5b7d9987642f72d44223a8e5e79e013bb2b3d579 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Tue, 11 Nov 2025 14:40:30 -0300 Subject: [PATCH 135/165] External strings are as good as internal ones A '__mode' metafield and an "n" key both can be external strings. --- lgc.c | 6 +++--- ltm.c | 2 +- lvm.c | 9 +++++++-- testes/strings.lua | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/lgc.c b/lgc.c index a775b6e510..60f042c7a8 100644 --- a/lgc.c +++ b/lgc.c @@ -594,10 +594,10 @@ static void traversestrongtable (global_State *g, Table *h) { */ static int getmode (global_State *g, Table *h) { const TValue *mode = gfasttm(g, h->metatable, TM_MODE); - if (mode == NULL || !ttisshrstring(mode)) - return 0; /* ignore non-(short)string modes */ + if (mode == NULL || !ttisstring(mode)) + return 0; /* ignore non-string modes */ else { - const char *smode = getshrstr(tsvalue(mode)); + const char *smode = getstr(tsvalue(mode)); const char *weakkey = strchr(smode, 'k'); const char *weakvalue = strchr(smode, 'v'); return ((weakkey != NULL) << 1) | (weakvalue != NULL); diff --git a/ltm.c b/ltm.c index 92a03e71be..8d64235e81 100644 --- a/ltm.c +++ b/ltm.c @@ -287,7 +287,7 @@ void luaT_getvararg (CallInfo *ci, StkId ra, TValue *rc) { return; } } - else if (ttisshrstring(rc)) { /* short-string value? */ + else if (ttisstring(rc)) { /* string value? */ size_t len; const char *s = getlstr(tsvalue(rc), len); if (len == 1 && s[0] == 'n') { /* key is "n"? */ diff --git a/lvm.c b/lvm.c index 2a9fb67a7e..2c868c2128 100644 --- a/lvm.c +++ b/lvm.c @@ -657,6 +657,11 @@ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { #define tostring(L,o) \ (ttisstring(o) || (cvt2str(o) && (luaO_tostring(L, o), 1))) +/* +** Check whether object is a short empty string to optimize concatenation. +** (External strings can be empty too; they will be concatenated like +** non-empty ones.) +*/ #define isemptystr(o) (ttisshrstring(o) && tsvalue(o)->shrlen == 0) /* copy strings in stack from top - n up to top - 1 to buffer */ @@ -691,8 +696,8 @@ void luaV_concat (lua_State *L, int total) { setobjs2s(L, top - 2, top - 1); /* result is second op. */ } else { - /* at least two non-empty string values; get as many as possible */ - size_t tl = tsslen(tsvalue(s2v(top - 1))); + /* at least two string values; get as many as possible */ + size_t tl = tsslen(tsvalue(s2v(top - 1))); /* total length */ TString *ts; /* collect total length and number of strings */ for (n = 1; n < total && tostring(L, s2v(top - n - 1)); n++) { diff --git a/testes/strings.lua b/testes/strings.lua index 46912d4392..84ff115483 100644 --- a/testes/strings.lua +++ b/testes/strings.lua @@ -540,6 +540,23 @@ else assert(y == x) local z = T.externstr(x) -- external allocated long string assert(z == y) + + local e = T.externstr("") -- empty external string + assert(e .. "x" == "x" and "x" .. e == "x") + assert(e .. e == "" and #e == 0) + + -- external string as the "n" key in vararg table + local n = T.externstr("n") + local n0 = T.externstr("n\0") + local function aux (...t) assert(t[n0] == nil); return t[n] end + assert(aux(10, 20, 30) == 3) + + -- external string as mode in weak table + local t = setmetatable({}, {__mode = T.externstr("kv")}) + t[{}] = {} + assert(next(t)) + collectgarbage() + assert(next(t) == nil) end print('OK') From 4cf498210e6a60637a7abb06d32460ec21efdbdc Mon Sep 17 00:00:00 2001 From: Roberto I Date: Tue, 11 Nov 2025 15:11:06 -0300 Subject: [PATCH 136/165] '__pairs' can also return a to-be-closed object --- lbaselib.c | 13 +++++++------ manual/manual.of | 4 ++-- testes/nextvar.lua | 7 ++++++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lbaselib.c b/lbaselib.c index b296c4b761..891bb90f48 100644 --- a/lbaselib.c +++ b/lbaselib.c @@ -279,21 +279,22 @@ static int luaB_next (lua_State *L) { static int pairscont (lua_State *L, int status, lua_KContext k) { (void)L; (void)status; (void)k; /* unused */ - return 3; + return 4; /* __pairs did all the work, just return its results */ } static int luaB_pairs (lua_State *L) { luaL_checkany(L, 1); if (luaL_getmetafield(L, 1, "__pairs") == LUA_TNIL) { /* no metamethod? */ - lua_pushcfunction(L, luaB_next); /* will return generator, */ - lua_pushvalue(L, 1); /* state, */ - lua_pushnil(L); /* and initial value */ + lua_pushcfunction(L, luaB_next); /* will return generator and */ + lua_pushvalue(L, 1); /* state */ + lua_pushnil(L); /* initial value */ + lua_pushnil(L); /* to-be-closed object */ } else { lua_pushvalue(L, 1); /* argument 'self' to metamethod */ - lua_callk(L, 1, 3, 0, pairscont); /* get 3 values from metamethod */ + lua_callk(L, 1, 4, 0, pairscont); /* get 4 values from metamethod */ } - return 3; + return 4; } diff --git a/manual/manual.of b/manual/manual.of index eaf0ce7890..9b6976ca05 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -6799,11 +6799,11 @@ In particular, you may set existing fields to nil. @LibEntry{pairs (t)| If @id{t} has a metamethod @idx{__pairs}, -calls it with @id{t} as argument and returns the first three +calls it with @id{t} as argument and returns the first four results from the call. Otherwise, -returns three values: the @Lid{next} function, the table @id{t}, and @nil, +returns the @Lid{next} function, the table @id{t}, plus two @nil values, so that the construction @verbatim{ for k,v in pairs(t) do @rep{body} end diff --git a/testes/nextvar.lua b/testes/nextvar.lua index 7e5bed5685..098e7891c9 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua @@ -905,13 +905,18 @@ local function foo1 (e,i) if i <= e.n then return i,a[i] end end -setmetatable(a, {__pairs = function (x) return foo, x, 0 end}) +local closed = false +setmetatable(a, {__pairs = function (x) + local tbc = setmetatable({}, {__close = function () closed = true end}) + return foo, x, 0, tbc + end}) local i = 0 for k,v in pairs(a) do i = i + 1 assert(k == i and v == k+1) end +assert(closed) -- 'tbc' has been closed a.n = 5 a[3] = 30 From d94f7ba3040eb06895d7305014e88157d3bfd1a1 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Mon, 24 Nov 2025 11:39:46 -0300 Subject: [PATCH 137/165] Details Comments, capitalization in the manual, globals in test 'heady.lua' --- lopcodes.h | 5 ++++- manual/manual.of | 16 +++++++++------- testes/heavy.lua | 18 ++++++++++-------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lopcodes.h b/lopcodes.h index f7bded2cc1..f5c95151ba 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -340,7 +340,7 @@ OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */ OP_GETVARG, /* A B C R[A] := R[B][R[C]], R[B] is vararg parameter */ -OP_ERRNNIL,/* A Bx raise error if R[A] ~= nil (K[Bx] is global name)*/ +OP_ERRNNIL,/* A Bx raise error if R[A] ~= nil (K[Bx - 1] is global name)*/ OP_VARARGPREP,/* (adjust vararg parameters) */ @@ -386,6 +386,9 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ power of 2) plus 1, or zero for size zero. If not k, the array size is vC. Otherwise, the array size is EXTRAARG _ vC. + (*) In OP_ERRNNIL, (Bx == 0) means index of global name doesn't + fit in Bx. (So, that name is not available for the instruction.) + (*) For comparisons, k specifies what condition the test should accept (true or false). diff --git a/manual/manual.of b/manual/manual.of index 9b6976ca05..96203d7fff 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2402,7 +2402,7 @@ g(3, 4, 5, 8) a=3, b=4, ... -> 5 8 g(5, r()) a=5, b=1, ... -> 2 3 } -The presence of a varag table in a variadic function is indicated +The presence of a vararg table in a variadic function is indicated by a name after the three dots. When present, a vararg table behaves like a read-only local variable @@ -2418,8 +2418,9 @@ local name = table.pack(...) } As an optimization, -if the vararg table is used only as a base in indexing expressions -(the @T{t} in @T{t[exp]} or @T{t.id}) and it is not an upvalue, +if the vararg table is used only as the base table +in the syntactic constructions @T{t[exp]} or @T{t.id}) +and it is not an upvalue, the code does not create an actual table and instead translates the indexing expressions into accesses to the internal vararg data. @@ -2427,8 +2428,7 @@ the indexing expressions into accesses to the internal vararg data. } -@sect3{multires| @title{Lists of expressions, multiple results, -and adjustment} +@sect3{multires| @title{Lists of Expressions, Multiple Results, and Adjustment} Both function calls and vararg expressions can result in multiple values. These expressions are called @def{multires expressions}. @@ -2686,7 +2686,7 @@ which behaves like a nil value. } -@sect3{constchar|@title{Pointers to strings} +@sect3{constchar|@title{Pointers to Strings} Several functions in the API return pointers (@T{const char*}) to Lua strings in the stack. @@ -4126,6 +4126,8 @@ Lua still has to allocate a header for the string. In case of a memory-allocation error, Lua will call @id{falloc} before raising the error. +The function returns a pointer to the string (that is, @id{s}). + } @@ -8413,7 +8415,7 @@ a value greater than any other numeric value. } -@LibEntry{math.ldexp(m, e)| +@LibEntry{math.ldexp (m, e)| Returns @M{m2@sp{e}}, where @id{e} is an integer. diff --git a/testes/heavy.lua b/testes/heavy.lua index 3b4e4ce352..e7219a91ae 100644 --- a/testes/heavy.lua +++ b/testes/heavy.lua @@ -1,6 +1,8 @@ -- $Id: testes/heavy.lua,v $ -- See Copyright Notice in file lua.h +global * + local function teststring () print("creating a string too long") do @@ -47,9 +49,9 @@ local function loadrep (x, what) end -function controlstruct () +local function controlstruct () print("control structure too long") - local lim = ((1 << 24) - 2) // 3 + local lim = ((1 << 24) - 2) // 4 local s = string.rep("a = a + 1\n", lim) s = "while true do " .. s .. "end" assert(load(s)) @@ -63,7 +65,7 @@ function controlstruct () end -function manylines () +local function manylines () print("loading chunk with too many lines") local st, msg = loadrep("\n", "lines") assert(not st and string.find(msg, "too many lines")) @@ -71,7 +73,7 @@ function manylines () end -function hugeid () +local function hugeid () print("loading chunk with huge identifier") local st, msg = loadrep("a", "chars") assert(not st and @@ -80,7 +82,7 @@ function hugeid () print('+') end -function toomanyinst () +local function toomanyinst () print("loading chunk with too many instructions") local st, msg = loadrep("a = 10; ", "instructions") print('+') @@ -107,7 +109,7 @@ local function loadrepfunc (prefix, f) end -function toomanyconst () +local function toomanyconst () print("loading function with too many constants") loadrepfunc("function foo () return {0,", function (n) @@ -126,7 +128,7 @@ function toomanyconst () end -function toomanystr () +local function toomanystr () local a = {} local st, msg = pcall(function () for i = 1, math.huge do @@ -144,7 +146,7 @@ function toomanystr () end -function toomanyidx () +local function toomanyidx () local a = {} local st, msg = pcall(function () for i = 1, math.huge do From f33cc4ddec886ea499d7d41dd60cac5ddc5687db Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 26 Nov 2025 11:18:29 -0300 Subject: [PATCH 138/165] New conceptual model for vararg Conceptually, all functions get their vararg arguments in a vararg table. The storing of vararg arguments in the stack is always treated as an optimization. --- lcode.c | 5 ++++ lobject.h | 5 ++-- lopcodes.h | 2 +- lparser.c | 24 +++++++--------- ltm.c | 64 ++++++++++++++++++++++++++++++++--------- ltm.h | 4 +-- lvm.c | 5 ++-- manual/manual.of | 68 +++++++++++++++++++++++++------------------- testes/coroutine.lua | 4 ++- testes/db.lua | 9 +++--- testes/vararg.lua | 33 +++++++++++++++++++++ 11 files changed, 154 insertions(+), 69 deletions(-) diff --git a/lcode.c b/lcode.c index 95ef900cfd..afed05d15d 100644 --- a/lcode.c +++ b/lcode.c @@ -1951,6 +1951,11 @@ void luaK_finish (FuncState *fs) { SET_OPCODE(*pc, OP_GETTABLE); /* must get vararg there */ break; } + case OP_VARARG: { + if (p->flag & PF_VATAB) /* function has a vararg table? */ + SETARG_k(*pc, 1); /* must get vararg there */ + break; + } case OP_JMP: { /* to optimize jumps to jumps */ int target = finaltarget(p->code, i); fixjump(fs, i, target); /* jump directly to final target */ diff --git a/lobject.h b/lobject.h index 841ab5b9c3..070f12a42f 100644 --- a/lobject.h +++ b/lobject.h @@ -584,9 +584,8 @@ typedef struct AbsLineInfo { ** Flags in Prototypes */ #define PF_ISVARARG 1 /* function is vararg */ -#define PF_VAVAR 2 /* function has vararg parameter */ -#define PF_VATAB 4 /* function has vararg table */ -#define PF_FIXED 8 /* prototype has parts in fixed memory */ +#define PF_VATAB 2 /* function has vararg table */ +#define PF_FIXED 4 /* prototype has parts in fixed memory */ /* diff --git a/lopcodes.h b/lopcodes.h index f5c95151ba..fac87da2ce 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -336,7 +336,7 @@ OP_SETLIST,/* A vB vC k R[A][vC+i] := R[A+i], 1 <= i <= vB */ OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */ -OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */ +OP_VARARG,/* A C R[A], ..., R[A+C-2] = vararg, R[B] is vararg param. */ OP_GETVARG, /* A B C R[A] := R[B][R[C]], R[B] is vararg parameter */ diff --git a/lparser.c b/lparser.c index 77141e79f9..a07044b8d9 100644 --- a/lparser.c +++ b/lparser.c @@ -1056,9 +1056,8 @@ static void constructor (LexState *ls, expdesc *t) { /* }====================================================================== */ -static void setvararg (FuncState *fs, int kind) { - lua_assert(kind & PF_ISVARARG); - fs->f->flag |= cast_byte(kind); +static void setvararg (FuncState *fs) { + fs->f->flag |= PF_ISVARARG; luaK_codeABC(fs, OP_VARARGPREP, 0, 0, 0); } @@ -1078,12 +1077,12 @@ static void parlist (LexState *ls) { break; } case TK_DOTS: { - varargk |= PF_ISVARARG; + varargk = 1; luaX_next(ls); /* skip '...' */ - if (ls->t.token == TK_NAME) { + if (ls->t.token == TK_NAME) new_varkind(ls, str_checkname(ls), RDKVAVAR); - varargk |= PF_VAVAR; - } + else + new_localvarliteral(ls, "(vararg table)"); break; } default: luaX_syntaxerror(ls, " or '...' expected"); @@ -1092,10 +1091,9 @@ static void parlist (LexState *ls) { } adjustlocalvars(ls, nparams); f->numparams = cast_byte(fs->nactvar); - if (varargk != 0) { - setvararg(fs, varargk); /* declared vararg */ - if (varargk & PF_VAVAR) - adjustlocalvars(ls, 1); /* vararg parameter */ + if (varargk) { + setvararg(fs); /* declared vararg */ + adjustlocalvars(ls, 1); /* vararg parameter */ } /* reserve registers for parameters (plus vararg parameter, if present) */ luaK_reserveregs(fs, fs->nactvar); @@ -1287,7 +1285,7 @@ static void simpleexp (LexState *ls, expdesc *v) { FuncState *fs = ls->fs; check_condition(ls, fs->f->flag & PF_ISVARARG, "cannot use '...' outside a vararg function"); - init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 0, 1)); + init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, fs->f->numparams, 1)); break; } case '{' /*}*/: { /* constructor */ @@ -2153,7 +2151,7 @@ static void mainfunc (LexState *ls, FuncState *fs) { BlockCnt bl; Upvaldesc *env; open_func(ls, fs, &bl); - setvararg(fs, PF_ISVARARG); /* main function is always vararg */ + setvararg(fs); /* main function is always vararg */ env = allocupvalue(fs); /* ...set environment upvalue */ env->instack = 1; env->idx = 0; diff --git a/ltm.c b/ltm.c index 8d64235e81..39ac59d423 100644 --- a/ltm.c +++ b/ltm.c @@ -242,6 +242,7 @@ static void createvarargtab (lua_State *L, StkId f, int n) { luaH_set(L, t, &key, &value); /* t.n = n */ for (i = 0; i < n; i++) luaH_setint(L, t, i + 1, s2v(f + i)); + luaC_checkGC(L); } @@ -265,11 +266,11 @@ void luaT_adjustvarargs (lua_State *L, CallInfo *ci, const Proto *p) { setobjs2s(L, L->top.p++, ci->func.p + i); setnilvalue(s2v(ci->func.p + i)); /* erase original parameter (for GC) */ } - if (p->flag & PF_VAVAR) { /* is there a vararg parameter? */ - if (p->flag & PF_VATAB) /* does it need a vararg table? */ - createvarargtab(L, ci->func.p + nfixparams + 1, nextra); - else /* no table; set parameter to nil */ - setnilvalue(s2v(L->top.p)); + if (p->flag & PF_VATAB) /* does it need a vararg table? */ + createvarargtab(L, ci->func.p + nfixparams + 1, nextra); + else { /* no table; set parameter to nil */ + setnilvalue(s2v(L->top.p)); + L->top.p++; } ci->func.p += totalargs + 1; ci->top.p += totalargs + 1; @@ -299,16 +300,53 @@ void luaT_getvararg (CallInfo *ci, StkId ra, TValue *rc) { } -void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) { - int i; - int nextra = ci->u.l.nextraargs; +/* +** Get the number of extra arguments in a vararg function. If vararg +** table has been optimized away, that number is in the call info. +** Otherwise, get the field 'n' from the vararg table and check that it +** has a proper value (non-negative integer not larger than the stack +** limit). +*/ +static int getnumargs (lua_State *L, CallInfo *ci, Table *h) { + if (h == NULL) /* no vararg table? */ + return ci->u.l.nextraargs; + else { + TValue res; + if (luaH_getshortstr(h, luaS_new(L, "n"), &res) != LUA_VNUMINT || + l_castS2U(ivalue(&res)) > cast_uint(INT_MAX/2)) + luaG_runerror(L, "vararg table has no proper 'n'"); + return cast_int(ivalue(&res)); + } +} + + +/* +** Get 'wanted' vararg arguments and put them in 'where'. 'vatab' is +** the register of the vararg table or -1 if there is no vararg table. +*/ +void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted, + int vatab) { + Table *h = (vatab < 0) ? NULL : hvalue(s2v(ci->func.p + vatab + 1)); + int nargs = getnumargs(L, ci, h); /* number of available vararg args. */ + int i, touse; /* 'touse' is minimum between 'wanted' and 'nargs' */ if (wanted < 0) { - wanted = nextra; /* get all extra arguments available */ - checkstackp(L, nextra, where); /* ensure stack space */ - L->top.p = where + nextra; /* next instruction will need top */ + touse = wanted = nargs; /* get all extra arguments available */ + checkstackp(L, nargs, where); /* ensure stack space */ + L->top.p = where + nargs; /* next instruction will need top */ + } + else + touse = (nargs > wanted) ? wanted : nargs; + if (h == NULL) { /* no vararg table? */ + for (i = 0; i < touse; i++) /* get vararg values from the stack */ + setobjs2s(L, where + i, ci->func.p - nargs + i); + } + else { /* get vararg values from vararg table */ + for (i = 0; i < touse; i++) { + lu_byte tag = luaH_getint(h, i + 1, s2v(where + i)); + if (tagisempty(tag)) + setnilvalue(s2v(where + i)); + } } - for (i = 0; i < wanted && i < nextra; i++) - setobjs2s(L, where + i, ci->func.p - nextra + i); for (; i < wanted; i++) /* complete required results with nil */ setnilvalue(s2v(where + i)); } diff --git a/ltm.h b/ltm.h index 86f457ebce..07fc8c1c98 100644 --- a/ltm.h +++ b/ltm.h @@ -98,8 +98,8 @@ LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2, LUAI_FUNC void luaT_adjustvarargs (lua_State *L, struct CallInfo *ci, const Proto *p); LUAI_FUNC void luaT_getvararg (CallInfo *ci, StkId ra, TValue *rc); -LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci, - StkId where, int wanted); +LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci, StkId where, + int wanted, int vatab); #endif diff --git a/lvm.c b/lvm.c index 2c868c2128..c70e2b8a8a 100644 --- a/lvm.c +++ b/lvm.c @@ -1935,8 +1935,9 @@ void luaV_execute (lua_State *L, CallInfo *ci) { } vmcase(OP_VARARG) { StkId ra = RA(i); - int n = GETARG_C(i) - 1; /* required results */ - Protect(luaT_getvarargs(L, ci, ra, n)); + int n = GETARG_C(i) - 1; /* required results (-1 means all) */ + int vatab = GETARG_k(i) ? GETARG_B(i) : -1; + Protect(luaT_getvarargs(L, ci, ra, n, vatab)); vmbreak; } vmcase(OP_GETVARG) { diff --git a/manual/manual.of b/manual/manual.of index 96203d7fff..9b8e144d76 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2221,7 +2221,7 @@ The form } can be used to emulate methods. A call @T{v:name(@rep{args})} -is syntactic sugar for @T{v.name(v,@rep{args})}, +is syntactic sugar for @T{v.name(v, @rep{args})}, except that @id{v} is evaluated only once. Arguments have the following syntax: @@ -2372,12 +2372,10 @@ which is indicated by three dots (@Char{...}) at the end of its parameter list. A variadic function does not adjust its argument list; instead, it collects all extra arguments and supplies them -to the function through a @def{vararg expression} and, -if present, a @def{vararg table}. - -A vararg expression is also written as three dots, -and its value is a list of all actual extra arguments, -similar to a function with multiple results @see{multires}. +to the function through a @def{vararg table}. +In that table, +the values at indices 1, 2, etc. are the extra arguments, +and the value at index @St{n} is the number of extra arguments. As an example, consider the following definitions: @verbatim{ @@ -2386,7 +2384,7 @@ function g(a, b, ...) end function r() return 1,2,3 end } Then, we have the following mapping from arguments to parameters and -to the vararg expression: +to the vararg table: @verbatim{ CALL PARAMETERS @@ -2396,33 +2394,39 @@ f(3, 4, 5) a=3, b=4 f(r(), 10) a=1, b=10 f(r()) a=1, b=2 -g(3) a=3, b=nil, ... -> (nothing) -g(3, 4) a=3, b=4, ... -> (nothing) -g(3, 4, 5, 8) a=3, b=4, ... -> 5 8 -g(5, r()) a=5, b=1, ... -> 2 3 +g(3) a=3, b=nil, va. table -> {n = 0} +g(3, 4) a=3, b=4, va. table -> {n = 0} +g(3, 4, 5, 8) a=3, b=4, va. table -> {5, 8, n = 2} +g(5, r()) a=5, b=1, va. table -> {2, 3, n = 2} } -The presence of a vararg table in a variadic function is indicated -by a name after the three dots. +A vararg table in a variadic function can have an optional name, +given after the three dots. When present, -a vararg table behaves like a read-only local variable -with the given name that is initialized with a table. -In that table, -the values at indices 1, 2, etc. are the extra arguments, -and the value at index @St{n} is the number of extra arguments. -In other words, the code behaves as if the function started with -the following statement, -assuming the standard behavior of @Lid{table.pack}: -@verbatim{ -local name = table.pack(...) -} +that name denotes a read-only local variable that +refers to the vararg table. +If the vararg table does not have a name, +it can only be accessed through a vararg expression. + +A vararg expression is also written as three dots, +and its value is a list of the values in the vararg table, +from 1 to the integer value at index @St{n}. +(Therefore, if the code does not modify the vararg table, +this list corresponds to the extra arguments in the function call.) +This list behaves like the results from a +function with multiple results @see{multires}. As an optimization, -if the vararg table is used only as the base table -in the syntactic constructions @T{t[exp]} or @T{t.id}) -and it is not an upvalue, +if the vararg table satisfies some conditions, the code does not create an actual table and instead translates -the indexing expressions into accesses to the internal vararg data. +the indexing expressions and the vararg expressions +into accesses to the internal vararg data. +The conditions are as follows: +If the vararg table has a name, +that name is not an upvalue in a nested function +and it is used only as the base table +in the syntactic constructions @T{t[exp]} or @T{t.id}). +Note that an anonymous vararg table always satisfy these conditions. } @@ -3103,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)}. } @@ -9197,6 +9201,10 @@ Compile-time constants may not appear in this listing, if they were optimized away by the compiler. Negative indices refer to vararg arguments; @num{-1} is the first vararg argument. +These negative indices are only available when the vararg table +has been optimized away; +otherwise, the vararg arguments are available in the vararg table. + The function returns @fail if there is no variable with the given index, and raises an error when called with a level out of range. diff --git a/testes/coroutine.lua b/testes/coroutine.lua index 4881d96478..ba394e0c46 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua @@ -702,7 +702,9 @@ else assert(t.currentline == t.linedefined + 2) assert(not debug.getinfo(c, 1)) -- no other level assert(coroutine.resume(c)) -- run next line - local n,v = debug.getlocal(c, 0, 2) -- check next local + local n,v = debug.getlocal(c, 0, 2) -- check vararg table + assert(n == "(vararg table)" and v == nil) + local n,v = debug.getlocal(c, 0, 3) -- check next local assert(n == "b" and v == 10) v = {coroutine.resume(c)} -- finish coroutine assert(v[1] == true and v[2] == 2 and v[3] == 3 and v[4] == undef) diff --git a/testes/db.lua b/testes/db.lua index 0f174f17f7..4220b68ba7 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -356,8 +356,8 @@ function f(a,b) global assert, g, string local _, y = debug.getlocal(1, 2) assert(x == a and y == b) - assert(debug.setlocal(2, 3, "pera") == "AA".."AA") - assert(debug.setlocal(2, 4, "manga") == "B") + assert(debug.setlocal(2, 4, "pera") == "AA".."AA") + assert(debug.setlocal(2, 5, "manga") == "B") x = debug.getinfo(2) assert(x.func == g and x.what == "Lua" and x.name == 'g' and x.nups == 2 and string.find(x.source, "^@.*db%.lua$")) @@ -392,7 +392,7 @@ function g (...) global * local B = 13 global assert - local x,y = debug.getlocal(1,5) + local x,y = debug.getlocal(1,6) assert(x == 'B' and y == 13) end end @@ -458,7 +458,8 @@ local function collectlocals (level) local tab = {} for i = 1, math.huge do local n, v = debug.getlocal(level + 1, i) - if not (n and string.find(n, "^[a-zA-Z0-9_]+$")) then + if not (n and string.find(n, "^[a-zA-Z0-9_]+$") or + n == "(vararg table)") then break -- consider only real variables end tab[n] = v diff --git a/testes/vararg.lua b/testes/vararg.lua index a01598ff3b..043fa7d47a 100644 --- a/testes/vararg.lua +++ b/testes/vararg.lua @@ -101,6 +101,38 @@ a,b,c,d,e = f(4) assert(a==nil and b==nil and c==nil and d==nil and e==nil) +do -- vararg expressions using unpack + local function aux (a, v, ...t) + for k, val in pairs(v) do t[k] = val end + return ... + end + + local t = table.pack(aux(10, {11, [5] = 24}, 1, 2, 3, nil, 4)) + assert(t.n == 5 and t[1] == 11 and t[2] == 2 and t[3] == 3 + and t[4] == nil and t[5] == 24) + + local t = table.pack(aux(nil, {1, [20] = "a", [30] = "b", n = 30})) + assert(t.n == 30 and t[1] == 1 and t[20] == "a" and t[30] == "b") + -- table has only those four elements + assert(next(t, next(t, next(t, next(t, next(t, nil))))) == nil) + + local a, b, c, d = aux(nil, {}, 10, 20, 30) + assert(a == 10 and b == 20 and c == 30 and d == nil) + + local function aux (a, b, n, ...t) t.n = n; return b, ... end + local t = table.pack(aux(10, 1, 10000)) + assert(t.n == 10001 and t[1] == 1 and #t == 1) + + local function checkerr (emsg, f, ...) + local st, msg = pcall(f, ...) + assert(not st and string.find(msg, emsg)) + end + checkerr("no proper 'n'", aux, 1, 1, -1) + checkerr("no proper 'n'", aux, 1, 1, math.maxinteger) + checkerr("no proper 'n'", aux, 1, 1, math.mininteger) + checkerr("no proper 'n'", aux, 1, 1, 1.0) +end + -- varargs for main chunks local f = assert(load[[ return {...} ]]) local x = f(2,3) @@ -205,6 +237,7 @@ do -- access to vararg parameter assert(t[k] == v[k]) end assert(t.n == v.n) + return ... end local t = table.pack(10, 20, 30) From a07f6a824197d7dc01c321599d3bc71936a2590e Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 28 Nov 2025 15:12:51 -0300 Subject: [PATCH 139/165] Functions with vararg tables don't need hidden args. Vararg functions with vararg tables don't use the arguments hidden in the stack; therfore, it doesn't need to build/keep them. --- lcode.c | 12 +++++++----- ldebug.c | 8 ++++---- ldo.c | 2 +- lobject.h | 10 +++++++++- lopcodes.h | 31 +++++++++++++++++-------------- lparser.c | 6 +++--- ltm.c | 39 +++++++++++++++++++++++++-------------- manual/manual.of | 2 +- testes/db.lua | 3 +++ 9 files changed, 70 insertions(+), 43 deletions(-) diff --git a/lcode.c b/lcode.c index afed05d15d..b0a8142121 100644 --- a/lcode.c +++ b/lcode.c @@ -806,7 +806,7 @@ void luaK_setoneret (FuncState *fs, expdesc *e) { ** Change a vararg parameter into a regular local variable */ void luaK_vapar2local (FuncState *fs, expdesc *var) { - fs->f->flag |= PF_VATAB; /* function will need a vararg table */ + needvatab(fs->f); /* function will need a vararg table */ /* now a vararg parameter is equivalent to a regular local variable */ var->k = VLOCAL; } @@ -1127,7 +1127,7 @@ void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { break; } case VVARGIND: { - fs->f->flag |= PF_VATAB; /* function will need a vararg table */ + needvatab(fs->f); /* function will need a vararg table */ /* now, assignment is to a regular table */ } /* FALLTHROUGH */ case VINDEXED: { @@ -1927,6 +1927,8 @@ static int finaltarget (Instruction *code, int i) { void luaK_finish (FuncState *fs) { int i; Proto *p = fs->f; + if (p->flag & PF_VATAB) /* will it use a vararg table? */ + p->flag &= cast_byte(~PF_VAHID); /* then it will not use hidden args. */ for (i = 0; i < fs->pc; i++) { Instruction *pc = &p->code[i]; /* avoid "not used" warnings when assert is off (for 'onelua.c') */ @@ -1934,7 +1936,7 @@ void luaK_finish (FuncState *fs) { lua_assert(i == 0 || luaP_isOT(*(pc - 1)) == luaP_isIT(*pc)); switch (GET_OPCODE(*pc)) { case OP_RETURN0: case OP_RETURN1: { - if (!(fs->needclose || (p->flag & PF_ISVARARG))) + if (!(fs->needclose || (p->flag & PF_VAHID))) break; /* no extra work */ /* else use OP_RETURN to do the extra work */ SET_OPCODE(*pc, OP_RETURN); @@ -1942,8 +1944,8 @@ void luaK_finish (FuncState *fs) { case OP_RETURN: case OP_TAILCALL: { if (fs->needclose) SETARG_k(*pc, 1); /* signal that it needs to close */ - if (p->flag & PF_ISVARARG) - SETARG_C(*pc, p->numparams + 1); /* signal that it is vararg */ + if (p->flag & PF_VAHID) /* does it use hidden arguments? */ + SETARG_C(*pc, p->numparams + 1); /* signal that */ break; } case OP_GETVARG: { diff --git a/ldebug.c b/ldebug.c index abead91ce6..8df5f5f28b 100644 --- a/ldebug.c +++ b/ldebug.c @@ -184,7 +184,7 @@ static const char *upvalname (const Proto *p, int uv) { static const char *findvararg (CallInfo *ci, int n, StkId *pos) { - if (clLvalue(s2v(ci->func.p))->p->flag & PF_ISVARARG) { + if (clLvalue(s2v(ci->func.p))->p->flag & PF_VAHID) { int nextra = ci->u.l.nextraargs; if (n >= -nextra) { /* 'n' is negative */ *pos = ci->func.p - nextra - (n + 1); @@ -304,7 +304,7 @@ static void collectvalidlines (lua_State *L, Closure *f) { int i; TValue v; setbtvalue(&v); /* boolean 'true' to be the value of all indices */ - if (!(p->flag & PF_ISVARARG)) /* regular function? */ + if (!(isvararg(p))) /* regular function? */ i = 0; /* consider all instructions */ else { /* vararg function */ lua_assert(GET_OPCODE(p->code[0]) == OP_VARARGPREP); @@ -348,7 +348,7 @@ static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, ar->nparams = 0; } else { - ar->isvararg = (f->l.p->flag & PF_ISVARARG) ? 1 : 0; + ar->isvararg = (isvararg(f->l.p)) ? 1 : 0; ar->nparams = f->l.p->numparams; } break; @@ -912,7 +912,7 @@ int luaG_tracecall (lua_State *L) { Proto *p = ci_func(ci)->p; ci->u.l.trap = 1; /* ensure hooks will be checked */ if (ci->u.l.savedpc == p->code) { /* first instruction (not resuming)? */ - if (p->flag & PF_ISVARARG) + if (isvararg(p)) return 0; /* hooks will start at VARARGPREP instruction */ else if (!(ci->callstatus & CIST_HOOKYIELD)) /* not yielded? */ luaD_hookcall(L, ci); /* check 'call' hook */ diff --git a/ldo.c b/ldo.c index 44937068f8..75ce14889a 100644 --- a/ldo.c +++ b/ldo.c @@ -487,7 +487,7 @@ static void rethook (lua_State *L, CallInfo *ci, int nres) { int ftransfer; if (isLua(ci)) { Proto *p = ci_func(ci)->p; - if (p->flag & PF_ISVARARG) + if (p->flag & PF_VAHID) delta = ci->u.l.nextraargs + p->numparams + 1; } ci->func.p += delta; /* if vararg, back to virtual 'func' */ diff --git a/lobject.h b/lobject.h index 070f12a42f..156c942f01 100644 --- a/lobject.h +++ b/lobject.h @@ -583,10 +583,18 @@ typedef struct AbsLineInfo { /* ** Flags in Prototypes */ -#define PF_ISVARARG 1 /* function is vararg */ +#define PF_VAHID 1 /* function has hidden vararg arguments */ #define PF_VATAB 2 /* function has vararg table */ #define PF_FIXED 4 /* prototype has parts in fixed memory */ +/* a vararg function either has hidden args. or a vararg table */ +#define isvararg(p) ((p)->flag & (PF_VAHID | PF_VATAB)) + +/* +** mark that a function needs a vararg table. (The flag PF_VAHID will +** be cleared later.) +*/ +#define needvatab(p) ((p)->flag |= PF_VATAB) /* ** Function Prototypes diff --git a/lopcodes.h b/lopcodes.h index fac87da2ce..b6bd182ea2 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -224,8 +224,8 @@ enum OpMode {iABC, ivABC, iABx, iAsBx, iAx, isJ}; /* -** Grep "ORDER OP" if you change these enums. Opcodes marked with a (*) -** has extra descriptions in the notes after the enumeration. +** Grep "ORDER OP" if you change this enum. +** See "Notes" below for more information about some instructions. */ typedef enum { @@ -238,7 +238,7 @@ OP_LOADF,/* A sBx R[A] := (lua_Number)sBx */ OP_LOADK,/* A Bx R[A] := K[Bx] */ OP_LOADKX,/* A R[A] := K[extra arg] */ OP_LOADFALSE,/* A R[A] := false */ -OP_LFALSESKIP,/*A R[A] := false; pc++ (*) */ +OP_LFALSESKIP,/*A R[A] := false; pc++ */ OP_LOADTRUE,/* A R[A] := true */ OP_LOADNIL,/* A B R[A], R[A+1], ..., R[A+B] := nil */ OP_GETUPVAL,/* A B R[A] := UpValue[B] */ @@ -289,7 +289,7 @@ OP_BXOR,/* A B C R[A] := R[B] ~ R[C] */ OP_SHL,/* A B C R[A] := R[B] << R[C] */ OP_SHR,/* A B C R[A] := R[B] >> R[C] */ -OP_MMBIN,/* A B C call C metamethod over R[A] and R[B] (*) */ +OP_MMBIN,/* A B C call C metamethod over R[A] and R[B] */ OP_MMBINI,/* A sB C k call C metamethod over R[A] and sB */ OP_MMBINK,/* A B C k call C metamethod over R[A] and K[B] */ @@ -315,12 +315,12 @@ OP_GTI,/* A sB k if ((R[A] > sB) ~= k) then pc++ */ OP_GEI,/* A sB k if ((R[A] >= sB) ~= k) then pc++ */ OP_TEST,/* A k if (not R[A] == k) then pc++ */ -OP_TESTSET,/* A B k if (not R[B] == k) then pc++ else R[A] := R[B] (*) */ +OP_TESTSET,/* A B k if (not R[B] == k) then pc++ else R[A] := R[B] */ OP_CALL,/* A B C R[A], ... ,R[A+C-2] := R[A](R[A+1], ... ,R[A+B-1]) */ OP_TAILCALL,/* A B C k return R[A](R[A+1], ... ,R[A+B-1]) */ -OP_RETURN,/* A B C k return R[A], ... ,R[A+B-2] (see note) */ +OP_RETURN,/* A B C k return R[A], ... ,R[A+B-2] */ OP_RETURN0,/* return */ OP_RETURN1,/* A return R[A] */ @@ -336,13 +336,13 @@ OP_SETLIST,/* A vB vC k R[A][vC+i] := R[A+i], 1 <= i <= vB */ OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */ -OP_VARARG,/* A C R[A], ..., R[A+C-2] = vararg, R[B] is vararg param. */ +OP_VARARG,/* A B C k R[A], ..., R[A+C-2] = varargs */ OP_GETVARG, /* A B C R[A] := R[B][R[C]], R[B] is vararg parameter */ OP_ERRNNIL,/* A Bx raise error if R[A] ~= nil (K[Bx - 1] is global name)*/ -OP_VARARGPREP,/* (adjust vararg parameters) */ +OP_VARARGPREP,/* (adjust varargs) */ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ } OpCode; @@ -371,7 +371,8 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ OP_RETURN*, OP_SETLIST) may use 'top'. (*) In OP_VARARG, if (C == 0) then use actual number of varargs and - set top (like in OP_CALL with C == 0). + set top (like in OP_CALL with C == 0). 'k' means function has a + vararg table, which is in R[B]. (*) In OP_RETURN, if (B == 0) then return up to 'top'. @@ -387,20 +388,22 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ is vC. Otherwise, the array size is EXTRAARG _ vC. (*) In OP_ERRNNIL, (Bx == 0) means index of global name doesn't - fit in Bx. (So, that name is not available for the instruction.) + fit in Bx. (So, that name is not available for the error message.) (*) For comparisons, k specifies what condition the test should accept (true or false). (*) In OP_MMBINI/OP_MMBINK, k means the arguments were flipped - (the constant is the first operand). + (the constant is the first operand). - (*) All 'skips' (pc++) assume that next instruction is a jump. + (*) All comparison and test instructions assume that the instruction + being skipped (pc++) is a jump. (*) In instructions OP_RETURN/OP_TAILCALL, 'k' specifies that the function builds upvalues, which may need to be closed. C > 0 means - the function is vararg, so that its 'func' must be corrected before - returning; in this case, (C - 1) is its number of fixed parameters. + the function has hidden vararg arguments, so that its 'func' must be + corrected before returning; in this case, (C - 1) is its number of + fixed parameters. (*) In comparisons with an immediate operand, C signals whether the original operand was a float. (It must be corrected in case of diff --git a/lparser.c b/lparser.c index a07044b8d9..e015dfc578 100644 --- a/lparser.c +++ b/lparser.c @@ -304,7 +304,7 @@ static void check_readonly (LexState *ls, expdesc *e) { break; } case VVARGIND: { - fs->f->flag |= PF_VATAB; /* function will need a vararg table */ + needvatab(fs->f); /* function will need a vararg table */ e->k = VINDEXED; } /* FALLTHROUGH */ case VINDEXUP: case VINDEXSTR: case VINDEXED: { /* global variable */ @@ -1057,7 +1057,7 @@ static void constructor (LexState *ls, expdesc *t) { static void setvararg (FuncState *fs) { - fs->f->flag |= PF_ISVARARG; + fs->f->flag |= PF_VAHID; /* by default, use hidden vararg arguments */ luaK_codeABC(fs, OP_VARARGPREP, 0, 0, 0); } @@ -1283,7 +1283,7 @@ static void simpleexp (LexState *ls, expdesc *v) { } case TK_DOTS: { /* vararg */ FuncState *fs = ls->fs; - check_condition(ls, fs->f->flag & PF_ISVARARG, + check_condition(ls, isvararg(fs->f), "cannot use '...' outside a vararg function"); init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, fs->f->numparams, 1)); break; diff --git a/ltm.c b/ltm.c index 39ac59d423..f2a373f86c 100644 --- a/ltm.c +++ b/ltm.c @@ -250,31 +250,42 @@ static void createvarargtab (lua_State *L, StkId f, int n) { ** initial stack: func arg1 ... argn extra1 ... ** ^ ci->func ^ L->top ** final stack: func nil ... nil extra1 ... func arg1 ... argn -** ^ ci->func ^ L->top +** ^ ci->func */ -void luaT_adjustvarargs (lua_State *L, CallInfo *ci, const Proto *p) { +static void buildhiddenargs (lua_State *L, CallInfo *ci, const Proto *p, + int totalargs, int nfixparams, int nextra) { int i; - int totalargs = cast_int(L->top.p - ci->func.p) - 1; - int nfixparams = p->numparams; - int nextra = totalargs - nfixparams; /* number of extra arguments */ ci->u.l.nextraargs = nextra; luaD_checkstack(L, p->maxstacksize + 1); - /* copy function to the top of the stack */ + /* copy function to the top of the stack, after extra arguments */ setobjs2s(L, L->top.p++, ci->func.p); - /* move fixed parameters to the top of the stack */ + /* move fixed parameters to after the copied function */ for (i = 1; i <= nfixparams; i++) { setobjs2s(L, L->top.p++, ci->func.p + i); setnilvalue(s2v(ci->func.p + i)); /* erase original parameter (for GC) */ } - if (p->flag & PF_VATAB) /* does it need a vararg table? */ + ci->func.p += totalargs + 1; /* 'func' now lives after hidden arguments */ + ci->top.p += totalargs + 1; +} + + +void luaT_adjustvarargs (lua_State *L, CallInfo *ci, const Proto *p) { + int totalargs = cast_int(L->top.p - ci->func.p) - 1; + int nfixparams = p->numparams; + int nextra = totalargs - nfixparams; /* number of extra arguments */ + if (p->flag & PF_VATAB) { /* does it need a vararg table? */ + lua_assert(!(p->flag & PF_VAHID)); createvarargtab(L, ci->func.p + nfixparams + 1, nextra); - else { /* no table; set parameter to nil */ - setnilvalue(s2v(L->top.p)); - L->top.p++; + /* move table to proper place (last parameter) */ + setobjs2s(L, ci->func.p + nfixparams + 1, L->top.p - 1); + } + else { /* no table */ + lua_assert(p->flag & PF_VAHID); + buildhiddenargs(L, ci, p, totalargs, nfixparams, nextra); + /* set vararg parameter to nil */ + setnilvalue(s2v(ci->func.p + nfixparams + 1)); + lua_assert(L->top.p <= ci->top.p && ci->top.p <= L->stack_last.p); } - ci->func.p += totalargs + 1; - ci->top.p += totalargs + 1; - lua_assert(L->top.p <= ci->top.p && ci->top.p <= L->stack_last.p); } diff --git a/manual/manual.of b/manual/manual.of index 9b8e144d76..54f67b3e2f 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2425,7 +2425,7 @@ The conditions are as follows: If the vararg table has a name, that name is not an upvalue in a nested function and it is used only as the base table -in the syntactic constructions @T{t[exp]} or @T{t.id}). +in the syntactic constructions @T{t[exp]} or @T{t.id}. Note that an anonymous vararg table always satisfy these conditions. } diff --git a/testes/db.lua b/testes/db.lua index 4220b68ba7..e15a5be6bd 100644 --- a/testes/db.lua +++ b/testes/db.lua @@ -726,6 +726,9 @@ assert(t.isvararg == false and t.nparams == 3 and t.nups == 0) t = debug.getinfo(function (a,b,...) return t[a] end, "u") assert(t.isvararg == true and t.nparams == 2 and t.nups == 1) +t = debug.getinfo(function (a,b,...t) t.n = 2; return t[a] end, "u") +assert(t.isvararg == true and t.nparams == 2 and t.nups == 0) + t = debug.getinfo(1) -- main assert(t.isvararg == true and t.nparams == 0 and t.nups == 1 and debug.getupvalue(t.func, 1) == "_ENV") From 985ef32248f17ae4ca2d4e83e5e39e15393bb2f6 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Mon, 1 Dec 2025 10:25:44 -0300 Subject: [PATCH 140/165] In luaB_close, running coroutines do not go to default This should had been corrected in commit fd897027f1. --- lcorolib.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lcorolib.c b/lcorolib.c index 23dd844156..eb30bf4da5 100644 --- a/lcorolib.c +++ b/lcorolib.c @@ -189,15 +189,17 @@ static int luaB_close (lua_State *L) { return 2; } } - case COS_RUN: /* running coroutine? */ + case COS_NORM: + return luaL_error(L, "cannot close a %s coroutine", statname[status]); + case COS_RUN: lua_geti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD); /* get main */ if (lua_tothread(L, -1) == co) return luaL_error(L, "cannot close main thread"); lua_closethread(co, L); /* close itself */ - lua_assert(0); /* previous call does not return */ + /* previous call does not return *//* FALLTHROUGH */ + default: + lua_assert(0); return 0; - default: /* normal or running coroutine */ - return luaL_error(L, "cannot close a %s coroutine", statname[status]); } } From 8164d09338d06ecd89bd654e4ff5379f040eba71 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Mon, 8 Dec 2025 11:08:12 -0300 Subject: [PATCH 141/165] Wrong assert in 'luaK_indexed' --- lcode.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lcode.c b/lcode.c index b0a8142121..4caa8046f6 100644 --- a/lcode.c +++ b/lcode.c @@ -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 */ From 104b0fc7008b1f6b7d818985fbbad05cd37ee654 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Mon, 8 Dec 2025 13:09:47 -0300 Subject: [PATCH 142/165] Details - Avoid fixing name "_ENV" in the code - Small improvements in the manual --- lparser.c | 4 ++-- manual/manual.of | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lparser.c b/lparser.c index e015dfc578..b3855d4cb6 100644 --- a/lparser.c +++ b/lparser.c @@ -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] */ diff --git a/manual/manual.of b/manual/manual.of index 54f67b3e2f..317adcaa4d 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -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, @@ -2458,7 +2458,7 @@ for instance @T{{e1, e2, e3}} @see{tableconstructor}.} 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.} @@ -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 @@ -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 @@ -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} From 82d721a8554df9b14ff520b4dd55ce5303ab560e Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 10 Dec 2025 10:35:05 -0300 Subject: [PATCH 143/165] 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.) --- manual/manual.of | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/manual/manual.of b/manual/manual.of index 317adcaa4d..5fa4e097e1 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -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)}. } @@ -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)}. } @@ -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, @@ -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}. From 3d03ae5bd6314f27c8635e06ec363150c2c19062 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 13 Dec 2025 11:00:30 -0300 Subject: [PATCH 144/165] 'luaL_newstate' starts state with warnings on It is easier to forget to turn them on then to turn them off. --- lauxlib.c | 2 +- lua.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lauxlib.c b/lauxlib.c index 1bb41bb1da..7cf90cb78a 100644 --- a/lauxlib.c +++ b/lauxlib.c @@ -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; } diff --git a/lua.c b/lua.c index b2967a447d..5054583de9 100644 --- a/lua.c +++ b/lua.c @@ -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) From a5522f06d2679b8f18534fd6a9968f7eb539dc31 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 13 Dec 2025 16:16:59 -0300 Subject: [PATCH 145/165] 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. --- ldo.c | 11 +++++++++++ ldo.h | 1 + lgc.c | 7 ++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ldo.c b/ldo.c index 75ce14889a..6d0184ecd5 100644 --- a/ldo.c +++ b/ldo.c @@ -220,6 +220,17 @@ l_noret luaD_errerr (lua_State *L) { } +/* +** Check whether stack has enough space to run a simple function (such +** as a finalizer): At least BASIC_STACK_SIZE in the Lua stack and +** 2 slots in the C stack. +*/ +int luaD_checkminstack (lua_State *L) { + return ((stacksize(L) < MAXSTACK - BASIC_STACK_SIZE) && + (getCcalls(L) < LUAI_MAXCCALLS - 2)); +} + + /* ** In ISO C, any pointer use after the pointer has been deallocated is ** undefined behavior. So, before a stack reallocation, all pointers diff --git a/ldo.h b/ldo.h index 2d4ca8be46..b64729541c 100644 --- a/ldo.h +++ b/ldo.h @@ -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); diff --git a/lgc.c b/lgc.c index 60f042c7a8..c64d74b8e8 100644 --- a/lgc.c +++ b/lgc.c @@ -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->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 no enough stack + to run finalizers */ g->gcstate = GCSpause; /* finish collection */ stepresult = step2pause; } From 578ae5745cecee56d48795cd4ae1eaf13618715c Mon Sep 17 00:00:00 2001 From: Roberto I Date: Tue, 23 Dec 2025 14:44:06 -0300 Subject: [PATCH 146/165] Details typo in comment + formatting + logical 'and' was written as a bitwise operation (makes code more fragile) --- lapi.c | 2 +- lgc.c | 2 +- lstrlib.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lapi.c b/lapi.c index 27fa524797..42c8fcdd70 100644 --- a/lapi.c +++ b/lapi.c @@ -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); diff --git a/lgc.c b/lgc.c index c64d74b8e8..f1d9a7ce8a 100644 --- a/lgc.c +++ b/lgc.c @@ -1672,7 +1672,7 @@ static l_mem singlestep (lua_State *L, int fast) { GCTM(L); /* call one finalizer */ stepresult = CWUFIN; } - else { /* no more finalizers or emergency mode or no enough stack + else { /* no more finalizers or emergency mode or not enough stack to run finalizers */ g->gcstate = GCSpause; /* finish collection */ stepresult = step2pause; diff --git a/lstrlib.c b/lstrlib.c index 23df839ea0..e26eb1a8de 100644 --- a/lstrlib.c +++ b/lstrlib.c @@ -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 */ From 632a71b24d8661228a726deb5e1698e9638f96d8 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 27 Dec 2025 16:22:13 -0300 Subject: [PATCH 147/165] 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'. --- lapi.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lapi.c b/lapi.c index 42c8fcdd70..9b6ca1ecfb 100644 --- a/lapi.c +++ b/lapi.c @@ -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 */ From c4e2c91973fed04e7da940c00c92f10f9eb0f9ec Mon Sep 17 00:00:00 2001 From: Roberto I Date: Tue, 30 Dec 2025 10:50:49 -0300 Subject: [PATCH 148/165] Details Some comments still talked about bit 'isrealasize', which has been removed. --- ltable.c | 7 +++---- ltm.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ltable.c b/ltable.c index b7f88f6ffe..2f61be84b2 100644 --- a/ltable.c +++ b/ltable.c @@ -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; diff --git a/ltm.h b/ltm.h index 07fc8c1c98..afc7ad00e2 100644 --- a/ltm.h +++ b/ltm.h @@ -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))) From 962f444a755882ecfc24ca7e96ffe193d64ed12d Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sun, 4 Jan 2026 16:27:54 -0300 Subject: [PATCH 149/165] Details In an assignment like 'a = &b', is looks suspicious if 'a' has a scope larger than 'b'. --- ltable.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ltable.c b/ltable.c index 2f61be84b2..2f2b5c1f5c 100644 --- a/ltable.c +++ b/ltable.c @@ -1155,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"); @@ -1175,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); From 45c7ae5b1b05069543fe1710454c651350bc1c42 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sun, 4 Jan 2026 16:31:17 -0300 Subject: [PATCH 150/165] 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. --- lstrlib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lstrlib.c b/lstrlib.c index e26eb1a8de..06ea10d9f6 100644 --- a/lstrlib.c +++ b/lstrlib.c @@ -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; } From 5cfc725a8b61a6f96c7324f60ac26739315095ba Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sun, 4 Jan 2026 16:39:22 -0300 Subject: [PATCH 151/165] Special case for 'string.rep' over an empty string --- lauxlib.h | 6 +++--- lstrlib.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lauxlib.h b/lauxlib.h index 7f1d3ca195..2d015362ff 100644 --- a/lauxlib.h +++ b/lauxlib.h @@ -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); diff --git a/lstrlib.c b/lstrlib.c index 06ea10d9f6..874cec8086 100644 --- a/lstrlib.c +++ b/lstrlib.c @@ -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"); From 2a7cf4f319fc276f4554a8f6364e6b1ba4eb2ded Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sun, 11 Jan 2026 15:36:03 -0300 Subject: [PATCH 152/165] 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. --- ldo.c | 20 ++++++++++++++------ lgc.c | 2 +- lstate.c | 17 +++++++++++------ lstate.h | 2 +- ltests.c | 23 +++++++++++++++++++++++ testes/gc.lua | 42 ++++++++++++++++++++++++++++++++++++++++++ testes/memerr.lua | 19 +++++++++++++++++++ testes/tracegc.lua | 9 +++++++-- 8 files changed, 118 insertions(+), 16 deletions(-) diff --git a/ldo.c b/ldo.c index 6d0184ecd5..12e0364b98 100644 --- a/ldo.c +++ b/ldo.c @@ -221,13 +221,21 @@ l_noret luaD_errerr (lua_State *L) { /* -** Check whether stack has enough space to run a simple function (such -** as a finalizer): At least BASIC_STACK_SIZE in the Lua stack and -** 2 slots in the C stack. +** 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) { - return ((stacksize(L) < MAXSTACK - BASIC_STACK_SIZE) && - (getCcalls(L) < LUAI_MAXCCALLS - 2)); + 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); } @@ -616,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)) /* diff --git a/lgc.c b/lgc.c index f1d9a7ce8a..0f89451c64 100644 --- a/lgc.c +++ b/lgc.c @@ -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 && luaD_checkminstack(L)) + if (g->tobefnz != NULL && !g->gcemergency && luaD_checkminstack(L)) callallpendingfinalizers(L); } diff --git a/lstate.c b/lstate.c index 70a11aaec6..7d34199198 100644 --- a/lstate.c +++ b/lstate.c @@ -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; diff --git a/lstate.h b/lstate.h index 20dc4d24f0..013872835d 100644 --- a/lstate.h +++ b/lstate.h @@ -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); diff --git a/ltests.c b/ltests.c index c4905f9487..ce2b20ca5e 100644 --- a/ltests.c +++ b/ltests.c @@ -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}, diff --git a/testes/gc.lua b/testes/gc.lua index 62713dac64..e50d9029b0 100644 --- a/testes/gc.lua +++ b/testes/gc.lua @@ -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') diff --git a/testes/memerr.lua b/testes/memerr.lua index 9c940ca79a..a55514a9e8 100644 --- a/testes/memerr.lua +++ b/testes/memerr.lua @@ -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) + -- }================================================================== diff --git a/testes/tracegc.lua b/testes/tracegc.lua index a8c929dffd..c1154f90f2 100644 --- a/testes/tracegc.lua +++ b/testes/tracegc.lua @@ -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 From f5d1e8639bf5df24c761602354218df21f796a30 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 16 Jan 2026 16:38:44 -0300 Subject: [PATCH 153/165] New compile option LUA_COMPAT_LOOPVAR When on, this option makes for-loop control variables not read only. --- lparser.c | 13 +++++++++++-- luaconf.h | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lparser.c b/lparser.c index b3855d4cb6..b27463af3e 100644 --- a/lparser.c +++ b/lparser.c @@ -1682,13 +1682,22 @@ static void forbody (LexState *ls, int base, int line, int nvars, int isgen) { } +/* +** Control whether for-loop control variables are read-only +*/ +#if defined(LUA_COMPAT_LOOPVAR) +#define LOOPVARKIND VDKREG +#else /* by default, these variables are read only */ +#define LOOPVARKIND RDKCONST +#endif + static void fornum (LexState *ls, TString *varname, int line) { /* fornum -> NAME = exp,exp[,exp] forbody */ FuncState *fs = ls->fs; int base = fs->freereg; new_localvarliteral(ls, "(for state)"); new_localvarliteral(ls, "(for state)"); - new_varkind(ls, varname, RDKCONST); /* control variable */ + new_varkind(ls, varname, LOOPVARKIND); /* control variable */ checknext(ls, '='); exp1(ls); /* initial value */ checknext(ls, ','); @@ -1715,7 +1724,7 @@ static void forlist (LexState *ls, TString *indexname) { new_localvarliteral(ls, "(for state)"); /* iterator function */ new_localvarliteral(ls, "(for state)"); /* state */ new_localvarliteral(ls, "(for state)"); /* closing var. (after swap) */ - new_varkind(ls, indexname, RDKCONST); /* control variable */ + new_varkind(ls, indexname, LOOPVARKIND); /* control variable */ /* other declared variables */ while (testnext(ls, ',')) { new_localvar(ls, str_checkname(ls)); diff --git a/luaconf.h b/luaconf.h index 96a77802b9..f076c98472 100644 --- a/luaconf.h +++ b/luaconf.h @@ -342,6 +342,13 @@ #define LUA_COMPAT_GLOBAL +/* +@@ LUA_COMPAT_LOOPVAR makes for-loop control variables not read-only, +** as they were in previous versions. +*/ +/* #define LUA_COMPAT_LOOPVAR */ + + /* @@ LUA_COMPAT_MATHLIB controls the presence of several deprecated ** functions in the mathematical library. From e992c6a95939c8e1fe357bfce481e0d0c762c3c6 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 20 Jan 2026 13:06:16 -0300 Subject: [PATCH 154/165] Some compilation options configurable from makefile Compilation options LUA_COMPAT_GLOBAL, LUA_COMPAT_LOOPVAR, and LUA_READLINELIB do not affect the API, so they can be changed through the make file. --- llex.c | 2 +- lparser.c | 4 ++-- ltests.h | 1 + luaconf.h | 12 ++++++++++-- manual/manual.of | 6 ++++++ 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/llex.c b/llex.c index f8bb3ea4b4..7cd9fcaf52 100644 --- a/llex.c +++ b/llex.c @@ -188,7 +188,7 @@ void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source, so they cannot be collected */ ls->envn = luaS_newliteral(L, LUA_ENV); /* get env string */ ls->brkn = luaS_newliteral(L, "break"); /* get "break" string */ -#if defined(LUA_COMPAT_GLOBAL) +#if LUA_COMPAT_GLOBAL /* compatibility mode: "global" is not a reserved word */ ls->glbn = luaS_newliteral(L, "global"); /* get "global" string */ ls->glbn->extra = 0; /* mark it as not reserved */ diff --git a/lparser.c b/lparser.c index b27463af3e..6b87773ea3 100644 --- a/lparser.c +++ b/lparser.c @@ -1685,7 +1685,7 @@ static void forbody (LexState *ls, int base, int line, int nvars, int isgen) { /* ** Control whether for-loop control variables are read-only */ -#if defined(LUA_COMPAT_LOOPVAR) +#if LUA_COMPAT_LOOPVAR #define LOOPVARKIND VDKREG #else /* by default, these variables are read only */ #define LOOPVARKIND RDKCONST @@ -2120,7 +2120,7 @@ static void statement (LexState *ls) { gotostat(ls, line); break; } -#if defined(LUA_COMPAT_GLOBAL) +#if LUA_COMPAT_GLOBAL case TK_NAME: { /* compatibility code to parse global keyword when "global" is not reserved */ diff --git a/ltests.h b/ltests.h index 93096da810..f5f14cd61c 100644 --- a/ltests.h +++ b/ltests.h @@ -14,6 +14,7 @@ /* test Lua with compatibility code */ #define LUA_COMPAT_MATHLIB #undef LUA_COMPAT_GLOBAL +#define LUA_COMPAT_GLOBAL 0 #define LUA_DEBUG diff --git a/luaconf.h b/luaconf.h index f076c98472..1b72e3384a 100644 --- a/luaconf.h +++ b/luaconf.h @@ -70,15 +70,19 @@ #if defined(LUA_USE_LINUX) #define LUA_USE_POSIX #define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#if !defined(LUA_READLINELIB) #define LUA_READLINELIB "libreadline.so" #endif +#endif #if defined(LUA_USE_MACOSX) #define LUA_USE_POSIX #define LUA_USE_DLOPEN /* macOS does not need -ldl */ +#if !defined(LUA_READLINELIB) #define LUA_READLINELIB "libedit.dylib" #endif +#endif #if defined(LUA_USE_IOS) @@ -339,14 +343,18 @@ /* @@ LUA_COMPAT_GLOBAL avoids 'global' being a reserved word */ -#define LUA_COMPAT_GLOBAL +#if !defined(LUA_COMPAT_GLOBAL) +#define LUA_COMPAT_GLOBAL 1 +#endif /* @@ LUA_COMPAT_LOOPVAR makes for-loop control variables not read-only, ** as they were in previous versions. */ -/* #define LUA_COMPAT_LOOPVAR */ +#if !defined(LUA_COMPAT_LOOPVAR) +#define LUA_COMPAT_LOOPVAR 0 +#endif /* diff --git a/manual/manual.of b/manual/manual.of index 5fa4e097e1..09075346fd 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -9594,12 +9594,18 @@ change between versions. @item{ The word @Rw{global} is a reserved word. Do not use it as a regular name. + +The compilation option @id{LUA_COMPAT_GLOBAL} (see @id{luaconf.h}) +makes @id{global} a regular word. } @item{ The control variable in @Rw{for} loops is read only. If you need to change it, declare a local variable with the same name in the loop body. + +The compilation option @id{LUA_COMPAT_LOOPVAR} (see @id{luaconf.h}) +makes these variables regular (writable). } @item{ From 3360710bd3ea8da06fa5062f9d10c2719083097c Mon Sep 17 00:00:00 2001 From: Roberto I Date: Thu, 22 Jan 2026 13:47:10 -0300 Subject: [PATCH 155/165] Another way to handle option -E A pointer to function 'l_getenv' can point to the regular 'getenv' or to a dumb function (that always returns NULL) to ignore environment variables. --- lua.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lua.c b/lua.c index 5054583de9..37fd1cb87e 100644 --- a/lua.c +++ b/lua.c @@ -374,12 +374,21 @@ static int runargs (lua_State *L, char **argv, int n) { } +static char *(*l_getenv)(const char *name); + +/* Function to ignore environment variables, used by option -E */ +static char *no_getenv (const char *name) { + UNUSED(name); + return NULL; +} + + static int handle_luainit (lua_State *L) { const char *name = "=" LUA_INITVARVERSION; - const char *init = getenv(name + 1); + const char *init = l_getenv(name + 1); if (init == NULL) { name = "=" LUA_INIT_VAR; - init = getenv(name + 1); /* try alternative name */ + init = l_getenv(name + 1); /* try alternative name */ } if (init == NULL) return LUA_OK; else if (init[0] == '@') @@ -715,17 +724,18 @@ static int pmain (lua_State *L) { if (args & has_v) /* option '-v'? */ print_version(); if (args & has_E) { /* option '-E'? */ + l_getenv = &no_getenv; /* program will ignore environment variables */ lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */ lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); } + else + l_getenv = &getenv; luai_openlibs(L); /* open standard libraries */ createargtable(L, argv, argc, script); /* create table 'arg' */ lua_gc(L, LUA_GCRESTART); /* start GC... */ lua_gc(L, LUA_GCGEN); /* ...in generational mode */ - if (!(args & has_E)) { /* no option '-E'? */ - if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */ - return 0; /* error running LUA_INIT */ - } + if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */ + return 0; /* error running LUA_INIT */ 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) */ From cfcaa9493b783527e5b5dfb71afb51602b3bccac Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 23 Jan 2026 16:25:18 -0300 Subject: [PATCH 156/165] Explanation about char* parameters in the C API --- manual/manual.of | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/manual/manual.of b/manual/manual.of index 09075346fd..893592da60 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2692,7 +2692,19 @@ which behaves like a nil value. @sect3{constchar|@title{Pointers to Strings} -Several functions in the API return pointers (@T{const char*}) +Several functions in the API accept pointers (@T{const char*}) +to C strings. +Some of there parameters have an associated length (@T{size_t}). +Unless stated otherwise, +when there is an associated length, +the string can contain embedded zeros; +moreover, the pointer can be @id{NULL} if the length is zero. +When there is no associated length, +the pointer must point to a zero-terminated string. +In any case, the string contents should remain unchanged +until the function returns. + +Several functions in the API also return pointers (@T{const char*}) to Lua strings in the stack. (See @Lid{lua_pushfstring}, @Lid{lua_pushlstring}, @Lid{lua_pushstring}, and @Lid{lua_tolstring}. From efbc29754544dd820bfdc81edf17d7dcfad31d05 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Thu, 29 Jan 2026 14:24:25 -0300 Subject: [PATCH 157/165] New year and (eventual) new release --- lua.h | 6 +++--- manual/2html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lua.h b/lua.h index ab473dc3e4..6deaed49c2 100644 --- a/lua.h +++ b/lua.h @@ -13,13 +13,13 @@ #include -#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2025 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_VERSION_MAJOR_N 5 #define LUA_VERSION_MINOR_N 5 -#define LUA_VERSION_RELEASE_N 0 +#define LUA_VERSION_RELEASE_N 1 #define LUA_VERSION_NUM (LUA_VERSION_MAJOR_N * 100 + LUA_VERSION_MINOR_N) #define LUA_VERSION_RELEASE_NUM (LUA_VERSION_NUM * 100 + LUA_VERSION_RELEASE_N) @@ -521,7 +521,7 @@ struct lua_Debug { /****************************************************************************** -* Copyright (C) 1994-2025 Lua.org, PUC-Rio. +* Copyright (C) 1994-2026 Lua.org, PUC-Rio. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/manual/2html b/manual/2html index b7afd2a6e4..d3b88b349b 100755 --- a/manual/2html +++ b/manual/2html @@ -30,7 +30,7 @@ by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes

Copyright -© 2025 Lua.org, PUC-Rio. All rights reserved. +© 2026 Lua.org, PUC-Rio. All rights reserved.


From c6b484823806e08e1756b1a6066a3ace6f080fae Mon Sep 17 00:00:00 2001 From: Roberto I Date: Fri, 30 Jan 2026 16:47:33 -0300 Subject: [PATCH 158/165] Environment variable for readline library name The name of the readline library can be changed from its default value through environment variable LUA_READLINELIB. --- lua.c | 24 ++++++++++++++++++------ testes/main.lua | 23 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/lua.c b/lua.c index 37fd1cb87e..3f4fc9f780 100644 --- a/lua.c +++ b/lua.c @@ -30,6 +30,12 @@ #define LUA_INIT_VAR "LUA_INIT" #endif +/* Name of the environment variable with the name of the readline library */ +#if !defined(LUA_RLLIB_VAR) +#define LUA_RLLIB_VAR "LUA_READLINELIB" +#endif + + #define LUA_INITVARVERSION LUA_INIT_VAR LUA_VERSUFFIX @@ -507,18 +513,24 @@ static void lua_freeline (char *line) { #include static void lua_initreadline (lua_State *L) { - void *lib = dlopen(LUA_READLINELIB, RTLD_NOW | RTLD_LOCAL); - if (lib == NULL) - lua_warning(L, "library '" LUA_READLINELIB "' not found", 0); - else { + const char *rllib = l_getenv(LUA_RLLIB_VAR); /* name of readline library */ + void *lib; /* library handle */ + if (rllib == NULL) /* no environment variable? */ + rllib = LUA_READLINELIB; /* use default name */ + lib = dlopen(rllib, RTLD_NOW | RTLD_LOCAL); + if (lib != NULL) { const char **name = cast(const char**, dlsym(lib, "rl_readline_name")); if (name != NULL) *name = "lua"; l_readline = cast(l_readlineT, cast_func(dlsym(lib, "readline"))); l_addhist = cast(l_addhistT, cast_func(dlsym(lib, "add_history"))); - if (l_readline == NULL) - lua_warning(L, "unable to load 'readline'", 0); + if (l_readline != NULL) /* could load readline function? */ + return; /* everything ok */ + /* else emit a warning */ } + lua_warning(L, "unable to load readline library '", 1); + lua_warning(L, rllib, 1); + lua_warning(L, "'", 0); } #else /* }{ */ diff --git a/testes/main.lua b/testes/main.lua index dc48dc485f..98d3695189 100644 --- a/testes/main.lua +++ b/testes/main.lua @@ -78,6 +78,9 @@ end RUN('lua -v') +RUN('lua -v > %s', out) +local release = string.match(getoutput(), "Lua (%d+%.%d+%.%d+)") + print(string.format("(temporary program file used in these tests: %s)", prog)) -- running stdin as a file @@ -167,7 +170,9 @@ checkout("10\n11\n") -- test errors in LUA_INIT NoRun('LUA_INIT:1: msg', 'env LUA_INIT="error(\'msg\')" lua') --- test option '-E' + +print("testing option '-E'") + local defaultpath, defaultCpath do @@ -192,6 +197,22 @@ assert(not string.find(defaultpath, "xxx") and string.find(defaultCpath, "lua")) +-- (LUA_READLINELIB was introduced in 5.5.1) +if release >= "5.5.1" then + print"testing readline library name" + -- should generate a warning when trying to load inexistent library "xuxu" + local env = [[LUA_READLINELIB=xuxu LUA_INIT="warn('@allow')"]] + local code = 'echo " " | env %s lua %s -W -i >%s 2>&1' + RUN(code, env, "", out) -- run code with no extra options + assert(string.find(getoutput(), + "warning: unable to load readline library 'xuxu'")) + + RUN(code, env, "-E", out) -- run again with option -E + -- no warning when LUA_READLINELIB is to be ignored + assert(not string.find(getoutput(), "warning")) +end + + -- test replacement of ';;' to default path local function convert (p) prepfile("print(package.path)") From b60e2bcd7ca4c349bd6ee7a8e929f55e04f7ca87 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 9 Feb 2026 13:44:27 -0300 Subject: [PATCH 159/165] Avoid an assignment of values that overlap The original code was like this, where t->u.ind.t and t->u.info overlap: t->u.ind.t = cast_byte((t->k == VLOCAL) ? t->u.var.ridx: t->u.info); --- lcode.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lcode.c b/lcode.c index 4caa8046f6..33cbd6874f 100644 --- a/lcode.c +++ b/lcode.c @@ -827,7 +827,7 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) { } /* FALLTHROUGH */ case VLOCAL: { /* already in a register */ int temp = e->u.var.ridx; - e->u.info = temp; /* (can't do a direct assignment; values overlap) */ + e->u.info = temp; /* (avoid a direct assignment; values overlap) */ e->k = VNONRELOC; /* becomes a non-relocatable value */ break; } @@ -1365,7 +1365,7 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { luaK_exp2anyreg(fs, t); /* put it in a register */ if (t->k == VUPVAL) { lu_byte temp = cast_byte(t->u.info); /* upvalue index */ - t->u.ind.t = temp; /* (can't do a direct assignment; values overlap) */ + t->u.ind.t = temp; /* (avoid a direct assignment; values overlap) */ lua_assert(isKstr(fs, k)); fillidxk(t, k->u.info, VINDEXUP); /* literal short string */ } @@ -1373,12 +1373,13 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { 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) */ + 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 */ - t->u.ind.t = cast_byte((t->k == VLOCAL) ? t->u.var.ridx: t->u.info); + lu_byte temp = cast_byte((t->k == VLOCAL) ? t->u.var.ridx: t->u.info); + t->u.ind.t = temp; /* (avoid a direct assignment; values may overlap?) */ if (isKstr(fs, k)) fillidxk(t, k->u.info, VINDEXSTR); /* literal short string */ else if (isCint(k)) /* int. constant in proper range? */ From 7c40c5edb2364745bf0add6feb02c7c90dfbae3e Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 10 Feb 2026 16:41:02 -0300 Subject: [PATCH 160/165] Details Spaces + added initialization to the documentation of global declarations. --- luaconf.h | 18 +++++++++--------- manual/manual.of | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/luaconf.h b/luaconf.h index 1b72e3384a..7f2206d06d 100644 --- a/luaconf.h +++ b/luaconf.h @@ -228,17 +228,17 @@ #if !defined(LUA_PATH_DEFAULT) #define LUA_PATH_DEFAULT \ - LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ - LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ - LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ + LUA_LDIR "?.lua;" LUA_LDIR "?\\init.lua;" \ + LUA_CDIR "?.lua;" LUA_CDIR "?\\init.lua;" \ + LUA_SHRDIR "?.lua;" LUA_SHRDIR "?\\init.lua;" \ ".\\?.lua;" ".\\?\\init.lua" #endif #if !defined(LUA_CPATH_DEFAULT) #define LUA_CPATH_DEFAULT \ - LUA_CDIR"?.dll;" \ - LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ - LUA_CDIR"loadall.dll;" ".\\?.dll" + LUA_CDIR "?.dll;" \ + LUA_CDIR "..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ + LUA_CDIR "loadall.dll;" ".\\?.dll" #endif #else /* }{ */ @@ -249,14 +249,14 @@ #if !defined(LUA_PATH_DEFAULT) #define LUA_PATH_DEFAULT \ - LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ - LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ + LUA_LDIR "?.lua;" LUA_LDIR "?/init.lua;" \ + LUA_CDIR "?.lua;" LUA_CDIR "?/init.lua;" \ "./?.lua;" "./?/init.lua" #endif #if !defined(LUA_CPATH_DEFAULT) #define LUA_CPATH_DEFAULT \ - LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so" + LUA_CDIR "?.so;" LUA_CDIR "loadall.so;" "./?.so" #endif #endif /* } */ diff --git a/manual/manual.of b/manual/manual.of index 893592da60..18c187f439 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -9743,7 +9743,7 @@ and @bnfNter{LiteralString}, see @See{lexical}.) @OrNL @Rw{local} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{global} @Rw{function} @bnfNter{Name} funcbody @OrNL @Rw{local} attnamelist @bnfopt{@bnfter{=} explist} -@OrNL @Rw{global} attnamelist +@OrNL @Rw{global} attnamelist @bnfopt{@bnfter{=} explist} @OrNL @Rw{global} @bnfopt{attrib} @bnfter{*} } From 10eb89d1141dc528806b32401e408e36fb2f3bf5 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Wed, 18 Feb 2026 13:24:04 -0300 Subject: [PATCH 161/165] 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. --- lutf8lib.c | 5 ++++- makefile | 2 +- testes/utf8.lua | 14 +++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lutf8lib.c b/lutf8lib.c index b7f3fe1e16..73f0e49bf6 100644 --- a/lutf8lib.c +++ b/lutf8lib.c @@ -56,6 +56,8 @@ static const char *utf8_decode (const char *s, l_uint32 *val, int strict) { l_uint32 res = 0; /* final result */ if (c < 0x80) /* ASCII? */ res = c; + else if (c >= 0xfe) /* c >= 1111 1110b ? */ + return NULL; /* would need six or more continuation bytes */ else { int count = 0; /* to count number of continuation bytes */ for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */ @@ -64,8 +66,9 @@ static const char *utf8_decode (const char *s, l_uint32 *val, int strict) { return NULL; /* invalid byte sequence */ res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ } + lua_assert(count <= 5); res |= ((l_uint32)(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 */ s += count; /* skip continuation bytes read */ } diff --git a/makefile b/makefile index 8674519f5f..fa165bca6a 100644 --- a/makefile +++ b/makefile @@ -60,7 +60,7 @@ CWARNS= $(CWARNSCPP) $(CWARNSC) $(CWARNGCC) # create problems; some are only available in newer gcc versions. To # use some of them, we also have to define an environment variable # ASAN_OPTIONS="detect_invalid_pointer_pairs=2". -# -fsanitize=undefined +# -fsanitize=undefined (you may need to add "-lubsan" to libs) # -fsanitize=pointer-subtract -fsanitize=address -fsanitize=pointer-compare # TESTS= -DLUA_USER_H='"ltests.h"' -Og -g diff --git a/testes/utf8.lua b/testes/utf8.lua index 028995a478..8a0213d651 100644 --- a/testes/utf8.lua +++ b/testes/utf8.lua @@ -238,10 +238,18 @@ s = "\0 \x7F\z s = string.gsub(s, " ", "") check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF}) + +-- again, without strictness +s = "\xF0\x90\x80\x80 \xF7\xBF\xBF\xBF\z + \xF8\x88\x80\x80\x80 \xFB\xBF\xBF\xBF\xBF\z + \xFC\x84\x80\x80\x80\x80 \xFD\xBF\xBF\xBF\xBF\xBF" +s = string.gsub(s, " ", "") +check(s, {0x10000,0x1FFFFF, 0x200000,0x3FFFFFF, 0x4000000,0x7FFFFFFF}, true) + do -- original UTF-8 values local s = "\u{4000000}\u{7FFFFFFF}" - assert(#s == 12) + assert(s == "\xFC\x84\x80\x80\x80\x80\xFD\xBF\xBF\xBF\xBF\xBF") check(s, {0x4000000, 0x7FFFFFFF}, true) s = "\u{200000}\u{3FFFFFF}" @@ -257,6 +265,10 @@ local x = "日本語a-4\0éó" check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243}) +-- more than 5 continuation bytes +assert(not utf8.len("\xff\x8f\x8f\x8f\x8f\x8f\x8f\x8f")) + + -- Supplementary Characters check("𣲷𠜎𠱓𡁻𠵼ab𠺢", {0x23CB7, 0x2070E, 0x20C53, 0x2107B, 0x20D7C, 0x61, 0x62, 0x20EA2,}) From 9e501d9855e560b08c50fb0cf6e147af93bb497e Mon Sep 17 00:00:00 2001 From: Roberto I Date: Mon, 9 Mar 2026 16:23:03 -0300 Subject: [PATCH 162/165] Slightly better documentation for LUAI_MAXALIGN --- luaconf.h | 11 +++++++++-- manual/manual.of | 8 ++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/luaconf.h b/luaconf.h index 7f2206d06d..5ac9d9884e 100644 --- a/luaconf.h +++ b/luaconf.h @@ -736,10 +736,17 @@ /* -@@ LUAI_MAXALIGN defines fields that, when used in a union, ensure -** maximum alignment for the other items in that union. +@@ LUAI_MAXALIGN defines fields that ensure proper alignment for +** memory areas offered by Lua (e.g., userdata memory). +** Add fields to it if you need alignment for non-ISO objects. */ +#if defined(LLONG_MAX) +/* use ISO C99 stuff */ +#define LUAI_MAXALIGN long double u; void *s; long long l +#else +/* use only C89 stuff */ #define LUAI_MAXALIGN lua_Number n; double u; void *s; lua_Integer i; long l +#endif /* }================================================================== */ diff --git a/manual/manual.of b/manual/manual.of index 18c187f439..5eb2fb1f4f 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -3915,8 +3915,12 @@ like any Lua object. This function creates and pushes on the stack a new full userdata, with @id{nuvalue} associated Lua values, called @id{user values}, plus an associated block of raw memory with @id{size} bytes. -(The user values can be set and read with the functions -@Lid{lua_setiuservalue} and @Lid{lua_getiuservalue}.) + +The user values can be set and read with the functions +@Lid{lua_setiuservalue} and @Lid{lua_getiuservalue}. +The block of memory is suitably aligned for any @N{ISO C} object. +(See macro @id{LUAI_MAXALIGN} in file @id{luaconf.h} for other +alignment requirements.) The function returns the address of the block of memory. Lua ensures that this address is valid as long as From 36d5d2b2847906aa3b66e020d5d894a14ba2bf90 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Mon, 9 Mar 2026 16:24:06 -0300 Subject: [PATCH 163/165] Details --- lopcodes.c | 2 +- lutf8lib.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lopcodes.c b/lopcodes.c index 7e182315bc..c4828bfc02 100644 --- a/lopcodes.c +++ b/lopcodes.c @@ -104,7 +104,7 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = { ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETVARG */ ,opmode(0, 0, 0, 0, 0, iABx) /* OP_ERRNNIL */ - ,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */ + ,opmode(0, 0, 1, 0, 0, iABC) /* OP_VARARGPREP */ ,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */ }; diff --git a/lutf8lib.c b/lutf8lib.c index 73f0e49bf6..0cd7f9c363 100644 --- a/lutf8lib.c +++ b/lutf8lib.c @@ -149,7 +149,7 @@ static int codepoint (lua_State *L) { static void pushutfchar (lua_State *L, int arg) { lua_Unsigned code = (lua_Unsigned)luaL_checkinteger(L, arg); luaL_argcheck(L, code <= MAXUTF, arg, "value out of range"); - lua_pushfstring(L, "%U", (long)code); + lua_pushfstring(L, "%U", cast(unsigned long, code)); } From 377cbea61b2688b21c7d243fc0f42498851df794 Mon Sep 17 00:00:00 2001 From: Roberto I Date: Mon, 9 Mar 2026 16:24:49 -0300 Subject: [PATCH 164/165] 'table.tunpack' using 'aux_getn' like the others 'table.tunpack' was not checking its first argument, which could result in error messages generated inside the API, without location information. --- ltablib.c | 11 +++++++---- testes/sort.lua | 13 +++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ltablib.c b/ltablib.c index 46ecb5e024..15c3c09f04 100644 --- a/ltablib.c +++ b/ltablib.c @@ -42,15 +42,17 @@ static int checkfield (lua_State *L, const char *key, int n) { /* ** Check that 'arg' either is a table or can behave like one (that is, -** has a metatable with the required metamethods) +** has a metatable with the required metamethods). */ static void checktab (lua_State *L, int arg, int what) { - if (lua_type(L, arg) != LUA_TTABLE) { /* is it not a table? */ + int tp = lua_type(L, arg); + if (tp != LUA_TTABLE) { /* is it not a table? */ int n = 1; /* number of elements to pop */ if (lua_getmetatable(L, arg) && /* must have metatable */ (!(what & TAB_R) || checkfield(L, "__index", ++n)) && (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) && - (!(what & TAB_L) || checkfield(L, "__len", ++n))) { + (!(what & TAB_L) || /* strings don't need '__len' to have a length */ + tp == LUA_TSTRING || checkfield(L, "__len", ++n))) { lua_pop(L, n); /* pop metatable and tested metamethods */ } else @@ -204,8 +206,9 @@ static int tpack (lua_State *L) { static int tunpack (lua_State *L) { lua_Unsigned n; + lua_Integer len = aux_getn(L, 1, TAB_R); lua_Integer i = luaL_optinteger(L, 2, 1); - lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, luaL_len(L, 1)); + lua_Integer e = luaL_opt(L, luaL_checkinteger, 3, len); if (i > e) return 0; /* empty range */ n = l_castS2U(e) - l_castS2U(i); /* number of elements minus 1 */ if (l_unlikely(n >= (unsigned int)INT_MAX || diff --git a/testes/sort.lua b/testes/sort.lua index b012766057..92aaca3cef 100644 --- a/testes/sort.lua +++ b/testes/sort.lua @@ -72,6 +72,19 @@ assert(a==1 and x==nil) a,x = unpack({1,2}, 1, 1) assert(a==1 and x==nil) + +do -- unpack with non-tables + local debug = require"debug" + local oldmt = debug.getmetatable(0) + local str = "hello" + debug.setmetatable(0, + { __len = function () return #str end, + __index = function (_, i) return string.sub(str, i, i) end}) + assert(table.concat({table.unpack(0)}) == str) + debug.setmetatable(0, oldmt) -- restore original metatable for numbers +end + + do local maxi = (1 << 31) - 1 -- maximum value for an int (usually) local mini = -(1 << 31) -- minimum value for an int (usually) From 51269bd783c9371252947b26cc865239dbb0153d Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sun, 15 Mar 2026 15:14:14 -0300 Subject: [PATCH 165/165] Adjustment in useless parameter L in macros luai_num* --- llimits.h | 12 ++++++------ lvm.c | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/llimits.h b/llimits.h index fc5cb276f6..3f0372552a 100644 --- a/llimits.h +++ b/llimits.h @@ -234,12 +234,12 @@ typedef unsigned long l_uint32; /* floor division (defined as 'floor(a/b)') */ #if !defined(luai_numidiv) -#define luai_numidiv(L,a,b) ((void)L, l_floor(luai_numdiv(L,a,b))) +#define luai_numidiv(L,a,b) l_floor(luai_numdiv(L,a,b)) #endif /* float division */ #if !defined(luai_numdiv) -#define luai_numdiv(L,a,b) ((a)/(b)) +#define luai_numdiv(L,a,b) ((void)L, (a)/(b)) #endif /* @@ -267,10 +267,10 @@ typedef unsigned long l_uint32; /* the others are quite standard operations */ #if !defined(luai_numadd) -#define luai_numadd(L,a,b) ((a)+(b)) -#define luai_numsub(L,a,b) ((a)-(b)) -#define luai_nummul(L,a,b) ((a)*(b)) -#define luai_numunm(L,a) (-(a)) +#define luai_numadd(L,a,b) ((void)L, (a)+(b)) +#define luai_numsub(L,a,b) ((void)L, (a)-(b)) +#define luai_nummul(L,a,b) ((void)L, (a)*(b)) +#define luai_numunm(L,a) ((void)L, -(a)) #define luai_numeq(a,b) ((a)==(b)) #define luai_numlt(a,b) ((a)<(b)) #define luai_numle(a,b) ((a)<=(b)) diff --git a/lvm.c b/lvm.c index c70e2b8a8a..96ae16390f 100644 --- a/lvm.c +++ b/lvm.c @@ -268,9 +268,9 @@ static int forprep (lua_State *L, StkId ra) { /* ** Execute a step of a float numerical for loop, returning ** true iff the loop must continue. (The integer case is -** written online with opcode OP_FORLOOP, for performance.) +** written inline with opcode OP_FORLOOP, for performance.) */ -static int floatforloop (StkId ra) { +static int floatforloop (lua_State *L, StkId ra) { lua_Number step = fltvalue(s2v(ra + 1)); lua_Number limit = fltvalue(s2v(ra)); lua_Number idx = fltvalue(s2v(ra + 2)); /* control variable */ @@ -1841,7 +1841,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { pc -= GETARG_Bx(i); /* jump back */ } } - else if (floatforloop(ra)) /* float loop */ + else if (floatforloop(L, ra)) /* float loop */ pc -= GETARG_Bx(i); /* jump back */ updatetrap(ci); /* allows a signal to break the loop */ vmbreak;