mirror of https://github.com/lua/lua
198 lines
5.3 KiB
Lua
198 lines
5.3 KiB
Lua
-- $Id: testes/cstack.lua $
|
|
-- See Copyright Notice in file all.lua
|
|
|
|
|
|
local tracegc = require"tracegc"
|
|
|
|
print"testing stack overflow detection"
|
|
|
|
-- Segmentation faults in these tests probably result from a C-stack
|
|
-- overflow. To avoid these errors, you should set a smaller limit for
|
|
-- the use of C stack by Lua, by changing the constant 'LUAI_MAXCCALLS'.
|
|
-- Alternatively, you can ensure a larger stack for the program.
|
|
|
|
|
|
local function checkerror (msg, f, ...)
|
|
local s, err = pcall(f, ...)
|
|
assert(not s and string.find(err, msg))
|
|
end
|
|
|
|
do print("testing stack overflow in message handling")
|
|
local count = 0
|
|
local function loop (x, y, z)
|
|
count = count + 1
|
|
return 1 + loop(x, y, z)
|
|
end
|
|
tracegc.stop() -- __gc should not be called with a full stack
|
|
local res, msg = xpcall(loop, loop)
|
|
tracegc.start()
|
|
assert(msg == "error in error handling")
|
|
print("final count: ", count)
|
|
end
|
|
|
|
|
|
-- bug since 2.5 (C-stack overflow in recursion inside pattern matching)
|
|
do print("testing recursion inside pattern matching")
|
|
local function f (size)
|
|
local s = string.rep("a", size)
|
|
local p = string.rep(".?", size)
|
|
return string.match(s, p)
|
|
end
|
|
local m = f(80)
|
|
assert(#m == 80)
|
|
checkerror("too complex", f, 2000)
|
|
end
|
|
|
|
|
|
do print("testing stack-overflow in recursive 'gsub'")
|
|
local count = 0
|
|
local function foo ()
|
|
count = count + 1
|
|
string.gsub("a", ".", foo)
|
|
end
|
|
checkerror("stack overflow", foo)
|
|
print("final count: ", count)
|
|
|
|
print("testing stack-overflow in recursive 'gsub' with metatables")
|
|
local count = 0
|
|
local t = setmetatable({}, {__index = foo})
|
|
foo = function ()
|
|
count = count + 1
|
|
string.gsub("a", ".", t)
|
|
end
|
|
checkerror("stack overflow", foo)
|
|
print("final count: ", count)
|
|
end
|
|
|
|
|
|
do -- bug in 5.4.0
|
|
print("testing limits in coroutines inside deep calls")
|
|
local count = 0
|
|
local lim = 1000
|
|
local function stack (n)
|
|
if n > 0 then return stack(n - 1) + 1
|
|
else coroutine.wrap(function ()
|
|
count = count + 1
|
|
stack(lim)
|
|
end)()
|
|
end
|
|
end
|
|
|
|
local st, msg = xpcall(stack, function () return "ok" end, lim)
|
|
assert(not st and msg == "ok")
|
|
print("final count: ", count)
|
|
end
|
|
|
|
|
|
do -- bug since 5.4.0
|
|
local count = 0
|
|
print("chain of 'coroutine.close'")
|
|
-- create N coroutines forming a list so that each one, when closed,
|
|
-- closes the previous one. (With a large enough N, previous Lua
|
|
-- versions crash in this test.)
|
|
local coro = false
|
|
for i = 1, 1000 do
|
|
local previous = coro
|
|
coro = coroutine.create(function()
|
|
local cc <close> = setmetatable({}, {__close=function()
|
|
count = count + 1
|
|
if previous then
|
|
assert(coroutine.close(previous))
|
|
end
|
|
end})
|
|
coroutine.yield() -- leaves 'cc' pending to be closed
|
|
end)
|
|
assert(coroutine.resume(coro)) -- start it and run until it yields
|
|
end
|
|
local st, msg = coroutine.close(coro)
|
|
assert(not st and string.find(msg, "C stack overflow"))
|
|
print("final count: ", count)
|
|
end
|
|
|
|
|
|
do
|
|
print("nesting of resuming yielded coroutines")
|
|
local count = 0
|
|
|
|
local function body ()
|
|
coroutine.yield()
|
|
local f = coroutine.wrap(body)
|
|
f(); -- start new coroutine (will stop in previous yield)
|
|
count = count + 1
|
|
f() -- call it recursively
|
|
end
|
|
|
|
local f = coroutine.wrap(body)
|
|
f()
|
|
assert(not pcall(f))
|
|
print("final count: ", count)
|
|
end
|
|
|
|
|
|
do -- bug in 5.4.2
|
|
print("nesting coroutines running after recoverable errors")
|
|
local count = 0
|
|
local function foo()
|
|
count = count + 1
|
|
pcall(1) -- create an error
|
|
-- running now inside 'precover' ("protected recover")
|
|
coroutine.wrap(foo)() -- call another coroutine
|
|
end
|
|
checkerror("C stack overflow", foo)
|
|
print("final count: ", count)
|
|
end
|
|
|
|
|
|
if T then
|
|
print("testing stack recovery")
|
|
local N = 0 -- trace number of calls
|
|
local LIM = -1 -- will store N just before stack overflow
|
|
|
|
-- trace stack size; after stack overflow, it should be
|
|
-- the maximum allowed stack size.
|
|
local stack1
|
|
local dummy
|
|
|
|
local function err(msg)
|
|
assert(string.find(msg, "stack overflow"))
|
|
local _, stacknow = T.stacklevel()
|
|
assert(stacknow == stack1 + 200)
|
|
end
|
|
|
|
-- When LIM==-1, the 'if' is not executed, so this function only
|
|
-- counts and stores the stack limits up to overflow. Then, LIM
|
|
-- becomes N, and then the 'if' code is run when the stack is
|
|
-- full. Then, there is a stack overflow inside 'xpcall', after which
|
|
-- the stack must have been restored back to its maximum normal size.
|
|
local function f()
|
|
dummy, stack1 = T.stacklevel()
|
|
if N == LIM then
|
|
xpcall(f, err)
|
|
local _, stacknow = T.stacklevel()
|
|
assert(stacknow == stack1)
|
|
return
|
|
end
|
|
N = N + 1
|
|
f()
|
|
end
|
|
|
|
local topB, sizeB -- top and size Before overflow
|
|
local topA, sizeA -- top and size After overflow
|
|
topB, sizeB = T.stacklevel()
|
|
tracegc.stop() -- __gc should not be called with a full stack
|
|
xpcall(f, err)
|
|
tracegc.start()
|
|
topA, sizeA = T.stacklevel()
|
|
-- sizes should be comparable
|
|
assert(topA == topB and sizeA < sizeB * 2)
|
|
print(string.format("maximum stack size: %d", stack1))
|
|
LIM = N -- will stop recursion at maximum level
|
|
N = 0 -- to count again
|
|
tracegc.stop() -- __gc should not be called with a full stack
|
|
f()
|
|
tracegc.start()
|
|
print"+"
|
|
end
|
|
|
|
print'OK'
|