utility.js (13621B)
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 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ 6 7 "use strict"; 8 9 loadRelativeToScript('dumpCFG.js'); 10 11 // Attribute bits - each call edge may carry a set of 'attrs' bits, saying eg 12 // that the edge takes place within a scope where GC is suppressed, for 13 // example. 14 var ATTR_GC_SUPPRESSED = 1 << 0; 15 var ATTR_CANSCRIPT_BOUNDED = 1 << 1; // Unimplemented 16 var ATTR_DOM_ITERATING = 1 << 2; // Unimplemented 17 var ATTR_NONRELEASING = 1 << 3; // ~RefPtr of value whose refcount will not go to zero 18 var ATTR_REPLACED = 1 << 4; // Ignore edge, it was replaced by zero or more better edges. 19 var ATTR_SYNTHETIC = 1 << 5; // Call was manufactured in some way. 20 21 var ATTR_LAST = 1 << 5; 22 var ATTRS_NONE = 0; 23 var ATTRS_ALL = (ATTR_LAST << 1) - 1; // All possible bits set 24 25 // The traversal algorithms we run will recurse into children if you change any 26 // attrs bit to zero. Use all bits set to maximally attributed, including 27 // additional bits that all just mean "unvisited", so that the first time we 28 // see a node with this attrs, we're guaranteed to turn at least one bit off 29 // and thereby keep going. 30 var ATTRS_UNVISITED = 0xffff; 31 32 // gcc appends this to mangled function names for "not in charge" 33 // constructors/destructors. 34 var internalMarker = " *INTERNAL* "; 35 36 if (! Set.prototype.hasOwnProperty("update")) { 37 Object.defineProperty(Set.prototype, "update", { 38 value: function (collection) { 39 for (let elt of collection) 40 this.add(elt); 41 } 42 }); 43 } 44 45 function assert(x, msg) 46 { 47 if (x) 48 return; 49 debugger; 50 if (msg) 51 throw new Error("assertion failed: " + msg + "\n"); 52 else 53 throw new Error("assertion failed"); 54 } 55 56 function defined(x) { 57 return x !== undefined; 58 } 59 60 function xprint(x, padding) 61 { 62 if (!padding) 63 padding = ""; 64 if (x instanceof Array) { 65 print(padding + "["); 66 for (var elem of x) 67 xprint(elem, padding + " "); 68 print(padding + "]"); 69 } else if (x instanceof Object) { 70 print(padding + "{"); 71 for (var prop in x) { 72 print(padding + " " + prop + ":"); 73 xprint(x[prop], padding + " "); 74 } 75 print(padding + "}"); 76 } else { 77 print(padding + x); 78 } 79 } 80 81 // Command-line argument parser. 82 // 83 // `parameters` is a dict of parameters specs, each of which is a dict with keys: 84 // 85 // - name: name of option, prefixed with "--" if it is named (otherwise, it 86 // is interpreted as a positional parameter.) 87 // - dest: key to store the result in, defaulting to the parameter name without 88 // any leading "--"" and with dashes replaced with underscores. 89 // - default: value of option if no value is given. Positional parameters with 90 // a default value are optional. If no default is given, the parameter's name 91 // is not included in the return value. 92 // - type: `bool` if it takes no argument, otherwise an argument is required. 93 // Named arguments default to 'bool', positional arguments to 'string'. 94 // - nargs: the only supported value is `+`, which means to grab all following 95 // arguments, up to the next named option, and store them as a list. 96 // 97 // The command line is parsed for `--foo=value` and `--bar` arguments. 98 // 99 // Return value is a dict of parameter values, keyed off of `dest` as determined 100 // above. An extra option named "rest" will be set to the list of all remaining 101 // arguments passed in. 102 // 103 function parse_options(parameters, inArgs = scriptArgs) { 104 const options = {}; 105 106 const named = {}; 107 const positional = []; 108 for (const param of parameters) { 109 if (param.name.startsWith("-")) { 110 named[param.name] = param; 111 if (!param.dest) { 112 if (!param.name.startsWith("--")) { 113 throw new Error(`parameter '${param.name}' requires param.dest to be set`); 114 } 115 param.dest = param.name.substring(2).replace("-", "_"); 116 } 117 } else { 118 if (!('default' in param) && positional.length > 0 && ('default' in positional.at(-1))) { 119 throw new Error(`required parameter '${param.name}' follows optional parameter`); 120 } 121 param.positional = true; 122 positional.push(param); 123 param.dest = param.dest || param.name.replace("-", "_"); 124 } 125 126 if (!param.type) { 127 if (param.nargs === "+") { 128 param.type = "list"; 129 } else if (param.positional) { 130 param.type = "string"; 131 } else { 132 param.type = "bool"; 133 } 134 } 135 136 if ('default' in param) { 137 options[param.dest] = param.default; 138 } 139 } 140 141 options.rest = []; 142 const args = [...inArgs]; 143 let grabbing_into = undefined; 144 while (args.length > 0) { 145 let arg = args.shift(); 146 let param; 147 if (arg.startsWith("-") && arg in named) { 148 param = named[arg]; 149 if (param.type !== 'bool') { 150 if (args.length == 0) { 151 throw(new Error(`${param.name} requires an argument`)); 152 } 153 arg = args.shift(); 154 } 155 } else { 156 const pos = arg.indexOf("="); 157 if (pos != -1) { 158 const name = arg.substring(0, pos); 159 param = named[name]; 160 if (!param) { 161 throw(new Error(`Unknown option '${name}'`)); 162 } else if (param.type === 'bool') { 163 throw(new Error(`--${param.name} does not take an argument`)); 164 } 165 arg = arg.substring(pos + 1); 166 } 167 } 168 169 // If this isn't a --named param, and we're not accumulating into a nargs="+" param, then 170 // use the next positional. 171 if (!param && !grabbing_into && positional.length > 0) { 172 param = positional.shift(); 173 } 174 175 // If a parameter was identified, then any old accumulator is done and we might start a new one. 176 if (param) { 177 if (param.type === 'list') { 178 grabbing_into = options[param.dest] = options[param.dest] || []; 179 } else { 180 grabbing_into = undefined; 181 } 182 } 183 184 if (grabbing_into) { 185 grabbing_into.push(arg); 186 } else if (param) { 187 if (param.type === 'bool') { 188 options[param.dest] = true; 189 } else { 190 options[param.dest] = arg; 191 } 192 } else { 193 options.rest.push(arg); 194 } 195 } 196 197 for (const param of positional) { 198 if (!('default' in param)) { 199 throw(new Error(`'${param.name}' option is required`)); 200 } 201 } 202 203 for (const param of parameters) { 204 if (param.nargs === '+' && options[param.dest].length == 0) { 205 throw(new Error(`at least one value required for option '${param.name}'`)); 206 } 207 } 208 209 return options; 210 } 211 212 function sameBlockId(id0, id1) 213 { 214 if (id0.Kind != id1.Kind) 215 return false; 216 if (!sameVariable(id0.Variable, id1.Variable)) 217 return false; 218 if (id0.Kind == "Loop" && id0.Loop != id1.Loop) 219 return false; 220 return true; 221 } 222 223 function sameVariable(var0, var1) 224 { 225 assert("Name" in var0 || var0.Kind == "This" || var0.Kind == "Return"); 226 assert("Name" in var1 || var1.Kind == "This" || var1.Kind == "Return"); 227 if ("Name" in var0) 228 return "Name" in var1 && var0.Name[0] == var1.Name[0]; 229 return var0.Kind == var1.Kind; 230 } 231 232 function blockIdentifier(body) 233 { 234 if (body.BlockId.Kind == "Loop") 235 return body.BlockId.Loop; 236 assert(body.BlockId.Kind == "Function", "body.Kind should be Function, not " + body.BlockId.Kind); 237 return body.BlockId.Variable.Name[0]; 238 } 239 240 function collectBodyEdges(body) 241 { 242 body.predecessors = []; 243 body.successors = []; 244 if (!("PEdge" in body)) 245 return; 246 247 for (var edge of body.PEdge) { 248 var [ source, target ] = edge.Index; 249 if (!(target in body.predecessors)) 250 body.predecessors[target] = []; 251 body.predecessors[target].push(edge); 252 if (!(source in body.successors)) 253 body.successors[source] = []; 254 body.successors[source].push(edge); 255 } 256 } 257 258 function getPredecessors(body) 259 { 260 if (!('predecessors' in body)) 261 collectBodyEdges(body); 262 return body.predecessors; 263 } 264 265 function getSuccessors(body) 266 { 267 if (!('successors' in body)) 268 collectBodyEdges(body); 269 return body.successors; 270 } 271 272 // Split apart a function from sixgill into its mangled and unmangled name. If 273 // no mangled name was given, use the unmangled name as its mangled name 274 function splitFunction(func) 275 { 276 var split = func.indexOf("$"); 277 if (split != -1) 278 return [ func.substr(0, split), func.substr(split+1) ]; 279 split = func.indexOf("|"); 280 if (split != -1) 281 return [ func.substr(0, split), func.substr(split+1) ]; 282 return [ func, func ]; 283 } 284 285 function mangled(fullname) 286 { 287 var [ mangled, unmangled ] = splitFunction(fullname); 288 return mangled; 289 } 290 291 function readable(fullname) 292 { 293 var [ mangled, unmangled ] = splitFunction(fullname); 294 return unmangled; 295 } 296 297 function xdbLibrary() 298 { 299 var lib = ctypes.open(os.getenv('XDB')); 300 var api = { 301 open: lib.declare("xdb_open", ctypes.default_abi, ctypes.void_t, ctypes.char.ptr), 302 min_data_stream: lib.declare("xdb_min_data_stream", ctypes.default_abi, ctypes.int), 303 max_data_stream: lib.declare("xdb_max_data_stream", ctypes.default_abi, ctypes.int), 304 read_key: lib.declare("xdb_read_key", ctypes.default_abi, ctypes.char.ptr, ctypes.int), 305 read_entry: lib.declare("xdb_read_entry", ctypes.default_abi, ctypes.char.ptr, ctypes.char.ptr), 306 free_string: lib.declare("xdb_free", ctypes.default_abi, ctypes.void_t, ctypes.char.ptr) 307 }; 308 try { 309 api.lookup_key = lib.declare("xdb_lookup_key", ctypes.default_abi, ctypes.int, ctypes.char.ptr); 310 } catch (e) { 311 // lookup_key is for development use only and is not strictly necessary. 312 } 313 return api; 314 } 315 316 function openLibrary(names) { 317 for (const name of names) { 318 try { 319 return ctypes.open(name); 320 } catch(e) { 321 } 322 } 323 return undefined; 324 } 325 326 function cLibrary() 327 { 328 const lib = openLibrary(['libc.so.6', 'libc.so', 'libc.dylib']); 329 if (!lib) { 330 throw new Error("Unable to open libc"); 331 } 332 333 if (getBuildConfiguration("moz-memory")) { 334 throw new Error("cannot use libc functions with --enable-jemalloc, since they will be routed " + 335 "through jemalloc, but calling libc.free() directly will bypass it and the " + 336 "malloc/free will be mismatched"); 337 } 338 339 return { 340 fopen: lib.declare("fopen", ctypes.default_abi, ctypes.void_t.ptr, ctypes.char.ptr, ctypes.char.ptr), 341 getline: lib.declare("getline", ctypes.default_abi, ctypes.ssize_t, ctypes.char.ptr.ptr, ctypes.size_t.ptr, ctypes.void_t.ptr), 342 fclose: lib.declare("fclose", ctypes.default_abi, ctypes.int, ctypes.void_t.ptr), 343 free: lib.declare("free", ctypes.default_abi, ctypes.void_t, ctypes.void_t.ptr), 344 }; 345 } 346 347 function* readFileLines_gen(filename) 348 { 349 var libc = cLibrary(); 350 var linebuf = ctypes.char.ptr(); 351 var bufsize = ctypes.size_t(0); 352 var fp = libc.fopen(filename, "r"); 353 if (fp.isNull()) 354 throw new Error("Unable to open '" + filename + "'"); 355 356 while (libc.getline(linebuf.address(), bufsize.address(), fp) > 0) 357 yield linebuf.readString(); 358 libc.fclose(fp); 359 libc.free(ctypes.void_t.ptr(linebuf)); 360 } 361 362 function addToKeyedList(collection, key, entry) 363 { 364 if (!(key in collection)) 365 collection[key] = []; 366 collection[key].push(entry); 367 return collection[key]; 368 } 369 370 function addToMappedList(map, key, entry) 371 { 372 if (!map.has(key)) 373 map.set(key, []); 374 map.get(key).push(entry); 375 return map.get(key); 376 } 377 378 function loadTypeInfo(filename) 379 { 380 return JSON.parse(os.file.readFile(filename)); 381 } 382 383 // Given the range `first` .. `last`, break it down into `count` batches and 384 // return the start of the (1-based) `num` batch. 385 function batchStart(num, count, first, last) { 386 const N = (last - first) + 1; 387 return Math.floor((num - 1) / count * N) + first; 388 } 389 390 // As above, but return the last value in the (1-based) `num` batch. 391 function batchLast(num, count, first, last) { 392 const N = (last - first) + 1; 393 return Math.floor(num / count * N) + first - 1; 394 } 395 396 // Debugging tool. See usage below. 397 function PropertyTracer(traced_prop, check) { 398 return { 399 matches(prop, value) { 400 if (prop != traced_prop) 401 return false; 402 if ('value' in check) 403 return value == check.value; 404 return true; 405 }, 406 407 // Also called when defining a property. 408 set(obj, prop, value) { 409 if (this.matches(prop, value)) 410 debugger; 411 return Reflect.set(...arguments); 412 }, 413 }; 414 } 415 416 // Usage: var myobj = traced({}, 'name', {value: 'Bob'}) 417 // 418 // This will execute a `debugger;` statement when myobj['name'] is defined or 419 // set to 'Bob'. 420 function traced(obj, traced_prop, check) { 421 return new Proxy(obj, PropertyTracer(traced_prop, check)); 422 }