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