summaryrefslogtreecommitdiffstats
path: root/testing/mozharness/mozharness/mozilla/building/hazards.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozharness/mozharness/mozilla/building/hazards.py')
-rw-r--r--testing/mozharness/mozharness/mozilla/building/hazards.py241
1 files changed, 241 insertions, 0 deletions
diff --git a/testing/mozharness/mozharness/mozilla/building/hazards.py b/testing/mozharness/mozharness/mozilla/building/hazards.py
new file mode 100644
index 000000000..6de235f89
--- /dev/null
+++ b/testing/mozharness/mozharness/mozilla/building/hazards.py
@@ -0,0 +1,241 @@
+import os
+import json
+import re
+
+from mozharness.base.errors import MakefileErrorList
+from mozharness.mozilla.buildbot import TBPL_WARNING
+
+
+class HazardError(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+ # Logging ends up calling splitlines directly on what is being logged, which would fail.
+ def splitlines(self):
+ return str(self).splitlines()
+
+class HazardAnalysis(object):
+ def clobber_shell(self, builder):
+ """Clobber the specially-built JS shell used to run the analysis"""
+ dirs = builder.query_abs_dirs()
+ builder.rmtree(dirs['shell_objdir'])
+
+ def configure_shell(self, builder):
+ """Configure the specially-built JS shell used to run the analysis"""
+ dirs = builder.query_abs_dirs()
+
+ if not os.path.exists(dirs['shell_objdir']):
+ builder.mkdir_p(dirs['shell_objdir'])
+
+ js_src_dir = os.path.join(dirs['gecko_src'], 'js', 'src')
+ rc = builder.run_command(['autoconf-2.13'],
+ cwd=js_src_dir,
+ env=builder.env,
+ error_list=MakefileErrorList)
+ if rc != 0:
+ rc = builder.run_command(['autoconf2.13'],
+ cwd=js_src_dir,
+ env=builder.env,
+ error_list=MakefileErrorList)
+ if rc != 0:
+ raise HazardError("autoconf failed, can't continue.")
+
+ rc = builder.run_command([os.path.join(js_src_dir, 'configure'),
+ '--enable-optimize',
+ '--disable-debug',
+ '--enable-ctypes',
+ '--with-system-nspr',
+ '--without-intl-api'],
+ cwd=dirs['shell_objdir'],
+ env=builder.env,
+ error_list=MakefileErrorList)
+ if rc != 0:
+ raise HazardError("Configure failed, can't continue.")
+
+ def build_shell(self, builder):
+ """Build a JS shell specifically for running the analysis"""
+ dirs = builder.query_abs_dirs()
+
+ rc = builder.run_command(['make', '-j', str(builder.config.get('concurrency', 4)), '-s'],
+ cwd=dirs['shell_objdir'],
+ env=builder.env,
+ error_list=MakefileErrorList)
+ if rc != 0:
+ raise HazardError("Build failed, can't continue.")
+
+ def clobber(self, builder):
+ """Clobber all of the old analysis data. Note that theoretically we could do
+ incremental analyses, but they seem to still be buggy."""
+ dirs = builder.query_abs_dirs()
+ builder.rmtree(dirs['abs_analysis_dir'])
+ builder.rmtree(dirs['abs_analyzed_objdir'])
+
+ def setup(self, builder):
+ """Prepare the config files and scripts for running the analysis"""
+ dirs = builder.query_abs_dirs()
+ analysis_dir = dirs['abs_analysis_dir']
+
+ if not os.path.exists(analysis_dir):
+ builder.mkdir_p(analysis_dir)
+
+ js_src_dir = os.path.join(dirs['gecko_src'], 'js', 'src')
+
+ values = {
+ 'js': os.path.join(dirs['shell_objdir'], 'dist', 'bin', 'js'),
+ 'analysis_scriptdir': os.path.join(js_src_dir, 'devtools', 'rootAnalysis'),
+ 'source_objdir': dirs['abs_analyzed_objdir'],
+ 'source': os.path.join(dirs['abs_work_dir'], 'source'),
+ 'sixgill': os.path.join(dirs['abs_work_dir'], builder.config['sixgill']),
+ 'sixgill_bin': os.path.join(dirs['abs_work_dir'], builder.config['sixgill_bin']),
+ 'gcc_bin': os.path.join(dirs['abs_work_dir'], 'gcc'),
+ }
+ defaults = """
+js = '%(js)s'
+analysis_scriptdir = '%(analysis_scriptdir)s'
+objdir = '%(source_objdir)s'
+source = '%(source)s'
+sixgill = '%(sixgill)s'
+sixgill_bin = '%(sixgill_bin)s'
+gcc_bin = '%(gcc_bin)s'
+jobs = 4
+""" % values
+
+ defaults_path = os.path.join(analysis_dir, 'defaults.py')
+ file(defaults_path, "w").write(defaults)
+ builder.log("Wrote analysis config file " + defaults_path)
+
+ build_script = builder.config['build_command']
+ builder.copyfile(os.path.join(dirs['mozharness_scriptdir'],
+ os.path.join('spidermonkey', build_script)),
+ os.path.join(analysis_dir, build_script),
+ copystat=True)
+
+ def run(self, builder, env, error_list):
+ """Execute the analysis, which consists of building all analyzed
+ source code with a GCC plugin active that siphons off the interesting
+ data, then running some JS scripts over the databases created by
+ the plugin."""
+ dirs = builder.query_abs_dirs()
+ analysis_dir = dirs['abs_analysis_dir']
+ analysis_scriptdir = os.path.join(dirs['abs_work_dir'], dirs['analysis_scriptdir'])
+
+ build_script = builder.config['build_command']
+ build_script = os.path.abspath(os.path.join(analysis_dir, build_script))
+
+ cmd = [
+ builder.config['python'],
+ os.path.join(analysis_scriptdir, 'analyze.py'),
+ "--source", dirs['gecko_src'],
+ "--buildcommand", build_script,
+ ]
+ retval = builder.run_command(cmd,
+ cwd=analysis_dir,
+ env=env,
+ error_list=error_list)
+ if retval != 0:
+ raise HazardError("failed to build")
+
+ def collect_output(self, builder):
+ """Gather up the analysis output and place in the upload dir."""
+ dirs = builder.query_abs_dirs()
+ analysis_dir = dirs['abs_analysis_dir']
+ upload_dir = dirs['abs_blob_upload_dir']
+ builder.mkdir_p(upload_dir)
+ files = (('rootingHazards.txt',
+ 'rooting_hazards',
+ 'list of rooting hazards, unsafe references, and extra roots'),
+ ('gcFunctions.txt',
+ 'gcFunctions',
+ 'list of functions that can gc, and why'),
+ ('allFunctions.txt',
+ 'allFunctions',
+ 'list of all functions that were compiled'),
+ ('gcTypes.txt',
+ 'gcTypes',
+ 'list of types containing unrooted gc pointers'),
+ ('unnecessary.txt',
+ 'extra',
+ 'list of extra roots (rooting with no GC function in scope)'),
+ ('refs.txt',
+ 'refs',
+ 'list of unsafe references to unrooted pointers'),
+ ('hazards.txt',
+ 'hazards',
+ 'list of just the hazards, together with gcFunction reason for each'))
+ for f, short, long in files:
+ builder.copy_to_upload_dir(os.path.join(analysis_dir, f),
+ short_desc=short,
+ long_desc=long,
+ compress=False, # blobber will compress
+ upload_dir=upload_dir)
+ print("== Hazards (temporarily inline here, beware weirdly interleaved output, see bug 1211402) ==")
+ print(file(os.path.join(analysis_dir, "hazards.txt")).read())
+
+ def upload_results(self, builder):
+ """Upload the results of the analysis."""
+ pass
+
+ def check_expectations(self, builder):
+ """Compare the actual to expected number of problems."""
+ if 'expect_file' not in builder.config:
+ builder.info('No expect_file given; skipping comparison with expected hazard count')
+ return
+
+ dirs = builder.query_abs_dirs()
+ analysis_dir = dirs['abs_analysis_dir']
+ analysis_scriptdir = os.path.join(dirs['gecko_src'], 'js', 'src', 'devtools', 'rootAnalysis')
+ expect_file = os.path.join(analysis_scriptdir, builder.config['expect_file'])
+ expect = builder.read_from_file(expect_file)
+ if expect is None:
+ raise HazardError("could not load expectation file")
+ data = json.loads(expect)
+
+ num_hazards = 0
+ num_refs = 0
+ with builder.opened(os.path.join(analysis_dir, "rootingHazards.txt")) as (hazards_fh, err):
+ if err:
+ raise HazardError("hazards file required")
+ for line in hazards_fh:
+ m = re.match(r"^Function.*has unrooted.*live across GC call", line)
+ if m:
+ num_hazards += 1
+
+ m = re.match(r'^Function.*takes unsafe address of unrooted', line)
+ if m:
+ num_refs += 1
+
+ expect_hazards = data.get('expect-hazards')
+ status = []
+ if expect_hazards is None:
+ status.append("%d hazards" % num_hazards)
+ else:
+ status.append("%d/%d hazards allowed" % (num_hazards, expect_hazards))
+
+ if expect_hazards is not None and expect_hazards != num_hazards:
+ if expect_hazards < num_hazards:
+ builder.warning("TEST-UNEXPECTED-FAIL %d more hazards than expected (expected %d, saw %d)" %
+ (num_hazards - expect_hazards, expect_hazards, num_hazards))
+ builder.buildbot_status(TBPL_WARNING)
+ else:
+ builder.info("%d fewer hazards than expected! (expected %d, saw %d)" %
+ (expect_hazards - num_hazards, expect_hazards, num_hazards))
+
+ expect_refs = data.get('expect-refs')
+ if expect_refs is None:
+ status.append("%d unsafe refs" % num_refs)
+ else:
+ status.append("%d/%d unsafe refs allowed" % (num_refs, expect_refs))
+
+ if expect_refs is not None and expect_refs != num_refs:
+ if expect_refs < num_refs:
+ builder.warning("TEST-UNEXPECTED-FAIL %d more unsafe refs than expected (expected %d, saw %d)" %
+ (num_refs - expect_refs, expect_refs, num_refs))
+ builder.buildbot_status(TBPL_WARNING)
+ else:
+ builder.info("%d fewer unsafe refs than expected! (expected %d, saw %d)" %
+ (expect_refs - num_refs, expect_refs, num_refs))
+
+ builder.info("TinderboxPrint: " + ", ".join(status))