diff options
| -rw-r--r-- | ldo.c | 54 | ||||
| -rw-r--r-- | testes/cstack.lua | 50 |
2 files changed, 87 insertions, 17 deletions
| @@ -207,50 +207,72 @@ int luaD_reallocstack (lua_State *L, int newsize, int raiseerror) { | |||
| 207 | */ | 207 | */ |
| 208 | int luaD_growstack (lua_State *L, int n, int raiseerror) { | 208 | int luaD_growstack (lua_State *L, int n, int raiseerror) { |
| 209 | int size = L->stacksize; | 209 | int size = L->stacksize; |
| 210 | int newsize = 2 * size; /* tentative new size */ | 210 | if (unlikely(size > LUAI_MAXSTACK)) { |
| 211 | if (unlikely(size > LUAI_MAXSTACK)) { /* need more space after extra size? */ | 211 | /* if stack is larger than maximum, thread is already using the |
| 212 | extra space reserved for errors, that is, thread is handling | ||
| 213 | a stack error; cannot grow further than that. */ | ||
| 214 | lua_assert(L->stacksize == ERRORSTACKSIZE); | ||
| 212 | if (raiseerror) | 215 | if (raiseerror) |
| 213 | luaD_throw(L, LUA_ERRERR); /* error inside message handler */ | 216 | luaD_throw(L, LUA_ERRERR); /* error inside message handler */ |
| 214 | else return 0; | 217 | return 0; /* if not 'raiseerror', just signal it */ |
| 215 | } | 218 | } |
| 216 | else { | 219 | else { |
| 220 | int newsize = 2 * size; /* tentative new size */ | ||
| 217 | int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK; | 221 | int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK; |
| 218 | if (newsize > LUAI_MAXSTACK) /* cannot cross the limit */ | 222 | if (newsize > LUAI_MAXSTACK) /* cannot cross the limit */ |
| 219 | newsize = LUAI_MAXSTACK; | 223 | newsize = LUAI_MAXSTACK; |
| 220 | if (newsize < needed) /* but must respect what was asked for */ | 224 | if (newsize < needed) /* but must respect what was asked for */ |
| 221 | newsize = needed; | 225 | newsize = needed; |
| 222 | if (unlikely(newsize > LUAI_MAXSTACK)) { /* stack overflow? */ | 226 | if (likely(newsize <= LUAI_MAXSTACK)) |
| 227 | return luaD_reallocstack(L, newsize, raiseerror); | ||
| 228 | else { /* stack overflow */ | ||
| 223 | /* add extra size to be able to handle the error message */ | 229 | /* add extra size to be able to handle the error message */ |
| 224 | luaD_reallocstack(L, ERRORSTACKSIZE, raiseerror); | 230 | luaD_reallocstack(L, ERRORSTACKSIZE, raiseerror); |
| 225 | if (raiseerror) | 231 | if (raiseerror) |
| 226 | luaG_runerror(L, "stack overflow"); | 232 | luaG_runerror(L, "stack overflow"); |
| 227 | else return 0; | 233 | return 0; |
| 228 | } | 234 | } |
| 229 | } /* else no errors */ | 235 | } |
| 230 | return luaD_reallocstack(L, newsize, raiseerror); | ||
| 231 | } | 236 | } |
| 232 | 237 | ||
| 233 | 238 | ||
| 234 | static int stackinuse (lua_State *L) { | 239 | static int stackinuse (lua_State *L) { |
| 235 | CallInfo *ci; | 240 | CallInfo *ci; |
| 241 | int res; | ||
| 236 | StkId lim = L->top; | 242 | StkId lim = L->top; |
| 237 | for (ci = L->ci; ci != NULL; ci = ci->previous) { | 243 | for (ci = L->ci; ci != NULL; ci = ci->previous) { |
| 238 | if (lim < ci->top) lim = ci->top; | 244 | if (lim < ci->top) lim = ci->top; |
| 239 | } | 245 | } |
| 240 | lua_assert(lim <= L->stack_last); | 246 | lua_assert(lim <= L->stack_last); |
| 241 | return cast_int(lim - L->stack) + 1; /* part of stack in use */ | 247 | res = cast_int(lim - L->stack) + 1; /* part of stack in use */ |
| 248 | if (res < LUA_MINSTACK) | ||
| 249 | res = LUA_MINSTACK; /* ensure a minimum size */ | ||
| 250 | return res; | ||
| 242 | } | 251 | } |
| 243 | 252 | ||
| 244 | 253 | ||
| 254 | /* | ||
| 255 | ** If stack size is more than 3 times the current use, reduce that size | ||
| 256 | ** to twice the current use. (So, the final stack size is at most 2/3 the | ||
| 257 | ** previous size, and half of its entries are empty.) | ||
| 258 | ** As a particular case, if stack was handling a stack overflow and now | ||
| 259 | ** it is not, 'max' (limited by LUAI_MAXSTACK) will be smaller than | ||
| 260 | ** 'stacksize' (equal to ERRORSTACKSIZE in this case), and so the stack | ||
| 261 | ** will be reduced to a "regular" size. | ||
| 262 | */ | ||
| 245 | void luaD_shrinkstack (lua_State *L) { | 263 | void luaD_shrinkstack (lua_State *L) { |
| 246 | int inuse = stackinuse(L); | 264 | int inuse = stackinuse(L); |
| 247 | int goodsize = inuse + BASIC_STACK_SIZE; | 265 | int nsize = inuse * 2; /* proposed new size */ |
| 248 | if (goodsize > LUAI_MAXSTACK) | 266 | int max = inuse * 3; /* maximum "reasonable" size */ |
| 249 | goodsize = LUAI_MAXSTACK; /* respect stack limit */ | 267 | if (max > LUAI_MAXSTACK) { |
| 268 | max = LUAI_MAXSTACK; /* respect stack limit */ | ||
| 269 | if (nsize > LUAI_MAXSTACK) | ||
| 270 | nsize = LUAI_MAXSTACK; | ||
| 271 | } | ||
| 250 | /* if thread is currently not handling a stack overflow and its | 272 | /* if thread is currently not handling a stack overflow and its |
| 251 | good size is smaller than current size, shrink its stack */ | 273 | size is larger than maximum "reasonable" size, shrink it */ |
| 252 | if (inuse <= (LUAI_MAXSTACK - EXTRA_STACK) && goodsize < L->stacksize) | 274 | if (inuse <= (LUAI_MAXSTACK - EXTRA_STACK) && L->stacksize > max) |
| 253 | luaD_reallocstack(L, goodsize, 0); /* ok if that fails */ | 275 | luaD_reallocstack(L, nsize, 0); /* ok if that fails */ |
| 254 | else /* don't change stack */ | 276 | else /* don't change stack */ |
| 255 | condmovestack(L,{},{}); /* (change only for debugging) */ | 277 | condmovestack(L,{},{}); /* (change only for debugging) */ |
| 256 | luaE_shrinkCI(L); /* shrink CI list */ | 278 | luaE_shrinkCI(L); /* shrink CI list */ |
| @@ -625,7 +647,7 @@ static int recover (lua_State *L, int status) { | |||
| 625 | luaD_seterrorobj(L, status, oldtop); | 647 | luaD_seterrorobj(L, status, oldtop); |
| 626 | L->ci = ci; | 648 | L->ci = ci; |
| 627 | L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ | 649 | L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ |
| 628 | luaD_shrinkstack(L); | 650 | luaD_shrinkstack(L); /* restore stack size in case of overflow */ |
| 629 | L->errfunc = ci->u.c.old_errfunc; | 651 | L->errfunc = ci->u.c.old_errfunc; |
| 630 | return 1; /* continue running the coroutine */ | 652 | return 1; /* continue running the coroutine */ |
| 631 | } | 653 | } |
| @@ -768,7 +790,7 @@ int luaD_pcall (lua_State *L, Pfunc func, void *u, | |||
| 768 | status = luaF_close(L, oldtop, status); | 790 | status = luaF_close(L, oldtop, status); |
| 769 | oldtop = restorestack(L, old_top); /* previous call may change stack */ | 791 | oldtop = restorestack(L, old_top); /* previous call may change stack */ |
| 770 | luaD_seterrorobj(L, status, oldtop); | 792 | luaD_seterrorobj(L, status, oldtop); |
| 771 | luaD_shrinkstack(L); | 793 | luaD_shrinkstack(L); /* restore stack size in case of overflow */ |
| 772 | } | 794 | } |
| 773 | L->errfunc = old_errfunc; | 795 | L->errfunc = old_errfunc; |
| 774 | return status; | 796 | return status; |
diff --git a/testes/cstack.lua b/testes/cstack.lua index 5767adf6..8ac48e89 100644 --- a/testes/cstack.lua +++ b/testes/cstack.lua | |||
| @@ -2,7 +2,7 @@ | |||
| 2 | -- See Copyright Notice in file all.lua | 2 | -- See Copyright Notice in file all.lua |
| 3 | 3 | ||
| 4 | 4 | ||
| 5 | print"testing C-stack overflow detection" | 5 | print"testing stack overflow detection" |
| 6 | 6 | ||
| 7 | -- Segmentation faults in these tests probably result from a C-stack | 7 | -- Segmentation faults in these tests probably result from a C-stack |
| 8 | -- overflow. To avoid these errors, you should set a smaller limit for | 8 | -- overflow. To avoid these errors, you should set a smaller limit for |
| @@ -98,4 +98,52 @@ do | |||
| 98 | print("final count: ", count) | 98 | print("final count: ", count) |
| 99 | end | 99 | end |
| 100 | 100 | ||
| 101 | |||
| 102 | if T then | ||
| 103 | print("testing stack recovery") | ||
| 104 | local N = 0 -- trace number of calls | ||
| 105 | local LIM = -1 -- will store N just before stack overflow | ||
| 106 | |||
| 107 | -- trace stack size; after stack overflow, it should be | ||
| 108 | -- the maximum allowed stack size. | ||
| 109 | local stack1 | ||
| 110 | local dummy | ||
| 111 | |||
| 112 | local function err(msg) | ||
| 113 | assert(string.find(msg, "stack overflow")) | ||
| 114 | local _, stacknow = T.stacklevel() | ||
| 115 | assert(stacknow == stack1 + 200) | ||
| 116 | end | ||
| 117 | |||
| 118 | -- When LIM==-1, the 'if' is not executed, so this function only | ||
| 119 | -- counts and stores the stack limits up to overflow. Then, LIM | ||
| 120 | -- becomes N, and then the 'if' code is run when the stack is | ||
| 121 | -- full. Then, there is a stack overflow inside 'xpcall', after which | ||
| 122 | -- the stack must have been restored back to its maximum normal size. | ||
| 123 | local function f() | ||
| 124 | dummy, stack1 = T.stacklevel() | ||
| 125 | if N == LIM then | ||
| 126 | xpcall(f, err) | ||
| 127 | local _, stacknow = T.stacklevel() | ||
| 128 | assert(stacknow == stack1) | ||
| 129 | return | ||
| 130 | end | ||
| 131 | N = N + 1 | ||
| 132 | f() | ||
| 133 | end | ||
| 134 | |||
| 135 | local topB, sizeB -- top and size Before overflow | ||
| 136 | local topA, sizeA -- top and size After overflow | ||
| 137 | topB, sizeB = T.stacklevel() | ||
| 138 | xpcall(f, err) | ||
| 139 | topA, sizeA = T.stacklevel() | ||
| 140 | -- sizes should be comparable | ||
| 141 | assert(topA == topB and sizeA < sizeB * 2) | ||
| 142 | print(string.format("maximum stack size: %d", stack1)) | ||
| 143 | LIM = N -- will stop recursion at maximum level | ||
| 144 | N = 0 -- to count again | ||
| 145 | f() | ||
| 146 | print"+" | ||
| 147 | end | ||
| 148 | |||
| 101 | print'OK' | 149 | print'OK' |
