forked from ThomasMonroe314/ugxrealms
add stacktraceplus mod
parent
7aae775b5e
commit
3b696a9b1e
|
@ -3,3 +3,5 @@
|
|||
*.sqlite
|
||||
/*.txt
|
||||
*.cfgfile
|
||||
irc_tokens
|
||||
*.db
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# StackTracePlus Minetest mod
|
||||
|
||||
StackTracePlus is a Lua module that extends `debug.traceback` to include more
|
||||
call frame information such as local and globals, locals and parameters
|
||||
dumps.
|
||||
|
||||
This mod merely plugs this module in and replaces `debug.traceback` with
|
||||
StackTracePlus's traceback dump routine.
|
||||
|
||||
# Installing
|
||||
|
||||
This repository uses git submodules.
|
||||
|
||||
Clone this repo either recursively (`--recursive`) or as usual, but make sure
|
||||
to `git submodule update --init` afterwards; it must go into the `mods`
|
||||
directory.
|
||||
|
||||
## Mod security
|
||||
|
||||
This mod modifies the global `debug` table which is not accessible by normal
|
||||
mods when mod security is enabled.
|
||||
|
||||
If you have enabled mod security (`secure.enable_security` is `true`), then you
|
||||
must whitelist this mod in `minetest.conf`'s `secure.trusted_mods` config
|
||||
entry.
|
||||
|
||||
# License
|
||||
|
||||
WTFPL / CC0 / Public Domain.
|
||||
|
||||
StackTracePlus is distributed under the MIT license.
|
|
@ -0,0 +1,29 @@
|
|||
language: python # Need python environment for pip
|
||||
|
||||
sudo: false # Use container-based infrastructure
|
||||
|
||||
env:
|
||||
- LUA="lua=5.1"
|
||||
- LUA="lua=5.2"
|
||||
- LUA="lua=5.3"
|
||||
- LUA="luajit=2.0"
|
||||
- LUA="luajit=2.1"
|
||||
|
||||
before_install:
|
||||
- pip install hererocks
|
||||
- hererocks lua_install --luarocks ^ --$LUA --compat none
|
||||
- export PATH=$PATH:$PWD/lua_install/bin
|
||||
- luarocks install luacheck
|
||||
- luarocks install luacov
|
||||
- luarocks install luacov-coveralls --server=https://luarocks.org/dev
|
||||
|
||||
install:
|
||||
- luarocks install lunitx
|
||||
|
||||
script:
|
||||
- luacheck rockspecs/stacktraceplus-git-1.rockspec
|
||||
- cd unittest
|
||||
- lua -lluacov run.lua
|
||||
|
||||
after_success:
|
||||
- luacov-coveralls -v -r .. -e ../lua_install
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2010 Ignacio Burgueño
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,130 @@
|
|||
# StackTracePlus #
|
||||
|
||||
[![Build Status](https://travis-ci.org/ignacio/StackTracePlus.png?branch=master)](https://travis-ci.org/ignacio/StackTracePlus)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/ignacio/StackTracePlus/badge.svg?branch=master)](https://coveralls.io/github/ignacio/StackTracePlus?branch=master)
|
||||
|
||||
StackTracePlus provides enhanced stack traces for [Lua 5.1, Lua 5.2, Lua 5.3][1], [LuaJIT][2] and [OpenResty][4].
|
||||
|
||||
StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess
|
||||
function names when they're not available, etc, so, instead of
|
||||
|
||||
lua5.1.exe: D:\trunk_git\sources\stacktraceplus\test\test.lua:10: attempt to concatenate a nil value
|
||||
stack traceback:
|
||||
D:\trunk_git\sources\stacktraceplus\test\test.lua:10: in function <D:\trunk_git\sources\stacktraceplus\test\test.lua:7>
|
||||
(tail call): ?
|
||||
D:\trunk_git\sources\stacktraceplus\test\test.lua:15: in main chunk
|
||||
[C]: ?
|
||||
|
||||
you'll get
|
||||
|
||||
lua5.1.exe: D:\trunk_git\sources\stacktraceplus\test\test.lua:10: attempt to concatenate a nil value
|
||||
Stack Traceback
|
||||
===============
|
||||
(2) C function 'function: 00A8F418'
|
||||
(3) Lua function 'g' at file 'D:\trunk_git\sources\stacktraceplus\test\test.lua:10' (best guess)
|
||||
Local variables:
|
||||
fun = table module
|
||||
str = string: "hey"
|
||||
tb = table: 027DCBE0 {dummy:1, blah:true, foo:bar}
|
||||
(*temporary) = nil
|
||||
(*temporary) = string: "text"
|
||||
(*temporary) = string: "attempt to concatenate a nil value"
|
||||
(4) tail call
|
||||
(5) main chunk of file 'D:\trunk_git\sources\stacktraceplus\test\test.lua' at line 15
|
||||
(6) C function 'function: 002CA480'
|
||||
|
||||
## Usage #
|
||||
|
||||
StackTracePlus can be used as a replacement for `debug.traceback`, as an `xpcall` error handler or even from C code. Note that
|
||||
only the Lua 5.1 interpreter and OpenResty allows the traceback function to be replaced "on the fly". Interpreters for LuaJIT, Lua 5.2 and 5.3 always calls luaL_traceback internally so there is no easy way to override that.
|
||||
|
||||
```lua
|
||||
local STP = require "StackTracePlus"
|
||||
|
||||
debug.traceback = STP.stacktrace
|
||||
function test()
|
||||
local s = "this is a string"
|
||||
local n = 42
|
||||
local t = { foo = "bar" }
|
||||
local co = coroutine
|
||||
local cr = coroutine.create
|
||||
|
||||
error("an error")
|
||||
end
|
||||
test()
|
||||
```
|
||||
|
||||
That script will output (only with Lua 5.1):
|
||||
|
||||
lua5.1: example.lua:11: an error
|
||||
Stack Traceback
|
||||
===============
|
||||
(2) C function 'function: 006B5758'
|
||||
(3) global C function 'error'
|
||||
(4) Lua global 'test' at file 'example.lua:11'
|
||||
Local variables:
|
||||
s = string: "this is a string"
|
||||
n = number: 42
|
||||
t = table: 006E5220 {foo:bar}
|
||||
co = coroutine table
|
||||
cr = C function: 003C7080
|
||||
(5) main chunk of file 'example.lua' at line 14
|
||||
(6) C function 'function: 00637B30'
|
||||
|
||||
**StackTracePlus** is aware of the usual Lua libraries, like *coroutine*, *table*, *string*, *io*, etc and functions like
|
||||
*print*, *pcall*, *assert*, and so on.
|
||||
|
||||
You can also make STP aware of your own tables and functions by calling *add_known_function* and *add_known_table*.
|
||||
|
||||
```lua
|
||||
local STP = require "StackTracePlus"
|
||||
|
||||
debug.traceback = STP.stacktrace
|
||||
local my_table = {
|
||||
f = function() end
|
||||
}
|
||||
function my_function()
|
||||
end
|
||||
|
||||
function test(data, func)
|
||||
local s = "this is a string"
|
||||
|
||||
error("an error")
|
||||
end
|
||||
|
||||
STP.add_known_table(my_table, "A description for my_table")
|
||||
STP.add_known_function(my_function, "A description for my_function")
|
||||
|
||||
test( my_table, my_function )
|
||||
```
|
||||
|
||||
Will output:
|
||||
|
||||
lua5.1: ..\test\example2.lua:13: an error
|
||||
Stack Traceback
|
||||
===============
|
||||
(2) C function 'function: 0073AAA8'
|
||||
(3) global C function 'error'
|
||||
(4) Lua global 'test' at file '..\test\example2.lua:13'
|
||||
Local variables:
|
||||
data = A description for my_table
|
||||
func = Lua function 'A description for my_function' (defined at line 7 of chunk ..\test\example2.lua)
|
||||
s = string: "this is a string"
|
||||
(5) main chunk of file '..\test\example2.lua' at line 19
|
||||
(6) C function 'function: 00317B30'
|
||||
|
||||
|
||||
## Installation #
|
||||
The easiest way to install is with [LuaRocks][3].
|
||||
|
||||
- luarocks install stacktraceplus
|
||||
|
||||
If you don't want to use LuaRocks, just copy StackTracePlus.lua to Lua's path.
|
||||
|
||||
## License #
|
||||
**StackTracePlus** is available under the MIT license.
|
||||
|
||||
[1]: http://www.lua.org/
|
||||
[2]: http://luajit.org/
|
||||
[3]: https://luarocks.org/
|
||||
[4]: https://openresty.org/
|
|
@ -0,0 +1,24 @@
|
|||
package = "StackTracePlus"
|
||||
version = "0.1.0-1"
|
||||
source = {
|
||||
url = "https://github.com/ignacio/StackTracePlus/archive/0.1.0-1.tar.gz",
|
||||
dir = "StackTracePlus-0.1.0-1"
|
||||
}
|
||||
description = {
|
||||
summary = "StackTracePlus provides enhanced stack traces for Lua",
|
||||
detailed = [[
|
||||
StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess
|
||||
function names when they're not available, etc.
|
||||
]],
|
||||
license = "MIT/X11",
|
||||
homepage = "http://github.com/ignacio/StackTracePlus"
|
||||
}
|
||||
|
||||
dependencies = { "lua >= 5.1" }
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
StackTracePlus = "src/StackTracePlus.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package = "StackTracePlus"
|
||||
version = "0.1.1-1"
|
||||
source = {
|
||||
url = "https://github.com/ignacio/StackTracePlus/archive/0.1.1-1.tar.gz",
|
||||
dir = "StackTracePlus-0.1.1-1"
|
||||
}
|
||||
description = {
|
||||
summary = "StackTracePlus provides enhanced stack traces for Lua",
|
||||
detailed = [[
|
||||
StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess
|
||||
function names when they're not available, etc.
|
||||
]],
|
||||
license = "MIT/X11",
|
||||
homepage = "http://github.com/ignacio/StackTracePlus"
|
||||
}
|
||||
|
||||
dependencies = { "lua >= 5.1, < 5.3" }
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
StackTracePlus = "src/StackTracePlus.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package = "StackTracePlus"
|
||||
version = "0.1.2-1"
|
||||
source = {
|
||||
url = "https://github.com/ignacio/StackTracePlus/archive/0.1.2-1.tar.gz",
|
||||
dir = "StackTracePlus-0.1.2-1"
|
||||
}
|
||||
description = {
|
||||
summary = "StackTracePlus provides enhanced stack traces for Lua",
|
||||
detailed = [[
|
||||
StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess
|
||||
function names when they're not available, etc.
|
||||
]],
|
||||
license = "MIT/X11",
|
||||
homepage = "http://github.com/ignacio/StackTracePlus"
|
||||
}
|
||||
|
||||
dependencies = { "lua >= 5.1, < 5.4" }
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
StackTracePlus = "src/StackTracePlus.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package = "StackTracePlus"
|
||||
version = "git-1"
|
||||
source = {
|
||||
url = "git://github.com/ignacio/StackTracePlus.git",
|
||||
branch = "master"
|
||||
}
|
||||
description = {
|
||||
summary = "StackTracePlus provides enhanced stack traces for Lua",
|
||||
detailed = [[
|
||||
StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess
|
||||
function names when they're not available, etc.
|
||||
]],
|
||||
license = "MIT/X11",
|
||||
homepage = "http://github.com/ignacio/StackTracePlus"
|
||||
}
|
||||
|
||||
dependencies = { "lua >= 5.1" }
|
||||
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
StackTracePlus = "src/StackTracePlus.lua"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,415 @@
|
|||
-- tables
|
||||
local _G = _G
|
||||
local string, io, debug, coroutine = string, io, debug, coroutine
|
||||
|
||||
-- functions
|
||||
local tostring, print, require = tostring, print, require
|
||||
local next, assert = next, assert
|
||||
local pcall, type, pairs, ipairs = pcall, type, pairs, ipairs
|
||||
local error = error
|
||||
|
||||
assert(debug, "debug table must be available at this point")
|
||||
|
||||
local io_open = io.open
|
||||
local string_gmatch = string.gmatch
|
||||
local string_sub = string.sub
|
||||
local table_concat = table.concat
|
||||
|
||||
local _M = {
|
||||
max_tb_output_len = 70 -- controls the maximum length of the 'stringified' table before cutting with ' (more...)'
|
||||
}
|
||||
|
||||
-- this tables should be weak so the elements in them won't become uncollectable
|
||||
local m_known_tables = { [_G] = "_G (global table)" }
|
||||
local function add_known_module(name, desc)
|
||||
local ok, mod = pcall(require, name)
|
||||
if ok then
|
||||
m_known_tables[mod] = desc
|
||||
end
|
||||
end
|
||||
|
||||
add_known_module("string", "string module")
|
||||
add_known_module("io", "io module")
|
||||
add_known_module("os", "os module")
|
||||
add_known_module("table", "table module")
|
||||
add_known_module("math", "math module")
|
||||
add_known_module("package", "package module")
|
||||
add_known_module("debug", "debug module")
|
||||
add_known_module("coroutine", "coroutine module")
|
||||
|
||||
-- lua5.2
|
||||
add_known_module("bit32", "bit32 module")
|
||||
-- luajit
|
||||
add_known_module("bit", "bit module")
|
||||
add_known_module("jit", "jit module")
|
||||
-- lua5.3
|
||||
if _VERSION >= "Lua 5.3" then
|
||||
add_known_module("utf8", "utf8 module")
|
||||
end
|
||||
|
||||
|
||||
local m_user_known_tables = {}
|
||||
|
||||
local m_known_functions = {}
|
||||
for _, name in ipairs{
|
||||
-- Lua 5.2, 5.1
|
||||
"assert",
|
||||
"collectgarbage",
|
||||
"dofile",
|
||||
"error",
|
||||
"getmetatable",
|
||||
"ipairs",
|
||||
"load",
|
||||
"loadfile",
|
||||
"next",
|
||||
"pairs",
|
||||
"pcall",
|
||||
"print",
|
||||
"rawequal",
|
||||
"rawget",
|
||||
"rawlen",
|
||||
"rawset",
|
||||
"require",
|
||||
"select",
|
||||
"setmetatable",
|
||||
"tonumber",
|
||||
"tostring",
|
||||
"type",
|
||||
"xpcall",
|
||||
|
||||
-- Lua 5.1
|
||||
"gcinfo",
|
||||
"getfenv",
|
||||
"loadstring",
|
||||
"module",
|
||||
"newproxy",
|
||||
"setfenv",
|
||||
"unpack",
|
||||
-- TODO: add table.* etc functions
|
||||
} do
|
||||
if _G[name] then
|
||||
m_known_functions[_G[name]] = name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
local m_user_known_functions = {}
|
||||
|
||||
local function safe_tostring (value)
|
||||
local ok, err = pcall(tostring, value)
|
||||
if ok then return err else return ("<failed to get printable value>: '%s'"):format(err) end
|
||||
end
|
||||
|
||||
-- Private:
|
||||
-- Parses a line, looking for possible function definitions (in a very naïve way)
|
||||
-- Returns '(anonymous)' if no function name was found in the line
|
||||
local function ParseLine(line)
|
||||
assert(type(line) == "string")
|
||||
--print(line)
|
||||
local match = line:match("^%s*function%s+(%w+)")
|
||||
if match then
|
||||
--print("+++++++++++++function", match)
|
||||
return match
|
||||
end
|
||||
match = line:match("^%s*local%s+function%s+(%w+)")
|
||||
if match then
|
||||
--print("++++++++++++local", match)
|
||||
return match
|
||||
end
|
||||
match = line:match("^%s*local%s+(%w+)%s+=%s+function")
|
||||
if match then
|
||||
--print("++++++++++++local func", match)
|
||||
return match
|
||||
end
|
||||
match = line:match("%s*function%s*%(") -- this is an anonymous function
|
||||
if match then
|
||||
--print("+++++++++++++function2", match)
|
||||
return "(anonymous)"
|
||||
end
|
||||
return "(anonymous)"
|
||||
end
|
||||
|
||||
-- Private:
|
||||
-- Tries to guess a function's name when the debug info structure does not have it.
|
||||
-- It parses either the file or the string where the function is defined.
|
||||
-- Returns '?' if the line where the function is defined is not found
|
||||
local function GuessFunctionName(info)
|
||||
--print("guessing function name")
|
||||
if type(info.source) == "string" and info.source:sub(1,1) == "@" then
|
||||
local file, err = io_open(info.source:sub(2), "r")
|
||||
if not file then
|
||||
print("file not found: "..tostring(err)) -- whoops!
|
||||
return "?"
|
||||
end
|
||||
local line
|
||||
for _ = 1, info.linedefined do
|
||||
line = file:read("*l")
|
||||
end
|
||||
if not line then
|
||||
print("line not found") -- whoops!
|
||||
return "?"
|
||||
end
|
||||
return ParseLine(line)
|
||||
else
|
||||
local line
|
||||
local lineNumber = 0
|
||||
for l in string_gmatch(info.source, "([^\n]+)\n-") do
|
||||
lineNumber = lineNumber + 1
|
||||
if lineNumber == info.linedefined then
|
||||
line = l
|
||||
break
|
||||
end
|
||||
end
|
||||
if not line then
|
||||
print("line not found") -- whoops!
|
||||
return "?"
|
||||
end
|
||||
return ParseLine(line)
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
-- Dumper instances are used to analyze stacks and collect its information.
|
||||
--
|
||||
local Dumper = {}
|
||||
|
||||
Dumper.new = function(thread)
|
||||
local t = { lines = {} }
|
||||
for k,v in pairs(Dumper) do t[k] = v end
|
||||
|
||||
t.dumping_same_thread = (thread == coroutine.running())
|
||||
|
||||
-- if a thread was supplied, bind it to debug.info and debug.get
|
||||
-- we also need to skip this additional level we are introducing in the callstack (only if we are running
|
||||
-- in the same thread we're inspecting)
|
||||
if type(thread) == "thread" then
|
||||
t.getinfo = function(level, what)
|
||||
if t.dumping_same_thread and type(level) == "number" then
|
||||
level = level + 1
|
||||
end
|
||||
return debug.getinfo(thread, level, what)
|
||||
end
|
||||
t.getlocal = function(level, loc)
|
||||
if t.dumping_same_thread then
|
||||
level = level + 1
|
||||
end
|
||||
return debug.getlocal(thread, level, loc)
|
||||
end
|
||||
else
|
||||
t.getinfo = debug.getinfo
|
||||
t.getlocal = debug.getlocal
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
-- helpers for collecting strings to be used when assembling the final trace
|
||||
function Dumper:add (text)
|
||||
self.lines[#self.lines + 1] = text
|
||||
end
|
||||
function Dumper:add_f (fmt, ...)
|
||||
self:add(fmt:format(...))
|
||||
end
|
||||
function Dumper:concat_lines ()
|
||||
return table_concat(self.lines)
|
||||
end
|
||||
|
||||
---
|
||||
-- Private:
|
||||
-- Iterates over the local variables of a given function.
|
||||
--
|
||||
-- @param level The stack level where the function is.
|
||||
--
|
||||
function Dumper:DumpLocals (level)
|
||||
local prefix = "\t "
|
||||
local i = 1
|
||||
|
||||
if self.dumping_same_thread then
|
||||
level = level + 1
|
||||
end
|
||||
|
||||
local name, value = self.getlocal(level, i)
|
||||
if not name then
|
||||
return
|
||||
end
|
||||
self:add("\tLocal variables:\r\n")
|
||||
while name do
|
||||
if type(value) == "number" then
|
||||
self:add_f("%s%s = number: %g\r\n", prefix, name, value)
|
||||
elseif type(value) == "boolean" then
|
||||
self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value))
|
||||
elseif type(value) == "string" then
|
||||
self:add_f("%s%s = string: %q\r\n", prefix, name, value)
|
||||
elseif type(value) == "userdata" then
|
||||
self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value))
|
||||
elseif type(value) == "nil" then
|
||||
self:add_f("%s%s = nil\r\n", prefix, name)
|
||||
elseif type(value) == "table" then
|
||||
if m_known_tables[value] then
|
||||
self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value])
|
||||
elseif m_user_known_tables[value] then
|
||||
self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value])
|
||||
else
|
||||
local txt = "{"
|
||||
for k,v in pairs(value) do
|
||||
txt = txt..safe_tostring(k)..":"..safe_tostring(v)
|
||||
if #txt > _M.max_tb_output_len then
|
||||
txt = txt.." (more...)"
|
||||
break
|
||||
end
|
||||
if next(value, k) then txt = txt..", " end
|
||||
end
|
||||
self:add_f("%s%s = %s %s\r\n", prefix, name, safe_tostring(value), txt.."}")
|
||||
end
|
||||
elseif type(value) == "function" then
|
||||
local info = self.getinfo(value, "nS")
|
||||
local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value]
|
||||
if info.what == "C" then
|
||||
self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value)))
|
||||
else
|
||||
local source = info.short_src
|
||||
if source:sub(2,7) == "string" then
|
||||
source = source:sub(9) -- uno más, por el espacio que viene (string "Baragent.Main", por ejemplo)
|
||||
end
|
||||
--for k,v in pairs(info) do print(k,v) end
|
||||
fun_name = fun_name or GuessFunctionName(info)
|
||||
self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source)
|
||||
end
|
||||
elseif type(value) == "thread" then
|
||||
self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value))
|
||||
end
|
||||
i = i + 1
|
||||
name, value = self.getlocal(level, i)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- Public:
|
||||
-- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc.
|
||||
-- This function is suitable to be used as an error handler with pcall or xpcall
|
||||
--
|
||||
-- @param thread An optional thread whose stack is to be inspected (defaul is the current thread)
|
||||
-- @param message An optional error string or object.
|
||||
-- @param level An optional number telling at which level to start the traceback (default is 1)
|
||||
--
|
||||
-- Returns a string with the stack trace and a string with the original error.
|
||||
--
|
||||
function _M.stacktrace(thread, message, level)
|
||||
if type(thread) ~= "thread" then
|
||||
-- shift parameters left
|
||||
thread, message, level = nil, thread, message
|
||||
end
|
||||
|
||||
thread = thread or coroutine.running()
|
||||
|
||||
level = level or 1
|
||||
|
||||
local dumper = Dumper.new(thread)
|
||||
|
||||
local original_error
|
||||
|
||||
if type(message) == "table" then
|
||||
dumper:add("an error object {\r\n")
|
||||
local first = true
|
||||
for k,v in pairs(message) do
|
||||
if first then
|
||||
dumper:add(" ")
|
||||
first = false
|
||||
else
|
||||
dumper:add(",\r\n ")
|
||||
end
|
||||
dumper:add(safe_tostring(k))
|
||||
dumper:add(": ")
|
||||
dumper:add(safe_tostring(v))
|
||||
end
|
||||
dumper:add("\r\n}")
|
||||
original_error = dumper:concat_lines()
|
||||
elseif type(message) == "string" then
|
||||
dumper:add(message)
|
||||
original_error = message
|
||||
end
|
||||
|
||||
dumper:add("\r\n")
|
||||
dumper:add[[
|
||||
Stack Traceback
|
||||
===============
|
||||
]]
|
||||
--print(error_message)
|
||||
|
||||
local level_to_show = level
|
||||
if dumper.dumping_same_thread then level = level + 1 end
|
||||
|
||||
local info = dumper.getinfo(level, "nSlf")
|
||||
while info do
|
||||
if info.what == "main" then
|
||||
if string_sub(info.source, 1, 1) == "@" then
|
||||
dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, string_sub(info.source, 2), info.currentline)
|
||||
else
|
||||
dumper:add_f("(%d) main chunk of %s at line %d\r\n", level_to_show, info.short_src, info.currentline)
|
||||
end
|
||||
elseif info.what == "C" then
|
||||
--print(info.namewhat, info.name)
|
||||
--for k,v in pairs(info) do print(k,v, type(v)) end
|
||||
local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func)
|
||||
dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name)
|
||||
--dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value)))
|
||||
elseif info.what == "tail" then
|
||||
--print("tail")
|
||||
--for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name)
|
||||
dumper:add_f("(%d) tail call\r\n", level_to_show)
|
||||
dumper:DumpLocals(level)
|
||||
elseif info.what == "Lua" then
|
||||
local source = info.short_src
|
||||
local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name
|
||||
if source:sub(2, 7) == "string" then
|
||||
source = source:sub(9)
|
||||
end
|
||||
local was_guessed = false
|
||||
if not function_name or function_name == "?" then
|
||||
--for k,v in pairs(info) do print(k,v, type(v)) end
|
||||
function_name = GuessFunctionName(info)
|
||||
was_guessed = true
|
||||
end
|
||||
-- test if we have a file name
|
||||
local function_type = (info.namewhat == "") and "function" or info.namewhat
|
||||
if info.source and info.source:sub(1, 1) == "@" then
|
||||
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 "")
|
||||
elseif info.source and info.source:sub(1,1) == '#' then
|
||||
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 "")
|
||||
else
|
||||
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)
|
||||
end
|
||||
dumper:DumpLocals(level)
|
||||
else
|
||||
dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what)
|
||||
end
|
||||
|
||||
level = level + 1
|
||||
level_to_show = level_to_show + 1
|
||||
info = dumper.getinfo(level, "nSlf")
|
||||
end
|
||||
|
||||
return dumper:concat_lines(), original_error
|
||||
end
|
||||
|
||||
--
|
||||
-- Adds a table to the list of known tables
|
||||
function _M.add_known_table(tab, description)
|
||||
if m_known_tables[tab] then
|
||||
error("Cannot override an already known table")
|
||||
end
|
||||
m_user_known_tables[tab] = description
|
||||
end
|
||||
|
||||
--
|
||||
-- Adds a function to the list of known functions
|
||||
function _M.add_known_function(fun, description)
|
||||
if m_known_functions[fun] then
|
||||
error("Cannot override an already known function")
|
||||
end
|
||||
m_user_known_functions[fun] = description
|
||||
end
|
||||
|
||||
return _M
|
|
@ -0,0 +1,15 @@
|
|||
require "luarocks.require"
|
||||
local STP = require "StackTracePlus"
|
||||
|
||||
debug.traceback = STP.stacktrace
|
||||
|
||||
function f(str, tb, ...)
|
||||
local g = function(fun)
|
||||
local str = str
|
||||
local tb = tb
|
||||
local a = nil .. "text"
|
||||
end
|
||||
return g(table)
|
||||
end
|
||||
|
||||
f("hey", {foo="bar", dummy=1, blah=true}, 1)
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
local STP = require("StackTracePlus")
|
||||
|
||||
local Page = {}
|
||||
|
||||
local function bla()
|
||||
error("oops")
|
||||
end
|
||||
|
||||
local function foo()
|
||||
bla()
|
||||
end
|
||||
|
||||
Page.render = foo
|
||||
|
||||
function main()
|
||||
local p = {}
|
||||
setmetatable(p, { __index = Page })
|
||||
local x = "render"
|
||||
p[x]()
|
||||
end
|
||||
|
||||
xpcall(main, function() print(STP.stacktrace()) end)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
local lunit = require "lunitx"
|
||||
|
||||
package.path = "../src/?.lua;../src/?/init.lua;".. package.path
|
||||
|
||||
require "test"
|
||||
|
||||
local stats = lunit.main()
|
||||
if stats.errors > 0 or stats.failed > 0 then
|
||||
os.exit(1)
|
||||
end
|
|
@ -0,0 +1,84 @@
|
|||
local STP = require "StackTracePlus"
|
||||
local lunit = require "lunitx"
|
||||
|
||||
if _VERSION >= "Lua 5.2" then
|
||||
_ENV = lunit.module("simple","seeall")
|
||||
else
|
||||
module( ..., package.seeall, lunit.testcase )
|
||||
end
|
||||
|
||||
function testLuaModule()
|
||||
local f = function()
|
||||
local t = table
|
||||
error("an error")
|
||||
end
|
||||
|
||||
local ok, err = xpcall(f, STP.stacktrace)
|
||||
assert_match( [[t = table module]], err, "" )
|
||||
end
|
||||
|
||||
function testKnownFunction()
|
||||
local my_function = function()
|
||||
end
|
||||
local f = function()
|
||||
error("an error")
|
||||
end
|
||||
STP.add_known_function(my_function, "this is my function")
|
||||
|
||||
local ok, err = xpcall(f, STP.stacktrace)
|
||||
assert_match( [['this is my function']], err, "" )
|
||||
end
|
||||
|
||||
function testKnownTable()
|
||||
local my_table = {}
|
||||
local f = function()
|
||||
error("an error")
|
||||
end
|
||||
STP.add_known_table(my_table, "this is my table")
|
||||
|
||||
local ok, err = xpcall(f, STP.stacktrace)
|
||||
assert_match( [[ = this is my table]], err, "" )
|
||||
end
|
||||
|
||||
function testCoroutine()
|
||||
local var_outside_co = {}
|
||||
|
||||
local function helper()
|
||||
local arg_helper = "hi there!"
|
||||
error("an error")
|
||||
end
|
||||
|
||||
local co = coroutine.create(function()
|
||||
local arg_inside_co = "arg1"
|
||||
helper()
|
||||
end)
|
||||
|
||||
local status, err_msg = coroutine.resume(co)
|
||||
assert_false(status)
|
||||
|
||||
local trace = STP.stacktrace(co)
|
||||
assert_match("arg_inside_co", trace)
|
||||
assert_match("arg_helper", trace)
|
||||
end
|
||||
|
||||
---
|
||||
-- Test case for issue #4
|
||||
-- https://github.com/ignacio/StackTracePlus/issues/4
|
||||
--
|
||||
-- When building the stack trace, if there is a table or userdata with a __tostring metamethod which may throw an
|
||||
-- error, we fail with 'error in error handling'.
|
||||
--
|
||||
function test_error_in_tostring()
|
||||
local t = setmetatable({}, {
|
||||
__tostring = function()
|
||||
error("Error in tostring")
|
||||
end
|
||||
})
|
||||
|
||||
local f = function()
|
||||
error("an error")
|
||||
end
|
||||
|
||||
local ok, err = xpcall(f, STP.stacktrace)
|
||||
assert_not_equal("error in error handling", err)
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
local secenv = _G
|
||||
local modname = minetest.get_current_modname()
|
||||
local sec = (minetest.setting_get('secure.enable_security') == 'true')
|
||||
local ie
|
||||
|
||||
-- //MFF (Mg|01/07/2016 for classic#508, hg#104, sb#166, creative#68)
|
||||
if (rawget(_G, "rawlen") == nil) then
|
||||
rawlen = {} -- That's extremely bad, but our only mean of stopping StackTracePlus from complaining about rawlen being undefined
|
||||
end
|
||||
|
||||
if sec then
|
||||
ie = minetest.request_insecure_environment()
|
||||
if ie == nil then
|
||||
error("Mod security is on but " .. modname .. " is not whitelisted as a secure mod")
|
||||
end
|
||||
end
|
||||
|
||||
if sec then
|
||||
string, io, debug, coroutine = ie.string, ie.io, ie.debug, ie.coroutine
|
||||
end
|
||||
|
||||
local STP = dofile(minetest.get_modpath(modname) .. '/StackTracePlus/src/StackTracePlus.lua')
|
||||
-- Remove string/table dump length limits
|
||||
STP.max_tb_output_len = math.huge
|
||||
|
||||
if sec then
|
||||
ie.debug.traceback = STP.stacktrace
|
||||
-- StackTracePlus caches the following modules, reset them to the original ones to
|
||||
-- avoid leaking them
|
||||
string, io, debug, coroutine = secenv.string, secenv.io, secenv.debug, secenv.coroutine
|
||||
ie = nil
|
||||
end
|
||||
debug.traceback = STP.stacktrace
|
||||
|
||||
minetest.log('action', "[" .. modname .. "] replaced debug.traceback")
|
Loading…
Reference in New Issue