Bug 950176 - Use mangled names to identify nodes in callgraph, r=bhackett
authorSteve Fink <sfink@mozilla.com>
Tue, 17 Dec 2013 11:21:41 -0800
changeset 171871 be83b982ebd31e11516a864c2a4b30b785a4820f
parent 171870 fc7cd53d9dd179a739c4a68656c915bcacc2fcff
child 171872 771eb66a8a6320cba86f887b6a201ed9bc407ca7
push id5166
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:47:54 +0000
treeherdermozilla-aurora@977eb2548b2d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs950176
milestone29.0a1
Bug 950176 - Use mangled names to identify nodes in callgraph, r=bhackett
js/src/devtools/rootAnalysis/CFG.js
js/src/devtools/rootAnalysis/analyze.py
js/src/devtools/rootAnalysis/analyzeRoots.js
js/src/devtools/rootAnalysis/annotations.js
js/src/devtools/rootAnalysis/computeCallgraph.js
js/src/devtools/rootAnalysis/computeGCFunctions.js
js/src/devtools/rootAnalysis/loadCallgraph.js
js/src/devtools/rootAnalysis/run_complete
js/src/devtools/rootAnalysis/utility.js
--- a/js/src/devtools/rootAnalysis/CFG.js
+++ b/js/src/devtools/rootAnalysis/CFG.js
@@ -85,18 +85,18 @@ function isMatchingConstructor(destructo
     if (edge.Kind != "Call")
         return false;
     var callee = edge.Exp[0];
     if (callee.Kind != "Var")
         return false;
     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);
         return false;
     }
     var constructorSubstring = match[1] + match[2];
     if (name.indexOf(constructorSubstring) == -1)
         return false;
--- a/js/src/devtools/rootAnalysis/analyze.py
+++ b/js/src/devtools/rootAnalysis/analyze.py
@@ -215,28 +215,36 @@ parser.add_argument('--expect-file', typ
 args = parser.parse_args()
 for k,v in vars(args).items():
     if v is not None:
         data[k] = v
 
 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:
     data['buildcommand'] = os.environ['BUILD']
 else:
     data['buildcommand'] = 'make -j4 -s'
 
 if 'ANALYZED_OBJDIR' in os.environ:
     data['objdir'] = os.environ['ANALYZED_OBJDIR']
 
 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',
           'gcTypes',
           'gcFunctions',
           'allFunctions',
           'hazards',
           'explain' ]
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -19,19 +19,18 @@ var suppressedFunctionsFile = scriptArgs
 var gcTypesFile = scriptArgs[3];
 var batch = (scriptArgs[4]|0) || 1;
 var numBatches = (scriptArgs[5]|0) || 1;
 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");
 assert(text.pop().length == 0);
 for (var line of text) {
     suppressedFunctions[line] = true;
 }
 text = null;
