|
1 /* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 |
|
3 "use strict"; |
|
4 |
|
5 loadRelativeToScript('utility.js'); |
|
6 loadRelativeToScript('annotations.js'); |
|
7 loadRelativeToScript('CFG.js'); |
|
8 |
|
9 var subclasses = {}; |
|
10 var superclasses = {}; |
|
11 var classFunctions = {}; |
|
12 |
|
13 var fieldCallSeen = {}; |
|
14 |
|
15 function addClassEntry(index, name, other) |
|
16 { |
|
17 if (!(name in index)) { |
|
18 index[name] = [other]; |
|
19 return; |
|
20 } |
|
21 |
|
22 for (var entry of index[name]) { |
|
23 if (entry == other) |
|
24 return; |
|
25 } |
|
26 |
|
27 index[name].push(other); |
|
28 } |
|
29 |
|
30 // CSU is "Class/Struct/Union" |
|
31 function processCSU(csuName, csu) |
|
32 { |
|
33 if (!("FunctionField" in csu)) |
|
34 return; |
|
35 for (var field of csu.FunctionField) { |
|
36 if (1 in field.Field) { |
|
37 var superclass = field.Field[1].Type.Name; |
|
38 var subclass = field.Field[1].FieldCSU.Type.Name; |
|
39 assert(subclass == csuName); |
|
40 addClassEntry(subclasses, superclass, subclass); |
|
41 addClassEntry(superclasses, subclass, superclass); |
|
42 } |
|
43 if ("Variable" in field) { |
|
44 // Note: not dealing with overloading correctly. |
|
45 var name = field.Variable.Name[0]; |
|
46 var key = csuName + ":" + field.Field[0].Name[0]; |
|
47 if (!(key in classFunctions)) |
|
48 classFunctions[key] = []; |
|
49 classFunctions[key].push(name); |
|
50 } |
|
51 } |
|
52 } |
|
53 |
|
54 function findVirtualFunctions(initialCSU, field, suppressed) |
|
55 { |
|
56 var worklist = [initialCSU]; |
|
57 |
|
58 // Virtual call targets on subclasses of nsISupports may be incomplete, |
|
59 // if the interface is scriptable. Just treat all indirect calls on |
|
60 // nsISupports objects as potentially GC'ing, except AddRef/Release |
|
61 // which should never enter the JS engine (even when calling dtors). |
|
62 while (worklist.length) { |
|
63 var csu = worklist.pop(); |
|
64 if (csu == "nsISupports" && (field == "AddRef" || field == "Release")) { |
|
65 suppressed[0] = true; |
|
66 return []; |
|
67 } |
|
68 if (isOverridableField(initialCSU, csu, field)) |
|
69 return null; |
|
70 |
|
71 if (csu in superclasses) { |
|
72 for (var superclass of superclasses[csu]) |
|
73 worklist.push(superclass); |
|
74 } |
|
75 } |
|
76 |
|
77 var functions = []; |
|
78 var worklist = [csu]; |
|
79 |
|
80 while (worklist.length) { |
|
81 var csu = worklist.pop(); |
|
82 var key = csu + ":" + field; |
|
83 |
|
84 if (key in classFunctions) { |
|
85 for (var name of classFunctions[key]) |
|
86 functions.push(name); |
|
87 } |
|
88 |
|
89 if (csu in subclasses) { |
|
90 for (var subclass of subclasses[csu]) |
|
91 worklist.push(subclass); |
|
92 } |
|
93 } |
|
94 |
|
95 return functions; |
|
96 } |
|
97 |
|
98 var memoized = {}; |
|
99 var memoizedCount = 0; |
|
100 |
|
101 function memo(name) |
|
102 { |
|
103 if (!(name in memoized)) { |
|
104 memoizedCount++; |
|
105 memoized[name] = "" + memoizedCount; |
|
106 print("#" + memoizedCount + " " + name); |
|
107 } |
|
108 return memoized[name]; |
|
109 } |
|
110 |
|
111 var seenCallees = null; |
|
112 var seenSuppressedCallees = null; |
|
113 |
|
114 // Return a list of all callees that the given edge might be a call to. Each |
|
115 // one is represented by an object with a 'kind' field that is one of |
|
116 // ('direct', 'field', 'indirect', 'unknown'). |
|
117 function getCallees(edge) |
|
118 { |
|
119 if (edge.Kind != "Call") |
|
120 return []; |
|
121 |
|
122 var callee = edge.Exp[0]; |
|
123 var callees = []; |
|
124 if (callee.Kind == "Var") { |
|
125 assert(callee.Variable.Kind == "Func"); |
|
126 callees.push({'kind': 'direct', 'name': callee.Variable.Name[0]}); |
|
127 } else { |
|
128 assert(callee.Kind == "Drf"); |
|
129 if (callee.Exp[0].Kind == "Fld") { |
|
130 var field = callee.Exp[0].Field; |
|
131 var fieldName = field.Name[0]; |
|
132 var csuName = field.FieldCSU.Type.Name; |
|
133 var functions = null; |
|
134 if ("FieldInstanceFunction" in field) { |
|
135 var suppressed = [ false ]; |
|
136 functions = findVirtualFunctions(csuName, fieldName, suppressed); |
|
137 if (suppressed[0]) { |
|
138 // Field call known to not GC; mark it as suppressed so |
|
139 // direct invocations will be ignored |
|
140 callees.push({'kind': "field", 'csu': csuName, 'field': fieldName, |
|
141 'suppressed': true}); |
|
142 } |
|
143 } |
|
144 if (functions) { |
|
145 // Known set of virtual call targets. Treat them as direct |
|
146 // calls to all possible resolved types, but also record edges |
|
147 // from this field call to each final callee. When the analysis |
|
148 // is checking whether an edge can GC and it sees an unrooted |
|
149 // pointer held live across this field call, it will know |
|
150 // whether any of the direct callees can GC or not. |
|
151 var targets = []; |
|
152 for (var name of functions) { |
|
153 callees.push({'kind': "direct", 'name': name}); |
|
154 targets.push({'kind': "direct", 'name': name}); |
|
155 } |
|
156 callees.push({'kind': "resolved-field", 'csu': csuName, 'field': fieldName, 'callees': targets}); |
|
157 } else { |
|
158 // Unknown set of call targets. Non-virtual field call, |
|
159 // or virtual call on an nsISupports object. |
|
160 callees.push({'kind': "field", 'csu': csuName, 'field': fieldName}); |
|
161 } |
|
162 } else if (callee.Exp[0].Kind == "Var") { |
|
163 // indirect call through a variable. |
|
164 callees.push({'kind': "indirect", 'variable': callee.Exp[0].Variable.Name[0]}); |
|
165 } else { |
|
166 // unknown call target. |
|
167 callees.push({'kind': "unknown"}); |
|
168 } |
|
169 } |
|
170 |
|
171 return callees; |
|
172 } |
|
173 |
|
174 var lastline; |
|
175 function printOnce(line) |
|
176 { |
|
177 if (line != lastline) { |
|
178 print(line); |
|
179 lastline = line; |
|
180 } |
|
181 } |
|
182 |
|
183 function processBody(caller, body) |
|
184 { |
|
185 if (!('PEdge' in body)) |
|
186 return; |
|
187 |
|
188 lastline = null; |
|
189 for (var edge of body.PEdge) { |
|
190 if (edge.Kind != "Call") |
|
191 continue; |
|
192 var edgeSuppressed = false; |
|
193 var seen = seenCallees; |
|
194 if (edge.Index[0] in body.suppressed) { |
|
195 edgeSuppressed = true; |
|
196 seen = seenSuppressedCallees; |
|
197 } |
|
198 for (var callee of getCallees(edge)) { |
|
199 var prologue = (edgeSuppressed || callee.suppressed) ? "SUPPRESS_GC " : ""; |
|
200 prologue += memo(caller) + " "; |
|
201 if (callee.kind == 'direct') { |
|
202 if (!(callee.name in seen)) { |
|
203 seen[name] = true; |
|
204 printOnce("D " + prologue + memo(callee.name)); |
|
205 } |
|
206 } else if (callee.kind == 'field') { |
|
207 var { csu, field } = callee; |
|
208 printOnce("F " + prologue + "CLASS " + csu + " FIELD " + field); |
|
209 } else if (callee.kind == 'resolved-field') { |
|
210 // Fully-resolved field call (usually a virtual method). Record |
|
211 // the callgraph edges. Do not consider suppression, since it |
|
212 // is local to this callsite and we are writing out a global |
|
213 // record here. |
|
214 // |
|
215 // Any field call that does *not* have an R entry must be |
|
216 // assumed to call anything. |
|
217 var { csu, field, callees } = callee; |
|
218 var fullFieldName = csu + "." + field; |
|
219 if (!(fullFieldName in fieldCallSeen)) { |
|
220 fieldCallSeen[fullFieldName] = true; |
|
221 for (var target of callees) |
|
222 printOnce("R " + memo(fullFieldName) + " " + memo(target.name)); |
|
223 } |
|
224 } else if (callee.kind == 'indirect') { |
|
225 printOnce("I " + prologue + "VARIABLE " + callee.variable); |
|
226 } else if (callee.kind == 'unknown') { |
|
227 printOnce("I " + prologue + "VARIABLE UNKNOWN"); |
|
228 } else { |
|
229 printErr("invalid " + callee.kind + " callee"); |
|
230 debugger; |
|
231 } |
|
232 } |
|
233 } |
|
234 } |
|
235 |
|
236 var callgraph = {}; |
|
237 |
|
238 var xdb = xdbLibrary(); |
|
239 xdb.open("src_comp.xdb"); |
|
240 |
|
241 var minStream = xdb.min_data_stream(); |
|
242 var maxStream = xdb.max_data_stream(); |
|
243 |
|
244 for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) { |
|
245 var csu = xdb.read_key(csuIndex); |
|
246 var data = xdb.read_entry(csu); |
|
247 var json = JSON.parse(data.readString()); |
|
248 processCSU(csu.readString(), json[0]); |
|
249 |
|
250 xdb.free_string(csu); |
|
251 xdb.free_string(data); |
|
252 } |
|
253 |
|
254 xdb.open("src_body.xdb"); |
|
255 |
|
256 printErr("Finished loading data structures"); |
|
257 |
|
258 var minStream = xdb.min_data_stream(); |
|
259 var maxStream = xdb.max_data_stream(); |
|
260 |
|
261 for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) { |
|
262 var name = xdb.read_key(nameIndex); |
|
263 var data = xdb.read_entry(name); |
|
264 functionBodies = JSON.parse(data.readString()); |
|
265 for (var body of functionBodies) |
|
266 body.suppressed = []; |
|
267 for (var body of functionBodies) { |
|
268 for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor)) |
|
269 pbody.suppressed[id] = true; |
|
270 } |
|
271 |
|
272 seenCallees = {}; |
|
273 seenSuppressedCallees = {}; |
|
274 |
|
275 var functionName = name.readString(); |
|
276 for (var body of functionBodies) |
|
277 processBody(functionName, body); |
|
278 |
|
279 xdb.free_string(name); |
|
280 xdb.free_string(data); |
|
281 } |