From 7f6f70853c8a2730fca2e95d5968ad52cf470bda Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 7 Nov 2018 14:42:05 -0200 Subject: [PATCH] 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.) --- liolib.c | 6 ++++-- lopcodes.h | 6 +++--- lparser.c | 25 ++++++++++++++----------- lvm.c | 23 ++++++++++++----------- testes/files.lua | 37 ++++++++++++++++++++++++++++++++++++- testes/locals.lua | 8 +++++++- 6 files changed, 76 insertions(+), 29 deletions(-) diff --git a/liolib.c b/liolib.c index b2a2fec8..7d6d51e6 100644 --- a/liolib.c +++ b/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; diff --git a/lopcodes.h b/lopcodes.h index 5a38f767..7bb83a1e 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -279,9 +279,9 @@ OP_FORLOOP,/* A Bx R(A)+=R(A+2); if R(A) 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); } diff --git a/lvm.c b/lvm.c index 9977bda4..7d5ab9db 100644 --- a/lvm.c +++ b/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; diff --git a/testes/files.lua b/testes/files.lua index e68eb9b8..34fcf851 100644 --- a/testes/files.lua +++ b/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 diff --git a/testes/locals.lua b/testes/locals.lua index 869ac1ff..28f88e54 100644 --- a/testes/locals.lua +++ b/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