diff --git a/all b/all index 2a8cb92f..039f6095 100755 --- a/all +++ b/all @@ -1,7 +1,7 @@ make -s -j cd testes/libs; make -s cd .. # back to directory 'testes' -ulimit -S -s 2000 +ulimit -S -s 1000 if { ../lua -W all.lua; } then echo -e "\n\n final OK!!!!\n\n" else diff --git a/ldblib.c b/ldblib.c index 59eb8f0e..26058b50 100644 --- a/ldblib.c +++ b/ldblib.c @@ -440,10 +440,7 @@ static int db_traceback (lua_State *L) { static int db_setcstacklimit (lua_State *L) { int limit = (int)luaL_checkinteger(L, 1); int res = lua_setcstacklimit(L, limit); - if (res == 0) - lua_pushboolean(L, 0); - else - lua_pushinteger(L, res); + lua_pushinteger(L, res); return 1; } diff --git a/ldo.c b/ldo.c index dc3cc9fd..0a6a7169 100644 --- a/ldo.c +++ b/ldo.c @@ -448,10 +448,11 @@ void luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, int narg1) { /* -** Call a function (C or Lua). The function to be called is at *func. -** The arguments are on the stack, right after the function. -** When returns, all the results are on the stack, starting at the original -** function position. +** Prepares the call to a function (C or Lua). For C functions, also do +** the call. The function to be called is at '*func'. The arguments are +** on the stack, right after the function. Returns true if the call was +** made (it was a C function). When returns true, all the results are +** on the stack, starting at the original function position. */ int luaD_precall (lua_State *L, StkId func, int nresults) { lua_CFunction f; @@ -511,32 +512,34 @@ int luaD_precall (lua_State *L, StkId func, int nresults) { } -static void stackerror (lua_State *L) { - if (getCcalls(L) == LUAI_MAXCCALLS) - luaG_runerror(L, "C stack overflow"); - else if (getCcalls(L) >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) - luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ -} - - -void luaD_call (lua_State *L, StkId func, int nResults) { - L->nCcalls++; +/* +** Call a function (C or Lua). 'inc' can be 1 (increment number +** of recursive invocations in the C stack) or nyci (the same plus +** increment number of non-yieldable calls). +*/ +static void docall (lua_State *L, StkId func, int nResults, int inc) { + L->nCcalls += inc; if (getCcalls(L) >= LUAI_MAXCCALLS) - stackerror(L); + luaE_checkcstack(L); if (!luaD_precall(L, func, nResults)) /* is a Lua function? */ luaV_execute(L, L->ci); /* call it */ - L->nCcalls--; + L->nCcalls -= inc; } +/* +** External interface for 'docall' +*/ +void luaD_call (lua_State *L, StkId func, int nResults) { + return docall(L, func, nResults, 1); +} + /* ** Similar to 'luaD_call', but does not allow yields during the call. */ void luaD_callnoyield (lua_State *L, StkId func, int nResults) { - incnny(L); - luaD_call(L, func, nResults); - decnny(L); + return docall(L, func, nResults, nyci); } @@ -650,13 +653,12 @@ static void resume (lua_State *L, void *ud) { int n = *(cast(int*, ud)); /* number of arguments */ StkId firstArg = L->top - n; /* first argument */ CallInfo *ci = L->ci; - if (L->status == LUA_OK) { /* starting a coroutine? */ - if (!luaD_precall(L, firstArg - 1, LUA_MULTRET)) /* Lua function? */ - luaV_execute(L, L->ci); /* call it */ - } + if (L->status == LUA_OK) /* starting a coroutine? */ + docall(L, firstArg - 1, LUA_MULTRET, 1); /* just call its body */ else { /* resuming from previous yield */ lua_assert(L->status == LUA_YIELD); L->status = LUA_OK; /* mark that it is running (again) */ + luaE_incCstack(L); /* control the C stack */ if (isLua(ci)) /* yielded inside a hook? */ luaV_execute(L, ci); /* just continue running Lua code */ else { /* 'common' yield */ @@ -684,9 +686,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, } else if (L->status != LUA_YIELD) /* ended with errors? */ return resume_error(L, "cannot resume dead coroutine", nargs); - L->nCcalls = (from) ? getCcalls(from) + 1 : 1; - if (getCcalls(L) >= LUAI_MAXCCALLS) - return resume_error(L, "C stack overflow", nargs); + L->nCcalls = (from) ? getCcalls(from) : 0; luai_userstateresume(L, nargs); api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); status = luaD_rawrunprotected(L, resume, &nargs); diff --git a/llimits.h b/llimits.h index 48c97f95..d6866d7c 100644 --- a/llimits.h +++ b/llimits.h @@ -234,6 +234,17 @@ typedef l_uint32 Instruction; #endif +/* +** Maximum depth for nested C calls, syntactical nested non-terminals, +** and other features implemented through recursion in C. (Value must +** fit in a 16-bit unsigned integer. It must also be compatible with +** the size of the C stack.) +*/ +#if !defined(LUAI_MAXCCALLS) +#define LUAI_MAXCCALLS 200 +#endif + + /* ** macros that are executed whenever program enters the Lua core ** ('lua_lock') and leaves the core ('lua_unlock') diff --git a/lparser.c b/lparser.c index 502a9b2d..bcdcfb6d 100644 --- a/lparser.c +++ b/lparser.c @@ -489,11 +489,7 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { } -static void enterlevel (LexState *ls) { - lua_State *L = ls->L; - L->nCcalls++; - checklimit(ls->fs, getCcalls(L), LUAI_MAXCCALLS, "C levels"); -} +#define enterlevel(ls) luaE_incCstack(ls->L) #define leavelevel(ls) ((ls)->L->nCcalls--) diff --git a/lstate.c b/lstate.c index 8cda3072..bd1b5120 100644 --- a/lstate.c +++ b/lstate.c @@ -97,25 +97,8 @@ void luaE_setdebt (global_State *g, l_mem debt) { LUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) { - global_State *g = G(L); - int ccalls; - luaE_freeCI(L); /* release unused CIs */ - ccalls = getCcalls(L); - if (limit >= 40000) - return 0; /* out of bounds */ - limit += CSTACKERR; - if (L != g-> mainthread) - return 0; /* only main thread can change the C stack */ - else if (ccalls <= CSTACKERR) - return 0; /* handling overflow */ - else { - int diff = limit - g->Cstacklimit; - if (ccalls + diff <= CSTACKERR) - return 0; /* new limit would cause an overflow */ - g->Cstacklimit = limit; /* set new limit */ - L->nCcalls += diff; /* correct 'nCcalls' */ - return limit - diff - CSTACKERR; /* success; return previous limit */ - } + UNUSED(L); UNUSED(limit); + return LUAI_MAXCCALLS; /* warning?? */ } @@ -172,6 +155,28 @@ void luaE_shrinkCI (lua_State *L) { } +/* +** Called when 'getCcalls(L)' larger or equal to LUAI_MAXCCALLS. +** If equal, raises an overflow error. If value is larger than +** LUAI_MAXCCALLS (which means it is handling an overflow) but +** not much larger, does not report an error (to allow overflow +** handling to work). +*/ +void luaE_checkcstack (lua_State *L) { + if (getCcalls(L) == LUAI_MAXCCALLS) + luaG_runerror(L, "C stack overflow"); + else if (getCcalls(L) >= (LUAI_MAXCCALLS / 10 * 11)) + luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ +} + + +LUAI_FUNC void luaE_incCstack (lua_State *L) { + L->nCcalls++; + if (getCcalls(L) >= LUAI_MAXCCALLS) + luaE_checkcstack(L); +} + + static void stack_init (lua_State *L1, lua_State *L) { int i; CallInfo *ci; /* initialize stack array */ @@ -357,7 +362,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { preinit_thread(L, g); g->allgc = obj2gco(L); /* by now, only object is the main thread */ L->next = NULL; - g->Cstacklimit = L->nCcalls = 0; + L->nCcalls = 0; incnny(L); /* main thread is always non yieldable */ g->frealloc = f; g->ud = ud; diff --git a/lstate.h b/lstate.h index 983aa0d5..a05db376 100644 --- a/lstate.h +++ b/lstate.h @@ -87,49 +87,13 @@ /* -** About 'nCcalls': each thread in Lua (a lua_State) keeps a count of -** how many "C calls" it still can do in the C stack, to avoid C-stack -** overflow. This count is very rough approximation; it considers only -** recursive functions inside the interpreter, as non-recursive calls -** can be considered using a fixed (although unknown) amount of stack -** space. -** -** The count has two parts: the lower part is the count itself; the -** higher part counts the number of non-yieldable calls in the stack. -** (They are together so that we can change both with one instruction.) -** -** Because calls to external C functions can use an unknown amount -** of space (e.g., functions using an auxiliary buffer), calls -** to these functions add more than one to the count (see CSTACKCF). -** -** The proper count excludes the number of CallInfo structures allocated -** by Lua, as a kind of "potential" calls. So, when Lua calls a function -** (and "consumes" one CallInfo), it needs neither to decrement nor to -** check 'nCcalls', as its use of C stack is already accounted for. +** About 'nCcalls': This count has two parts: the lower 16 bits counts +** the number of recursive invocations in the C stack; the higher +** 16 bits counts the number of non-yieldable calls in the stack. +** (They are together so that we can change and save both with one +** instruction.) */ -/* number of "C stack slots" used by an external C function */ -#define CSTACKCF 10 - - -/* -** The C-stack size is sliced in the following zones: -** - larger than CSTACKERR: normal stack; -** - [CSTACKMARK, CSTACKERR]: buffer zone to signal a stack overflow; -** - [CSTACKCF, CSTACKERRMARK]: error-handling zone; -** - below CSTACKERRMARK: buffer zone to signal overflow during overflow; -** (Because the counter can be decremented CSTACKCF at once, we need -** the so called "buffer zones", with at least that size, to properly -** detect a change from one zone to the next.) -*/ -#define CSTACKERR (8 * CSTACKCF) -#define CSTACKMARK (CSTACKERR - (CSTACKCF + 2)) -#define CSTACKERRMARK (CSTACKCF + 2) - - -/* initial limit for the C-stack of threads */ -#define CSTACKTHREAD (2 * CSTACKERR) - /* true if this thread does not have non-yieldable calls in the stack */ #define yieldable(L) (((L)->nCcalls & 0xffff0000) == 0) @@ -144,7 +108,8 @@ /* Decrement the number of non-yieldable calls */ #define decnny(L) ((L)->nCcalls -= 0x10000) - +/* Non-yieldable call increment */ +#define nyci (0x10000 | 1) @@ -290,7 +255,6 @@ typedef struct global_State { TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ lua_WarnFunction warnf; /* warning function */ void *ud_warn; /* auxiliary data to 'warnf' */ - unsigned int Cstacklimit; /* current limit for the C stack */ } global_State; @@ -314,7 +278,7 @@ struct lua_State { CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ volatile lua_Hook hook; ptrdiff_t errfunc; /* current error handling function (stack index) */ - l_uint32 nCcalls; /* number of allowed nested C calls - 'nci' */ + l_uint32 nCcalls; /* number of nested (non-yieldable | C) calls */ int oldpc; /* last pc traced */ int stacksize; int basehookcount; @@ -383,11 +347,11 @@ LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L); LUAI_FUNC void luaE_freeCI (lua_State *L); LUAI_FUNC void luaE_shrinkCI (lua_State *L); +LUAI_FUNC void luaE_checkcstack (lua_State *L); +LUAI_FUNC void luaE_incCstack (lua_State *L); LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont); LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where); -#define luaE_exitCcall(L) ((L)->nCcalls++) - #endif diff --git a/luaconf.h b/luaconf.h index 229413d2..d9cf18ca 100644 --- a/luaconf.h +++ b/luaconf.h @@ -36,21 +36,6 @@ ** ===================================================================== */ -/* >>> move back to llimits.h -@@ LUAI_MAXCCALLS defines the maximum depth for nested calls and -** also limits the maximum depth of other recursive algorithms in -** the implementation, such as syntactic analysis. A value too -** large may allow the interpreter to crash (C-stack overflow). -** The default value seems ok for regular machines, but may be -** too high for restricted hardware. -** The test file 'cstack.lua' may help finding a good limit. -** (It will crash with a limit too high.) -*/ -#if !defined(LUAI_MAXCCALLS) -#define LUAI_MAXCCALLS 200 -#endif - - /* @@ LUA_USE_C89 controls the use of non-ISO-C89 features. ** Define it if you want Lua to avoid the use of a few C99 features diff --git a/lvm.c b/lvm.c index a232e1e7..eadf66bf 100644 --- a/lvm.c +++ b/lvm.c @@ -1124,7 +1124,7 @@ void luaV_finishOp (lua_State *L) { void luaV_execute (lua_State *L, CallInfo *ci) { - const CallInfo *origci = ci; + CallInfo * const origci = ci; LClosure *cl; TValue *k; StkId base; @@ -1624,7 +1624,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { vmcase(OP_TAILCALL) { int b = GETARG_B(i); /* number of arguments + 1 (function) */ int nparams1 = GETARG_C(i); - /* delat is virtual 'func' - real 'func' (vararg functions) */ + /* delta is virtual 'func' - real 'func' (vararg functions) */ int delta = (nparams1) ? ci->u.l.nextraargs + nparams1 : 0; if (b != 0) L->top = ra + b; @@ -1648,7 +1648,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { updatetrap(ci); updatestack(ci); /* stack may have been relocated */ ci->func -= delta; - luaD_poscall(L, ci, cast_int(L->top - ra)); + luaD_poscall(L, ci, cast_int(L->top - ra)); /* finish caller */ goto ret; } ci->func -= delta; diff --git a/manual/manual.of b/manual/manual.of index 8b34b5bd..86631bbc 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -2436,8 +2436,16 @@ When you interact with the Lua API, you are responsible for ensuring consistency. In particular, @emph{you are responsible for controlling stack overflow}. -You can use the function @Lid{lua_checkstack} -to ensure that the stack has enough space for pushing new elements. +When you call any API function, +you must ensure the stack has enough room to accommodate the results. + +There is one exception to the above rule: +When you call a Lua function +without a fixed number of results @seeF{lua_call}, +Lua ensures that the stack has enough space for all results. +However, it does not ensure any extra space. +So, before pushing anything on the stack after such a call +you should use @Lid{lua_checkstack}. Whenever Lua calls C, it ensures that the stack has space for @@ -2446,13 +2454,9 @@ that is, you can safely push up to @id{LUA_MINSTACK} values into it. @id{LUA_MINSTACK} is defined as 20, so that usually you do not have to worry about stack space unless your code has loops pushing elements onto the stack. - -When you call a Lua function -without a fixed number of results @seeF{lua_call}, -Lua ensures that the stack has enough space for all results, -but it does not ensure any extra space. -So, before pushing anything on the stack after such a call -you should use @Lid{lua_checkstack}. +Whenever necessary, +you can use the function @Lid{lua_checkstack} +to ensure that the stack has enough space for pushing new elements. } @@ -2695,7 +2699,7 @@ Therefore, if a @N{C function} @id{foo} calls an API function and this API function yields (directly or indirectly by calling another function that yields), Lua cannot return to @id{foo} any more, -because the @id{longjmp} removes its frame from the C stack. +because the @id{longjmp} removes its frame from the @N{C stack}. To avoid this kind of problem, Lua raises an error whenever it tries to yield across an API call, @@ -2719,7 +2723,7 @@ After the thread resumes, it eventually will finish running the callee function. However, the callee function cannot return to the original function, -because its frame in the C stack was destroyed by the yield. +because its frame in the @N{C stack} was destroyed by the yield. Instead, Lua calls a @def{continuation function}, which was given as an argument to the callee function. As the name implies, @@ -2841,7 +2845,7 @@ and therefore may raise any errors. Converts the @x{acceptable index} @id{idx} into an equivalent @x{absolute index} -(that is, one that does not depend on the stack top). +(that is, one that does not depend on the stack size). } @@ -4340,7 +4344,7 @@ as if it was already marked. Note that, both in case of errors and of a regular return, by the time the @idx{__close} metamethod runs, the @N{C stack} was already unwound, -so that any automatic C variable declared in the calling function +so that any automatic @N{C variable} declared in the calling function will be out of scope. } @@ -4955,20 +4959,6 @@ calling @Lid{lua_yield} with @id{nresults} equal to zero } -@APIEntry{int (lua_setcstacklimit) (lua_State *L, unsigned int limit);| -@apii{0,0,-} - -Sets a new limit for the C stack. -This limit controls how deeply nested calls can go in Lua, -with the intent of avoiding a stack overflow. -Returns the old limit in case of success, -or zero in case of error. -For more details about this function, -see @Lid{debug.setcstacklimit}, -its equivalent in the standard library. - -} - @APIEntry{void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);| @apii{0,0,-} @@ -8756,34 +8746,6 @@ to the userdata @id{u} plus a boolean, } -@LibEntry{debug.setcstacklimit (limit)| - -Sets a new limit for the C stack. -This limit controls how deeply nested calls can go in Lua, -with the intent of avoiding a stack overflow. -A limit too small restricts recursive calls pointlessly; -a limit too large exposes the interpreter to stack-overflow crashes. -Unfortunately, there is no way to know a priori -the maximum safe limit for a platform. - -Each call made from Lua code counts one unit. -Other operations (e.g., calls made from C to Lua or resuming a coroutine) -may have a higher cost. - -This function has the following restrictions: -@description{ -@item{It can only be called from the main coroutine (thread);} -@item{It cannot be called while handling a stack-overflow error;} -@item{@id{limit} must be less than 40000;} -@item{@id{limit} cannot be less than the amount of C stack in use.} -} -If a call does not respect some restriction, -it returns a false value. -Otherwise, -the call returns the old limit. - -} - @LibEntry{debug.sethook ([thread,] hook, mask [, count])| Sets the given function as the debug hook. diff --git a/testes/cstack.lua b/testes/cstack.lua index c1177f3b..5767adf6 100644 --- a/testes/cstack.lua +++ b/testes/cstack.lua @@ -1,75 +1,29 @@ -- $Id: testes/cstack.lua $ -- See Copyright Notice in file all.lua -do return end - -local debug = require "debug" print"testing C-stack overflow detection" -print"If this test crashes, see its file ('cstack.lua')" -- Segmentation faults in these tests probably result from a C-stack --- overflow. To avoid these errors, you can use the function --- 'debug.setcstacklimit' to set a smaller limit for the use of --- C stack by Lua. After finding a reliable limit, you might want --- to recompile Lua with this limit as the value for --- the constant 'LUAI_MAXCCALLS', which defines the default limit. --- (The default limit is printed by this test.) +-- overflow. To avoid these errors, you should set a smaller limit for +-- the use of C stack by Lua, by changing the constant 'LUAI_MAXCCALLS'. -- Alternatively, you can ensure a larger stack for the program. --- For Linux, a limit up to 30_000 seems Ok. Windows cannot go much --- higher than 2_000. - - --- get and print original limit -local origlimit = debug.setcstacklimit(400) -print("default stack limit: " .. origlimit) - - --- Do the tests using the original limit. Or else you may want to change --- 'currentlimit' to lower values to avoid a seg. fault or to higher --- values to check whether they are reliable. -local currentlimit = origlimit -debug.setcstacklimit(currentlimit) -print("current stack limit: " .. currentlimit) - local function checkerror (msg, f, ...) local s, err = pcall(f, ...) assert(not s and string.find(err, msg)) end --- auxiliary function to keep 'count' on the screen even if the program --- crashes. -local count -local back = string.rep("\b", 8) -local function progress () - count = count + 1 - local n = string.format("%-8d", count) - io.stderr:write(back, n) -- erase previous value and write new one -end - - -do print("testing simple recursion:") - count = 0 - local function foo () - progress() - foo() -- do recursive calls until a stack error (or crash) - end - checkerror("stack overflow", foo) - print("\tfinal count: ", count) -end - - do print("testing stack overflow in message handling") - count = 0 + local count = 0 local function loop (x, y, z) - progress() + count = count + 1 return 1 + loop(x, y, z) end local res, msg = xpcall(loop, loop) assert(msg == "error in error handling") - print("\tfinal count: ", count) + print("final count: ", count) end @@ -82,97 +36,66 @@ do print("testing recursion inside pattern matching") end local m = f(80) assert(#m == 80) - checkerror("too complex", f, 200000) + checkerror("too complex", f, 2000) end do print("testing stack-overflow in recursive 'gsub'") - count = 0 + local count = 0 local function foo () - progress() + count = count + 1 string.gsub("a", ".", foo) end checkerror("stack overflow", foo) - print("\tfinal count: ", count) + print("final count: ", count) print("testing stack-overflow in recursive 'gsub' with metatables") - count = 0 + local count = 0 local t = setmetatable({}, {__index = foo}) foo = function () count = count + 1 - progress(count) string.gsub("a", ".", t) end checkerror("stack overflow", foo) - print("\tfinal count: ", count) + print("final count: ", count) end + do -- bug in 5.4.0 print("testing limits in coroutines inside deep calls") - count = 0 + local count = 0 local lim = 1000 local function stack (n) - progress() if n > 0 then return stack(n - 1) + 1 else coroutine.wrap(function () + count = count + 1 stack(lim) end)() end end - print(xpcall(stack, function () return "ok" end, lim)) + local st, msg = xpcall(stack, function () return "ok" end, lim) + assert(not st and msg == "ok") + print("final count: ", count) end -do print("testing changes in C-stack limit") +do + print("nesting of resuming yielded coroutines") + local count = 0 - -- Just an alternative limit, different from the current one - -- (smaller to avoid stack overflows) - local alterlimit = currentlimit * 8 // 10 - - assert(not debug.setcstacklimit(0)) -- limit too small - assert(not debug.setcstacklimit(50000)) -- limit too large - local co = coroutine.wrap (function () - return debug.setcstacklimit(alterlimit) - end) - assert(not co()) -- cannot change C stack inside coroutine - - local n - local function foo () n = n + 1; foo () end - - local function check () - n = 0 - pcall(foo) - return n + local function body () + coroutine.yield() + local f = coroutine.wrap(body) + f(); -- start new coroutine (will stop in previous yield) + count = count + 1 + f() -- call it recursively end - -- set limit to 'alterlimit' - assert(debug.setcstacklimit(alterlimit) == currentlimit) - local limalter = check() - -- set a very low limit (given that there are already several active - -- calls to arrive here) - local lowlimit = 38 - assert(debug.setcstacklimit(lowlimit) == alterlimit) - -- usable limit is much lower, due to active calls - local actuallow = check() - assert(actuallow < lowlimit - 30) - -- now, add 'lowlimit' extra slots, which should all be available - assert(debug.setcstacklimit(lowlimit + lowlimit) == lowlimit) - local lim2 = check() - assert(lim2 == actuallow + lowlimit) - - - -- 'setcstacklimit' works inside protected calls. (The new stack - -- limit is kept when 'pcall' returns.) - assert(pcall(function () - assert(debug.setcstacklimit(alterlimit) == lowlimit * 2) - assert(check() <= limalter) - end)) - - assert(check() == limalter) - -- restore original limit - assert(debug.setcstacklimit(origlimit) == alterlimit) + local f = coroutine.wrap(body) + f() + assert(not pcall(f)) + print("final count: ", count) end - print'OK' diff --git a/testes/errors.lua b/testes/errors.lua index 88918df7..f975b3dd 100644 --- a/testes/errors.lua +++ b/testes/errors.lua @@ -532,7 +532,8 @@ local function testrep (init, rep, close, repc, finalresult) end s = init .. string.rep(rep, 500) local res, msg = load(s) -- 500 levels not ok - assert(not res and string.find(msg, "too many")) + assert(not res and (string.find(msg, "too many") or + string.find(msg, "overflow"))) end testrep("local a; a", ",a", "= 1", ",1") -- multiple assignment