luatraverse.lua

1
-------------------------------------------------------------------------------
2
-- This module implements a function that traverses all live objects.
3
-- You can implement your own function to pass as a parameter of traverse
4
-- and give you the information you want. As an example we have implemented
5
-- countreferences and findallpaths
6
--
7
-- Alexandra Barros - 2006.03.15
8
-------------------------------------------------------------------------------
9
 
10
--luacheck: ignore 143/debug 113/getfenv
11
 
12
local List = {}
13
 
14
function List.new ()
15
	return {first = 0, last = -1}
16
end
17
 
18
function List.push (list, value)
19
	local last = list.last + 1
20
    list.last = last
21
    list[last] = value
22
end
23
 
24
function List.pop (list)
25
    local first = list.first
26
    if first > list.last then error("list is empty") end
27
    local value = list[first]
28
    list[first] = nil
29
    list.first = first + 1
30
    return value
31
end
32
 
33
function List.isempty (list)
34
	return list.first > list.last
35
end
36
 
37
local traverse, edge;
38
local types = {};
39
local get_metatable, get_environment, get_registry, get_locals, upvalues
40
 
41
 
42
-- select implementation of get_metatable depending on available API
43
if type( debug ) == "table" and
44
   type( debug.getmetatable ) == "function" then
45
 
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, obj, local_name, "isname", nil);
260
			edge(env, obj, local_value, "local", local_name);
261
		end
262
	end
263
end
264
 
265
-- Main function
266
-- 'funcs' is a table that contains a funcation for every lua type and also the
267
-- function edge edge (traverseedge).
268
function traverse(funcs, ignoreobjs, seenobjs)
269
 
270
	-- The keys of the marked table are the objects (for example, table: 00442330).
271
	-- The value of each key is true if the object has been found and false
272
	-- otherwise.
273
	local env = {marked = seenobjs or {}, list=List.new(), funcs=funcs}
274
 
275
	if ignoreobjs then
276
		for i=1, #ignoreobjs do
277
			env.marked[ignoreobjs[i]] = true
278
		end
279
	end
280
 
281
	env.marked["traverse"] = true
282
	env.marked[traverse] = true
283
 
284
	-- marks and inserts on the list
285
	edge(env, nil, "_G", "isname", nil)
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
293
 
294
	-- traverses the active thread
295
	-- inserts the local variables
296
	-- interates over the function on the stack, starting from the one that
297
	-- called traverse
298
	handle_locals(env, nil);
299
 
300
	while not List.isempty(env.list) do
301
 
302
		local obj = List.pop(env.list)
303
		local t = type(obj)
304
		types[t](env, obj)
305
 
306
	end
307
end
308
 
309
function types.table(env, obj)
310
	local f = env.funcs.table
311
	if f then f(obj) end
312
 
313
	for key, value in pairs(obj) do
314
		edge(env, obj, key, "key", nil)
315
		edge(env, obj, value, "value", key)
316
	end
317
 
318
	handle_metatable(env, obj)
319
end
320
 
321
function types.string(env, obj)
322
	local f = env.funcs.string
323
	if f then f(obj) end
324
end
325
 
326
function types.userdata(env, obj)
327
	local f = env.funcs.userdata
328
	if f then f(obj) end
329
 
330
	handle_metatable(env, obj)
331
	handle_environment(env, obj)
332
end
333
 
334
types["function"] = function (env, obj)
335
	local f = env.funcs.func
336
	if f then f(obj) end
337
 
338
	handle_environment(env, obj)
339
 
340
	-- handle upvalues
341
	for name, value in upvalues(obj, true, true) do
342
		edge(env, obj, name, "isname", nil)
343
		edge(env, obj, value, "upvalue", name)
344
	end
345
end
346
 
347
function types.thread(env, t)
348
	local f = env.funcs.thread
349
	if f then f(t) end
350
 
351
	handle_environment(env, t)
352
	handle_locals(env, t);
353
 
354
end
355
 
356
 
357
-- 'how' is a string that identifies the content of 'to' and 'value':
358
-- 		if 'how' is "key", then 'to' is a key and 'name' is nil.
359
-- 		if 'how' is "value", then 'to' is an object and 'name' is the name of the
360
--		key.
361
function edge(env, from, to, how, name)
362
 
363
	local t = type(to)
364
 
365
	if to and (t~="boolean") and (t~="number") and (t~="new") then
366
		-- If the destination object has not been found yet
367
		if not env.marked[to] then
368
			env.marked[to] = true
369
			List.push(env.list, to) -- puts on the list to be traversed
370
		end
371
 
372
		local f = env.funcs.edge
373
		if f then f(from, to, how, name) end
374
 
375
	end
376
end
377
 
378
return {
379
	traverse = traverse;
380
};