js/src/devtools/rootAnalysis/computeCallgraph.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:a16b7f6b1c0f
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 }

mercurial