luatraverse.lua

changeset 11
1e2c945346ca
parent 8
b75900150167
child 12
e3e3cbe544ec
equal deleted inserted replaced
10:cafaa46928c8 11:1e2c945346ca
5 -- countreferences and findallpaths 5 -- countreferences and findallpaths
6 -- 6 --
7 -- Alexandra Barros - 2006.03.15 7 -- Alexandra Barros - 2006.03.15
8 ------------------------------------------------------------------------------- 8 -------------------------------------------------------------------------------
9 9
10 module("traverse", package.seeall) 10 --luacheck: ignore 143/debug 113/getfenv
11 11
12 local List = {} 12 local List = {}
13 13
14 function List.new () 14 function List.new ()
15 return {first = 0, last = -1} 15 return {first = 0, last = -1}
19 local last = list.last + 1 19 local last = list.last + 1
20 list.last = last 20 list.last = last
21 list[last] = value 21 list[last] = value
22 end 22 end
23 23
24 function List.pop (list) 24 function List.pop (list)
25 local first = list.first 25 local first = list.first
26 if first > list.last then error("list is empty") end 26 if first > list.last then error("list is empty") end
27 local value = list[first] 27 local value = list[first]
28 list[first] = nil 28 list[first] = nil
29 list.first = first + 1 29 list.first = first + 1
30 return value 30 return value
31 end 31 end
32 32
33 function List.isempty (list) 33 function List.isempty (list)
34 return list.first > list.last 34 return list.first > list.last
35 end 35 end
36 36
37 -- Counts all references for a given object 37 local traverse, edge;
38 function countreferences(value) 38 local types = {};
39 local count = -1 39 local get_metatable, get_environment, get_registry, get_locals, upvalues
40 local f = function(from, to, how, v) 40
41 if to == value then 41
42 count = count + 1 42 -- select implementation of get_metatable depending on available API
43 end 43 if type( debug ) == "table" and
44 end 44 type( debug.getmetatable ) == "function" then
45 traverse({edge=f}, {count, f}) 45
46 return count 46 local get_mt = debug.getmetatable
47 function get_metatable( val, enabled )
48 if enabled then return get_mt( val ) end
49 end
50
51 elseif type( getmetatable ) == "function" then
52
53 function get_metatable( val, enabled )
54 if enabled then return getmetatable( val ) end
55 end
56
57 else
58
59 function get_metatable() end
60
61 end
62
63
64 -- select implementation of get_environment depending on available API
65 if type( debug ) == "table" and
66 type( debug.getfenv ) == "function" then
67
68 local get_fe = debug.getfenv
69 function get_environment( val, n, enabled )
70 if enabled and n == 1 then
71 local uv = get_fe( val )
72 return uv, uv ~= nil
73 end
74 return nil, false
75 end
76
77 elseif type( debug ) == "table" and
78 type( debug.getuservalue ) == "function" then
79
80 local get_uv = debug.getuservalue
81 if _VERSION < "Lua 5.4" then
82 local get_uv_noindex = get_uv
83 function get_uv( val, n )
84 if n == 1 then
85 local uv = get_uv_noindex( val )
86 return uv, uv ~= nil
87 end
88 return nil, false
89 end
90 end
91 function get_environment( val, n, enabled )
92 if enabled then
93 -- getuservalue in Lua 5.2 throws on light userdata!
94 local ok, res1, res2 = pcall( get_uv, val, n )
95 if ok then return res1, res2 end
96 return nil, false
97 end
98 end
99
100 elseif type( getfenv ) == "function" then
101
102 function get_environment( val, n, enabled )
103 if enabled and n == 1 and type( val ) == "function" then
104 local uv = getfenv( val )
105 return uv, uv ~= nil
106 end
107 return nil, false
108 end
109
110 else
111
112 function get_environment() return nil, false end
113
114 end
115
116
117 -- select implementation of get_registry
118 if type( debug ) == "table" and
119 type( debug.getregistry ) == "function" then
120 get_registry = debug.getregistry
121 else
122 function get_registry() end
123 end
124
125
126 -- select implementation of get_locals
127 -- locs = {
128 -- {
129 -- name = "example_function";
130 -- func = <function>;
131 -- [1] = { "local_name", <local value> };
132 -- [2] = ...;
133 -- };
134 -- }
135 if type( debug ) == "table" and
136 type( debug.getinfo ) == "function" and
137 type( debug.getlocal ) == "function" then
138
139 local getinfo, getlocal = debug.getinfo, debug.getlocal
140
141 local function getinfo_nothread( _, func, what )
142 return getinfo( func, what )
143 end
144
145 local function getlocal_nothread( _, level, loc )
146 return getlocal( level, loc )
147 end
148
149 function get_locals( thread, enabled )
150 if enabled then
151 local locs = {}
152 local start = 1
153 local gi, gl = getinfo, getlocal
154 if not thread then
155 gi, gl = getinfo_nothread, getlocal_nothread
156 end
157 local info, i = gi( thread, 0, "nf" ), 0
158 while info do
159 local t = { name = info.name, func = info.func }
160 local j, n,v = 1, gl( thread, i, 1 )
161 while n ~= nil do
162 t[ j ] = { n, v }
163 j = j + 1
164 n,v = gl( thread, i, j )
165 end
166 i = i + 1
167 locs[ i ] = t
168 -- We skip the currently-executing traverse() function
169 -- and anything that is only referenced through it
170 if info.func == traverse then start = i+1 end
171 info = gi( thread, i, "nf" )
172 end
173 return locs, start
174 end
175 end
176
177 else
178
179 function get_locals() end
180
181 end
182
183
184 -- select implementation of upvalues depending on available API
185 local function dummy_iter() end
186 if type( debug ) == "table" and
187 type( debug.getupvalue ) == "function" then
188
189 local get_up, uv_iter = debug.getupvalue
190 if _VERSION == "Lua 5.1" then
191
192 function uv_iter( state )
193 local name, uv = get_up( state.value, state.n )
194 state.n = state.n + 1
195 return name, uv, nil
196 end
197
198 else -- Lua 5.2 (and later) mixes upvalues and environments
199
200 local get_upid
201 if type( debug.upvalueid ) == "function" then
202 get_upid = debug.upvalueid
203 end
204
205 function uv_iter( state )
206 local name, uv = get_up( state.value, state.n )
207 state.n = state.n + 1
208 if name == "_ENV" and not state.show_env then
209 return uv_iter( state )
210 end
211 local id = nil
212 if get_upid ~= nil and name ~= nil then
213 id = get_upid( state.value, state.n - 1 )
214 end
215 return name, uv, id
216 end
217 end
218
219 function upvalues( val, enabled, show_env )
220 if enabled then
221 return uv_iter, { value = val, n = 1, show_env = show_env }
222 else
223 return dummy_iter
224 end
225 end
226
227 else
228
229 function upvalues()
230 return dummy_iter
231 end
232
233 end
234
235 local function handle_metatable(env, obj)
236 local mtable = get_metatable(obj)
237 if mtable ~= nil then
238 edge(env, obj, mtable, "ismetatable", nil)
239 end
240 end
241
242 local function handle_environment(env, obj)
243 local n = 0
244 repeat
245 n = n + 1
246 local fenv, has_env = get_environment(obj, n)
247 if has_env and fenv ~= nil then
248 edge(env, obj, fenv, "environment", nil)
249 end
250 until not has_env
251 end
252
253 local function handle_locals(env, obj)
254 local stack = get_locals(obj);
255 if not stack then return; end
256 for _, frame in ipairs(stack) do
257 for local_entry in ipairs(frame) do
258 local local_name, local_value = local_entry[1], local_entry[2];
259 edge(env, nil, local_name, "isname", nil);
260 edge(env, nil, local_value, "local", local_name);
261 end
262 end
47 end 263 end
48 264
49 -- Main function 265 -- Main function
50 -- 'funcs' is a table that contains a funcation for every lua type and also the 266 -- 'funcs' is a table that contains a funcation for every lua type and also the
51 -- function edge edge (traverseedge). 267 -- function edge edge (traverseedge).
52 function traverse(funcs, ignoreobjs) 268 function traverse(funcs, ignoreobjs, seenobjs)
53 269
54 -- The keys of the marked table are the objetcts (for example, table: 00442330). 270 -- The keys of the marked table are the objects (for example, table: 00442330).
55 -- The value of each key is true if the object has been found and false 271 -- The value of each key is true if the object has been found and false
56 -- otherwise. 272 -- otherwise.
57 local env = {marked = {}, list=List.new(), funcs=funcs} 273 local env = {marked = seenobjs or {}, list=List.new(), funcs=funcs}
58 274
59 if ignoreobjs then 275 if ignoreobjs then
60 for i=1, #ignoreobjs do 276 for i=1, #ignoreobjs do
61 env.marked[ignoreobjs[i]] = true 277 env.marked[ignoreobjs[i]] = true
62 end 278 end
63 end 279 end
64 280
65 env.marked["traverse"] = true 281 env.marked["traverse"] = true
66 env.marked[traverse] = true 282 env.marked[traverse] = true
67 283
68 -- marks and inserts on the list 284 -- marks and inserts on the list
69 edge(env, nil, "_G", "isname", nil) 285 edge(env, nil, "_G", "isname", nil)
70 edge(env, nil, _G, "value", "_G") 286 edge(env, nil, _G, "value", "_G")
287
288 -- traverse the registry
289 local registry = get_registry();
290 if registry ~= nil then
291 edge(env, nil, registry, "value", "REGISTRY");
292 end
71 293
72 -- traverses the active thread 294 -- traverses the active thread
73 -- inserts the local variables 295 -- inserts the local variables
74 -- interates over the function on the stack, starting from the one that 296 -- interates over the function on the stack, starting from the one that
75 -- called traverse 297 -- called traverse
76 298 handle_locals(env, nil);
77 for i=2, math.huge do 299
78 local info = debug.getinfo(i, "f") 300 while not List.isempty(env.list) do
79 if not info then break end 301
80 for j=1, math.huge do
81 local n, v = debug.getlocal(i, j)
82 if not n then break end
83
84 edge(env, nil, n, "isname", nil)
85 edge(env, nil, v, "local", n)
86 end
87 end
88
89 while not List.isempty(env.list) do
90
91 local obj = List.pop(env.list) 302 local obj = List.pop(env.list)
92 local t = type(obj) 303 local t = type(obj)
93 _M["traverse" .. t](env, obj) 304 types[t](env, obj)
94 305
95 end 306 end
96 307 end
97 end 308
98 309 function types.table(env, obj)
99 function traversetable(env, obj)
100
101 local f = env.funcs.table 310 local f = env.funcs.table
102 if f then f(obj) end 311 if f then f(obj) end
103 312
104 for key, value in pairs(obj) do 313 for key, value in pairs(obj) do
105 edge(env, obj, key, "key", nil) 314 edge(env, obj, key, "key", nil)
106 edge(env, obj, value, "value", key) 315 edge(env, obj, value, "value", key)
107 end 316 end
108 317
109 local mtable = debug.getmetatable(obj) 318 handle_metatable(env, obj)
110 if mtable then edge(env, obj, mtable, "ismetatable", nil) end 319 end
111 320
112 end 321 function types.string(env, obj)
113
114 function traversestring(env, obj)
115 local f = env.funcs.string 322 local f = env.funcs.string
116 if f then f(obj) end 323 if f then f(obj) end
117 324 end
118 end 325
119 326 function types.userdata(env, obj)
120 function traverseuserdata(env, obj)
121 local f = env.funcs.userdata 327 local f = env.funcs.userdata
122 if f then f(obj) end 328 if f then f(obj) end
123 329
124 local mtable = debug.getmetatable(obj) 330 handle_metatable(env, obj)
125 if mtable then edge(env, obj, mtable, "ismetatable", nil) end 331 handle_environment(env, obj)
126 332 end
127 local fenv = debug.getfenv(obj) 333
128 if fenv then edge(env, obj, fenv, "environment", nil) end 334 types["function"] = function (env, obj)
129
130 end
131
132 function traversefunction(env, obj)
133 local f = env.funcs.func 335 local f = env.funcs.func
134 if f then f(obj) end 336 if f then f(obj) end
135 337
136 -- gets the upvalues 338 handle_environment(env, obj)
137 local i = 1 339
138 while true do 340 -- handle upvalues
139 local n, v = debug.getupvalue(obj, i) 341 for name, value in upvalues(obj, true, true) do
140 if not n then break end -- when there is no upvalues 342 edge(env, obj, name, "isname", nil)
141 edge(env, obj, n, "isname", nil) 343 edge(env, obj, value, "upvalue", name)
142 edge(env, obj, v, "upvalue", n) 344 end
143 i = i + 1 345 end
144 end 346
145 347 function types.thread(env, t)
146 local fenv = debug.getfenv(obj)
147 edge(env, obj, fenv, "environment", nil)
148
149 end
150
151 function traversethread(env, t)
152 local f = env.funcs.thread 348 local f = env.funcs.thread
153 if f then f(t) end 349 if f then f(t) end
154 350
155 for i=1, math.huge do 351 handle_environment(env, t)
156 local info = debug.getinfo(t, i, "f") 352 handle_locals(env, t);
157 if not info then break end 353
158 for j=1, math.huge do
159 local n, v = debug.getlocal(t, i , j)
160 if not n then break end
161 print(n, v)
162
163 edge(env, nil, n, "isname", nil)
164 edge(env, nil, v, "local", n)
165 end
166 end
167
168 local fenv = debug.getfenv(t)
169 edge(env, t, fenv, "environment", nil)
170
171 end 354 end
172 355
173 356
174 -- 'how' is a string that identifies the content of 'to' and 'value': 357 -- 'how' is a string that identifies the content of 'to' and 'value':
175 -- if 'how' is "key", then 'to' is a key and 'name' is nil. 358 -- if 'how' is "key", then 'to' is a key and 'name' is nil.
176 -- if 'how' is "value", then 'to' is an object and 'name' is the name of the 359 -- if 'how' is "value", then 'to' is an object and 'name' is the name of the
177 -- key. 360 -- key.
178 function edge(env, from, to, how, name) 361 function edge(env, from, to, how, name)
179 362
180 local t = type(to) 363 local t = type(to)
181 364
182 if to and (t~="boolean") and (t~="number") and (t~="new") then 365 if to and (t~="boolean") and (t~="number") and (t~="new") then
183 -- If the destination object has not been found yet 366 -- If the destination object has not been found yet
184 if not env.marked[to] then 367 if not env.marked[to] then
185 env.marked[to] = true 368 env.marked[to] = true
186 List.push(env.list, to) -- puts on the list to be traversed 369 List.push(env.list, to) -- puts on the list to be traversed
187 end 370 end
188 371
189 local f = env.funcs.edge 372 local f = env.funcs.edge
190 if f then f(from, to, how, name) end 373 if f then f(from, to, how, name) end
191 374
192 end 375 end
193 end 376 end
194 377
195 return _M; 378 return {
379 traverse = traverse;
380 };

mercurial