Browse Source

Revision of stackless implementation

- more organized handling of 'nCcalls'
- comments
- deprecation of 'setcstacklimit'
pull/25/head
Roberto Ierusalimschy 4 years ago
parent
commit
287b302acb
  1. 2
      all
  2. 5
      ldblib.c
  3. 52
      ldo.c
  4. 11
      llimits.h
  5. 6
      lparser.c
  6. 45
      lstate.c
  7. 56
      lstate.h
  8. 15
      luaconf.h
  9. 6
      lvm.c
  10. 72
      manual/manual.of
  11. 137
      testes/cstack.lua
  12. 3
      testes/errors.lua

2
all

@ -1,7 +1,7 @@
make -s -j make -s -j
cd testes/libs; make -s cd testes/libs; make -s
cd .. # back to directory 'testes' cd .. # back to directory 'testes'
ulimit -S -s 2000 ulimit -S -s 1000
if { ../lua -W all.lua; } then if { ../lua -W all.lua; } then
echo -e "\n\n final OK!!!!\n\n" echo -e "\n\n final OK!!!!\n\n"
else else

5
ldblib.c

@ -440,10 +440,7 @@ static int db_traceback (lua_State *L) {
static int db_setcstacklimit (lua_State *L) { static int db_setcstacklimit (lua_State *L) {
int limit = (int)luaL_checkinteger(L, 1); int limit = (int)luaL_checkinteger(L, 1);
int res = lua_setcstacklimit(L, limit); int res = lua_setcstacklimit(L, limit);
if (res == 0) lua_pushinteger(L, res);
lua_pushboolean(L, 0);
else
lua_pushinteger(L, res);
return 1; return 1;
} }

52
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. ** Prepares the call to a function (C or Lua). For C functions, also do
** The arguments are on the stack, right after the function. ** the call. The function to be called is at '*func'. The arguments are
** When returns, all the results are on the stack, starting at the original ** on the stack, right after the function. Returns true if the call was
** function position. ** 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) { int luaD_precall (lua_State *L, StkId func, int nresults) {
lua_CFunction f; 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) ** Call a function (C or Lua). 'inc' can be 1 (increment number
luaG_runerror(L, "C stack overflow"); ** of recursive invocations in the C stack) or nyci (the same plus
else if (getCcalls(L) >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) ** increment number of non-yieldable calls).
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ */
} static void docall (lua_State *L, StkId func, int nResults, int inc) {
L->nCcalls += inc;
void luaD_call (lua_State *L, StkId func, int nResults) {
L->nCcalls++;
if (getCcalls(L) >= LUAI_MAXCCALLS) if (getCcalls(L) >= LUAI_MAXCCALLS)
stackerror(L); luaE_checkcstack(L);
if (!luaD_precall(L, func, nResults)) /* is a Lua function? */ if (!luaD_precall(L, func, nResults)) /* is a Lua function? */
luaV_execute(L, L->ci); /* call it */ 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. ** Similar to 'luaD_call', but does not allow yields during the call.
*/ */
void luaD_callnoyield (lua_State *L, StkId func, int nResults) { void luaD_callnoyield (lua_State *L, StkId func, int nResults) {
incnny(L); return docall(L, func, nResults, nyci);
luaD_call(L, func, nResults);
decnny(L);
} }
@ -650,13 +653,12 @@ static void resume (lua_State *L, void *ud) {
int n = *(cast(int*, ud)); /* number of arguments */ int n = *(cast(int*, ud)); /* number of arguments */
StkId firstArg = L->top - n; /* first argument */ StkId firstArg = L->top - n; /* first argument */
CallInfo *ci = L->ci; CallInfo *ci = L->ci;
if (L->status == LUA_OK) { /* starting a coroutine? */ if (L->status == LUA_OK) /* starting a coroutine? */
if (!luaD_precall(L, firstArg - 1, LUA_MULTRET)) /* Lua function? */ docall(L, firstArg - 1, LUA_MULTRET, 1); /* just call its body */
luaV_execute(L, L->ci); /* call it */
}
else { /* resuming from previous yield */ else { /* resuming from previous yield */
lua_assert(L->status == LUA_YIELD); lua_assert(L->status == LUA_YIELD);
L->status = LUA_OK; /* mark that it is running (again) */ L->status = LUA_OK; /* mark that it is running (again) */
luaE_incCstack(L); /* control the C stack */
if (isLua(ci)) /* yielded inside a hook? */ if (isLua(ci)) /* yielded inside a hook? */
luaV_execute(L, ci); /* just continue running Lua code */ luaV_execute(L, ci); /* just continue running Lua code */
else { /* 'common' yield */ 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? */ else if (L->status != LUA_YIELD) /* ended with errors? */
return resume_error(L, "cannot resume dead coroutine", nargs); return resume_error(L, "cannot resume dead coroutine", nargs);
L->nCcalls = (from) ? getCcalls(from) + 1 : 1; L->nCcalls = (from) ? getCcalls(from) : 0;
if (getCcalls(L) >= LUAI_MAXCCALLS)
return resume_error(L, "C stack overflow", nargs);
luai_userstateresume(L, nargs); luai_userstateresume(L, nargs);
api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs);
status = luaD_rawrunprotected(L, resume, &nargs); status = luaD_rawrunprotected(L, resume, &nargs);

11
llimits.h

@ -234,6 +234,17 @@ typedef l_uint32 Instruction;
#endif #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 ** macros that are executed whenever program enters the Lua core
** ('lua_lock') and leaves the core ('lua_unlock') ** ('lua_lock') and leaves the core ('lua_unlock')

6
lparser.c

@ -489,11 +489,7 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) {
} }
static void enterlevel (LexState *ls) { #define enterlevel(ls) luaE_incCstack(ls->L)
lua_State *L = ls->L;
L->nCcalls++;
checklimit(ls->fs, getCcalls(L), LUAI_MAXCCALLS, "C levels");
}
#define leavelevel(ls) ((ls)->L->nCcalls--) #define leavelevel(ls) ((ls)->L->nCcalls--)

45
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) { LUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) {
global_State *g = G(L); UNUSED(L); UNUSED(limit);
int ccalls; return LUAI_MAXCCALLS; /* warning?? */
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 */
}
} }
@ -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) { static void stack_init (lua_State *L1, lua_State *L) {
int i; CallInfo *ci; int i; CallInfo *ci;
/* initialize stack array */ /* initialize stack array */
@ -357,7 +362,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
preinit_thread(L, g); preinit_thread(L, g);
g->allgc = obj2gco(L); /* by now, only object is the main thread */ g->allgc = obj2gco(L); /* by now, only object is the main thread */
L->next = NULL; L->next = NULL;
g->Cstacklimit = L->nCcalls = 0; L->nCcalls = 0;
incnny(L); /* main thread is always non yieldable */ incnny(L); /* main thread is always non yieldable */
g->frealloc = f; g->frealloc = f;
g->ud = ud; g->ud = ud;

56
lstate.h

@ -87,48 +87,12 @@
/* /*
** About 'nCcalls': each thread in Lua (a lua_State) keeps a count of ** About 'nCcalls': This count has two parts: the lower 16 bits counts
** how many "C calls" it still can do in the C stack, to avoid C-stack ** the number of recursive invocations in the C stack; the higher
** overflow. This count is very rough approximation; it considers only ** 16 bits counts the number of non-yieldable calls in the stack.
** recursive functions inside the interpreter, as non-recursive calls ** (They are together so that we can change and save both with one
** can be considered using a fixed (although unknown) amount of stack ** instruction.)
** 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.
*/
/* 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 */ /* true if this thread does not have non-yieldable calls in the stack */
@ -144,7 +108,8 @@
/* Decrement the number of non-yieldable calls */ /* Decrement the number of non-yieldable calls */
#define decnny(L) ((L)->nCcalls -= 0x10000) #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 */ TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */
lua_WarnFunction warnf; /* warning function */ lua_WarnFunction warnf; /* warning function */
void *ud_warn; /* auxiliary data to 'warnf' */ void *ud_warn; /* auxiliary data to 'warnf' */
unsigned int Cstacklimit; /* current limit for the C stack */
} global_State; } global_State;
@ -314,7 +278,7 @@ struct lua_State {
CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ CallInfo base_ci; /* CallInfo for first level (C calling Lua) */
volatile lua_Hook hook; volatile lua_Hook hook;
ptrdiff_t errfunc; /* current error handling function (stack index) */ 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 oldpc; /* last pc traced */
int stacksize; int stacksize;
int basehookcount; 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 CallInfo *luaE_extendCI (lua_State *L);
LUAI_FUNC void luaE_freeCI (lua_State *L); LUAI_FUNC void luaE_freeCI (lua_State *L);
LUAI_FUNC void luaE_shrinkCI (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_warning (lua_State *L, const char *msg, int tocont);
LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where); LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where);
#define luaE_exitCcall(L) ((L)->nCcalls++)
#endif #endif

