js/src/devtools/rootAnalysis/computeCallgraph.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial