mirror of
https://github.com/lua/lua
synced 2024-11-22 04:41:23 +03:00
Revision of stackless implementation
- more organized handling of 'nCcalls' - comments - deprecation of 'setcstacklimit'
This commit is contained in:
parent
5d8ce05b3f
commit
287b302acb
2
all
2
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
|
||||
|
5
ldblib.c
5
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;
|
||||
}
|
||||
|
||||
|
52
ldo.c
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.
|
||||
** 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);
|
||||
|
11
llimits.h
11
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')
|
||||
|
@ -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--)
|
||||
|
45
lstate.c
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) {
|
||||
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;
|
||||
|
56
lstate.h
56
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
|
||||
|
||||
|
15
luaconf.h
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.
|
||||
** Define it if you want Lua to avoid the use of a few C99 features
|
||||
|
6
lvm.c
6
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;
|
||||
|
@ -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.
|
||||
|
@ -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 <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 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 <const> = 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 <const> = check()
|
||||
-- set a very low limit (given that there are already several active
|
||||
-- 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)
|
||||
local f = coroutine.wrap(body)
|
||||
f()
|
||||
assert(not pcall(f))
|
||||
print("final count: ", count)
|
||||
end
|
||||
|
||||
|
||||
print'OK'
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user