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.
This commit is contained in:
Roberto I
2025-11-08 11:43:42 -03:00
parent f791bb6906
commit e44f3a2ffc
13 changed files with 87 additions and 9 deletions

16
lcode.c
View File

@@ -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' ** Convert a constant in 'v' into an expression description 'e'
*/ */

View File

@@ -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 int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v);
LUAI_FUNC void luaK_fixline (FuncState *fs, int line); LUAI_FUNC void luaK_fixline (FuncState *fs, int line);
LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); 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_reserveregs (FuncState *fs, int n);
LUAI_FUNC void luaK_checkstack (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_int (FuncState *fs, int reg, lua_Integer n);

View File

@@ -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' */ /* add src:line information to 'msg' */
const char *luaG_addinfo (lua_State *L, const char *msg, TString *src, const char *luaG_addinfo (lua_State *L, const char *msg, TString *src,
int line) { int line) {

View File

@@ -53,6 +53,7 @@ LUAI_FUNC l_noret luaG_tointerror (lua_State *L, const TValue *p1,
const TValue *p2); const TValue *p2);
LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1, LUAI_FUNC l_noret luaG_ordererror (lua_State *L, const TValue *p1,
const TValue *p2); 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 l_noret luaG_runerror (lua_State *L, const char *fmt, ...);
LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg, LUAI_FUNC const char *luaG_addinfo (lua_State *L, const char *msg,
TString *src, int line); TString *src, int line);

View File

@@ -107,6 +107,7 @@ static const void *const disptab[NUM_OPCODES] = {
&&L_OP_CLOSURE, &&L_OP_CLOSURE,
&&L_OP_VARARG, &&L_OP_VARARG,
&&L_OP_GETVARG, &&L_OP_GETVARG,
&&L_OP_ERRNNIL,
&&L_OP_VARARGPREP, &&L_OP_VARARGPREP,
&&L_OP_EXTRAARG &&L_OP_EXTRAARG

View File

@@ -103,6 +103,7 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */ ,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */
,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */
,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETVARG */ ,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, 1, iABC) /* OP_VARARGPREP */
,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */ ,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */
}; };

View File

@@ -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_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_VARARGPREP,/* (adjust vararg parameters) */
OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */

View File

@@ -95,6 +95,7 @@ static const char *const opnames[] = {
"CLOSURE", "CLOSURE",
"VARARG", "VARARG",
"GETVARG", "GETVARG",
"ERRNNIL",
"VARARGPREP", "VARARGPREP",
"EXTRAARG", "EXTRAARG",
NULL NULL

View File

@@ -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 ** Recursively traverse list of globals to be initalized. When
** going, generate table description for the global. In the end, ** 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 ** the stack to the corresponding table description. 'n' is the variable
** being handled, range [0, nvars - 1]. ** 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? */ if (n == nvars) { /* traversed all variables? */
expdesc e; expdesc e;
int nexps = explist(ls, &e); /* read list of expressions */ 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; TString *varname = getlocalvardesc(fs, firstidx + n)->vd.name;
buildglobal(ls, varname, &var); /* create global variable in 'var' */ buildglobal(ls, varname, &var); /* create global variable in 'var' */
enterlevel(ls); /* control recursion depth */ enterlevel(ls); /* control recursion depth */
initglobal(ls, nvars, firstidx, n + 1); initglobal(ls, nvars, firstidx, n + 1, line);
leavelevel(ls); leavelevel(ls);
checkglobal(ls, varname, line);
storevartop(fs, &var); storevartop(fs, &var);
} }
} }
@@ -1913,7 +1925,7 @@ static void globalnames (LexState *ls, lu_byte defkind) {
nvars++; nvars++;
} while (testnext(ls, ',')); } while (testnext(ls, ','));
if (testnext(ls, '=')) /* initialization? */ 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 */ 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 */ fs->nactvar++; /* enter its scope */
buildglobal(ls, fname, &var); buildglobal(ls, fname, &var);
body(ls, &b, 0, ls->linenumber); /* compile and return closure in 'b' */ body(ls, &b, 0, ls->linenumber); /* compile and return closure in 'b' */
checkglobal(ls, fname, line);
luaK_storevar(fs, &var, &b); luaK_storevar(fs, &var, &b);
luaK_fixline(fs, line); /* definition "happens" in the first line */ luaK_fixline(fs, line); /* definition "happens" in the first line */
} }

