diff options
| author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2019-07-31 10:43:51 -0300 |
|---|---|---|
| committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2019-07-31 10:43:51 -0300 |
| commit | f645d3157372c73573dff221c5b26691cb0e7d56 (patch) | |
| tree | 61adb1f332bbd8c0c0365b81cef8de47fa2ea06a | |
| parent | 35b4efc270db2418bc2cac6671575a45028061c3 (diff) | |
| download | lua-f645d3157372c73573dff221c5b26691cb0e7d56.tar.gz lua-f645d3157372c73573dff221c5b26691cb0e7d56.tar.bz2 lua-f645d3157372c73573dff221c5b26691cb0e7d56.zip | |
To-be-closed variables must be closed on initialization
When initializing a to-be-closed variable, check whether it has a
'__close' metamethod (or is a false value) and raise an error if
if it hasn't. This produces more accurate error messages. (The
check before closing still need to be done: in the C API, the value
is not constant; and the object may lose its '__close' metamethod
during the block.)
| -rw-r--r-- | lfunc.c | 49 | ||||
| -rw-r--r-- | ltests.c | 3 | ||||
| -rw-r--r-- | lvm.c | 6 | ||||
| -rw-r--r-- | manual/manual.of | 19 | ||||
| -rw-r--r-- | testes/api.lua | 11 | ||||
| -rw-r--r-- | testes/locals.lua | 23 |
6 files changed, 70 insertions, 41 deletions
| @@ -124,11 +124,23 @@ static int prepclosingmethod (lua_State *L, TValue *obj, TValue *err) { | |||
| 124 | 124 | ||
| 125 | 125 | ||
| 126 | /* | 126 | /* |
| 127 | ** Raise an error with message 'msg', inserting the name of the | ||
| 128 | ** local variable at position 'level' in the stack. | ||
| 129 | */ | ||
| 130 | static void varerror (lua_State *L, StkId level, const char *msg) { | ||
| 131 | int idx = cast_int(level - L->ci->func); | ||
| 132 | const char *vname = luaG_findlocal(L, L->ci, idx, NULL); | ||
| 133 | if (vname == NULL) vname = "?"; | ||
| 134 | luaG_runerror(L, msg, vname); | ||
| 135 | } | ||
| 136 | |||
| 137 | |||
| 138 | /* | ||
| 127 | ** Prepare and call a closing method. If status is OK, code is still | 139 | ** Prepare and call a closing method. If status is OK, code is still |
| 128 | ** inside the original protected call, and so any error will be handled | 140 | ** inside the original protected call, and so any error will be handled |
| 129 | ** there. Otherwise, a previous error already activated original | 141 | ** there. Otherwise, a previous error already activated the original |
| 130 | ** protected call, and so the call to the closing method must be | 142 | ** protected call, and so the call to the closing method must be |
| 131 | ** protected here. (A status = CLOSEPROTECT behaves like a previous | 143 | ** protected here. (A status == CLOSEPROTECT behaves like a previous |
| 132 | ** error, to also run the closing method in protected mode). | 144 | ** error, to also run the closing method in protected mode). |
| 133 | ** If status is OK, the call to the closing method will be pushed | 145 | ** If status is OK, the call to the closing method will be pushed |
| 134 | ** at the top of the stack. Otherwise, values are pushed after | 146 | ** at the top of the stack. Otherwise, values are pushed after |
| @@ -140,12 +152,8 @@ static int callclosemth (lua_State *L, StkId level, int status) { | |||
| 140 | if (likely(status == LUA_OK)) { | 152 | if (likely(status == LUA_OK)) { |
| 141 | if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */ | 153 | if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */ |
| 142 | callclose(L, NULL); /* call closing method */ | 154 | callclose(L, NULL); /* call closing method */ |
| 143 | else if (!ttisnil(uv)) { /* non-closable non-nil value? */ | 155 | else if (!l_isfalse(uv)) /* non-closable non-false value? */ |
| 144 | int idx = cast_int(level - L->ci->func); | 156 | varerror(L, level, "attempt to close non-closable variable '%s'"); |
| 145 | const char *vname = luaG_findlocal(L, L->ci, idx, NULL); | ||
| 146 | if (vname == NULL) vname = "?"; | ||
| 147 | luaG_runerror(L, "attempt to close non-closable variable '%s'", vname); | ||
| 148 | } | ||
| 149 | } | 157 | } |
| 150 | else { /* must close the object in protected mode */ | 158 | else { /* must close the object in protected mode */ |
| 151 | ptrdiff_t oldtop; | 159 | ptrdiff_t oldtop; |
| @@ -170,9 +178,7 @@ static int callclosemth (lua_State *L, StkId level, int status) { | |||
| 170 | ** (can raise a memory-allocation error) | 178 | ** (can raise a memory-allocation error) |
| 171 | */ | 179 | */ |
| 172 | static void trynewtbcupval (lua_State *L, void *ud) { | 180 | static void trynewtbcupval (lua_State *L, void *ud) { |
| 173 | StkId level = cast(StkId, ud); | 181 | newupval(L, 1, cast(StkId, ud), &L->openupval); |
| 174 | lua_assert(L->openupval == NULL || uplevel(L->openupval) < level); | ||
| 175 | newupval(L, 1, level, &L->openupval); | ||
| 176 | } | 182 | } |
| 177 | 183 | ||
| 178 | 184 | ||
| @@ -182,13 +188,22 @@ static void trynewtbcupval (lua_State *L, void *ud) { | |||
| 182 | ** as there is no upvalue to call it later. | 188 | ** as there is no upvalue to call it later. |
| 183 | */ | 189 | */ |
| 184 | void luaF_newtbcupval (lua_State *L, StkId level) { | 190 | void luaF_newtbcupval (lua_State *L, StkId level) { |
| 185 | int status = luaD_rawrunprotected(L, trynewtbcupval, level); | 191 | TValue *obj = s2v(level); |
| 186 | if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */ | 192 | lua_assert(L->openupval == NULL || uplevel(L->openupval) < level); |
| 187 | lua_assert(status == LUA_ERRMEM); | 193 | if (!l_isfalse(obj)) { /* false doesn't need to be closed */ |
| 188 | luaD_seterrorobj(L, LUA_ERRMEM, level + 1); /* save error message */ | 194 | int status; |
| 189 | if (prepclosingmethod(L, s2v(level), s2v(level + 1))) | 195 | const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); |
| 196 | if (ttisnil(tm)) /* no metamethod? */ | ||
| 197 | varerror(L, level, "variable '%s' got a non-closable value"); | ||
| 198 | status = luaD_rawrunprotected(L, trynewtbcupval, level); | ||
| 199 | if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */ | ||
| 200 | lua_assert(status == LUA_ERRMEM); | ||
| 201 | luaD_seterrorobj(L, LUA_ERRMEM, level + 1); /* save error message */ | ||
| 202 | /* next call must succeed, as object is closable */ | ||
| 203 | prepclosingmethod(L, s2v(level), s2v(level + 1)); | ||
| 190 | callclose(L, NULL); /* call closing method */ | 204 | callclose(L, NULL); /* call closing method */ |
| 191 | luaD_throw(L, LUA_ERRMEM); /* throw memory error */ | 205 | luaD_throw(L, LUA_ERRMEM); /* throw memory error */ |
| 206 | } | ||
| 192 | } | 207 | } |
| 193 | } | 208 | } |
| 194 | 209 | ||
| @@ -1572,6 +1572,9 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) { | |||
| 1572 | else if EQ("error") { | 1572 | else if EQ("error") { |
| 1573 | lua_error(L1); | 1573 | lua_error(L1); |
| 1574 | } | 1574 | } |
| 1575 | else if EQ("abort") { | ||
| 1576 | abort(); | ||
| 1577 | } | ||
| 1575 | else if EQ("throw") { | 1578 | else if EQ("throw") { |
| 1576 | #if defined(__cplusplus) | 1579 | #if defined(__cplusplus) |
| 1577 | static struct X { int x; } x; | 1580 | static struct X { int x; } x; |
| @@ -1739,10 +1739,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) { | |||
| 1739 | vmbreak; | 1739 | vmbreak; |
| 1740 | } | 1740 | } |
| 1741 | vmcase(OP_TFORPREP) { | 1741 | vmcase(OP_TFORPREP) { |
| 1742 | if (!ttisnil(s2v(ra + 3))) { /* is 'toclose' not nil? */ | 1742 | /* create to-be-closed upvalue (if needed) */ |
| 1743 | /* create to-be-closed upvalue for it */ | 1743 | halfProtect(luaF_newtbcupval(L, ra + 3)); |
| 1744 | halfProtect(luaF_newtbcupval(L, ra + 3)); | ||
| 1745 | } | ||
| 1746 | pc += GETARG_Bx(i); | 1744 | pc += GETARG_Bx(i); |
| 1747 | i = *(pc++); /* go to next instruction */ | 1745 | i = *(pc++); /* go to next instruction */ |
| 1748 | lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i)); | 1746 | lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i)); |
diff --git a/manual/manual.of b/manual/manual.of index 1646f113..8eebe9cb 100644 --- a/manual/manual.of +++ b/manual/manual.of | |||
| @@ -1534,15 +1534,17 @@ or exiting by an error. | |||
| 1534 | 1534 | ||
| 1535 | Here, to @emph{close} a value means | 1535 | Here, to @emph{close} a value means |
| 1536 | to call its @idx{__close} metamethod. | 1536 | to call its @idx{__close} metamethod. |
| 1537 | If the value is @nil, it is ignored; | ||
| 1538 | otherwise, | ||
| 1539 | if it does not have a @idx{__close} metamethod, | ||
| 1540 | an error is raised. | ||
| 1541 | When calling the metamethod, | 1537 | When calling the metamethod, |
| 1542 | the value itself is passed as the first argument | 1538 | the value itself is passed as the first argument |
| 1543 | and the error object (if any) is passed as a second argument; | 1539 | and the error object that caused the exit (if any) |
| 1540 | is passed as a second argument; | ||
| 1544 | if there was no error, the second argument is @nil. | 1541 | if there was no error, the second argument is @nil. |
| 1545 | 1542 | ||
| 1543 | The value assigned to a to-be-closed variable | ||
| 1544 | must have a @idx{__close} metamethod | ||
| 1545 | or be a false value. | ||
| 1546 | (@nil and @false are ignored as to-be-closed values.) | ||
| 1547 | |||
| 1546 | If several to-be-closed variables go out of scope at the same event, | 1548 | If several to-be-closed variables go out of scope at the same event, |
| 1547 | they are closed in the reverse order that they were declared. | 1549 | they are closed in the reverse order that they were declared. |
| 1548 | 1550 | ||
| @@ -2917,8 +2919,9 @@ it is left unchanged. | |||
| 2917 | @APIEntry{void lua_close (lua_State *L);| | 2919 | @APIEntry{void lua_close (lua_State *L);| |
| 2918 | @apii{0,0,-} | 2920 | @apii{0,0,-} |
| 2919 | 2921 | ||
| 2920 | Destroys all objects in the given Lua state | 2922 | Close all active to-be-closed variables in the main thread, |
| 2921 | (calling the corresponding garbage-collection metamethods, if any) | 2923 | release all objects in the given Lua state |
| 2924 | (calling the corresponding garbage-collection metamethods, if any), | ||
| 2922 | and frees all dynamic memory used by this state. | 2925 | and frees all dynamic memory used by this state. |
| 2923 | 2926 | ||
| 2924 | On several platforms, you may not need to call this function, | 2927 | On several platforms, you may not need to call this function, |
| @@ -4186,7 +4189,7 @@ An index marked as to-be-closed should not be removed from the stack | |||
| 4186 | by any other function in the API except @Lid{lua_settop} or @Lid{lua_pop}. | 4189 | by any other function in the API except @Lid{lua_settop} or @Lid{lua_pop}. |
| 4187 | 4190 | ||
| 4188 | This function should not be called for an index | 4191 | This function should not be called for an index |
| 4189 | that is equal to or below an already marked to-be-closed index. | 4192 | that is equal to or below an active to-be-closed index. |
| 4190 | 4193 | ||
| 4191 | This function can raise an out-of-memory error. | 4194 | This function can raise an out-of-memory error. |
| 4192 | In that case, the value in the given index is immediately closed, | 4195 | In that case, the value in the given index is immediately closed, |
diff --git a/testes/api.lua b/testes/api.lua index 3f7f7596..f6915c3e 100644 --- a/testes/api.lua +++ b/testes/api.lua | |||
| @@ -1096,7 +1096,7 @@ do | |||
| 1096 | assert(type(a[1]) == "string" and a[2][1] == 11) | 1096 | assert(type(a[1]) == "string" and a[2][1] == 11) |
| 1097 | assert(#openresource == 0) -- was closed | 1097 | assert(#openresource == 0) -- was closed |
| 1098 | 1098 | ||
| 1099 | -- error | 1099 | -- closing by error |
| 1100 | local a, b = pcall(T.makeCfunc[[ | 1100 | local a, b = pcall(T.makeCfunc[[ |
| 1101 | call 0 1 # create resource | 1101 | call 0 1 # create resource |
| 1102 | toclose -1 # mark it to be closed | 1102 | toclose -1 # mark it to be closed |
| @@ -1105,6 +1105,15 @@ do | |||
| 1105 | assert(a == false and b[1] == 11) | 1105 | assert(a == false and b[1] == 11) |
| 1106 | assert(#openresource == 0) -- was closed | 1106 | assert(#openresource == 0) -- was closed |
| 1107 | 1107 | ||
| 1108 | -- non-closable value | ||
| 1109 | local a, b = pcall(T.makeCfunc[[ | ||
| 1110 | newtable # create non-closable object | ||
| 1111 | toclose -1 # mark it to be closed (shoud raise an error) | ||
| 1112 | abort # will not be executed | ||
| 1113 | ]]) | ||
| 1114 | assert(a == false and | ||
| 1115 | string.find(b, "non%-closable value")) | ||
| 1116 | |||
| 1108 | local function check (n) | 1117 | local function check (n) |
| 1109 | assert(#openresource == n) | 1118 | assert(#openresource == n) |
| 1110 | end | 1119 | end |
diff --git a/testes/locals.lua b/testes/locals.lua index 3b145ca3..6eb1ba0e 100644 --- a/testes/locals.lua +++ b/testes/locals.lua | |||
| @@ -215,11 +215,13 @@ end | |||
| 215 | do | 215 | do |
| 216 | local a = {} | 216 | local a = {} |
| 217 | do | 217 | do |
| 218 | local b <close> = false -- not to be closed | ||
| 218 | local x <close> = setmetatable({"x"}, {__close = function (self) | 219 | local x <close> = setmetatable({"x"}, {__close = function (self) |
| 219 | a[#a + 1] = self[1] end}) | 220 | a[#a + 1] = self[1] end}) |
| 220 | local w, y <close>, z = func2close(function (self, err) | 221 | local w, y <close>, z = func2close(function (self, err) |
| 221 | assert(err == nil); a[#a + 1] = "y" | 222 | assert(err == nil); a[#a + 1] = "y" |
| 222 | end, 10, 20) | 223 | end, 10, 20) |
| 224 | local c <close> = nil -- not to be closed | ||
| 223 | a[#a + 1] = "in" | 225 | a[#a + 1] = "in" |
| 224 | assert(w == 10 and z == 20) | 226 | assert(w == 10 and z == 20) |
| 225 | end | 227 | end |
| @@ -325,24 +327,22 @@ do -- errors in __close | |||
| 325 | end | 327 | end |
| 326 | 328 | ||
| 327 | 329 | ||
| 328 | do | 330 | do -- errors due to non-closable values |
| 329 | |||
| 330 | -- errors due to non-closable values | ||
| 331 | local function foo () | 331 | local function foo () |
| 332 | local x <close> = {} | 332 | local x <close> = {} |
| 333 | os.exit(false) -- should not run | ||
| 333 | end | 334 | end |
| 334 | local stat, msg = pcall(foo) | 335 | local stat, msg = pcall(foo) |
| 335 | assert(not stat and string.find(msg, "variable 'x'")) | 336 | assert(not stat and |
| 337 | string.find(msg, "variable 'x' got a non%-closable value")) | ||
| 336 | 338 | ||
| 337 | |||
| 338 | -- with other errors, non-closable values are ignored | ||
| 339 | local function foo () | 339 | local function foo () |
| 340 | local x <close> = 34 | 340 | local xyz <close> = setmetatable({}, {__close = print}) |
| 341 | local y <close> = func2close(function () error(32) end) | 341 | getmetatable(xyz).__close = nil -- remove metamethod |
| 342 | end | 342 | end |
| 343 | local stat, msg = pcall(foo) | 343 | local stat, msg = pcall(foo) |
| 344 | assert(not stat and msg == 32) | 344 | assert(not stat and |
| 345 | 345 | string.find(msg, "attempt to close non%-closable variable 'xyz'")) | |
| 346 | end | 346 | end |
| 347 | 347 | ||
| 348 | 348 | ||
| @@ -519,7 +519,8 @@ end | |||
| 519 | -- a suspended coroutine should not close its variables when collected | 519 | -- a suspended coroutine should not close its variables when collected |
| 520 | local co | 520 | local co |
| 521 | co = coroutine.wrap(function() | 521 | co = coroutine.wrap(function() |
| 522 | local x <close> = function () os.exit(false) end -- should not run | 522 | -- should not run |
| 523 | local x <close> = func2close(function () os.exit(false) end) | ||
| 523 | co = nil | 524 | co = nil |
| 524 | coroutine.yield() | 525 | coroutine.yield() |
| 525 | end) | 526 | end) |
