commit be73f72fcc944a8ebae2c60d2ce84139acb011b9
parent 3cd9b56ae6002b4ef28d2467abd119606ae625d3
Author: Roberto Ierusalimschy <roberto@inf.puc-rio.br>
Date: Tue, 18 Jun 2019 16:51:55 -0300
New function 'setCstacklimit'
Added new functions to dynamically set the C-stack limit
('lua_setCstacklimit' in the C-API, 'debug.setCstacklimit' in Lua).
Diffstat:
9 files changed, 149 insertions(+), 12 deletions(-)
diff --git a/ldblib.c b/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}
};
diff --git a/ldo.c b/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)
diff --git a/lstate.c b/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;
diff --git a/lstate.h b/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;
diff --git a/ltests.h b/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))
diff --git a/lua.h b/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;
diff --git a/manual/manual.of b/manual/manual.of
@@ -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.
diff --git a/testes/cstack.lua b/testes/cstack.lua
@@ -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'
diff --git a/testes/errors.lua b/testes/errors.lua
@@ -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")