commit b2f7b3b79f3117885b265575f6c5dbf934757797
parent 540d8052265776451bb9f0ab4dee4ec860563cbe
Author: Roberto Ierusalimschy <roberto@inf.puc-rio.br>
Date: Wed, 21 Dec 2022 12:04:32 -0300
Control variables in for loops are read only
Diffstat:
5 files changed, 40 insertions(+), 37 deletions(-)
diff --git a/lparser.c b/lparser.c
@@ -187,10 +187,10 @@ static int registerlocalvar (LexState *ls, FuncState *fs, TString *varname) {
/*
-** Create a new local variable with the given 'name'. Return its index
-** in the function.
+** Create a new local variable with the given 'name' and given 'kind'.
+** Return its index in the function.
*/
-static int new_localvar (LexState *ls, TString *name) {
+static int new_localvarkind (LexState *ls, TString *name, int kind) {
lua_State *L = ls->L;
FuncState *fs = ls->fs;
Dyndata *dyd = ls->dyd;
@@ -200,11 +200,19 @@ static int new_localvar (LexState *ls, TString *name) {
luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1,
dyd->actvar.size, Vardesc, USHRT_MAX, "local variables");
var = &dyd->actvar.arr[dyd->actvar.n++];
- var->vd.kind = VDKREG; /* default */
+ var->vd.kind = kind; /* default */
var->vd.name = name;
return dyd->actvar.n - 1 - fs->firstlocal;
}
+
+/*
+** Create a new local variable with the given 'name' and regular kind.
+*/
+static int new_localvar (LexState *ls, TString *name) {
+ return new_localvarkind(ls, name, VDKREG);
+}
+
#define new_localvarliteral(ls,v) \
new_localvar(ls, \
luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char)) - 1));
@@ -1573,7 +1581,7 @@ static void fornum (LexState *ls, TString *varname, int line) {
new_localvarliteral(ls, "(for state)");
new_localvarliteral(ls, "(for state)");
new_localvarliteral(ls, "(for state)");
- new_localvar(ls, varname);
+ new_localvarkind(ls, varname, RDKCONST); /* control variable */
checknext(ls, '=');
exp1(ls); /* initial value */
checknext(ls, ',');
@@ -1601,8 +1609,8 @@ static void forlist (LexState *ls, TString *indexname) {
new_localvarliteral(ls, "(for state)");
new_localvarliteral(ls, "(for state)");
new_localvarliteral(ls, "(for state)");
- /* create declared variables */
- new_localvar(ls, indexname);
+ new_localvarkind(ls, indexname, RDKCONST); /* control variable */
+ /* other declared variables */
while (testnext(ls, ',')) {
new_localvar(ls, str_checkname(ls));
nvars++;
@@ -1728,14 +1736,14 @@ static void localstat (LexState *ls) {
FuncState *fs = ls->fs;
int toclose = -1; /* index of to-be-closed variable (if any) */
Vardesc *var; /* last variable */
- int vidx, kind; /* index and kind of last variable */
+ int vidx; /* index of last variable */
int nvars = 0;
int nexps;
expdesc e;
do {
- vidx = new_localvar(ls, str_checkname(ls));
- kind = getlocalattribute(ls);
- getlocalvardesc(fs, vidx)->vd.kind = kind;
+ TString *vname = str_checkname(ls);
+ int kind = getlocalattribute(ls);
+ vidx = new_localvarkind(ls, vname, kind);
if (kind == RDKTOCLOSE) { /* to-be-closed? */
if (toclose != -1) /* one already present? */
luaK_semerror(ls, "multiple to-be-closed variables in local list");
diff --git a/manual/manual.of b/manual/manual.of
@@ -1467,7 +1467,7 @@ It has the following syntax:
exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}}
}
The given identifier (@bnfNter{Name}) defines the control variable,
-which is a new variable local to the loop body (@emph{block}).
+which is a new read-only variable local to the loop body (@emph{block}).
The loop starts by evaluating once the three control expressions.
Their values are called respectively
@@ -1499,11 +1499,6 @@ For integer loops,
the control variable never wraps around;
instead, the loop ends in case of an overflow.
-You should not change the value of the control variable
-during the loop.
-If you need its value after the loop,
-assign it to another variable before exiting the loop.
-
}
@sect4{@title{The generic @Rw{for} loop}
@@ -1526,7 +1521,8 @@ for @rep{var_1}, @Cdots, @rep{var_n} in @rep{explist} do @rep{body} end
works as follows.
The names @rep{var_i} declare loop variables local to the loop body.
-The first of these variables is the @emph{control variable}.
+The first of these variables is the @emph{control variable},
+which is a read-only variable.
The loop starts by evaluating @rep{explist}
to produce four values:
@@ -1550,9 +1546,6 @@ to-be-closed variable @see{to-be-closed},
which can be used to release resources when the loop ends.
Otherwise, it does not interfere with the loop.
-You should not change the value of the control variable
-during the loop.
-
}
}
@@ -9156,6 +9149,9 @@ change between versions.
@itemize{
@item{
+The control variable in @Rw{for} loops are read only.
+If you need to change it,
+declare a local variable with the same name in the loop body.
}
}
diff --git a/testes/attrib.lua b/testes/attrib.lua
@@ -236,7 +236,7 @@ package.path = oldpath
local fname = "file_does_not_exist2"
local m, err = pcall(require, fname)
for t in string.gmatch(package.path..";"..package.cpath, "[^;]+") do
- t = string.gsub(t, "?", fname)
+ local t = string.gsub(t, "?", fname)
assert(string.find(err, t, 1, true))
end
diff --git a/testes/closure.lua b/testes/closure.lua
@@ -60,32 +60,29 @@ end
-- testing closures with 'for' control variable
a = {}
for i=1,10 do
- a[i] = {set = function(x) i=x end, get = function () return i end}
+ a[i] = function () return i end
if i == 3 then break end
end
assert(a[4] == undef)
-a[1].set(10)
-assert(a[2].get() == 2)
-a[2].set('a')
-assert(a[3].get() == 3)
-assert(a[2].get() == 'a')
+assert(a[2]() == 2)
+assert(a[3]() == 3)
a = {}
local t = {"a", "b"}
for i = 1, #t do
local k = t[i]
- a[i] = {set = function(x, y) i=x; k=y end,
+ a[i] = {set = function(x) k=x end,
get = function () return i, k end}
if i == 2 then break end
end
-a[1].set(10, 20)
+a[1].set(10)
local r,s = a[2].get()
assert(r == 2 and s == 'b')
r,s = a[1].get()
-assert(r == 10 and s == 20)
-a[2].set('a', 'b')
+assert(r == 1 and s == 10)
+a[2].set('a')
r,s = a[2].get()
-assert(r == "a" and s == "b")
+assert(r == 2 and s == "a")
-- testing closures with 'for' control variable x break
diff --git a/testes/nextvar.lua b/testes/nextvar.lua
@@ -609,10 +609,12 @@ do
a = 0; for i=1.0, 0.99999, -1 do a=a+1 end; assert(a==1)
end
-do -- changing the control variable
- local a
- a = 0; for i = 1, 10 do a = a + 1; i = "x" end; assert(a == 10)
- a = 0; for i = 10.0, 1, -1 do a = a + 1; i = "x" end; assert(a == 10)
+do -- attempt to change the control variable
+ local st, msg = load "for i = 1, 10 do i = 10 end"
+ assert(not st and string.find(msg, "assign to const variable 'i'"))
+
+ local st, msg = load "for v, k in pairs{} do v = 10 end"
+ assert(not st and string.find(msg, "assign to const variable 'v'"))
end
-- conversion