No more to-be-closed functions

To-be-closed variables must contain objects with '__toclose'
metamethods (or nil). Functions were removed for several reasons:

* Functions interact badly with sandboxes. If a sandbox raises
an error to interrupt a script, a to-be-closed function still
can hijack control and continue running arbitrary sandboxed code.

* Functions interact badly with coroutines. If a coroutine yields
and is never resumed again, its to-be-closed functions will never
run. To-be-closed objects, on the other hand, will still be closed,
provided they have appropriate finalizers.

* If you really need a function, it is easy to create a dummy
object to run that function in its '__toclose' metamethod.

This comit also adds closing of variables in case of panic.
This commit is contained in:
Roberto Ierusalimschy
2019-01-04 13:09:47 -02:00
parent c6f7181e91
commit 4ace93ca65
8 changed files with 97 additions and 68 deletions

View File

@@ -396,6 +396,23 @@ do
assert(string.find(msg, "stack overflow"))
end
-- exit in panic still close to-be-closed variables
assert(T.checkpanic([[
pushstring "return {__close = function () Y = 'ho'; end}"
newtable
loadstring -2
call 0 1
setmetatable -2
toclose -1
pushstring "hi"
error
]],
[[
getglobal Y
concat 2 # concat original error with global Y
]]) == "hiho")
end
-- testing deep C stack
@@ -1115,7 +1132,7 @@ end)
testamem("to-be-closed variables", function()
local flag
do
local *toclose x = function () flag = true end
local *toclose x = setmetatable({}, {__close = function () flag = true end})
flag = false
local x = {}
end

View File

@@ -142,8 +142,15 @@ do
-- to-be-closed variables in coroutines
local X
local function func2close (f)
return setmetatable({}, {__close = f})
end
co = coroutine.create(function ()
local *toclose x = function (err) assert(err == nil); X = false end
local *toclose x = func2close(function (self, err)
assert(err == nil); X = false
end)
X = true
coroutine.yield()
end)
@@ -154,7 +161,9 @@ do
-- error killing a coroutine
co = coroutine.create(function()
local *toclose x = function (err) assert(err == nil); error(111) end
local *toclose x = func2close(function (self, err)
assert(err == nil); error(111)
end)
coroutine.yield()
end)
coroutine.resume(co)

View File

@@ -258,7 +258,7 @@ do
::L2:: goto L3
::L1:: do
local *toclose a = function () X = true end
local *toclose a = setmetatable({}, {__close = function () X = true end})
assert(X == nil)
if a then goto L2 end -- jumping back out of scope of 'a'
end

View File

@@ -177,13 +177,19 @@ print"testing to-be-closed variables"
local function stack(n) n = ((n == 0) or stack(n - 1)) end
local function func2close (f)
return setmetatable({}, {__close = f})
end
do
local a = {}
do
local *toclose x = setmetatable({"x"}, {__close = function (self)
a[#a + 1] = self[1] end})
local *toclose y = function (x) assert(x == nil); a[#a + 1] = "y" end
local *toclose y = func2close(function (self, err)
assert(err == nil); a[#a + 1] = "y"
end)
a[#a + 1] = "in"
end
a[#a + 1] = "out"
@@ -193,7 +199,7 @@ end
do
local X = false
local function closescope () stack(10); X = true end
local closescope = func2close(function () stack(10); X = true end)
-- closing functions do not corrupt returning values
local function foo (x)
@@ -228,13 +234,13 @@ do
-- calls cannot be tail in the scope of to-be-closed variables
local X, Y
local function foo ()
local *toclose _ = function () Y = 10 end
local *toclose _ = func2close(function () Y = 10 end)
assert(X == true and Y == nil) -- 'X' not closed yet
return 1,2,3
end
local function bar ()
local *toclose _ = function () X = false end
local *toclose _ = func2close(function () X = false end)
X = true
do
return foo() -- not a tail call!
@@ -249,11 +255,15 @@ end
do -- errors in __close
local log = {}
local function foo (err)
local *toclose x = function (msg) log[#log + 1] = msg; error(1) end
local *toclose x1 = function (msg) log[#log + 1] = msg; end
local *toclose gc = function () collectgarbage() end
local *toclose y = function (msg) log[#log + 1] = msg; error(2) end
local *toclose z = function (msg) log[#log + 1] = msg or 10; error(3) end
local *toclose x =
func2close(function (self, msg) log[#log + 1] = msg; error(1) end)
local *toclose x1 =
func2close(function (self, msg) log[#log + 1] = msg; end)
local *toclose gc = func2close(function () collectgarbage() end)
local *toclose y =
func2close(function (self, msg) log[#log + 1] = msg; error(2) end)
local *toclose z =
func2close(function (self, msg) log[#log + 1] = msg or 10; error(3) end)
if err then error(4) end
end
local stat, msg = pcall(foo, false)
@@ -282,7 +292,7 @@ do
-- with other errors, non-closable values are ignored
local function foo ()
local *toclose x = 34
local *toclose y = function () error(32) end
local *toclose y = func2close(function () error(32) end)
end
local stat, msg = pcall(foo)
assert(not stat and msg == 32)
@@ -294,7 +304,7 @@ if rawget(_G, "T") then
-- memory error inside closing function
local function foo ()
local *toclose y = function () T.alloccount() end
local *toclose y = func2close(function () T.alloccount() end)
local *toclose x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error
end})
@@ -308,12 +318,12 @@ if rawget(_G, "T") then
local _, msg = pcall(foo)
assert(msg == "not enough memory")
local function close (msg)
local close = func2close(function (self, msg)
T.alloccount()
assert(msg == "not enough memory")
end
end)
-- set a memory limit and return a closing function to remove the limit
-- set a memory limit and return a closing object to remove the limit
local function enter (count)
stack(10) -- reserve some stack space
T.alloccount(count)
@@ -336,13 +346,13 @@ if rawget(_G, "T") then
-- repeat test with extra closing upvalues
local function test ()
local *toclose xxx = function (msg)
local *toclose xxx = func2close(function (self, msg)
assert(msg == "not enough memory");
error(1000) -- raise another error
end
local *toclose xx = function (msg)
end)
local *toclose xx = func2close(function (self, msg)
assert(msg == "not enough memory");
end
end)
local *toclose x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error
os.exit(false) -- should not run
@@ -413,9 +423,9 @@ do
local x = false
local y = false
local co = coroutine.create(function ()
local *toclose xv = function () x = true end
local *toclose xv = func2close(function () x = true end)
do
local *toclose yv = function () y = true end
local *toclose yv = func2close(function () y = true end)
coroutine.yield(100) -- yield doesn't close variable
end
coroutine.yield(200) -- yield doesn't close variable
@@ -453,9 +463,7 @@ do
end,
nil, -- state
nil, -- control variable
function () -- closing function
numopen = numopen - 1
end
func2close(function () numopen = numopen - 1 end) -- closing function
end
local s = 0