Browse Source

New function 'setCstacklimit'

Added new functions to dynamically set the C-stack limit
('lua_setCstacklimit' in the C-API, 'debug.setCstacklimit' in Lua).
Roberto Ierusalimschy 6 years ago
parent
commit
be73f72fcc
9 changed files with 149 additions and 12 deletions
  1. 12 0
      ldblib.c
  2. 4 3
      ldo.c
  3. 26 2
      lstate.c
  4. 5 0
      lstate.h
  5. 1 1
      ltests.h
  6. 1 0
      lua.h
  7. 42 0
      manual/manual.of
  8. 48 0
      testes/cstack.lua
  9. 10 6
      testes/errors.lua

+ 12 - 0
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[] = {
 static const luaL_Reg dblib[] = {
   {"debug", db_debug},
   {"debug", db_debug},
   {"getuservalue", db_getuservalue},
   {"getuservalue", db_getuservalue},
@@ -454,6 +465,7 @@ static const luaL_Reg dblib[] = {
   {"setmetatable", db_setmetatable},
   {"setmetatable", db_setmetatable},
   {"setupvalue", db_setupvalue},
   {"setupvalue", db_setupvalue},
   {"traceback", db_traceback},
   {"traceback", db_traceback},
+  {"setCstacklimit", db_setCstacklimit},
   {NULL, NULL}
   {NULL, NULL}
 };
 };
 
 

+ 4 - 3
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) {
 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;
   struct lua_longjmp lj;
   lj.status = LUA_OK;
   lj.status = LUA_OK;
   lj.previous = L->errorJmp;  /* chain new error handler */
   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);
     (*f)(L, ud);
   );
   );
   L->errorJmp = lj.previous;  /* restore old error handler */
   L->errorJmp = lj.previous;  /* restore old error handler */
-  L->nCcalls = oldnCcalls - L->nci;
+  L->nCcalls = g->Cstacklimit - oldnCcalls - L->nci;
   return lj.status;
   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? */
   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);
   if (from == NULL)
   if (from == NULL)
-    L->nCcalls = LUAI_MAXCSTACK;
+    L->nCcalls = CSTACKTHREAD;
   else  /* correct 'nCcalls' for this thread */
   else  /* correct 'nCcalls' for this thread */
     L->nCcalls = getCcalls(from) + from->nci - L->nci - CSTACKCF;
     L->nCcalls = getCcalls(from) + from->nci - L->nci - CSTACKCF;
   if (L->nCcalls <= CSTACKERR)
   if (L->nCcalls <= CSTACKERR)

+ 26 - 2
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
 ** Decrement count of "C calls" and check for overflows. In case of
 ** a stack overflow, check appropriate error ("regular" overflow or
 ** a stack overflow, check appropriate error ("regular" overflow or
@@ -121,7 +144,7 @@ void luaE_enterCcall (lua_State *L) {
       else if (ncalls >= CSTACKMARK) {
       else if (ncalls >= CSTACKMARK) {
         /* not in error-handling zone; raise the error now */
         /* not in error-handling zone; raise the error now */
         L->nCcalls = (CSTACKMARK - 1);  /* enter error-handling zone */
         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;
       /* else stack is in the error-handling zone;
          allow message handler to work */
          allow message handler to work */
@@ -263,7 +286,7 @@ static void preinit_thread (lua_State *L, global_State *g) {
   L->stacksize = 0;
   L->stacksize = 0;
   L->twups = L;  /* thread has no upvalues */
   L->twups = L;  /* thread has no upvalues */
   L->errorJmp = NULL;
   L->errorJmp = NULL;
-  L->nCcalls = LUAI_MAXCSTACK + CSTACKERR;
+  L->nCcalls = CSTACKTHREAD;
   L->hook = NULL;
   L->hook = NULL;
   L->hookmask = 0;
   L->hookmask = 0;
   L->basehookcount = 0;
   L->basehookcount = 0;
@@ -365,6 +388,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 = LUAI_MAXCSTACK;
   g->frealloc = f;
   g->frealloc = f;
   g->ud = ud;
   g->ud = ud;
   g->warnf = NULL;
   g->warnf = NULL;

+ 5 - 0
lstate.h

@@ -103,6 +103,10 @@
 #define CSTACKERRMARK	(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 */
 #define yieldable(L)		(((L)->nCcalls & 0xffff0000) == 0)
 #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 */
   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;
 
 
 
 

+ 1 - 1
ltests.h

@@ -31,7 +31,7 @@
 
 
 /* compiled with -O0, Lua uses a lot of C stack space... */
 /* compiled with -O0, Lua uses a lot of C stack space... */
 #undef LUAI_MAXCSTACK
 #undef LUAI_MAXCSTACK
-#define LUAI_MAXCSTACK	400
+#define LUAI_MAXCSTACK	(400 + CSTACKERR)
 
 
 /* to avoid warnings, and to make sure value is really unused */
 /* to avoid warnings, and to make sure value is really unused */
 #define UNUSED(x)       (x=0, (void)(x))
 #define UNUSED(x)       (x=0, (void)(x))

+ 1 - 0
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_gethookmask) (lua_State *L);
 LUA_API int (lua_gethookcount) (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 {
 struct lua_Debug {
   int event;
   int event;

+ 42 - 0
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);|
 @APIEntry{void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);|
 @apii{0,0,-}
 @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])|
 @LibEntry{debug.sethook ([thread,] hook, mask [, count])|
 
 
 Sets the given function as the debug hook.
 Sets the given function as the debug hook.

+ 48 - 0
testes/cstack.lua

@@ -1,8 +1,14 @@
 -- $Id: testes/cstack.lua $
 -- $Id: testes/cstack.lua $
 -- See Copyright Notice in file all.lua
 -- See Copyright Notice in file all.lua
 
 
+local debug = require "debug"
+
 print"testing C-stack overflow detection"
 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
 -- Segmentation faults in these tests probably result from a C-stack
 -- overflow. To avoid these errors, recompile Lua with a smaller
 -- overflow. To avoid these errors, recompile Lua with a smaller
 -- value for the constant 'LUAI_MAXCCALLS' or else ensure a larger
 -- 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)
   print("\tfinal count: ", count)
 end
 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'
 print'OK'

+ 10 - 6
testes/errors.lua

@@ -523,9 +523,13 @@ end
 
 
 -- testing syntax limits
 -- 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)
   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)
   s = init .. string.rep(rep, 10000)
   local res, msg = load(s)   -- 10000 levels not ok
   local res, msg = load(s)   -- 10000 levels not ok
   assert(not res and (string.find(msg, "too many registers") or
   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", ",a", "= 1", ",1")    -- multiple assignment
 testrep("local a; a=", "{", "0", "}")
 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("", "do ", "", " end")
 testrep("", "while a do ", "", " end")
 testrep("", "while a do ", "", " end")
 testrep("local a; ", "if a then else ", "", " end")
 testrep("local a; ", "if a then else ", "", " end")
 testrep("", "function foo () ", "", " 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")
 checkmessage("a = f(x" .. string.rep(",x", 260) .. ")", "too many registers")