mirror of https://github.com/rusefi/lua.git
New function 'setCstacklimit'
Added new functions to dynamically set the C-stack limit ('lua_setCstacklimit' in the C-API, 'debug.setCstacklimit' in Lua).
This commit is contained in:
parent
3cd9b56ae6
commit
be73f72fcc
12
ldblib.c
12
ldblib.c
|
@ -437,6 +437,17 @@ 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);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static const luaL_Reg dblib[] = {
|
||||
{"debug", db_debug},
|
||||
{"getuservalue", db_getuservalue},
|
||||
|
@ -454,6 +465,7 @@ static const luaL_Reg dblib[] = {
|
|||
{"setmetatable", db_setmetatable},
|
||||
{"setupvalue", db_setupvalue},
|
||||
{"traceback", db_traceback},
|
||||
{"setCstacklimit", db_setCstacklimit},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
7
ldo.c
7
ldo.c
|
@ -139,7 +139,8 @@ l_noret luaD_throw (lua_State *L, int errcode) {
|
|||
|
||||
|
||||
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
|
||||
l_uint32 oldnCcalls = L->nCcalls + L->nci;
|
||||
global_State *g = G(L);
|
||||
l_uint32 oldnCcalls = g->Cstacklimit - (L->nCcalls + L->nci);
|
||||
struct lua_longjmp lj;
|
||||
lj.status = LUA_OK;
|
||||
lj.previous = L->errorJmp; /* chain new error handler */
|
||||
|
@ -148,7 +149,7 @@ int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
|
|||
(*f)(L, ud);
|
||||
);
|
||||
L->errorJmp = lj.previous; /* restore old error handler */
|
||||
L->nCcalls = oldnCcalls - L->nci;
|
||||
L->nCcalls = g->Cstacklimit - oldnCcalls - L->nci;
|
||||
return lj.status;
|
||||
}
|
||||
|
||||
|
@ -671,7 +672,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);
|
||||
if (from == NULL)
|
||||
L->nCcalls = LUAI_MAXCSTACK;
|
||||
L->nCcalls = CSTACKTHREAD;
|
||||
else /* correct 'nCcalls' for this thread */
|
||||
L->nCcalls = getCcalls(from) + from->nci - L->nci - CSTACKCF;
|
||||
if (L->nCcalls <= CSTACKERR)
|
||||
|
|
28
lstate.c
28
lstate.c
|
@ -96,6 +96,29 @@ 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 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Decrement count of "C calls" and check for overflows. In case of
|
||||
** a stack overflow, check appropriate error ("regular" overflow or
|
||||
|
@ -121,7 +144,7 @@ void luaE_enterCcall (lua_State *L) {
|
|||
else if (ncalls >= CSTACKMARK) {
|
||||
/* not in error-handling zone; raise the error now */
|
||||
L->nCcalls = (CSTACKMARK - 1); /* enter error-handling zone */
|
||||
luaG_runerror(L, "C stack overflow1");
|
||||
luaG_runerror(L, "C stack overflow");
|
||||
}
|
||||
/* else stack is in the error-handling zone;
|
||||
allow message handler to work */
|
||||
|
@ -263,7 +286,7 @@ static void preinit_thread (lua_State *L, global_State *g) {
|
|||
L->stacksize = 0;
|
||||
L->twups = L; /* thread has no upvalues */
|
||||
L->errorJmp = NULL;
|
||||
L->nCcalls = LUAI_MAXCSTACK + CSTACKERR;
|
||||
L->nCcalls = CSTACKTHREAD;
|
||||
L->hook = NULL;
|
||||
L->hookmask = 0;
|
||||
L->basehookcount = 0;
|
||||
|
@ -365,6 +388,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 = LUAI_MAXCSTACK;
|
||||
g->frealloc = f;
|
||||
g->ud = ud;
|
||||
g->warnf = NULL;
|
||||
|
|
5
lstate.h
5
lstate.h
|
@ -103,6 +103,10 @@
|
|||
#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)
|
||||
|
||||
|
@ -267,6 +271,7 @@ 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;
|
||||
|
||||
|
||||
|
|
2
ltests.h
2
ltests.h
|
@ -31,7 +31,7 @@
|
|||
|
||||
/* compiled with -O0, Lua uses a lot of C stack space... */
|
||||
#undef LUAI_MAXCSTACK
|
||||
#define LUAI_MAXCSTACK 400
|
||||
#define LUAI_MAXCSTACK (400 + CSTACKERR)
|
||||
|
||||
/* to avoid warnings, and to make sure value is really unused */
|
||||
#define UNUSED(x) (x=0, (void)(x))
|
||||
|
|
1
lua.h
1
lua.h
|
@ -462,6 +462,7 @@ LUA_API lua_Hook (lua_gethook) (lua_State *L);
|
|||
LUA_API int (lua_gethookmask) (lua_State *L);
|
||||
LUA_API int (lua_gethookcount) (lua_State *L);
|
||||
|
||||
LUA_API int (lua_setCstacklimit) (lua_State *L, unsigned int limit);
|
||||
|
||||
struct lua_Debug {
|
||||
int event;
|
||||
|
|
|
@ -4803,6 +4803,20 @@ 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,-}
|
||||
|
||||
|
@ -8516,6 +8530,34 @@ 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.}
|
||||
}
|
||||
In case of success,
|
||||
this function returns the old limit.
|
||||
In case of error,
|
||||
it returns @false.
|
||||
|
||||
}
|
||||
|
||||
@LibEntry{debug.sethook ([thread,] hook, mask [, count])|
|
||||
|
||||
Sets the given function as the debug hook.
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
-- $Id: testes/cstack.lua $
|
||||
-- See Copyright Notice in file all.lua
|
||||
|
||||
local debug = require "debug"
|
||||
|
||||
print"testing C-stack overflow detection"
|
||||
|
||||
local origlimit = debug.setCstacklimit(400)
|
||||
print("current stack limit: " .. origlimit)
|
||||
debug.setCstacklimit(origlimit)
|
||||
|
||||
-- Segmentation faults in these tests probably result from a C-stack
|
||||
-- overflow. To avoid these errors, recompile Lua with a smaller
|
||||
-- value for the constant 'LUAI_MAXCCALLS' or else ensure a larger
|
||||
|
@ -79,4 +85,46 @@ do print("testing stack-overflow in recursive 'gsub'")
|
|||
print("\tfinal count: ", count)
|
||||
end
|
||||
|
||||
|
||||
do print("testing changes in C-stack limit")
|
||||
|
||||
assert(not debug.setCstacklimit(0)) -- limit too small
|
||||
assert(not debug.setCstacklimit(50000)) -- limit too large
|
||||
local co = coroutine.wrap (function ()
|
||||
return debug.setCstacklimit(400)
|
||||
end)
|
||||
assert(co() == false) -- 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
|
||||
|
||||
assert(debug.setCstacklimit(400) == origlimit)
|
||||
local lim400 = check()
|
||||
-- a very low limit (given that the several calls to arive here)
|
||||
local lowlimit = 38
|
||||
assert(debug.setCstacklimit(lowlimit) == 400)
|
||||
assert(check() < lowlimit - 30)
|
||||
assert(debug.setCstacklimit(600) == lowlimit)
|
||||
local lim600 = check()
|
||||
assert(lim600 == lim400 + 200)
|
||||
|
||||
|
||||
-- 'setCstacklimit' works inside protected calls. (The new stack
|
||||
-- limit is kept when 'pcall' returns.)
|
||||
assert(pcall(function ()
|
||||
assert(debug.setCstacklimit(400) == 600)
|
||||
assert(check() <= lim400)
|
||||
end))
|
||||
|
||||
assert(check() == lim400)
|
||||
assert(debug.setCstacklimit(origlimit) == 400) -- restore original limit
|
||||
end
|
||||
|
||||
|
||||
print'OK'
|
||||
|
|
|
@ -523,9 +523,13 @@ end
|
|||
|
||||
-- testing syntax limits
|
||||
|
||||
local function testrep (init, rep, close, repc)
|
||||
local function testrep (init, rep, close, repc, finalresult)
|
||||
local s = init .. string.rep(rep, 100) .. close .. string.rep(repc, 100)
|
||||
assert(load(s)) -- 100 levels is OK
|
||||
local res, msg = load(s)
|
||||
assert(res) -- 100 levels is OK
|
||||
if (finalresult) then
|
||||
assert(res() == finalresult)
|
||||
end
|
||||
s = init .. string.rep(rep, 10000)
|
||||
local res, msg = load(s) -- 10000 levels not ok
|
||||
assert(not res and (string.find(msg, "too many registers") or
|
||||
|
@ -534,14 +538,14 @@ end
|
|||
|
||||
testrep("local a; a", ",a", "= 1", ",1") -- multiple assignment
|
||||
testrep("local a; a=", "{", "0", "}")
|
||||
testrep("local a; a=", "(", "2", ")")
|
||||
testrep("local a; ", "a(", "2", ")")
|
||||
testrep("return ", "(", "2", ")", 2)
|
||||
testrep("local function a (x) return x end; return ", "a(", "2.2", ")", 2.2)
|
||||
testrep("", "do ", "", " end")
|
||||
testrep("", "while a do ", "", " end")
|
||||
testrep("local a; ", "if a then else ", "", " end")
|
||||
testrep("", "function foo () ", "", " end")
|
||||
testrep("local a; a=", "a..", "a", "")
|
||||
testrep("local a; a=", "a^", "a", "")
|
||||
testrep("local a = ''; return ", "a..", "'a'", "", "a")
|
||||
testrep("local a = 1; return ", "a^", "a", "", 1)
|
||||
|
||||
checkmessage("a = f(x" .. string.rep(",x", 260) .. ")", "too many registers")
|
||||
|
||||
|
|
Loading…
Reference in New Issue