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'