diff options
Diffstat (limited to 'js/src/devtools/rootAnalysis/utility.js')
-rw-r--r-- | js/src/devtools/rootAnalysis/utility.js | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/js/src/devtools/rootAnalysis/utility.js b/js/src/devtools/rootAnalysis/utility.js new file mode 100644 index 0000000000..5ec8c3e961 --- /dev/null +++ b/js/src/devtools/rootAnalysis/utility.js @@ -0,0 +1,422 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ + +"use strict"; + +loadRelativeToScript('dumpCFG.js'); + +// Attribute bits - each call edge may carry a set of 'attrs' bits, saying eg +// that the edge takes place within a scope where GC is suppressed, for +// example. +var ATTR_GC_SUPPRESSED = 1 << 0; +var ATTR_CANSCRIPT_BOUNDED = 1 << 1; // Unimplemented +var ATTR_DOM_ITERATING = 1 << 2; // Unimplemented +var ATTR_NONRELEASING = 1 << 3; // ~RefPtr of value whose refcount will not go to zero +var ATTR_REPLACED = 1 << 4; // Ignore edge, it was replaced by zero or more better edges. +var ATTR_SYNTHETIC = 1 << 5; // Call was manufactured in some way. + +var ATTR_LAST = 1 << 5; +var ATTRS_NONE = 0; +var ATTRS_ALL = (ATTR_LAST << 1) - 1; // All possible bits set + +// The traversal algorithms we run will recurse into children if you change any +// attrs bit to zero. Use all bits set to maximally attributed, including +// additional bits that all just mean "unvisited", so that the first time we +// see a node with this attrs, we're guaranteed to turn at least one bit off +// and thereby keep going. +var ATTRS_UNVISITED = 0xffff; + +// gcc appends this to mangled function names for "not in charge" +// constructors/destructors. +var internalMarker = " *INTERNAL* "; + +if (! Set.prototype.hasOwnProperty("update")) { + Object.defineProperty(Set.prototype, "update", { + value: function (collection) { + for (let elt of collection) + this.add(elt); + } + }); +} + +function assert(x, msg) +{ + if (x) + return; + debugger; + if (msg) + throw new Error("assertion failed: " + msg + "\n"); + else + throw new Error("assertion failed"); +} + +function defined(x) { + return x !== undefined; +} + +function xprint(x, padding) +{ + if (!padding) + padding = ""; + if (x instanceof Array) { + print(padding + "["); + for (var elem of x) + xprint(elem, padding + " "); + print(padding + "]"); + } else if (x instanceof Object) { + print(padding + "{"); + for (var prop in x) { + print(padding + " " + prop + ":"); + xprint(x[prop], padding + " "); + } + print(padding + "}"); + } else { + print(padding + x); + } +} + +// Command-line argument parser. +// +// `parameters` is a dict of parameters specs, each of which is a dict with keys: +// +// - name: name of option, prefixed with "--" if it is named (otherwise, it +// is interpreted as a positional parameter.) +// - dest: key to store the result in, defaulting to the parameter name without +// any leading "--"" and with dashes replaced with underscores. +// - default: value of option if no value is given. Positional parameters with +// a default value are optional. If no default is given, the parameter's name +// is not included in the return value. +// - type: `bool` if it takes no argument, otherwise an argument is required. +// Named arguments default to 'bool', positional arguments to 'string'. +// - nargs: the only supported value is `+`, which means to grab all following +// arguments, up to the next named option, and store them as a list. +// +// The command line is parsed for `--foo=value` and `--bar` arguments. +// +// Return value is a dict of parameter values, keyed off of `dest` as determined +// above. An extra option named "rest" will be set to the list of all remaining +// arguments passed in. +// +function parse_options(parameters, inArgs = scriptArgs) { + const options = {}; + + const named = {}; + const positional = []; + for (const param of parameters) { + if (param.name.startsWith("-")) { + named[param.name] = param; + if (!param.dest) { + if (!param.name.startsWith("--")) { + throw new Error(`parameter '${param.name}' requires param.dest to be set`); + } + param.dest = param.name.substring(2).replace("-", "_"); + } + } else { + if (!('default' in param) && positional.length > 0 && ('default' in positional.at(-1))) { + throw new Error(`required parameter '${param.name}' follows optional parameter`); + } + param.positional = true; + positional.push(param); + param.dest = param.dest || param.name.replace("-", "_"); + } + + if (!param.type) { + if (param.nargs === "+") { + param.type = "list"; + } else if (param.positional) { + param.type = "string"; + } else { + param.type = "bool"; + } + } + + if ('default' in param) { + options[param.dest] = param.default; + } + } + + options.rest = []; + const args = [...inArgs]; + let grabbing_into = undefined; + while (args.length > 0) { + let arg = args.shift(); + let param; + if (arg.startsWith("-") && arg in named) { + param = named[arg]; + if (param.type !== 'bool') { + if (args.length == 0) { + throw(new Error(`${param.name} requires an argument`)); + } + arg = args.shift(); + } + } else { + const pos = arg.indexOf("="); + if (pos != -1) { + const name = arg.substring(0, pos); + param = named[name]; + if (!param) { + throw(new Error(`Unknown option '${name}'`)); + } else if (param.type === 'bool') { + throw(new Error(`--${param.name} does not take an argument`)); + } + arg = arg.substring(pos + 1); + } + } + + // If this isn't a --named param, and we're not accumulating into a nargs="+" param, then + // use the next positional. + if (!param && !grabbing_into && positional.length > 0) { + param = positional.shift(); + } + + // If a parameter was identified, then any old accumulator is done and we might start a new one. + if (param) { + if (param.type === 'list') { + grabbing_into = options[param.dest] = options[param.dest] || []; + } else { + grabbing_into = undefined; + } + } + + if (grabbing_into) { + grabbing_into.push(arg); + } else if (param) { + if (param.type === 'bool') { + options[param.dest] = true; + } else { + options[param.dest] = arg; + } + } else { + options.rest.push(arg); + } + } + + for (const param of positional) { + if (!('default' in param)) { + throw(new Error(`'${param.name}' option is required`)); + } + } + + for (const param of parameters) { + if (param.nargs === '+' && options[param.dest].length == 0) { + throw(new Error(`at least one value required for option '${param.name}'`)); + } + } + + return options; +} + +function sameBlockId(id0, id1) +{ + if (id0.Kind != id1.Kind) + return false; + if (!sameVariable(id0.Variable, id1.Variable)) + return false; + if (id0.Kind == "Loop" && id0.Loop != id1.Loop) + return false; + return true; +} + +function sameVariable(var0, var1) +{ + assert("Name" in var0 || var0.Kind == "This" || var0.Kind == "Return"); + assert("Name" in var1 || var1.Kind == "This" || var1.Kind == "Return"); + if ("Name" in var0) + return "Name" in var1 && var0.Name[0] == var1.Name[0]; + return var0.Kind == var1.Kind; +} + +function blockIdentifier(body) +{ + if (body.BlockId.Kind == "Loop") + return body.BlockId.Loop; + assert(body.BlockId.Kind == "Function", "body.Kind should be Function, not " + body.BlockId.Kind); + return body.BlockId.Variable.Name[0]; +} + +function collectBodyEdges(body) +{ + body.predecessors = []; + body.successors = []; + if (!("PEdge" in body)) + return; + + for (var edge of body.PEdge) { + var [ source, target ] = edge.Index; + if (!(target in body.predecessors)) + body.predecessors[target] = []; + body.predecessors[target].push(edge); + if (!(source in body.successors)) + body.successors[source] = []; + body.successors[source].push(edge); + } +} + +function getPredecessors(body) +{ + if (!('predecessors' in body)) + collectBodyEdges(body); + return body.predecessors; +} + +function getSuccessors(body) +{ + if (!('successors' in body)) + collectBodyEdges(body); + return body.successors; +} + +// Split apart a function from sixgill into its mangled and unmangled name. If +// no mangled name was given, use the unmangled name as its mangled name +function splitFunction(func) +{ + var split = func.indexOf("$"); + if (split != -1) + return [ func.substr(0, split), func.substr(split+1) ]; + split = func.indexOf("|"); + if (split != -1) + return [ func.substr(0, split), func.substr(split+1) ]; + return [ func, func ]; +} + +function mangled(fullname) +{ + var [ mangled, unmangled ] = splitFunction(fullname); + return mangled; +} + +function readable(fullname) +{ + var [ mangled, unmangled ] = splitFunction(fullname); + return unmangled; +} + +function xdbLibrary() +{ + var lib = ctypes.open(os.getenv('XDB')); + var api = { + open: lib.declare("xdb_open", ctypes.default_abi, ctypes.void_t, ctypes.char.ptr), + min_data_stream: lib.declare("xdb_min_data_stream", ctypes.default_abi, ctypes.int), + max_data_stream: lib.declare("xdb_max_data_stream", ctypes.default_abi, ctypes.int), + read_key: lib.declare("xdb_read_key", ctypes.default_abi, ctypes.char.ptr, ctypes.int), + read_entry: lib.declare("xdb_read_entry", ctypes.default_abi, ctypes.char.ptr, ctypes.char.ptr), + free_string: lib.declare("xdb_free", ctypes.default_abi, ctypes.void_t, ctypes.char.ptr) + }; + try { + api.lookup_key = lib.declare("xdb_lookup_key", ctypes.default_abi, ctypes.int, ctypes.char.ptr); + } catch (e) { + // lookup_key is for development use only and is not strictly necessary. + } + return api; +} + +function openLibrary(names) { + for (const name of names) { + try { + return ctypes.open(name); + } catch(e) { + } + } + return undefined; +} + +function cLibrary() +{ + const lib = openLibrary(['libc.so.6', 'libc.so', 'libc.dylib']); + if (!lib) { + throw new Error("Unable to open libc"); + } + + if (getBuildConfiguration()["moz-memory"]) { + throw new Error("cannot use libc functions with --enable-jemalloc, since they will be routed " + + "through jemalloc, but calling libc.free() directly will bypass it and the " + + "malloc/free will be mismatched"); + } + + return { + fopen: lib.declare("fopen", ctypes.default_abi, ctypes.void_t.ptr, ctypes.char.ptr, ctypes.char.ptr), + getline: lib.declare("getline", ctypes.default_abi, ctypes.ssize_t, ctypes.char.ptr.ptr, ctypes.size_t.ptr, ctypes.void_t.ptr), + fclose: lib.declare("fclose", ctypes.default_abi, ctypes.int, ctypes.void_t.ptr), + free: lib.declare("free", ctypes.default_abi, ctypes.void_t, ctypes.void_t.ptr), + }; +} + +function* readFileLines_gen(filename) +{ + var libc = cLibrary(); + var linebuf = ctypes.char.ptr(); + var bufsize = ctypes.size_t(0); + var fp = libc.fopen(filename, "r"); + if (fp.isNull()) + throw new Error("Unable to open '" + filename + "'"); + + while (libc.getline(linebuf.address(), bufsize.address(), fp) > 0) + yield linebuf.readString(); + libc.fclose(fp); + libc.free(ctypes.void_t.ptr(linebuf)); +} + +function addToKeyedList(collection, key, entry) +{ + if (!(key in collection)) + collection[key] = []; + collection[key].push(entry); + return collection[key]; +} + +function addToMappedList(map, key, entry) +{ + if (!map.has(key)) + map.set(key, []); + map.get(key).push(entry); + return map.get(key); +} + +function loadTypeInfo(filename) +{ + return JSON.parse(os.file.readFile(filename)); +} + +// Given the range `first` .. `last`, break it down into `count` batches and +// return the start of the (1-based) `num` batch. +function batchStart(num, count, first, last) { + const N = (last - first) + 1; + return Math.floor((num - 1) / count * N) + first; +} + +// As above, but return the last value in the (1-based) `num` batch. +function batchLast(num, count, first, last) { + const N = (last - first) + 1; + return Math.floor(num / count * N) + first - 1; +} + +// Debugging tool. See usage below. +function PropertyTracer(traced_prop, check) { + return { + matches(prop, value) { + if (prop != traced_prop) + return false; + if ('value' in check) + return value == check.value; + return true; + }, + + // Also called when defining a property. + set(obj, prop, value) { + if (this.matches(prop, value)) + debugger; + return Reflect.set(...arguments); + }, + }; +} + +// Usage: var myobj = traced({}, 'name', {value: 'Bob'}) +// +// This will execute a `debugger;` statement when myobj['name'] is defined or +// set to 'Bob'. +function traced(obj, traced_prop, check) { + return new Proxy(obj, PropertyTracer(traced_prop, check)); +} |