Bug 910947 - More static rooting analysis fixes to get it all running on build slaves. r=divine-right
authorSteve Fink <sfink@mozilla.com>
Thu, 29 Aug 2013 14:51:19 -0700
changeset 153048 7c79b89c492a161b2f6f7e643c29e30fb4b4fa96
parent 153047 3f307f37b77a321b9deeb4f26e20d78494ea38d0
child 153049 00e7c35894e2d4d27578955e9756afda33cb72cb
push idunknown
push userunknown
push dateunknown
reviewersdivine-right
bugs910947
milestone26.0a1
Bug 910947 - More static rooting analysis fixes to get it all running on build slaves. r=divine-right DONTBUILD 'cause NPOTB
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/run_complete
js/src/devtools/rootAnalysis/suppressedPoints.js
rename from js/src/devtools/rootAnalysis/suppressedPoints.js
rename to js/src/devtools/rootAnalysis/CFG.js
--- a/js/src/devtools/rootAnalysis/suppressedPoints.js
+++ b/js/src/devtools/rootAnalysis/CFG.js
@@ -45,41 +45,110 @@ function isMatchingDestructor(constructo
 
     var destructExp = edge.PEdgeCallInstance.Exp;
     if (destructExp.Kind != "Var")
         return false;
 
     return sameVariable(constructExp.Variable, destructExp.Variable);
 }
 
+// Return all calls within the RAII scope of the constructor matched by
+// isConstructor()
 function allRAIIGuardedCallPoints(body, isConstructor)
 {
     if (!("PEdge" in body))
         return [];
 
     var points = [];
-    var successors = getSuccessors(body);
 
     for (var edge of body.PEdge) {
         if (edge.Kind != "Call")
             continue;
         var callee = edge.Exp[0];
         if (callee.Kind != "Var")
             continue;
         var variable = callee.Variable;
         assert(variable.Kind == "Func");
         if (!isConstructor(variable.Name[0]))
             continue;
         if (edge.PEdgeCallInstance.Exp.Kind != "Var")
             continue;
 
-        Array.prototype.push.apply(points, pointsInRAIIScope(body, edge, successors));
+        Array.prototype.push.apply(points, pointsInRAIIScope(body, edge));
     }
 
     return points;
 }
 
-// Compute the points within a function body where GC is suppressed.
-function computeSuppressedPoints(body)
+// Test whether the given edge is the constructor corresponding to the given
+// destructor edge
+function isMatchingConstructor(destructor, edge)
+{
+    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 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;
+
+    var destructExp = destructor.PEdgeCallInstance.Exp;
+    assert(destructExp.Kind == "Var");
+
+    var constructExp = edge.PEdgeCallInstance.Exp;
+    if (constructExp.Kind != "Var")
+        return false;
+
+    return sameVariable(constructExp.Variable, destructExp.Variable);
+}
+
+function findMatchingConstructor(destructorEdge, body)
 {
-    for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
-        pbody.suppressed[id] = true;
+    var worklist = [destructorEdge];
+    var predecessors = getPredecessors(body);
+    while(worklist.length > 0) {
+        var edge = worklist.pop();
+        if (isMatchingConstructor(destructorEdge, edge))
+            return edge;
+        if (edge.Index[0] in predecessors) {
+            for (var e of predecessors[edge.Index[0]])
+                worklist.push(e);
+        }
+    }
+    printErr("Could not find matching constructor!");
+    debugger;
 }
