Browse Source

To-be-closed variable in 'for' loop separated from the state

The variable to be closed in a generic 'for' loop now is the
4th value produced in the loop initialization, instead of being
the loop state (the 2nd value produced). That allows a loop to
use a state with a '__toclose' metamethod but do not close it.
(As an example, 'f:lines()' might use the file 'f' as a state
for the loop, but it should not close the file when the loop ends.)
Roberto Ierusalimschy 6 years ago
parent
commit
7f6f70853c
6 changed files with 76 additions and 29 deletions
  1. 4 2
      liolib.c
  2. 3 3
      lopcodes.h
  3. 14 11
      lparser.c
  4. 12 11
      lvm.c
  5. 36 1
      testes/files.lua
  6. 7 1
      testes/locals.lua

+ 4 - 2
liolib.c

@@ -386,8 +386,10 @@ static int io_lines (lua_State *L) {
   }
   aux_lines(L, toclose);  /* push iteration function */
   if (toclose) {
-    lua_pushvalue(L, 1);  /* file will be second result */
-    return 2;
+    lua_pushnil(L);  /* state */
+    lua_pushnil(L);  /* control */
+    lua_pushvalue(L, 1);  /* file is the to-be-closed variable (4th result) */
+    return 4;
   }
   else
     return 1;

+ 3 - 3
lopcodes.h

@@ -279,9 +279,9 @@ OP_FORLOOP,/*	A Bx	R(A)+=R(A+2);
 			if R(A) <?= R(A+1) then { pc-=Bx; R(A+3)=R(A) }	*/
 OP_FORPREP,/*	A Bx	R(A)-=R(A+2); pc+=Bx				*/
 
-OP_TFORPREP,/*	A Bx	create upvalue A; pc+=Bx			*/
-OP_TFORCALL,/*	A C	R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));	*/
-OP_TFORLOOP,/*	A Bx	if R(A+1) ~= nil then { R(A)=R(A+1); pc -= Bx }	*/
+OP_TFORPREP,/*	A Bx	create upvalue for R(A + 3); pc+=Bx		*/
+OP_TFORCALL,/*	A C	R(A+4), ... ,R(A+3+C) := R(A)(R(A+1), R(A+2));	*/
+OP_TFORLOOP,/*	A Bx	if R(A+2) ~= nil then { R(A)=R(A+2); pc -= Bx }	*/
 
 OP_SETLIST,/*	A B C	R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B	*/
 

+ 14 - 11
lparser.c

@@ -165,6 +165,9 @@ static int registerlocalvar (LexState *ls, TString *varname) {
 }
 
 