6
lvm.c
View File

@@ -1940,6 +1940,12 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
luaT_getvararg(ci, ra, rc); luaT_getvararg(ci, ra, rc);
vmbreak; vmbreak;
} }
vmcase(OP_ERRNNIL) {
TValue *ra = vRA(i);
if (!ttisnil(ra))
halfProtect(luaG_errnnil(L, cl, GETARG_Bx(i)));
vmbreak;
}
vmcase(OP_VARARGPREP) { vmcase(OP_VARARGPREP) {
ProtectNT(luaT_adjustvarargs(L, ci, cl->p)); ProtectNT(luaT_adjustvarargs(L, ci, cl->p));
if (l_unlikely(trap)) { /* previous "Protect" updated trap */ if (l_unlikely(trap)) { /* previous "Protect" updated trap */

View File

@@ -1660,9 +1660,15 @@ The declaration can include an initialization:
@producname{stat}@producbody{@Rw{global} @producname{stat}@producbody{@Rw{global}
attnamelist @bnfopt{@bnfter{=} explist}} 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}. 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 The list of names may be prefixed by an attribute
(a name between angle brackets) (a name between angle brackets)
@@ -2312,8 +2318,10 @@ global function f () @rep{body} end
} }
translates to translates to
@verbatim{ @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 The @emphx{colon} syntax
is used to emulate @def{methods}, is used to emulate @def{methods},

View File

@@ -293,6 +293,7 @@ end
foo() foo()
-------------------------------------------------------------------------- --------------------------------------------------------------------------
-- check for compilation errors
local function checkerr (code, err) local function checkerr (code, err)
local st, msg = load(code) local st, msg = load(code)
assert(not st and string.find(msg, err)) assert(not st and string.find(msg, err))
@@ -414,22 +415,26 @@ end
do print "testing initialization in global declarations" do print "testing initialization in global declarations"
global<const> a, b, c = 10, 20, 30 global<const> a, b, c = 10, 20, 30
assert(_ENV.a == 10 and b == 20 and c == 30) assert(_ENV.a == 10 and b == 20 and c == 30)
_ENV.a = nil; _ENV.b = nil; _ENV.c = nil;
global<const> a, b, c = 10 global<const> a, b, c = 10
assert(_ENV.a == 10 and b == nil and c == nil) assert(_ENV.a == 10 and b == nil and c == nil)
_ENV.a = nil; _ENV.b = nil; _ENV.c = nil;
global table global table
global a, b, c, d = table.unpack{1, 2, 3, 6, 5} 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) 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 local a, b = 100, 200
do do
global a, b = a, b global a, b = a, b
end end
assert(_ENV.a == 100 and _ENV.b == 200) 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 end
do do
@@ -454,5 +459,19 @@ do
assert(env.a == 10 and env.b == 20 and env.c == 30) assert(env.a == 10 and env.b == 20 and env.c == 30)
end 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' print'OK'

View File

@@ -166,9 +166,9 @@ local function expand (n,s)
e, s, expand(n-1,s), e) e, s, expand(n-1,s), e)
end end
G=0; collectgarbage(); a =collectgarbage("count") G=0; collectgarbage()
load(expand(20,"G=G+1"))() load(expand(20,"G=G+1"))()
assert(G==20); collectgarbage(); -- assert(gcinfo() <= a+1) assert(G==20); collectgarbage()
G = nil G = nil
testamem("running code on new thread", function () testamem("running code on new thread", function ()