js/src/devtools/rootAnalysis/analyze.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 #!/usr/bin/python
     3 #
     4 # This Source Code Form is subject to the terms of the Mozilla Public
     5 # License, v. 2.0. If a copy of the MPL was not distributed with this
     6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     8 """
     9 Runs the static rooting analysis
    10 """
    12 from subprocess import Popen
    13 import subprocess
    14 import os
    15 import argparse
    16 import sys
    17 import re
    19 def env(config):
    20     e = dict(os.environ)
    21     e['PATH'] = '%s:%s' % (e['PATH'], config['sixgill_bin'])
    22     e['XDB'] = '%(sixgill_bin)s/xdb.so' % config
    23     e['SOURCE'] = config['source']
    24     e['ANALYZED_OBJDIR'] = config['objdir']
    25     return e
    27 def fill(command, config):
    28     try:
    29         return tuple(s % config for s in command)
    30     except:
    31         print("Substitution failed:")
    32         problems = []
    33         for fragment in command:
    34             try:
    35                 fragment % config
    36             except:
    37                 problems.append(fragment)
    38         raise Exception("\n".join(["Substitution failed:"] + [ "  %s" % s for s in problems ]))
    40 def print_command(command, outfile=None, env=None):
    41     output = ' '.join(command)
    42     if outfile:
    43         output += ' > ' + outfile
    44     if env:
    45         changed = {}
    46         e = os.environ
    47         for key,value in env.items():
    48             if (key not in e) or (e[key] != value):
    49                 changed[key] = value
    50         if changed:
    51             outputs = []
    52             for key, value in changed.items():
    53                 if key in e and e[key] in value:
    54                     start = value.index(e[key])
    55                     end = start + len(e[key])
    56                     outputs.append('%s="%s${%s}%s"' % (key,
    57                                                        value[:start],
    58                                                        key,
    59                                                        value[end:]))
    60                 else:
    61                     outputs.append("%s='%s'" % (key, value))
    62             output = ' '.join(outputs) + " " + output
    64     print output
    66 def generate_hazards(config, outfilename):
    67     jobs = []
    68     for i in range(config['jobs']):
    69         command = fill(('%(js)s',
    70                         '%(analysis_scriptdir)s/analyzeRoots.js',
    71                         '%(gcFunctions_list)s',
    72                         '%(gcEdges)s',
    73                         '%(suppressedFunctions_list)s',
    74                         '%(gcTypes)s',
    75                         str(i+1), '%(jobs)s',
    76                         'tmp.%s' % (i+1,)),
    77                        config)
    78         outfile = 'rootingHazards.%s' % (i+1,)
    79         output = open(outfile, 'w')
    80         print_command(command, outfile=outfile, env=env(config))
    81         jobs.append((command, Popen(command, stdout=output, env=env(config))))
    83     final_status = 0
    84     while jobs:
    85         pid, status = os.wait()
    86         jobs = [ job for job in jobs if job[1].pid != pid ]
    87         final_status = final_status or status
    89     if final_status:
    90         raise subprocess.CalledProcessError(final_status, 'analyzeRoots.js')
    92     with open(outfilename, 'w') as output:
    93         command = ['cat'] + [ 'rootingHazards.%s' % (i+1,) for i in range(config['jobs']) ]
    94         print_command(command, outfile=outfilename)
    95         subprocess.call(command, stdout=output)
    97 JOBS = { 'dbs':
    98              (('%(ANALYSIS_SCRIPTDIR)s/run_complete',
    99                '--foreground',
   100                '--no-logs',
   101                '--build-root=%(objdir)s',
   102                '--wrap-dir=%(sixgill)s/scripts/wrap_gcc',
   103                '--work-dir=work',
   104                '-b', '%(sixgill_bin)s',
   105                '--buildcommand=%(buildcommand)s',
   106                '.'),
   107               ()),
   109          'callgraph':
   110              (('%(js)s', '%(analysis_scriptdir)s/computeCallgraph.js'),
   111               'callgraph.txt'),
   113          'gcFunctions':
   114              (('%(js)s', '%(analysis_scriptdir)s/computeGCFunctions.js', '%(callgraph)s',
   115                '[gcFunctions]', '[gcFunctions_list]', '[gcEdges]', '[suppressedFunctions_list]'),
   116               ('gcFunctions.txt', 'gcFunctions.lst', 'gcEdges.txt', 'suppressedFunctions.lst')),
   118          'gcTypes':
   119              (('%(js)s', '%(analysis_scriptdir)s/computeGCTypes.js',),
   120               'gcTypes.txt'),
   122          'allFunctions':
   123              (('%(sixgill_bin)s/xdbkeys', 'src_body.xdb',),
   124               'allFunctions.txt'),
   126          'hazards':
   127              (generate_hazards, 'rootingHazards.txt'),
   129          'explain':
   130              (('python', '%(analysis_scriptdir)s/explain.py',
   131                '%(hazards)s', '%(gcFunctions)s',
   132                '[explained_hazards]', '[unnecessary]', '[refs]'),
   133               ('hazards.txt', 'unnecessary.txt', 'refs.txt'))
   134          }
   136 def out_indexes(command):
   137     for i in range(len(command)):
   138         m = re.match(r'^\[(.*)\]$', command[i])
   139         if m:
   140             yield (i, m.group(1))
   142 def run_job(name, config):
   143     cmdspec, outfiles = JOBS[name]
   144     print("Running " + name + " to generate " + str(outfiles))
   145     if hasattr(cmdspec, '__call__'):
   146         cmdspec(config, outfiles)
   147     else:
   148         temp_map = {}
   149         cmdspec = fill(cmdspec, config)
   150         if isinstance(outfiles, basestring):
   151             stdout_filename = '%s.tmp' % name
   152             temp_map[stdout_filename] = outfiles
   153             print_command(cmdspec, outfile=outfiles, env=env(config))
   154         else:
   155             stdout_filename = None
   156             pc = list(cmdspec)
   157             outfile = 0
   158             for (i, name) in out_indexes(cmdspec):
   159                 pc[i] = outfiles[outfile]
   160                 outfile += 1
   161             print_command(pc, env=env(config))
   163         command = list(cmdspec)
   164         outfile = 0
   165         for (i, name) in out_indexes(cmdspec):
   166             command[i] = '%s.tmp' % name
   167             temp_map[command[i]] = outfiles[outfile]
   168             outfile += 1
   170         sys.stdout.flush()
   171         if stdout_filename is None:
   172             subprocess.check_call(command, env=env(config))
   173         else:
   174             with open(stdout_filename, 'w') as output:
   175                 subprocess.check_call(command, stdout=output, env=env(config))
   176         for (temp, final) in temp_map.items():
   177             try:
   178                 os.rename(temp, final)
   179             except OSError:
   180                 print("Error renaming %s -> %s" % (temp, final))
   181                 raise
   183 config = { 'ANALYSIS_SCRIPTDIR': os.path.dirname(__file__) }
   185 defaults = [ '%s/defaults.py' % config['ANALYSIS_SCRIPTDIR'],
   186              '%s/defaults.py' % os.getcwd() ]
   188 for default in defaults:
   189     try:
   190         execfile(default, config)
   191         print("Loaded %s" % default)
   192     except:
   193         pass
   195 data = config.copy()
   197 parser = argparse.ArgumentParser(description='Statically analyze build tree for rooting hazards.')
   198 parser.add_argument('step', metavar='STEP', type=str, nargs='?',
   199                     help='run starting from this step')
   200 parser.add_argument('--source', metavar='SOURCE', type=str, nargs='?',
   201                     help='source code to analyze')
   202 parser.add_argument('--upto', metavar='UPTO', type=str, nargs='?',
   203                     help='last step to execute')
   204 parser.add_argument('--jobs', '-j', default=None, metavar='JOBS', type=int,
   205                     help='number of simultaneous analyzeRoots.js jobs')
   206 parser.add_argument('--list', const=True, nargs='?', type=bool,
   207                     help='display available steps')
   208 parser.add_argument('--buildcommand', '--build', '-b', type=str, nargs='?',
   209                     help='command to build the tree being analyzed')
   210 parser.add_argument('--tag', '-t', type=str, nargs='?',
   211                     help='name of job, also sets build command to "build.<tag>"')
   212 parser.add_argument('--expect-file', type=str, nargs='?',
   213                     help='deprecated option, temporarily still present for backwards compatibility')
   215 args = parser.parse_args()
   216 for k,v in vars(args).items():
   217     if v is not None:
   218         data[k] = v
   220 if args.tag and not args.buildcommand:
   221     args.buildcommand="build.%s" % args.tag
   223 if args.jobs is not None:
   224     data['jobs'] = args.jobs
   225 if not data.get('jobs'):
   226     data['jobs'] = subprocess.check_output(['nproc', '--ignore=1'])
   228 if args.buildcommand:
   229     data['buildcommand'] = args.buildcommand
   230 elif 'BUILD' in os.environ:
   231     data['buildcommand'] = os.environ['BUILD']
   232 else:
   233     data['buildcommand'] = 'make -j4 -s'
   235 if 'ANALYZED_OBJDIR' in os.environ:
   236     data['objdir'] = os.environ['ANALYZED_OBJDIR']
   238 if 'SOURCE' in os.environ:
   239     data['source'] = os.environ['SOURCE']
   240 if not data.get('source') and data.get('sixgill_bin'):
   241     path = subprocess.check_output(['sh', '-c', data['sixgill_bin'] + '/xdbkeys file_source.xdb | grep jsapi.cpp'])
   242     data['source'] = path.replace("/js/src/jsapi.cpp", "")
   244 steps = [ 'dbs',
   245           'callgraph',
   246           'gcTypes',
   247           'gcFunctions',
   248           'allFunctions',
   249           'hazards',
   250           'explain' ]
   252 if args.list:
   253     for step in steps:
   254         command, outfilename = JOBS[step]
   255         if outfilename:
   256             print("%s -> %s" % (step, outfilename))
   257         else:
   258             print(step)
   259     sys.exit(0)
   261 for step in steps:
   262     command, outfiles = JOBS[step]
   263     if isinstance(outfiles, basestring):
   264         data[step] = outfiles
   265     else:
   266         outfile = 0
   267         for (i, name) in out_indexes(command):
   268             data[name] = outfiles[outfile]
   269             outfile += 1
   270         assert len(outfiles) == outfile, 'step \'%s\': mismatched number of output files and params' % step
   272 if args.step:
   273     steps = steps[steps.index(args.step):]
   275 if args.upto:
   276     steps = steps[:steps.index(args.upto)+1]
   278 for step in steps:
   279     run_job(step, data)

mercurial