lua_settop/lua_pop closes to-be-closed variables

The existence of 'lua_closeslot' is no reason for lua_pop not to close
to-be-closed variables too.  It is too error-prone for lua_pop not to
close tbc variables being popped from the stack.
This commit is contained in:
Roberto Ierusalimschy 2021-03-09 11:42:45 -03:00
parent f5df7f91f7
commit 511d53a826
3 changed files with 44 additions and 20 deletions

15
lapi.c
View File

@ -173,7 +173,7 @@ LUA_API int lua_gettop (lua_State *L) {
LUA_API void lua_settop (lua_State *L, int idx) { LUA_API void lua_settop (lua_State *L, int idx) {
CallInfo *ci; CallInfo *ci;
StkId func; StkId func, newtop;
ptrdiff_t diff; /* difference for new top */ ptrdiff_t diff; /* difference for new top */
lua_lock(L); lua_lock(L);
ci = L->ci; ci = L->ci;
@ -188,12 +188,13 @@ LUA_API void lua_settop (lua_State *L, int idx) {
api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top"); api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top");
diff = idx + 1; /* will "subtract" index (as it is negative) */ diff = idx + 1; /* will "subtract" index (as it is negative) */
} }
#if defined(LUA_COMPAT_5_4_0) api_check(L, L->tbclist < L->top, "previous pop of an unclosed slot");
if (diff < 0 && hastocloseCfunc(ci->nresults)) newtop = L->top + diff;
luaF_close(L, L->top + diff, CLOSEKTOP, 0); if (diff < 0 && L->tbclist >= newtop) {
#endif lua_assert(hastocloseCfunc(ci->nresults));
api_check(L, L->tbclist < L->top + diff, "cannot pop an unclosed slot"); luaF_close(L, newtop, CLOSEKTOP, 0);
L->top += diff; }
L->top = newtop; /* correct top only after closing any upvalue */
lua_unlock(L); lua_unlock(L);
} }

View File

@ -4253,12 +4253,8 @@ If the new top is greater than the old one,
then the new elements are filled with @nil. then the new elements are filled with @nil.
If @id{index} @N{is 0}, then all stack elements are removed. If @id{index} @N{is 0}, then all stack elements are removed.
For compatibility reasons, This function can run arbitrary code when removing an index
this function may close slots marked as to-be-closed @see{lua_toclose}, marked as to-be-closed from the stack.
and therefore it can run arbitrary code.
You should not rely on this behavior:
Instead, always close to-be-closed slots explicitly,
with @Lid{lua_closeslot}, before removing them from the stack.
} }
@ -4347,19 +4343,22 @@ otherwise, returns @id{NULL}.
@apii{0,0,m} @apii{0,0,m}
Marks the given index in the stack as a Marks the given index in the stack as a
to-be-closed @Q{variable} @see{to-be-closed}. to-be-closed slot @see{to-be-closed}.
Like a to-be-closed variable in Lua, Like a to-be-closed variable in Lua,
the value at that index in the stack will be closed the value at that slot in the stack will be closed
when it goes out of scope. when it goes out of scope.
Here, in the context of a C function, Here, in the context of a C function,
to go out of scope means that the running function returns to Lua, to go out of scope means that the running function returns to Lua,
there is an error, or there is an error,
or the slot is removed from the stack through
@Lid{lua_settop} or @Lid{lua_pop},
or there is a call to @Lid{lua_closeslot}. or there is a call to @Lid{lua_closeslot}.
An index marked as to-be-closed should neither be removed from the stack A slot marked as to-be-closed should not be removed from the stack
nor modified before a corresponding call to @Lid{lua_closeslot}. by any other function in the API except @Lid{lua_settop} or @Lid{lua_pop},
unless previously deactivated by @Lid{lua_closeslot}.
This function should not be called for an index This function should not be called for an index
that is equal to or below an active to-be-closed index. that is equal to or below an active to-be-closed slot.
Note that, both in case of errors and of a regular return, Note that, both in case of errors and of a regular return,
by the time the @idx{__close} metamethod runs, by the time the @idx{__close} metamethod runs,

View File

@ -1130,7 +1130,7 @@ do
-- closing resources with 'closeslot' -- closing resources with 'closeslot'
_ENV.xxx = true _ENV.xxx = true
local a = T.testC([[ local a = T.testC([[
pushvalue 2 # stack: S, NR, CH pushvalue 2 # stack: S, NR, CH, NR
call 0 1 # create resource; stack: S, NR, CH, R call 0 1 # create resource; stack: S, NR, CH, R
toclose -1 # mark it to be closed toclose -1 # mark it to be closed
pushvalue 2 # stack: S, NR, CH, R, NR pushvalue 2 # stack: S, NR, CH, R, NR
@ -1151,6 +1151,30 @@ do
]], newresource, check) ]], newresource, check)
assert(a == 3 and _ENV.xxx == nil) -- no extra items left in the stack assert(a == 3 and _ENV.xxx == nil) -- no extra items left in the stack
-- closing resources with 'pop'
local a = T.testC([[
pushvalue 2 # stack: S, NR, CH, NR
call 0 1 # create resource; stack: S, NR, CH, R
toclose -1 # mark it to be closed
pushvalue 2 # stack: S, NR, CH, R, NR
call 0 1 # create another resource; stack: S, NR, CH, R, R
toclose -1 # mark it to be closed
pushvalue 3 # stack: S, NR, CH, R, R, CH
pushint 2 # there should be two open resources
call 1 0 # stack: S, NR, CH, R, R
pop 1 # pop second resource
pushvalue 3 # stack: S, NR, CH, R, CH
pushint 1 # there should be one open resource
call 1 0 # stack: S, NR, CH, R
pop 1 # pop other resource from the stack
pushvalue 3 # stack: S, NR, CH, CH
pushint 0 # there should be no open resources
call 1 0 # stack: S, NR, CH
pushint *
return 1 # return stack size
]], newresource, check)
assert(a == 3) -- no extra items left in the stack
-- non-closable value -- non-closable value
local a, b = pcall(T.makeCfunc[[ local a, b = pcall(T.makeCfunc[[
pushint 32 pushint 32