mirror of
https://github.com/lua/lua
synced 2024-11-22 04:41:23 +03:00
314745ed84
Test files are more polite regarding the use of globals when locals would do, and when globals are necessary deleting them after use.
1182 lines
28 KiB
Lua
1182 lines
28 KiB
Lua
-- $Id: testes/locals.lua $
|
|
-- See Copyright Notice in file all.lua
|
|
|
|
print('testing local variables and environments')
|
|
|
|
local debug = require"debug"
|
|
|
|
local tracegc = require"tracegc"
|
|
|
|
|
|
-- bug in 5.1:
|
|
|
|
local function f(x) x = nil; return x end
|
|
assert(f(10) == nil)
|
|
|
|
local function f() local x; return x end
|
|
assert(f(10) == nil)
|
|
|
|
local function f(x) x = nil; local y; return x, y end
|
|
assert(f(10) == nil and select(2, f(20)) == nil)
|
|
|
|
do
|
|
local i = 10
|
|
do local i = 100; assert(i==100) end
|
|
do local i = 1000; assert(i==1000) end
|
|
assert(i == 10)
|
|
if i ~= 10 then
|
|
local i = 20
|
|
else
|
|
local i = 30
|
|
assert(i == 30)
|
|
end
|
|
end
|
|
|
|
|
|
|
|
f = nil
|
|
|
|
local f
|
|
local x = 1
|
|
|
|
a = nil
|
|
load('local a = {}')()
|
|
assert(a == nil)
|
|
|
|
function f (a)
|
|
local _1, _2, _3, _4, _5
|
|
local _6, _7, _8, _9, _10
|
|
local x = 3
|
|
local b = a
|
|
local c,d = a,b
|
|
if (d == b) then
|
|
local x = 'q'
|
|
x = b
|
|
assert(x == 2)
|
|
else
|
|
assert(nil)
|
|
end
|
|
assert(x == 3)
|
|
local f = 10
|
|
end
|
|
|
|
local b=10
|
|
local a; repeat local b; a,b=1,2; assert(a+1==b); until a+b==3
|
|
|
|
|
|
assert(x == 1)
|
|
|
|
f(2)
|
|
assert(type(f) == 'function')
|
|
|
|
|
|
local function getenv (f)
|
|
local a,b = debug.getupvalue(f, 1)
|
|
assert(a == '_ENV')
|
|
return b
|
|
end
|
|
|
|
-- test for global table of loaded chunks
|
|
assert(getenv(load"a=3") == _G)
|
|
local c = {}; local f = load("a = 3", nil, nil, c)
|
|
assert(getenv(f) == c)
|
|
assert(c.a == nil)
|
|
f()
|
|
assert(c.a == 3)
|
|
|
|
-- old test for limits for special instructions
|
|
do
|
|
local i = 2
|
|
local p = 4 -- p == 2^i
|
|
repeat
|
|
for j=-3,3 do
|
|
assert(load(string.format([[local a=%s;
|
|
a=a+%s;
|
|
assert(a ==2^%s)]], j, p-j, i), '')) ()
|
|
assert(load(string.format([[local a=%s;
|
|
a=a-%s;
|
|
assert(a==-2^%s)]], -j, p-j, i), '')) ()
|
|
assert(load(string.format([[local a,b=0,%s;
|
|
a=b-%s;
|
|
assert(a==-2^%s)]], -j, p-j, i), '')) ()
|
|
end
|
|
p = 2 * p; i = i + 1
|
|
until p <= 0
|
|
end
|
|
|
|
print'+'
|
|
|
|
|
|
if rawget(_G, "T") then
|
|
-- testing clearing of dead elements from tables
|
|
collectgarbage("stop") -- stop GC
|
|
local a = {[{}] = 4, [3] = 0, alo = 1,
|
|
a1234567890123456789012345678901234567890 = 10}
|
|
|
|
local t = T.querytab(a)
|
|
|
|
for k,_ in pairs(a) do a[k] = undef end
|
|
collectgarbage() -- restore GC and collect dead fields in 'a'
|
|
for i=0,t-1 do
|
|
local k = querytab(a, i)
|
|
assert(k == nil or type(k) == 'number' or k == 'alo')
|
|
end
|
|
|
|
-- testing allocation errors during table insertions
|
|
local a = {}
|
|
local function additems ()
|
|
a.x = true; a.y = true; a.z = true
|
|
a[1] = true
|
|
a[2] = true
|
|
end
|
|
for i = 1, math.huge do
|
|
T.alloccount(i)
|
|
local st, msg = pcall(additems)
|
|
T.alloccount()
|
|
local count = 0
|
|
for k, v in pairs(a) do
|
|
assert(a[k] == v)
|
|
count = count + 1
|
|
end
|
|
if st then assert(count == 5); break end
|
|
end
|
|
end
|
|
|
|
|
|
-- testing lexical environments
|
|
|
|
assert(_ENV == _G)
|
|
|
|
do
|
|
local dummy
|
|
local _ENV = (function (...) return ... end)(_G, dummy) -- {
|
|
|
|
do local _ENV = {assert=assert}; assert(true) end
|
|
local mt = {_G = _G}
|
|
local foo,x
|
|
A = false -- "declare" A
|
|
do local _ENV = mt
|
|
function foo (x)
|
|
A = x
|
|
do local _ENV = _G; A = 1000 end
|
|
return function (x) return A .. x end
|
|
end
|
|
end
|
|
assert(getenv(foo) == mt)
|
|
x = foo('hi'); assert(mt.A == 'hi' and A == 1000)
|
|
assert(x('*') == mt.A .. '*')
|
|
|
|
do local _ENV = {assert=assert, A=10};
|
|
do local _ENV = {assert=assert, A=20};
|
|
assert(A==20);x=A
|
|
end
|
|
assert(A==10 and x==20)
|
|
end
|
|
assert(x==20)
|
|
|
|
A = nil
|
|
|
|
|
|
do -- constants
|
|
local a<const>, b, c<const> = 10, 20, 30
|
|
b = a + c + b -- 'b' is not constant
|
|
assert(a == 10 and b == 60 and c == 30)
|
|
local function checkro (name, code)
|
|
local st, msg = load(code)
|
|
local gab = string.format("attempt to assign to const variable '%s'", name)
|
|
assert(not st and string.find(msg, gab))
|
|
end
|
|
checkro("y", "local x, y <const>, z = 10, 20, 30; x = 11; y = 12")
|
|
checkro("x", "local x <const>, y, z <const> = 10, 20, 30; x = 11")
|
|
checkro("z", "local x <const>, y, z <const> = 10, 20, 30; y = 10; z = 11")
|
|
checkro("foo", "local foo <const> = 10; function foo() end")
|
|
checkro("foo", "local foo <const> = {}; function foo() end")
|
|
|
|
checkro("z", [[
|
|
local a, z <const>, b = 10;
|
|
function foo() a = 20; z = 32; end
|
|
]])
|
|
|
|
checkro("var1", [[
|
|
local a, var1 <const> = 10;
|
|
function foo() a = 20; z = function () var1 = 12; end end
|
|
]])
|
|
end
|
|
|
|
|
|
print"testing to-be-closed variables"
|
|
|
|
local function stack(n) n = ((n == 0) or stack(n - 1)) end
|
|
|
|
local function func2close (f, x, y)
|
|
local obj = setmetatable({}, {__close = f})
|
|
if x then
|
|
return x, obj, y
|
|
else
|
|
return obj
|
|
end
|
|
end
|
|
|
|
|
|
do
|
|
local a = {}
|
|
do
|
|
local b <close> = false -- not to be closed
|
|
local x <close> = setmetatable({"x"}, {__close = function (self)
|
|
a[#a + 1] = self[1] end})
|
|
local w, y <close>, z = func2close(function (self, err)
|
|
assert(err == nil); a[#a + 1] = "y"
|
|
end, 10, 20)
|
|
local c <close> = nil -- not to be closed
|
|
a[#a + 1] = "in"
|
|
assert(w == 10 and z == 20)
|
|
end
|
|
a[#a + 1] = "out"
|
|
assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
|
|
end
|
|
|
|
do
|
|
local X = false
|
|
|
|
local x, closescope = func2close(function (_, msg)
|
|
stack(10);
|
|
assert(msg == nil)
|
|
X = true
|
|
end, 100)
|
|
assert(x == 100); x = 101; -- 'x' is not read-only
|
|
|
|
-- closing functions do not corrupt returning values
|
|
local function foo (x)
|
|
local _ <close> = closescope
|
|
return x, X, 23
|
|
end
|
|
|
|
local a, b, c = foo(1.5)
|
|
assert(a == 1.5 and b == false and c == 23 and X == true)
|
|
|
|
X = false
|
|
foo = function (x)
|
|
local _<close> = func2close(function (_, msg)
|
|
-- without errors, enclosing function should be still active when
|
|
-- __close is called
|
|
assert(debug.getinfo(2).name == "foo")
|
|
assert(msg == nil)
|
|
end)
|
|
local _<close> = closescope
|
|
local y = 15
|
|
return y
|
|
end
|
|
|
|
assert(foo() == 15 and X == true)
|
|
|
|
X = false
|
|
foo = function ()
|
|
local x <close> = closescope
|
|
return x
|
|
end
|
|
|
|
assert(foo() == closescope and X == true)
|
|
|
|
end
|
|
|
|
|
|
-- testing to-be-closed x compile-time constants
|
|
-- (there were some bugs here in Lua 5.4-rc3, due to a confusion
|
|
-- between compile levels and stack levels of variables)
|
|
do
|
|
local flag = false
|
|
local x = setmetatable({},
|
|
{__close = function() assert(flag == false); flag = true end})
|
|
local y <const> = nil
|
|
local z <const> = nil
|
|
do
|
|
local a <close> = x
|
|
end
|
|
assert(flag) -- 'x' must be closed here
|
|
end
|
|
|
|
do
|
|
-- similar problem, but with implicit close in for loops
|
|
local flag = false
|
|
local x = setmetatable({},
|
|
{__close = function () assert(flag == false); flag = true end})
|
|
-- return an empty iterator, nil, nil, and 'x' to be closed
|
|
local function a ()
|
|
return (function () return nil end), nil, nil, x
|
|
end
|
|
local v <const> = 1
|
|
local w <const> = 1
|
|
local x <const> = 1
|
|
local y <const> = 1
|
|
local z <const> = 1
|
|
for k in a() do
|
|
a = k
|
|
end -- ending the loop must close 'x'
|
|
assert(flag) -- 'x' must be closed here
|
|
end
|
|
|
|
|
|
|
|
do
|
|
-- calls cannot be tail in the scope of to-be-closed variables
|
|
local X, Y
|
|
local function foo ()
|
|
local _ <close> = func2close(function () Y = 10 end)
|
|
assert(X == true and Y == nil) -- 'X' not closed yet
|
|
return 1,2,3
|
|
end
|
|
|
|
local function bar ()
|
|
local _ <close> = func2close(function () X = false end)
|
|
X = true
|
|
do
|
|
return foo() -- not a tail call!
|
|
end
|
|
end
|
|
|
|
local a, b, c, d = bar()
|
|
assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil)
|
|
end
|
|
|
|
|
|
do
|
|
-- bug in 5.4.3: previous condition (calls cannot be tail in the
|
|
-- scope of to-be-closed variables) must be valid for tbc variables
|
|
-- created by 'for' loops.
|
|
|
|
local closed = false
|
|
|
|
local function foo ()
|
|
return function () return true end, 0, 0,
|
|
func2close(function () closed = true end)
|
|
end
|
|
|
|
local function tail() return closed end
|
|
|
|
local function foo1 ()
|
|
for k in foo() do return tail() end
|
|
end
|
|
|
|
assert(foo1() == false)
|
|
assert(closed == true)
|
|
end
|
|
|
|
|
|
do
|
|
-- bug in 5.4.4: 'break' may generate wrong 'close' instruction when
|
|
-- leaving a loop block.
|
|
|
|
local closed = false
|
|
|
|
local o1 = setmetatable({}, {__close=function() closed = true end})
|
|
|
|
local function test()
|
|
for k, v in next, {}, nil, o1 do
|
|
local function f() return k end -- create an upvalue
|
|
break
|
|
end
|
|
assert(closed)
|
|
end
|
|
|
|
test()
|
|
end
|
|
|
|
|
|
do print("testing errors in __close")
|
|
|
|
-- original error is in __close
|
|
local function foo ()
|
|
|
|
local x <close> =
|
|
func2close(function (self, msg)
|
|
assert(string.find(msg, "@y"))
|
|
error("@x")
|
|
end)
|
|
|
|
local x1 <close> =
|
|
func2close(function (self, msg)
|
|
assert(string.find(msg, "@y"))
|
|
end)
|
|
|
|
local gc <close> = func2close(function () collectgarbage() end)
|
|
|
|
local y <close> =
|
|
func2close(function (self, msg)
|
|
assert(string.find(msg, "@z")) -- error in 'z'
|
|
error("@y")
|
|
end)
|
|
|
|
local z <close> =
|
|
func2close(function (self, msg)
|
|
assert(msg == nil)
|
|
error("@z")
|
|
end)
|
|
|
|
return 200
|
|
end
|
|
|
|
local stat, msg = pcall(foo, false)
|
|
assert(string.find(msg, "@x"))
|
|
|
|
|
|
-- original error not in __close
|
|
local function foo ()
|
|
|
|
local x <close> =
|
|
func2close(function (self, msg)
|
|
-- after error, 'foo' was discarded, so caller now
|
|
-- must be 'pcall'
|
|
assert(debug.getinfo(2).name == "pcall")
|
|
assert(string.find(msg, "@x1"))
|
|
end)
|
|
|
|
local x1 <close> =
|
|
func2close(function (self, msg)
|
|
assert(debug.getinfo(2).name == "pcall")
|
|
assert(string.find(msg, "@y"))
|
|
error("@x1")
|
|
end)
|
|
|
|
local gc <close> = func2close(function () collectgarbage() end)
|
|
|
|
local y <close> =
|
|
func2close(function (self, msg)
|
|
assert(debug.getinfo(2).name == "pcall")
|
|
assert(string.find(msg, "@z"))
|
|
error("@y")
|
|
end)
|
|
|
|
local first = true
|
|
local z <close> =
|
|
func2close(function (self, msg)
|
|
assert(debug.getinfo(2).name == "pcall")
|
|
-- 'z' close is called once
|
|
assert(first and msg == 4)
|
|
first = false
|
|
error("@z")
|
|
end)
|
|
|
|
error(4) -- original error
|
|
end
|
|
|
|
local stat, msg = pcall(foo, true)
|
|
assert(string.find(msg, "@x1"))
|
|
|
|
-- error leaving a block
|
|
local function foo (...)
|
|
do
|
|
local x1 <close> =
|
|
func2close(function (self, msg)
|
|
assert(string.find(msg, "@X"))
|
|
error("@Y")
|
|
end)
|
|
|
|
local x123 <close> =
|
|
func2close(function (_, msg)
|
|
assert(msg == nil)
|
|
error("@X")
|
|
end)
|
|
end
|
|
os.exit(false) -- should not run
|
|
end
|
|
|
|
local st, msg = xpcall(foo, debug.traceback)
|
|
assert(string.match(msg, "^[^ ]* @Y"))
|
|
|
|
-- error in toclose in vararg function
|
|
local function foo (...)
|
|
local x123 <close> = func2close(function () error("@x123") end)
|
|
end
|
|
|
|
local st, msg = xpcall(foo, debug.traceback)
|
|
assert(string.match(msg, "^[^ ]* @x123"))
|
|
assert(string.find(msg, "in metamethod 'close'"))
|
|
end
|
|
|
|
|
|
do -- errors due to non-closable values
|
|
local function foo ()
|
|
local x <close> = {}
|
|
os.exit(false) -- should not run
|
|
end
|
|
local stat, msg = pcall(foo)
|
|
assert(not stat and
|
|
string.find(msg, "variable 'x' got a non%-closable value"))
|
|
|
|
local function foo ()
|
|
local xyz <close> = setmetatable({}, {__close = print})
|
|
getmetatable(xyz).__close = nil -- remove metamethod
|
|
end
|
|
local stat, msg = pcall(foo)
|
|
assert(not stat and string.find(msg, "metamethod 'close'"))
|
|
|
|
local function foo ()
|
|
local a1 <close> = func2close(function (_, msg)
|
|
assert(string.find(msg, "number value"))
|
|
error(12)
|
|
end)
|
|
local a2 <close> = setmetatable({}, {__close = print})
|
|
local a3 <close> = func2close(function (_, msg)
|
|
assert(msg == nil)
|
|
error(123)
|
|
end)
|
|
getmetatable(a2).__close = 4 -- invalidate metamethod
|
|
end
|
|
local stat, msg = pcall(foo)
|
|
assert(not stat and msg == 12)
|
|
end
|
|
|
|
|
|
do -- tbc inside close methods
|
|
local track = {}
|
|
local function foo ()
|
|
local x <close> = func2close(function ()
|
|
local xx <close> = func2close(function (_, msg)
|
|
assert(msg == nil)
|
|
track[#track + 1] = "xx"
|
|
end)
|
|
track[#track + 1] = "x"
|
|
end)
|
|
track[#track + 1] = "foo"
|
|
return 20, 30, 40
|
|
end
|
|
local a, b, c, d = foo()
|
|
assert(a == 20 and b == 30 and c == 40 and d == nil)
|
|
assert(track[1] == "foo" and track[2] == "x" and track[3] == "xx")
|
|
|
|
-- again, with errors
|
|
local track = {}
|
|
local function foo ()
|
|
local x0 <close> = func2close(function (_, msg)
|
|
assert(msg == 202)
|
|
track[#track + 1] = "x0"
|
|
end)
|
|
local x <close> = func2close(function ()
|
|
local xx <close> = func2close(function (_, msg)
|
|
assert(msg == 101)
|
|
track[#track + 1] = "xx"
|
|
error(202)
|
|
end)
|
|
track[#track + 1] = "x"
|
|
error(101)
|
|
end)
|
|
track[#track + 1] = "foo"
|
|
return 20, 30, 40
|
|
end
|
|
local st, msg = pcall(foo)
|
|
assert(not st and msg == 202)
|
|
assert(track[1] == "foo" and track[2] == "x" and track[3] == "xx" and
|
|
track[4] == "x0")
|
|
end
|
|
|
|
|
|
local function checktable (t1, t2)
|
|
assert(#t1 == #t2)
|
|
for i = 1, #t1 do
|
|
assert(t1[i] == t2[i])
|
|
end
|
|
end
|
|
|
|
|
|
do -- test for tbc variable high in the stack
|
|
|
|
-- function to force a stack overflow
|
|
local function overflow (n)
|
|
overflow(n + 1)
|
|
end
|
|
|
|
-- error handler will create tbc variable handling a stack overflow,
|
|
-- high in the stack
|
|
local function errorh (m)
|
|
assert(string.find(m, "stack overflow"))
|
|
local x <close> = func2close(function (o) o[1] = 10 end)
|
|
return x
|
|
end
|
|
|
|
local flag
|
|
local st, obj
|
|
-- run test in a coroutine so as not to swell the main stack
|
|
local co = coroutine.wrap(function ()
|
|
-- tbc variable down the stack
|
|
local y <close> = func2close(function (obj, msg)
|
|
assert(msg == nil)
|
|
obj[1] = 100
|
|
flag = obj
|
|
end)
|
|
tracegc.stop()
|
|
st, obj = xpcall(overflow, errorh, 0)
|
|
tracegc.start()
|
|
end)
|
|
co()
|
|
assert(not st and obj[1] == 10 and flag[1] == 100)
|
|
end
|
|
|
|
|
|
if rawget(_G, "T") then
|
|
|
|
do
|
|
-- bug in 5.4.3
|
|
-- 'lua_settop' may use a pointer to stack invalidated by 'luaF_close'
|
|
|
|
-- reduce stack size
|
|
collectgarbage(); collectgarbage(); collectgarbage()
|
|
|
|
-- force a stack reallocation
|
|
local function loop (n)
|
|
if n < 400 then loop(n + 1) end
|
|
end
|
|
|
|
-- close metamethod will reallocate the stack
|
|
local o = setmetatable({}, {__close = function () loop(0) end})
|
|
|
|
local script = [[toclose 2; settop 1; return 1]]
|
|
|
|
assert(T.testC(script, o) == script)
|
|
|
|
end
|
|
|
|
|
|
-- memory error inside closing function
|
|
local function foo ()
|
|
local y <close> = func2close(function () T.alloccount() end)
|
|
local x <close> = setmetatable({}, {__close = function ()
|
|
T.alloccount(0); local x = {} -- force a memory error
|
|
end})
|
|
error(1000) -- common error inside the function's body
|
|
end
|
|
|
|
stack(5) -- ensure a minimal number of CI structures
|
|
|
|
-- despite memory error, 'y' will be executed and
|
|
-- memory limit will be lifted
|
|
local _, msg = pcall(foo)
|
|
assert(msg == "not enough memory")
|
|
|
|
local closemsg
|
|
local close = func2close(function (self, msg)
|
|
T.alloccount()
|
|
closemsg = msg
|
|
end)
|
|
|
|
-- 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)
|
|
closemsg = nil
|
|
return close
|
|
end
|
|
|
|
local function test ()
|
|
local x <close> = enter(0) -- set a memory limit
|
|
local y = {} -- raise a memory error
|
|
end
|
|
|
|
local _, msg = pcall(test)
|
|
assert(msg == "not enough memory" and closemsg == "not enough memory")
|
|
|
|
|
|
-- repeat test with extra closing upvalues
|
|
local function test ()
|
|
local xxx <close> = func2close(function (self, msg)
|
|
assert(msg == "not enough memory");
|
|
error(1000) -- raise another error
|
|
end)
|
|
local xx <close> = func2close(function (self, msg)
|
|
assert(msg == "not enough memory");
|
|
end)
|
|
local x <close> = enter(0) -- set a memory limit
|
|
local y = {} -- raise a memory error
|
|
end
|
|
|
|
local _, msg = pcall(test)
|
|
assert(msg == 1000 and closemsg == "not enough memory")
|
|
|
|
do -- testing 'toclose' in C string buffer
|
|
collectgarbage()
|
|
local s = string.rep('a', 10000) -- large string
|
|
local m = T.totalmem()
|
|
collectgarbage("stop")
|
|
s = string.upper(s) -- allocate buffer + new string (10K each)
|
|
-- ensure buffer was deallocated
|
|
assert(T.totalmem() - m <= 11000)
|
|
collectgarbage("restart")
|
|
end
|
|
|
|
do -- now some tests for freeing buffer in case of errors
|
|
local lim = 10000 -- some size larger than the static buffer
|
|
local extra = 2000 -- some extra memory (for callinfo, etc.)
|
|
|
|
local s = string.rep("a", lim)
|
|
|
|
-- concat this table needs two buffer resizes (one for each 's')
|
|
local a = {s, s}
|
|
|
|
collectgarbage(); collectgarbage()
|
|
|
|
local m = T.totalmem()
|
|
collectgarbage("stop")
|
|
|
|
-- error in the first buffer allocation
|
|
T. totalmem(m + extra)
|
|
assert(not pcall(table.concat, a))
|
|
-- first buffer was not even allocated
|
|
assert(T.totalmem() - m <= extra)
|
|
|
|
-- error in the second buffer allocation
|
|
T. totalmem(m + lim + extra)
|
|
assert(not pcall(table.concat, a))
|
|
-- first buffer was released by 'toclose'
|
|
assert(T.totalmem() - m <= extra)
|
|
|
|
-- error in creation of final string
|
|
T.totalmem(m + 2 * lim + extra)
|
|
assert(not pcall(table.concat, a))
|
|
-- second buffer was released by 'toclose'
|
|
assert(T.totalmem() - m <= extra)
|
|
|
|
-- userdata, buffer, buffer, final string
|
|
T.totalmem(m + 4*lim + extra)
|
|
assert(#table.concat(a) == 2*lim)
|
|
|
|
T.totalmem(0) -- remove memory limit
|
|
collectgarbage("restart")
|
|
|
|
print'+'
|
|
end
|
|
|
|
|
|
do
|
|
-- '__close' vs. return hooks in C functions
|
|
local trace = {}
|
|
|
|
local function hook (event)
|
|
trace[#trace + 1] = event .. " " .. (debug.getinfo(2).name or "?")
|
|
end
|
|
|
|
-- create tbc variables to be used by C function
|
|
local x = func2close(function (_,msg)
|
|
trace[#trace + 1] = "x"
|
|
end)
|
|
|
|
local y = func2close(function (_,msg)
|
|
trace[#trace + 1] = "y"
|
|
end)
|
|
|
|
debug.sethook(hook, "r")
|
|
local t = {T.testC([[
|
|
toclose 2 # x
|
|
pushnum 10
|
|
pushint 20
|
|
toclose 3 # y
|
|
return 2
|
|
]], x, y)}
|
|
debug.sethook()
|
|
|
|
-- hooks ran before return hook from 'testC'
|
|
checktable(trace,
|
|
{"return sethook", "y", "return ?", "x", "return ?", "return testC"})
|
|
-- results are correct
|
|
checktable(t, {10, 20})
|
|
end
|
|
end
|
|
|
|
|
|
do -- '__close' vs. return hooks in Lua functions
|
|
local trace = {}
|
|
|
|
local function hook (event)
|
|
trace[#trace + 1] = event .. " " .. debug.getinfo(2).name
|
|
end
|
|
|
|
local function foo (...)
|
|
local x <close> = func2close(function (_,msg)
|
|
trace[#trace + 1] = "x"
|
|
end)
|
|
|
|
local y <close> = func2close(function (_,msg)
|
|
debug.sethook(hook, "r")
|
|
end)
|
|
|
|
return ...
|
|
end
|
|
|
|
local t = {foo(10,20,30)}
|
|
debug.sethook()
|
|
checktable(t, {10, 20, 30})
|
|
checktable(trace,
|
|
{"return sethook", "return close", "x", "return close", "return foo"})
|
|
end
|
|
|
|
|
|
print "to-be-closed variables in coroutines"
|
|
|
|
do
|
|
-- yielding inside closing metamethods
|
|
|
|
local trace = {}
|
|
local co = coroutine.wrap(function ()
|
|
|
|
trace[#trace + 1] = "nowX"
|
|
|
|
-- will be closed after 'y'
|
|
local x <close> = func2close(function (_, msg)
|
|
assert(msg == nil)
|
|
trace[#trace + 1] = "x1"
|
|
coroutine.yield("x")
|
|
trace[#trace + 1] = "x2"
|
|
end)
|
|
|
|
return pcall(function ()
|
|
do -- 'z' will be closed first
|
|
local z <close> = func2close(function (_, msg)
|
|
assert(msg == nil)
|
|
trace[#trace + 1] = "z1"
|
|
coroutine.yield("z")
|
|
trace[#trace + 1] = "z2"
|
|
end)
|
|
end
|
|
|
|
trace[#trace + 1] = "nowY"
|
|
|
|
-- will be closed after 'z'
|
|
local y <close> = func2close(function(_, msg)
|
|
assert(msg == nil)
|
|
trace[#trace + 1] = "y1"
|
|
coroutine.yield("y")
|
|
trace[#trace + 1] = "y2"
|
|
end)
|
|
|
|
return 10, 20, 30
|
|
end)
|
|
end)
|
|
|
|
assert(co() == "z")
|
|
assert(co() == "y")
|
|
assert(co() == "x")
|
|
checktable({co()}, {true, 10, 20, 30})
|
|
checktable(trace, {"nowX", "z1", "z2", "nowY", "y1", "y2", "x1", "x2"})
|
|
|
|
end
|
|
|
|
|
|
do
|
|
-- yielding inside closing metamethods while returning
|
|
-- (bug in 5.4.3)
|
|
|
|
local extrares -- result from extra yield (if any)
|
|
|
|
local function check (body, extra, ...)
|
|
local t = table.pack(...) -- expected returns
|
|
local co = coroutine.wrap(body)
|
|
if extra then
|
|
extrares = co() -- runs until first (extra) yield
|
|
end
|
|
local res = table.pack(co()) -- runs until yield inside '__close'
|
|
assert(res.n == 2 and res[2] == nil)
|
|
local res2 = table.pack(co()) -- runs until end of function
|
|
assert(res2.n == t.n)
|
|
for i = 1, #t do
|
|
if t[i] == "x" then
|
|
assert(res2[i] == res[1]) -- value that was closed
|
|
else
|
|
assert(res2[i] == t[i])
|
|
end
|
|
end
|
|
end
|
|
|
|
local function foo ()
|
|
local x <close> = func2close(coroutine.yield)
|
|
local extra <close> = func2close(function (self)
|
|
assert(self == extrares)
|
|
coroutine.yield(100)
|
|
end)
|
|
extrares = extra
|
|
return table.unpack{10, x, 30}
|
|
end
|
|
check(foo, true, 10, "x", 30)
|
|
assert(extrares == 100)
|
|
|
|
local function foo ()
|
|
local x <close> = func2close(coroutine.yield)
|
|
return
|
|
end
|
|
check(foo, false)
|
|
|
|
local function foo ()
|
|
local x <close> = func2close(coroutine.yield)
|
|
local y, z = 20, 30
|
|
return x
|
|
end
|
|
check(foo, false, "x")
|
|
|
|
local function foo ()
|
|
local x <close> = func2close(coroutine.yield)
|
|
local extra <close> = func2close(coroutine.yield)
|
|
return table.unpack({}, 1, 100) -- 100 nils
|
|
end
|
|
check(foo, true, table.unpack({}, 1, 100))
|
|
|
|
end
|
|
|
|
do
|
|
-- yielding inside closing metamethods after an error
|
|
|
|
local co = coroutine.wrap(function ()
|
|
|
|
local function foo (err)
|
|
|
|
local z <close> = func2close(function(_, msg)
|
|
assert(msg == nil or msg == err + 20)
|
|
coroutine.yield("z")
|
|
return 100, 200
|
|
end)
|
|
|
|
local y <close> = func2close(function(_, msg)
|
|
-- still gets the original error (if any)
|
|
assert(msg == err or (msg == nil and err == 1))
|
|
coroutine.yield("y")
|
|
if err then error(err + 20) end -- creates or changes the error
|
|
end)
|
|
|
|
local x <close> = func2close(function(_, msg)
|
|
assert(msg == err or (msg == nil and err == 1))
|
|
coroutine.yield("x")
|
|
return 100, 200
|
|
end)
|
|
|
|
if err == 10 then error(err) else return 10, 20 end
|
|
end
|
|
|
|
coroutine.yield(pcall(foo, nil)) -- no error
|
|
coroutine.yield(pcall(foo, 1)) -- error in __close
|
|
return pcall(foo, 10) -- 'foo' will raise an error
|
|
end)
|
|
|
|
local a, b = co() -- first foo: no error
|
|
assert(a == "x" and b == nil) -- yields inside 'x'; Ok
|
|
a, b = co()
|
|
assert(a == "y" and b == nil) -- yields inside 'y'; Ok
|
|
a, b = co()
|
|
assert(a == "z" and b == nil) -- yields inside 'z'; Ok
|
|
local a, b, c = co()
|
|
assert(a and b == 10 and c == 20) -- returns from 'pcall(foo, nil)'
|
|
|
|
local a, b = co() -- second foo: error in __close
|
|
assert(a == "x" and b == nil) -- yields inside 'x'; Ok
|
|
a, b = co()
|
|
assert(a == "y" and b == nil) -- yields inside 'y'; Ok
|
|
a, b = co()
|
|
assert(a == "z" and b == nil) -- yields inside 'z'; Ok
|
|
local st, msg = co() -- reports the error in 'y'
|
|
assert(not st and msg == 21)
|
|
|
|
local a, b = co() -- third foo: error in function body
|
|
assert(a == "x" and b == nil) -- yields inside 'x'; Ok
|
|
a, b = co()
|
|
assert(a == "y" and b == nil) -- yields inside 'y'; Ok
|
|
a, b = co()
|
|
assert(a == "z" and b == nil) -- yields inside 'z'; Ok
|
|
local st, msg = co() -- gets final error
|
|
assert(not st and msg == 10 + 20)
|
|
|
|
end
|
|
|
|
|
|
do
|
|
-- an error in a wrapped coroutine closes variables
|
|
local x = false
|
|
local y = false
|
|
local co = coroutine.wrap(function ()
|
|
local xv <close> = func2close(function () x = true end)
|
|
do
|
|
local yv <close> = func2close(function () y = true end)
|
|
coroutine.yield(100) -- yield doesn't close variable
|
|
end
|
|
coroutine.yield(200) -- yield doesn't close variable
|
|
error(23) -- error does
|
|
end)
|
|
|
|
local b = co()
|
|
assert(b == 100 and not x and not y)
|
|
b = co()
|
|
assert(b == 200 and not x and y)
|
|
local a, b = pcall(co)
|
|
assert(not a and b == 23 and x and y)
|
|
end
|
|
|
|
|
|
do
|
|
|
|
-- error in a wrapped coroutine raising errors when closing a variable
|
|
local x = 0
|
|
local co = coroutine.wrap(function ()
|
|
local xx <close> = func2close(function (_, msg)
|
|
x = x + 1;
|
|
assert(string.find(msg, "@XXX"))
|
|
error("@YYY")
|
|
end)
|
|
local xv <close> = func2close(function () x = x + 1; error("@XXX") end)
|
|
coroutine.yield(100)
|
|
error(200)
|
|
end)
|
|
assert(co() == 100); assert(x == 0)
|
|
local st, msg = pcall(co); assert(x == 2)
|
|
assert(not st and string.find(msg, "@YYY")) -- should get error raised
|
|
|
|
local x = 0
|
|
local y = 0
|
|
co = coroutine.wrap(function ()
|
|
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")
|
|
end)
|
|
coroutine.yield(100)
|
|
return 200
|
|
end)
|
|
assert(co() == 100); assert(x == 0)
|
|
local st, msg = pcall(co)
|
|
assert(x == 1 and y == 1)
|
|
-- should get first error raised
|
|
assert(not st and string.find(msg, "%w+%.%w+:%d+: YYY"))
|
|
|
|
end
|
|
|
|
|
|
-- a suspended coroutine should not close its variables when collected
|
|
local co
|
|
co = coroutine.wrap(function()
|
|
-- should not run
|
|
local x <close> = func2close(function () os.exit(false) end)
|
|
co = nil
|
|
coroutine.yield()
|
|
end)
|
|
co() -- start coroutine
|
|
assert(co == nil) -- eventually it will be collected
|
|
collectgarbage()
|
|
|
|
|
|
if rawget(_G, "T") then
|
|
print("to-be-closed variables x coroutines in C")
|
|
do
|
|
local token = 0
|
|
local count = 0
|
|
local f = T.makeCfunc[[
|
|
toclose 1
|
|
toclose 2
|
|
return .
|
|
]]
|
|
|
|
local obj = func2close(function (_, msg)
|
|
count = count + 1
|
|
token = coroutine.yield(count, token)
|
|
end)
|
|
|
|
local co = coroutine.wrap(f)
|
|
local ct, res = co(obj, obj, 10, 20, 30, 3) -- will return 10, 20, 30
|
|
-- initial token value, after closing 2nd obj
|
|
assert(ct == 1 and res == 0)
|
|
-- run until yield when closing 1st obj
|
|
ct, res = co(100)
|
|
assert(ct == 2 and res == 100)
|
|
res = {co(200)} -- run until end
|
|
assert(res[1] == 10 and res[2] == 20 and res[3] == 30 and res[4] == nil)
|
|
assert(token == 200)
|
|
end
|
|
|
|
do
|
|
local f = T.makeCfunc[[
|
|
toclose 1
|
|
return .
|
|
]]
|
|
|
|
local obj = func2close(function ()
|
|
local temp
|
|
local x <close> = func2close(function ()
|
|
coroutine.yield(temp)
|
|
return 1,2,3 -- to be ignored
|
|
end)
|
|
temp = coroutine.yield("closing obj")
|
|
return 1,2,3 -- to be ignored
|
|
end)
|
|
|
|
local co = coroutine.wrap(f)
|
|
local res = co(obj, 10, 30, 1) -- will return only 30
|
|
assert(res == "closing obj")
|
|
res = co("closing x")
|
|
assert(res == "closing x")
|
|
res = {co()}
|
|
assert(res[1] == 30 and res[2] == nil)
|
|
end
|
|
|
|
do
|
|
-- still cannot yield inside 'closeslot'
|
|
local f = T.makeCfunc[[
|
|
toclose 1
|
|
closeslot 1
|
|
]]
|
|
local obj = func2close(coroutine.yield)
|
|
local co = coroutine.create(f)
|
|
local st, msg = coroutine.resume(co, obj)
|
|
assert(not st and string.find(msg, "attempt to yield across"))
|
|
|
|
-- nor outside a coroutine
|
|
local f = T.makeCfunc[[
|
|
toclose 1
|
|
]]
|
|
local st, msg = pcall(f, obj)
|
|
assert(not st and string.find(msg, "attempt to yield from outside"))
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- to-be-closed variables in generic for loops
|
|
do
|
|
local numopen = 0
|
|
local function open (x)
|
|
numopen = numopen + 1
|
|
return
|
|
function () -- iteraction function
|
|
x = x - 1
|
|
if x > 0 then return x end
|
|
end,
|
|
nil, -- state
|
|
nil, -- control variable
|
|
func2close(function () numopen = numopen - 1 end) -- closing function
|
|
end
|
|
|
|
local s = 0
|
|
for i in open(10) do
|
|
s = s + i
|
|
end
|
|
assert(s == 45 and numopen == 0)
|
|
|
|
local s = 0
|
|
for i in open(10) do
|
|
if i < 5 then break end
|
|
s = s + i
|
|
end
|
|
assert(s == 35 and numopen == 0)
|
|
|
|
local s = 0
|
|
for i in open(10) do
|
|
for j in open(10) do
|
|
if i + j < 5 then goto endloop end
|
|
s = s + i
|
|
end
|
|
end
|
|
::endloop::
|
|
assert(s == 375 and numopen == 0)
|
|
end
|
|
|
|
print('OK')
|
|
|
|
return 5,f
|
|
|
|
end -- }
|
|
|