| --[=[ |
| ** lua.stx / llex.c |
| Tue Dec 2 10:45:48 EDT 1997 |
| >> BUG: "lastline" was not reset on function entry, so debug information |
| >> started only in the 2nd line of a function. |
| |
| |
| |
| ================================================================= |
| --- Version 3.1 alpha |
| |
| ** lua.c |
| Thu Jan 15 14:34:58 EDT 1998 |
| >> must include "stdlib.h" (for "exit()"). |
| |
| ** lbuiltin.c / lobject.h |
| Thu Jan 15 14:34:58 EDT 1998 |
| >> MAX_WORD may be bigger than MAX_INT |
| (by lhf) |
| |
| ** llex.c |
| Mon Jan 19 18:17:18 EDT 1998 |
| >> wrong line number (+1) in error report when file starts with "#..." |
| |
| ** lstrlib.c |
| Tue Jan 27 15:27:49 EDT 1998 |
| >> formats like "%020d" were considered too big (3 digits); moreover, |
| >> some sistems limit printf to at most 500 chars, so we can limit sizes |
| >> to 2 digits (99). |
| |
| ** lapi.c |
| Tue Jan 27 17:12:36 EDT 1998 |
| >> "lua_getstring" may create a new string, so should check GC |
| |
| ** lstring.c / ltable.c |
| Wed Jan 28 14:48:12 EDT 1998 |
| >> tables can become full of "empty" slots, and keep growing without limits. |
| |
| ** lstrlib.c |
| Mon Mar 9 15:26:09 EST 1998 |
| >> gsub('a', '(b?)%1*' ...) loops (because the capture is empty). |
| |
| ** lstrlib.c |
| Mon May 18 19:20:00 EST 1998 |
| >> arguments for "format" 'x', 'X', 'o' and 'u' must be unsigned int. |
| |
| |
| |
| ================================================================= |
| --- Version 3.1 |
| |
| ** liolib.c / lauxlib.c |
| Mon Sep 7 15:57:02 EST 1998 |
| >> function "luaL_argerror" prints wrong argument number (from a user's point |
| of view) when functions have upvalues. |
| |
| ** lstrlib.c |
| Tue Nov 10 17:29:36 EDT 1998 |
| >> gsub/strfind do not check whether captures are properly finished. |
| (by roberto/tomas) |
| |
| ** lbuiltin.c |
| Fri Dec 18 11:22:55 EDT 1998 |
| >> "tonumber" goes crazy with negative numbers in other bases (not 10), |
| because "strtol" returns long, not unsigned long. |
| (by Visual C++) |
| |
| ** lstrlib.c |
| Mon Jan 4 10:41:40 EDT 1999 |
| >> "format" does not check size of format item (such as "%00000...00000d"). |
| |
| ** lapi.c |
| Wed Feb 3 14:40:21 EDT 1999 |
| >> getlocal cannot return the local itself, since lua_isstring and |
| lua_isnumber can modify it. |
| |
| ** lstrlib.c |
| Thu Feb 4 17:08:50 EDT 1999 |
| >> format "%s" may break limit of "sprintf" on some machines. |
| (by Marcelo Sales) |
| |
| ** lzio.c |
| Thu Mar 4 11:49:37 EST 1999 |
| >> file stream cannot call fread after EOF. |
| (by lhf) |
| |
| |
| |
| ================================================================= |
| --- Version 3.2 (beta) |
| |
| ** lstrlib.c |
| Fri Apr 30 11:10:20 EST 1999 |
| >> '$' at end of pattern was matching regular '$', too. |
| (by anna; since 2.5) |
| |
| ** lbuiltin.c |
| Fri May 21 17:15:11 EST 1999 |
| >> foreach, foreachi, foreachvar points to function in stack when stack |
| can be reallocated. |
| (by tomas; since 3.2 beta) |
| |
| ** lparser.c |
| Wed Jun 16 10:32:46 EST 1999 |
| >> cannot assign to unlimited variables, because it causes overflow in |
| the number of returns of a function. |
| (since 3.1) |
| |
| |
| |
| ================================================================= |
| --- Version 3.2 |
| |
| ** lmathlib.c |
| Wed Aug 18 11:28:38 EST 1999 |
| >> random(0) and random(x,0) are wrong (0 is read as no argument!). |
| (by Dave Bollinger; since 3.1) |
| |
| ** lparser.c |
| Thu Sep 2 10:07:20 EST 1999 |
| >> in the (old) expression << ls->fs->f->consts[checkname(ls)] >>, checkname |
| could realloc f->consts. |
| (by Supratik Champati; since 3.2 beta) |
| |
| ** lobject.c / lbuiltin.c |
| Wed Sep 8 17:41:54 EST 1999 |
| >> tonumber'e1' and tonumber(' ', x), for x!=10, gave 0 instead of nil. |
| (since 3.1) |
| |
| ** lstrlib.c |
| Thu Nov 11 14:36:30 EDT 1999 |
| >> `strfind' does not handle \0 in plain search. |
| (by Jon Kleiser; since 3.1) |
| |
| ** lparser.c |
| Wed Dec 29 16:05:43 EDT 1999 |
| >> return gives wrong line in debug information |
| (by lhf; since 3.2 [at least]) |
| |
| ** ldo.c |
| Thu Dec 30 16:39:33 EDT 1999 |
| >> cannot reopen stdin (for binary mode) |
| (by lhf & roberto; since 3.1) |
| |
| ** lapi.c |
| Thu Mar 2 09:41:53 EST 2000 |
| >> lua_settable should check stack space (it could call a T.M.) |
| (by lhf & celes; since 3.2; it was already fixed by fixed stack) |
| |
| ** lparser.c |
| Mon Apr 3 09:59:06 EST 2000 |
| >> '%' should be in expfollow |
| (by Edgar Toernig; since 3.1; it was already fixed) |
| |
| ** lbuiltin.c |
| Mon Apr 3 10:05:05 EST 2000 |
| >> tostring() without arguments gives seg. fault. |
| (by Edgar Toernig; since 3.0) |
| |
| |
| |
| ================================================================= |
| --- Version 4.0 alpha |
| |
| Tested with full test suites (as locked in Mon Apr 24 14:23:11 EST 2000) |
| in the following platforms: |
| * Linux - gcc, g++ |
| * AIX - gcc |
| * Solaris - gcc, cc |
| * IRIX - cc, cc-purify |
| * Windows - Visual C++ (.c e .cpp, warning level=4) |
| |
| |
| ** lstrlib.c |
| Tue May 2 15:27:58 EST 2000 |
| >> `strfind' gets wrong subject length when there is an offset |
| (by Jon Kleiser; since 4.0a) |
| |
| ** lparser.c |
| Fri May 12 15:11:12 EST 2000 |
| >> first element in a list constructor is not adjusted to one value |
| >> (e.g. «a = {gsub('a','a','')}») |
| (by Tomas; since 4.0a) |
| |
| ** lparser.c |
| Wed May 24 14:50:16 EST 2000 |
| >> record-constructor starting with an upvalue name gets an error |
| >> (e.g. «local a; function f() x = {a=1} end») |
| (by Edgar Toernig; since 3.1) |
| |
| ** lparser.c |
| Tue Aug 29 15:56:05 EST 2000 |
| >> error message for `for' uses `while' |
| (since 4.0a; already corrected) |
| |
| ** lgc.c |
| Tue Aug 29 15:57:41 EST 2000 |
| >> gc tag method for nil could call line hook |
| (by ry; since ?) |
| |
| |
| |
| ================================================================= |
| --- Version 4.0 Beta |
| |
| ** liolib.c |
| Fri Sep 22 15:12:37 EST 2000 |
| >> `read("*w")' should return nil at EOF |
| (by roberto; since 4.0b) |
| |
| ** lvm.c |
| Mon Sep 25 11:47:48 EST 2000 |
| >> lua_gettable does not get key from stack top |
| (by Philip Yi; since 4.0b) |
| |
| ** lgc.c |
| Mon Sep 25 11:50:48 EST 2000 |
| >> GC may crash when checking locked C closures |
| (by Philip Yi; since 4.0b) |
| |
| ** lapi.c |
| Wed Sep 27 09:50:19 EST 2000 |
| >> lua_tag should return LUA_NOTAG for non-valid indices |
| (by Paul Hankin; since 4.0b) |
| |
| ** llex.h / llex.c / lparser.c |
| Wed Sep 27 13:39:45 EST 2000 |
| >> parser overwrites semantic information when looking ahead |
| >> (e.g. «a = {print'foo'}») |
| (by Edgar Toernig; since 4.0b, deriving from previous bug) |
| |
| ** liolib.c |
| Thu Oct 26 10:50:46 EDT 2000 |
| >> in function `read_file', realloc() doesn't free the buffer if it can't |
| >> allocate new memory |
| (by Mauro Vezzosi; since 4.0b) |
| |
| |
| |
| ================================================================= |
| --- Version 4.0 |
| |
| ** lparser.c |
| Wed Nov 29 09:51:44 EDT 2000 |
| >> parser does not accept a `;' after a `return' |
| (by lhf; since 4.0b) |
| |
| ** liolib.c |
| Fri Dec 22 15:30:42 EDT 2000 |
| >> when `read' fails it must return nil (and not no value) |
| (by cassino; since at least 3.1) |
| |
| ** lstring.c/lapi.c |
| Thu Feb 1 11:55:45 EDT 2001 |
| >> lua_pushuserdata(L, NULL) is buggy |
| (by Edgar Toernig; since 4.0) |
| |
| ** ldo.c |
| Fri Feb 2 14:06:40 EDT 2001 |
| >> «while 1 dostring[[print('hello\n')]] end» never reclaims memory |
| (by Andrew Paton; since 4.0b) |
| |
| ** lbaselib.c |
| Tue Feb 6 11:57:13 EDT 2001 |
| >> ESC (which starts precompiled code) in C is \33, not \27 |
| (by Edgar Toernig and lhf; since 4.0b) |
| |
| ** lparser.c |
| Tue Jul 10 16:59:18 EST 2001 |
| >> error message for `%a' gave wrong line number |
| (by Leonardo Constantino; since 4.0) |
| |
| ** lbaselib.c |
| Fri Dec 21 15:21:05 EDT 2001 |
| >> seg. fault when rawget/rawset get extra arguments |
| (by Eric Mauger; since 4.0b) |
| |
| ** lvm.c |
| Wed Jun 19 13:28:20 EST 2002 |
| >> line hook gets wrong `ar' |
| (by Daniel C. Sinclair; since 4.0.b) |
| |
| ** ldo.c |
| Wed Jun 19 13:31:49 EST 2002 |
| >> `protectedparser' may run GC, and then collect `filename' |
| >> (in function `parse_file') |
| (by Alex Bilyk; since 4.0) |
| |
| |
| |
| |
| ================================================================= |
| --- Version 5.0 alpha |
| |
| ** lgc.c |
| Fri Aug 30 13:49:14 EST 2002 |
| >> GC metamethod stored in a weak metatable being collected together with |
| >> userdata may not be cleared properly |
| (by Roberto; since 5.0a) |
| |
| ** lapi.c |
| Thu Nov 21 11:00:00 EST 2002 |
| >> ULONG_MAX>>10 may not fit into an int |
| (by Jeff Petkau; since 4.0) |
| |
| ** lparser.c |
| Fri Dec 6 17:06:40 UTC 2002 |
| >> scope of generic for variables is not sound |
| (by Gavin Wraith; since 5.0a) |
| |
| |
| |
| |
| ================================================================= |
| --- Version 5.0 beta |
| ** lbaselib.c |
| Fri Dec 20 09:53:19 UTC 2002 |
| >> `resume' was checking the wrong value for stack overflow |
| (by Maik Zimmermann; since 5.0b) |
| |
| ** ldo.c |
| Thu Jan 23 11:29:06 UTC 2003 |
| >> error during garbage collection in luaD_protectedparser is not being |
| >> protected |
| (by Benoit Germain; since 5.0a) |
| |
| ** ldo.c (and others) |
| Fri Feb 28 14:20:33 EST 2003 |
| >> GC metamethod calls could mess C/Lua stack syncronization |
| (by Roberto; since 5.0b) |
| |
| ** lzio.h/zlio.c |
| Thu Mar 20 11:40:12 EST 2003 |
| >> zio mixes a 255 as first char in a buffer with EOZ |
| (by lhf; since 5.0a) |
| |
| |
| |
| --]=] |
| ----------------------------------------------------------------- |
| -- Lua 5.0 (final) |
| |
| Bug{ |
| what = [[lua_closethread exists only in the manual]], |
| report = [[by Nguyen Binh, 28/04/2003]], |
| patch = [[no patch; the manual is wrong]], |
| } |
| |
| |
| Bug{ |
| what = [[attempt to resume a running coroutine crashes Lua]], |
| example = [[ |
| function co_func (current_co) |
| coroutine.resume(co) |
| end |
| co = coroutine.create(co_func) |
| coroutine.resume(co) |
| coroutine.resume(co) --> seg. fault |
| ]], |
| report = [[by Alex Bilyk, 09/05/2003]], |
| patch = [[ |
| * ldo.c: |
| 325,326c325 |
| < if (nargs >= L->top - L->base) |
| < luaG_runerror(L, "cannot resume dead coroutine"); |
| --- |
| > lua_assert(nargs < L->top - L->base); |
| 329c328,329 |
| < else if (ci->state & CI_YIELD) { /* inside a yield? */ |
| --- |
| > else { /* inside a yield */ |
| > lua_assert(ci->state & CI_YIELD); |
| 344,345d343 |
| < else |
| < luaG_runerror(L, "cannot resume non-suspended coroutine"); |
| 351a350,358 |
| > static int resume_error (lua_State *L, const char *msg) { |
| > L->top = L->ci->base; |
| > setsvalue2s(L->top, luaS_new(L, msg)); |
| > incr_top(L); |
| > lua_unlock(L); |
| > return LUA_ERRRUN; |
| > } |
| > |
| > |
| 355a363,368 |
| > if (L->ci == L->base_ci) { |
| > if (nargs >= L->top - L->base) |
| > return resume_error(L, "cannot resume dead coroutine"); |
| > } |
| > else if (!(L->ci->state & CI_YIELD)) /* not inside a yield? */ |
| > return resume_error(L, "cannot resume non-suspended coroutine"); |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[file:close cannot be called without a file. (results in seg fault)]], |
| example = [[ |
| > io.stdin.close() -- correct call shold be io.stdin:close() |
| ]], |
| report = [[by Tuomo Valkonen, 27/05/2003]], |
| patch = [[ |
| * liolib.c: |
| 161c161 |
| < if (lua_isnone(L, 1)) { |
| --- |
| > if (lua_isnone(L, 1) && lua_type(L, lua_upvalueindex(1)) == LUA_TTABLE) { |
| ]], --}} |
| } |
| |
| |
| Bug{ |
| what = [[C functions also may have stacks larger than current top]], |
| example = [[ |
| Must recompile lua with a change in lua.c and with lua_assert defined: |
| * lua.c: |
| 381a382 |
| > lua_checkstack(l, 1000); |
| ]], |
| report = [[Alex Bilyk, 09/06/2003]], |
| patch = [[ |
| * lgc.c: |
| 247c247 |
| < if (!(ci->state & CI_C) && lim < ci->top) |
| --- |
| > if (lim < ci->top) |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[`pc' address is invalidated when a coroutine is suspended]], |
| example = [[ |
| function g(x) |
| coroutine.yield(x) |
| end |
| |
| function f (i) |
| debug.sethook(print, "l") |
| for j=1,1000 do |
| g(i+j) |
| end |
| end |
| |
| co = coroutine.wrap(f) |
| co(10) |
| pcall(co) |
| pcall(co) |
| ]], |
| report = [[Nick Trout, 07/07/2003]], |
| patch = [[ |
| * lvm.c: |
| 402,403c402,403 |
| < L->ci->u.l.pc = &pc; |
| < if (L->hookmask & LUA_MASKCALL) |
| --- |
| > if (L->hookmask & LUA_MASKCALL) { |
| > L->ci->u.l.pc = &pc; |
| 404a405 |
| > } |
| 405a407 |
| > L->ci->u.l.pc = &pc; |
| 676,678c678 |
| < lua_assert(ci->u.l.pc == &pc && |
| < ttisfunction(ci->base - 1) && |
| < (ci->state & CI_SAVEDPC)); |
| --- |
| > lua_assert(ttisfunction(ci->base - 1) && (ci->state & CI_SAVEDPC)); |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[userdata to be collected still counts into new GC threshold, |
| increasing memory consumption]], |
| report = [[Roberto, 25/07/2003]], |
| example = [[ |
| a = newproxy(true) |
| getmetatable(a).__gc = function () end |
| for i=1,10000000 do |
| newproxy(a) |
| if math.mod(i, 10000) == 0 then print(gcinfo()) end |
| end |
| ]], |
| patch = [[ |
| * lgc.h: |
| 18c18 |
| < void luaC_separateudata (lua_State *L); |
| --- |
| > size_t luaC_separateudata (lua_State *L); |
| |
| * lgc.c: |
| 113c113,114 |
| < void luaC_separateudata (lua_State *L) { |
| --- |
| > size_t luaC_separateudata (lua_State *L) { |
| > size_t deadmem = 0; |
| 127a129 |
| > deadmem += sizeudata(gcotou(curr)->uv.len); |
| 136a139 |
| > return deadmem; |
| 390c393 |
| < static void checkSizes (lua_State *L) { |
| --- |
| > static void checkSizes (lua_State *L, size_t deadmem) { |
| 400c403 |
| < G(L)->GCthreshold = 2*G(L)->nblocks; /* new threshold */ |
| --- |
| > G(L)->GCthreshold = 2*G(L)->nblocks - deadmem; /* new threshold */ |
| 454c457,458 |
| < static void mark (lua_State *L) { |
| --- |
| > static size_t mark (lua_State *L) { |
| > size_t deadmem; |
| 467c471 |
| < luaC_separateudata(L); /* separate userdata to be preserved */ |
| --- |
| > deadmem = luaC_separateudata(L); /* separate userdata to be preserved */ |
| 475a480 |
| > return deadmem; |
| 480c485 |
| < mark(L); |
| --- |
| > size_t deadmem = mark(L); |
| 482c487 |
| < checkSizes(L); |
| --- |
| > checkSizes(L, deadmem); |
| ]] |
| } |
| |
| Bug{ |
| what=[[IBM AS400 (OS400) has sizeof(void *)==16, and a `%p' may generate |
| up to 60 characters in a `printf'. That causes a buffer overflow in |
| `tostring'.]], |
| |
| report = [[David Burgess, 25/08/2003]], |
| |
| example = [[print{}; (in an AS400 machine)]], |
| |
| patch = [[ |
| * liolib.c: |
| 178c178 |
| < char buff[32]; |
| --- |
| > char buff[128]; |
| |
| * lbaselib.c: |
| 327c327 |
| < char buff[64]; |
| --- |
| > char buff[128]; |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[syntax `local function' does not increment stack size]], |
| |
| report = [[Rici Lake, 26/09/2003]], |
| |
| example = [[ |
| -- must run this with precompiled code |
| local a,b,c |
| local function d () end |
| ]], |
| |
| patch = [[ |
| * lparser.c: |
| 1143a1144 |
| > FuncState *fs = ls->fs; |
| 1145c1146,1147 |
| < init_exp(&v, VLOCAL, ls->fs->freereg++); |
| --- |
| > init_exp(&v, VLOCAL, fs->freereg); |
| > luaK_reserveregs(fs, 1); |
| 1148c1150,1152 |
| < luaK_storevar(ls->fs, &v, &b); |
| --- |
| > luaK_storevar(fs, &v, &b); |
| > /* debug information will only see the variable after this point! */ |
| > getlocvar(fs, fs->nactvar - 1).startpc = fs->pc; |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| |
| what = [[count hook may be called without being set]], |
| |
| report = [[Andreas Stenius, 06/10/2003]], |
| |
| example = [[ |
| set your hooks with |
| |
| lua_sethook(L, my_hook, LUA_MASKLINE | LUA_MASKRET, 1); |
| |
| (It is weird to use a count > 0 without setting the count hook, |
| but it is not wrong.) |
| ]], |
| |
| patch = [[ |
| * lvm.c: |
| 69c69 |
| < if (mask > LUA_MASKLINE) { /* instruction-hook set? */ |
| --- |
| > if (mask & LUA_MASKCOUNT) { /* instruction-hook set? */ |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| |
| what = [[`dofile' eats one return value when called without arguments]], |
| |
| report = [[Frederico Abraham, 15/01/2004]], |
| |
| example = [[ |
| a,b = dofile() --< here you enter `return 1,2,3 <eof>' |
| print(a,b) --> 2 3 (should be 1 and 2) |
| ]], |
| |
| patch = [[ |
| * lbaselib.c: |
| 313a314 |
| > int n = lua_gettop(L); |
| 317c318 |
| < return lua_gettop(L) - 1; |
| --- |
| > return lua_gettop(L) - n; |
| ]], |
| |
| } |
| |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.0.2 |
| |
| Bug{ |
| what = [[string concatenation may cause arithmetic overflow, leading |
| to a buffer overflow]], |
| |
| report = [[Rici Lake, 20/05/2004]], |
| |
| example = [[ |
| longs = string.rep("\0", 2^25) |
| function catter(i) |
| return assert(loadstring( |
| string.format("return function(a) return a%s end", |
| string.rep("..a", i-1))))() |
| end |
| rep129 = catter(129) |
| rep129(longs) |
| ]], |
| |
| patch = [[ |
| * lvm.c: |
| @@ -321,15 +321,15 @@ |
| luaG_concaterror(L, top-2, top-1); |
| } else if (tsvalue(top-1)->tsv.len > 0) { /* if len=0, do nothing */ |
| /* at least two string values; get as many as possible */ |
| - lu_mem tl = cast(lu_mem, tsvalue(top-1)->tsv.len) + |
| - cast(lu_mem, tsvalue(top-2)->tsv.len); |
| + size_t tl = tsvalue(top-1)->tsv.len; |
| char *buffer; |
| int i; |
| - while (n < total && tostring(L, top-n-1)) { /* collect total length */ |
| - tl += tsvalue(top-n-1)->tsv.len; |
| - n++; |
| + /* collect total length */ |
| + for (n = 1; n < total && tostring(L, top-n-1); n++) { |
| + size_t l = tsvalue(top-n-1)->tsv.len; |
| + if (l >= MAX_SIZET - tl) luaG_runerror(L, "string length overflow"); |
| + tl += l; |
| } |
| - if (tl > MAX_SIZET) luaG_runerror(L, "string size overflow"); |
| buffer = luaZ_openspace(L, &G(L)->buff, tl); |
| tl = 0; |
| for (i=n; i>0; i--) { /* concat all strings */ |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[lua_getupvalue and setupvalue do not check for index too small]], |
| |
| report = [[Mike Pall, ?/2004]], |
| |
| example = [[debug.getupvalue(function() end, 0)]], |
| |
| patch = [[ |
| * lapi.c |
| 941c941 |
| < if (n > f->c.nupvalues) return NULL; |
| --- |
| > if (!(1 <= n && n <= f->c.nupvalues)) return NULL; |
| 947c947 |
| < if (n > p->sizeupvalues) return NULL; |
| --- |
| > if (!(1 <= n && n <= p->sizeupvalues)) return NULL; |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[values holded in open upvalues of suspended threads may be |
| incorrectly collected]], |
| |
| report = [[Spencer Schumann, 31/12/2004]], |
| |
| example = [[ |
| local thread_id = 0 |
| local threads = {} |
| |
| function fn(thread) |
| thread_id = thread_id + 1 |
| threads[thread_id] = function() |
| thread = nil |
| end |
| coroutine.yield() |
| end |
| |
| while true do |
| local thread = coroutine.create(fn) |
| coroutine.resume(thread, thread) |
| end |
| ]], |
| |
| patch = [[ |
| * lgc.c: |
| 221,224c221,222 |
| < if (!u->marked) { |
| < markobject(st, &u->value); |
| < u->marked = 1; |
| < } |
| --- |
| > markobject(st, u->v); |
| > u->marked = 1; |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[rawset/rawget do not ignore extra arguments]], |
| |
| report = [[Romulo Bahiense, 11/03/2005]], |
| |
| example = [[ |
| a = {} |
| rawset(a, 1, 2, 3) |
| print(a[1], a[2]) -- should be 2 and nil |
| ]], |
| |
| patch = [[ |
| * lbaselib.c: |
| 175a176 |
| > lua_settop(L, 2); |
| 183a185 |
| > lua_settop(L, 3); |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[weak tables that survive one collection are never collected]], |
| |
| report = [[Chromix, 02/01/2006]], |
| |
| example = [[ |
| a = {} |
| print(gcinfo()) |
| for i = 1, 10000 do |
| a[i] = setmetatable({}, {__mode = "v"}) |
| end |
| collectgarbage() |
| a = nil |
| collectgarbage() |
| print(gcinfo()) |
| ]], |
| |
| patch = [[ |
| * lgc.c |
| @@ -366,7 +366,7 @@ |
| GCObject *curr; |
| int count = 0; /* number of collected items */ |
| while ((curr = *p) != NULL) { |
| - if (curr->gch.marked > limit) { |
| + if ((curr->gch.marked & ~(KEYWEAK | VALUEWEAK)) > limit) { |
| unmark(curr); |
| p = &curr->gch.next; |
| } |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| what = [[Some "not not exp" may not result in boolean values]], |
| report = [[]], |
| since = [[4.0]], |
| example = [[ |
| -- should print false, but prints nil |
| print(not not (nil and 4)) |
| ]], |
| patch = [[]], |
| } |
| |
| |
| Bug{ |
| what = [[On some machines, closing a "piped file" (created with io.popen) |
| may crash Lua]], |
| report = [[]], |
| since = [[5.0]], |
| example = [[ |
| -- only on some machines |
| f = io.popen("ls") |
| f:close() |
| ]], |
| patch = [[]], |
| } |
| |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.1 |
| |
| Bug{ |
| what = [[In 16-bit machines, expressions and/or with numeric constants as the |
| right operand may result in weird values]], |
| |
| report = [[Andreas Stenius/Kein-Hong Man, 15/03/2006]], |
| |
| example = [[ |
| print(false or 0) -- on 16-bit machines |
| ]], |
| |
| patch = [[ |
| * lcode.c: |
| @@ -731,17 +731,15 @@ |
| case OPR_AND: { |
| lua_assert(e1->t == NO_JUMP); /* list must be closed */ |
| luaK_dischargevars(fs, e2); |
| - luaK_concat(fs, &e1->f, e2->f); |
| - e1->k = e2->k; e1->u.s.info = e2->u.s.info; |
| - e1->u.s.aux = e2->u.s.aux; e1->t = e2->t; |
| + luaK_concat(fs, &e2->f, e1->f); |
| + *e1 = *e2; |
| break; |
| } |
| case OPR_OR: { |
| lua_assert(e1->f == NO_JUMP); /* list must be closed */ |
| luaK_dischargevars(fs, e2); |
| - luaK_concat(fs, &e1->t, e2->t); |
| - e1->k = e2->k; e1->u.s.info = e2->u.s.info; |
| - e1->u.s.aux = e2->u.s.aux; e1->f = e2->f; |
| + luaK_concat(fs, &e2->t, e1->t); |
| + *e1 = *e2; |
| break; |
| } |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| what = [[luaL_checkudata may produce wrong error message]], |
| |
| report = [[Greg Falcon, 21/03/2006]], |
| |
| example = [[ |
| getmetatable(io.stdin).__gc() |
| --> bad argument #1 to '__gc' (FILE* expected, got table) |
| ]], |
| |
| patch = [[ |
| * lauxlib.c: |
| @@ -123,11 +123,17 @@ |
| |
| LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { |
| void *p = lua_touserdata(L, ud); |
| - lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ |
| - if (p == NULL || !lua_getmetatable(L, ud) || !lua_rawequal(L, -1, -2)) |
| - luaL_typerror(L, ud, tname); |
| - lua_pop(L, 2); /* remove both metatables */ |
| - return p; |
| + if (p != NULL) { /* value is a userdata? */ |
| + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ |
| + lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ |
| + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ |
| + lua_pop(L, 2); /* remove both metatables */ |
| + return p; |
| + } |
| + } |
| + } |
| + luaL_typerror(L, ud, tname); /* else error */ |
| + return NULL; /* to avoid warnings */ |
| } |
| ]] |
| |
| } |
| |
| |
| Bug{ |
| what = [[ |
| In Windows, |
| when Lua is used in an application that also uses DirectX, |
| it may present an erractic behavior. |
| THIS IS NOT A LUA BUG! |
| The problem is that DirectX violates an ABI that Lua depends on.]], |
| |
| patch = [[ |
| The simplest solution is to use DirectX with |
| the D3DCREATE_FPU_PRESERVE flag. |
| |
| Otherwise, you can change the definition of lua_number2int, |
| in luaconf.h, to this one: |
| #define lua_number2int(i,d) __asm fld d __asm fistp i |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| what = [[option '%q' in string.format does not handle '\r' correctly.]], |
| |
| example = [[ |
| local s = "a string with \r and \n and \r\n and \n\r" |
| local c = string.format("return %q", s) |
| assert(assert(loadstring(c))() == s) |
| ]], |
| |
| patch = [[ |
| * lstrlib.c: |
| @@ -703,6 +703,10 @@ |
| luaL_addchar(b, *s); |
| break; |
| } |
| + case '\r': { |
| + luaL_addlstring(b, "\\r", 2); |
| + break; |
| + } |
| case '\0': { |
| luaL_addlstring(b, "\\000", 4); |
| break; |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| what = [[lua_dostring/lua_dofile should return any values returned |
| by the chunk]], |
| |
| patch = [[ |
| * lauxlib.h: |
| @@ -108,9 +108,11 @@ |
| |
| #define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) |
| |
| -#define luaL_dofile(L, fn) (luaL_loadfile(L, fn) || lua_pcall(L, 0, 0, 0)) |
| +#define luaL_dofile(L, fn) \ |
| + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) |
| |
| -#define luaL_dostring(L, s) (luaL_loadstring(L, s) || lua_pcall(L, 0, 0, 0))+#define luaL_dostring(L, s) \ |
| + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) |
| |
| #define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| |
| what = [[garbage collector does not compensate enough for finalizers]], |
| |
| patch = [[ |
| lgc.c: |
| @@ -322,4 +322,6 @@ |
| |
| -static void propagateall (global_State *g) { |
| - while (g->gray) propagatemark(g); |
| +static size_t propagateall (global_State *g) { |
| + size_t m = 0; |
| + while (g->gray) m += propagatemark(g); |
| + return m; |
| } |
| @@ -542,3 +544,3 @@ |
| marktmu(g); /* mark `preserved' userdata */ |
| - propagateall(g); /* remark, to propagate `preserveness' */ |
| + udsize += propagateall(g); /* remark, to propagate `preserveness' */ |
| cleartable(g->weak); /* remove collected objects from weak tables */ |
| @@ -592,2 +594,4 @@ |
| GCTM(L); |
| + if (g->estimate > GCFINALIZECOST) |
| + g->estimate -= GCFINALIZECOST; |
| ]] |
| } |
| |
| |
| Bug{ |
| |
| what = [[debug hooks may get wrong when mixed with coroutines]], |
| |
| report = [[by Ivko Stanilov, 03/06/2006]], |
| |
| example = [[ |
| co = coroutine.create(function (a,b) |
| coroutine.yield(a, b) |
| return b, "end" |
| end) |
| |
| debug.sethook(co, function() end, "lcr") |
| coroutine.resume(co, 100, 2000) |
| coroutine.resume(co, 100, 2000) |
| ]], |
| |
| patch = [[ |
| * ldo.c: |
| @@ -389,6 +389,7 @@ |
| return; |
| } |
| else { /* resuming from previous yield */ |
| + L->status = 0; |
| if (!f_isLua(ci)) { /* `common' yield? */ |
| /* finish interrupted execution of `OP_CALL' */ |
| lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL || |
| @@ -399,7 +400,6 @@ |
| else /* yielded inside a hook: just continue its execution */ |
| L->base = L->ci->base; |
| } |
| - L->status = 0; |
| luaV_execute(L, cast_int(L->ci - L->base_ci)); |
| } |
| ]], |
| |
| } |
| |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.1.1 |
| |
| Bug{ |
| what = [[list constructors have wrong limit]], |
| |
| report = [[by Norman Ramsey, June 2006]], |
| |
| since = "5.1", |
| |
| example = [[ |
| a = {} |
| a[1] = "x={1" |
| for i = 2, 2^20 do |
| a[i] = 1 |
| end |
| a[#a + 1] = "}" |
| s = table.concat(a, ",") |
| assert(loadstring(s))() |
| print(#x) |
| ]], |
| |
| patch = [[ |
| * lparser.c: |
| @@ -489,7 +489,7 @@ |
| |
| static void listfield (LexState *ls, struct ConsControl *cc) { |
| expr(ls, &cc->v); |
| - luaY_checklimit(ls->fs, cc->na, MAXARG_Bx, "items in a constructor"); |
| + luaY_checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor"); |
| cc->na++; |
| cc->tostore++; |
| } |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| what = [[wrong message error in some cases involving closures]], |
| |
| report = [[Shmuel Zeigerman, on 07/2006]], |
| |
| since = "5.1", |
| |
| example = [[ |
| local Var |
| local function main() |
| NoSuchName (function() Var=0 end) |
| end |
| main() |
| --> lua5.1: temp:3: attempt to call upvalue 'Var' (a nil value) |
| ]], |
| |
| patch = [[ |
| *ldebug.c: |
| @@ -435,14 +435,16 @@ |
| break; |
| } |
| case OP_CLOSURE: { |
| - int nup; |
| + int nup, j; |
| check(b < pt->sizep); |
| nup = pt->p[b]->nups; |
| check(pc + nup < pt->sizecode); |
| - for (; nup>0; nup--) { |
| - OpCode op1 = GET_OPCODE(pt->code[pc+nup]); |
| + for (j = 1; j <= nup; j++) { |
| + OpCode op1 = GET_OPCODE(pt->code[pc + j]); |
| check(op1 == OP_GETUPVAL || op1 == OP_MOVE); |
| } |
| + if (reg != NO_REG) /* tracing? */ |
| + pc += nup; /* do not 'execute' these pseudo-instructions */ |
| break; |
| } |
| case OP_VARARG: { |
| ]], |
| |
| } |
| |
| |
| Bug{ |
| what = [[string.format("%") may read past the string]], |
| report = [[Roberto, on 09/2006]], |
| since = [[5.0]], |
| example = [[print(string.format("%"))]], |
| patch = [[ |
| *lstrlib.c: |
| @@ -723,7 +723,7 @@ |
| |
| static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { const char *p = strfrmt; |
| - while (strchr(FLAGS, *p)) p++; /* skip flags */ |
| + while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ |
| if ((size_t)(p - strfrmt) >= sizeof(FLAGS)) |
| luaL_error(L, "invalid format (repeated flags)"); |
| if (isdigit(uchar(*p))) p++; /* skip width */ |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[os.date throws an error when result is the empty string]], |
| report = [[]], |
| since = [[4.0]], |
| example = [[print(os.date(""))]], |
| patch = [[ |
| *loslib.c: |
| @@ -148,7 +148,18 @@ |
| else { |
| - char b[256]; |
| - if (strftime(b, sizeof(b), s, stm)) |
| - lua_pushstring(L, b); |
| - else |
| - return luaL_error(L, LUA_QL("date") " format too long"); |
| + char cc[3]; |
| + luaL_Buffer b; |
| + cc[0] = '%'; cc[2] = '\0'; |
| + luaL_buffinit(L, &b); |
| + for (; *s; s++) { |
| + if (*s != '%' || *(s + 1) == '\0') /* no conversion specifier? */ |
| + luaL_addchar(&b, *s); |
| + else { |
| + size_t reslen; |
| + char buff[200]; /* should be big enough for any conversion result */ |
| + cc[1] = *(++s); |
| + reslen = strftime(buff, sizeof(buff), cc, stm); |
| + luaL_addlstring(&b, buff, reslen); |
| + } |
| + } |
| + luaL_pushresult(&b); |
| } |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[setfenv accepts invalid 1st argument]], |
| report = [[Doug Rogers, on 02/2007]], |
| since = [[5.0]], |
| example = [[setfenv(nil, {}) -- should throw an error]], |
| patch = [[ |
| *lbaselib.c: |
| @@ -116,3 +116,3 @@ |
| |
| -static void getfunc (lua_State *L) { |
| +static void getfunc (lua_State *L, int opt) { |
| if (lua_isfunction(L, 1)) lua_pushvalue(L, 1); |
| @@ -120,3 +120,3 @@ |
| lua_Debug ar; |
| - int level = luaL_optint(L, 1, 1); |
| + int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1); |
| luaL_argcheck(L, level >= 0, 1, "level must be non-negative"); |
| @@ -133,3 +133,3 @@ |
| static int luaB_getfenv (lua_State *L) { |
| - getfunc(L); |
| + getfunc(L, 1); |
| if (lua_iscfunction(L, -1)) /* is a C function? */ |
| @@ -144,3 +144,3 @@ |
| luaL_checktype(L, 2, LUA_TTABLE); |
| - getfunc(L); |
| + getfunc(L, 0); |
| lua_pushvalue(L, 2); |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[wrong code for arithmetic expressions in some specific scenarios]], |
| report = [[Thierry Grellier, on 01/2007]], |
| since = [[5.1]], |
| example = [[ |
| -- use a large number of names (almost 256) |
| v1=1; v2=1; v3=1; v4=1; v5=1; v6=1; v7=1; v8=1; v9=1; |
| v10=1; v11=1; v12=1; v13=1; v14=1; v15=1; v16=1; v17=1; |
| v18=1; v19=1; v20=1; v21=1; v22=1; v23=1; v24=1; v25=1; |
| v26=1; v27=1; v28=1; v29=1; v30=1; v31=1; v32=1; v33=1; |
| v34=1; v35=1; v36=1; v37=1; v38=1; v39=1; v40=1; v41=1; |
| v42=1; v43=1; v44=1; v45=1; v46=1; v47=1; v48=1; v49=1; |
| v50=1; v51=1; v52=1; v53=1; v54=1; v55=1; v56=1; v57=1; |
| v58=1; v59=1; v60=1; v61=1; v62=1; v63=1; v64=1; v65=1; |
| v66=1; v67=1; v68=1; v69=1; v70=1; v71=1; v72=1; v73=1; |
| v74=1; v75=1; v76=1; v77=1; v78=1; v79=1; v80=1; v81=1; |
| v82=1; v83=1; v84=1; v85=1; v86=1; v87=1; v88=1; v89=1; |
| v90=1; v91=1; v92=1; v93=1; v94=1; v95=1; v96=1; v97=1; |
| v98=1; v99=1; v100=1; v101=1; v102=1; v103=1; v104=1; v105=1; |
| v106=1; v107=1; v108=1; v109=1; v110=1; v111=1; v112=1; v113=1; |
| v114=1; v115=1; v116=1; v117=1; v118=1; v119=1; v120=1; v121=1; |
| v122=1; v123=1; v124=1; v125=1; v126=1; v127=1; v128=1; v129=1; |
| v130=1; v131=1; v132=1; v133=1; v134=1; v135=1; v136=1; v137=1; |
| v138=1; v139=1; v140=1; v141=1; v142=1; v143=1; v144=1; v145=1; |
| v146=1; v147=1; v148=1; v149=1; v150=1; v151=1; v152=1; v153=1; |
| v154=1; v155=1; v156=1; v157=1; v158=1; v159=1; v160=1; v161=1; |
| v162=1; v163=1; v164=1; v165=1; v166=1; v167=1; v168=1; v169=1; |
| v170=1; v171=1; v172=1; v173=1; v174=1; v175=1; v176=1; v177=1; |
| v178=1; v179=1; v180=1; v181=1; v182=1; v183=1; v184=1; v185=1; |
| v186=1; v187=1; v188=1; v189=1; v190=1; v191=1; v192=1; v193=1; |
| v194=1; v195=1; v196=1; v197=1; v198=1; v199=1; v200=1; v201=1; |
| v202=1; v203=1; v204=1; v205=1; v206=1; v207=1; v208=1; v209=1; |
| v210=1; v211=1; v212=1; v213=1; v214=1; v215=1; v216=1; v217=1; |
| v218=1; v219=1; v220=1; v221=1; v222=1; v223=1; v224=1; v225=1; |
| v226=1; v227=1; v228=1; v229=1; v230=1; v231=1; v232=1; v233=1; |
| v234=1; v235=1; v236=1; v237=1; v238=1; v239=1; v240=1; v241=1; |
| v242=1; v243=1; v244=1; v245=1; v246=1; v247=1; v248=1; v249=1; |
| v250=1; |
| v251={k1 = 1}; |
| v252=1; |
| print(2 * v251.k1, v251.k1 * 2); -- 2 2, OK |
| v253=1; |
| print(2 * v251.k1, v251.k1 * 2); -- 1 2, ??? |
| ]], |
| patch = [[ |
| *lcode.c: |
| @@ -657,10 +657,16 @@ |
| if (constfolding(op, e1, e2)) |
| return; |
| else { |
| - int o1 = luaK_exp2RK(fs, e1); |
| int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0; |
| - freeexp(fs, e2); |
| - freeexp(fs, e1); |
| + int o1 = luaK_exp2RK(fs, e1); |
| + if (o1 > o2) { |
| + freeexp(fs, e1); |
| + freeexp(fs, e2); |
| + } |
| + else { |
| + freeexp(fs, e2); |
| + freeexp(fs, e1); |
| + } |
| e1->u.s.info = luaK_codeABC(fs, op, 0, o1, o2); |
| e1->k = VRELOCABLE; |
| } |
| @@ -718,10 +724,15 @@ |
| luaK_exp2nextreg(fs, v); /* operand must be on the `stack' */ |
| break; |
| } |
| - default: { |
| + case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: |
| + case OPR_MOD: case OPR_POW: { |
| if (!isnumeral(v)) luaK_exp2RK(fs, v); |
| break; |
| } |
| + default: { |
| + luaK_exp2RK(fs, v); |
| + break; |
| + } |
| } |
| } |
| ]], |
| } |
| |
| Bug{ |
| what = [[assignment of nil to parameter may be optimized away]], |
| report = [[Thomas Lauer, on 03/2007]], |
| since = [[5.1]], |
| example = [[ |
| function f (a) |
| a=nil |
| return a |
| end |
| |
| print(f("test")) |
| ]], |
| patch = [[ |
| *lcode.c: |
| @@ -35,16 +35,20 @@ |
| void luaK_nil (FuncState *fs, int from, int n) { |
| Instruction *previous; |
| if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ |
| - if (fs->pc == 0) /* function start? */ |
| - return; /* positions are already clean */ |
| - previous = &fs->f->code[fs->pc-1]; |
| - if (GET_OPCODE(*previous) == OP_LOADNIL) { |
| - int pfrom = GETARG_A(*previous); |
| - int pto = GETARG_B(*previous); |
| - if (pfrom <= from && from <= pto+1) { /* can connect both? */ |
| - if (from+n-1 > pto) |
| - SETARG_B(*previous, from+n-1); |
| - return; |
| + if (fs->pc == 0) { /* function start? */ |
| + if (from >= fs->nactvar) |
| + return; /* positions are already clean */ |
| + } |
| + else { |
| + previous = &fs->f->code[fs->pc-1]; |
| + if (GET_OPCODE(*previous) == OP_LOADNIL) { |
| + int pfrom = GETARG_A(*previous); |
| + int pto = GETARG_B(*previous); |
| + if (pfrom <= from && from <= pto+1) { /* can connect both? */ |
| + if (from+n-1 > pto) |
| + SETARG_B(*previous, from+n-1); |
| + return; |
| + } |
| } |
| } |
| } |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[__concat metamethod converts numbers to strings]], |
| report = [[Paul Winwood, on 12/2006]], |
| since = [[5.0]], |
| example = [[ |
| a = {} |
| setmetatable(a, {__concat = function (a,b) print(type(a), type(b)) end}) |
| a = 4 .. a |
| ]], |
| patch = [[ |
| *lvm.c: |
| @@ -281,10 +281,12 @@ |
| do { |
| StkId top = L->base + last + 1; |
| int n = 2; /* number of elements handled in this pass (at least 2) */ |
| - if (!tostring(L, top-2) || !tostring(L, top-1)) { |
| + if (!(ttisstring(top-2) || ttisnumber(top-2)) || !tostring(L, top-1)) { |
| if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT)) |
| luaG_concaterror(L, top-2, top-1); |
| - } else if (tsvalue(top-1)->len > 0) { /* if len=0, do nothing */ |
| + } else if (tsvalue(top-1)->len == 0) /* second op is empty? */ |
| + (void)tostring(L, top - 2); /* result is first op (as string) */ |
| + else { |
| /* at least two string values; get as many as possible */ |
| size_t tl = tsvalue(top-1)->len; |
| char *buffer; |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[As a library, loadlib.c should not access Lua internals |
| (via lobject.h)]], |
| report = [[Jérôme Vuarand, on 03/2007]], |
| since = [[5.0]], |
| example = [[the bug has no effect on external behavior]], |
| patch = [[remove the '#include "lobject.h" and use |
| 'lua_pushfstring' instead of 'luaO_pushfstring']], |
| } |
| |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.1.2 |
| |
| Bug{ |
| what = [[Lua may close standard files, |
| which then may be used by C]], |
| report = [[David Manura/Ross Berteig, on 04/2007]], |
| since = [[]], |
| example = [[ |
| io.close(io.stderr) |
| -- in some systems, following attempts to write to 'stderr' may crash |
| a = a + 1 |
| ]], |
| patch = [[ |
| ]], |
| } |
| |
| Bug{ |
| what = [[code generated for "-nil", "-true", and "-false" is wrong]], |
| report = [[David Manura/Rici Lake, on 04/2007]], |
| since = [[5.1]], |
| example = [[print(-nil)]], |
| patch = [[ |
| lcode.c: |
| @@ -699,7 +699,7 @@ |
| e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0; |
| switch (op) { |
| case OPR_MINUS: { |
| - if (e->k == VK) |
| + if (!isnumeral(e)) |
| luaK_exp2anyreg(fs, e); /* cannot operate on non-numeric constants */ |
| codearith(fs, OP_UNM, e, &e2); |
| break; |
| ]], |
| } |
| |
| Bug{ |
| what = [[Count hook may be called without being set.]], |
| report = [[Mike Pall, on 05/2007]], |
| since = [[?]], |
| example = [[]], |
| patch = [[ |
| lvm.c: |
| @@ -61,11 +61,9 @@ |
| lu_byte mask = L->hookmask; |
| const Instruction *oldpc = L->savedpc; |
| L->savedpc = pc; |
| - if (mask > LUA_MASKLINE) { /* instruction-hook set? */ |
| - if (L->hookcount == 0) { |
| - resethookcount(L); |
| - luaD_callhook(L, LUA_HOOKCOUNT, -1); |
| - } |
| + if ((mask & LUA_MASKCOUNT) && L->hookcount == 0) { |
| + resethookcount(L); |
| + luaD_callhook(L, LUA_HOOKCOUNT, -1); |
| } |
| if (mask & LUA_MASKLINE) { |
| Proto *p = ci_func(L->ci)->l.p; |
| ]], |
| } |
| |
| Bug{ |
| what = [[recursive coroutines may overflow C stack]], |
| report = [[ , on ]], |
| since = [[5.0]], |
| example = [[ |
| a = function(a) coroutine.wrap(a)(a) end |
| a(a) |
| ]], |
| patch = [[The 'nCcalls' counter should be shared by all threads. |
| (That is, it should be declared in the 'global_State' structure, |
| not in 'lua_State'.) |
| ]], |
| } |
| |
| Bug{ |
| what = [[wrong error message in some concatenations]], |
| report = [[Alex Davies, on 05/2007]], |
| since = [[5.1.2]], |
| example = [[a = nil; a = (1)..a]], |
| patch = [[ |
| ldebug.c: |
| @@ -563,8 +563,8 @@ |
| |
| |
| void luaG_concaterror (lua_State *L, StkId p1, StkId p2) { |
| - if (ttisstring(p1)) p1 = p2; |
| - lua_assert(!ttisstring(p1)); |
| + if (ttisstring(p1) || ttisnumber(p1)) p1 = p2; |
| + lua_assert(!ttisstring(p1) && !ttisnumber(p1)); |
| luaG_typeerror(L, p1, "concatenate"); |
| } |
| |
| ]], |
| } |
| |
| Bug{ |
| what = [[Very small numbers all collide in the hash function. |
| (This creates only performance problems; the behavoir is correct.)]], |
| report = [[, on ]], |
| since = [[5.0]], |
| example = [[]], |
| patch = [[ |
| ltable.c: |
| 87,88c87,88 |
| < n += 1; /* normalize number (avoid -0) */ |
| < lua_assert(sizeof(a) <= sizeof(n)); |
| --- |
| > if (luai_numeq(n, 0)) /* avoid problems with -0 */ |
| > return gnode(t, 0); |
| ]], |
| } |
| |
| Bug{ |
| what = [[Too many variables in an assignment may cause a |
| C stack overflow]], |
| report = [[Mike Pall, on 07/2007]], |
| since = [[5.0]], |
| example = [[ |
| $ ulimit -s 1024 # Reduce C stack to 1MB for quicker results |
| $ lua -e 'local s = "a,"; for i=1,18 do s = s..s end print(loadstring("local a;"..s.."a=nil", ""))' |
| ]], |
| patch = [[ |
| lparser.c: |
| @@ -938,6 +938,8 @@ |
| primaryexp(ls, &nv.v); |
| if (nv.v.k == VLOCAL) |
| check_conflict(ls, lh, &nv.v); |
| + luaY_checklimit(ls->fs, nvars, LUAI_MAXCCALLS - ls->L->nCcalls, |
| + "variable names"); |
| assignment(ls, &nv, nvars+1); |
| } |
| else { /* assignment -> `=' explist1 */ |
| ]], |
| } |
| |
| Bug{ |
| what = [[An error in a module loaded through the '-l' option |
| shows no traceback]], |
| report = [[David Manura, on 08/2007]], |
| since = [[5.1]], |
| example = [[lua -ltemp (assuming temp.lua has an error)]], |
| patch = [[ |
| lua.c: |
| @@ -144,7 +144,7 @@ |
| static int dolibrary (lua_State *L, const char *name) { |
| lua_getglobal(L, "require"); |
| lua_pushstring(L, name); |
| - return report(L, lua_pcall(L, 1, 0, 0)); |
| + return report(L, docall(L, 1, 1)); |
| } |
| ]], |
| } |
| |
| Bug{ |
| what = [['gsub' may go wild when wrongly called without its third |
| argument and with a large subject]], |
| report = [[Florian Berger, on 10/2007]], |
| since = [[5.1]], |
| example = [[ |
| x = string.rep('a', 10000) .. string.rep('b', 10000) |
| print(#string.gsub(x, 'b')) |
| ]], |
| patch = [[ |
| lstrlib.c: |
| @@ -631,6 +631,2 @@ |
| } |
| - default: { |
| - luaL_argerror(L, 3, "string/function/table expected"); |
| - return; |
| - } |
| } |
| @@ -650,2 +646,3 @@ |
| const char *p = luaL_checkstring(L, 2); |
| + int tr = lua_type(L, 3); |
| int max_s = luaL_optint(L, 4, srcl+1); |
| @@ -655,2 +652,5 @@ |
| luaL_Buffer b; |
| + luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || |
| + tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, |
| + "string/function/table expected"); |
| luaL_buffinit(L, &b); |
| ]], |
| } |
| |
| Bug{ |
| what = [[table.remove removes last element of a table when given |
| an out-of-bound index]], |
| report = [[Patrick Donnelly, on 11/2007]], |
| since = [[5.0]], |
| example = [[ |
| a = {1,2,3} |
| table.remove(a, 4) |
| print(a[3]) --> nil (should be 3) |
| ]], |
| patch = [[ |
| ltablib.c: |
| @@ -118,7 +118,8 @@ |
| static int tremove (lua_State *L) { |
| int e = aux_getn(L, 1); |
| int pos = luaL_optint(L, 2, e); |
| - if (e == 0) return 0; /* table is `empty' */ |
| + if (!(1 <= pos && pos <= e)) /* position is outside bounds? */ |
| + return 0; /* nothing to remove */ |
| luaL_setn(L, 1, e - 1); /* t.n = n-1 */ |
| lua_rawgeti(L, 1, pos); /* result = t[pos] */ |
| for ( ;pos<e; pos++) { |
| ]], |
| } |
| |
| Bug{ |
| what = [[lua_setfenv may crash if called over an invalid object]], |
| report = [[Mike Pall, on 11/2007]], |
| since = [[5.1]], |
| example = [[ |
| > debug.setfenv(3, {}) |
| ]], |
| patch = [[ |
| lapi.c: |
| @@ -749,7 +749,7 @@ |
| res = 0; |
| break; |
| } |
| - luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1)); |
| + if (res) luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1)); |
| L->top--; |
| lua_unlock(L); |
| return res; |
| ]], |
| } |
| |
| Bug{ |
| what = [[stand-alone interpreter shows incorrect error message |
| when the "message" is a coroutine]], |
| report = [[Patrick Donnelly, on 17/12/2007]], |
| since = [[5.1]], |
| example = [[> error(coroutine.create(function() end))]], |
| patch = [[ |
| lua.c: |
| @@ -74,6 +74,8 @@ |
| |
| |
| static int traceback (lua_State *L) { |
| + if (!lua_isstring(L, 1)) /* 'message' not a string? */ |
| + return 1; /* keep it intact */ |
| lua_getfield(L, LUA_GLOBALSINDEX, "debug"); |
| if (!lua_istable(L, -1)) { |
| lua_pop(L, 1); |
| |
| ]], |
| } |
| |
| Bug{ |
| what = [[debug.sethook/gethook may overflow the thread's stack]], |
| report = [[Ivko Stanilov, on 2008/01/04]], |
| since = [[5.1]], |
| example = [[ |
| a = coroutine.create(function() yield() end) |
| coroutine.resume(a) |
| debug.sethook(a) -- may overflow the stack of 'a' |
| ]], |
| patch = [[ |
| ldblib.c: |
| @@ -268,12 +268,11 @@ |
| count = luaL_optint(L, arg+3, 0); |
| func = hookf; mask = makemask(smask, count); |
| } |
| - gethooktable(L1); |
| - lua_pushlightuserdata(L1, L1); |
| + gethooktable(L); |
| + lua_pushlightuserdata(L, L1); |
| lua_pushvalue(L, arg+1); |
| - lua_xmove(L, L1, 1); |
| - lua_rawset(L1, -3); /* set new hook */ |
| - lua_pop(L1, 1); /* remove hook table */ |
| + lua_rawset(L, -3); /* set new hook */ |
| + lua_pop(L, 1); /* remove hook table */ |
| lua_sethook(L1, func, mask, count); /* set hooks */ |
| return 0; |
| } |
| @@ -288,11 +287,10 @@ |
| if (hook != NULL && hook != hookf) /* external hook? */ |
| lua_pushliteral(L, "external hook"); |
| else { |
| - gethooktable(L1); |
| - lua_pushlightuserdata(L1, L1); |
| - lua_rawget(L1, -2); /* get hook */ |
| - lua_remove(L1, -2); /* remove hook table */ |
| - lua_xmove(L1, L, 1); |
| + gethooktable(L); |
| + lua_pushlightuserdata(L, L1); |
| + lua_rawget(L, -2); /* get hook */ |
| + lua_remove(L, -2); /* remove hook table */ |
| } |
| lua_pushstring(L, unmakemask(mask, buff)); |
| lua_pushinteger(L, lua_gethookcount(L1)); |
| ]] |
| } |
| |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.1.3 |
| |
| Bug{ |
| what = [[LUAI_MAXCSTACK must be smaller than -LUA_REGISTRYINDEX]], |
| report = [[Patrick Donnelly, on 2008/02/11]], |
| since = [[5.1.3]], |
| example = [[ |
| j = 1e4 |
| co = coroutine.create(function() |
| t = {} |
| for i = 1, j do t[i] = i end |
| return unpack(t) |
| end) |
| print(coroutine.resume(co)) |
| ]], |
| patch = [[ |
| luaconf.h: |
| 443c443,444 |
| < ** functions to consume unlimited stack space. |
| --- |
| > ** functions to consume unlimited stack space. (must be smaller than |
| > ** -LUA_REGISTRYINDEX) |
| 445,446c446 |
| < #define LUAI_MCS_AUX ((int)(INT_MAX / (4*sizeof(LUA_NUMBER)))) |
| < #define LUAI_MAXCSTACK (LUAI_MCS_AUX > SHRT_MAX ? SHRT_MAX : LUAI_MCS_AUX) |
| --- |
| > #define LUAI_MAXCSTACK 8000 |
| ]], |
| } |
| |
| Bug{ |
| what = [[coroutine.resume pushes element without ensuring stack size]], |
| report = [[on 2008/02/11]], |
| since = [[5.0]], |
| example = [[(this bug cannot be detected without internal assertions)]], |
| patch = [[ |
| lbaselib.c: |
| @@ -526,7 +526,7 @@ |
| status = lua_resume(co, narg); |
| if (status == 0 || status == LUA_YIELD) { |
| int nres = lua_gettop(co); |
| - if (!lua_checkstack(L, nres)) |
| + if (!lua_checkstack(L, nres + 1)) |
| luaL_error(L, "too many results to resume"); |
| lua_xmove(co, L, nres); /* move yielded values */ |
| return nres; |
| ]], |
| } |
| |
| Bug{ |
| what = [[lua_checkstack may have arithmetic overflow for large 'size']], |
| report = [[Patrick Donnelly, on 2008/02/12]], |
| since = [[5.0]], |
| example = [[ |
| print(unpack({1,2,3}, 0, 2^31-3)) |
| ]], |
| patch = [[ |
| --- lapi.c 2008/01/03 15:20:39 2.55.1.3 |
| +++ lapi.c 2008/02/14 16:05:21 |
| @@ -93,15 +93,14 @@ |
| |
| |
| LUA_API int lua_checkstack (lua_State *L, int size) { |
| - int res; |
| + int res = 1; |
| lua_lock(L); |
| - if ((L->top - L->base + size) > LUAI_MAXCSTACK) |
| + if (size > LUAI_MAXCSTACK || (L->top - L->base + size) > LUAI_MAXCSTACK) |
| res = 0; /* stack overflow */ |
| - else { |
| + else if (size > 0) { |
| luaD_checkstack(L, size); |
| if (L->ci->top < L->top + size) |
| L->ci->top = L->top + size; |
| - res = 1; |
| } |
| lua_unlock(L); |
| return res; |
| ]], |
| } |
| |
| Bug{ |
| what = [[unpack with maximum indices may crash due to arithmetic overflow]], |
| report = [[Patrick Donnelly, on 2008/02/12]], |
| since = [[5.1]], |
| example = [[ |
| print(unpack({1,2,3}, 2^31-1, 2^31-1)) |
| ]], |
| patch = [[ |
| --- lbaselib.c 2008/02/11 16:24:24 1.191.1.5 |
| +++ lbaselib.c 2008/02/14 16:10:25 |
| @@ -344,10 +344,12 @@ |
| luaL_checktype(L, 1, LUA_TTABLE); |
| i = luaL_optint(L, 2, 1); |
| e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1)); |
| + if (i > e) return 0; /* empty range */ |
| n = e - i + 1; /* number of elements */ |
| - if (n <= 0) return 0; /* empty range */ |
| - luaL_checkstack(L, n, "table too big to unpack"); |
| - for (; i<=e; i++) /* push arg[i...e] */ |
| + if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */ |
| + return luaL_error(L, "too many results to unpack"); |
| + lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */ |
| + while (i++ < e) /* push arg[i + 1...e] */ |
| lua_rawgeti(L, 1, i); |
| return n; |
| } |
| ]], |
| } |
| |
| Bug{ |
| what = [[The validator for precompiled code has several flaws that |
| allow malicious binary code to crash the application]], |
| report = [[Peter Cawley, on 2008/03/24]], |
| since = [[5.0]], |
| example = [[ |
| a = string.dump(function()return;end) |
| a = a:gsub(string.char(30,37,122,128), string.char(34,0,0), 1) |
| loadstring(a)() |
| ]], |
| patch = [[ |
| --- ldebug.c 2007/12/28 15:32:23 2.29.1.3 |
| +++ ldebug.c 2008/04/04 15:15:40 |
| @@ -275,12 +275,12 @@ |
| |
| static int precheck (const Proto *pt) { |
| check(pt->maxstacksize <= MAXSTACK); |
| - lua_assert(pt->numparams+(pt->is_vararg & VARARG_HASARG) <= pt->maxstacksize); |
| - lua_assert(!(pt->is_vararg & VARARG_NEEDSARG) || |
| + check(pt->numparams+(pt->is_vararg & VARARG_HASARG) <= pt->maxstacksize); |
| + check(!(pt->is_vararg & VARARG_NEEDSARG) || |
| (pt->is_vararg & VARARG_HASARG)); |
| check(pt->sizeupvalues <= pt->nups); |
| check(pt->sizelineinfo == pt->sizecode || pt->sizelineinfo == 0); |
| - check(GET_OPCODE(pt->code[pt->sizecode-1]) == OP_RETURN); |
| + check(pt->sizecode > 0 && GET_OPCODE(pt->code[pt->sizecode-1]) == OP_RETURN); |
| return 1; |
| } |
| |
| @@ -363,7 +363,11 @@ |
| } |
| switch (op) { |
| case OP_LOADBOOL: { |
| - check(c == 0 || pc+2 < pt->sizecode); /* check its jump */ |
| + if (c == 1) { /* does it jump? */ |
| + check(pc+2 < pt->sizecode); /* check its jump */ |
| + check(GET_OPCODE(pt->code[pc+1]) != OP_SETLIST || |
| + GETARG_C(pt->code[pc+1]) != 0); |
| + } |
| break; |
| } |
| case OP_LOADNIL: { |
| @@ -428,7 +432,10 @@ |
| } |
| case OP_SETLIST: { |
| if (b > 0) checkreg(pt, a + b); |
| - if (c == 0) pc++; |
| + if (c == 0) { |
| + pc++; |
| + check(pc < pt->sizecode - 1); |
| + } |
| break; |
| } |
| case OP_CLOSURE: { |
| ]], |
| } |
| |
| Bug{ |
| what = [[maliciously crafted precompiled code can blow the C stack]], |
| report = [[Greg Falcon, on 2008/03/25]], |
| since = [[5.0]], |
| example = [[ |
| function crash(depth) |
| local init = '\27\76\117\97\81\0\1\4\4\4\8\0\7\0\0\0\61\115\116' .. |
| '\100\105\110\0\1\0\0\0\1\0\0\0\0\0\0\2\2\0\0\0\36' .. |
| '\0\0\0\30\0\128\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0' .. |
| '\1\0\0\0\0\0\0\2' |
| local mid = '\1\0\0\0\30\0\128\0\0\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\0' |
| local fin = '\0\0\0\0\0\0\0\2\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\2\0' .. |
| '\0\0\97\0\1\0\0\0\1\0\0\0\0\0\0\0' |
| local lch = '\2\0\0\0\36\0\0\0\30\0\128\0\0\0\0\0\1\0\0\0\0\0\0' .. |
| '\0\1\0\0\0\1\0\0\0\0\0\0\2' |
| local rch = '\0\0\0\0\0\0\0\2\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\2\0' .. |
| '\0\0\97\0\1\0\0\0\1' |
| for i=1,depth do lch,rch = lch..lch,rch..rch end |
| loadstring(init .. lch .. mid .. rch .. fin) |
| end |
| for i=1,25 do print(i); crash(i) end |
| ]], |
| patch = [[ |
| --- lundump.c 2008/04/04 16:00:45 2.7.1.3 |
| +++ lundump.c 2008/04/04 19:51:41 2.7.1.4 |
| @@ -161,7 +161,9 @@ |
| |
| static Proto* LoadFunction(LoadState* S, TString* p) |
| { |
| - Proto* f=luaF_newproto(S->L); |
| + Proto* f; |
| + if (++S->L->nCcalls > LUAI_MAXCCALLS) error(S,"code too deep"); |
| + f=luaF_newproto(S->L); |
| setptvalue2s(S->L,S->L->top,f); incr_top(S->L); |
| f->source=LoadString(S); if (f->source==NULL) f->source=p; |
| f->linedefined=LoadInt(S); |
| @@ -175,6 +177,7 @@ |
| LoadDebug(S,f); |
| IF (!luaG_checkcode(f), "bad code"); |
| S->L->top--; |
| + S->L->nCcalls--; |
| return f; |
| } |
| ]], |
| } |
| |
| Bug{ |
| what = [[code validator may reject (maliciously crafted) correct code]], |
| report = [[Greg Falcon, on 2008/03/26]], |
| since = [[5.0]], |
| example = [[ |
| z={} |
| for i=1,27290 do z[i]='1,' end |
| z = 'if 1+1==2 then local a={' .. table.concat(z) .. '} end' |
| func = loadstring(z) |
| print(loadstring(string.dump(func))) |
| ]], |
| patch = [[ |
| --- ldebug.c 2008/04/04 15:30:05 2.29.1.4 |
| +++ ldebug.c 2008/04/04 15:47:10 |
| @@ -346,9 +346,18 @@ |
| int dest = pc+1+b; |
| check(0 <= dest && dest < pt->sizecode); |
| if (dest > 0) { |
| - /* cannot jump to a setlist count */ |
| - Instruction d = pt->code[dest-1]; |
| - check(!(GET_OPCODE(d) == OP_SETLIST && GETARG_C(d) == 0)); |
| + int j; |
| + /* check that it does not jump to a setlist count; this |
| + is tricky, because the count from a previous setlist may |
| + have the same value of an invalid setlist; so, we must |
| + go all the way back to the first of them (if any) */ |
| + for (j = 0; j < dest; j++) { |
| + Instruction d = pt->code[dest-1-j]; |
| + if (!(GET_OPCODE(d) == OP_SETLIST && GETARG_C(d) == 0)) break; |
| + } |
| + /* if 'j' is even, previous value is not a setlist (even if |
| + it looks like one) */ |
| + check((j&1) == 0); |
| } |
| } |
| break; |
| ]], |
| } |
| |
| Bug{ |
| what = [[maliciously crafted precompiled code can inject invalid boolean |
| values into Lua code]], |
| report = [[Greg Falcon, on 2008/03/27]], |
| since = [[5.0]], |
| example = [[ |
| maybe = string.dump(function() return ({[true]=true})[true] end) |
| maybe = maybe:gsub('\1\1','\1\2') |
| maybe = loadstring(maybe)() |
| assert(type(maybe) == "boolean" and maybe ~= true and maybe ~= false) |
| ]], |
| patch = [[ |
| --- lundump.c 2008/01/18 16:39:11 2.7.1.2 |
| +++ lundump.c 2008/04/04 15:50:39 |
| @@ -115,7 +115,7 @@ |
| setnilvalue(o); |
| break; |
| case LUA_TBOOLEAN: |
| - setbvalue(o,LoadChar(S)); |
| + setbvalue(o,LoadChar(S)!=0); |
| break; |
| case LUA_TNUMBER: |
| setnvalue(o,LoadNumber(S)); |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [['string.byte' gets confused with some out-of-range negative indices]], |
| report = [[Mike Pall, on 2008/06/03]], |
| since = [[5.1]], |
| example = [[ |
| print(string.byte("abc", -5)) --> 97 98 99 (should print nothing) |
| ]], |
| patch = [[ |
| --- lstrlib.c 2007/12/28 15:32:23 1.132.1.3 |
| +++ lstrlib.c 2008/07/05 11:53:42 |
| @@ -35,7 +35,8 @@ |
| |
| static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) { |
| /* relative string position: negative means back from end */ |
| - return (pos>=0) ? pos : (ptrdiff_t)len+pos+1; |
| + if (pos < 0) pos += (ptrdiff_t)len + 1; |
| + return (pos >= 0) ? pos : 0; |
| } |
| |
| |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[user-requested GC step may loop forever]], |
| report = [[Makoto Hamanaka, on 2008/07/01]], |
| since = [[5.1]], |
| example = [[ |
| collectgarbage("setpause", 100) -- small value |
| collectgarbage("setstepmul", 2000) -- large value |
| collectgarbage("step",0) |
| ]], |
| patch = [[ |
| --- lapi.c 2008/02/14 16:46:39 2.55.1.4 |
| +++ lapi.c 2008/07/04 18:34:48 |
| @@ -929,10 +929,13 @@ |
| g->GCthreshold = g->totalbytes - a; |
| else |
| g->GCthreshold = 0; |
| - while (g->GCthreshold <= g->totalbytes) |
| + while (g->GCthreshold <= g->totalbytes) { |
| luaC_step(L); |
| - if (g->gcstate == GCSpause) /* end of cycle? */ |
| - res = 1; /* signal it */ |
| + if (g->gcstate == GCSpause) { /* end of cycle? */ |
| + res = 1; /* signal it */ |
| + break; |
| + } |
| + } |
| break; |
| } |
| case LUA_GCSETPAUSE: { |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [['module' may change the environment of a C function]], |
| report = [[Peter Cawley, on 2008/07/16]], |
| since = [[5.1]], |
| example = [[ |
| pcall(module, "xuxu") |
| assert(debug.getfenv(pcall) == xuxu) |
| ]], |
| patch = [[ |
| --- loadlib.c 2007/12/28 14:58:43 1.52.1.2 |
| +++ loadlib.c 2008/08/05 19:39:00 |
| @@ -506,8 +506,11 @@ |
| |
| static void setfenv (lua_State *L) { |
| lua_Debug ar; |
| - lua_getstack(L, 1, &ar); |
| - lua_getinfo(L, "f", &ar); |
| + if (lua_getstack(L, 1, &ar) == 0 || |
| + lua_getinfo(L, "f", &ar) == 0 || /* get calling function */ |
| + lua_iscfunction(L, -1)) |
| + luaL_error(L, "function " LUA_QL("module") |
| + " not called from a Lua function"); |
| lua_pushvalue(L, -2); |
| lua_setfenv(L, -2); |
| lua_pop(L, 1); |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[internal macro 'svalue' is wrong]], |
| report = [[Martijn van Buul, on 2008/08/04]], |
| since = [[5.1]], |
| example = [[ |
| /* in luaconf.h */ |
| #define LUAI_USER_ALIGNMENT_T union { char b[32]; } |
| ]], |
| patch = [[ |
| --- lobject.h 2007/12/27 13:02:25 2.20.1.1 |
| +++ lobject.h 2008/08/05 19:40:48 |
| @@ -210,3 +210,3 @@ |
| #define getstr(ts) cast(const char *, (ts) + 1) |
| -#define svalue(o) getstr(tsvalue(o)) |
| +#define svalue(o) getstr(rawtsvalue(o)) |
| |
| ]], |
| } |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.1.4 |
| |
| Bug{ |
| what = [[malicious zero-length string in binary code may segfault Lua]], |
| report = [[Peter Cawley, on 2008/09/01]], |
| since = [[5.1]], |
| example = [[ |
| loadstring(('').dump(function()X''end):gsub('\2%z%z%zX','\0\0\0'))() |
| ]], |
| patch = [[ |
| ]], |
| } |
| |
| |
| Bug{ |
| what = [[wrong code generation for some particular boolean expressions]], |
| report = [[Brian Kelley, on 2009/04/15]], |
| since = [[5.0]], |
| example = [[ |
| print(((1 or false) and true) or false) --> 1 |
| -- should be 'true' |
| ]], |
| patch = [[ |
| --- lcode.c 2007/12/28 15:32:23 2.25.1.3 |
| +++ lcode.c 2009/06/15 14:07:34 |
| @@ -544,15 +544,18 @@ |
| pc = NO_JUMP; /* always true; do nothing */ |
| break; |
| } |
| - case VFALSE: { |
| - pc = luaK_jump(fs); /* always jump */ |
| - break; |
| - } |
| case VJMP: { |
| invertjump(fs, e); |
| pc = e->u.s.info; |
| break; |
| } |
| + case VFALSE: { |
| + if (!hasjumps(e)) { |
| + pc = luaK_jump(fs); /* always jump */ |
| + break; |
| + } |
| + /* else go through */ |
| + } |
| default: { |
| pc = jumponcond(fs, e, 0); |
| break; |
| @@ -572,14 +575,17 @@ |
| pc = NO_JUMP; /* always false; do nothing */ |
| break; |
| } |
| - case VTRUE: { |
| - pc = luaK_jump(fs); /* always jump */ |
| - break; |
| - } |
| case VJMP: { |
| pc = e->u.s.info; |
| break; |
| } |
| + case VTRUE: { |
| + if (!hasjumps(e)) { |
| + pc = luaK_jump(fs); /* always jump */ |
| + break; |
| + } |
| + /* else go through */ |
| + } |
| default: { |
| pc = jumponcond(fs, e, 1); |
| break; |
| ]], |
| } |
| |
| Bug{ |
| what = [['luaV_settable' may invalidate a reference to a table and try |
| to reuse it]], |
| report = [[Mark Feldman, on 2009/06/27]], |
| since = [[5.0]], |
| example = [[ |
| grandparent = {} |
| grandparent.__newindex = function(s,_,_) print(s) end |
| |
| parent = {} |
| parent.__newindex = parent |
| setmetatable(parent, grandparent) |
| |
| child = setmetatable({}, parent) |
| child.foo = 10 --> (crash on some machines) |
| ]], |
| patch = [[ |
| --- lvm.c 2007/12/28 15:32:23 2.63.1.3 |
| +++ lvm.c 2009/07/01 20:36:59 |
| @@ -133,6 +133,7 @@ |
| |
| void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { |
| int loop; |
| + TValue temp; |
| for (loop = 0; loop < MAXTAGLOOP; loop++) { |
| const TValue *tm; |
| if (ttistable(t)) { /* `t' is a table? */ |
| @@ -152,7 +153,9 @@ |
| callTM(L, tm, t, key, val); |
| return; |
| } |
| - t = tm; /* else repeat with `tm' */ |
| + /* else repeat with `tm' */ |
| + setobj(L, &temp, tm); /* avoid pointing inside table (may rehash) */ |
| + t = &temp; |
| } |
| luaG_runerror(L, "loop in settable"); |
| } |
| ]], |
| } |
| |
| Bug{ |
| what = [[smart use of varargs may create functions that return too |
| many arguments and overflow the stack of C functions]], |
| report = [[Patrick Donnelly, on 2008/12/10]], |
| since = [[]], |
| example = [[ |
| local function lunpack(i, ...) |
| if i == 0 then return ... |
| else |
| return lunpack(i-1, 1, ...) |
| end |
| end |
| |
| Now, if C calls lunpack(n) with a huge n, it may end with |
| too many values in its stack and confuse its stack indices. |
| ]], |
| patch = [[ |
| ]], |
| } |
| |
| Bug{ |
| what = [['debug.getfenv' does not check whether it has an argument]], |
| report = [[Patrick Donnelly, 2009/07/30]], |
| since = [[5.1]], |
| example = [[debug.getfenv() -- should raise an error]], |
| patch = [[ |
| --- ldblib.c 2008/01/21 13:11:21 1.104.1.3 |
| +++ ldblib.c 2009/08/04 18:43:12 |
| @@ -45,6 +45,7 @@ |
| |
| |
| static int db_getfenv (lua_State *L) { |
| + luaL_checkany(L, 1); |
| lua_getfenv(L, 1); |
| return 1; |
| } |
| ]], |
| } |
| |
| Bug{ |
| what = [[GC may get stuck during a parser and avoids proper resizing of |
| the string table, |
| making its lists grow too much and degrading performance]], |
| report = [[Sean Conner, 2009/11/10]], |
| since = [[5.1]], |
| example = [[See http://lua-users.org/lists/lua-l/2009-11/msg00463.html]], |
| patch = [[ |
| --- llex.c 2007/12/27 13:02:25 2.20.1.1 |
| +++ llex.c 2009/11/23 14:49:40 |
| @@ -118,8 +118,10 @@ |
| lua_State *L = ls->L; |
| TString *ts = luaS_newlstr(L, str, l); |
| TValue *o = luaH_setstr(L, ls->fs->h, ts); /* entry for `str' */ |
| - if (ttisnil(o)) |
| + if (ttisnil(o)) { |
| setbvalue(o, 1); /* make sure `str' will not be collected */ |
| + luaC_checkGC(L); |
| + } |
| return ts; |
| } |
| |
| ]] |
| } |
| |
| Bug{ |
| what = [['string.format' may get buffer as an argument when there are |
| missing arguments and format string is too long]], |
| report = [[Roberto I., 2010/04/12]], |
| since = [[5.0]], |
| example = [[ |
| x = string.rep("x", 10000) .. "%d" |
| print(string.format(x)) -- gives wrong error message |
| ]], |
| patch = [[ |
| --- lstrlib.c 2008/07/11 17:27:21 1.132.1.4 |
| +++ lstrlib.c 2010/05/14 15:12:53 |
| @@ -754,6 +754,7 @@ |
| |
| |
| static int str_format (lua_State *L) { |
| + int top = lua_gettop(L); |
| int arg = 1; |
| size_t sfl; |
| const char *strfrmt = luaL_checklstring(L, arg, &sfl); |
| @@ -768,7 +769,8 @@ |
| else { /* format item */ |
| char form[MAX_FORMAT]; /* to store the format (`%...') */ |
| char buff[MAX_ITEM]; /* to store the formatted item */ |
| - arg++; |
| + if (++arg > top) |
| + luaL_argerror(L, arg, "no value"); |
| strfrmt = scanformat(L, strfrmt, form); |
| switch (*strfrmt++) { |
| case 'c': { |
| ]] |
| } |
| |
| Bug{ |
| what = [['io.read(op, "*n")' may return garbage if second read fails]], |
| report = [[Roberto I., 2010/04/12]], |
| since = [[5.0]], |
| example = [[ |
| print(io.read("*n", "*n")) --<< enter "10 hi" |
| --> file (0x884420) nil |
| ]], |
| patch = [[ |
| --- liolib.c 2008/01/18 17:47:43 2.73.1.3 |
| +++ liolib.c 2010/05/14 15:29:29 |
| @@ -276,7 +276,10 @@ |
| lua_pushnumber(L, d); |
| return 1; |
| } |
| - else return 0; /* read fails */ |
| + else { |
| + lua_pushnil(L); /* "result" to be removed */ |
| + return 0; /* read fails */ |
| + } |
| } |
| |
| |
| ]] |
| } |
| |
| Bug{ |
| what = [[wrong code generation for some particular boolean expressions]], |
| report = [[Thierry Van Elsuwe, 2011/01/20]], |
| since = [[5.0]], |
| example = [[ |
| print((('hi' or true) and true) or true) |
| --> hi (should be true) |
| print(((nil and nil) or false) and true) |
| --> nil (should be false) |
| ]], |
| patch = [[ |
| --- lcode.c 2009/06/15 14:12:25 2.25.1.4 |
| +++ lcode.c 2011/01/31 14:44:25 |
| @@ -549,13 +549,6 @@ |
| pc = e->u.s.info; |
| break; |
| } |
| - case VFALSE: { |
| - if (!hasjumps(e)) { |
| - pc = luaK_jump(fs); /* always jump */ |
| - break; |
| - } |
| - /* else go through */ |
| - } |
| default: { |
| pc = jumponcond(fs, e, 0); |
| break; |
| @@ -579,13 +572,6 @@ |
| pc = e->u.s.info; |
| break; |
| } |
| - case VTRUE: { |
| - if (!hasjumps(e)) { |
| - pc = luaK_jump(fs); /* always jump */ |
| - break; |
| - } |
| - /* else go through */ |
| - } |
| default: { |
| pc = jumponcond(fs, e, 1); |
| break; |
| ]] |
| } |
| |
| Bug{ |
| what = [[__newindex metamethod may not work if metatable is its own |
| metatable]], |
| report = [[Cuero Bugot, 2011/08/09]], |
| since = [[5.1]], |
| example = [[ |
| meta={} |
| setmetatable(meta, meta) |
| meta.__newindex = function(t, key, value) print("set") end |
| o = setmetatable({}, meta) |
| o.x = 10 -- should print 'set' |
| ]], |
| patch = [[ |
| --- lvm.c 2009/07/01 21:10:33 2.63.1.4 |
| +++ lvm.c 2011/08/17 20:36:28 |
| @@ -142,6 +142,7 @@ |
| if (!ttisnil(oldval) || /* result is no nil? */ |
| (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */ |
| setobj2t(L, oldval, val); |
| + h->flags = 0; |
| luaC_barriert(L, h, val); |
| return; |
| } |
| ]] |
| } |
| |
| Bug{ |
| what = [[parser may collect a prototype while building it]], |
| report = [[Ingo van Lil, 2011/10/13]], |
| since = [[5.1.4 (caused by patch 5.1.4-6)]], |
| example = nil, |
| patch = [[ |
| --- lparser.c 2007/12/28 15:32:23 2.42.1.3 |
| +++ lparser.c 2011/10/17 13:10:43 |
| @@ -374,9 +374,9 @@ |
| lua_assert(luaG_checkcode(f)); |
| lua_assert(fs->bl == NULL); |
| ls->fs = fs->prev; |
| - L->top -= 2; /* remove table and prototype from the stack */ |
| /* last token read was anchored in defunct function; must reanchor it */ |
| if (fs) anchor_token(ls); |
| + L->top -= 2; /* remove table and prototype from the stack */ |
| } |
| |
| |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[When loading a file, |
| Lua may call the reader function again after it returned end of input |
| ]], |
| report = [[Chris Howie, 2013/06/05]], |
| since = [[5.1]], |
| fix = [[5.2]], |
| example = [[ |
| load(function () print("called"); return nil end) |
| --> called |
| --> called (should be called only once!) |
| ]], |
| patch = [[ |
| --- lzio.h 2007/12/27 13:02:25 1.21.1.1 |
| +++ lzio.h 2013/07/04 13:55:59 |
| @@ -59,6 +59,7 @@ |
| lua_Reader reader; |
| void* data; /* additional data */ |
| lua_State *L; /* Lua state (for reader) */ |
| + int eoz; /* true if reader has no more data */ |
| }; |
| |
| |
| --- lzio.c 2007/12/27 13:02:25 1.31.1.1 |
| +++ lzio.c 2013/07/04 13:53:06 |
| @@ -22,10 +22,14 @@ |
| size_t size; |
| lua_State *L = z->L; |
| const char *buff; |
| + if (z->eoz) return EOZ; |
| lua_unlock(L); |
| buff = z->reader(L, z->data, &size); |
| lua_lock(L); |
| - if (buff == NULL || size == 0) return EOZ; |
| + if (buff == NULL || size == 0) { |
| + z->eoz = 1; /* avoid calling reader function next time */ |
| + return EOZ; |
| + } |
| z->n = size - 1; |
| z->p = buff; |
| return char2int(*(z->p++)); |
| @@ -51,6 +55,7 @@ |
| z->data = data; |
| z->n = 0; |
| z->p = NULL; |
| + z->eoz = 0; |
| } |
| ]] |
| } |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.2.0 |
| |
| Bug{ |
| what = [[memory hoarding when creating Lua hooks for coroutines]], |
| report = [[Arseny Vakhrushev, 2012/01/16]], |
| since = [[5.1]], |
| fix = [[5.2.1]], |
| example = [[ |
| collectgarbage(); print(collectgarbage'count' * 1024) |
| |
| for i = 1, 100 do |
| local co = coroutine.create(function () end) |
| local x = {} |
| for j=1,1000 do x[j] = j end |
| debug.sethook(co, function () return x end, 'l') |
| end |
| |
| collectgarbage(); print(collectgarbage'count' * 1024) |
| -- value should back to near the original level |
| ]], |
| patch = [[ |
| -- For 5.2 |
| |
| --- ldblib.c 2011/10/24 14:54:05 1.131 |
| +++ ldblib.c 2012/01/18 02:36:59 |
| @@ -253,14 +253,15 @@ |
| } |
| |
| |
| -#define gethooktable(L) luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY); |
| +#define gethooktable(L) luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY) |
| |
| |
| static void hookf (lua_State *L, lua_Debug *ar) { |
| static const char *const hooknames[] = |
| {"call", "return", "line", "count", "tail call"}; |
| gethooktable(L); |
| - lua_rawgetp(L, -1, L); |
| + lua_pushthread(L); |
| + lua_rawget(L, -2); |
| if (lua_isfunction(L, -1)) { |
| lua_pushstring(L, hooknames[(int)ar->event]); |
| if (ar->currentline >= 0) |
| @@ -306,10 +307,15 @@ |
| count = luaL_optint(L, arg+3, 0); |
| func = hookf; mask = makemask(smask, count); |
| } |
| - gethooktable(L); |
| + if (gethooktable(L) == 0) { /* creating hook table? */ |
| + lua_pushstring(L, "k"); |
| + lua_setfield(L, -2, "__mode"); /** hooktable.__mode = "k" */ |
| + lua_pushvalue(L, -1); |
| + lua_setmetatable(L, -2); /* setmetatable(hooktable) = hooktable */ |
| + } |
| + lua_pushthread(L1); lua_xmove(L1, L, 1); |
| lua_pushvalue(L, arg+1); |
| - lua_rawsetp(L, -2, L1); /* set new hook */ |
| - lua_pop(L, 1); /* remove hook table */ |
| + lua_rawset(L, -3); /* set new hook */ |
| lua_sethook(L1, func, mask, count); /* set hooks */ |
| return 0; |
| } |
| @@ -325,7 +331,8 @@ |
| lua_pushliteral(L, "external hook"); |
| else { |
| gethooktable(L); |
| - lua_rawgetp(L, -1, L1); /* get hook */ |
| + lua_pushthread(L1); lua_xmove(L1, L, 1); |
| + lua_rawget(L, -2); /* get hook */ |
| lua_remove(L, -2); /* remove hook table */ |
| } |
| lua_pushstring(L, unmakemask(mask, buff)); |
| ]] |
| } |
| |
| Bug{ |
| what = [[Lexical gets confused with some combination of arithmetic |
| operators and hexadecimal numbers]], |
| report = [[Alexandra Barros, 2012/01/17]], |
| since = [[5.2.0]], |
| fix = [[5.2.1]], |
| example = [[print(0xE+1)]], |
| patch = [[ |
| --- llex.c 2011/11/30 12:43:51 2.59 |
| +++ llex.c 2012/01/20 18:22:50 |
| @@ -223,12 +223,19 @@ |
| |
| /* LUA_NUMBER */ |
| static void read_numeral (LexState *ls, SemInfo *seminfo) { |
| + const char *expo = "Ee"; |
| + int first = ls->current; |
| lua_assert(lisdigit(ls->current)); |
| - do { |
| - save_and_next(ls); |
| - if (check_next(ls, "EePp")) /* exponent part? */ |
| + save_and_next(ls); |
| + if (first == '0' && check_next(ls, "Xx")) /* hexadecimal? */ |
| + expo = "Pp"; |
| + for (;;) { |
| + if (check_next(ls, expo)) /* exponent part? */ |
| check_next(ls, "+-"); /* optional exponent sign */ |
| - } while (lislalnum(ls->current) || ls->current == '.'); |
| + if (lisxdigit(ls->current) || ls->current == '.') |
| + save_and_next(ls); |
| + else break; |
| + } |
| save(ls, '\0'); |
| buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ |
| if (!buff2d(ls->buff, &seminfo->r)) /* format error? */ |
| ]] |
| } |
| |
| Bug{ |
| what = [[Finalizers may call functions from a dynamic library after |
| the library has been unloaded]], |
| report = [[Josh Haberman, 2012/04/08]], |
| since = [[5.1]], |
| fix = [[5.2.1]], |
| example = [[ |
| local u = setmetatable({}, {__gc = function () foo() end}) |
| local m = require 'mod' -- 'mod' may be any dynamic library written in C |
| foo = m.foo -- 'foo' may be any function from 'mod' |
| -- end program; it crashes |
| ]], |
| patch = [[ |
| loadlib.c: |
| 95c95 |
| < #define LIBPREFIX "LOADLIB: " |
| --- |
| > #define CLIBS "_CLIBS" |
| 251,266c251,256 |
| < |
| < static void **ll_register (lua_State *L, const char *path) { |
| < void **plib; |
| < lua_pushfstring(L, "%s%s", LIBPREFIX, path); |
| < lua_gettable(L, LUA_REGISTRYINDEX); /* check library in registry? */ |
| < if (!lua_isnil(L, -1)) /* is there an entry? */ |
| < plib = (void **)lua_touserdata(L, -1); |
| < else { /* no entry yet; create one */ |
| < lua_pop(L, 1); /* remove result from gettable */ |
| < plib = (void **)lua_newuserdata(L, sizeof(const void *)); |
| < *plib = NULL; |
| < luaL_setmetatable(L, "_LOADLIB"); |
| < lua_pushfstring(L, "%s%s", LIBPREFIX, path); |
| < lua_pushvalue(L, -2); |
| < lua_settable(L, LUA_REGISTRYINDEX); |
| < } |
| --- |
| > static void *ll_checkclib (lua_State *L, const char *path) { |
| > void *plib; |
| > lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); |
| > lua_getfield(L, -1, path); |
| > plib = lua_touserdata(L, -1); /* plib = CLIBS[path] */ |
| > lua_pop(L, 2); /* pop CLIBS table and 'plib' */ |
| 270a261,270 |
| > static void ll_addtoclib (lua_State *L, const char *path, void *plib) { |
| > lua_getfield(L, LUA_REGISTRYINDEX, CLIBS); |
| > lua_pushlightuserdata(L, plib); |
| > lua_pushvalue(L, -1); |
| > lua_setfield(L, -3, path); /* CLIBS[path] = plib */ |
| > lua_rawseti(L, -2, luaL_len(L, -2) + 1); /* CLIBS[#CLIBS + 1] = plib */ |
| > lua_pop(L, 1); /* pop CLIBS table */ |
| > } |
| > |
| > |
| 272,273c272,273 |
| < ** __gc tag method: calls library's `ll_unloadlib' function with the lib |
| < ** handle |
| --- |
| > ** __gc tag method for CLIBS table: calls 'll_unloadlib' for all lib |
| > ** handles in list CLIBS |
| 276,278c276,281 |
| < void **lib = (void **)luaL_checkudata(L, 1, "_LOADLIB"); |
| < if (*lib) ll_unloadlib(*lib); |
| < *lib = NULL; /* mark library as closed */ |
| --- |
| > int n = luaL_len(L, 1); |
| > for (; n >= 1; n--) { /* for each handle, in reverse order */ |
| > lua_rawgeti(L, 1, n); /* get handle CLIBS[n] */ |
| > ll_unloadlib(lua_touserdata(L, -1)); |
| > lua_pop(L, 1); /* pop handle */ |
| > } |
| 284,286c287,292 |
| < void **reg = ll_register(L, path); |
| < if (*reg == NULL) *reg = ll_load(L, path, *sym == '*'); |
| < if (*reg == NULL) return ERRLIB; /* unable to load library */ |
| --- |
| > void *reg = ll_checkclib(L, path); /* check loaded C libraries */ |
| > if (reg == NULL) { /* must load library? */ |
| > reg = ll_load(L, path, *sym == '*'); |
| > if (reg == NULL) return ERRLIB; /* unable to load library */ |
| > ll_addtoclib(L, path, reg); |
| > } |
| 292c298 |
| < lua_CFunction f = ll_sym(L, *reg, sym); |
| --- |
| > lua_CFunction f = ll_sym(L, reg, sym); |
| 675,676c681,683 |
| < /* create new type _LOADLIB */ |
| < luaL_newmetatable(L, "_LOADLIB"); |
| --- |
| > /* create table CLIBS to keep track of loaded C libraries */ |
| > luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS); |
| > lua_createtable(L, 0, 1); /* metatable for CLIBS */ |
| 678a686 |
| > lua_setmetatable(L, -2); |
| ]] |
| } |
| |
| Bug{ |
| what = [[wrong handling of 'nCcalls' in coroutines]], |
| report = [[Alexander Gavrilov, 2012/04/18]], |
| since = [[5.2.0]], |
| fix = [[5.2.1]], |
| example = [[ |
| coroutine.wrap(function() |
| print(pcall(pcall,pcall,pcall,pcall,pcall,error,3)) |
| end)() |
| ]], |
| patch = [[ |
| --- ldo.c 2011/11/29 15:55:08 2.102 |
| +++ ldo.c 2012/04/26 20:38:32 |
| @@ -402,8 +402,6 @@ |
| int n; |
| lua_assert(ci->u.c.k != NULL); /* must have a continuation */ |
| lua_assert(L->nny == 0); |
| - /* finish 'luaD_call' */ |
| - L->nCcalls--; |
| /* finish 'lua_callk' */ |
| adjustresults(L, ci->nresults); |
| /* call continuation function */ |
| @@ -513,7 +511,6 @@ |
| api_checknelems(L, n); |
| firstArg = L->top - n; /* yield results come from continuation */ |
| } |
| - L->nCcalls--; /* finish 'luaD_call' */ |
| luaD_poscall(L, firstArg); /* finish 'luaD_precall' */ |
| } |
| unroll(L, NULL); |
| ]] |
| } |
| |
| Bug{ |
| what = [[Internal Lua values may escape through the debug API]], |
| report = [[Dan Tull, 2012/04/20]], |
| since = [[5.1]], |
| fix = [[5.2.1]], |
| example = [[ |
| -- for Lua 5.1 |
| local firsttime = true |
| local function foo () |
| if firsttime then |
| firsttime = false |
| return "a = 1" |
| else |
| for i = 1, 10 do |
| print(debug.getlocal(2, i)) |
| end |
| end |
| end |
| |
| print(load(foo)) -- prints some lines and then seg. fault. |
| ]], |
| patch = [[ |
| ]] |
| } |
| |
| Bug{ |
| what = [[Problems when yielding from debug hooks]], |
| report = [[Erik Cassel, 2012/06/05]], |
| since = [[5.2.0]], |
| fix = [[5.2.1]], |
| example = [[ |
| Set, in C, a line hook that simply yields, |
| and then call any Lua function. |
| You get an infinite loop of yields. |
| ]], |
| patch = [[ |
| ]] |
| } |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.2.1 |
| |
| Bug{ |
| what = [[Some patterns can overflow the C stack, due to recursion]], |
| report = [[Tim Starling, 2012/07/08]], |
| since = [[2.5]], |
| fix = [[5.2.2]], |
| example = [[print(string.find(string.rep("a", 2^20), string.rep(".?", 2^20)))]], |
| patch = [[ |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [['pcall' may not restore previous error function when |
| inside coroutines]], |
| report = [[Alexander Gavrilov, 2012/06/12]], |
| since = [[5.2.0]], |
| fix = [[5.2.2]], |
| example = [[ |
| function errfunc(x) |
| return 'errfunc' |
| end |
| |
| function test(do_yield) |
| print(do_yield and "yielding" or "not yielding") |
| pcall(function() -- this pcall sets errfunc back to none |
| if do_yield then |
| coroutine.yield() -- stops errfunc from being restored |
| end |
| end) |
| error('fail!') |
| end |
| |
| coro = coroutine.wrap(function() |
| print(xpcall(test, errfunc, false)) |
| print(xpcall(test, errfunc, true)) |
| print(xpcall(test, errfunc, false)) |
| end) |
| |
| coro() |
| --> not yielding |
| --> false errfunc |
| --> yielding |
| coro() |
| --> false temp:12: fail! <<<< should be 'errfunc' too |
| --> not yielding |
| --> false errfunc |
| ]], |
| patch = [[ |
| --- ldo.c 2012/08/28 18:30:45 2.107 |
| +++ ldo.c 2012/09/23 15:49:55 |
| @@ -403,7 +403,11 @@ |
| int n; |
| lua_assert(ci->u.c.k != NULL); /* must have a continuation */ |
| lua_assert(L->nny == 0); |
| - /* finish 'lua_callk' */ |
| + if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */ |
| + ci->callstatus &= ~CIST_YPCALL; /* finish 'lua_pcall' */ |
| + L->errfunc = ci->u.c.old_errfunc; |
| + } |
| + /* finish 'lua_callk'/'lua_pcall' */ |
| adjustresults(L, ci->nresults); |
| /* call continuation function */ |
| if (!(ci->callstatus & CIST_STAT)) /* no call status? */ |
| ]] |
| } |
| |
| Bug{ |
| what = [[Check for garbage collector in function calls does not cover |
| all paths]], |
| report = [[Roberto, 2012/08/15]], |
| since = [[5.2.1]], |
| fix = [[5.2.2]], |
| example = [[ |
| See <a href="http://lua-users.org/lists/lua-l/2012-08/msg00149.html"> |
| http://lua-users.org/lists/lua-l/2012-08/msg00149.html</a> |
| ]], |
| patch = [[ |
| @@ -311,6 +311,7 @@ |
| ci->top = L->top + LUA_MINSTACK; |
| lua_assert(ci->top <= L->stack_last); |
| ci->callstatus = 0; |
| + luaC_checkGC(L); /* stack grow uses memory */ |
| if (L->hookmask & LUA_MASKCALL) |
| luaD_hook(L, LUA_HOOKCALL, -1); |
| lua_unlock(L); |
| @@ -338,6 +339,7 @@ |
| ci->u.l.savedpc = p->code; /* starting point */ |
| ci->callstatus = CIST_LUA; |
| L->top = ci->top; |
| + luaC_checkGC(L); /* stack grow uses memory */ |
| if (L->hookmask & LUA_MASKCALL) |
| callhook(L, ci); |
| return 0; |
| @@ -393,7 +395,6 @@ |
| luaV_execute(L); /* call it */ |
| if (!allowyield) L->nny--; |
| L->nCcalls--; |
| - luaC_checkGC(L); |
| } |
| ]] |
| } |
| |
| Bug{ |
| what = [[load/loadfile returns wrong result when given an environment |
| for a binary chunk with no upvalues]], |
| report = [[Vladimir Strakh, 2012/11/28]], |
| since = [[5.2.0]], |
| fix = [[5.2.2]], |
| example = [[ |
| f = load(string.dump(function () return 1 end), nil, "b", {}) |
| print(type(f)) --> table (whould be a function) |
| ]], |
| patch = [[ |
| --- lbaselib.c 2012/04/27 14:13:19 1.274 |
| +++ lbaselib.c 2012/12/03 20:08:15 |
| @@ -244,5 +244,11 @@ |
| |
| -static int load_aux (lua_State *L, int status) { |
| - if (status == LUA_OK) |
| +static int load_aux (lua_State *L, int status, int envidx) { |
| + if (status == LUA_OK) { |
| + if (envidx != 0) { /* 'env' parameter? */ |
| + lua_pushvalue(L, envidx); /* environment for loaded function */ |
| + if (!lua_setupvalue(L, -2, 1)) /* set it as 1st upvalue */ |
| + lua_pop(L, 1); /* remove 'env' if not used by previous call */ |
| + } |
| return 1; |
| + } |
| else { |
| @@ -258,9 +264,5 @@ |
| const char *mode = luaL_optstring(L, 2, NULL); |
| - int env = !lua_isnone(L, 3); /* 'env' parameter? */ |
| + int env = (!lua_isnone(L, 3) ? 3 : 0); /* 'env' index or 0 if no 'env' */ |
| int status = luaL_loadfilex(L, fname, mode); |
| - if (status == LUA_OK && env) { /* 'env' parameter? */ |
| - lua_pushvalue(L, 3); |
| - lua_setupvalue(L, -2, 1); /* set it as 1st upvalue of loaded chunk */ |
| - } |
| - return load_aux(L, status); |
| + return load_aux(L, status, env); |
| } |
| @@ -309,5 +311,5 @@ |
| size_t l; |
| - int top = lua_gettop(L); |
| const char *s = lua_tolstring(L, 1, &l); |
| const char *mode = luaL_optstring(L, 3, "bt"); |
| + int env = (!lua_isnone(L, 4) ? 4 : 0); /* 'env' index or 0 if no 'env' */ |
| if (s != NULL) { /* loading a string? */ |
| @@ -322,7 +324,3 @@ |
| } |
| - if (status == LUA_OK && top >= 4) { /* is there an 'env' argument */ |
| - lua_pushvalue(L, 4); /* environment for loaded function */ |
| - lua_setupvalue(L, -2, 1); /* set it as 1st upvalue */ |
| - } |
| - return load_aux(L, status); |
| + return load_aux(L, status, env); |
| } |
| ]] |
| } |
| |
| Bug{ |
| what = [[Lua does not check memory use when creating error messages]], |
| report = [[John Dunn, 2012/09/24]], |
| since = [[5.2.0]], |
| fix = nil, |
| example = [[ |
| local code = "function test()\n bob.joe.larry = 23\n end" |
| |
| load(code)() |
| |
| -- memory will grow steadly |
| for i = 1, math.huge do |
| pcall(test) |
| if i % 100000 == 0 then |
| io.write(collectgarbage'count'*1024, "\n") |
| end |
| end |
| ]], |
| patch = [[ |
| ]] |
| } |
| |
| |
| |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.2.2 |
| |
| |
| Bug{ |
| what = [[stack overflow in vararg functions with many fixed |
| parameters called with few arguments]], |
| report = [[云风, 2013/04/17]], |
| since = [[5.1]], |
| fix = [[5.2.3]], |
| example = [[ |
| function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, |
| p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, |
| p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, |
| p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, |
| p41, p42, p43, p44, p45, p46, p48, p49, p50, ...) |
| local a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14 |
| end |
| |
| f() -- seg. fault (on some machines) |
| ]], |
| patch = [[ |
| --- ldo.c 2012/10/01 14:05:04 2.108 |
| +++ ldo.c 2013/04/19 20:56:06 |
| @@ -324,7 +324,7 @@ |
| case LUA_TLCL: { /* Lua function: prepare its call */ |
| StkId base; |
| Proto *p = clLvalue(func)->p; |
| - luaD_checkstack(L, p->maxstacksize); |
| + luaD_checkstack(L, p->maxstacksize + p->numparams); |
| func = restorestack(L, funcr); |
| n = cast_int(L->top - func) - 1; /* number of real arguments */ |
| for (; n < p->numparams; n++) |
| ]], |
| } |
| |
| Bug{ |
| what = [[garbage collector can trigger too many times in recursive loops]], |
| report = [[Roberto, 2013/04/25]], |
| since = [[5.2.2]], |
| fix = [[5.2.3]], |
| example = [[ |
| function f() f() end |
| f() -- it takes too long before a "stack overflow" error |
| ]], |
| patch = [[ |
| --- lgc.c 2013/04/12 18:48:47 2.140.1.1 |
| +++ lgc.c 2013/04/25 21:30:20 |
| @@ -495,2 +495,3 @@ |
| static lu_mem traversestack (global_State *g, lua_State *th) { |
| + int n = 0; |
| StkId o = th->stack; |
| @@ -505,3 +506,9 @@ |
| } |
| - return sizeof(lua_State) + sizeof(TValue) * th->stacksize; |
| + else { /* count call infos to compute size */ |
| + CallInfo *ci; |
| + for (ci = &th->base_ci; ci != th->ci; ci = ci->next) |
| + n++; |
| + } |
| + return sizeof(lua_State) + sizeof(TValue) * th->stacksize + |
| + sizeof(CallInfo) * n; |
| } |
| ]] |
| } |
| |
| -- [[]] |
| Bug{ |
| what = [[Wrong assert when reporting concatenation errors |
| (manifests only when Lua is compiled in debug mode)]], |
| report = [[Roberto, 2013/05/05]], |
| since = [[?]], |
| fix = [[5.2.3]], |
| example = [[ |
| -- only with Lua compiled in debug mode |
| print({} .. 2) |
| ]], |
| patch = [[ |
| --- ldebug.c 2013/04/12 18:48:47 2.90.1.1 |
| +++ ldebug.c 2013/05/05 14:38:30 |
| @@ -519,5 +519,5 @@ |
| l_noret luaG_concaterror (lua_State *L, StkId p1, StkId p2) { |
| if (ttisstring(p1) || ttisnumber(p1)) p1 = p2; |
| - lua_assert(!ttisstring(p1) && !ttisnumber(p2)); |
| + lua_assert(!ttisstring(p1) && !ttisnumber(p1)); |
| luaG_typeerror(L, p1, "concatenate"); |
| } |
| ]] |
| } |
| |
| Bug{ |
| what = [[Wrong error message in some short-cut expressions]], |
| report = [[Egor Skriptunoff, 2013/05/10]], |
| since = [[5.0]], |
| fix = [[5.2.3]], |
| example = [[ |
| > a,b,c = true,true,true |
| > (a and b or c)('', '') |
| stdin:1: attempt to call a boolean value (global 'c') |
| |
| (It should be global 'b' instead of 'c'.) |
| ]], |
| patch = [[ |
| --- ldebug.c 2013/05/06 17:20:22 2.90.1.2 |
| +++ ldebug.c 2013/05/14 19:52:48 |
| @@ -327,12 +327,20 @@ |
| } |
| |
| |
| +static int filterpc (int pc, int jmptarget) { |
| + if (pc < jmptarget) /* is code conditional (inside a jump)? */ |
| + return -1; /* cannot know who sets that register */ |
| + else return pc; /* current position sets that register */ |
| +} |
| + |
| + |
| /* |
| ** try to find last instruction before 'lastpc' that modified register 'reg' |
| */ |
| static int findsetreg (Proto *p, int lastpc, int reg) { |
| int pc; |
| int setreg = -1; /* keep last instruction that changed 'reg' */ |
| + int jmptarget = 0; /* any code before this address is conditional */ |
| for (pc = 0; pc < lastpc; pc++) { |
| Instruction i = p->code[pc]; |
| OpCode op = GET_OPCODE(i); |
| @@ -341,33 +349,38 @@ |
| case OP_LOADNIL: { |
| int b = GETARG_B(i); |
| if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */ |
| - setreg = pc; |
| + setreg = filterpc(pc, jmptarget); |
| break; |
| } |
| case OP_TFORCALL: { |
| - if (reg >= a + 2) setreg = pc; /* affect all regs above its base */ |
| + if (reg >= a + 2) /* affect all regs above its base */ |
| + setreg = filterpc(pc, jmptarget); |
| break; |
| } |
| case OP_CALL: |
| case OP_TAILCALL: { |
| - if (reg >= a) setreg = pc; /* affect all registers above base */ |
| + if (reg >= a) /* affect all registers above base */ |
| + setreg = filterpc(pc, jmptarget); |
| break; |
| } |
| case OP_JMP: { |
| int b = GETARG_sBx(i); |
| int dest = pc + 1 + b; |
| /* jump is forward and do not skip `lastpc'? */ |
| - if (pc < dest && dest <= lastpc) |
| - pc += b; /* do the jump */ |
| + if (pc < dest && dest <= lastpc) { |
| + if (dest > jmptarget) |
| + jmptarget = dest; /* update 'jmptarget' */ |
| + } |
| break; |
| } |
| case OP_TEST: { |
| - if (reg == a) setreg = pc; /* jumped code can change 'a' */ |
| + if (reg == a) /* jumped code can change 'a' */ |
| + setreg = filterpc(pc, jmptarget); |
| break; |
| } |
| default: |
| if (testAMode(op) && reg == a) /* any instruction that set A */ |
| - setreg = pc; |
| + setreg = filterpc(pc, jmptarget); |
| break; |
| } |
| } |
| ]] |
| } |
| |
| Bug{ |
| what = [[luac listings choke on long strings]], |
| report = [[Ashwin Hirschi, 2013/07/03]], |
| since = [[5.1.2]], |
| fix = [[5.2.3]], |
| example = [[ |
| -- When you call 'luac -l' over this chunk, it chokes the output |
| s="Lorem ipsum dolor sit amet, consectetur, " |
| ]], |
| patch = [[ |
| --- luac.c 2011-11-29 15:46:33 -0200 1.69 |
| +++ luac.c 2013-07-03 21:26:01 -0300 |
| @@ -251,7 +251,7 @@ |
| static void PrintConstant(const Proto* f, int i) |
| { |
| const TValue* o=&f->k[i]; |
| - switch (ttype(o)) |
| + switch (ttypenv(o)) |
| { |
| case LUA_TNIL: |
| printf("nil"); |
| ]] |
| } |
| |
| Bug{ |
| what = [[GC can collect a long string still in use during parser]], |
| report = [[Roberto, 2013/08/30]], |
| since = [[5.2]], |
| fix = [[5.2.3]], |
| example = [[This bug is very difficult to happen (and to reproduce), |
| because it depends on the GC running in a very specific way when |
| parsing a source code with long (larger than 40 characters) identifiers.]], |
| patch = [[ |
| --- ltable.h 2013/04/12 18:48:47 2.16.1.1 |
| +++ ltable.h 2013/08/30 15:34:24 |
| @@ -18,4 +18,8 @@ |
| #define invalidateTMcache(t) ((t)->flags = 0) |
| |
| +/* returns the key, given the value of a table entry */ |
| +#define keyfromval(v) \ |
| + (gkey(cast(Node *, cast(char *, (v)) - offsetof(Node, i_val)))) |
| + |
| |
| LUAI_FUNC const TValue *luaH_getint (Table *t, int key); |
| |
| --- llex.c 2013/04/12 18:48:47 2.63.1.1 |
| +++ llex.c 2013/08/30 15:34:59 |
| @@ -134,4 +134,7 @@ |
| luaC_checkGC(L); |
| } |
| + else { /* string already present */ |
| + ts = rawtsvalue(keyfromval(o)); /* re-use value previously stored */ |
| + } |
| L->top--; /* remove string from stack */ |
| return ts; |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[Call to macro 'luai_userstateclose' should be done only |
| after the calls to __gc methods.]], |
| report = [[Jean-Luc Jumpertz, 2013/09/02]], |
| since = [[ ]], |
| fix = nil, |
| example = [[No example]], |
| patch = [[ |
| --- lstate.c 2013/04/12 18:48:47 2.99.1.1 |
| +++ lstate.c 2013/11/08 17:39:57 |
| @@ -194,2 +194,4 @@ |
| g->gcrunning = 1; /* allow gc */ |
| + g->version = lua_version(NULL); |
| + luai_userstateopen(L); |
| } |
| @@ -224,2 +226,4 @@ |
| luaC_freeallobjects(L); /* collect all objects */ |
| + if (g->version) /* closing a fully built state? */ |
| + luai_userstateclose(L); |
| luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); |
| @@ -289,3 +293,3 @@ |
| g->panic = NULL; |
| - g->version = lua_version(NULL); |
| + g->version = NULL; |
| g->gcstate = GCSpause; |
| @@ -308,4 +312,2 @@ |
| } |
| - else |
| - luai_userstateopen(L); |
| return L; |
| @@ -317,3 +319,2 @@ |
| lua_lock(L); |
| - luai_userstateclose(L); |
| close_state(L); |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[Resuming the running coroutine makes it unyieldable]], |
| report = [[Florian Nücke, 2013/10/28]], |
| since = [[5.2]], |
| fix = [[5.2.3]], |
| example = [[ |
| -- should print 'true' |
| print(coroutine.resume(coroutine.create(function() |
| coroutine.resume(coroutine.running()) |
| coroutine.yield() |
| end))) |
| ]], |
| patch = [[ |
| --- ldo.c 2013/04/19 21:03:23 2.108.1.2 |
| +++ ldo.c 2013/11/08 18:20:57 |
| @@ -536,2 +536,3 @@ |
| int status; |
| + int oldnny = L->nny; /* save 'nny' */ |
| lua_lock(L); |
| @@ -557,3 +558,3 @@ |
| } |
| - L->nny = 1; /* do not allow yields */ |
| + L->nny = oldnny; /* restore 'nny' */ |
| L->nCcalls--; |
| ]] |
| } |
| |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.2.3 |
| |
| Bug{ |
| what = [[compiler can optimize away overflow check in 'table.unpack']], |
| report = [[Paige DePol, 2014/03/30]], |
| since = [[5.1 (at least)]], |
| fix = nil, |
| example = [[ |
| > unpack({}, 0, 2^31 - 1) |
| (segfaults on some platforms with some compiler options) |
| ]], |
| patch = [[ |
| --- ltablib.c 2013/04/12 18:48:47 1.65.1.1 |
| +++ ltablib.c 2014/05/07 16:32:55 1.65.1.2 |
| @@ -134,13 +135,14 @@ |
| |
| |
| static int unpack (lua_State *L) { |
| - int i, e, n; |
| + int i, e; |
| + unsigned int n; |
| luaL_checktype(L, 1, LUA_TTABLE); |
| i = luaL_optint(L, 2, 1); |
| e = luaL_opt(L, luaL_checkint, 3, luaL_len(L, 1)); |
| if (i > e) return 0; /* empty range */ |
| - n = e - i + 1; /* number of elements */ |
| - if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */ |
| + n = (unsigned int)e - (unsigned int)i; /* number of elements minus 1 */ |
| + if (n > (INT_MAX - 10) || !lua_checkstack(L, ++n)) |
| return luaL_error(L, "too many results to unpack"); |
| lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */ |
| while (i++ < e) /* push arg[i + 1...e] */ |
| ]] |
| } |
| |
| Bug{ |
| what = [[Ephemeron table can wrongly collect entry with strong key]], |
| report = [[Jörg Richter, 2014/08/22]], |
| since = [[5.2]], |
| fix = nil, |
| example = [[ |
| (This bug is very hard to reproduce, |
| because it depends on a specific interleaving of |
| events between the incremental collector and the program.) |
| ]], |
| patch = [[ |
| --- lgc.c 2013/04/26 18:22:05 2.140.1.2 |
| +++ lgc.c 2014/09/01 13:24:33 |
| @@ -403,7 +403,7 @@ |
| reallymarkobject(g, gcvalue(gval(n))); /* mark it now */ |
| } |
| } |
| - if (prop) |
| + if (g->gcstate != GCSatomic || prop) |
| linktable(h, &g->ephemeron); /* have to propagate again */ |
| else if (hasclears) /* does table have white keys? */ |
| linktable(h, &g->allweak); /* may have to clean white keys */ |
| ]] |
| } |
| |
| Bug{ |
| what = [[Chunk with too many lines can seg. fault]], |
| report = [[Roberto, 2014/11/14]], |
| since = [[5.1 (at least)]], |
| fix = nil, |
| example = [[ |
| -- the cause of the bug is the use of an unitialized variable, so |
| -- it cannot be reproduced reliably |
| local s = string.rep("\n", 2^24) |
| print(load(function () return s end)) |
| ]], |
| patch = [[ |
| --- llex.c 2013/08/30 15:49:41 2.63.1.2 |
| +++ llex.c 2015/02/09 17:05:31 |
| @@ -153,5 +153,5 @@ |
| next(ls); /* skip `\n\r' or `\r\n' */ |
| if (++ls->linenumber >= MAX_INT) |
| - luaX_syntaxerror(ls, "chunk has too many lines"); |
| + lexerror(ls, "chunk has too many lines", 0); |
| } |
| |
| ]] |
| } |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.3.0 |
| |
| Bug{ |
| what = [['string.format("%f")' can cause a buffer overflow |
| (only when 'lua_Number' is long double!)]], |
| report = [[Roberto, 2015/01/13]], |
| since = [[5.3]], |
| fix = nil, |
| example = [[string.format("%.99f", 1e4000) -- when floats are long double]], |
| patch = [[ |
| --- lstrlib.c 2014/12/11 14:03:07 1.221 |
| +++ lstrlib.c 2015/02/23 19:01:42 |
| @@ -800,3 +800,4 @@ |
| /* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ |
| -#define MAX_ITEM 512 |
| +#define MAX_ITEM \ |
| + (sizeof(lua_Number) <= 4 ? 150 : sizeof(lua_Number) <= 8 ? 450 : 5050) |
| |
| ]] |
| } |
| |
| Bug{ |
| what = [['debug.getlocal' on a coroutine suspended in a hook |
| can crash the interpreter]], |
| report = [[云风, 2015/02/11]], |
| since = [[5.2]], |
| fix = nil, |
| example = [[see http://lua-users.org/lists/lua-l/2015-02/msg00146.html]], |
| patch = [[ |
| --- ldebug.c 2015/01/02 12:52:22 2.110 |
| +++ ldebug.c 2015/02/13 16:03:23 |
| @@ -49,4 +49,14 @@ |
| |
| |
| +static void swapextra (lua_State *L) { |
| + if (L->status == LUA_YIELD) { |
| + CallInfo *ci = L->ci; /* get function that yielded */ |
| + StkId temp = ci->func; /* exchange its 'func' and 'extra' values */ |
| + ci->func = restorestack(L, ci->extra); |
| + ci->extra = savestack(L, temp); |
| + } |
| +} |
| + |
| + |
| /* |
| ** this function can be called asynchronous (e.g. during a signal) |
| @@ -145,4 +155,5 @@ |
| const char *name; |
| lua_lock(L); |
| + swapextra(L); |
| if (ar == NULL) { /* information about non-active function? */ |
| if (!isLfunction(L->top - 1)) /* not a Lua function? */ |
| @@ -159,4 +170,5 @@ |
| } |
| } |
| + swapextra(L); |
| lua_unlock(L); |
| return name; |
| @@ -166,10 +178,13 @@ |
| LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { |
| StkId pos = 0; /* to avoid warnings */ |
| - const char *name = findlocal(L, ar->i_ci, n, &pos); |
| + const char *name; |
| lua_lock(L); |
| + swapextra(L); |
| + name = findlocal(L, ar->i_ci, n, &pos); |
| if (name) { |
| setobjs2s(L, pos, L->top - 1); |
| L->top--; /* pop value */ |
| } |
| + swapextra(L); |
| lua_unlock(L); |
| return name; |
| @@ -271,4 +286,5 @@ |
| StkId func; |
| lua_lock(L); |
| + swapextra(L); |
| if (*what == '>') { |
| ci = NULL; |
| @@ -289,4 +305,5 @@ |
| api_incr_top(L); |
| } |
| + swapextra(L); |
| if (strchr(what, 'L')) |
| collectvalidlines(L, cl); |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[suspended '__le' metamethod can give wrong result]], |
| report = [[Eric Zhong, 2015/04/07]], |
| since = [[5.2]], |
| fix = nil, |
| |
| example = [[ |
| mt = {__le = function (a,b) coroutine.yield("yield"); return a.x <= b.x end} |
| t1 = setmetatable({x=1}, mt) |
| t2 = {x=2} |
| co = coroutine.wrap(function (a,b) return t2 <= t1 end) |
| co() |
| print(co()) --> true (should be false) |
| ]], |
| |
| patch = [[ |
| --- lstate.h 2014/10/30 18:53:28 2.119 |
| +++ lstate.h 2015/04/13 15:58:40 |
| @@ -94,6 +94,7 @@ |
| #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ |
| #define CIST_TAIL (1<<5) /* call was tail called */ |
| #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ |
| +#define CIST_LEQ (1<<7) /* using __lt for __le */ |
| |
| #define isLua(ci) ((ci)->callstatus & CIST_LUA) |
| |
| --- lvm.c 2014/12/27 20:30:38 2.232 |
| +++ lvm.c 2015/04/13 15:51:30 |
| @@ -292,9 +292,14 @@ |
| return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; |
| else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* first try 'le' */ |
| return res; |
| - else if ((res = luaT_callorderTM(L, r, l, TM_LT)) < 0) /* else try 'lt' */ |
| - luaG_ordererror(L, l, r); |
| - return !res; |
| + else { /* try 'lt': */ |
| + L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ |
| + res = luaT_callorderTM(L, r, l, TM_LT); |
| + L->ci->callstatus ^= CIST_LEQ; /* clear mark */ |
| + if (res < 0) |
| + luaG_ordererror(L, l, r); |
| + return !res; /* result is negated */ |
| + } |
| } |
| |
| |
| @@ -553,11 +558,11 @@ |
| case OP_LE: case OP_LT: case OP_EQ: { |
| int res = !l_isfalse(L->top - 1); |
| L->top--; |
| - /* metamethod should not be called when operand is K */ |
| - lua_assert(!ISK(GETARG_B(inst))); |
| - if (op == OP_LE && /* "<=" using "<" instead? */ |
| - ttisnil(luaT_gettmbyobj(L, base + GETARG_B(inst), TM_LE))) |
| - res = !res; /* invert result */ |
| + if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ |
| + lua_assert(op == OP_LE); |
| + ci->callstatus ^= CIST_LEQ; /* clear mark */ |
| + res = !res; /* negate result */ |
| + } |
| lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); |
| if (res != GETARG_A(inst)) /* condition failed? */ |
| ci->u.l.savedpc++; /* skip jump instruction */ |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[return hook may not see correct values for |
| active local variables when function returns]], |
| report = [[Philipp Janda/Peng Yi, 2015/05/19]], |
| since = [[5.0]], |
| fix = nil, |
| example = [[ |
| see messasge http://lua-users.org/lists/lua-l/2015-05/msg00376.html]], |
| patch = [[ |
| ]] |
| } |
| |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.3.1 |
| |
| Bug{ |
| what = [['io.lines' does not check maximum number of options]], |
| report = [[Patrick Donnell, 2015/07/10]], |
| since = [[5.3.0]], |
| fix = nil, |
| example = [[ |
| -- can segfault in some machines |
| t ={}; for i = 1, 253 do t[i] = 1 end |
| io.lines("someexistingfile", table.unpack(t))() |
| ]], |
| patch = [[ |
| --- liolib.c 2015/07/07 17:03:34 2.146 |
| +++ liolib.c 2015/07/15 14:40:28 2.147 |
| @@ -318,8 +318,15 @@ |
| static int io_readline (lua_State *L); |
| |
| |
| +/* |
| +** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit |
| +** in the limit for upvalues of a closure) |
| +*/ |
| +#define MAXARGLINE 250 |
| + |
| static void aux_lines (lua_State *L, int toclose) { |
| int n = lua_gettop(L) - 1; /* number of arguments to read */ |
| + luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments"); |
| lua_pushinteger(L, n); /* number of arguments to read */ |
| lua_pushboolean(L, toclose); /* close/not close file when finished */ |
| lua_rotate(L, 2, 2); /* move 'n' and 'toclose' to their positions */ |
| ]] |
| } |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.3.2 |
| |
| Bug{ |
| what = [[Metatable may access its own dealocated field when |
| it has a self reference in __newindex]], |
| report = [[actboy168@gmail.com, 2016/01/01]], |
| since = [[5.3.2]], |
| fix = nil, |
| example = [[ |
| local mt = {} |
| mt.__newindex = mt |
| local t = setmetatable({}, mt) |
| t[1] = 1 -- will segfault on some machines |
| ]], |
| patch = [[ |
| --- lvm.c 2015/11/23 11:30:45 2.265 |
| +++ lvm.c 2016/01/01 14:34:12 |
| @@ -190,18 +190,19 @@ |
| for (loop = 0; loop < MAXTAGLOOP; loop++) { |
| const TValue *tm; |
| if (oldval != NULL) { |
| - lua_assert(ttistable(t) && ttisnil(oldval)); |
| + Table *h = hvalue(t); /* save 't' table */ |
| + lua_assert(ttisnil(oldval)); |
| /* must check the metamethod */ |
| - if ((tm = fasttm(L, hvalue(t)->metatable, TM_NEWINDEX)) == NULL && |
| + if ((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL && |
| /* no metamethod; is there a previous entry in the table? */ |
| (oldval != luaO_nilobject || |
| /* no previous entry; must create one. (The next test is |
| always true; we only need the assignment.) */ |
| - (oldval = luaH_newkey(L, hvalue(t), key), 1))) { |
| + (oldval = luaH_newkey(L, h, key), 1))) { |
| /* no metamethod and (now) there is an entry with given key */ |
| setobj2t(L, cast(TValue *, oldval), val); |
| - invalidateTMcache(hvalue(t)); |
| - luaC_barrierback(L, hvalue(t), val); |
| + invalidateTMcache(h); |
| + luaC_barrierback(L, h, val); |
| return; |
| } |
| /* else will try the metamethod */ |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[label between local definitions can mix-up their initializations]], |
| report = [[Karel Tuma, 2016/03/01]], |
| since = [[5.2]], |
| fix = nil, |
| example = [[ |
| do |
| local k = 0 |
| local x |
| ::foo:: |
| local y -- should be reset to nil after goto, but it is not |
| assert(not y) |
| y = true |
| k = k + 1 |
| if k < 2 then goto foo end |
| end |
| ]], |
| patch = [[ |
| --- lparser.c 2015/11/02 16:09:30 2.149 |
| +++ lparser.c 2016/03/03 12:03:37 |
| @@ -1226,7 +1226,7 @@ |
| checkrepeated(fs, ll, label); /* check for repeated labels */ |
| checknext(ls, TK_DBCOLON); /* skip double colon */ |
| /* create new entry for this label */ |
| - l = newlabelentry(ls, ll, label, line, fs->pc); |
| + l = newlabelentry(ls, ll, label, line, luaK_getlabel(fs)); |
| skipnoopstat(ls); /* skip other no-op statements */ |
| if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ |
| /* assume that locals are already out of scope */ |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [['gmatch' iterator fails when called from a coroutine different |
| from the one that created it]], |
| report = [[Nagaev Boris, 2016/03/18]], |
| since = [[5.3.2]], |
| fix = nil, |
| example = [[ |
| local f = string.gmatch("1 2 3 4 5", "%d+") |
| print(f()) --> 1 |
| co = coroutine.wrap(f) |
| print(co()) --> ??? (should be 2) |
| ]], |
| patch = [[ |
| --- lstrlib.c 2015/11/25 16:28:17 1.239 |
| +++ lstrlib.c 2016/04/11 15:29:41 |
| @@ -688,6 +688,7 @@ |
| static int gmatch_aux (lua_State *L) { |
| GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3)); |
| const char *src; |
| + gm->ms.L = L; |
| for (src = gm->src; src <= gm->ms.src_end; src++) { |
| const char *e; |
| reprepstate(&gm->ms); |
| ]] |
| } |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.3.3 |
| |
| |
| Bug{ |
| what = [[expression list with four or more expressions in |
| a 'for' loop can crash the interpreter]], |
| report = [[Marco Schöpl, 2016/06/17]], |
| since = [[5.2]], |
| fix = nil, |
| example = [[ |
| -- the next loop will probably crash the interpreter |
| repeat until load "for _ in _,_,_,_ do local function _() end" |
| ]], |
| patch = [[ |
| --- lparser.c 2016/05/13 19:10:16 2.153 |
| +++ lparser.c 2016/06/17 19:52:48 |
| @@ -323,6 +323,8 @@ |
| luaK_nil(fs, reg, extra); |
| } |
| } |
| + if (nexps > nvars) |
| + ls->fs->freereg -= nexps - nvars; /* remove extra values */ |
| } |
| |
| |
| @@ -1160,11 +1162,8 @@ |
| int nexps; |
| checknext(ls, '='); |
| nexps = explist(ls, &e); |
| - if (nexps != nvars) { |
| + if (nexps != nvars) |
| adjust_assign(ls, nvars, nexps, &e); |
| - if (nexps > nvars) |
| - ls->fs->freereg -= nexps - nvars; /* remove extra values */ |
| - } |
| else { |
| luaK_setoneret(ls->fs, &e); /* close last expression */ |
| luaK_storevar(ls->fs, &lh->v, &e); |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[Checking a format for 'os.date' may read pass the format string]], |
| report = [[Nagaev Boris, 2016/07/10]], |
| since = [[5.3.3]], |
| fix = nil, |
| example = [[ |
| This bug does not seem to happen with regular compilers. |
| It needs an "interceptor" 'memcmp' function that continues |
| reading memory after a difference is found.]], |
| patch = [[ |
| 2c2 |
| < ** $Id: bugs,v 1.160 2018/05/24 20:25:14 roberto Exp roberto $ |
| --- |
| > ** $Id: bugs,v 1.160 2018/05/24 20:25:14 roberto Exp roberto $ |
| 263c263,264 |
| < for (option = LUA_STRFTIMEOPTIONS; *option != '\0'; option += oplen) { |
| --- |
| > int convlen = (int)strlen(conv); |
| > for (option = LUA_STRFTIMEOPTIONS; *option != '\0' && oplen <= convlen; option += oplen) { |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[Lua can generate wrong code in functions with too many constants]], |
| report = [[Marco Schöpl, 2016/07/17]], |
| since = [[5.3.3]], |
| fix = nil, |
| example = [[See http://lua-users.org/lists/lua-l/2016-07/msg00303.html]], |
| patch = [[ |
| --- lcode.c 2016/06/20 19:12:46 2.110 |
| +++ lcode.c 2016/07/18 15:43:41 |
| @@ -1018,8 +1018,8 @@ |
| */ |
| static void codebinexpval (FuncState *fs, OpCode op, |
| expdesc *e1, expdesc *e2, int line) { |
| - int rk1 = luaK_exp2RK(fs, e1); /* both operands are "RK" */ |
| - int rk2 = luaK_exp2RK(fs, e2); |
| + int rk2 = luaK_exp2RK(fs, e2); /* both operands are "RK" */ |
| + int rk1 = luaK_exp2RK(fs, e1); |
| freeexps(fs, e1, e2); |
| e1->u.info = luaK_codeABC(fs, op, 0, rk1, rk2); /* generate opcode */ |
| e1->k = VRELOCABLE; /* all those operations are relocatable */ |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[When a coroutine tries to resume a non-suspended coroutine, |
| it can do some mess (and break C assertions) before detecting the error]], |
| report = [[Marco Schöpl, 2016/07/20]], |
| since = [[ ]], |
| fix = nil, |
| example = [[ |
| -- with C assertions on |
| A = coroutine.running() |
| B = coroutine.create(function() coroutine.resume(A) end) |
| coroutine.resume(B) |
| |
| -- or |
| A = coroutine.wrap(function() pcall(A, _) end) |
| A() |
| ]], |
| patch = [[ |
| ]] |
| } |
| |
| |
| ----------------------------------------------------------------- |
| -- Lua 5.3.4 |
| |
| |
| Bug{ |
| what = [[Wrong code for a goto followed by a label inside an 'if']], |
| report = [[云风, 2017/04/13]], |
| since = [[5.2]], |
| fix = nil, |
| example = [[ |
| -- should print 32323232..., but prints only '3' |
| if true then |
| goto LBL |
| ::loop:: |
| print(2) |
| ::LBL:: |
| print(3) |
| goto loop |
| end |
| ]], |
| patch = [[ |
| --- lparser.c 2017/04/19 17:20:42 2.155.1.1 |
| +++ lparser.c 2017/04/29 18:11:40 2.155.1.2 |
| @@ -1392,7 +1392,7 @@ |
| luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */ |
| enterblock(fs, &bl, 0); /* must enter block before 'goto' */ |
| gotostat(ls, v.t); /* handle goto/break */ |
| - skipnoopstat(ls); /* skip other no-op statements */ |
| + while (testnext(ls, ';')) {} /* skip semicolons */ |
| if (block_follow(ls, 0)) { /* 'goto' is the entire block? */ |
| leaveblock(fs); |
| return; /* and that is it */ |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[Lua crashes when building sequences with more than 2^30 elements.]], |
| report = [[Viacheslav Usov, 2017/05/11]], |
| since = [[ ]], |
| fix = nil, |
| example = [[ |
| -- crashes if machine has enough memory |
| local t = {} |
| for i = 1, 0x7fffffff do |
| t[i] = i |
| end |
| ]], |
| patch = [[ |
| --- ltable.c 2017/04/19 17:20:42 2.118.1.1 |
| +++ ltable.c 2018/05/24 18:34:38 |
| @@ -223,7 +223,9 @@ |
| unsigned int na = 0; /* number of elements to go to array part */ |
| unsigned int optimal = 0; /* optimal size for array part */ |
| /* loop while keys can fill more than half of total size */ |
| - for (i = 0, twotoi = 1; *pna > twotoi / 2; i++, twotoi *= 2) { |
| + for (i = 0, twotoi = 1; |
| + twotoi > 0 && *pna > twotoi / 2; |
| + i++, twotoi *= 2) { |
| if (nums[i] > 0) { |
| a += nums[i]; |
| if (a > twotoi/2) { /* more than half elements present? */ |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[Table length computation overflows for sequences larger than |
| 2^31 elements.]], |
| report = [[Viacheslav Usov, 2017/05/12]], |
| since = [[ ]], |
| fix = nil, |
| example = [[ |
| -- on a machine with enough memory |
| local t = {} |
| for i = 1, 2147483681 do |
| t[i] = i |
| end |
| print(#t) |
| ]], |
| patch = [[ |
| --- ltable.h 2017/04/19 17:20:42 2.23.1.1 |
| +++ ltable.h 2018/05/24 19:31:50 |
| @@ -56,3 +56,3 @@ |
| LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); |
| -LUAI_FUNC int luaH_getn (Table *t); |
| +LUAI_FUNC lua_Unsigned luaH_getn (Table *t); |
| |
| --- ltable.c 2018/05/24 19:22:37 2.118.1.2 |
| +++ ltable.c 2018/05/24 19:25:05 |
| @@ -614,4 +614,4 @@ |
| |
| -static int unbound_search (Table *t, unsigned int j) { |
| - unsigned int i = j; /* i is zero or a present index */ |
| +static lua_Unsigned unbound_search (Table *t, lua_Unsigned j) { |
| + lua_Unsigned i = j; /* i is zero or a present index */ |
| j++; |
| @@ -620,3 +620,3 @@ |
| i = j; |
| - if (j > cast(unsigned int, MAX_INT)/2) { /* overflow? */ |
| + if (j > l_castS2U(LUA_MAXINTEGER) / 2) { /* overflow? */ |
| /* table was built with bad purposes: resort to linear search */ |
| @@ -630,3 +630,3 @@ |
| while (j - i > 1) { |
| - unsigned int m = (i+j)/2; |
| + lua_Unsigned m = (i+j)/2; |
| if (ttisnil(luaH_getint(t, m))) j = m; |
| @@ -642,3 +642,3 @@ |
| */ |
| -int luaH_getn (Table *t) { |
| +lua_Unsigned luaH_getn (Table *t) { |
| unsigned int j = t->sizearray; |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[Lua does not check GC when creating error messages]], |
| report = [[Viacheslav Usov, 2017/07/06]], |
| since = [[5.3.2]], |
| fix = nil, |
| example = [[ |
| function test() |
| bob.joe.larry = 23 |
| end |
| |
| -- memory will grow steadly |
| for i = 1, math.huge do |
| pcall(test) |
| if i % 100000 == 0 then |
| io.write(collectgarbage'count'*1024, "\n") |
| end |
| end |
| ]], |
| patch = [[ |
| --- ldebug.c 2017/04/19 17:20:42 2.121.1.1 |
| +++ ldebug.c 2017/07/10 17:08:39 |
| @@ -653,6 +653,7 @@ |
| CallInfo *ci = L->ci; |
| const char *msg; |
| va_list argp; |
| + luaC_checkGC(L); /* error message uses memory */ |
| va_start(argp, fmt); |
| msg = luaO_pushvfstring(L, fmt, argp); /* format message */ |
| va_end(argp); |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[dead keys with nil values can stay in weak tables]], |
| report = [[云风 Cloud Wu, 2017/08/15]], |
| since = [[5.2]], |
| fix = nil, |
| example = [[ |
| -- The following chunk, under a memory checker like valgrind, |
| -- produces a memory access violation. |
| |
| local a = setmetatable({}, {__mode = 'kv'}) |
| |
| a['ABCDEFGHIJKLMNOPQRSTUVWXYZ' .. 'abcdefghijklmnopqrstuvwxyz'] = {} |
| a[next(a)] = nil |
| collectgarbage() |
| print(a['BCDEFGHIJKLMNOPQRSTUVWXYZ' .. 'abcdefghijklmnopqrstuvwxyz']) |
| ]], |
| patch = [[ |
| --- lgc.c 2016/12/22 13:08:50 2.215 |
| +++ lgc.c 2017/08/31 16:08:23 |
| @@ -643,8 +643,9 @@ |
| for (n = gnode(h, 0); n < limit; n++) { |
| if (!ttisnil(gval(n)) && (iscleared(g, gkey(n)))) { |
| setnilvalue(gval(n)); /* remove value ... */ |
| - removeentry(n); /* and remove entry from table */ |
| } |
| + if (ttisnil(gval(n))) /* is entry empty? */ |
| + removeentry(n); /* remove entry from table */ |
| } |
| } |
| } |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [['lua_pushcclosure' should not call the garbage collector when |
| 'n' is zero.]], |
| report = [[Andrew Gierth, 2017/12/05]], |
| since = [[5.3.3]], |
| fix = nil, |
| example = [[ ]], |
| patch = [[ |
| --- lapi.c 2017/04/19 17:13:00 2.259.1.1 |
| +++ lapi.c 2017/12/06 18:14:45 |
| @@ -533,6 +533,7 @@ |
| lua_lock(L); |
| if (n == 0) { |
| setfvalue(L->top, fn); |
| + api_incr_top(L); |
| } |
| else { |
| CClosure *cl; |
| @@ -546,9 +547,9 @@ |
| /* does not need barrier because closure is white */ |
| } |
| setclCvalue(L, L->top, cl); |
| + api_incr_top(L); |
| + luaC_checkGC(L); |
| } |
| - api_incr_top(L); |
| - luaC_checkGC(L); |
| lua_unlock(L); |
| } |
| ]] |
| } |
| |
| |
| Bug{ |
| what = [[memory-allocation error when resizing a table can leave it |
| in an inconsistent state.]], |
| report = [[Roberto, 2017/12/08]], |
| since = [[5.0]], |
| fix = nil, |
| example = [[ |
| local a = {x = 1, y = 1, z = 1} |
| a[1] = 10 -- goes to the hash part (which has 4 slots) |
| print(a[1]) --> 10 |
| |
| -- assume that the 2nd memory allocation from now fails |
| pcall(rawset, a, 2, 20) -- forces a rehash |
| |
| -- a[1] now exists both in the array part (because the array part |
| -- grew) and in the hash part (because the allocation of the hash |
| -- part failed, keeping it as it was). |
| -- This makes the following traversal goes forever... |
| for k,v in pairs(a) do print(k,v) end |
| ]], |
| patch = [[ |
| --- ltable.c 2018/05/24 19:39:05 2.118.1.3 |
| +++ ltable.c 2018/06/04 16:00:25 |
| @@ -332,17 +332,34 @@ |
| } |
| |
| |
| +typedef struct { |
| + Table *t; |
| + unsigned int nhsize; |
| +} AuxsetnodeT; |
| + |
| + |
| +static void auxsetnode (lua_State *L, void *ud) { |
| + AuxsetnodeT *asn = cast(AuxsetnodeT *, ud); |
| + setnodevector(L, asn->t, asn->nhsize); |
| +} |
| + |
| + |
| void luaH_resize (lua_State *L, Table *t, unsigned int nasize, |
| unsigned int nhsize) { |
| unsigned int i; |
| int j; |
| + AuxsetnodeT asn; |
| unsigned int oldasize = t->sizearray; |
| int oldhsize = allocsizenode(t); |
| Node *nold = t->node; /* save old hash ... */ |
| if (nasize > oldasize) /* array part must grow? */ |
| setarrayvector(L, t, nasize); |
| /* create new hash part with appropriate size */ |
| - setnodevector(L, t, nhsize); |
| + asn.t = t; asn.nhsize = nhsize; |
| + if (luaD_rawrunprotected(L, auxsetnode, &asn) != LUA_OK) { /* mem. error? */ |
| + setarrayvector(L, t, oldasize); /* array back to its original size */ |
| + luaD_throw(L, LUA_ERRMEM); /* rethrow memory error */ |
| + } |
| if (nasize < oldasize) { /* array part must shrink? */ |
| t->sizearray = nasize; |
| /* re-insert elements from vanishing slice */ |
| ]] |
| } |
| |
| |
| |
| |
| --[=[ |
| Bug{ |
| what = [[ ]], |
| report = [[ ]], |
| since = [[ ]], |
| fix = nil, |
| example = [[ ]], |
| patch = [[ |
| ]] |
| } |
| ]=] |
| |
| |