Advertisement
HR_Shaft

StackTracePlus lua debug for SAPP

May 26th, 2016
157
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.65 KB | None | 0 0
  1. -- For use with lua scripts.  Add the following to your script(s) then load/run them.  
  2. -- If there is an error, it will tell you which script, and which line the error is on in console window.
  3.  
  4. -- add these 3 lines to each of your lua scripts (and uncomment them by removing "--" :
  5.  
  6. --function OnError(Message)
  7.     --print(debug.traceback())
  8. --end
  9.  
  10. -- tables
  11. local _G = _G
  12. local string, io, debug, coroutine = string, io, debug, coroutine
  13.  
  14. -- functions
  15. local tostring, print, require = tostring, print, require
  16. local next, assert = next, assert
  17. local pcall, type, pairs, ipairs = pcall, type, pairs, ipairs
  18. local error = error
  19.  
  20. assert(debug, "debug table must be available at this point")
  21.  
  22. local io_open = io.open
  23. local string_gmatch = string.gmatch
  24. local string_sub = string.sub
  25. local table_concat = table.concat
  26.  
  27. local _M = {
  28.     max_tb_output_len = 70  -- controls the maximum length of the 'stringified' table before cutting with ' (more...)'
  29. }
  30.  
  31. -- this tables should be weak so the elements in them won't become uncollectable
  32. local m_known_tables = { [_G] = "_G (global table)" }
  33. local function add_known_module(name, desc)
  34.     local ok, mod = pcall(require, name)
  35.     if ok then
  36.         m_known_tables[mod] = desc
  37.     end
  38. end
  39.  
  40. add_known_module("string", "string module")
  41. add_known_module("io", "io module")
  42. add_known_module("os", "os module")
  43. add_known_module("table", "table module")
  44. add_known_module("math", "math module")
  45. add_known_module("package", "package module")
  46. add_known_module("debug", "debug module")
  47. add_known_module("coroutine", "coroutine module")
  48.  
  49. -- lua5.2
  50. add_known_module("bit32", "bit32 module")
  51. -- luajit
  52. add_known_module("bit", "bit module")
  53. add_known_module("jit", "jit module")
  54. -- lua5.3
  55. if _VERSION >= "Lua 5.3" then
  56.     add_known_module("utf8", "utf8 module")
  57. end
  58.  
  59.  
  60. local m_user_known_tables = {}
  61.  
  62. local m_known_functions = {}
  63. for _, name in ipairs{
  64.     -- Lua 5.2, 5.1
  65.     "assert",
  66.     "collectgarbage",
  67.     "dofile",
  68.     "error",
  69.     "getmetatable",
  70.     "ipairs",
  71.     "load",
  72.     "loadfile",
  73.     "next",
  74.     "pairs",
  75.     "pcall",
  76.     "print",
  77.     "rawequal",
  78.     "rawget",
  79.     "rawlen",
  80.     "rawset",
  81.     "require",
  82.     "select",
  83.     "setmetatable",
  84.     "tonumber",
  85.     "tostring",
  86.     "type",
  87.     "xpcall",
  88.    
  89.     -- Lua 5.1
  90.     "gcinfo",
  91.     "getfenv",
  92.     "loadstring",
  93.     "module",
  94.     "newproxy",
  95.     "setfenv",
  96.     "unpack",
  97.     -- TODO: add table.* etc functions
  98. } do
  99.     if _G[name] then
  100.         m_known_functions[_G[name]] = name
  101.     end
  102. end
  103.  
  104.  
  105.  
  106. local m_user_known_functions = {}
  107.  
  108. local function safe_tostring (value)
  109.     local ok, err = pcall(tostring, value)
  110.     if ok then return err else return ("<failed to get printable value>: '%s'"):format(err) end
  111. end
  112.  
  113. -- Private:
  114. -- Parses a line, looking for possible function definitions (in a very na�ve way)
  115. -- Returns '(anonymous)' if no function name was found in the line
  116. local function ParseLine(line)
  117.     assert(type(line) == "string")
  118.     --print(line)
  119.     local match = line:match("^%s*function%s+(%w+)")
  120.     if match then
  121.         --print("+++++++++++++function", match)
  122.         return match
  123.     end
  124.     match = line:match("^%s*local%s+function%s+(%w+)")
  125.     if match then
  126.         --print("++++++++++++local", match)
  127.         return match
  128.     end
  129.     match = line:match("^%s*local%s+(%w+)%s+=%s+function")
  130.     if match then
  131.         --print("++++++++++++local func", match)
  132.         return match
  133.     end
  134.     match = line:match("%s*function%s*%(")  -- this is an anonymous function
  135.     if match then
  136.         --print("+++++++++++++function2", match)
  137.         return "(anonymous)"
  138.     end
  139.     return "(anonymous)"
  140. end
  141.  
  142. -- Private:
  143. -- Tries to guess a function's name when the debug info structure does not have it.
  144. -- It parses either the file or the string where the function is defined.
  145. -- Returns '?' if the line where the function is defined is not found
  146. local function GuessFunctionName(info)
  147.     --print("guessing function name")
  148.     if type(info.source) == "string" and info.source:sub(1,1) == "@" then
  149.         local file, err = io_open(info.source:sub(2), "r")
  150.         if not file then
  151.             print("file not found: "..tostring(err))    -- whoops!
  152.             return "?"
  153.         end
  154.         local line
  155.         for _ = 1, info.linedefined do
  156.             line = file:read("*l")
  157.         end
  158.         if not line then
  159.             print("line not found") -- whoops!
  160.             return "?"
  161.         end
  162.         return ParseLine(line)
  163.     else
  164.         local line
  165.         local lineNumber = 0
  166.         for l in string_gmatch(info.source, "([^\n]+)\n-") do
  167.             lineNumber = lineNumber + 1
  168.             if lineNumber == info.linedefined then
  169.                 line = l
  170.                 break
  171.             end
  172.         end
  173.         if not line then
  174.             print("line not found") -- whoops!
  175.             return "?"
  176.         end
  177.         return ParseLine(line)
  178.     end
  179. end
  180.  
  181. ---
  182. -- Dumper instances are used to analyze stacks and collect its information.
  183. --
  184. local Dumper = {}
  185.  
  186. Dumper.new = function(thread)
  187.     local t = { lines = {} }
  188.     for k,v in pairs(Dumper) do t[k] = v end
  189.  
  190.     t.dumping_same_thread = (thread == coroutine.running())
  191.  
  192.     -- if a thread was supplied, bind it to debug.info and debug.get
  193.     -- we also need to skip this additional level we are introducing in the callstack (only if we are running
  194.     -- in the same thread we're inspecting)
  195.     if type(thread) == "thread" then
  196.         t.getinfo = function(level, what)
  197.             if t.dumping_same_thread and type(level) == "number" then
  198.                 level = level + 1
  199.             end
  200.             return debug.getinfo(thread, level, what)
  201.         end
  202.         t.getlocal = function(level, loc)
  203.             if t.dumping_same_thread then
  204.                 level = level + 1
  205.             end
  206.             return debug.getlocal(thread, level, loc)
  207.         end
  208.     else
  209.         t.getinfo = debug.getinfo
  210.         t.getlocal = debug.getlocal
  211.     end
  212.  
  213.     return t
  214. end
  215.  
  216. -- helpers for collecting strings to be used when assembling the final trace
  217. function Dumper:add (text)
  218.     self.lines[#self.lines + 1] = text
  219. end
  220. function Dumper:add_f (fmt, ...)
  221.     self:add(fmt:format(...))
  222. end
  223. function Dumper:concat_lines ()
  224.     return table_concat(self.lines)
  225. end
  226.  
  227. ---
  228. -- Private:
  229. -- Iterates over the local variables of a given function.
  230. --
  231. -- @param level The stack level where the function is.
  232. --
  233. function Dumper:DumpLocals (level)
  234.     local prefix = "\t "
  235.     local i = 1
  236.  
  237.     if self.dumping_same_thread then
  238.         level = level + 1
  239.     end
  240.    
  241.     local name, value = self.getlocal(level, i)
  242.     if not name then
  243.         return
  244.     end
  245.     self:add("\tLocal variables:\r\n")
  246.     while name do
  247.         if type(value) == "number" then
  248.             self:add_f("%s%s = number: %g\r\n", prefix, name, value)
  249.         elseif type(value) == "boolean" then
  250.             self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value))
  251.         elseif type(value) == "string" then
  252.             self:add_f("%s%s = string: %q\r\n", prefix, name, value)
  253.         elseif type(value) == "userdata" then
  254.             self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value))
  255.         elseif type(value) == "nil" then
  256.             self:add_f("%s%s = nil\r\n", prefix, name)
  257.         elseif type(value) == "table" then
  258.             if m_known_tables[value] then
  259.                 self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value])
  260.             elseif m_user_known_tables[value] then
  261.                 self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value])
  262.             else
  263.                 local txt = "{"
  264.                 for k,v in pairs(value) do
  265.                     txt = txt..safe_tostring(k)..":"..safe_tostring(v)
  266.                     if #txt > _M.max_tb_output_len then
  267.                         txt = txt.." (more...)"
  268.                         break
  269.                     end
  270.                     if next(value, k) then txt = txt..", " end
  271.                 end
  272.                 self:add_f("%s%s = %s  %s\r\n", prefix, name, safe_tostring(value), txt.."}")
  273.             end
  274.         elseif type(value) == "function" then
  275.             local info = self.getinfo(value, "nS")
  276.             local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value]
  277.             if info.what == "C" then
  278.                 self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value)))
  279.             else
  280.                 local source = info.short_src
  281.                 if source:sub(2,7) == "string" then
  282.                     source = source:sub(9)  -- uno m�s, por el espacio que viene (string "Baragent.Main", por ejemplo)
  283.                 end
  284.                 --for k,v in pairs(info) do print(k,v) end
  285.                 fun_name = fun_name or GuessFunctionName(info)
  286.                 self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source)
  287.             end
  288.         elseif type(value) == "thread" then
  289.             self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value))
  290.         end
  291.         i = i + 1
  292.         name, value = self.getlocal(level, i)
  293.     end
  294. end
  295.  
  296.  
  297. ---
  298. -- Public:
  299. -- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc.
  300. -- This function is suitable to be used as an error handler with pcall or xpcall
  301. --
  302. -- @param thread An optional thread whose stack is to be inspected (defaul is the current thread)
  303. -- @param message An optional error string or object.
  304. -- @param level An optional number telling at which level to start the traceback (default is 1)
  305. --
  306. -- Returns a string with the stack trace and a string with the original error.
  307. --
  308. function _M.stacktrace(thread, message, level)
  309.     if type(thread) ~= "thread" then
  310.         -- shift parameters left
  311.         thread, message, level = nil, thread, message
  312.     end
  313.  
  314.     thread = thread or coroutine.running()
  315.  
  316.     level = level or 1
  317.  
  318.     local dumper = Dumper.new(thread)
  319.  
  320.     local original_error
  321.    
  322.     if type(message) == "table" then
  323.         dumper:add("an error object {\r\n")
  324.         local first = true
  325.         for k,v in pairs(message) do
  326.             if first then
  327.                 dumper:add("  ")
  328.                 first = false
  329.             else
  330.                 dumper:add(",\r\n  ")
  331.             end
  332.             dumper:add(safe_tostring(k))
  333.             dumper:add(": ")
  334.             dumper:add(safe_tostring(v))
  335.         end
  336.         dumper:add("\r\n}")
  337.         original_error = dumper:concat_lines()
  338.     elseif type(message) == "string" then
  339.         dumper:add(message)
  340.         original_error = message
  341.     end
  342.    
  343.     dumper:add("\r\n")
  344.     dumper:add[[
  345. Stack Traceback
  346. ===============
  347. ]]
  348.     --print(error_message)
  349.    
  350.     local level_to_show = level
  351.     if dumper.dumping_same_thread then level = level + 1 end
  352.  
  353.     local info = dumper.getinfo(level, "nSlf")
  354.     while info do
  355.         if info.what == "main" then
  356.             if string_sub(info.source, 1, 1) == "@" then
  357.                 dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, string_sub(info.source, 2), info.currentline)
  358.             else
  359.                 dumper:add_f("(%d) main chunk of %s at line %d\r\n", level_to_show, info.short_src, info.currentline)
  360.             end
  361.         elseif info.what == "C" then
  362.             --print(info.namewhat, info.name)
  363.             --for k,v in pairs(info) do print(k,v, type(v)) end
  364.             local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func)
  365.             dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name)
  366.             --dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value)))
  367.         elseif info.what == "tail" then
  368.             --print("tail")
  369.             --for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name)
  370.             dumper:add_f("(%d) tail call\r\n", level_to_show)
  371.             dumper:DumpLocals(level)
  372.         elseif info.what == "Lua" then
  373.             local source = info.short_src
  374.             local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name
  375.             if source:sub(2, 7) == "string" then
  376.                 source = source:sub(9)
  377.             end
  378.             local was_guessed = false
  379.             if not function_name or function_name == "?" then
  380.                 --for k,v in pairs(info) do print(k,v, type(v)) end
  381.                 function_name = GuessFunctionName(info)
  382.                 was_guessed = true
  383.             end
  384.             -- test if we have a file name
  385.             local function_type = (info.namewhat == "") and "function" or info.namewhat
  386.             if info.source and info.source:sub(1, 1) == "@" then
  387.                 dumper:add_f("(%d) Lua %s '%s' at file '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "")
  388.             elseif info.source and info.source:sub(1,1) == '#' then
  389.                 dumper:add_f("(%d) Lua %s '%s' at template '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "")
  390.             else
  391.                 dumper:add_f("(%d) Lua %s '%s' at line %d of chunk '%s'\r\n", level_to_show, function_type, function_name, info.currentline, source)
  392.             end
  393.             dumper:DumpLocals(level)
  394.         else
  395.             dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what)
  396.         end
  397.        
  398.         level = level + 1
  399.         level_to_show = level_to_show + 1
  400.         info = dumper.getinfo(level, "nSlf")
  401.     end
  402.    
  403.     return dumper:concat_lines(), original_error
  404. end
  405.  
  406. --
  407. -- Adds a table to the list of known tables
  408. function _M.add_known_table(tab, description)
  409.     if m_known_tables[tab] then
  410.         error("Cannot override an already known table")
  411.     end
  412.     m_user_known_tables[tab] = description
  413. end
  414.  
  415. --
  416. -- Adds a function to the list of known functions
  417. function _M.add_known_function(fun, description)
  418.     if m_known_functions[fun] then
  419.         error("Cannot override an already known function")
  420.     end
  421.     m_user_known_functions[fun] = description
  422. end
  423.  
  424. return _M
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement