Comparison

tools/modtrace.lua @ 11195:c4cb536b67b5

tools.modtrace: Library for tracing/debugging Lua module and method calls
author Matthew Wild <mwild1@gmail.com>
date Fri, 30 Oct 2020 13:53:24 +0000
child 11197:50f182931bdd
comparison
equal deleted inserted replaced
11194:9d1ce6f28401 11195:c4cb536b67b5
1 -- Trace module calls and method calls on created objects
2 --
3 -- Very rough and for debugging purposes only. It makes many
4 -- assumptions and there are many ways it could fail.
5 --
6 -- Example use:
7 --
8 -- local dbuffer = require "tools.modtrace".trace("util.dbuffer");
9 --
10
11 local t_pack = require "util.table".pack;
12 local serialize = require "util.serialization".serialize;
13 local unpack = table.unpack or unpack; --luacheck: ignore 113
14 local set = require "util.set";
15
16 local function stringify_value(v)
17 if type(v) == "string" and #v > 20 then
18 return ("<string(%d)>"):format(#v);
19 elseif type(v) == "function" then
20 return tostring(v);
21 end
22 return serialize(v, "debug");
23 end
24
25 local function stringify_params(...)
26 local n = select("#", ...);
27 local r = {};
28 for i = 1, n do
29 table.insert(r, stringify_value((select(i, ...))));
30 end
31 return table.concat(r, ", ");
32 end
33
34 local function stringify_result(ret)
35 local r = {};
36 for i = 1, ret.n do
37 table.insert(r, stringify_value(ret[i]));
38 end
39 return table.concat(r, ", ");
40 end
41
42 local function stringify_call(method_name, ...)
43 return ("%s(%s)"):format(method_name, stringify_params(...));
44 end
45
46 local function wrap_method(original_obj, original_method, method_name)
47 method_name = ("<%s>:%s"):format(getmetatable(original_obj).__name or "object", method_name);
48 return function (new_obj_self, ...)
49 local opts = new_obj_self._modtrace_opts;
50 local f = opts.output or io.stderr;
51 f:write(stringify_call(method_name, ...));
52 local ret = t_pack(original_method(original_obj, ...));
53 if ret.n > 0 then
54 f:write(" = ", stringify_result(ret), "\n");
55 else
56 f:write("\n");
57 end
58 return unpack(ret, 1, ret.n);
59 end;
60 end
61
62 local function wrap_function(original_function, function_name, opts)
63 local f = opts.output or io.stderr;
64 return function (...)
65 f:write(stringify_call(function_name, ...));
66 local ret = t_pack(original_function(...));
67 if ret.n > 0 then
68 f:write(" = ", stringify_result(ret), "\n");
69 else
70 f:write("\n");
71 end
72 return unpack(ret, 1, ret.n);
73 end;
74 end
75
76 local function wrap_metamethod(name, method)
77 if name == "__index" then
78 return function (new_obj, k)
79 local original_method;
80 if type(method) == "table" then
81 original_method = new_obj._modtrace_original_obj[k];
82 else
83 original_method = method(new_obj._modtrace_original_obj, k);
84 end
85 if original_method == nil then
86 return nil;
87 end
88 return wrap_method(new_obj._modtrace_original_obj, original_method, k);
89 end;
90 end
91 return function (new_obj, ...)
92 return method(new_obj._modtrace_original_obj, ...);
93 end;
94 end
95
96 local function wrap_mt(original_mt)
97 local new_mt = {};
98 for k, v in pairs(original_mt) do
99 new_mt[k] = wrap_metamethod(k, v);
100 end
101 return new_mt;
102 end
103
104 local function wrap_obj(original_obj, opts)
105 local new_mt = wrap_mt(getmetatable(original_obj));
106 return setmetatable({_modtrace_original_obj = original_obj, _modtrace_opts = opts}, new_mt);
107 end
108
109 local function wrap_new(original_new, function_name, opts)
110 local f = opts.output or io.stderr;
111 return function (...)
112 f:write(stringify_call(function_name, ...));
113 local ret = t_pack(original_new(...));
114 local obj = ret[1];
115
116 if ret.n == 1 and type(ret[1]) == "table" then
117 f:write(" = <", getmetatable(ret[1]).__name or "object", ">", "\n");
118 elseif ret.n > 0 then
119 f:write(" = ", stringify_result(ret), "\n");
120 else
121 f:write("\n");
122 end
123
124 if obj then
125 ret[1] = wrap_obj(obj, opts);
126 end
127 return unpack(ret, 1, ret.n);
128 end;
129 end
130
131 local function trace(module, opts)
132 if type(module) == "string" then
133 module = require(module);
134 end
135 opts = opts or {};
136 local new_methods = set.new(opts.new_methods or {"new"});
137 local fake_module = setmetatable({}, {
138 __index = function (_, k)
139 if new_methods:contains(k) then
140 return wrap_new(module[k], k, opts);
141 else
142 return wrap_function(module[k], k, opts);
143 end
144 end;
145 });
146 return fake_module;
147 end
148
149 return {
150 wrap = trace;
151 trace = trace;
152 }