mirror of
https://github.com/lua/lua.git
synced 2025-01-28 06:03:00 +08:00
41c800b352
A closing method cannot be called in its own stack slot, as there may be returning values in the stack after that slot, and the call would corrupt those values. Instead, the closing method must be copied to the top of the stack to be called. Moreover, even when a function returns no value, its return istruction still has to have its position (which will set the stack top) after the local variables, otherwise a closing method might corrupt another not-yet-called closing method.
369 lines
8.4 KiB
Lua
369 lines
8.4 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 scoped x = setmetatable({"x"}, {__close = function (self)
|
|
a[#a + 1] = self[1] end})
|
|
local scoped 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 scoped _ = 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 scoped _ = closescope
|
|
local y = 15
|
|
return y
|
|
end
|
|
|
|
assert(foo() == 15 and X == true)
|
|
|
|
X = false
|
|
foo = function ()
|
|
local scoped 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 scoped _ = function () Y = 10 end
|
|
assert(X == 20 and Y == nil)
|
|
return 1,2,3
|
|
end
|
|
|
|
local function bar ()
|
|
local scoped _ = 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 scoped x = function (msg) log[#log + 1] = msg; error(1) end
|
|
local scoped x1 = function (msg) log[#log + 1] = msg; end
|
|
local scoped gc = function () collectgarbage() end
|
|
local scoped y = function (msg) log[#log + 1] = msg; error(2) end
|
|
local scoped 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 scoped y = function () T.alloccount() end
|
|
local scoped 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 scoped 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 == "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 scoped xxx = function (msg)
|
|
assert(msg == "not enough memory");
|
|
error(1000) -- raise another error
|
|
end
|
|
local scoped xx = function (msg)
|
|
assert(msg == "not enough memory");
|
|
end
|
|
local scoped 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)
|
|
|
|
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 scoped xv = function () x = true end
|
|
do
|
|
local scoped 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 scoped 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
|
|
|
|
print('OK')
|
|
|
|
return 5,f
|
|
|
|
end -- }
|
|
|