lua/testes/locals.lua
Roberto Ierusalimschy 28d829c867 Calls cannot be tail in the scope of a to-be-closed variable
A to-be-closed variable must be closed when a block ends, so even
a 'return foo()' cannot directly returns the results of 'foo'; the
function must close the scope before returning.
2018-12-04 15:01:42 -02:00

503 lines
12 KiB
Lua

-- $Id: testes/locals.lua $
-- See Copyright Notice in file all.lua
print('testing local variables and environments')
local debug = require"debug"
-- 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
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 (now just a generic test)
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 fiels 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
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)
print"testing to-be-closed variables"
local function stack(n) n = ((n == 0) or stack(n - 1)) 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
a[#a + 1] = "in"
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 function closescope () stack(10); X = true end
-- closing functions do not corrupt returning values
local function foo (x)
local *toclose _ = 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 *toclose _ = closescope
local y = 15
return y
end
assert(foo() == 15 and X == true)
X = false
foo = function ()
local *toclose x = closescope
return x
end
assert(foo() == closescope and X == true)
end
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
assert(X == true and Y == nil) -- 'X' not closed yet
return 1,2,3
end
local function bar ()
local *toclose _ = 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 -- 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
if err then error(4) end
end
local stat, msg = pcall(foo, false)
assert(msg == 1)
assert(log[1] == 10 and log[2] == 3 and log[3] == 2 and log[4] == 2
and #log == 4)
log = {}
local stat, msg = pcall(foo, true)
assert(msg == 1)
assert(log[1] == 4 and log[2] == 3 and log[3] == 2 and log[4] == 2
and #log == 4)
end
do
-- errors due to non-closable values
local function foo ()
local *toclose x = 34
end
local stat, msg = pcall(foo)
assert(not stat and string.find(msg, "variable 'x'"))
-- with other errors, non-closable values are ignored
local function foo ()
local *toclose x = 34
local *toclose y = function () error(32) end
end
local stat, msg = pcall(foo)
assert(not stat and msg == 32)
end
if rawget(_G, "T") then
-- memory error inside closing function
local function foo ()
local *toclose y = function () T.alloccount() end
local *toclose x = setmetatable({}, {__close = function ()
T.alloccount(0); local x = {} -- force a memory error
end})
error("a") -- 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 function close (msg)
T.alloccount()
assert(msg == "not enough memory")
end
-- set a memory limit and return a closing function to remove the limit
local function enter (count)
stack(10) -- reserve some stack space
T.alloccount(count)
return close
end
local function test ()
local *toclose x = enter(0) -- set a memory limit
-- creation of previous upvalue will raise a memory error
assert(false) -- should not run
end
local _, msg = pcall(test)
assert(msg == "not enough memory")
-- now use metamethod for closing
close = setmetatable({}, {__close = function ()
T.alloccount()
end})
-- repeat test with extra closing upvalues
local function test ()
local *toclose xxx = function (msg)
assert(msg == "not enough memory");
error(1000) -- raise another error
end
local *toclose xx = function (msg)
assert(msg == "not enough memory");
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
end
local _, msg = pcall(test)
assert(msg == 1000)
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()
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, upvalue, 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
end
-- to-be-closed variables in coroutines
do
-- an error in a coroutine closes variables
local x = false
local y = false
local co = coroutine.create(function ()
local *toclose xv = function () x = true end
do
local *toclose yv = 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 a, b = coroutine.resume(co)
assert(a and b == 100 and not x and not y)
a, b = coroutine.resume(co)
assert(a and b == 200 and not x and y)
a, b = coroutine.resume(co)
assert(not a and b == 23 and x and y)
end
-- a suspended coroutine should not close its variables when collected
local co
co = coroutine.wrap(function()
local *toclose x = function () os.exit(false) end -- should not run
co = nil
coroutine.yield()
end)
co() -- start coroutine
assert(co == nil) -- eventually it will be collected
-- 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
function () -- closing function
numopen = numopen - 1
end
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)
-- repeat test with '__open' metamethod instead of a function
local function open (x)
numopen = numopen + 1
local state = setmetatable({x},
{__close = function () numopen = numopen - 1 end})
return
function (t) -- iteraction function
t[1] = t[1] - 1
if t[1] > 0 then return t[1] end
end,
state,
nil,
state -- to-be-closed
end
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)
end
print('OK')
return 5,f
end -- }