aboutsummaryrefslogtreecommitdiff
path: root/src/MoonP/stacktraceplus.h
diff options
context:
space:
mode:
authorLi Jin <dragon-fly@qq.com>2020-03-11 09:12:17 +0800
committerLi Jin <dragon-fly@qq.com>2020-03-11 09:12:17 +0800
commit61896f4e39c8f70a4cf7b804e1887f4c324a5ab7 (patch)
tree3243f68647271af395a869433f61c4f8dc3ba56f /src/MoonP/stacktraceplus.h
parent9705b3bfd45f536a3efc93a8782b96607547254a (diff)
downloadyuescript-61896f4e39c8f70a4cf7b804e1887f4c324a5ab7.tar.gz
yuescript-61896f4e39c8f70a4cf7b804e1887f4c324a5ab7.tar.bz2
yuescript-61896f4e39c8f70a4cf7b804e1887f4c324a5ab7.zip
fix commit messed with gitignore.
Diffstat (limited to 'src/MoonP/stacktraceplus.h')
-rw-r--r--src/MoonP/stacktraceplus.h548
1 files changed, 548 insertions, 0 deletions
diff --git a/src/MoonP/stacktraceplus.h b/src/MoonP/stacktraceplus.h
new file mode 100644
index 0000000..a309578
--- /dev/null
+++ b/src/MoonP/stacktraceplus.h
@@ -0,0 +1,548 @@
1R"lua_codes(
2--[[
3Copyright (c) 2010 Ignacio BurgueƱo, modified by Li Jin
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in
13all copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21THE SOFTWARE.]]
22
23-- tables
24local _G = _G
25local string, io, debug, coroutine = string, io, debug, coroutine
26
27-- functions
28local tostring, require = tostring, require
29local next, assert = next, assert
30local pcall, type, pairs, ipairs = pcall, type, pairs, ipairs
31local error = error
32
33assert(debug, "debug table must be available at this point")
34
35local string_gmatch = string.gmatch
36local string_sub = string.sub
37local table_concat = table.concat
38
39local moonp = require("moonp")
40
41local _M = {
42 max_tb_output_len = 70, -- controls the maximum length of the 'stringified' table before cutting with ' (more...)'
43 dump_locals = true,
44 simplified = false
45}
46
47-- this tables should be weak so the elements in them won't become uncollectable
48local m_known_tables = { [_G] = "_G (global table)" }
49local function add_known_module(name, desc)
50 local ok, mod = pcall(require, name)
51 if ok then
52 m_known_tables[mod] = desc
53 end
54end
55
56add_known_module("string", "string module")
57add_known_module("io", "io module")
58add_known_module("os", "os module")
59add_known_module("table", "table module")
60add_known_module("math", "math module")
61add_known_module("package", "package module")
62add_known_module("debug", "debug module")
63add_known_module("coroutine", "coroutine module")
64
65-- lua5.2
66add_known_module("bit32", "bit32 module")
67-- luajit
68add_known_module("bit", "bit module")
69add_known_module("jit", "jit module")
70-- lua5.3
71if _VERSION >= "Lua 5.3" then
72 add_known_module("utf8", "utf8 module")
73end
74
75
76local m_user_known_tables = {}
77
78local m_known_functions = {}
79for _, name in ipairs{
80 -- Lua 5.2, 5.1
81 "assert",
82 "collectgarbage",
83 "dofile",
84 "error",
85 "getmetatable",
86 "ipairs",
87 "load",
88 "loadfile",
89 "next",
90 "pairs",
91 "pcall",
92 "print",
93 "rawequal",
94 "rawget",
95 "rawlen",
96 "rawset",
97 "require",
98 "select",
99 "setmetatable",
100 "tonumber",
101 "tostring",
102 "type",
103 "xpcall",
104
105 -- Lua 5.1
106 "gcinfo",
107 "getfenv",
108 "loadstring",
109 "module",
110 "newproxy",
111 "setfenv",
112 "unpack",
113 -- TODO: add table.* etc functions
114} do
115 if _G[name] then
116 m_known_functions[_G[name]] = name
117 end
118end
119
120local m_user_known_functions = {}
121
122local function safe_tostring (value)
123 local ok, err = pcall(tostring, value)
124 if ok then return err else return ("<failed to get printable value>: '%s'"):format(err) end
125end
126
127-- Private:
128-- Parses a line, looking for possible function definitions (in a very naive way)
129-- Returns '(anonymous)' if no function name was found in the line
130local function ParseLine(line)
131 assert(type(line) == "string")
132 local match = line:match("^%s*function%s+(%w+)")
133 if match then
134 --print("+++++++++++++function", match)
135 return match
136 end
137 match = line:match("^%s*local%s+function%s+(%w+)")
138 if match then
139 --print("++++++++++++local", match)
140 return match
141 end
142 match = line:match("^%s*local%s+(%w+)%s+=%s+function")
143 if match then
144 --print("++++++++++++local func", match)
145 return match
146 end
147 match = line:match("%s*function%s*%(") -- this is an anonymous function
148 if match then
149 --print("+++++++++++++function2", match)
150 return "(anonymous)"
151 end
152 return "(anonymous)"
153end
154
155-- Private:
156-- Tries to guess a function's name when the debug info structure does not have it.
157-- It parses either the file or the string where the function is defined.
158-- Returns '?' if the line where the function is defined is not found
159local function GuessFunctionName(info)
160 -- print("guessing function name")
161 if type(info.source) == "string" and info.source:sub(1,1) == "@" then
162 local fname = info.source:sub(2)
163 local text
164 if moonp.file_exist(fname) then
165 text = moonp.read_file(fname)
166 end
167 if not text then
168 -- print("file not found: "..tostring(err)) -- whoops!
169 return "?"
170 end
171 local line
172 local count = 0
173 for lineText in (text.."\n"):gmatch("(.-)\n") do
174 line = lineText
175 count = count + 1
176 if count == info.linedefined then
177 break
178 end
179 end
180 if not line then
181 --print("line not found") -- whoops!
182 return "?"
183 end
184 return ParseLine(line)
185 else
186 local line
187 local lineNumber = 0
188 for l in string_gmatch(info.source, "([^\n]+)\n-") do
189 lineNumber = lineNumber + 1
190 if lineNumber == info.linedefined then
191 line = l
192 break
193 end
194 end
195 if not line then
196 -- print("line not found") -- whoops!
197 return "?"
198 end
199 return ParseLine(line)
200 end
201end
202
203---
204-- Dumper instances are used to analyze stacks and collect its information.
205--
206local Dumper = {}
207
208Dumper.new = function(thread)
209 local t = { lines = {} }
210 for k,v in pairs(Dumper) do t[k] = v end
211
212 t.dumping_same_thread = (thread == coroutine.running())
213
214 -- if a thread was supplied, bind it to debug.info and debug.get
215 -- we also need to skip this additional level we are introducing in the callstack (only if we are running
216 -- in the same thread we're inspecting)
217 if type(thread) == "thread" then
218 t.getinfo = function(level, what)
219 if t.dumping_same_thread and type(level) == "number" then
220 level = level + 1
221 end
222 return debug.getinfo(thread, level, what)
223 end
224 t.getlocal = function(level, loc)
225 if t.dumping_same_thread then
226 level = level + 1
227 end
228 return debug.getlocal(thread, level, loc)
229 end
230 else
231 t.getinfo = debug.getinfo
232 t.getlocal = debug.getlocal
233 end
234
235 return t
236end
237
238-- helpers for collecting strings to be used when assembling the final trace
239function Dumper:add (text)
240 self.lines[#self.lines + 1] = text
241end
242function Dumper:add_f (fmt, ...)
243 self:add(fmt:format(...))
244end
245function Dumper:concat_lines ()
246 return table_concat(self.lines)
247end
248
249---
250-- Private:
251-- Iterates over the local variables of a given function.
252--
253-- @param level The stack level where the function is.
254--
255function Dumper:DumpLocals (level)
256 if not _M.dump_locals then return end
257
258 local prefix = "\t "
259 local i = 1
260
261 if self.dumping_same_thread then
262 level = level + 1
263 end
264
265 local name, value = self.getlocal(level, i)
266 if not name then
267 return
268 end
269 self:add("\tLocal variables:\r\n")
270 while name do
271 if type(value) == "number" then
272 self:add_f("%s%s = number: %g\r\n", prefix, name, value)
273 elseif type(value) == "boolean" then
274 self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value))
275 elseif type(value) == "string" then
276 self:add_f("%s%s = string: %q\r\n", prefix, name, value)
277 elseif type(value) == "userdata" then
278 self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value))
279 elseif type(value) == "nil" then
280 self:add_f("%s%s = nil\r\n", prefix, name)
281 elseif type(value) == "table" then
282 if m_known_tables[value] then
283 self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value])
284 elseif m_user_known_tables[value] then
285 self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value])
286 else
287 local txt = "{"
288 for k,v in pairs(value) do
289 txt = txt..safe_tostring(k)..":"..safe_tostring(v)
290 if #txt > _M.max_tb_output_len then
291 txt = txt.." (more...)"
292 break
293 end
294 if next(value, k) then txt = txt..", " end
295 end
296 self:add_f("%s%s = %s %s\r\n", prefix, name, safe_tostring(value), txt.."}")
297 end
298 elseif type(value) == "function" then
299 local info = self.getinfo(value, "nS")
300 local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value]
301 if info.what == "C" then
302 self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value)))
303 else
304 local source = info.short_src
305 if source:sub(2,7) == "string" then
306 source = source:sub(9)
307 end
308 --for k,v in pairs(info) do print(k,v) end
309 fun_name = fun_name or GuessFunctionName(info)
310 self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source)
311 end
312 elseif type(value) == "thread" then
313 self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value))
314 end
315 i = i + 1
316 name, value = self.getlocal(level, i)
317 end
318end
319
320local function getMoonLineNumber(fname, line)
321 local moonCompiled = require("moonp").moon_compiled
322 local source = moonCompiled["@"..fname]
323 if not source then
324 source = moonCompiled["@="..fname]
325 end
326 if not source then
327 if moonp.file_exist(fname) then
328 local codes = moonp.read_file(fname)
329 local moonFile = codes:match("^%s*--%s*%[moon%]:%s*([^\n]*)")
330 if moonFile then
331 fname = moonFile:gsub("^%s*(.-)%s*$", "%1")
332 source = codes
333 end
334 end
335 end
336 if source then
337 local i, target = 1, tonumber(line)
338 for lineCode in source:gmatch("([^\n]*)\n") do
339 if i == target then
340 local num = lineCode:match("--%s*(%d*)%s*$")
341 if num then
342 return fname, num
343 end
344 break
345 end
346 i = i + 1
347 end
348 end
349 return fname, line
350end
351
352---
353-- Public:
354-- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc.
355-- This function is suitable to be used as an error handler with pcall or xpcall
356--
357-- @param thread An optional thread whose stack is to be inspected (defaul is the current thread)
358-- @param message An optional error string or object.
359-- @param level An optional number telling at which level to start the traceback (default is 1)
360--
361-- Returns a string with the stack trace and a string with the original error.
362--
363function _M.stacktrace(thread, message, level)
364 if type(thread) ~= "thread" then
365 -- shift parameters left
366 thread, message, level = nil, thread, message
367 end
368
369 thread = thread or coroutine.running()
370
371 level = level or 1
372
373 local dumper = Dumper.new(thread)
374
375 if type(message) == "table" then
376 dumper:add("an error object {\r\n")
377 local first = true
378 for k,v in pairs(message) do
379 if first then
380 dumper:add(" ")
381 first = false
382 else
383 dumper:add(",\r\n ")
384 end
385 dumper:add(safe_tostring(k))
386 dumper:add(": ")
387 dumper:add(safe_tostring(v))
388 end
389 dumper:add("\r\n}")
390 elseif type(message) == "string" then
391 local fname, line, msg = message:match('(.+):(%d+): (.*)$')
392 local nfname, nline, nmsg = fname:match('(.+):(%d+): (.*)$')
393 if nfname then
394 fname = nmsg
395 end
396 if fname then
397 fname = fname:gsub("%[string \"", "")
398 fname = fname:gsub("\"%]", "")
399 fname = fname:gsub("^%s*(.-)%s*$", "%1")
400 local extension = fname:match("%.([^%.\\/]*)$")
401 if not extension then
402 local fext = fname .. ".lua"
403 if moonp.file_exist(fext) then
404 fname = fext
405 else
406 fext = fname .. ".moon"
407 if moonp.file_exist(fext) then
408 fname = fext
409 end
410 end
411 end
412 fname, line = getMoonLineNumber(fname, line)
413 if _M.simplified then
414 message = table.concat({
415 "", fname, ":",
416 line, ": ", msg})
417 message = message:gsub("^%(moonplus%):%s*%d+:%s*", "")
418 message = message:gsub("%s(%d+):", "%1:")
419 else
420 message = table.concat({
421 "[string \"", fname, "\"]:",
422 line, ": ", msg})
423 end
424 end
425 dumper:add(message)
426 end
427
428 local moonp = require("moonp")
429 if moonp._hide_stacktrace_ then
430 local msg = dumper:concat_lines()
431 moonp._hide_stacktrace_ = nil
432 return message
433 end
434
435 dumper:add("\r\n")
436 dumper:add[[
437Stack Traceback
438===============
439]]
440
441 local level_to_show = 1
442 if dumper.dumping_same_thread then level = level + 1 end
443
444 local info = dumper.getinfo(level, "nSlf")
445 while info do
446 if info.source and info.source:sub(1,1) == "@" then
447 info.source = info.source:sub(2)
448 elseif info.what == "main" or info.what == "Lua" then
449 info.source = info.source
450 end
451 local fname = info.source
452 local extension = fname:match("%.([^%.\\/]*)$")
453 if not extension then
454 local fext = fname .. ".lua"
455 if moonp.file_exist(fext) then
456 fname = fext
457 else
458 fext = fname .. ".moon"
459 if moonp.file_exist(fext) then
460 fname = fext
461 end
462 end
463 end
464 info.source, info.currentline = getMoonLineNumber(fname, info.currentline)
465 if info.what == "main" then
466 if _M.simplified then
467 dumper:add_f("(%d) '%s':%d\r\n", level_to_show, info.source, info.currentline)
468 else
469 dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, info.source, info.currentline)
470 end
471 elseif info.what == "C" then
472 --print(info.namewhat, info.name)
473 --for k,v in pairs(info) do print(k,v, type(v)) end
474 local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func)
475 dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name)
476 --dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value)))
477 elseif info.what == "tail" then
478 --print("tail")
479 --for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name)
480 dumper:add_f("(%d) tail call\r\n", level_to_show)
481 dumper:DumpLocals(level)
482 elseif info.what == "Lua" then
483 local source = info.source
484 local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name
485 if source:sub(2, 7) == "string" then
486 source = source:sub(10,-3)
487 end
488 local was_guessed = false
489 if not function_name or function_name == "?" then
490 --for k,v in pairs(info) do print(k,v, type(v)) end
491 function_name = GuessFunctionName(info)
492 was_guessed = true
493 end
494 -- test if we have a file name
495 local function_type = (info.namewhat == "") and "function" or info.namewhat
496 if info.source and info.source:sub(1, 1) == "@" then
497 if _M.simplified then
498 dumper:add_f("(%d) '%s':%d%s\r\n", level_to_show, info.source:sub(2), info.currentline, was_guessed and " (guess)" or "")
499 else
500 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 "")
501 end
502 elseif info.source and info.source:sub(1,1) == '#' then
503 if _M.simplified then
504 dumper:add_f("(%d) '%s':%d%s\r\n", level_to_show, info.source:sub(2), info.currentline, was_guessed and " (guess)" or "")
505 else
506 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 "")
507 end
508 else
509 if _M.simplified then
510 dumper:add_f("(%d) '%s':%d\r\n", level_to_show, source, info.currentline)
511 else
512 dumper:add_f("(%d) Lua %s '%s' at chunk '%s':%d\r\n", level_to_show, function_type, function_name, source, info.currentline)
513 end
514 end
515 dumper:DumpLocals(level)
516 else
517 dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what)
518 end
519
520 level = level + 1
521 level_to_show = level_to_show + 1
522 info = dumper.getinfo(level, "nSlf")
523 end
524
525 return dumper:concat_lines()
526end
527
528--
529-- Adds a table to the list of known tables
530function _M.add_known_table(tab, description)
531 if m_known_tables[tab] then
532 error("Cannot override an already known table")
533 end
534 m_user_known_tables[tab] = description
535end
536
537--
538-- Adds a function to the list of known functions
539function _M.add_known_function(fun, description)
540 if m_known_functions[fun] then
541 error("Cannot override an already known function")
542 end
543 m_user_known_functions[fun] = description
544end
545
546return _M
547
548)lua_codes";