2
0

cstack.lua 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. -- $Id: testes/cstack.lua $
  2. -- See Copyright Notice in file all.lua
  3. local debug = require "debug"
  4. print"testing C-stack overflow detection"
  5. print"If this test crashes, see its file ('cstack.lua')"
  6. -- Segmentation faults in these tests probably result from a C-stack
  7. -- overflow. To avoid these errors, you can use the function
  8. -- 'debug.setcstacklimit' to set a smaller limit for the use of
  9. -- C stack by Lua. After finding a reliable limit, you might want
  10. -- to recompile Lua with this limit as the value for
  11. -- the constant 'LUAI_MAXCCALLS', which defines the default limit.
  12. -- (The default limit is printed by this test.)
  13. -- Alternatively, you can ensure a larger stack for the program.
  14. -- For Linux, a limit up to 30_000 seems Ok. Windows cannot go much
  15. -- higher than 2_000.
  16. -- get and print original limit
  17. local origlimit <const> = debug.setcstacklimit(400)
  18. print("default stack limit: " .. origlimit)
  19. -- Do the tests using the original limit. Or else you may want to change
  20. -- 'currentlimit' to lower values to avoid a seg. fault or to higher
  21. -- values to check whether they are reliable.
  22. local currentlimit <const> = origlimit
  23. debug.setcstacklimit(currentlimit)
  24. print("current stack limit: " .. currentlimit)
  25. local function checkerror (msg, f, ...)
  26. local s, err = pcall(f, ...)
  27. assert(not s and string.find(err, msg))
  28. end
  29. -- auxiliary function to keep 'count' on the screen even if the program
  30. -- crashes.
  31. local count
  32. local back = string.rep("\b", 8)
  33. local function progress ()
  34. count = count + 1
  35. local n = string.format("%-8d", count)
  36. io.stderr:write(back, n) -- erase previous value and write new one
  37. end
  38. do print("testing simple recursion:")
  39. count = 0
  40. local function foo ()
  41. progress()
  42. foo() -- do recursive calls until a stack error (or crash)
  43. end
  44. checkerror("stack overflow", foo)
  45. print("\tfinal count: ", count)
  46. end
  47. do print("testing stack overflow in message handling")
  48. count = 0
  49. local function loop (x, y, z)
  50. progress()
  51. return 1 + loop(x, y, z)
  52. end
  53. local res, msg = xpcall(loop, loop)
  54. assert(msg == "error in error handling")
  55. print("\tfinal count: ", count)
  56. end
  57. -- bug since 2.5 (C-stack overflow in recursion inside pattern matching)
  58. do print("testing recursion inside pattern matching")
  59. local function f (size)
  60. local s = string.rep("a", size)
  61. local p = string.rep(".?", size)
  62. return string.match(s, p)
  63. end
  64. local m = f(80)
  65. assert(#m == 80)
  66. checkerror("too complex", f, 200000)
  67. end
  68. do print("testing stack-overflow in recursive 'gsub'")
  69. count = 0
  70. local function foo ()
  71. progress()
  72. string.gsub("a", ".", foo)
  73. end
  74. checkerror("stack overflow", foo)
  75. print("\tfinal count: ", count)
  76. print("testing stack-overflow in recursive 'gsub' with metatables")
  77. count = 0
  78. local t = setmetatable({}, {__index = foo})
  79. foo = function ()
  80. count = count + 1
  81. progress(count)
  82. string.gsub("a", ".", t)
  83. end
  84. checkerror("stack overflow", foo)
  85. print("\tfinal count: ", count)
  86. end
  87. do -- bug in 5.4.0
  88. print("testing limits in coroutines inside deep calls")
  89. count = 0
  90. local lim = 1000
  91. local function stack (n)
  92. progress()
  93. if n > 0 then return stack(n - 1) + 1
  94. else coroutine.wrap(function ()
  95. stack(lim)
  96. end)()
  97. end
  98. end
  99. print(xpcall(stack, function () return "ok" end, lim))
  100. end
  101. do print("testing changes in C-stack limit")
  102. -- Just an alternative limit, different from the current one
  103. -- (smaller to avoid stack overflows)
  104. local alterlimit <const> = currentlimit * 8 // 10
  105. assert(not debug.setcstacklimit(0)) -- limit too small
  106. assert(not debug.setcstacklimit(50000)) -- limit too large
  107. local co = coroutine.wrap (function ()
  108. return debug.setcstacklimit(alterlimit)
  109. end)
  110. assert(not co()) -- cannot change C stack inside coroutine
  111. local n
  112. local function foo () n = n + 1; foo () end
  113. local function check ()
  114. n = 0
  115. pcall(foo)
  116. return n
  117. end
  118. -- set limit to 'alterlimit'
  119. assert(debug.setcstacklimit(alterlimit) == currentlimit)
  120. local limalter <const> = check()
  121. -- set a very low limit (given that there are already several active
  122. -- calls to arrive here)
  123. local lowlimit <const> = 38
  124. assert(debug.setcstacklimit(lowlimit) == alterlimit)
  125. -- usable limit is much lower, due to active calls
  126. local actuallow = check()
  127. assert(actuallow < lowlimit - 30)
  128. -- now, add 'lowlimit' extra slots, which should all be available
  129. assert(debug.setcstacklimit(lowlimit + lowlimit) == lowlimit)
  130. local lim2 <const> = check()
  131. assert(lim2 == actuallow + lowlimit)
  132. -- 'setcstacklimit' works inside protected calls. (The new stack
  133. -- limit is kept when 'pcall' returns.)
  134. assert(pcall(function ()
  135. assert(debug.setcstacklimit(alterlimit) == lowlimit * 2)
  136. assert(check() <= limalter)
  137. end))
  138. assert(check() == limalter)
  139. -- restore original limit
  140. assert(debug.setcstacklimit(origlimit) == alterlimit)
  141. end
  142. print'OK'