+/*
+** Create a new local variable with the given 'name'.
+*/
 static void new_localvar (LexState *ls, TString *name) {
   FuncState *fs = ls->fs;
   Dyndata *dyd = ls->dyd;
@@ -176,13 +179,8 @@ static void new_localvar (LexState *ls, TString *name) {
   dyd->actvar.arr[dyd->actvar.n++].idx = cast(short, reg);
 }
 
-
-static void new_localvarliteral_ (LexState *ls, const char *name, size_t sz) {
-  new_localvar(ls, luaX_newstring(ls, name, sz));
-}
-
 #define new_localvarliteral(ls,v) \
-	new_localvarliteral_(ls, "" v, (sizeof(v)/sizeof(char))-1)
+    new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1));
 
 
 static LocVar *getlocvar (FuncState *fs, int i) {
@@ -192,6 +190,9 @@ static LocVar *getlocvar (FuncState *fs, int i) {
 }
 
 
+/*
+** Start the scope for the last 'nvars' created variables.
+*/
 static void adjustlocalvars (LexState *ls, int nvars) {
   FuncState *fs = ls->fs;
   fs->nactvar = cast_byte(fs->nactvar + nvars);
@@ -1357,7 +1358,6 @@ static void forbody (LexState *ls, int base, int line, int nvars, int kind) {
   BlockCnt bl;
   FuncState *fs = ls->fs;
   int prep, endfor;
-  adjustlocalvars(ls, 3);  /* control variables */
   checknext(ls, TK_DO);
   prep = luaK_codeABx(fs, forprep[kind], base, 0);
   enterblock(fs, &bl, 0);  /* scope for declared variables */
@@ -1399,6 +1399,7 @@ static void fornum (LexState *ls, TString *varname, int line) {
     luaK_int(fs, fs->freereg, 1);
     luaK_reserveregs(fs, 1);
   }
+  adjustlocalvars(ls, 3);  /* control variables */
   forbody(ls, base, line, 1, basicfor);
 }
 
@@ -1407,7 +1408,7 @@ static void forlist (LexState *ls, TString *indexname) {
   /* forlist -> NAME {,NAME} IN explist forbody */
   FuncState *fs = ls->fs;
   expdesc e;
-  int nvars = 4;  /* gen, state, control, plus at least one declared var */
+  int nvars = 5;  /* gen, state, control, toclose, 'indexname' */
   int line;
   int base = fs->freereg;
   /* create control variables */
@@ -1415,6 +1416,7 @@ static void forlist (LexState *ls, TString *indexname) {
   new_localvarliteral(ls, "(for state)");
   markupval(fs, fs->nactvar);  /* state may create an upvalue */
   new_localvarliteral(ls, "(for control)");
+  new_localvarliteral(ls, "(for toclose)");
   /* create declared variables */
   new_localvar(ls, indexname);
   while (testnext(ls, ',')) {
@@ -1423,9 +1425,10 @@ static void forlist (LexState *ls, TString *indexname) {
   }
   checknext(ls, TK_IN);
   line = ls->linenumber;
-  adjust_assign(ls, 3, explist(ls, &e), &e);
+  adjust_assign(ls, 4, explist(ls, &e), &e);
+  adjustlocalvars(ls, 4);  /* control variables */
   luaK_checkstack(fs, 3);  /* extra space to call generator */
-  forbody(ls, base, line, nvars - 3, 2);
+  forbody(ls, base, line, nvars - 4, 2);
 }
 
 
@@ -1575,9 +1578,9 @@ static void tocloselocalstat (LexState *ls) {
   new_localvar(ls, str_checkname(ls));
   checknext(ls, '=');
   exp1(ls, 0);
-  luaK_codeABC(fs, OP_TBC, fs->nactvar, 0, 0);
   markupval(fs, fs->nactvar);
   adjustlocalvars(ls, 1);
+  luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
 }
 
 

+ 12 - 11
lvm.c

@@ -1654,11 +1654,11 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
         vmbreak;
       }
       vmcase(OP_TFORPREP) {
-        /* is 'state' a function or has a '__close' metamethod? */
-        if (ttisfunction(s2v(ra + 1)) ||
-            !ttisnil(luaT_gettmbyobj(L, s2v(ra + 1), TM_CLOSE))) {
+        /* is 'toclose' a function or has a '__close' metamethod? */
+        if (ttisfunction(s2v(ra + 3)) ||
+            !ttisnil(luaT_gettmbyobj(L, s2v(ra + 3), TM_CLOSE))) {
           /* create to-be-closed upvalue for it */
-          halfProtect(luaF_newtbcupval(L, ra + 1));
+          halfProtect(luaF_newtbcupval(L, ra + 3));
         }
         pc += GETARG_Bx(i);
         i = *(pc++);  /* go to next instruction */
@@ -1668,13 +1668,14 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
       vmcase(OP_TFORCALL) {
        l_tforcall:
         /* 'ra' has the iterator function, 'ra + 1' has the state,
-           and 'ra + 2' has the control variable. The call will use
-           the stack after these values (starting at 'ra + 3')
+           'ra + 2' has the control variable, and 'ra + 3' has the
+           to-be-closed variable. The call will use the stack after
+           these values (starting at 'ra + 4')
         */
         /* push function, state, and control variable */
-        memcpy(ra + 3, ra, 3 * sizeof(*ra));
-        L->top = ra + 6;
-        Protect(luaD_call(L, ra + 3, GETARG_C(i)));  /* do the call */
+        memcpy(ra + 4, ra, 3 * sizeof(*ra));
+        L->top = ra + 4 + 3;
+        Protect(luaD_call(L, ra + 4, GETARG_C(i)));  /* do the call */
         if (trap) {  /* stack may have changed? */
           updatebase(ci);  /* keep 'base' correct */
           ra = RA(i);  /* keep 'ra' correct for next instruction */
@@ -1686,8 +1687,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
       }
       vmcase(OP_TFORLOOP) {
         l_tforloop:
-        if (!ttisnil(s2v(ra + 1))) {  /* continue loop? */
-          setobjs2s(L, ra, ra + 1);  /* save control variable */
+        if (!ttisnil(s2v(ra + 2))) {  /* continue loop? */
+          setobjs2s(L, ra, ra + 2);  /* save control variable */
           pc -= GETARG_Bx(i);  /* jump back */
         }
         vmbreak;

+ 36 - 1
testes/files.lua

@@ -200,7 +200,7 @@ return x + y * z
 assert(f:close())
 f = coroutine.wrap(dofile)
 assert(f(file) == 10)
-print(f(100, 101) == 20)
+assert(f(100, 101) == 20)
 assert(f(200) == 100 + 200 * 101)
 assert(os.remove(file))
 
@@ -422,6 +422,41 @@ assert(load(io.lines(file, "L"), nil, nil, t))()
 assert(t.a == -((10 + 34) * 2))
 
 
+do   -- testing closing file in line iteration
+
+  -- get the to-be-closed variable from a loop
+  local function gettoclose (lv)
+    lv = lv + 1
+    for i = 1, math.maxinteger do
+      local n, v = debug.getlocal(lv, i)
+      if n == "(for toclose)" then
+        return v
+      end
+    end
+  end
+
+  local f
+  for l in io.lines(file) do
+    f = gettoclose(1)
+    assert(io.type(f) == "file")
+    break
+  end
+  assert(io.type(f) == "closed file")
+
+  f = nil
+  local function foo (name)
+    for l in io.lines(name) do
+      f = gettoclose(1)
+      assert(io.type(f) == "file")
+      error(f)   -- exit loop with an error
+    end
+  end
+  local st, msg = pcall(foo, file)
+  assert(st == false and io.type(msg) == "closed file")
+
+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

+ 7 - 1
testes/locals.lua

@@ -371,6 +371,8 @@ do
         x = x - 1
         if x > 0 then return x end
       end,
+      nil,   -- state
+      nil,   -- control variable
       function ()   -- closing function
         numopen = numopen - 1
       end
@@ -392,12 +394,16 @@ do
   -- repeat test with '__open' metamethod instead of a function
   local function open (x)
     numopen = numopen + 1
+    local state = setmetatable({x},
+                        {__close = function () numopen = numopen - 1 end})
     return
       function (t)   -- iteraction function
         t[1] = t[1] - 1
         if t[1] > 0 then return t[1] end
       end,
-      setmetatable({x}, {__close = function () numopen = numopen - 1 end})
+      state,
+      nil,
+      state    -- to-be-closed
   end
 
   local s = 0