mirror of
https://github.com/lua/lua.git
synced 2025-01-14 05:43:00 +08:00
Upvalues removed from 'openupval' before being closed
Undo commit c220b0a5d0: '__close' is not called again in case of errors. (Upvalue is removed from the list before the call.) The common error that justified that change was C stack overflows, which are much rarer with the stackless implementation.
This commit is contained in:
parent
409256b784
commit
f9d29b0c44
30
lfunc.c
30
lfunc.c
@ -220,24 +220,38 @@ void luaF_unlinkupval (UpVal *uv) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Close all upvalues up to the given stack level. 'status' indicates
|
||||
** how/why the function was called:
|
||||
** - LUA_OK: regular code exiting the scope of a variable; may raise
|
||||
** an error due to errors in __close metamethods;
|
||||
** - CLOSEPROTECT: finishing a thread; run all metamethods in protected
|
||||
** mode;
|
||||
** - NOCLOSINGMETH: close upvalues without running __close metamethods;
|
||||
** - other values: error status from previous errors, to be propagated.
|
||||
**
|
||||
** Returns the resulting status, either the original status or an error
|
||||
** in a closing method.
|
||||
*/
|
||||
int luaF_close (lua_State *L, StkId level, int status) {
|
||||
UpVal *uv;
|
||||
while ((uv = L->openupval) != NULL && uplevel(uv) >= level) {
|
||||
StkId upl; /* stack index pointed by 'uv' */
|
||||
while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) {
|
||||
TValue *slot = &uv->u.value; /* new position for value */
|
||||
lua_assert(uplevel(uv) < L->top);
|
||||
if (uv->tbc && status != NOCLOSINGMETH) {
|
||||
/* must run closing method, which may change the stack */
|
||||
ptrdiff_t levelrel = savestack(L, level);
|
||||
status = callclosemth(L, uplevel(uv), status);
|
||||
level = restorestack(L, levelrel);
|
||||
}
|
||||
luaF_unlinkupval(uv);
|
||||
luaF_unlinkupval(uv); /* remove upvalue from 'openupval' list */
|
||||
setobj(L, slot, uv->v); /* move value to upvalue slot */
|
||||
uv->v = slot; /* now current value lives here */
|
||||
if (!iswhite(uv)) { /* neither white nor dead? */
|
||||
nw2black(uv); /* closed upvalues cannot be gray */
|
||||
luaC_barrier(L, uv, slot);
|
||||
}
|
||||
if (uv->tbc && status != NOCLOSINGMETH) {
|
||||
/* must run closing method, which may change the stack */
|
||||
ptrdiff_t levelrel = savestack(L, level);
|
||||
status = callclosemth(L, upl, status);
|
||||
level = restorestack(L, levelrel);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
@ -1630,7 +1630,6 @@ they are closed in the reverse order that they were declared.
|
||||
If there is any error while running a closing method,
|
||||
that error is handled like an error in the regular code
|
||||
where the variable was defined.
|
||||
However, Lua may call the method one more time.
|
||||
|
||||
After an error,
|
||||
the other pending closing methods will still be called.
|
||||
|
@ -392,21 +392,13 @@ do print("testing errors in __close")
|
||||
|
||||
local y <close> =
|
||||
func2close(function (self, msg)
|
||||
assert(string.find(msg, "@z")) -- first error in 'z'
|
||||
checkwarn("@z") -- second error in 'z' generated a warning
|
||||
assert(string.find(msg, "@z")) -- error in 'z'
|
||||
error("@y")
|
||||
end)
|
||||
|
||||
local first = true
|
||||
local z <close> =
|
||||
-- 'z' close is called twice
|
||||
func2close(function (self, msg)
|
||||
if first then
|
||||
assert(msg == nil)
|
||||
first = false
|
||||
else
|
||||
assert(string.find(msg, "@z")) -- own error
|
||||
end
|
||||
assert(msg == nil)
|
||||
error("@z")
|
||||
end)
|
||||
|
||||
@ -468,8 +460,8 @@ do print("testing errors in __close")
|
||||
local function foo (...)
|
||||
do
|
||||
local x1 <close> =
|
||||
func2close(function ()
|
||||
checkwarn("@X")
|
||||
func2close(function (self, msg)
|
||||
assert(string.find(msg, "@X"))
|
||||
error("@Y")
|
||||
end)
|
||||
|
||||
@ -494,8 +486,6 @@ do print("testing errors in __close")
|
||||
local st, msg = xpcall(foo, debug.traceback)
|
||||
assert(string.match(msg, "^[^ ]* @x123"))
|
||||
assert(string.find(msg, "in metamethod 'close'"))
|
||||
checkwarn("@x123") -- from second call to close 'x123'
|
||||
|
||||
endwarn()
|
||||
end
|
||||
|
||||
@ -686,8 +676,10 @@ do
|
||||
local x = 0
|
||||
local y = 0
|
||||
co = coroutine.wrap(function ()
|
||||
local xx <close> = func2close(function ()
|
||||
y = y + 1; checkwarn("XXX"); error("YYY")
|
||||
local xx <close> = func2close(function (_, err)
|
||||
y = y + 1;
|
||||
assert(string.find(err, "XXX"))
|
||||
error("YYY")
|
||||
end)
|
||||
local xv <close> = func2close(function ()
|
||||
x = x + 1; error("XXX")
|
||||
@ -697,7 +689,7 @@ do
|
||||
end)
|
||||
assert(co() == 100); assert(x == 0)
|
||||
local st, msg = pcall(co)
|
||||
assert(x == 2 and y == 1) -- first close is called twice
|
||||
assert(x == 1 and y == 1)
|
||||
-- should get first error raised
|
||||
assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
|
||||
checkwarn("YYY")
|
||||
|
Loading…
x
Reference in New Issue
Block a user