callgraph.js (8977B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 loadRelativeToScript('utility.js'); 6 loadRelativeToScript('annotations.js'); 7 loadRelativeToScript('CFG.js'); 8 9 // Map from csu => set of immediate subclasses 10 var subclasses = new Map(); 11 12 // Map from csu => set of immediate superclasses 13 var superclasses = new Map(); 14 15 // Map from "csu.name:nargs" => set of full method name 16 var virtualDefinitions = new Map(); 17 18 // Every virtual method declaration, anywhere. 19 // 20 // Map from csu => Set of function-info. 21 // function-info: { 22 // name : simple string 23 // typedfield : "name:nargs" ("mangled" field name) 24 // field: full Field datastructure 25 // annotations : Set of [annotation-name, annotation-value] 2-element arrays 26 // inherited : whether the method is inherited from a base class 27 // pureVirtual : whether the method is pure virtual on this CSU 28 // dtor : if this is a virtual destructor with a definition in this class or 29 // a superclass, then the full name of the definition as if it were defined 30 // in this class. This is weird, but it's how gcc emits it. We will add a 31 // synthetic call from this function to its immediate base classes' dtors, 32 // so even if the function does not actually exist and is inherited from a 33 // base class, we will get a path to the inherited function. (Regular 34 // virtual methods are *not* claimed to exist when they don't.) 35 // } 36 var virtualDeclarations = new Map(); 37 38 var virtualResolutionsSeen = new Set(); 39 40 var ID = { 41 jscode: 1, 42 anyfunc: 2, 43 nogcfunc: 3, 44 gc: 4, 45 }; 46 47 // map is a map from names to sets of entries. 48 function addToNamedSet(map, name, entry) 49 { 50 if (!map.has(name)) 51 map.set(name, new Set()); 52 const s = map.get(name); 53 s.add(entry); 54 return s; 55 } 56 57 // CSU is "Class/Struct/Union" 58 function processCSU(csuName, csu) 59 { 60 if (!("FunctionField" in csu)) 61 return; 62 63 for (const {Base} of (csu.CSUBaseClass || [])) { 64 addToNamedSet(subclasses, Base, csuName); 65 addToNamedSet(superclasses, csuName, Base); 66 } 67 68 for (const {Field, Variable} of csu.FunctionField) { 69 // Virtual method 70 const info = Field[0]; 71 const name = info.Name[0]; 72 const annotations = new Set(); 73 const funcInfo = { 74 name, 75 typedfield: typedField(info), 76 field: info, 77 annotations, 78 inherited: (info.FieldCSU.Type.Name != csuName), // Always false for virtual dtors 79 pureVirtual: Boolean(Variable), 80 dtor: false, 81 }; 82 83 if (Variable && isSyntheticVirtualDestructor(name)) { 84 // This is one of gcc's artificial dtors. 85 funcInfo.dtor = Variable.Name[0]; 86 funcInfo.pureVirtual = false; 87 } 88 89 addToNamedSet(virtualDeclarations, csuName, funcInfo); 90 if ('Annotation' in info) { 91 for (const {Name: [annType, annValue]} of info.Annotation) { 92 annotations.add([annType, annValue]); 93 } 94 } 95 96 if (Variable) { 97 // Note: not dealing with overloading correctly. 98 const name = Variable.Name[0]; 99 addToNamedSet(virtualDefinitions, fieldKey(csuName, Field[0]), name); 100 } 101 } 102 } 103 104 // Return a list of all callees that the given edge might be a call to. Each 105 // one is represented by an object with a 'kind' field that is one of 106 // ('direct', 'field', 'resolved-field', 'indirect', 'unknown'), though note 107 // that 'resolved-field' is really a global record of virtual method 108 // resolutions, indepedent of this particular edge. 109 function translateCallees(edge) 110 { 111 if (edge.Kind != "Call") 112 return []; 113 114 const callee = edge.Exp[0]; 115 if (callee.Kind == "Var") { 116 assert(callee.Variable.Kind == "Func"); 117 return [{'kind': 'direct', 'name': callee.Variable.Name[0]}]; 118 } 119 120 // At some point, we were intentionally invoking invalid function pointers 121 // (as in, a small integer cast to a function pointer type) to convey a 122 // small amount of information in the crash address. 123 if (callee.Kind == "Int") 124 return []; // Intentional crash 125 126 assert(callee.Kind == "Drf"); 127 let called = callee.Exp[0]; 128 let indirection = 1; 129 if (called.Kind == "Drf") { 130 // This is probably a reference to a function pointer (`func*&`). It 131 // would be possible to determine that for certain by looking up the 132 // variable's type, which is doable but unnecessary. Indirect calls 133 // are assumed to call anything (any function in the codebase) unless they 134 // are annotated otherwise, and the `funkyName` annotation applies to 135 // `(**funkyName)(args)` as well as `(*funkyName)(args)`, it's ok. 136 called = called.Exp[0]; 137 indirection += 1; 138 } 139 140 if (called.Kind == "Var") { 141 // indirect call through a variable. Note that the `indirection` field is 142 // currently unused by the later analysis. It is the number of dereferences 143 // applied to the variable before invoking the resulting function. 144 // 145 // The variable name passed through is the simplified one, since that is 146 // what annotations.js uses and we don't want the annotation to be missed 147 // if eg there is another variable of the same name in a sibling scope such 148 // that the fully decorated name no longer matches. 149 const [decorated, bare] = called.Variable.Name; 150 return [{'kind': "indirect", 'variable': bare, indirection}]; 151 } 152 153 if (called.Kind != "Fld") { 154 // unknown call target. 155 return [{'kind': "unknown"}]; 156 } 157 158 // Return one 'field' callee record giving the full description of what's 159 // happening here (which is either a virtual method call, or a call through 160 // a function pointer stored in a field), and then boil the call down to a 161 // synthetic function that incorporates both the name of the field and the 162 // static type of whatever you're calling the method on. Both refer to the 163 // same call; they're just different ways of describing it. 164 const callees = []; 165 const field = called.Field; 166 const staticCSU = getFieldCallInstanceCSU(edge, field); 167 callees.push({'kind': "field", 'csu': field.FieldCSU.Type.Name, staticCSU, 168 'field': field.Name[0], 'fieldKey': fieldKey(staticCSU, field), 169 'isVirtual': ("FieldInstanceFunction" in field)}); 170 callees.push({'kind': "direct", 'name': fieldKey(staticCSU, field)}); 171 172 return callees; 173 } 174 175 function getCallees(body, edge, scopeAttrs, functionBodies) { 176 const calls = []; 177 178 // getCallEdgeProperties can set the ATTR_REPLACED attribute, which 179 // means that the call in the edge has been replaced by zero or 180 // more edges to other functions. This is used when the original 181 // edge will end up calling through a function pointer or something 182 // (eg ~shared_ptr<T> calls a function pointer that can only be 183 // T::~T()). The original call edges are left in the graph in case 184 // they are useful for other purposes. 185 for (const callee of translateCallees(edge)) { 186 if (callee.kind != "direct") { 187 calls.push({ callee, attrs: scopeAttrs }); 188 } else { 189 const edgeInfo = getCallEdgeProperties(body, edge, callee.name, functionBodies); 190 for (const extra of (edgeInfo.extraCalls || [])) { 191 calls.push({ attrs: scopeAttrs | extra.attrs, callee: { name: extra.name, 'kind': "direct", } }); 192 } 193 calls.push({ callee, attrs: scopeAttrs | edgeInfo.attrs}); 194 } 195 } 196 197 return calls; 198 } 199 200 function loadTypes(type_xdb_filename) { 201 const xdb = xdbLibrary(); 202 xdb.open(type_xdb_filename); 203 204 const minStream = xdb.min_data_stream(); 205 const maxStream = xdb.max_data_stream(); 206 207 for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) { 208 const csu = xdb.read_key(csuIndex); 209 const data = xdb.read_entry(csu); 210 const json = JSON.parse(data.readString()); 211 processCSU(csu.readString(), json[0]); 212 213 xdb.free_string(csu); 214 xdb.free_string(data); 215 } 216 } 217 218 function loadTypesWithCache(type_xdb_filename, cache_filename) { 219 try { 220 const cacheAB = os.file.readFile(cache_filename, "binary"); 221 const cb = serialize(); 222 cb.clonebuffer = cacheAB.buffer; 223 const cacheData = deserialize(cb); 224 subclasses = cacheData.subclasses; 225 superclasses = cacheData.superclasses; 226 virtualDefinitions = cacheData.virtualDefinitions; 227 } catch (e) { 228 loadTypes(type_xdb_filename); 229 const cb = serialize({subclasses, superclasses, virtualDefinitions}); 230 os.file.writeTypedArrayToFile(cache_filename, 231 new Uint8Array(cb.arraybuffer)); 232 } 233 }