aboutsummaryrefslogtreecommitdiff
path: root/src/StackTracePlus.h
diff options
context:
space:
mode:
authorLi Jin <dragon-fly@qq.com>2020-03-11 00:21:33 +0800
committerLi Jin <dragon-fly@qq.com>2020-03-11 00:21:33 +0800
commit1ee056eedf773ac6166247755439092e0e0a9bca (patch)
tree1b8863338d37fe6ded7fbaecd92d76cdb3801ab3 /src/StackTracePlus.h
parent015af4b70cd751e1c1580fd542997a796e1ca225 (diff)
downloadyuescript-1ee056eedf773ac6166247755439092e0e0a9bca.tar.gz
yuescript-1ee056eedf773ac6166247755439092e0e0a9bca.tar.bz2
yuescript-1ee056eedf773ac6166247755439092e0e0a9bca.zip
add macro functions.
Diffstat (limited to 'src/StackTracePlus.h')
-rw-r--r--src/StackTracePlus.h548
1 files changed, 0 insertions, 548 deletions
diff --git a/src/StackTracePlus.h b/src/StackTracePlus.h
deleted file mode 100644
index 202ae22..0000000
--- a/src/StackTracePlus.h
+++ /dev/null
@@ -1,548 +0,0 @@
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 _M = {
40 max_tb_output_len = 70, -- controls the maximum length of the 'stringified' table before cutting with ' (more...)'
41 dump_locals = true,
42 simplified = false
43}
44
45-- this tables should be weak so the elements in them won't become uncollectable
46local m_known_tables = { [_G] = "_G (global table)" }
47local function add_known_module(name, desc)
48 local ok, mod = pcall(require, name)
49 if ok then
50 m_known_tables[mod] = desc
51 end
52end
53
54add_known_module("string", "string module")
55add_known_module("io", "io module")
56add_known_module("os", "os module")
57add_known_module("table", "table module")
58add_known_module("math", "math module")
59add_known_module("package", "package module")
60add_known_module("debug", "debug module")
61add_known_module("coroutine", "coroutine module")
62
63-- lua5.2
64add_known_module("bit32", "bit32 module")
65-- luajit
66add_known_module("bit", "bit module")
67add_known_module("jit", "jit module")
68-- lua5.3
69if _VERSION >= "Lua 5.3" then
70 add_known_module("utf8", "utf8 module")
71end
72
73
74local m_user_known_tables = {}
75
76local m_known_functions = {}
77for _, name in ipairs{
78 -- Lua 5.2, 5.1
79 "assert",
80 "collectgarbage",
81 "dofile",
82 "error",
83 "getmetatable",
84 "ipairs",
85 "load",
86 "loadfile",
87 "next",
88 "pairs",
89 "pcall",
90 "print",
91 "rawequal",
92 "rawget",
93 "rawlen",
94 "rawset",
95 "require",
96 "select",
97 "setmetatable",
98 "tonumber",
99 "tostring",
100 "type",
101 "xpcall",
102
103 -- Lua 5.1
104 "gcinfo",
105 "getfenv",
106 "loadstring",
107 "module",
108 "newproxy",
109 "setfenv",
110 "unpack",
111 -- TODO: add table.* etc functions
112} do
113 if _G[name] then
114 m_known_functions[_G[name]] = name
115 end
116end
117
118local m_user_known_functions = {}
119
120local function safe_tostring (value)
121 local ok, err = pcall(tostring, value)
122 if ok then return err else return ("<failed to get printable value>: '%s'"):format(err) end
123end
124
125-- Private:
126-- Parses a line, looking for possible function definitions (in a very naive way)
127-- Returns '(anonymous)' if no function name was found in the line
128local function ParseLine(line)
129 assert(type(line) == "string")
130 local match = line:match("^%s*function%s+(%w+)")
131 if match then
132 --print("+++++++++++++function", match)
133 return match
134 end
135 match = line:match("^%s*local%s+function%s+(%w+)")
136 if match then
137 --print("++++++++++++local", match)
138 return match
139 end
140 match = line:match("^%s*local%s+(%w+)%s+=%s+function")
141 if match then
142 --print("++++++++++++local func", match)
143 return match
144 end
145 match = line:match("%s*function%s*%(") -- this is an anonymous function
146 if match then
147 --print("+++++++++++++function2", match)
148 return "(anonymous)"
149 end
150 return "(anonymous)"
151end
152
153-- Private:
154-- Tries to guess a function's name when the debug info structure does not have it.
155-- It parses either the file or the string where the function is defined.
156-- Returns '?' if the line where the function is defined is not found
157local function GuessFunctionName(info)
158 -- print("guessing function name")
159 if type(info.source) == "string" and info.source:sub(1,1) == "@" then
160 local file = io.open(info.source:sub(2))
161 local text
162 if file then
163 text = file:read("*a")
164 file:close()
165 end
166 if not text then
167 -- print("file not found: "..tostring(err)) -- whoops!
168 return "?"
169 end
170 local line
171 local count = 0
172 for lineText in (text.."\n"):gmatch("(.-)\n") do
173 line = lineText
174 count = count + 1
175 if count == info.linedefined then
176 break
177 end
178 end
179 if not line then
180 --print("line not found") -- whoops!
181 return "?"
182 end
183 return ParseLine(line)
184 else
185 local line
186 local lineNumber = 0
187 for l in string_gmatch(info.source, "([^\n]+)\n-") do
188 lineNumber = lineNumber + 1
189 if lineNumber == info.linedefined then
190 line = l
191 break
192 end
193 end
194 if not line then
195 -- print("line not found") -- whoops!
196 return "?"
197 end
198 return ParseLine(line)
199 end
200end
201
202---
203-- Dumper instances are used to analyze stacks and collect its information.
204--
205local Dumper = {}
206
207Dumper.new = function(thread)
208 local t = { lines = {} }
209 for k,v in pairs(Dumper) do t[k] = v end
210
211 t.dumping_same_thread = (thread == coroutine.running())
212
213 -- if a thread was supplied, bind it to debug.info and debug.get
214 -- we also need to skip this additional level we are introducing in the callstack (only if we are running
215 -- in the same thread we're inspecting)
216 if type(thread) == "thread" then
217 t.getinfo = function(level, what)
218 if t.dumping_same_thread and type(level) == "number" then
219 level = level + 1
220 end
221 return debug.getinfo(thread, level, what)
222 end
223 t.getlocal = function(level, loc)
224 if t.dumping_same_thread then
225 level = level + 1
226 end
227 return debug.getlocal(thread, level, loc)
228 end
229 else
230 t.getinfo = debug.getinfo
231 t.getlocal = debug.getlocal
232 end
233
234 return t
235end
236
237-- helpers for collecting strings to be used when assembling the final trace
238function Dumper:add (text)
239 self.lines[#self.lines + 1] = text
240end
241function Dumper:add_f (fmt, ...)
242 self:add(fmt:format(...))
243end
244function Dumper:concat_lines ()
245 return table_concat(self.lines)
246end
247
248---
249-- Private:
250-- Iterates over the local variables of a given function.
251--
252-- @param level The stack level where the function is.
253--
254function Dumper:DumpLocals (level)
255 if not _M.dump_locals then return end
256
257 local prefix = "\t "
258 local i = 1
259
260 if self.dumping_same_thread then
261 level = level + 1
262 end
263
264 local name, value = self.getlocal(level, i)
265 if not name then
266 return
267 end
268 self:add("\tLocal variables:\r\n")
269 while name do
270 if type(value) == "number" then
271 self:add_f("%s%s = number: %g\r\n", prefix, name, value)
272 elseif type(value) == "boolean" then
273 self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value))
274 elseif type(value) == "string" then
275 self:add_f("%s%s = string: %q\r\n", prefix, name, value)
276 elseif type(value) == "userdata" then
277 self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value))
278 elseif type(value) == "nil" then
279 self:add_f("%s%s = nil\r\n", prefix, name)
280 elseif type(value) == "table" then
281 if m_known_tables[value] then
282 self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value])
283 elseif m_user_known_tables[value] then
284 self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value])
285 else
286 local txt = "{"
287 for k,v in pairs(value) do
288 txt = txt..safe_tostring(k)..":"..safe_tostring(v)
289 if #txt > _M.max_tb_output_len then
290 txt = txt.." (more...)"
291 break
292 end
293 if next(value, k) then txt = txt..", " end
294 end
295 self:add_f("%s%s = %s %s\r\n", prefix, name, safe_tostring(value), txt.."}")
296 end
297 elseif type(value) == "function" then
298 local info = self.getinfo(value, "nS")
299 local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value]
300 if info.what == "C" then
301 self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value)))
302 else
303 local source = info.short_src
304 if source:sub(2,7) == "string" then
305 source = source:sub(9)
306 end
307 --for k,v in pairs(info) do print(k,v) end
308 fun_name = fun_name or GuessFunctionName(info)
309 self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source)
310 end
311 elseif type(value) == "thread" then
312 self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value))
313 end
314 i = i + 1
315 name, value = self.getlocal(level, i)
316 end
317end
318
319local function getMoonLineNumber(fname, line)
320 local moonCompiled = require("moonp").moon_compiled
321 local source = moonCompiled["@"..fname]
322 local file = io.open(fname)
323 if not source and file then
324 local codes = file:read("*a")
325 file:close()
326 local moonFile = codes:match("^%s*--%s*%[moon%]:%s*([^\n]*)")
327 if moonFile then
328 fname = moonFile:gsub("^%s*(.-)%s*$", "%1")
329 source = codes
330 end
331 end
332 if source then
333 local i, target = 1, tonumber(line)
334 for lineCode in source:gmatch("([^\n]*)\n") do
335 if i == target then
336 local num = lineCode:match("--%s*(%d*)%s*$")
337 if num then
338 return fname, num
339 end
340 break
341 end
342 i = i + 1
343 end
344 end
345 return fname, line
346end
347
348---
349-- Public:
350-- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc.
351-- This function is suitable to be used as an error handler with pcall or xpcall
352--
353-- @param thread An optional thread whose stack is to be inspected (defaul is the current thread)
354-- @param message An optional error string or object.
355-- @param level An optional number telling at which level to start the traceback (default is 1)
356--
357-- Returns a string with the stack trace and a string with the original error.
358--
359function _M.stacktrace(thread, message, level)
360 if type(thread) ~= "thread" then
361 -- shift parameters left
362 thread, message, level = nil, thread, message
363 end
364
365 thread = thread or coroutine.running()
366
367 level = level or 1
368
369 local dumper = Dumper.new(thread)
370
371 local original_error
372
373 if type(message) == "table" then
374 dumper:add("an error object {\r\n")
375 local first = true
376 for k,v in pairs(message) do
377 if first then
378 dumper:add(" ")
379 first = false
380 else
381 dumper:add(",\r\n ")
382 end
383 dumper:add(safe_tostring(k))
384 dumper:add(": ")
385 dumper:add(safe_tostring(v))
386 end
387 dumper:add("\r\n}")
388 original_error = dumper:concat_lines()
389 elseif type(message) == "string" then
390 original_error = message
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 local file = io.open(fext)
404 if file then
405 file:close()
406 fname = fext
407 else
408 fext = fname .. ".moon"
409 file = io.open(fext)
410 if file then
411 file:close()
412 fname = fext
413 end
414 end
415 end
416 fname, line = getMoonLineNumber(fname, line)
417 if _M.simplified then
418 message = table.concat({
419 "'", fname, "':",
420 line, ": ", msg})
421 else
422 message = table.concat({
423 "[string \"", fname, "\"]:",
424 line, ": ", msg})
425 end
426 end
427 dumper:add(message)
428 end
429
430 dumper:add("\r\n")
431 dumper:add[[
432Stack Traceback
433===============
434]]
435 --print(error_message)
436
437 local level_to_show = 1
438 if dumper.dumping_same_thread then level = level + 1 end
439
440 local info = dumper.getinfo(level, "nSlf")
441 while info do
442 if info.source and info.source:sub(1,1) == "@" then
443 info.source = info.source:sub(2)
444 elseif info.what == "main" or info.what == "Lua" then
445 info.source = info.source
446 end
447 local fname = info.source
448 local extension = fname:match("%.([^%.\\/]*)$")
449 if not extension then
450 local fext = fname .. ".lua"
451 local file = io.open(fext)
452 if file then
453 file:close()
454 fname = fext
455 else
456 fext = fname .. ".moon"
457 file = io.open(fext)
458 if file then
459 file:close()
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(), original_error
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";