+
+function pointsInRAIIScope(body, constructorEdge) {
+    var seen = {};
+    var worklist = [constructorEdge.Index[1]];
+    var points = [];
+    while (worklist.length) {
+        var point = worklist.pop();
+        if (point in seen)
+            continue;
+        seen[point] = true;
+        points.push([body, point]);
+        var successors = getSuccessors(body);
+        if (!(point in successors))
+            continue;
+        for (var nedge of successors[point]) {
+            if (isMatchingDestructor(constructorEdge, nedge))
+                continue;
+            if (nedge.Kind == "Loop")
+                Array.prototype.push.apply(points, findAllPoints(nedge.BlockId));
+            worklist.push(nedge.Index[1]);
+        }
+    }
+
+    return points;
+}
--- a/js/src/devtools/rootAnalysis/analyze.py
+++ b/js/src/devtools/rootAnalysis/analyze.py
@@ -92,16 +92,17 @@ def generate_hazards(config, outfilename
     with open(outfilename, 'w') as output:
         command = ['cat'] + [ 'rootingHazards.%s' % (i+1,) for i in range(config['jobs']) ]
         print_command(command, outfile=outfilename)
         subprocess.call(command, stdout=output)
 
 JOBS = { 'dbs':
              (('%(ANALYSIS_SCRIPTDIR)s/run_complete',
                '--foreground',
+               '--no-logs',
                '--build-root=%(objdir)s',
                '--wrap-dir=%(sixgill)s/scripts/wrap_gcc',
                '--work-dir=work',
                '-b', '%(sixgill_bin)s',
                '--buildcommand=%(buildcommand)s',
                '.'),
               ()),
 
@@ -156,16 +157,17 @@ def run_job(name, config):
 
         command = list(cmdspec)
         outfile = 0
         for (i, name) in out_indexes(cmdspec):
             command[i] = '%s.tmp' % name
             temp_map[command[i]] = outfiles[outfile]
             outfile += 1
 
+        sys.stdout.flush()
         with open(temp, 'w') as output:
             subprocess.check_call(command, stdout=output, env=env(config))
         for (temp, final) in temp_map.items():
             try:
                 os.rename(temp, final)
             except OSError:
                 print("Error renaming %s -> %s" % (temp, final))
                 raise
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -1,15 +1,15 @@
 /* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 
 "use strict";
 
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
-loadRelativeToScript('suppressedPoints.js');
+loadRelativeToScript('CFG.js');
 
 var sourceRoot = (environment['SOURCE'] || '') + '/'
 
 var functionBodies;
 
 if (typeof scriptArgs[0] != 'string' || typeof scriptArgs[1] != 'string')
     throw "Usage: analyzeRoots.js <gcFunctions.lst> <gcEdges.txt> <suppressedFunctions.lst> <gcTypes.txt> [start end [tmpfile]]";
 
@@ -238,29 +238,16 @@ function edgeCanGC(edge)
         var fullFieldName = csuName + "." + field.Name[0];
         return fieldCallCannotGC(csuName, fullFieldName) ? null : fullFieldName;
     }
     assert(callee.Exp[0].Kind == "Var");
     var calleeName = callee.Exp[0].Variable.Name[0];
     return indirectCallCannotGC(functionName, calleeName) ? null : "*" + calleeName;
 }
 
-function computePredecessors(body)
-{
-    body.predecessors = [];
-    if (!("PEdge" in body))
-        return;
-    for (var edge of body.PEdge) {
-        var target = edge.Index[1];
-        if (!(target in body.predecessors))
-            body.predecessors[target] = [];
-        body.predecessors[target].push(edge);
-    }
-}
-
 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).
 
     while (worklist.length) {
         var entry = worklist.pop();
@@ -568,15 +555,17 @@ var end = Math.min(minStream + each * ba
 for (var nameIndex = start; nameIndex <= end; nameIndex++) {
     var name = xdb.read_key(nameIndex);
     var functionName = name.readString();
     var data = xdb.read_entry(name);
     functionBodies = JSON.parse(data.readString());
 
     for (var body of functionBodies)
         body.suppressed = [];
-    for (var body of functionBodies)
-        computeSuppressedPoints(body);
+    for (var body of functionBodies) {
+        for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
+            pbody.suppressed[id] = true;
+    }
     processBodies(functionName);
 
     xdb.free_string(name);
     xdb.free_string(data);
 }
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -61,20 +61,20 @@ var ignoreClasses = {
 // a function pointer field named FIELD.
 var ignoreCallees = {
     "js::Class.trace" : true,
     "js::Class.finalize" : true,
     "JSRuntime.destroyPrincipals" : true,
     "nsISupports.AddRef" : true,
     "nsISupports.Release" : true, // makes me a bit nervous; this is a bug but can happen
     "nsAXPCNativeCallContext.GetJSContext" : true,
-    "js::ion::MDefinition.op" : true, // macro generated virtuals just return a constant
-    "js::ion::MDefinition.opName" : true, // macro generated virtuals just return a constant
-    "js::ion::LInstruction.getDef" : true, // virtual but no implementation can GC
-    "js::ion::IonCache.kind" : true, // macro generated virtuals just return a constant
+    "js::jit::MDefinition.op" : true, // macro generated virtuals just return a constant
+    "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.
 };
 
 function fieldCallCannotGC(csu, fullfield)
 {
     if (csu in ignoreClasses)
@@ -100,16 +100,18 @@ function ignoreEdgeUse(edge, variable)
     if (edge.Kind == "Call") {
         var callee = edge.Exp[0];
         if (callee.Kind == "Var") {
             var name = callee.Variable.Name[0];
             if (/~Anchor/.test(name))
                 return true;
             if (/~DebugOnly/.test(name))
                 return true;
+            if (/~ScopedThreadSafeStringInspector/.test(name))
+                return true;
         }
     }
 
     return false;
 }
 
 function ignoreEdgeAddressTaken(edge)
 {
--- a/js/src/devtools/rootAnalysis/computeCallgraph.js
+++ b/js/src/devtools/rootAnalysis/computeCallgraph.js
@@ -1,15 +1,15 @@
 /* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 
 "use strict";
 
 loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
-loadRelativeToScript('suppressedPoints.js');
+loadRelativeToScript('CFG.js');
 
 var subclasses = {};
 var superclasses = {};
 var classFunctions = {};
 
 function addClassEntry(index, name, other)
 {
     if (!(name in index)) {
@@ -225,18 +225,20 @@ var minStream = xdb.min_data_stream();
 var maxStream = xdb.max_data_stream();
 
 for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
     var name = xdb.read_key(nameIndex);
     var data = xdb.read_entry(name);
     functionBodies = JSON.parse(data.readString());
     for (var body of functionBodies)
         body.suppressed = [];
-    for (var body of functionBodies)
-        computeSuppressedPoints(body);
+    for (var body of functionBodies) {
+        for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
+            pbody.suppressed[id] = true;
+    }
 
     seenCallees = {};
     seenSuppressedCallees = {};
 
     for (var body of functionBodies)
         processBody(name.readString(), body);
 
     xdb.free_string(name);
--- a/js/src/devtools/rootAnalysis/run_complete
+++ b/js/src/devtools/rootAnalysis/run_complete
@@ -22,16 +22,17 @@
 # machines) and watching a shared poll_file for jobs. if the output directory
 # for this script already exists then an incremental analysis will be performed
 # and the reports will only reflect the changes since the earlier run.
 
 use strict;
 use IO::Handle;
 use File::Basename qw(dirname);
 use Getopt::Long;
+use Cwd;
 
 #################################
 # environment specific settings #
 #################################
 
 my $WORKDIR;
 my $SIXGILL_BIN;
 
@@ -50,18 +51,20 @@ my $ann_file = "";
 # optional output directory to do a diff against.
 my $old_dir = "";
 
 # run in the foreground
 my $foreground;
 
 my $builder = "make -j4";
 
+my $suppress_logs;
 GetOptions("build-root|b=s" => \$build_dir,
            "poll-file=s" => \$poll_file,
+           "no-logs!" => \$suppress_logs,
            "work-dir=s" => \$WORKDIR,
            "sixgill-binaries|binaries|b=s" => \$SIXGILL_BIN,
            "wrap-dir=s" => \$wrap_dir,
            "annotations-file|annotations|a=s" => \$ann_file,
            "old-dir|old=s" => \$old_dir,
            "foreground!" => \$foreground,
            "buildcommand=s" => \$builder,
            )
@@ -98,16 +101,23 @@ sub clean_project {
     system("make clean");
 }
 
 # code to build the project from $build_dir.
 sub build_project {
     return system($builder) >> 8;
 }
 
+our %kill_on_exit;
+END {
+    for my $pid (keys %kill_on_exit) {
+        kill($pid);
+    }
+}
+
 # commands to start the various xgill binaries. timeouts can be specified
 # for the backend analyses here, and a memory limit can be specified for
 # xmanager if desired (and USE_COUNT_ALLOCATOR is defined in util/alloc.h).
 my $xmanager = "$SIXGILL_BIN/xmanager";
 my $xsource = "$SIXGILL_BIN/xsource";
 my $xmemlocal = "$SIXGILL_BIN/xmemlocal -timeout=20";
 my $xinfer = "$SIXGILL_BIN/xinfer -timeout=60";
 my $xcheck = "$SIXGILL_BIN/xcheck -timeout=30";
@@ -138,36 +148,37 @@ if (not $foreground) {
 
 # if the result directory does not already exist, mark for a clean build.
 my $do_clean = 0;
 if (not (-d $result_dir)) {
     $do_clean = 1;
     mkdir $result_dir;
 }
 
-open(OUT, ">> $result_dir/complete.log");
-OUT->autoflush(1);  # don't buffer writes to the main log.
+if (!$suppress_logs) {
+    my $log_file = "$result_dir/complete.log";
+    open(OUT, ">>", $log_file) or die "append to $log_file: $!";
+    OUT->autoflush(1);  # don't buffer writes to the main log.
 
-# redirect stdout and stderr to the log.
-STDOUT->fdopen(\*OUT, "w");
-STDERR->fdopen(\*OUT, "w");
+    # redirect stdout and stderr to the log.
+    STDOUT->fdopen(\*OUT, "w");
+    STDERR->fdopen(\*OUT, "w");
+}
 
 # pids to wait on before exiting. these are collating worker output.
 my @waitpids;
 
 chdir $result_dir;
 
 # to do a partial run, comment out the commands here you don't want to do.
 
 my $status = run_build();
 
 # end of run commands.
 
-close(OUT);
-
 for my $pid (@waitpids) {
     waitpid($pid, 0);
     $status ||= $? >> 8;
 }
 
 print "Exiting run_complete with status $status\n";
 exit $status;
 
@@ -192,30 +203,33 @@ sub run_build
     print "build started: ";
     print scalar(localtime());
     print "\n";
 
     # fork off a process to run the build.
     defined(my $pid = fork) or die;
 
     # log file for the manager.
-    my $log_file = "$result_dir/build_manager.log";
+    my $manager_log_file = "$result_dir/build_manager.log";
 
     if (!$pid) {
         # this is the child process, fork another process to run a manager.
         defined(my $pid = fork) or die;
-        exec("$xmanager -terminate-on-assert > $log_file 2>&1") if (!$pid);
+        exec("$xmanager -terminate-on-assert > $manager_log_file 2>&1") if (!$pid);
+        $kill_on_exit{$pid} = 1;
 
-        # open new streams to redirect stdout and stderr.
-        open(LOGOUT, "> $result_dir/build.log");
-        open(LOGERR, "> $result_dir/build_err.log");
-        STDOUT->fdopen(\*LOGOUT, "w");
-        STDERR->fdopen(\*LOGERR, "w");
+        if (!$suppress_logs) {
+            # open new streams to redirect stdout and stderr.
+            open(LOGOUT, "> $result_dir/build.log");
+            open(LOGERR, "> $result_dir/build_err.log");
+            STDOUT->fdopen(\*LOGOUT, "w");
+            STDERR->fdopen(\*LOGERR, "w");
+        }
 
-        my $address = get_manager_address($log_file);
+        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"
@@ -234,21 +248,23 @@ sub run_build
         my $exit_status = build_project();
 
         # signal the manager that it's over.
         system("$xsource -remote=$address -end-manager");
 
         # wait for the manager to clean up and terminate.
         print "Waiting for manager to finish (build status $exit_status)...\n";
         waitpid($pid, 0);
+        my $manager_status = $?;
+        delete $kill_on_exit{$pid};
 
         # build is finished, the complete run can resume.
         # return value only useful if --foreground
-        print "Exiting with status " . ($? || $exit_status) . "\n";
-        exit($? || $exit_status);
+        print "Exiting with status " . ($manager_status || $exit_status) . "\n";
+        exit($manager_status || $exit_status);
     }
 
     # this is the complete process, wait for the build to finish.
     waitpid($pid, 0);
     my $status = $? >> 8;
     print "build finished (status $status): ";
     print scalar(localtime());
     print "\n";