| 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 | }; |