@@ -185,17 +184,17 @@ function edgeKillsVariable(edge, variabl
             if (instance.Kind != "Var" || !sameVariable(instance.Variable, variable))
                 break;
 
             var callee = edge.Exp[0];
             if (callee.Kind != "Var")
                 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('(');
             if (openParen < 0)
                 break;
             calleeName = calleeName.substring(0, openParen);
 
             var lastColon = calleeName.lastIndexOf('::');
@@ -219,35 +218,33 @@ function edgeKillsVariable(edge, variabl
 function edgeCanGC(edge)
 {
     if (edge.Kind != "Call")
         return false;
     var callee = edge.Exp[0];
     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");
     if (callee.Exp[0].Kind == "Fld") {
         var field = callee.Exp[0].Field;
         var csuName = field.FieldCSU.Type.Name;
         var fullFieldName = csuName + "." + field.Name[0];
         if (fieldCallCannotGC(csuName, fullFieldName))
             return null;
         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)
 {
     // Scan through all edges following an unrooted variable use, using an
     // explicit worklist. A worklist contains a following edge together with a
     // description of where one of its predecessors GC'd (if any).
 
@@ -359,16 +356,22 @@ function variableLiveAcrossGC(suppressed
                 if (call)
                     return call;
             }
         }
     }
     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) {
         if (!("PEdge" in body))
             continue;
         for (var edge of body.PEdge) {
             if (edgeTakesVariableAddress(edge, variable)) {
                 if (edge.Kind == "Assign" || (!suppressed && edgeCanGC(edge)))
@@ -489,17 +492,17 @@ function typeDesc(type)
         return '???';
     }
 }
 
 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;
         var name;
         if (variable.Variable.Kind == "This")
             name = "this";
         else
             name = variable.Variable.Name[0];
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -14,18 +14,26 @@ var ignoreIndirectCalls = {
 
     // I don't know why these are getting truncated
     "nsTraceRefcntImpl.cpp:void (* leakyLogAddRef)(void*": true,
     "nsTraceRefcntImpl.cpp:void (* leakyLogAddRef)(void*, int, int)": true,
     "nsTraceRefcntImpl.cpp:void (* leakyLogRelease)(void*": true,
     "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;
 
     if (name == "mapper" && caller == "ptio.c:pt_MapError")
         return true;
 
     if (name == "params" && caller == "PR_ExplodeTime")
         return true;
@@ -37,18 +45,17 @@ function indirectCallCannotGC(caller, na
     if (name == "checkArg" && caller == CheckCallArgs)
         return true;
 
     // hook called during script finalization which cannot GC.
     if (/CallDestroyScriptHook/.test(caller))
         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;
     }
 
     return false;
 }
 
 // Ignore calls through functions pointers with these types
@@ -75,37 +82,28 @@ var ignoreCallees = {
     "js::jit::MDefinition.opName" : true, // macro generated virtuals just return a constant
     "js::jit::LInstruction.getDef" : true, // virtual but no implementation can GC
     "js::jit::IonCache.kind" : true, // macro generated virtuals just return a constant
     "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC
     "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC.
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
     "nsIThreadManager.GetIsMainThread" : true,
     "PLDHashTableOps.hashKey" : true,
+    "z_stream_s.zfree" : true,
 };
 
 function fieldCallCannotGC(csu, fullfield)
 {
     if (csu in ignoreClasses)
         return true;
     if (fullfield in ignoreCallees)
         return true;
     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.
     if (edge.Kind == "Call") {
         var callee = edge.Exp[0];
         if (callee.Kind == "Var") {
             var name = callee.Variable.Name[0];
             if (/~Anchor/.test(name))
@@ -172,18 +170,21 @@ var ignoreFunctions = {
     "void mozilla::AutoJSContext::AutoJSContext(mozilla::detail::GuardObjectNotifier*)" : true,
     "void mozilla::AutoSafeJSContext::~AutoSafeJSContext(int32)" : true,
 
     // And these are workarounds to avoid even more analysis work,
     // which would sadly still be needed even with bug 898815.
     "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;
 
     // Templatized function
     if (fun.indexOf("void nsCOMPtr<T>::Assert_NoQueryNeeded()") >= 0)
         return true;
 
     // XXX modify refillFreeList<NoGC> to not need data flow analysis to understand it cannot GC.
@@ -242,10 +243,14 @@ function isSuppressConstructor(name)
 function isOverridableField(csu, field)
 {
     if (csu != 'nsISupports')
         return false;
     if (field == 'GetCurrentJSContext')
         return false;
     if (field == 'IsOnCurrentThread')
         return false;
+    if (field == 'GetNativeContext')
+        return false;
+    if (field == 'GetThreadFromPRThread')
+        return false;
     return true;
 }
--- a/js/src/devtools/rootAnalysis/computeCallgraph.js
+++ b/js/src/devtools/rootAnalysis/computeCallgraph.js
@@ -118,22 +118,17 @@ function getCallees(edge)
 {
     if (edge.Kind != "Call")
         return [];
 
     var callee = edge.Exp[0];
     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") {
             var field = callee.Exp[0].Field;
             var fieldName = field.Name[0];
             var csuName = field.FieldCSU.Type.Name;
             var functions = null;
             if ("FieldInstanceFunction" in field) {
--- a/js/src/devtools/rootAnalysis/computeGCFunctions.js
+++ b/js/src/devtools/rootAnalysis/computeGCFunctions.js
@@ -18,27 +18,31 @@ var gcEdges_filename = scriptArgs[3] || 
 var suppressedFunctionsList_filename = scriptArgs[4] || "suppressedFunctions.lst";
 
 loadCallgraph(callgraph_filename);
 
 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
 // calling a function that is in gcFunctions.txt.
 //
 // Right now, it is unused. It was meant for ~AutoCompartment when it might
 // wrap an exception, but anything held live across ~AC will have to be held
 // live across the corresponding constructor (and hence the whole scope of the
--- a/js/src/devtools/rootAnalysis/loadCallgraph.js
+++ b/js/src/devtools/rootAnalysis/loadCallgraph.js
@@ -1,17 +1,44 @@
 /* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 
 "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)
 {
     if (caller in suppressedFunctions)
         return false;
 
     if (ignoreGCFunction(caller))
         return false;
@@ -30,57 +57,70 @@ function addCallEdge(caller, callee, sup
         calleeGraph[caller] = [];
     calleeGraph[caller].push({callee:callee, suppressed:suppressed});
 
     if (!(callee in callerGraph))
         callerGraph[callee] = [];
     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 = {};
     var resolvedFunctions = {};
 
     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;
         }
     }
 
     // Initialize suppressedFunctions to the set of all functions, and the
     // worklist to all toplevel callers.
     var worklist = [];
@@ -94,18 +134,16 @@ function loadCallgraph(file)
     }
 
     // Find all functions reachable via an unsuppressed call chain, and remove
     // them from the suppressedFunctions set. Everything remaining is only
     // reachable when GC is suppressed.
     var top = worklist.length;
     while (top > 0) {
         name = worklist[--top];
-        if (shouldSuppressGC(name))
-            continue;
         if (!(name in suppressedFunctions))
             continue;
         delete suppressedFunctions[name];
         if (!(name in calleeGraph))
             continue;
         for (var entry of calleeGraph[name]) {
             if (!entry.suppressed)
                 worklist[top++] = entry.callee;
@@ -120,18 +158,18 @@ function loadCallgraph(file)
 
     for (var name in suppressedFieldCalls) {
         suppressedFunctions[name] = true;
     }
 
     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.
     var worklist = [];
     for (var name in gcFunctions)
         worklist.push(name);
 
     // Recursively find all callers and add them to the set of gcFunctions.
--- a/js/src/devtools/rootAnalysis/run_complete
+++ b/js/src/devtools/rootAnalysis/run_complete
@@ -228,18 +228,20 @@ sub run_build
         my $address = get_manager_address($manager_log_file);
 
         # write the configuration file for the wrapper script.
         my $config_file = "$WORKDIR/xgill.config";
         open(CONFIG, ">", $config_file) or die "create $config_file: $!";
         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
 	$ENV{"XGILL_CONFIG"} = Cwd::abs_path($config_file);
 
         # update the PATH so that the build will see the wrappers.
         $ENV{"PATH"} = "$wrap_dir:" . $ENV{"PATH"};
 
--- a/js/src/devtools/rootAnalysis/utility.js
+++ b/js/src/devtools/rootAnalysis/utility.js
@@ -98,31 +98,40 @@ function getPredecessors(body)
 
 function getSuccessors(body)
 {
     if (!('successors' in body))
         collectBodyEdges(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)
+{
+    var split = func.indexOf("|");
+    if (split == -1)
+        return [ func, func ];
+    return [ func.substr(0, split), func.substr(split+1) ];
+}
+
+function mangled(fullname)
 {
-    // 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 = fullname.indexOf("|");
+    if (split == -1)
+        return fullname;
+    return fullname.substr(0, split);
+}
 
-    if (/\(int32\)/.test(name))
-        return name.replace("(int32)","()");
-    if (/\(\)/.test(name))
-        return name.replace("()","(int32)");
-    return null;
+function readable(fullname)
+{
+    var split = fullname.indexOf("|");
+    if (split == -1)
+        return fullname;
+    return fullname.substr(split+1);
 }
 
 function xdbLibrary()
 {
     var lib = ctypes.open(environment['XDB']);
     return {
         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),