aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Ierusalimschy <roberto@inf.puc-rio.br>2018-10-18 16:15:09 -0300
committerRoberto Ierusalimschy <roberto@inf.puc-rio.br>2018-10-18 16:15:09 -0300
commit3c7dc52909ce0688bdb20cacaf686413a79aaf48 (patch)
tree15a55b38f747154a1ebf4cf7d7eb088fe98daedc
parentbd96330d037660d9a1769c6c0d989f017e5f0278 (diff)
downloadlua-3c7dc52909ce0688bdb20cacaf686413a79aaf48.tar.gz
lua-3c7dc52909ce0688bdb20cacaf686413a79aaf48.tar.bz2
lua-3c7dc52909ce0688bdb20cacaf686413a79aaf48.zip
Handling of memory errors when creating to-be-closed upvalues
-rw-r--r--lfunc.c126
-rw-r--r--lfunc.h1
-rw-r--r--lvm.c3
-rw-r--r--testes/locals.lua58
4 files changed, 148 insertions, 40 deletions
diff --git a/lfunc.c b/lfunc.c
index fde72b8c..4f9362f3 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -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**/
64static 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*/
59UpVal *luaF_findupval (lua_State *L, StkId level) { 86UpVal *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
92static 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)); 110static 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*/
133static 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*/
157static 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*/
169void 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
121void luaF_unlinkupval (UpVal *uv) { 183void 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 }
diff --git a/lfunc.h b/lfunc.h
index 4c788005..c9fe1314 100644
--- a/lfunc.h
+++ b/lfunc.h
@@ -47,6 +47,7 @@ LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems);
47LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems); 47LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems);
48LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); 48LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl);
49LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); 49LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);
50LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level);
50LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status); 51LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status);
51LUAI_FUNC void luaF_unlinkupval (UpVal *uv); 52LUAI_FUNC void luaF_unlinkupval (UpVal *uv);
52LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); 53LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);
diff --git a/lvm.c b/lvm.c
index e2994aa9..0d82756b 100644
--- a/lvm.c
+++ b/lvm.c
@@ -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)
211end 211end
212 212
213do 213if 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)
225T.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
228end 274end
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
232local co = coroutine.wrap(function() 278local 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()
235end) 281end)
236co() 282co()