Просмотр исходного кода

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.
Roberto I 2 месяцев назад
Родитель
Сommit
e44f3a2ffc
13 измененных файлов с 87 добавлено и 9 удалено
  1. 16 0
      lcode.c
  2. 2 0
      lcode.h
  3. 8 0
      ldebug.c
  4. 1 0
      ldebug.h
  5. 1 0
      ljumptab.h
  6. 1 0
      lopcodes.c
  7. 2 0
      lopcodes.h
  8. 1 0
      lopnames.h
  9. 16 3
      lparser.c
  10. 6 0
      lvm.c
  11. 11 3
      manual/manual.of
  12. 20 1
      testes/goto.lua
  13. 2 2
      testes/memerr.lua

+ 16 - 0
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'
 */

+ 2 - 0
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);

+ 8 - 0
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) {

+ 1 - 0
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);

+ 1 - 0
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
 

+ 1 - 0
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 */
 };

+ 2 - 0
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	*/

+ 1 - 0
lopnames.h

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

+ 16 - 3
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 */
 }

+ 6 - 0
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 */

+ 11 - 3
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},

+ 20 - 1
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<const> 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<const> 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'
 

+ 2 - 2
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 ()