1
0
mirror of https://github.com/lua/lua.git synced 2025-01-28 06:03:00 +08:00
lua/testes/locals.lua
Roberto Ierusalimschy 41c800b352 Closing methods should not interfere with returning values
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.
2018-10-25 12:50:20 -03:00

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 -- }