aboutsummaryrefslogtreecommitdiff
path: root/src/MoonP/stacktraceplus.h
diff options
context:
space:
mode:
authorLi Jin <dragon-fly@qq.com>2021-02-17 11:22:07 +0800
committerLi Jin <dragon-fly@qq.com>2021-02-17 11:22:07 +0800
commit7066392d1c974065181d95d93274136dcd625d43 (patch)
treecf51eafc2c52cbc12246a306bca172d799193d30 /src/MoonP/stacktraceplus.h
parent90cd12ad9ef465f3e435e1bd034dcfbe4e19d016 (diff)
downloadyuescript-7066392d1c974065181d95d93274136dcd625d43.tar.gz
yuescript-7066392d1c974065181d95d93274136dcd625d43.tar.bz2
yuescript-7066392d1c974065181d95d93274136dcd625d43.zip
stop reusing variables, rename project.
Diffstat (limited to 'src/MoonP/stacktraceplus.h')
-rw-r--r--src/MoonP/stacktraceplus.h528
1 files changed, 0 insertions, 528 deletions
diff --git a/src/MoonP/stacktraceplus.h b/src/MoonP/stacktraceplus.h
deleted file mode 100644
index e884a2c..0000000
--- a/src/MoonP/stacktraceplus.h
+++ /dev/null
@@ -1,528 +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 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 local name_path = fname:gsub("%.", moonp.dirsep)
328 local file_exist, file_path
329 for path in package.path:gmatch("[^;]+") do
330 file_path = path:gsub("?", name_path)
331 file_exist = moonp.file_exist(file_path)
332 if file_exist then
333 break
334 end
335 end
336 if file_exist then
337 local codes = moonp.read_file(file_path)
338 local moonFile = codes:match("^%s*--%s*%[moonp%]:%s*([^\n]*)")
339 if moonFile then
340 fname = moonFile:gsub("^%s*(.-)%s*$", "%1")
341 source = codes
342 end
343 end
344 end
345 if source then
346 local current, target = 1, tonumber(line)
347 local findLine = line
348 for lineCode in source:gmatch("([^\n]*)\n") do
349 local num = lineCode:match("--%s*(%d+)%s*$")
350 if num then
351 findLine = num
352 end
353 if current == target then
354 return fname, findLine or line
355 end
356 current = current + 1
357 end
358 end
359 return fname, line
360end
361
362---
363-- Public:
364-- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc.
365-- This function is suitable to be used as an error handler with pcall or xpcall
366--
367-- @param thread An optional thread whose stack is to be inspected (defaul is the current thread)
368-- @param message An optional error string or object.
369-- @param level An optional number telling at which level to start the traceback (default is 1)
370--
371-- Returns a string with the stack trace.
372--
373function _M.stacktrace(thread, message, level)
374 if type(thread) ~= "thread" then
375 -- shift parameters left
376 thread, message, level = nil, thread, message
377 end
378
379 thread = thread or coroutine.running()
380
381 level = level or 1
382
383 local dumper = Dumper.new(thread)
384
385 if type(message) == "table" then
386 dumper:add("an error object {\r\n")
387 local first = true
388 for k,v in pairs(message) do
389 if first then
390 dumper:add(" ")
391 first = false
392 else
393 dumper:add(",\r\n ")
394 end
395 dumper:add(safe_tostring(k))
396 dumper:add(": ")
397 dumper:add(safe_tostring(v))
398 end
399 dumper:add("\r\n}")
400 elseif type(message) == "string" then
401 local fname, line, msg = message:match('(.+):(%d+): (.*)$')
402 if fname then
403 local nfname, nline, nmsg = fname:match('(.+):(%d+): (.*)$')
404 if nfname then
405 fname = nmsg
406 end
407 end
408 if fname then
409 local fn = fname:match("%[string \"(.-)\"%]")
410 if fn then fname = fn end
411 fname = fname:gsub("^%s*(.-)%s*$", "%1")
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 dumper:add("\r\n")
429 dumper:add[[
430Stack Traceback
431===============
432]]
433
434 local level_to_show = 1
435 if dumper.dumping_same_thread then level = level + 1 end
436
437 local info = dumper.getinfo(level, "nSlf")
438 while info do
439 if info.source and info.source:sub(1,1) == "@" then
440 info.source = info.source:sub(2)
441 elseif info.what == "main" or info.what == "Lua" then
442 info.source = info.source
443 end
444 info.source, info.currentline = getMoonLineNumber(info.source, info.currentline)
445 if info.what == "main" then
446 if _M.simplified then
447 dumper:add_f("(%d) '%s':%d\r\n", level_to_show, info.source, info.currentline)
448 else
449 dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, info.source, info.currentline)
450 end
451 elseif info.what == "C" then
452 --print(info.namewhat, info.name)
453 --for k,v in pairs(info) do print(k,v, type(v)) end
454 local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func)
455 dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name)
456 --dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value)))
457 elseif info.what == "tail" then
458 --print("tail")
459 --for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name)
460 dumper:add_f("(%d) tail call\r\n", level_to_show)
461 dumper:DumpLocals(level)
462 elseif info.what == "Lua" then
463 local source = info.source
464 local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name
465 if source:sub(2, 7) == "string" then
466 source = source:sub(10,-3)
467 end
468 local was_guessed = false
469 if not function_name or function_name == "?" then
470 --for k,v in pairs(info) do print(k,v, type(v)) end
471 function_name = GuessFunctionName(info)
472 was_guessed = true
473 end
474 -- test if we have a file name
475 local function_type = (info.namewhat == "") and "function" or info.namewhat
476 if info.source and info.source:sub(1, 1) == "@" then
477 if _M.simplified then
478 dumper:add_f("(%d) '%s':%d%s\r\n", level_to_show, info.source:sub(2), info.currentline, was_guessed and " (guess)" or "")
479 else
480 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 "")
481 end
482 elseif info.source and info.source:sub(1,1) == '#' then
483 if _M.simplified then
484 dumper:add_f("(%d) '%s':%d%s\r\n", level_to_show, info.source:sub(2), info.currentline, was_guessed and " (guess)" or "")
485 else
486 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 "")
487 end
488 else
489 if _M.simplified then
490 dumper:add_f("(%d) '%s':%d\r\n", level_to_show, source, info.currentline)
491 else
492 dumper:add_f("(%d) Lua %s '%s' at chunk '%s':%d\r\n", level_to_show, function_type, function_name, source, info.currentline)
493 end
494 end
495 dumper:DumpLocals(level)
496 else
497 dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what)
498 end
499
500 level = level + 1
501 level_to_show = level_to_show + 1
502 info = dumper.getinfo(level, "nSlf")
503 end
504
505 return dumper:concat_lines()
506end
507
508--
509-- Adds a table to the list of known tables
510function _M.add_known_table(tab, description)
511 if m_known_tables[tab] then
512 error("Cannot override an already known table")
513 end
514 m_user_known_tables[tab] = description
515end
516
517--
518-- Adds a function to the list of known functions
519function _M.add_known_function(fun, description)
520 if m_known_functions[fun] then
521 error("Cannot override an already known function")
522 end
523 m_user_known_functions[fun] = description
524end
525
526return _M
527
528)lua_codes";