summaryrefslogtreecommitdiffstats
path: root/js/src/devtools/rootAnalysis/analyze.py
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/devtools/rootAnalysis/analyze.py')
-rwxr-xr-xjs/src/devtools/rootAnalysis/analyze.py298
1 files changed, 298 insertions, 0 deletions
diff --git a/js/src/devtools/rootAnalysis/analyze.py b/js/src/devtools/rootAnalysis/analyze.py
new file mode 100755
index 000000000..69482dab7
--- /dev/null
+++ b/js/src/devtools/rootAnalysis/analyze.py
@@ -0,0 +1,298 @@
+#!/usr/bin/python
+
+#
+# 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/.
+
+"""
+Runs the static rooting analysis
+"""
+
+from subprocess import Popen
+import subprocess
+import os
+import argparse
+import sys
+import re
+
+def env(config):
+ e = dict(os.environ)
+ e['PATH'] = ':'.join(p for p in (config.get('gcc_bin'), config.get('sixgill_bin'), e['PATH']) if p)
+ e['XDB'] = '%(sixgill_bin)s/xdb.so' % config
+ e['SOURCE'] = config['source']
+ e['ANALYZED_OBJDIR'] = config['objdir']
+ return e
+
+def fill(command, config):
+ try:
+ return tuple(s % config for s in command)
+ except:
+ print("Substitution failed:")
+ problems = []
+ for fragment in command:
+ try:
+ fragment % config
+ except:
+ problems.append(fragment)
+ raise Exception("\n".join(["Substitution failed:"] + [ " %s" % s for s in problems ]))
+
+def print_command(command, outfile=None, env=None):
+ output = ' '.join(command)
+ if outfile:
+ output += ' > ' + outfile
+ if env:
+ changed = {}
+ e = os.environ
+ for key,value in env.items():
+ if (key not in e) or (e[key] != value):
+ changed[key] = value
+ if changed:
+ outputs = []
+ for key, value in changed.items():
+ if key in e and e[key] in value:
+ start = value.index(e[key])
+ end = start + len(e[key])
+ outputs.append('%s="%s${%s}%s"' % (key,
+ value[:start],
+ key,
+ value[end:]))
+ else:
+ outputs.append("%s='%s'" % (key, value))
+ output = ' '.join(outputs) + " " + output
+
+ print output
+
+def generate_hazards(config, outfilename):
+ jobs = []
+ for i in range(int(config['jobs'])):
+ command = fill(('%(js)s',
+ '%(analysis_scriptdir)s/analyzeRoots.js',
+ '%(gcFunctions_list)s',
+ '%(gcEdges)s',
+ '%(suppressedFunctions_list)s',
+ '%(gcTypes)s',
+ '%(typeInfo)s',
+ str(i+1), '%(jobs)s',
+ 'tmp.%s' % (i+1,)),
+ config)
+ outfile = 'rootingHazards.%s' % (i+1,)
+ output = open(outfile, 'w')
+ if config['verbose']:
+ print_command(command, outfile=outfile, env=env(config))
+ jobs.append((command, Popen(command, stdout=output, env=env(config))))
+
+ final_status = 0
+ while jobs:
+ pid, status = os.wait()
+ jobs = [ job for job in jobs if job[1].pid != pid ]
+ final_status = final_status or status
+
+ if final_status:
+ raise subprocess.CalledProcessError(final_status, 'analyzeRoots.js')
+
+ with open(outfilename, 'w') as output:
+ command = ['cat'] + [ 'rootingHazards.%s' % (i+1,) for i in range(int(config['jobs'])) ]
+ if config['verbose']:
+ 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',
+ '.'),
+ ()),
+
+ 'list-dbs':
+ (('ls', '-l'),
+ ()),
+
+ 'callgraph':
+ (('%(js)s', '%(analysis_scriptdir)s/computeCallgraph.js', '%(typeInfo)s'),
+ 'callgraph.txt'),
+
+ 'gcFunctions':
+ (('%(js)s', '%(analysis_scriptdir)s/computeGCFunctions.js', '%(callgraph)s',
+ '[gcFunctions]', '[gcFunctions_list]', '[gcEdges]', '[suppressedFunctions_list]'),
+ ('gcFunctions.txt', 'gcFunctions.lst', 'gcEdges.txt', 'suppressedFunctions.lst')),
+
+ 'gcTypes':
+ (('%(js)s', '%(analysis_scriptdir)s/computeGCTypes.js',
+ '[gcTypes]', '[typeInfo]'),
+ ('gcTypes.txt', 'typeInfo.txt')),
+
+ 'allFunctions':
+ (('%(sixgill_bin)s/xdbkeys', 'src_body.xdb',),
+ 'allFunctions.txt'),
+
+ 'hazards':
+ (generate_hazards, 'rootingHazards.txt'),
+
+ 'explain':
+ ((os.environ.get('PYTHON', 'python2.7'),
+ '%(analysis_scriptdir)s/explain.py',
+ '%(hazards)s', '%(gcFunctions)s',
+ '[explained_hazards]', '[unnecessary]', '[refs]'),
+ ('hazards.txt', 'unnecessary.txt', 'refs.txt'))
+ }
+
+def out_indexes(command):
+ for i in range(len(command)):
+ m = re.match(r'^\[(.*)\]$', command[i])
+ if m:
+ yield (i, m.group(1))
+
+def run_job(name, config):
+ cmdspec, outfiles = JOBS[name]
+ print("Running " + name + " to generate " + str(outfiles))
+ if hasattr(cmdspec, '__call__'):
+ cmdspec(config, outfiles)
+ else:
+ temp_map = {}
+ cmdspec = fill(cmdspec, config)
+ if isinstance(outfiles, basestring):
+ stdout_filename = '%s.tmp' % name
+ temp_map[stdout_filename] = outfiles
+ if config['verbose']:
+ print_command(cmdspec, outfile=outfiles, env=env(config))
+ else:
+ stdout_filename = None
+ pc = list(cmdspec)
+ outfile = 0
+ for (i, name) in out_indexes(cmdspec):
+ pc[i] = outfiles[outfile]
+ outfile += 1
+ if config['verbose']:
+ print_command(pc, env=env(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()
+ if stdout_filename is None:
+ subprocess.check_call(command, env=env(config))
+ else:
+ with open(stdout_filename, '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
+
+config = { 'ANALYSIS_SCRIPTDIR': os.path.dirname(__file__) }
+
+defaults = [ '%s/defaults.py' % config['ANALYSIS_SCRIPTDIR'],
+ '%s/defaults.py' % os.getcwd() ]
+
+parser = argparse.ArgumentParser(description='Statically analyze build tree for rooting hazards.')
+parser.add_argument('step', metavar='STEP', type=str, nargs='?',
+ help='run starting from this step')
+parser.add_argument('--source', metavar='SOURCE', type=str, nargs='?',
+ help='source code to analyze')
+parser.add_argument('--objdir', metavar='DIR', type=str, nargs='?',
+ help='object directory of compiled files')
+parser.add_argument('--js', metavar='JSSHELL', type=str, nargs='?',
+ help='full path to ctypes-capable JS shell')
+parser.add_argument('--upto', metavar='UPTO', type=str, nargs='?',
+ help='last step to execute')
+parser.add_argument('--jobs', '-j', default=None, metavar='JOBS', type=int,
+ help='number of simultaneous analyzeRoots.js jobs')
+parser.add_argument('--list', const=True, nargs='?', type=bool,
+ help='display available steps')
+parser.add_argument('--buildcommand', '--build', '-b', type=str, nargs='?',
+ help='command to build the tree being analyzed')
+parser.add_argument('--tag', '-t', type=str, nargs='?',
+ help='name of job, also sets build command to "build.<tag>"')
+parser.add_argument('--expect-file', type=str, nargs='?',
+ help='deprecated option, temporarily still present for backwards compatibility')
+parser.add_argument('--verbose', '-v', action='store_true',
+ help='Display cut & paste commands to run individual steps')
+
+args = parser.parse_args()
+
+for default in defaults:
+ try:
+ execfile(default, config)
+ if args.verbose:
+ print("Loaded %s" % default)
+ except:
+ pass
+
+data = config.copy()
+
+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']).strip()
+
+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',
+ 'gcTypes',
+ 'callgraph',
+ 'gcFunctions',
+ 'allFunctions',
+ 'hazards',
+ 'explain' ]
+
+if args.list:
+ for step in steps:
+ command, outfilename = JOBS[step]
+ if outfilename:
+ print("%s -> %s" % (step, outfilename))
+ else:
+ print(step)
+ sys.exit(0)
+
+for step in steps:
+ command, outfiles = JOBS[step]
+ if isinstance(outfiles, basestring):
+ data[step] = outfiles
+ else:
+ outfile = 0
+ for (i, name) in out_indexes(command):
+ data[name] = outfiles[outfile]
+ outfile += 1
+ assert len(outfiles) == outfile, 'step \'%s\': mismatched number of output files (%d) and params (%d)' % (step, outfile, len(outfiles))
+
+if args.step:
+ steps = steps[steps.index(args.step):]
+
+if args.upto:
+ steps = steps[:steps.index(args.upto)+1]
+
+for step in steps:
+ run_job(step, data)