From e44f3a2ffc7ced5e75cca7657aaa60ef27da89aa Mon Sep 17 00:00:00 2001 From: Roberto I Date: Sat, 8 Nov 2025 11:43:42 -0300 Subject: 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 f09edb5f..d82f8263 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 f6397a3c..09e5c802 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 9110f437..abead91c 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 2bfce3cb..20d07818 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 f896b658..52fa6d74 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 47458e40..7e182315 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 82bba721..f7bded2c 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 aa7bea77..0554a2e9 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 40a30ff6..77141e79 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 efb0db28..2a9fb67a 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 0127df02..eaf0ce78 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 a692a623..906208b5 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 2cc8f481..9c940ca7 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 () -- cgit v1.2.3-55-g6feb