summaryrefslogtreecommitdiffstats
path: root/js/src/devtools/rootAnalysis/utility.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/devtools/rootAnalysis/utility.js')
-rw-r--r--js/src/devtools/rootAnalysis/utility.js422
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));
+}