diff options
| author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-10-18 16:15:09 -0300 |
|---|---|---|
| committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-10-18 16:15:09 -0300 |
| commit | 3c7dc52909ce0688bdb20cacaf686413a79aaf48 (patch) | |
| tree | 15a55b38f747154a1ebf4cf7d7eb088fe98daedc | |
| parent | bd96330d037660d9a1769c6c0d989f017e5f0278 (diff) | |
| download | lua-3c7dc52909ce0688bdb20cacaf686413a79aaf48.tar.gz lua-3c7dc52909ce0688bdb20cacaf686413a79aaf48.tar.bz2 lua-3c7dc52909ce0688bdb20cacaf686413a79aaf48.zip | |
Handling of memory errors when creating to-be-closed upvalues
| -rw-r--r-- | lfunc.c | 126 | ||||
| -rw-r--r-- | lfunc.h | 1 | ||||
| -rw-r--r-- | lvm.c | 3 | ||||
| -rw-r--r-- | testes/locals.lua | 58 |
4 files changed, 148 insertions, 40 deletions
| @@ -40,6 +40,7 @@ LClosure *luaF_newLclosure (lua_State *L, int n) { | |||
| 40 | return c; | 40 | return c; |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | |||
| 43 | /* | 44 | /* |
| 44 | ** fill a closure with new closed upvalues | 45 | ** fill a closure with new closed upvalues |
| 45 | */ | 46 | */ |
| @@ -56,31 +57,43 @@ void luaF_initupvals (lua_State *L, LClosure *cl) { | |||
| 56 | } | 57 | } |
| 57 | 58 | ||
| 58 | 59 | ||
| 60 | /* | ||
| 61 | ** Create a new upvalue with the given tag at the given level, | ||
| 62 | ** and link it to the list of open upvalues of 'L' after entry 'prev'. | ||
| 63 | **/ | ||
| 64 | static UpVal *newupval (lua_State *L, int tag, StkId level, UpVal **prev) { | ||
| 65 | GCObject *o = luaC_newobj(L, tag, sizeof(UpVal)); | ||
| 66 | UpVal *uv = gco2upv(o); | ||
| 67 | UpVal *next = *prev; | ||
| 68 | uv->v = s2v(level); /* current value lives in the stack */ | ||
| 69 | uv->u.open.next = next; /* link it to list of open upvalues */ | ||
| 70 | uv->u.open.previous = prev; | ||
| 71 | if (next) | ||
| 72 | next->u.open.previous = &uv->u.open.next; | ||
| 73 | *prev = uv; | ||
| 74 | if (!isintwups(L)) { /* thread not in list of threads with upvalues? */ | ||
| 75 | L->twups = G(L)->twups; /* link it to the list */ | ||
| 76 | G(L)->twups = L; | ||
| 77 | } | ||
| 78 | return uv; | ||
| 79 | } | ||
| 80 | |||
| 81 | |||
| 82 | /* | ||
| 83 | ** Find and reuse, or create if it does not exist, a regular upvalue | ||
| 84 | ** at the given level. | ||
| 85 | */ | ||
| 59 | UpVal *luaF_findupval (lua_State *L, StkId level) { | 86 | UpVal *luaF_findupval (lua_State *L, StkId level) { |
| 60 | UpVal **pp = &L->openupval; | 87 | UpVal **pp = &L->openupval; |
| 61 | GCObject *o; | ||
| 62 | UpVal *p; | 88 | UpVal *p; |
| 63 | UpVal *uv; | ||
| 64 | lua_assert(isintwups(L) || L->openupval == NULL); | 89 | lua_assert(isintwups(L) || L->openupval == NULL); |
| 65 | while ((p = *pp) != NULL && uplevel(p) >= level) { | 90 | while ((p = *pp) != NULL && uplevel(p) >= level) { /* search for it */ |
| 66 | if (uplevel(p) == level && !isdead(G(L), p)) /* corresponding upvalue? */ | 91 | if (uplevel(p) == level && !isdead(G(L), p)) /* corresponding upvalue? */ |
| 67 | return p; /* return it */ | 92 | return p; /* return it */ |
| 68 | pp = &p->u.open.next; | 93 | pp = &p->u.open.next; |
| 69 | } | 94 | } |
| 70 | /* not found: create a new upvalue between 'pp' and 'p' */ | 95 | /* not found: create a new upvalue after 'pp' */ |
| 71 | o = luaC_newobj(L, LUA_TUPVAL, sizeof(UpVal)); | 96 | return newupval(L, LUA_TUPVAL, level, pp); |
| 72 | uv = gco2upv(o); | ||
| 73 | uv->u.open.next = p; /* link it to list of open upvalues */ | ||
| 74 | uv->u.open.previous = pp; | ||
| 75 | if (p) | ||
| 76 | p->u.open.previous = &uv->u.open.next; | ||
| 77 | *pp = uv; | ||
| 78 | uv->v = s2v(level); /* current value lives in the stack */ | ||
| 79 | if (!isintwups(L)) { /* thread not in list of threads with upvalues? */ | ||
| 80 | L->twups = G(L)->twups; /* link it to the list */ | ||
| 81 | G(L)->twups = L; | ||
| 82 | } | ||
| 83 | return uv; | ||
| 84 | } | 97 | } |
| 85 | 98 | ||
| 86 | 99 | ||
| @@ -89,25 +102,44 @@ static void callclose (lua_State *L, void *ud) { | |||
| 89 | } | 102 | } |
| 90 | 103 | ||
| 91 | 104 | ||
| 92 | static int closeupval (lua_State *L, UpVal *uv, StkId level, int status) { | 105 | /* |
| 93 | StkId func = level + 1; /* save slot for old error message */ | 106 | ** Prepare closing method with its argument for object at |
| 94 | if (status != LUA_OK) /* was there an error? */ | 107 | ** index 'func' in the stack. Assume there is an error message |
| 95 | luaD_seterrorobj(L, status, level); /* save error message */ | 108 | ** (or nil) just below the object. |
| 96 | else | 109 | */ |
| 97 | setnilvalue(s2v(level)); | 110 | static int prepclosingmethod (lua_State *L, StkId func) { |
| 98 | if (ttisfunction(uv->v)) { /* object to-be-closed is a function? */ | 111 | if (ttisfunction(s2v(func))) { /* object to-be-closed is a function? */ |
| 99 | setobj2s(L, func, uv->v); /* will call it */ | 112 | setobjs2s(L, func + 1, func - 1); /* push error msg. as argument */ |
| 100 | setobjs2s(L, func + 1, level); /* error msg. as argument */ | ||
| 101 | } | 113 | } |
| 102 | else { /* try '__close' metamethod */ | 114 | else { /* try '__close' metamethod */ |
| 103 | const TValue *tm = luaT_gettmbyobj(L, uv->v, TM_CLOSE); | 115 | const TValue *tm = luaT_gettmbyobj(L, s2v(func), TM_CLOSE); |
| 104 | if (ttisnil(tm)) | 116 | if (ttisnil(tm)) /* no metamethod? */ |
| 105 | return status; /* no metamethod */ | 117 | return 0; /* nothing to call */ |
| 118 | setobjs2s(L, func + 1, func); /* 'self' is the argument */ | ||
| 106 | setobj2s(L, func, tm); /* will call metamethod */ | 119 | setobj2s(L, func, tm); /* will call metamethod */ |
| 107 | setobj2s(L, func + 1, uv->v); /* with 'self' as argument */ | ||
| 108 | } | 120 | } |
| 109 | L->top = func + 2; /* add function and argument */ | 121 | L->top = func + 2; /* add function and argument */ |
| 110 | if (status == LUA_OK) /* not in "error mode"? */ | 122 | return 1; |
| 123 | } | ||
| 124 | |||
| 125 | |||
| 126 | /* | ||
| 127 | ** Prepare and call a closing method. If status is OK, code is | ||
| 128 | ** still inside the original protected call, and so any error | ||
| 129 | ** will be handled there. Otherwise, a previous error already | ||
| 130 | ** activated original protected call, and so the call to the | ||
| 131 | ** closing method must be protected here. | ||
| 132 | */ | ||
| 133 | static int closeupval (lua_State *L, TValue *uv, StkId level, int status) { | ||
| 134 | StkId func = level + 1; /* save slot for old error message */ | ||
| 135 | if (unlikely(status != LUA_OK)) /* was there an error? */ | ||
| 136 | luaD_seterrorobj(L, status, level); /* save error message */ | ||
| 137 | else | ||
| 138 | setnilvalue(s2v(level)); /* no error message */ | ||
| 139 | setobj2s(L, func, uv); /* put object on top of error message */ | ||
| 140 | if (!prepclosingmethod(L, func)) | ||
| 141 | return status; /* nothing to call */ | ||
| 142 | if (likely(status == LUA_OK)) /* not in "error mode"? */ | ||
| 111 | callclose(L, func); /* call closing method */ | 143 | callclose(L, func); /* call closing method */ |
| 112 | else { /* already inside error handler; cannot raise another error */ | 144 | else { /* already inside error handler; cannot raise another error */ |
| 113 | int newstatus = luaD_pcall(L, callclose, func, savestack(L, level), 0); | 145 | int newstatus = luaD_pcall(L, callclose, func, savestack(L, level), 0); |
| @@ -118,6 +150,36 @@ static int closeupval (lua_State *L, UpVal *uv, StkId level, int status) { | |||
| 118 | } | 150 | } |
| 119 | 151 | ||
| 120 | 152 | ||
| 153 | /* | ||
| 154 | ** Try to create a to-be-closed upvalue | ||
| 155 | ** (can raise a memory-allocation error) | ||
| 156 | */ | ||
| 157 | static void trynewtbcupval (lua_State *L, void *ud) { | ||
| 158 | StkId level = cast(StkId, ud); | ||
| 159 | lua_assert(L->openupval == NULL || uplevel(L->openupval) < level); | ||
| 160 | newupval(L, LUA_TUPVALTBC, level, &L->openupval); | ||
| 161 | } | ||
| 162 | |||
| 163 | |||
| 164 | /* | ||
| 165 | ** Create a to-be-closed upvalue. If there is a memory error | ||
| 166 | ** when creating the upvalue, the closing method must be called here, | ||
| 167 | ** as there is no upvalue to call it later. | ||
| 168 | */ | ||
| 169 | void luaF_newtbcupval (lua_State *L, StkId level) { | ||
| 170 | int status = luaD_rawrunprotected(L, trynewtbcupval, level); | ||
| 171 | if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */ | ||
| 172 | StkId func = level + 1; | ||
| 173 | lua_assert(status == LUA_ERRMEM); | ||
| 174 | setobjs2s(L, func, level); /* open space for error message */ | ||
| 175 | luaD_seterrorobj(L, status, level); /* save error message */ | ||
| 176 | if (prepclosingmethod(L, func)) | ||
| 177 | callclose(L, func); /* call closing method */ | ||
| 178 | luaD_throw(L, LUA_ERRMEM); /* throw memory error */ | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | |||
| 121 | void luaF_unlinkupval (UpVal *uv) { | 183 | void luaF_unlinkupval (UpVal *uv) { |
| 122 | lua_assert(upisopen(uv)); | 184 | lua_assert(upisopen(uv)); |
| 123 | *uv->u.open.previous = uv->u.open.next; | 185 | *uv->u.open.previous = uv->u.open.next; |
| @@ -139,7 +201,7 @@ int luaF_close (lua_State *L, StkId level, int status) { | |||
| 139 | luaC_barrier(L, uv, slot); | 201 | luaC_barrier(L, uv, slot); |
| 140 | if (status >= 0 && uv->tt == LUA_TUPVALTBC) { /* must be closed? */ | 202 | if (status >= 0 && uv->tt == LUA_TUPVALTBC) { /* must be closed? */ |
| 141 | ptrdiff_t levelrel = savestack(L, level); | 203 | ptrdiff_t levelrel = savestack(L, level); |
| 142 | status = closeupval(L, uv, upl, status); /* may reallocate the stack */ | 204 | status = closeupval(L, uv->v, upl, status); /* may reallocate the stack */ |
| 143 | level = restorestack(L, levelrel); | 205 | level = restorestack(L, levelrel); |
| 144 | } | 206 | } |
| 145 | } | 207 | } |
| @@ -47,6 +47,7 @@ LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems); | |||
| 47 | LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems); | 47 | LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems); |
| 48 | LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); | 48 | LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); |
| 49 | LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); | 49 | LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); |
| 50 | LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level); | ||
| 50 | LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status); | 51 | LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status); |
| 51 | LUAI_FUNC void luaF_unlinkupval (UpVal *uv); | 52 | LUAI_FUNC void luaF_unlinkupval (UpVal *uv); |
| 52 | LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); | 53 | LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); |
| @@ -1456,8 +1456,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { | |||
| 1456 | vmbreak; | 1456 | vmbreak; |
| 1457 | } | 1457 | } |
| 1458 | vmcase(OP_TBC) { | 1458 | vmcase(OP_TBC) { |
| 1459 | UpVal *up = luaF_findupval(L, ra); /* create new upvalue */ | 1459 | luaF_newtbcupval(L, ra); /* create new to-be-closed upvalue */ |
| 1460 | up->tt = LUA_TUPVALTBC; /* mark it to be closed */ | ||
| 1461 | vmbreak; | 1460 | vmbreak; |
| 1462 | } | 1461 | } |
| 1463 | vmcase(OP_JMP) { | 1462 | vmcase(OP_JMP) { |
diff --git a/testes/locals.lua b/testes/locals.lua index 8d55e9f5..d12c70a0 100644 --- a/testes/locals.lua +++ b/testes/locals.lua | |||
| @@ -180,7 +180,7 @@ do | |||
| 180 | do | 180 | do |
| 181 | local scoped x = setmetatable({"x"}, {__close = function (self) | 181 | local scoped x = setmetatable({"x"}, {__close = function (self) |
| 182 | a[#a + 1] = self[1] end}) | 182 | a[#a + 1] = self[1] end}) |
| 183 | local scoped y = function () a[#a + 1] = "y" end | 183 | local scoped y = function (x) assert(x == nil); a[#a + 1] = "y" end |
| 184 | a[#a + 1] = "in" | 184 | a[#a + 1] = "in" |
| 185 | end | 185 | end |
| 186 | a[#a + 1] = "out" | 186 | a[#a + 1] = "out" |
| @@ -210,27 +210,73 @@ do -- errors in __close | |||
| 210 | and #log == 4) | 210 | and #log == 4) |
| 211 | end | 211 | end |
| 212 | 212 | ||
| 213 | do | 213 | if rawget(_G, "T") then |
| 214 | local function stack(n) n = (n == 0) or stack(n - 1); end; | ||
| 214 | -- memory error inside closing function | 215 | -- memory error inside closing function |
| 215 | local function foo () | 216 | local function foo () |
| 216 | local scoped y = function () io.write(2); T.alloccount() end | 217 | local scoped y = function () T.alloccount() end |
| 217 | local scoped x = setmetatable({}, {__close = function () | 218 | local scoped x = setmetatable({}, {__close = function () |
| 218 | T.alloccount(0); local x = {} -- force a memory error | 219 | T.alloccount(0); local x = {} -- force a memory error |
| 219 | end}) | 220 | end}) |
| 220 | io.write("1\n") | ||
| 221 | error("a") -- common error inside the function's body | 221 | error("a") -- common error inside the function's body |
| 222 | end | 222 | end |
| 223 | 223 | ||
| 224 | stack(5) -- ensure a minimal number of CI structures | ||
| 225 | |||
| 226 | -- despite memory error, 'y' will be executed and | ||
| 227 | -- memory limit will be lifted | ||
| 224 | local _, msg = pcall(foo) | 228 | local _, msg = pcall(foo) |
| 225 | T.alloccount() | ||
| 226 | assert(msg == "not enough memory") | 229 | assert(msg == "not enough memory") |
| 227 | 230 | ||
| 231 | local function close (msg) | ||
| 232 | T.alloccount() | ||
| 233 | assert(msg == "not enough memory") | ||
| 234 | end | ||
| 235 | |||
| 236 | -- set a memory limit and return a closing function to remove the limit | ||
| 237 | local function enter (count) | ||
| 238 | stack(10) -- reserve some stack space | ||
| 239 | T.alloccount(count) | ||
| 240 | return close | ||
| 241 | end | ||
| 242 | |||
| 243 | local function test () | ||
| 244 | local scoped x = enter(0) -- set a memory limit | ||
| 245 | -- creation of previous upvalue will raise a memory error | ||
| 246 | os.exit(false) -- should not run | ||
| 247 | end | ||
| 248 | |||
| 249 | local _, msg = pcall(test) | ||
| 250 | assert(msg == "not enough memory") | ||
| 251 | |||
| 252 | -- now use metamethod for closing | ||
| 253 | close = setmetatable({}, {__close = function () | ||
| 254 | T.alloccount() | ||
| 255 | end}) | ||
| 256 | |||
| 257 | -- repeat test with extra closing upvalues | ||
| 258 | local function test () | ||
| 259 | local scoped xxx = function (msg) | ||
| 260 | assert(msg == "not enough memory"); | ||
| 261 | error(1000) -- raise another error | ||
| 262 | end | ||
| 263 | local scoped xx = function (msg) | ||
| 264 | assert(msg == "not enough memory"); | ||
| 265 | end | ||
| 266 | local scoped x = enter(0) -- set a memory limit | ||
| 267 | -- creation of previous upvalue will raise a memory error | ||
| 268 | os.exit(false) -- should not run | ||
| 269 | end | ||
| 270 | |||
| 271 | local _, msg = pcall(test) | ||
| 272 | assert(msg == 1000) | ||
| 273 | |||
| 228 | end | 274 | end |
| 229 | 275 | ||
| 230 | 276 | ||
| 231 | -- a suspended coroutine should not close its variables when collected | 277 | -- a suspended coroutine should not close its variables when collected |
| 232 | local co = coroutine.wrap(function() | 278 | local co = coroutine.wrap(function() |
| 233 | local scoped x = function () os.exit(1) end -- should not run | 279 | local scoped x = function () os.exit(false) end -- should not run |
| 234 | coroutine.yield() | 280 | coroutine.yield() |
| 235 | end) | 281 | end) |
| 236 | co() | 282 | co() |
