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:
Roberto Ierusalimschy 2019-06-18 16:52:22 -03:00
parent 3cd9b56ae6
commit be73f72fcc
9 changed files with 149 additions and 12 deletions

View File

@ -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
View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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
View File

@ -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;

View File

@ -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.

View File

@ -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'

View File

@ -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")