Comparison

util/debug.lua @ 4412:5d7d9a60bc7f

util.debug: Experimental new library for producing more extensive debug tracebacks
author Matthew Wild <mwild1@gmail.com>
date Thu, 03 Nov 2011 12:41:21 +0000
child 4418:70b5e533325d
comparison
equal deleted inserted replaced
4411:cf4e49b250c7 4412:5d7d9a60bc7f
1 -- Variables ending with these names will not
2 -- have their values printed ('password' includes
3 -- 'new_password', etc.)
4 local censored_names = {
5 password = true;
6 passwd = true;
7 pass = true;
8 pwd = true;
9 };
10
11 local function get_locals_table(level)
12 local locals = {};
13 for local_num = 1, math.huge do
14 local name, value = debug.getlocal(level, local_num);
15 if not name then break; end
16 table.insert(locals, { name = name, value = value });
17 end
18 return locals;
19 end
20
21 local function get_upvalues_table(func)
22 local upvalues = {};
23 for upvalue_num = 1, math.huge do
24 local name, value = debug.getupvalue(func, upvalue_num);
25 if not name then break; end
26 table.insert(upvalues, { name = name, value = value });
27 end
28 return upvalues;
29 end
30
31 local function string_from_var_table(var_table, max_line_len, indent_str)
32 local var_string = {};
33 local col_pos = 0;
34 max_line_len = max_line_len or math.huge;
35 indent_str = "\n"..(indent_str or "");
36 for _, var in ipairs(var_table) do
37 local name, value = var.name, var.value;
38 if name:sub(1,1) ~= "(" then
39 if type(value) == "string" then
40 if censored_names[name:match("%a+$")] then
41 value = "<hidden>";
42 else
43 value = ("%q"):format(value);
44 end
45 else
46 value = tostring(value);
47 end
48 if #value > max_line_len then
49 value = value:sub(1, max_line_len-3).."…";
50 end
51 local str = ("%s = %s"):format(name, tostring(value));
52 col_pos = col_pos + #str;
53 if col_pos > max_line_len then
54 table.insert(var_string, indent_str);
55 col_pos = 0;
56 end
57 table.insert(var_string, str);
58 end
59 end
60 if #var_string == 0 then
61 return nil;
62 else
63 return "{ "..table.concat(var_string, ", "):gsub(indent_str..", ", indent_str).." }";
64 end
65 end
66
67 function get_traceback_table(thread, start_level)
68 local levels = {};
69 for level = start_level, math.huge do
70 local info;
71 if thread then
72 info = debug.getinfo(thread, level);
73 else
74 info = debug.getinfo(level);
75 end
76 if not info then break; end
77
78 levels[(level-start_level)+1] = {
79 level = level;
80 info = info;
81 locals = get_locals_table(level);
82 upvalues = get_upvalues_table(info.func);
83 };
84 end
85 return levels;
86 end
87
88 function debug.traceback(thread, message, level)
89 if type(thread) ~= "thread" then
90 thread, message, level = coroutine.running(), thread, message;
91 end
92 if level and type(message) ~= "string" then
93 return nil, "invalid message";
94 elseif not level then
95 level = message or 2;
96 end
97
98 message = message and (message.."\n") or "";
99
100 local levels = get_traceback_table(thread, level+2);
101
102 local lines = {};
103 for nlevel, level in ipairs(levels) do
104 local info = level.info;
105 local line = "...";
106 local func_type = info.namewhat.." ";
107 if func_type == " " then func_type = ""; end;
108 if info.short_src == "[C]" then
109 line = "[ C ] "..func_type.."C function "..(info.name and ("%q"):format(info.name) or "(unknown name)")
110 elseif info.what == "main" then
111 line = "[Lua] "..info.short_src.." line "..info.currentline;
112 else
113 local name = info.name or " ";
114 if name ~= " " then
115 name = ("%q"):format(name);
116 end
117 if func_type == "global " or func_type == "local " then
118 func_type = func_type.."function ";
119 end
120 line = "[Lua] "..info.short_src.." line "..info.currentline.." in "..func_type..name.." defined on line "..info.linedefined;
121 end
122 nlevel = nlevel-1;
123 table.insert(lines, "\t"..(nlevel==0 and ">" or " ").."("..nlevel..") "..line);
124 local npadding = (" "):rep(#tostring(nlevel));
125 local locals_str = string_from_var_table(level.locals, 65, "\t "..npadding);
126 if locals_str then
127 table.insert(lines, "\t "..npadding.."Locals: "..locals_str);
128 end
129 local upvalues_str = string_from_var_table(level.upvalues, 65, "\t "..npadding);
130 if upvalues_str then
131 table.insert(lines, "\t "..npadding.."Upvals: "..upvalues_str);
132 end
133 end
134 return message.."stack traceback:\n"..table.concat(lines, "\n");
135 end