15
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. @@ 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 ** Define it if you want Lua to avoid the use of a few C99 features

6
lvm.c

@ -1124,7 +1124,7 @@ void luaV_finishOp (lua_State *L) {
void luaV_execute (lua_State *L, CallInfo *ci) { void luaV_execute (lua_State *L, CallInfo *ci) {
const CallInfo *origci = ci; CallInfo * const origci = ci;
LClosure *cl; LClosure *cl;
TValue *k; TValue *k;
StkId base; StkId base;
@ -1624,7 +1624,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
vmcase(OP_TAILCALL) { vmcase(OP_TAILCALL) {
int b = GETARG_B(i); /* number of arguments + 1 (function) */ int b = GETARG_B(i); /* number of arguments + 1 (function) */
int nparams1 = GETARG_C(i); 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; int delta = (nparams1) ? ci->u.l.nextraargs + nparams1 : 0;
if (b != 0) if (b != 0)
L->top = ra + b; L->top = ra + b;
@ -1648,7 +1648,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
updatetrap(ci); updatetrap(ci);
updatestack(ci); /* stack may have been relocated */ updatestack(ci); /* stack may have been relocated */
ci->func -= delta; 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; goto ret;
} }
ci->func -= delta; ci->func -= delta;

72
manual/manual.of

@ -2436,8 +2436,16 @@ When you interact with the Lua API,
you are responsible for ensuring consistency. you are responsible for ensuring consistency.
In particular, In particular,
@emph{you are responsible for controlling stack overflow}. @emph{you are responsible for controlling stack overflow}.
You can use the function @Lid{lua_checkstack} When you call any API function,
to ensure that the stack has enough space for pushing new elements. 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, Whenever Lua calls C,
it ensures that the stack has space for 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, @id{LUA_MINSTACK} is defined as 20,
so that usually you do not have to worry about stack space so that usually you do not have to worry about stack space
unless your code has loops pushing elements onto the stack. unless your code has loops pushing elements onto the stack.
Whenever necessary,
When you call a Lua function you can use the function @Lid{lua_checkstack}
without a fixed number of results @seeF{lua_call}, to ensure that the stack has enough space for pushing new elements.
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}.
} }
@ -2695,7 +2699,7 @@ Therefore, if a @N{C function} @id{foo} calls an API function
and this API function yields and this API function yields
(directly or indirectly by calling another function that yields), (directly or indirectly by calling another function that yields),
Lua cannot return to @id{foo} any more, 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, To avoid this kind of problem,
Lua raises an error whenever it tries to yield across an API call, 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. it eventually will finish running the callee function.
However, However,
the callee function cannot return to the original function, 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}, Instead, Lua calls a @def{continuation function},
which was given as an argument to the callee function. which was given as an argument to the callee function.
As the name implies, As the name implies,
@ -2841,7 +2845,7 @@ and therefore may raise any errors.
Converts the @x{acceptable index} @id{idx} Converts the @x{acceptable index} @id{idx}
into an equivalent @x{absolute index} 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, Note that, both in case of errors and of a regular return,
by the time the @idx{__close} metamethod runs, by the time the @idx{__close} metamethod runs,
the @N{C stack} was already unwound, 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. 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);| @APIEntry{void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);|
@apii{0,0,-} @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])| @LibEntry{debug.sethook ([thread,] hook, mask [, count])|
Sets the given function as the debug hook. Sets the given function as the debug hook.

137
testes/cstack.lua

@ -1,75 +1,29 @@
-- $Id: testes/cstack.lua $ -- $Id: testes/cstack.lua $
-- See Copyright Notice in file all.lua -- See Copyright Notice in file all.lua
do return end
local debug = require "debug"
print"testing C-stack overflow detection" 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 -- Segmentation faults in these tests probably result from a C-stack
-- overflow. To avoid these errors, you can use the function -- overflow. To avoid these errors, you should set a smaller limit for
-- 'debug.setcstacklimit' to set a smaller limit for the use of -- the use of C stack by Lua, by changing the constant 'LUAI_MAXCCALLS'.
-- 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.)
-- Alternatively, you can ensure a larger stack for the program. -- 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 <const> = 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 <const> = origlimit
debug.setcstacklimit(currentlimit)
print("current stack limit: " .. currentlimit)
local function checkerror (msg, f, ...) local function checkerror (msg, f, ...)
local s, err = pcall(f, ...) local s, err = pcall(f, ...)
assert(not s and string.find(err, msg)) assert(not s and string.find(err, msg))
end 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") do print("testing stack overflow in message handling")
count = 0 local count = 0
local function loop (x, y, z) local function loop (x, y, z)
progress() count = count + 1
return 1 + loop(x, y, z) return 1 + loop(x, y, z)
end end
local res, msg = xpcall(loop, loop) local res, msg = xpcall(loop, loop)
assert(msg == "error in error handling") assert(msg == "error in error handling")
print("\tfinal count: ", count) print("final count: ", count)
end end
@ -82,97 +36,66 @@ do print("testing recursion inside pattern matching")
end end
local m = f(80) local m = f(80)
assert(#m == 80) assert(#m == 80)
checkerror("too complex", f, 200000) checkerror("too complex", f, 2000)
end end
do print("testing stack-overflow in recursive 'gsub'") do print("testing stack-overflow in recursive 'gsub'")
count = 0 local count = 0
local function foo () local function foo ()
progress() count = count + 1
string.gsub("a", ".", foo) string.gsub("a", ".", foo)
end end
checkerror("stack overflow", foo) checkerror("stack overflow", foo)
print("\tfinal count: ", count) print("final count: ", count)
print("testing stack-overflow in recursive 'gsub' with metatables") print("testing stack-overflow in recursive 'gsub' with metatables")
count = 0 local count = 0
local t = setmetatable({}, {__index = foo}) local t = setmetatable({}, {__index = foo})
foo = function () foo = function ()
count = count + 1 count = count + 1
progress(count)
string.gsub("a", ".", t) string.gsub("a", ".", t)
end end
checkerror("stack overflow", foo) checkerror("stack overflow", foo)
print("\tfinal count: ", count) print("final count: ", count)
end end
do -- bug in 5.4.0 do -- bug in 5.4.0
print("testing limits in coroutines inside deep calls") print("testing limits in coroutines inside deep calls")
count = 0 local count = 0
local lim = 1000 local lim = 1000
local function stack (n) local function stack (n)
progress()
if n > 0 then return stack(n - 1) + 1 if n > 0 then return stack(n - 1) + 1
else coroutine.wrap(function () else coroutine.wrap(function ()
count = count + 1
stack(lim) stack(lim)
end)() end)()
end 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 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 local function body ()
-- (smaller to avoid stack overflows) coroutine.yield()
local alterlimit <const> = currentlimit * 8 // 10 local f = coroutine.wrap(body)
f(); -- start new coroutine (will stop in previous yield)
assert(not debug.setcstacklimit(0)) -- limit too small count = count + 1
assert(not debug.setcstacklimit(50000)) -- limit too large f() -- call it recursively
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
end end
-- set limit to 'alterlimit' local f = coroutine.wrap(body)
assert(debug.setcstacklimit(alterlimit) == currentlimit) f()
local limalter <const> = check() assert(not pcall(f))
-- set a very low limit (given that there are already several active print("final count: ", count)
-- calls to arrive here)
local lowlimit <const> = 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 <const> = 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)
end end
print'OK' print'OK'

3
testes/errors.lua

@ -532,7 +532,8 @@ local function testrep (init, rep, close, repc, finalresult)
end end
s = init .. string.rep(rep, 500) s = init .. string.rep(rep, 500)
local res, msg = load(s) -- 500 levels not ok 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 end
testrep("local a; a", ",a", "= 1", ",1") -- multiple assignment testrep("local a; a", ",a", "= 1", ",1") -- multiple assignment

Loading…
Cancel
Save