diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2020-10-14 15:46:58 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2020-10-14 15:46:58 -0300 |
commit | 52c86797608f1bf927be5bab1e9b97b7d35bdf2c (patch) | |
tree | 41165b997293675eeb2a40bffe8417939e76c95e | |
parent | 849b2ecbd28793408d21ebd734525ab56ae5ca1e (diff) | |
download | lua-52c86797608f1bf927be5bab1e9b97b7d35bdf2c.tar.gz lua-52c86797608f1bf927be5bab1e9b97b7d35bdf2c.tar.bz2 lua-52c86797608f1bf927be5bab1e9b97b7d35bdf2c.zip |
Fixed bug of keys removed from tables vs 'next'
Fixed the bug that a key removed from a table might not be found
again by 'next'. (This is needed to allow keys to be removed during a
traversal.) This bug was introduced in commit 73ec04fc.
-rw-r--r-- | lgc.c | 17 | ||||
-rw-r--r-- | lobject.h | 18 | ||||
-rw-r--r-- | ltable.c | 27 | ||||
-rw-r--r-- | testes/nextvar.lua | 32 |
4 files changed, 67 insertions, 27 deletions
@@ -161,18 +161,17 @@ static void linkgclist_ (GCObject *o, GCObject **pnext, GCObject **list) { | |||
161 | 161 | ||
162 | 162 | ||
163 | /* | 163 | /* |
164 | ** Clear keys for empty entries in tables. If entry is empty | 164 | ** Clear keys for empty entries in tables. If entry is empty, mark its |
165 | ** and its key is not marked, mark its entry as dead. This allows the | 165 | ** entry as dead. This allows the collection of the key, but keeps its |
166 | ** collection of the key, but keeps its entry in the table (its removal | 166 | ** entry in the table: its removal could break a chain and could break |
167 | ** could break a chain). The main feature of a dead key is that it must | 167 | ** a table traversal. Other places never manipulate dead keys, because |
168 | ** be different from any other value, to do not disturb searches. | 168 | ** its associated empty value is enough to signal that the entry is |
169 | ** Other places never manipulate dead keys, because its associated empty | 169 | ** logically empty. |
170 | ** value is enough to signal that the entry is logically empty. | ||
171 | */ | 170 | */ |
172 | static void clearkey (Node *n) { | 171 | static void clearkey (Node *n) { |
173 | lua_assert(isempty(gval(n))); | 172 | lua_assert(isempty(gval(n))); |
174 | if (keyiswhite(n)) | 173 | if (keyiscollectable(n)) |
175 | setdeadkey(n); /* unused and unmarked key; remove it */ | 174 | setdeadkey(n); /* unused key; remove it */ |
176 | } | 175 | } |
177 | 176 | ||
178 | 177 | ||
@@ -21,10 +21,12 @@ | |||
21 | */ | 21 | */ |
22 | #define LUA_TUPVAL LUA_NUMTYPES /* upvalues */ | 22 | #define LUA_TUPVAL LUA_NUMTYPES /* upvalues */ |
23 | #define LUA_TPROTO (LUA_NUMTYPES+1) /* function prototypes */ | 23 | #define LUA_TPROTO (LUA_NUMTYPES+1) /* function prototypes */ |
24 | #define LUA_TDEADKEY (LUA_NUMTYPES+2) /* removed keys in tables */ | ||
25 | |||
24 | 26 | ||
25 | 27 | ||
26 | /* | 28 | /* |
27 | ** number of all possible types (including LUA_TNONE) | 29 | ** number of all possible types (including LUA_TNONE but excluding DEADKEY) |
28 | */ | 30 | */ |
29 | #define LUA_TOTALTYPES (LUA_TPROTO + 2) | 31 | #define LUA_TOTALTYPES (LUA_TPROTO + 2) |
30 | 32 | ||
@@ -555,7 +557,7 @@ typedef struct Proto { | |||
555 | 557 | ||
556 | /* | 558 | /* |
557 | ** {================================================================== | 559 | ** {================================================================== |
558 | ** Closures | 560 | ** Functions |
559 | ** =================================================================== | 561 | ** =================================================================== |
560 | */ | 562 | */ |
561 | 563 | ||
@@ -743,13 +745,13 @@ typedef struct Table { | |||
743 | 745 | ||
744 | 746 | ||
745 | /* | 747 | /* |
746 | ** Use a "nil table" to mark dead keys in a table. Those keys serve | 748 | ** Dead keys in tables have the tag DEADKEY but keep their original |
747 | ** to keep space for removed entries, which may still be part of | 749 | ** gcvalue. This distinguishes them from regular keys but allows them to |
748 | ** chains. Note that the 'keytt' does not have the BIT_ISCOLLECTABLE | 750 | ** be found when searched in a special way. ('next' needs that to find |
749 | ** set, so these values are considered not collectable and are different | 751 | ** keys removed from a table during a traversal.) |
750 | ** from any valid value. | ||
751 | */ | 752 | */ |
752 | #define setdeadkey(n) (keytt(n) = LUA_TTABLE, gckey(n) = NULL) | 753 | #define setdeadkey(node) (keytt(node) = LUA_TDEADKEY) |
754 | #define keyisdead(node) (keytt(node) == LUA_TDEADKEY) | ||
753 | 755 | ||
754 | /* }================================================================== */ | 756 | /* }================================================================== */ |
755 | 757 | ||
@@ -172,11 +172,17 @@ static Node *mainpositionTV (const Table *t, const TValue *key) { | |||
172 | ** be equal to floats. It is assumed that 'eqshrstr' is simply | 172 | ** be equal to floats. It is assumed that 'eqshrstr' is simply |
173 | ** pointer equality, so that short strings are handled in the | 173 | ** pointer equality, so that short strings are handled in the |
174 | ** default case. | 174 | ** default case. |
175 | */ | 175 | ** A true 'deadok' means to accept dead keys as equal to their original |
176 | static int equalkey (const TValue *k1, const Node *n2) { | 176 | ** values, which can only happen if the original key was collectable. |
177 | if (rawtt(k1) != keytt(n2)) /* not the same variants? */ | 177 | ** All dead values are compared in the default case, by pointer |
178 | ** identity. (Note that dead long strings are also compared by | ||
179 | ** identity). | ||
180 | */ | ||
181 | static int equalkey (const TValue *k1, const Node *n2, int deadok) { | ||
182 | if ((rawtt(k1) != keytt(n2)) && /* not the same variants? */ | ||
183 | !(deadok && keyisdead(n2) && iscollectable(k1))) | ||
178 | return 0; /* cannot be same key */ | 184 | return 0; /* cannot be same key */ |
179 | switch (ttypetag(k1)) { | 185 | switch (keytt(n2)) { |
180 | case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: | 186 | case LUA_VNIL: case LUA_VFALSE: case LUA_VTRUE: |
181 | return 1; | 187 | return 1; |
182 | case LUA_VNUMINT: | 188 | case LUA_VNUMINT: |
@@ -187,7 +193,7 @@ static int equalkey (const TValue *k1, const Node *n2) { | |||
187 | return pvalue(k1) == pvalueraw(keyval(n2)); | 193 | return pvalue(k1) == pvalueraw(keyval(n2)); |
188 | case LUA_VLCF: | 194 | case LUA_VLCF: |
189 | return fvalue(k1) == fvalueraw(keyval(n2)); | 195 | return fvalue(k1) == fvalueraw(keyval(n2)); |
190 | case LUA_VLNGSTR: | 196 | case ctb(LUA_VLNGSTR): |
191 | return luaS_eqlngstr(tsvalue(k1), keystrval(n2)); | 197 | return luaS_eqlngstr(tsvalue(k1), keystrval(n2)); |
192 | default: | 198 | default: |
193 | return gcvalue(k1) == gcvalueraw(keyval(n2)); | 199 | return gcvalue(k1) == gcvalueraw(keyval(n2)); |
@@ -251,11 +257,12 @@ static unsigned int setlimittosize (Table *t) { | |||
251 | /* | 257 | /* |
252 | ** "Generic" get version. (Not that generic: not valid for integers, | 258 | ** "Generic" get version. (Not that generic: not valid for integers, |
253 | ** which may be in array part, nor for floats with integral values.) | 259 | ** which may be in array part, nor for floats with integral values.) |
260 | ** See explanation about 'deadok' in function 'equalkey'. | ||
254 | */ | 261 | */ |
255 | static const TValue *getgeneric (Table *t, const TValue *key) { | 262 | static const TValue *getgeneric (Table *t, const TValue *key, int deadok) { |
256 | Node *n = mainpositionTV(t, key); | 263 | Node *n = mainpositionTV(t, key); |
257 | for (;;) { /* check whether 'key' is somewhere in the chain */ | 264 | for (;;) { /* check whether 'key' is somewhere in the chain */ |
258 | if (equalkey(key, n)) | 265 | if (equalkey(key, n, deadok)) |
259 | return gval(n); /* that's it */ | 266 | return gval(n); /* that's it */ |
260 | else { | 267 | else { |
261 | int nx = gnext(n); | 268 | int nx = gnext(n); |
@@ -292,7 +299,7 @@ static unsigned int findindex (lua_State *L, Table *t, TValue *key, | |||
292 | if (i - 1u < asize) /* is 'key' inside array part? */ | 299 | if (i - 1u < asize) /* is 'key' inside array part? */ |
293 | return i; /* yes; that's the index */ | 300 | return i; /* yes; that's the index */ |
294 | else { | 301 | else { |
295 | const TValue *n = getgeneric(t, key); | 302 | const TValue *n = getgeneric(t, key, 1); |
296 | if (unlikely(isabstkey(n))) | 303 | if (unlikely(isabstkey(n))) |
297 | luaG_runerror(L, "invalid key to 'next'"); /* key not found */ | 304 | luaG_runerror(L, "invalid key to 'next'"); /* key not found */ |
298 | i = cast_int(nodefromval(n) - gnode(t, 0)); /* key index in hash table */ | 305 | i = cast_int(nodefromval(n) - gnode(t, 0)); /* key index in hash table */ |
@@ -730,7 +737,7 @@ const TValue *luaH_getstr (Table *t, TString *key) { | |||
730 | else { /* for long strings, use generic case */ | 737 | else { /* for long strings, use generic case */ |
731 | TValue ko; | 738 | TValue ko; |
732 | setsvalue(cast(lua_State *, NULL), &ko, key); | 739 | setsvalue(cast(lua_State *, NULL), &ko, key); |
733 | return getgeneric(t, &ko); | 740 | return getgeneric(t, &ko, 0); |
734 | } | 741 | } |
735 | } | 742 | } |
736 | 743 | ||
@@ -750,7 +757,7 @@ const TValue *luaH_get (Table *t, const TValue *key) { | |||
750 | /* else... */ | 757 | /* else... */ |
751 | } /* FALLTHROUGH */ | 758 | } /* FALLTHROUGH */ |
752 | default: | 759 | default: |
753 | return getgeneric(t, key); | 760 | return getgeneric(t, key, 0); |
754 | } | 761 | } |
755 | } | 762 | } |
756 | 763 | ||
diff --git a/testes/nextvar.lua b/testes/nextvar.lua index a16d557b..29cb05d5 100644 --- a/testes/nextvar.lua +++ b/testes/nextvar.lua | |||
@@ -359,6 +359,38 @@ end | |||
359 | assert(n == 5) | 359 | assert(n == 5) |
360 | 360 | ||
361 | 361 | ||
362 | do | ||
363 | print("testing next x GC of deleted keys") | ||
364 | -- bug in 5.4.1 | ||
365 | local co = coroutine.wrap(function (t) | ||
366 | for k, v in pairs(t) do | ||
367 | local k1 = next(t) -- all previous keys were deleted | ||
368 | assert(k == k1) -- current key is the first in the table | ||
369 | t[k] = nil | ||
370 | local expected = (type(k) == "table" and k[1] or | ||
371 | type(k) == "function" and k() or | ||
372 | string.sub(k, 1, 1)) | ||
373 | assert(expected == v) | ||
374 | coroutine.yield(v) | ||
375 | end | ||
376 | end) | ||
377 | local t = {} | ||
378 | t[{1}] = 1 -- add several unanchored, collectable keys | ||
379 | t[{2}] = 2 | ||
380 | t[string.rep("a", 50)] = "a" -- long string | ||
381 | t[string.rep("b", 50)] = "b" | ||
382 | t[{3}] = 3 | ||
383 | t[string.rep("c", 10)] = "c" -- short string | ||
384 | t[function () return 10 end] = 10 | ||
385 | local count = 7 | ||
386 | while co(t) do | ||
387 | collectgarbage("collect") -- collect dead keys | ||
388 | count = count - 1 | ||
389 | end | ||
390 | assert(count == 0 and next(t) == nil) -- traversed the whole table | ||
391 | end | ||
392 | |||
393 | |||
362 | local function test (a) | 394 | local function test (a) |
363 | assert(not pcall(table.insert, a, 2, 20)); | 395 | assert(not pcall(table.insert, a, 2, 20)); |
364 | table.insert(a, 10); table.insert(a, 2, 20); | 396 | table.insert(a, 10); table.insert(a, 2, 20); |