forked from terralang/terra
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathleak.lua
120 lines (110 loc) · 3.66 KB
/
leak.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
local leak={}
local special={}
function leak.track(var)
return setmetatable({[special]=var},{__mode="v"})
end
function leak.find(self)
collectgarbage()
collectgarbage()
assert(type(self)=="table")
local env = { seen = {}, unscanned = {}, target = self[special] }
self[special] = nil
if not env.target then
return false
end
local function traverse(from, to, how, name)
local t = type(to)
if t == "string" or t == "number" or t == "boolean" or t == "nil" or --ignore types that don't reference anything
to == env or --ignore our local working environment 'env'
env.seen[to] then --ignore objects we have already added to worklist
return
end
env.seen[to] = {from = from, to = to, how = how, name = name}
table.insert(env.unscanned,to)
end
--initialize roots
for i=2, math.huge do
local info = debug.getinfo(i, "f")
if not info then break end
for j=1, math.huge do
local n, v = debug.getlocal(i, j)
if not n then break end
traverse(nil, n, "isname", nil)
traverse(nil, v, "local", n)
end
end
traverse(nil,debug.getregistry(),"value","$registry")
traverse(nil,"_G","isname")
traverse(nil,_G,"value","_G")
local function pathstring(f)
local node = env.seen[f]
assert(node)
local from = node.from and pathstring(node.from)
local function tablekey(e)
local fmt = type(e) == "string" and (e:match("[%a_][%w_]*") and ".%s" or '["%s"]') or "[%s]"
return fmt:format(tostring(e))
end
if "isname" == node.how then
from = from or "$locals"
string.format("%s.$name%s",from,tablekey(node.to))
elseif "local" == node.how then
return string.format("$locals.%s",node.name)
elseif "value" == node.how then
if not from then return node.name end
return from..tablekey(node.name)
elseif "key" == node.how then
return string.format("%s.$keys%s",from,tablekey(node.to))
elseif "upvalue" == node.how then
return string.format("%s.$upvalue.%s",from,node.name)
elseif "environment" == node.how then
return string.format("%s.$environment",from)
elseif "ismetatable" == node.how then
return string.format("%s.$metatable",from)
end
end
while #env.unscanned > 0 do
local obj = table.remove(env.unscanned)
if obj == env.target then
return true, pathstring(obj)
end
local t = type(obj)
if "table" == t then
local mtable = debug.getmetatable(obj)
local weakkeys,weakvalues
if mtable then
traverse(obj, mtable, "ismetatable", nil)
local m = mtable.__mode
if type(m) == "string" then
weakkeys = m:find("k")
weakvalues = m:find("v")
end
end
for key, value in pairs(obj) do
if not weakkeys then
traverse(obj, key, "key", nil)
end
if not weakvalues then
traverse(obj, value, "value", key)
end
end
elseif "function" == t then
for i = 1, math.huge do
local n, v = debug.getupvalue(obj, i)
if not n then break end -- when there is no upvalues
traverse(obj, n, "isname", nil)
traverse(obj, v, "upvalue", n)
end
local fenv = debug.getfenv(obj)
traverse(obj, fenv, "environment", nil)
elseif "userdata" == t or "cdata" == t then
local mtable = debug.getmetatable(obj)
if mtable then traverse(obj, mtable, "ismetatable", nil) end
local fenv = debug.getfenv(obj)
if fenv then traverse(obj, fenv, "environment", nil) end
else
print("warning! ignoring unknown object type: ",t)
end
end
return true, "error! failed to find the path to object"
end
return leak