├── src └── StackTracePlus.lua ├── unittest ├── run.lua └── test.lua ├── test ├── test.lua └── test_questionmark.lua ├── rockspecs ├── stacktraceplus-git-1.rockspec ├── stacktraceplus-0.1.0-1.rockspec ├── stacktraceplus-0.1.1-1.rockspec └── stacktraceplus-0.1.2-1.rockspec ├── .travis.yml ├── LICENSE └── README.md /src/StackTracePlus.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ignacio/StackTracePlus/HEAD/src/StackTracePlus.lua -------------------------------------------------------------------------------- /unittest/run.lua: -------------------------------------------------------------------------------- 1 | local lunit = require "lunitx" 2 | 3 | package.path = "../src/?.lua;../src/?/init.lua;".. package.path 4 | 5 | require "test" 6 | 7 | local stats = lunit.main() 8 | if stats.errors > 0 or stats.failed > 0 then 9 | os.exit(1) 10 | end 11 | -------------------------------------------------------------------------------- /test/test.lua: -------------------------------------------------------------------------------- 1 | require "luarocks.require" 2 | local STP = require "StackTracePlus" 3 | 4 | debug.traceback = STP.stacktrace 5 | 6 | function f(str, tb, ...) 7 | local g = function(fun) 8 | local str = str 9 | local tb = tb 10 | local a = nil .. "text" 11 | end 12 | return g(table) 13 | end 14 | 15 | f("hey", {foo="bar", dummy=1, blah=true}, 1) -------------------------------------------------------------------------------- /test/test_questionmark.lua: -------------------------------------------------------------------------------- 1 | 2 | local STP = require("StackTracePlus") 3 | 4 | local Page = {} 5 | 6 | local function bla() 7 | error("oops") 8 | end 9 | 10 | local function foo() 11 | bla() 12 | end 13 | 14 | Page.render = foo 15 | 16 | function main() 17 | local p = {} 18 | setmetatable(p, { __index = Page }) 19 | local x = "render" 20 | p[x]() 21 | end 22 | 23 | xpcall(main, function() print(STP.stacktrace()) end) 24 | 25 | -------------------------------------------------------------------------------- /rockspecs/stacktraceplus-git-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "StackTracePlus" 2 | version = "git-1" 3 | source = { 4 | url = "git://github.com/ignacio/StackTracePlus.git", 5 | branch = "master" 6 | } 7 | description = { 8 | summary = "StackTracePlus provides enhanced stack traces for Lua", 9 | detailed = [[ 10 | StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess 11 | function names when they're not available, etc. 12 | ]], 13 | license = "MIT/X11", 14 | homepage = "http://github.com/ignacio/StackTracePlus" 15 | } 16 | 17 | dependencies = { "lua >= 5.1" } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | StackTracePlus = "src/StackTracePlus.lua" 23 | } 24 | } -------------------------------------------------------------------------------- /rockspecs/stacktraceplus-0.1.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "StackTracePlus" 2 | version = "0.1.0-1" 3 | source = { 4 | url = "https://github.com/ignacio/StackTracePlus/archive/0.1.0-1.tar.gz", 5 | dir = "StackTracePlus-0.1.0-1" 6 | } 7 | description = { 8 | summary = "StackTracePlus provides enhanced stack traces for Lua", 9 | detailed = [[ 10 | StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess 11 | function names when they're not available, etc. 12 | ]], 13 | license = "MIT/X11", 14 | homepage = "http://github.com/ignacio/StackTracePlus" 15 | } 16 | 17 | dependencies = { "lua >= 5.1" } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | StackTracePlus = "src/StackTracePlus.lua" 23 | } 24 | } -------------------------------------------------------------------------------- /rockspecs/stacktraceplus-0.1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "StackTracePlus" 2 | version = "0.1.1-1" 3 | source = { 4 | url = "https://github.com/ignacio/StackTracePlus/archive/0.1.1-1.tar.gz", 5 | dir = "StackTracePlus-0.1.1-1" 6 | } 7 | description = { 8 | summary = "StackTracePlus provides enhanced stack traces for Lua", 9 | detailed = [[ 10 | StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess 11 | function names when they're not available, etc. 12 | ]], 13 | license = "MIT/X11", 14 | homepage = "http://github.com/ignacio/StackTracePlus" 15 | } 16 | 17 | dependencies = { "lua >= 5.1, < 5.3" } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | StackTracePlus = "src/StackTracePlus.lua" 23 | } 24 | } -------------------------------------------------------------------------------- /rockspecs/stacktraceplus-0.1.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "StackTracePlus" 2 | version = "0.1.2-1" 3 | source = { 4 | url = "https://github.com/ignacio/StackTracePlus/archive/0.1.2-1.tar.gz", 5 | dir = "StackTracePlus-0.1.2-1" 6 | } 7 | description = { 8 | summary = "StackTracePlus provides enhanced stack traces for Lua", 9 | detailed = [[ 10 | StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess 11 | function names when they're not available, etc. 12 | ]], 13 | license = "MIT/X11", 14 | homepage = "http://github.com/ignacio/StackTracePlus" 15 | } 16 | 17 | dependencies = { "lua >= 5.1, < 5.4" } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | StackTracePlus = "src/StackTracePlus.lua" 23 | } 24 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python # Need python environment for pip 2 | 3 | sudo: false # Use container-based infrastructure 4 | 5 | env: 6 | - LUA="lua=5.1" 7 | - LUA="lua=5.2" 8 | - LUA="lua=5.3" 9 | - LUA="luajit=2.0" 10 | - LUA="luajit=2.1" 11 | 12 | before_install: 13 | - pip install hererocks 14 | - hererocks lua_install --luarocks ^ --$LUA --compat none 15 | - export PATH=$PATH:$PWD/lua_install/bin 16 | - luarocks install luacheck 17 | - luarocks install luacov 18 | - luarocks install luacov-coveralls --server=https://luarocks.org/dev 19 | 20 | install: 21 | - luarocks install lunitx 22 | 23 | script: 24 | - luacheck rockspecs/stacktraceplus-git-1.rockspec 25 | - cd unittest 26 | - lua -lluacov run.lua 27 | 28 | after_success: 29 | - luacov-coveralls -v -r .. -e ../lua_install 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 Ignacio Burgueño 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /unittest/test.lua: -------------------------------------------------------------------------------- 1 | local STP = require "StackTracePlus" 2 | local lunit = require "lunitx" 3 | 4 | if _VERSION >= "Lua 5.2" then 5 | _ENV = lunit.module("simple","seeall") 6 | else 7 | module( ..., package.seeall, lunit.testcase ) 8 | end 9 | 10 | function testLuaModule() 11 | local f = function() 12 | local t = table 13 | error("an error") 14 | end 15 | 16 | local ok, err = xpcall(f, STP.stacktrace) 17 | assert_match( [[t = table module]], err, "" ) 18 | end 19 | 20 | function testKnownFunction() 21 | local my_function = function() 22 | end 23 | local f = function() 24 | error("an error") 25 | end 26 | STP.add_known_function(my_function, "this is my function") 27 | 28 | local ok, err = xpcall(f, STP.stacktrace) 29 | assert_match( [['this is my function']], err, "" ) 30 | end 31 | 32 | function testKnownTable() 33 | local my_table = {} 34 | local f = function() 35 | error("an error") 36 | end 37 | STP.add_known_table(my_table, "this is my table") 38 | 39 | local ok, err = xpcall(f, STP.stacktrace) 40 | assert_match( [[ = this is my table]], err, "" ) 41 | end 42 | 43 | function testCoroutine() 44 | local var_outside_co = {} 45 | 46 | local function helper() 47 | local arg_helper = "hi there!" 48 | error("an error") 49 | end 50 | 51 | local co = coroutine.create(function() 52 | local arg_inside_co = "arg1" 53 | helper() 54 | end) 55 | 56 | local status, err_msg = coroutine.resume(co) 57 | assert_false(status) 58 | 59 | local trace = STP.stacktrace(co) 60 | assert_match("arg_inside_co", trace) 61 | assert_match("arg_helper", trace) 62 | end 63 | 64 | --- 65 | -- Test case for issue #4 66 | -- https://github.com/ignacio/StackTracePlus/issues/4 67 | -- 68 | -- When building the stack trace, if there is a table or userdata with a __tostring metamethod which may throw an 69 | -- error, we fail with 'error in error handling'. 70 | -- 71 | function test_error_in_tostring() 72 | local t = setmetatable({}, { 73 | __tostring = function() 74 | error("Error in tostring") 75 | end 76 | }) 77 | 78 | local f = function() 79 | error("an error") 80 | end 81 | 82 | local ok, err = xpcall(f, STP.stacktrace) 83 | assert_not_equal("error in error handling", err) 84 | end 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StackTracePlus # 2 | 3 | [![Build Status](https://travis-ci.org/ignacio/StackTracePlus.png?branch=master)](https://travis-ci.org/ignacio/StackTracePlus) 4 | [![Coverage Status](https://coveralls.io/repos/github/ignacio/StackTracePlus/badge.svg?branch=master)](https://coveralls.io/github/ignacio/StackTracePlus?branch=master) 5 | 6 | StackTracePlus provides enhanced stack traces for [Lua 5.1, Lua 5.2, Lua 5.3][1], [LuaJIT][2] and [OpenResty][4]. 7 | 8 | StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess 9 | function names when they're not available, etc, so, instead of 10 | 11 | lua5.1.exe: D:\trunk_git\sources\stacktraceplus\test\test.lua:10: attempt to concatenate a nil value 12 | stack traceback: 13 | D:\trunk_git\sources\stacktraceplus\test\test.lua:10: in function 14 | (tail call): ? 15 | D:\trunk_git\sources\stacktraceplus\test\test.lua:15: in main chunk 16 | [C]: ? 17 | 18 | you'll get 19 | 20 | lua5.1.exe: D:\trunk_git\sources\stacktraceplus\test\test.lua:10: attempt to concatenate a nil value 21 | Stack Traceback 22 | =============== 23 | (2) C function 'function: 00A8F418' 24 | (3) Lua function 'g' at file 'D:\trunk_git\sources\stacktraceplus\test\test.lua:10' (best guess) 25 | Local variables: 26 | fun = table module 27 | str = string: "hey" 28 | tb = table: 027DCBE0 {dummy:1, blah:true, foo:bar} 29 | (*temporary) = nil 30 | (*temporary) = string: "text" 31 | (*temporary) = string: "attempt to concatenate a nil value" 32 | (4) tail call 33 | (5) main chunk of file 'D:\trunk_git\sources\stacktraceplus\test\test.lua' at line 15 34 | (6) C function 'function: 002CA480' 35 | 36 | ## Usage # 37 | 38 | StackTracePlus can be used as a replacement for `debug.traceback`, as an `xpcall` error handler or even from C code. Note that 39 | 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. 40 | 41 | ```lua 42 | local STP = require "StackTracePlus" 43 | 44 | debug.traceback = STP.stacktrace 45 | function test() 46 | local s = "this is a string" 47 | local n = 42 48 | local t = { foo = "bar" } 49 | local co = coroutine 50 | local cr = coroutine.create 51 | 52 | error("an error") 53 | end 54 | test() 55 | ``` 56 | 57 | That script will output (only with Lua 5.1): 58 | 59 | lua5.1: example.lua:11: an error 60 | Stack Traceback 61 | =============== 62 | (2) C function 'function: 006B5758' 63 | (3) global C function 'error' 64 | (4) Lua global 'test' at file 'example.lua:11' 65 | Local variables: 66 | s = string: "this is a string" 67 | n = number: 42 68 | t = table: 006E5220 {foo:bar} 69 | co = coroutine table 70 | cr = C function: 003C7080 71 | (5) main chunk of file 'example.lua' at line 14 72 | (6) C function 'function: 00637B30' 73 | 74 | **StackTracePlus** is aware of the usual Lua libraries, like *coroutine*, *table*, *string*, *io*, etc and functions like 75 | *print*, *pcall*, *assert*, and so on. 76 | 77 | You can also make STP aware of your own tables and functions by calling *add_known_function* and *add_known_table*. 78 | 79 | ```lua 80 | local STP = require "StackTracePlus" 81 | 82 | debug.traceback = STP.stacktrace 83 | local my_table = { 84 | f = function() end 85 | } 86 | function my_function() 87 | end 88 | 89 | function test(data, func) 90 | local s = "this is a string" 91 | 92 | error("an error") 93 | end 94 | 95 | STP.add_known_table(my_table, "A description for my_table") 96 | STP.add_known_function(my_function, "A description for my_function") 97 | 98 | test( my_table, my_function ) 99 | ``` 100 | 101 | Will output: 102 | 103 | lua5.1: ..\test\example2.lua:13: an error 104 | Stack Traceback 105 | =============== 106 | (2) C function 'function: 0073AAA8' 107 | (3) global C function 'error' 108 | (4) Lua global 'test' at file '..\test\example2.lua:13' 109 | Local variables: 110 | data = A description for my_table 111 | func = Lua function 'A description for my_function' (defined at line 7 of chunk ..\test\example2.lua) 112 | s = string: "this is a string" 113 | (5) main chunk of file '..\test\example2.lua' at line 19 114 | (6) C function 'function: 00317B30' 115 | 116 | 117 | ## Installation # 118 | The easiest way to install is with [LuaRocks][3]. 119 | 120 | - luarocks install stacktraceplus 121 | 122 | If you don't want to use LuaRocks, just copy StackTracePlus.lua to Lua's path. 123 | 124 | ## License # 125 | **StackTracePlus** is available under the MIT license. 126 | 127 | [1]: http://www.lua.org/ 128 | [2]: http://luajit.org/ 129 | [3]: https://luarocks.org/ 130 | [4]: https://openresty.org/ 131 | --------------------------------------------------------------------------------