blob: 95a9710084a7c192891a7e5610983cbb911cfb17 [file] [log] [blame]
/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
/*jshint strict:false */
"use strict";
loadRelativeToScript('utility.js');
loadRelativeToScript('annotations.js');
loadRelativeToScript('CFG.js');
var theFunctionNameToFind;
if (scriptArgs[0] == '--function') {
theFunctionNameToFind = scriptArgs[1];
scriptArgs = scriptArgs.slice(2);
}
var typeInfo_filename = scriptArgs[0] || "typeInfo.txt";
var subclasses = new Map(); // Map from csu => set of immediate subclasses
var superclasses = new Map(); // Map from csu => set of immediate superclasses
var classFunctions = new Map(); // Map from "csu:name" => set of full method name
var virtualResolutionsSeen = new Set();
function addEntry(map, name, entry)
{
if (!map.has(name))
map.set(name, new Set());
map.get(name).add(entry);
}
// CSU is "Class/Struct/Union"
function processCSU(csuName, csu)
{
if (!("FunctionField" in csu))
return;
for (var field of csu.FunctionField) {
if (1 in field.Field) {
var superclass = field.Field[1].Type.Name;
var subclass = field.Field[1].FieldCSU.Type.Name;
assert(subclass == csuName);
addEntry(subclasses, superclass, subclass);
addEntry(superclasses, subclass, superclass);
}
if ("Variable" in field) {
// Note: not dealing with overloading correctly.
var name = field.Variable.Name[0];
var key = csuName + ":" + field.Field[0].Name[0];
addEntry(classFunctions, key, name);
}
}
}
// Return the nearest ancestor method definition, or all nearest definitions in
// the case of multiple inheritance.
function ancestorMethods(csu, method)
{
var key = csu + ":" + method;
if (classFunctions.has(key))
return new Set(classFunctions.get(key));
var functions = new Set();
if (superclasses.has(csu)) {
for (var parent of superclasses.get(csu))
functions.update(ancestorMethods(parent, method));
}
return functions;
}
function findVirtualFunctions(initialCSU, field, suppressed)
{
var worklist = [initialCSU];
var functions = new Set();
// Loop through all methods of initialCSU (by looking at all methods of ancestor csus).
//
// If field is nsISupports::AddRef or ::Release, return an empty list and
// turn on a 'suppressed' flag to notify the caller that....
//
// If this is a method that is annotated to be safe (as in, it is known
// that it will never be overridden with an implementation that could GC),
// then return null to indicate that calls through it need not be
// considered.
//
// Otherwise, continue through.
while (worklist.length) {
var csu = worklist.pop();
if (isSuppressedVirtualMethod(csu, field)) {
suppressed[0] = true;
return new Set();
}
if (isOverridableField(initialCSU, csu, field)) {
// We will still resolve the virtual function call, because it's
// nice to have as complete a callgraph as possible for other uses.
// But push a token saying that we can run arbitrary code.
functions.add(null);
}
if (superclasses.has(csu))
worklist.push(...superclasses.get(csu));
}
// Now return a list of all the instantiations of the method named 'field'
// in initialCSU, any of its descendants, or any of its ancestors.
functions.update(ancestorMethods(initialCSU, field));
var worklist = [initialCSU];
while (worklist.length) {
var csu = worklist.pop();
var key = csu + ":" + field;
if (classFunctions.has(key))
functions.update(classFunctions.get(key));
if (subclasses.has(csu))
worklist.push(...subclasses.get(csu));
}
return functions;
}
var memoized = new Map();
var memoizedCount = 0;
function memo(name)
{
if (!memoized.has(name)) {
let id = memoized.size + 1;
memoized.set(name, "" + id);
print(`#${id} ${name}`);
}
return memoized.get(name);
}
// Return a list of all callees that the given edge might be a call to. Each
// one is represented by an object with a 'kind' field that is one of
// ('direct', 'field', 'resolved-field', 'indirect', 'unknown'), though note
// that 'resolved-field' is really a global record of virtual method
// resolutions, indepedent of this particular edge.
function getCallees(edge)
{
if (edge.Kind != "Call")
return [];
var callee = edge.Exp[0];
var callees = [];
if (callee.Kind == "Var") {
assert(callee.Variable.Kind == "Func");
callees.push({'kind': 'direct', 'name': callee.Variable.Name[0]});
} else {
assert(callee.Kind == "Drf");
if (callee.Exp[0].Kind == "Fld") {
var field = callee.Exp[0].Field;
var fieldName = field.Name[0];
var csuName = field.FieldCSU.Type.Name;
var functions = null;
if ("FieldInstanceFunction" in field) {
var suppressed = [ false ];
functions = findVirtualFunctions(csuName, fieldName, suppressed);
if (suppressed[0]) {
// Field call known to not GC; mark it as suppressed so
// direct invocations will be ignored
callees.push({'kind': "field", 'csu': csuName, 'field': fieldName,
'suppressed': true});
}
}
if (functions) {
// Known set of virtual call targets. Treat them as direct
// calls to all possible resolved types, but also record edges
// from this field call to each final callee. When the analysis
// is checking whether an edge can GC and it sees an unrooted
// pointer held live across this field call, it will know
// whether any of the direct callees can GC or not.
var targets = [];
var fullyResolved = true;
for (var name of functions) {
if (name === null) {
// virtual call on an nsISupports object
callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
fullyResolved = false;
} else {
callees.push({'kind': "direct", 'name': name});
targets.push({'kind': "direct", 'name': name});
}
}
if (fullyResolved)
callees.push({'kind': "resolved-field", 'csu': csuName, 'field': fieldName, 'callees': targets});
} else {
// Unknown set of call targets. Non-virtual field call.
callees.push({'kind': "field", 'csu': csuName, 'field': fieldName});
}
} else if (callee.Exp[0].Kind == "Var") {
// indirect call through a variable.
callees.push({'kind': "indirect", 'variable': callee.Exp[0].Variable.Name[0]});
} else {
// unknown call target.
callees.push({'kind': "unknown"});
}
}
return callees;
}
var lastline;
function printOnce(line)
{
if (line != lastline) {
print(line);
lastline = line;
}
}
// Returns a table mapping function name to lists of [annotation-name,
// annotation-value] pairs: { function-name => [ [annotation-name, annotation-value] ] }
function getAnnotations(body)
{
var all_annotations = {};
for (var v of (body.DefineVariable || [])) {
if (v.Variable.Kind != 'Func')
continue;
var name = v.Variable.Name[0];
var annotations = all_annotations[name] = [];
for (var ann of (v.Type.Annotation || [])) {
annotations.push(ann.Name);
}
}
return all_annotations;
}
function getTags(functionName, body) {
var tags = new Set();
var annotations = getAnnotations(body);
if (functionName in annotations) {
for (var [ annName, annValue ] of annotations[functionName]) {
if (annName == 'Tag')
tags.add(annValue);
}
}
return tags;
}
function processBody(functionName, body)
{
if (!('PEdge' in body))
return;
for (var tag of getTags(functionName, body).values())
print("T " + memo(functionName) + " " + tag);
// Set of all callees that have been output so far, in order to suppress
// repeated callgraph edges from being recorded. Use a separate set for
// suppressed callees, since we don't want a suppressed edge (within one
// RAII scope) to prevent an unsuppressed edge from being recorded. The
// seen array is indexed by a boolean 'suppressed' variable.
var seen = [ new Set(), new Set() ];
lastline = null;
for (var edge of body.PEdge) {
if (edge.Kind != "Call")
continue;
// Whether this call is within the RAII scope of a GC suppression class
var edgeSuppressed = (edge.Index[0] in body.suppressed);
for (var callee of getCallees(edge)) {
var suppressed = Boolean(edgeSuppressed || callee.suppressed);
var prologue = suppressed ? "SUPPRESS_GC " : "";
prologue += memo(functionName) + " ";
if (callee.kind == 'direct') {
if (!seen[+suppressed].has(callee.name)) {
seen[+suppressed].add(callee.name);
printOnce("D " + prologue + memo(callee.name));
}
} else if (callee.kind == 'field') {
var { csu, field } = callee;
printOnce("F " + prologue + "CLASS " + csu + " FIELD " + field);
} else if (callee.kind == 'resolved-field') {
// Fully-resolved field (virtual method) call. Record the
// callgraph edges. Do not consider suppression, since it is
// local to this callsite and we are writing out a global
// record here.
//
// Any field call that does *not* have an R entry must be
// assumed to call anything.
var { csu, field, callees } = callee;
var fullFieldName = csu + "." + field;
if (!virtualResolutionsSeen.has(fullFieldName)) {
virtualResolutionsSeen.add(fullFieldName);
for (var target of callees)
printOnce("R " + memo(fullFieldName) + " " + memo(target.name));
}
} else if (callee.kind == 'indirect') {
printOnce("I " + prologue + "VARIABLE " + callee.variable);
} else if (callee.kind == 'unknown') {
printOnce("I " + prologue + "VARIABLE UNKNOWN");
} else {
printErr("invalid " + callee.kind + " callee");
debugger;
}
}
}
}
GCSuppressionTypes = loadTypeInfo(typeInfo_filename)["Suppress GC"];
var xdb = xdbLibrary();
xdb.open("src_comp.xdb");
var minStream = xdb.min_data_stream();
var maxStream = xdb.max_data_stream();
for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) {
var csu = xdb.read_key(csuIndex);
var data = xdb.read_entry(csu);
var json = JSON.parse(data.readString());
processCSU(csu.readString(), json[0]);
xdb.free_string(csu);
xdb.free_string(data);
}
xdb.open("src_body.xdb");
printErr("Finished loading data structures");
var minStream = xdb.min_data_stream();
var maxStream = xdb.max_data_stream();
if (theFunctionNameToFind) {
var index = xdb.lookup_key(theFunctionNameToFind);
if (!index) {
printErr("Function not found");
quit(1);
}
minStream = maxStream = index;
}
function process(functionName, functionBodies)
{
for (var body of functionBodies)
body.suppressed = [];
for (var body of functionBodies) {
for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
pbody.suppressed[id] = true;
}
for (var body of functionBodies)
processBody(functionName, body);
// GCC generates multiple constructors and destructors ("in-charge" and
// "not-in-charge") to handle virtual base classes. They are normally
// identical, and it appears that GCC does some magic to alias them to the
// same thing. But this aliasing is not visible to the analysis. So we'll
// add a dummy call edge from "foo" -> "foo *INTERNAL* ", since only "foo"
// will show up as called but only "foo *INTERNAL* " will be emitted in the
// case where the constructors are identical.
//
// This is slightly conservative in the case where they are *not*
// identical, but that should be rare enough that we don't care.
var markerPos = functionName.indexOf(internalMarker);
if (markerPos > 0) {
var inChargeXTor = functionName.replace(internalMarker, "");
print("D " + memo(inChargeXTor) + " " + memo(functionName));
// Bug 1056410: Oh joy. GCC does something even funkier internally,
// where it generates calls to ~Foo() but a body for ~Foo(int32) even
// though it uses the same mangled name for both. So we need to add a
// synthetic edge from ~Foo() -> ~Foo(int32).
//
// inChargeXTor will have the (int32).
if (functionName.indexOf("::~") > 0) {
var calledDestructor = inChargeXTor.replace("(int32)", "()");
print("D " + memo(calledDestructor) + " " + memo(inChargeXTor));
}
}
// Further note: from http://mentorembedded.github.io/cxx-abi/abi.html the
// different kinds of constructors/destructors are:
// C1 # complete object constructor
// C2 # base object constructor
// C3 # complete object allocating constructor
// D0 # deleting destructor
// D1 # complete object destructor
// D2 # base object destructor
//
// In actual practice, I have observed C4 and D4 xtors generated by gcc
// 4.9.3 (but not 4.7.3). The gcc source code says:
//
// /* This is the old-style "[unified]" constructor.
// In some cases, we may emit this function and call
// it from the clones in order to share code and save space. */
//
// Unfortunately, that "call... from the clones" does not seem to appear in
// the CFG we get from GCC. So if we see a C4 constructor or D4 destructor,
// inject an edge to it from C1, C2, and C3 (or D1, D2, and D3). (Note that
// C3 isn't even used in current GCC, but add the edge anyway just in
// case.)
if (functionName.indexOf("C4E") != -1 || functionName.indexOf("D4Ev") != -1) {
var [ mangled, unmangled ] = splitFunction(functionName);
// E terminates the method name (and precedes the method parameters).
// If eg "C4E" shows up in the mangled name for another reason, this
// will create bogus edges in the callgraph. But will affect little and
// is somewhat difficult to avoid, so we will live with it.
for (let [synthetic, variant] of [['C4E', 'C1E'],
['C4E', 'C2E'],
['C4E', 'C3E'],
['D4Ev', 'D1Ev'],
['D4Ev', 'D2Ev'],
['D4Ev', 'D3Ev']])
{
if (mangled.indexOf(synthetic) == -1)
continue;
let variant_mangled = mangled.replace(synthetic, variant);
let variant_full = variant_mangled + "$" + unmangled;
print("D " + memo(variant_full) + " " + memo(functionName));
// Record the fact that we may have generated an alias function, so
// that we can emit it into gcFunctions.txt even though there is no
// definition of it in src_body.xdb.
print("A " + memo(variant_full) + " " + memo(functionName));
}
}
}
for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
var name = xdb.read_key(nameIndex);
var data = xdb.read_entry(name);
process(name.readString(), JSON.parse(data.readString()));
xdb.free_string(name);
xdb.free_string(data);
}