mirror of
https://github.com/lua/lua.git
synced 2025-01-28 06:03:00 +08:00
7696c6474f
To remove a to-be-closed variable from the stack in the C API a function must use 'lua_settop' or 'lua_pop'. Previous implementation of 'luaL_pushresult' was not closing the box. (This commit also added tests to check that box is being closed "as soon as possible".)
479 lines
11 KiB
Lua
479 lines
11 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
|
|
-- to-be-closed variables must be closed in tail calls
|
|
local X, Y
|
|
local function foo ()
|
|
local *toclose _ = function () Y = 10 end
|
|
assert(X == 20 and Y == nil)
|
|
return 1,2,3
|
|
end
|
|
|
|
local function bar ()
|
|
local *toclose _ = function () X = 20 end
|
|
return foo()
|
|
end
|
|
|
|
local a, b, c, d = bar()
|
|
assert(a == 1 and b == 2 and c == 3 and X == 20 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
|
|
|
|
|
|
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 -- }
|
|
|