Skip to content

Commit

Permalink
lua_settop/lua_pop closes to-be-closed variables
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
roberto-ieru committed Mar 9, 2021
1 parent f5df7f9 commit 511d53a
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 20 deletions.
15 changes: 8 additions & 7 deletions lapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ LUA_API int lua_gettop (lua_State *L) {

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

Expand Down
23 changes: 11 additions & 12 deletions manual/manual.of
Original file line number Diff line number Diff line change
Expand Up @@ -4253,12 +4253,8 @@ If the new top is greater than the old one,
then the new elements are filled with @nil.
If @id{index} @N{is 0}, then all stack elements are removed.

For compatibility reasons,
this function may close slots marked as to-be-closed @see{lua_toclose},
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.
This function can run arbitrary code when removing an index
marked as to-be-closed from the stack.

}

Expand Down Expand Up @@ -4347,19 +4343,22 @@ otherwise, returns @id{NULL}.
@apii{0,0,m}

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,
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.
Here, in the context of a C function,
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}.
An index marked as to-be-closed should neither be removed from the stack
nor modified before a corresponding call to @Lid{lua_closeslot}.
A slot marked as to-be-closed should not be removed from the stack
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
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,
by the time the @idx{__close} metamethod runs,
Expand Down
26 changes: 25 additions & 1 deletion testes/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1130,7 +1130,7 @@ do
-- closing resources with 'closeslot'
_ENV.xxx = true
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
toclose -1 # mark it to be closed
pushvalue 2 # stack: S, NR, CH, R, NR
Expand All @@ -1151,6 +1151,30 @@ do
]], newresource, check)
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
local a, b = pcall(T.makeCfunc[[
pushint 32
Expand Down

0 comments on commit 511d53a

Please sign in to comment.