1-- $Id: testes/cstack.lua $
2-- See Copyright Notice in file all.lua
3
4
5local tracegc = require"tracegc"
6
7print"testing stack overflow detection"
8
9-- Segmentation faults in these tests probably result from a C-stack
10-- overflow. To avoid these errors, you should set a smaller limit for
11-- the use of C stack by Lua, by changing the constant 'LUAI_MAXCCALLS'.
12-- Alternatively, you can ensure a larger stack for the program.
13
14
15local function checkerror (msg, f, ...)
16 local s, err = pcall(f, ...)
17 assert(not s and string.find(err, msg))
18end
19
20do print("testing stack overflow in message handling")
21 local count = 0
22 local function loop (x, y, z)
23 count = count + 1
24 return 1 + loop(x, y, z)
25 end
26 tracegc.stop() -- __gc should not be called with a full stack
27 local res, msg = xpcall(loop, loop)
28 tracegc.start()
29 assert(msg == "error in error handling")
30 print("final count: ", count)
31end
32
33
34-- bug since 2.5 (C-stack overflow in recursion inside pattern matching)
35do print("testing recursion inside pattern matching")
36 local function f (size)
37 local s = string.rep("a", size)
38 local p = string.rep(".?", size)
39 return string.match(s, p)
40 end
41 local m = f(80)
42 assert(#m == 80)
43 checkerror("too complex", f, 2000)
44end
45
46
47do print("testing stack-overflow in recursive 'gsub'")
48 local count = 0
49 local function foo ()
50 count = count + 1
51 string.gsub("a", ".", foo)
52 end
53 checkerror("stack overflow", foo)
54 print("final count: ", count)
55
56 print("testing stack-overflow in recursive 'gsub' with metatables")
57 local count = 0
58 local t = setmetatable({}, {__index = foo})
59 foo = function ()
60 count = count + 1
61 string.gsub("a", ".", t)
62 end
63 checkerror("stack overflow", foo)
64 print("final count: ", count)
65end
66
67
68do -- bug in 5.4.0
69 print("testing limits in coroutines inside deep calls")
70 local count = 0
71 local lim = 1000
72 local function stack (n)
73 if n > 0 then return stack(n - 1) + 1
74 else coroutine.wrap(function ()
75 count = count + 1
76 stack(lim)
77 end)()
78 end
79 end
80
81 local st, msg = xpcall(stack, function () return "ok" end, lim)
82 assert(not st and msg == "ok")
83 print("final count: ", count)
84end
85
86
87do -- bug since 5.4.0
88 local count = 0
89 print("chain of 'coroutine.close'")
90 -- create N coroutines forming a list so that each one, when closed,
91 -- closes the previous one. (With a large enough N, previous Lua
92 -- versions crash in this test.)
93 local coro = false
94 for i = 1, 1000 do
95 local previous = coro
96 coro = coroutine.create(function()
97 local cc <close> = setmetatable({}, {__close=function()
98 count = count + 1
99 if previous then
100 assert(coroutine.close(previous))
101 end
102 end})
103 coroutine.yield() -- leaves 'cc' pending to be closed
104 end)
105 assert(coroutine.resume(coro)) -- start it and run until it yields
106 end
107 local st, msg = coroutine.close(coro)
108 assert(not st and string.find(msg, "C stack overflow"))
109 print("final count: ", count)
110end
111
112
113do
114 print("nesting of resuming yielded coroutines")
115 local count = 0
116
117 local function body ()
118 coroutine.yield()
119 local f = coroutine.wrap(body)
120 f(); -- start new coroutine (will stop in previous yield)
121 count = count + 1
122 f() -- call it recursively
123 end
124
125 local f = coroutine.wrap(body)
126 f()
127 assert(not pcall(f))
128 print("final count: ", count)
129end
130
131
132do -- bug in 5.4.2
133 print("nesting coroutines running after recoverable errors")
134 local count = 0
135 local function foo()
136 count = count + 1
137 pcall(1) -- create an error
138 -- running now inside 'precover' ("protected recover")
139 coroutine.wrap(foo)() -- call another coroutine
140 end
141 checkerror("C stack overflow", foo)
142 print("final count: ", count)
143end
144
145
146if T then
147 print("testing stack recovery")
148 local N = 0 -- trace number of calls
149 local LIM = -1 -- will store N just before stack overflow
150
151 -- trace stack size; after stack overflow, it should be
152 -- the maximum allowed stack size.
153 local stack1
154 local dummy
155
156 local function err(msg)
157 assert(string.find(msg, "stack overflow"))
158 local _, stacknow = T.stacklevel()
159 assert(stacknow == stack1 + 200)
160 end
161
162 -- When LIM==-1, the 'if' is not executed, so this function only
163 -- counts and stores the stack limits up to overflow. Then, LIM
164 -- becomes N, and then the 'if' code is run when the stack is
165 -- full. Then, there is a stack overflow inside 'xpcall', after which
166 -- the stack must have been restored back to its maximum normal size.
167 local function f()
168 dummy, stack1 = T.stacklevel()
169 if N == LIM then
170 xpcall(f, err)
171 local _, stacknow = T.stacklevel()
172 assert(stacknow == stack1)
173 return
174 end
175 N = N + 1
176 f()
177 end
178
179 local topB, sizeB -- top and size Before overflow
180 local topA, sizeA -- top and size After overflow
181 topB, sizeB = T.stacklevel()
182 tracegc.stop() -- __gc should not be called with a full stack
183 xpcall(f, err)
184 tracegc.start()
185 topA, sizeA = T.stacklevel()
186 -- sizes should be comparable
187 assert(topA == topB and sizeA < sizeB * 2)
188 print(string.format("maximum stack size: %d", stack1))
189 LIM = N -- will stop recursion at maximum level
190 N = 0 -- to count again
191 tracegc.stop() -- __gc should not be called with a full stack
192 f()
193 tracegc.start()
194 print"+"
195end
196
197print'OK'