-- $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'
