Bug 950176 - Use mangled names to identify nodes in callgraph, r=bhackett

This commit is contained in:
Steve Fink 2013-12-17 11:21:41 -08:00
parent 7215be83c0
commit 9bda717046
9 changed files with 137 additions and 73 deletions

View File

@ -90,8 +90,8 @@ function isMatchingConstructor(destructor, edge)
var variable = callee.Variable;
if (variable.Kind != "Func")
return false;
var name = variable.Name[0];
var destructorName = destructor.Exp[0].Variable.Name[0];
var name = readable(variable.Name[0]);
var destructorName = readable(destructor.Exp[0].Variable.Name[0]);
var match = destructorName.match(/^(.*?::)~(\w+)\(/);
if (!match) {
printErr("Unhandled destructor syntax: " + destructorName);

View File

@ -220,6 +220,11 @@ for k,v in vars(args).items():
if args.tag and not args.buildcommand:
args.buildcommand="build.%s" % args.tag
if args.jobs is not None:
data['jobs'] = args.jobs
if not data.get('jobs'):
data['jobs'] = subprocess.check_output(['nproc', '--ignore=1'])
if args.buildcommand:
data['buildcommand'] = args.buildcommand
elif 'BUILD' in os.environ:
@ -232,6 +237,9 @@ if 'ANALYZED_OBJDIR' in os.environ:
if 'SOURCE' in os.environ:
data['source'] = os.environ['SOURCE']
if not data.get('source') and data.get('sixgill_bin'):
path = subprocess.check_output(['sh', '-c', data['sixgill_bin'] + '/xdbkeys file_source.xdb | grep jsapi.cpp'])
data['source'] = path.replace("/js/src/jsapi.cpp", "")
steps = [ 'dbs',
'callgraph',

View File

@ -24,9 +24,8 @@ var tmpfile = scriptArgs[6] || "tmp.txt";
var gcFunctions = {};
var text = snarf("gcFunctions.lst").split("\n");
assert(text.pop().length == 0);
for (var line of text) {
gcFunctions[line] = true;
}
for (var line of text)
gcFunctions[mangled(line)] = true;
var suppressedFunctions = {};
var text = snarf(suppressedFunctionsFile).split("\n");
@ -190,7 +189,7 @@ function edgeKillsVariable(edge, variable)
break;
assert(callee.Variable.Kind == "Func");
var calleeName = callee.Variable.Name[0];
var calleeName = readable(callee.Variable.Name[0]);
// Constructor calls include the text 'Name::Name(' or 'Name<...>::Name('.
var openParen = calleeName.indexOf('(');
@ -224,11 +223,9 @@ function edgeCanGC(edge)
if (callee.Kind == "Var") {
var variable = callee.Variable;
assert(variable.Kind == "Func");
if (variable.Name[0] in gcFunctions)
var callee = mangled(variable.Name[0]);
if (callee in gcFunctions)
return "'" + variable.Name[0] + "'";
var otherName = otherDestructorName(variable.Name[0]);
if (otherName in gcFunctions)
return "'" + otherName + "'";
return null;
}
assert(callee.Kind == "Drf");
@ -241,8 +238,8 @@ function edgeCanGC(edge)
return (fullFieldName in suppressedFunctions) ? null : fullFieldName;
}
assert(callee.Exp[0].Kind == "Var");
var calleeName = callee.Exp[0].Variable.Name[0];
return indirectCallCannotGC(functionName, calleeName) ? null : "*" + calleeName;
var varName = callee.Exp[0].Variable.Name[0];
return indirectCallCannotGC(functionName, varName) ? null : "*" + varName;
}
function variableUseFollowsGC(suppressed, variable, worklist)
@ -364,6 +361,12 @@ function variableLiveAcrossGC(suppressed, variable)
return null;
}
// An unrooted variable has its address stored in another variable via
// assignment, or passed into a function that can GC. If the address is
// assigned into some other variable, we can't track it to see if it is held
// live across a GC. If it is passed into a function that can GC, then it's
// sort of like a Handle to an unrooted location, and the callee could GC
// before overwriting it or rooting it.
function unsafeVariableAddressTaken(suppressed, variable)
{
for (var body of functionBodies) {
@ -494,7 +497,7 @@ function processBodies(functionName)
{
if (!("DefineVariable" in functionBodies[0]))
return;
var suppressed = (functionName in suppressedFunctions);
var suppressed = (mangled(functionName) in suppressedFunctions);
for (var variable of functionBodies[0].DefineVariable) {
if (variable.Variable.Kind == "Return")
continue;

View File

@ -19,8 +19,16 @@ var ignoreIndirectCalls = {
"nsTraceRefcntImpl.cpp:void (* leakyLogRelease)(void*, int, int)": true,
};
function indirectCallCannotGC(caller, name)
function indirectCallCannotGC(fullCaller, fullVariable)
{
var caller = readable(fullCaller);
// This is usually a simple variable name, but sometimes a full name gets
// passed through. And sometimes that name is truncated. Examples:
// _ZL13gAbortHandler|mozalloc_oom.cpp:void (* gAbortHandler)(size_t)
// _ZL14pMutexUnlockFn|umutex.cpp:void (* pMutexUnlockFn)(const void*
var name = readable(fullVariable);
if (name in ignoreIndirectCalls)
return true;
@ -42,8 +50,7 @@ function indirectCallCannotGC(caller, name)
return true;
// template method called during marking and hence cannot GC
if (name == "op" &&
/^bool js::WeakMap<Key, Value, HashPolicy>::keyNeedsMark\(JSObject\*\)/.test(caller))
if (name == "op" && caller.indexOf("bool js::WeakMap<Key, Value, HashPolicy>::keyNeedsMark(JSObject*)") != -1)
{
return true;
}
@ -80,6 +87,7 @@ var ignoreCallees = {
"mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
"nsIThreadManager.GetIsMainThread" : true,
"PLDHashTableOps.hashKey" : true,
"z_stream_s.zfree" : true,
};
function fieldCallCannotGC(csu, fullfield)
@ -91,16 +99,6 @@ function fieldCallCannotGC(csu, fullfield)
return false;
}
function shouldSuppressGC(name)
{
// Various dead code that should only be called inside AutoEnterAnalysis.
// Functions with no known caller are by default treated as not suppressing GC.
return /TypeScript::Purge/.test(name)
|| /StackTypeSet::addPropagateThis/.test(name)
|| /ScriptAnalysis::addPushedType/.test(name)
|| /IonBuilder/.test(name);
}
function ignoreEdgeUse(edge, variable)
{
// Functions which should not be treated as using variable.
@ -177,8 +175,11 @@ var ignoreFunctions = {
"void js::AutoCompartment::AutoCompartment(js::ExclusiveContext*, JSCompartment*)": true,
};
function ignoreGCFunction(fun)
function ignoreGCFunction(mangled)
{
assert(mangled in readableNames);
var fun = readableNames[mangled][0];
if (fun in ignoreFunctions)
return true;
@ -247,5 +248,9 @@ function isOverridableField(csu, field)
return false;
if (field == 'IsOnCurrentThread')
return false;
if (field == 'GetNativeContext')
return false;
if (field == 'GetThreadFromPRThread')
return false;
return true;
}

View File

@ -123,12 +123,7 @@ function getCallees(edge)
var callees = [];
if (callee.Kind == "Var") {
assert(callee.Variable.Kind == "Func");
var origName = callee.Variable.Name[0];
var names = [ origName, otherDestructorName(origName) ];
for (var name of names) {
if (name)
callees.push({'kind': 'direct', 'name': name});
}
callees.push({'kind': 'direct', 'name': callee.Variable.Name[0]});
} else {
assert(callee.Kind == "Drf");
if (callee.Exp[0].Kind == "Fld") {

View File

@ -23,17 +23,21 @@ printErr("Writing " + gcFunctions_filename);
redirect(gcFunctions_filename);
for (var name in gcFunctions) {
print("");
print("GC Function: " + name);
print("GC Function: " + name + "|" + readableNames[name][0]);
do {
name = gcFunctions[name];
print(" " + name);
if (name in readableNames)
print(" " + readableNames[name][0]);
else
print(" " + name);
} while (name in gcFunctions);
}
printErr("Writing " + gcFunctionsList_filename);
redirect(gcFunctionsList_filename);
for (var name in gcFunctions) {
print(name);
for (var readable of readableNames[name])
print(name + "|" + readable);
}
// gcEdges is a list of edges that can GC for more specific reasons than just

View File

@ -2,11 +2,38 @@
"use strict";
var calleeGraph = {};
var callerGraph = {};
var gcFunctions = {};
loadRelativeToScript('utility.js');
// Functions come out of sixgill in the form "mangled|readable". The mangled
// name is Truth. One mangled name might correspond to multiple readable names,
// for multiple reasons, including (1) sixgill/gcc doesn't always qualify types
// the same way or de-typedef the same amount; (2) sixgill's output treats
// references and pointers the same, and so doesn't distinguish them, but C++
// treats them as separate for overloading and linking; (3) (identical)
// destructors sometimes have an int32 parameter, sometimes not.
//
// The readable names are useful because they're far more meaningful to the
// user, and are what should show up in reports and questions to mrgiggles. At
// least in most cases, it's fine to have the extra mangled name tacked onto
// the beginning for these.
//
// The strategy used is to separate out the pieces whenever they are read in,
// create a table mapping mangled names to (one of the) readable names, and
// use the mangled names in all computation.
//
// Note that callgraph.txt uses a compressed representation -- each name is
// mapped to an integer, and those integers are what is recorded in the edges.
// But the integers depend on the full name, whereas the true edge should only
// consider the mangled name. And some of the names encoded in callgraph.txt
// are FieldCalls, not just function names.
var readableNames = {}; // map from mangled name => list of readable names
var mangledName = {}; // map from demangled names => mangled names. Could be eliminated.
var calleeGraph = {}; // map from mangled => list of tuples of {'callee':mangled, 'suppressed':bool}
var callerGraph = {}; // map from mangled => list of tuples of {'caller':mangled, 'suppressed':bool}
var gcFunctions = {}; // map from mangled callee => reason
var suppressedFunctions = {}; // set of mangled names (map from mangled name => true)
var gcEdges = {};
var suppressedFunctions = {};
function addGCFunction(caller, reason)
{
@ -35,8 +62,13 @@ function addCallEdge(caller, callee, suppressed)
callerGraph[callee].push({caller:caller, suppressed:suppressed});
}
// Map from identifier to full "mangled|readable" name. Or sometimes to a
// Class.Field name.
var functionNames = [""];
// Map from identifier to mangled name (or to a Class.Field)
var idToMangled = [""];
function loadCallgraph(file)
{
var suppressedFieldCalls = {};
@ -45,37 +77,45 @@ function loadCallgraph(file)
var textLines = snarf(file).split('\n');
for (var line of textLines) {
var match;
if (match = /^\#(\d+) (.*)/.exec(line)) {
if (match = line.charAt(0) == "#" && /^\#(\d+) (.*)/.exec(line)) {
assert(functionNames.length == match[1]);
functionNames.push(match[2]);
var [ mangled, readable ] = splitFunction(match[2]);
if (mangled in readableNames)
readableNames[mangled].push(readable);
else
readableNames[mangled] = [ readable ];
mangledName[readable] = mangled;
idToMangled.push(mangled);
continue;
}
var suppressed = false;
if (/SUPPRESS_GC/.test(line)) {
if (line.indexOf("SUPPRESS_GC") != -1) {
match = /^(..)SUPPRESS_GC (.*)/.exec(line);
line = match[1] + match[2];
suppressed = true;
}
if (match = /^I (\d+) VARIABLE ([^\,]*)/.exec(line)) {
var caller = functionNames[match[1]];
var tag = line.charAt(0);
if (match = tag == 'I' && /^I (\d+) VARIABLE ([^\,]*)/.exec(line)) {
var mangledCaller = idToMangled[match[1]];
var name = match[2];
if (!indirectCallCannotGC(caller, name) && !suppressed)
addGCFunction(caller, "IndirectCall: " + name);
} else if (match = /^F (\d+) CLASS (.*?) FIELD (.*)/.exec(line)) {
var caller = functionNames[match[1]];
if (!indirectCallCannotGC(functionNames[match[1]], name) && !suppressed)
addGCFunction(mangledCaller, "IndirectCall: " + name);
} else if (match = tag == 'F' && /^F (\d+) CLASS (.*?) FIELD (.*)/.exec(line)) {
var caller = idToMangled[match[1]];
var csu = match[2];
var fullfield = csu + "." + match[3];
if (suppressed)
suppressedFieldCalls[fullfield] = true;
else if (!fieldCallCannotGC(csu, fullfield))
addGCFunction(caller, "FieldCall: " + fullfield);
} else if (match = /^D (\d+) (\d+)/.exec(line)) {
var caller = functionNames[match[1]];
var callee = functionNames[match[2]];
} else if (match = tag == 'D' && /^D (\d+) (\d+)/.exec(line)) {
var caller = idToMangled[match[1]];
var callee = idToMangled[match[2]];
addCallEdge(caller, callee, suppressed);
} else if (match = /^R (\d+) (\d+)/.exec(line)) {
var callerField = functionNames[match[1]];
var callee = functionNames[match[2]];
} else if (match = tag == 'R' && /^R (\d+) (\d+)/.exec(line)) {
var callerField = idToMangled[match[1]];
var callee = idToMangled[match[2]];
addCallEdge(callerField, callee, false);
resolvedFunctions[callerField] = true;
}
@ -99,8 +139,6 @@ function loadCallgraph(file)
var top = worklist.length;
while (top > 0) {
name = worklist[--top];
if (shouldSuppressGC(name))
continue;
if (!(name in suppressedFunctions))
continue;
delete suppressedFunctions[name];
@ -125,8 +163,8 @@ function loadCallgraph(file)
for (var gcName of [ 'jsgc.cpp:void Collect(JSRuntime*, uint8, int64, uint32, uint32)',
'void js::MinorGC(JSRuntime*, uint32)' ])
{
assert(gcName in callerGraph);
addGCFunction(gcName, "GC");
assert(gcName in mangledName);
addGCFunction(mangledName[gcName], "GC");
}
// Initialize the worklist to all known gcFunctions.

View File

@ -233,8 +233,10 @@ sub run_build
print CONFIG "$prefix_dir\n";
print CONFIG Cwd::abs_path("$result_dir/build_xgill.log")."\n";
print CONFIG "$address\n";
print CONFIG "-fplugin-arg-xgill-annfile=$ann_file\n"
if ($ann_file ne "" && -e $ann_file);
my @extra = ("-fplugin-arg-xgill-mangle=1");
push(@extra, "-fplugin-arg-xgill-annfile=$ann_file")
if ($ann_file ne "" && -e $ann_file);
print CONFIG join(" ", @extra) . "\n";
close(CONFIG);
# Tell the wrapper where to find the config

View File

@ -103,21 +103,30 @@ function getSuccessors(body)
return body.successors;
}
function otherDestructorName(name)
// 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)
{
// gcc's information for destructors can be pretty messed up. Some functions
// have destructors with no arguments, some have destructors with an int32
// argument, some have both, and which one matches what the programmer wrote
// is anyone's guess. Work around this by treating calls to one destructor
// form as a call to both destructor forms.
if (!/::~/.test(name))
return null;
var split = func.indexOf("|");
if (split == -1)
return [ func, func ];
return [ func.substr(0, split), func.substr(split+1) ];
}
if (/\(int32\)/.test(name))
return name.replace("(int32)","()");
if (/\(\)/.test(name))
return name.replace("()","(int32)");
return null;
function mangled(fullname)
{
var split = fullname.indexOf("|");
if (split == -1)
return fullname;
return fullname.substr(0, split);
}
function readable(fullname)
{
var split = fullname.indexOf("|");
if (split == -1)
return fullname;
return fullname.substr(split+1);
}
function xdbLibrary()