config/find_OOM_errors.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
-rw-r--r--

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

     1 #!/usr/bin/env python
     2 # This Source Code Form is subject to the terms of the Mozilla Public
     3 # License, v. 2.0. If a copy of the MPL was not distributed with this
     4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5 from __future__ import print_function
     7 usage = """%prog: A test for OOM conditions in the shell.
     9 %prog finds segfaults and other errors caused by incorrect handling of
    10 allocation during OOM (out-of-memory) conditions.
    11 """
    13 help = """Check for regressions only. This runs a set of files with a known
    14 number of OOM errors (specified by REGRESSION_COUNT), and exits with a non-zero
    15 result if more or less errors are found. See js/src/Makefile.in for invocation.
    16 """
    19 import hashlib
    20 import re
    21 import shlex
    22 import subprocess
    23 import sys
    24 import threading
    25 import time
    27 from optparse import OptionParser
    29 #####################################################################
    30 # Utility functions
    31 #####################################################################
    32 def run(args, stdin=None):
    33   class ThreadWorker(threading.Thread):
    34     def __init__(self, pipe):
    35       super(ThreadWorker, self).__init__()
    36       self.all = ""
    37       self.pipe = pipe
    38       self.setDaemon(True)
    40     def run(self):
    41       while True:
    42         line = self.pipe.readline()
    43         if line == '': break
    44         else:
    45           self.all += line
    47   try:
    48     if type(args) == str:
    49       args = shlex.split(args)
    51     args = [str(a) for a in args] # convert to strs
    53     stdin_pipe = subprocess.PIPE if stdin else None
    54     proc = subprocess.Popen(args, stdin=stdin_pipe, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    55     if stdin_pipe:
    56       proc.stdin.write(stdin)
    57       proc.stdin.close()
    59     stdout_worker = ThreadWorker(proc.stdout)
    60     stderr_worker = ThreadWorker(proc.stderr)
    61     stdout_worker.start()
    62     stderr_worker.start()
    64     proc.wait()
    65     stdout_worker.join()
    66     stderr_worker.join()
    68   except KeyboardInterrupt as e:
    69     sys.exit(-1)
    71   stdout, stderr = stdout_worker.all, stderr_worker.all
    72   result = (stdout, stderr, proc.returncode)
    73   return result
    75 def get_js_files():
    76   (out, err, exit) = run('find ../jit-test/tests -name "*.js"')
    77   if (err, exit) != ("", 0):
    78     sys.exit("Wrong directory, run from an objdir")
    79   return out.split()
    83 #####################################################################
    84 # Blacklisting
    85 #####################################################################
    86 def in_blacklist(sig):
    87   return sig in blacklist
    89 def add_to_blacklist(sig):
    90   blacklist[sig] = blacklist.get(sig, 0)
    91   blacklist[sig] += 1
    93 # How often is a particular lines important for this.
    94 def count_lines():
    95   """Keep track of the amount of times individual lines occur, in order to
    96      prioritize the errors which occur most frequently."""
    97   counts = {}
    98   for string,count in blacklist.items():
    99     for line in string.split("\n"):
   100       counts[line] = counts.get(line, 0) + count
   102   lines = []
   103   for k,v in counts.items():
   104     lines.append("{0:6}: {1}".format(v, k))
   106   lines.sort()
   108   countlog = file("../OOM_count_log", "w")
   109   countlog.write("\n".join(lines))
   110   countlog.flush()
   111   countlog.close()
   114 #####################################################################
   115 # Output cleaning
   116 #####################################################################
   117 def clean_voutput(err):
   118   # Skip what we can't reproduce
   119   err = re.sub(r"^--\d+-- run: /usr/bin/dsymutil \"shell/js\"$", "", err, flags=re.MULTILINE)
   120   err = re.sub(r"^==\d+==", "", err, flags=re.MULTILINE)
   121   err = re.sub(r"^\*\*\d+\*\*", "", err, flags=re.MULTILINE)
   122   err = re.sub(r"^\s+by 0x[0-9A-Fa-f]+: ", "by: ", err, flags=re.MULTILINE)
   123   err = re.sub(r"^\s+at 0x[0-9A-Fa-f]+: ", "at: ", err, flags=re.MULTILINE)
   124   err = re.sub(r"(^\s+Address 0x)[0-9A-Fa-f]+( is not stack'd)", r"\1\2", err, flags=re.MULTILINE)
   125   err = re.sub(r"(^\s+Invalid write of size )\d+", r"\1x", err, flags=re.MULTILINE)
   126   err = re.sub(r"(^\s+Invalid read of size )\d+", r"\1x", err, flags=re.MULTILINE)
   127   err = re.sub(r"(^\s+Address 0x)[0-9A-Fa-f]+( is )\d+( bytes inside a block of size )[0-9,]+( free'd)", r"\1\2\3\4", err, flags=re.MULTILINE)
   129   # Skip the repeating bit due to the segfault
   130   lines = []
   131   for l in err.split('\n'):
   132     if l == " Process terminating with default action of signal 11 (SIGSEGV)":
   133       break
   134     lines.append(l)
   135   err = '\n'.join(lines)
   137   return err
   139 def remove_failed_allocation_backtraces(err):
   140   lines = []
   142   add = True
   143   for l in err.split('\n'):
   145     # Set start and end conditions for including text
   146     if l == " The site of the failed allocation is:":
   147       add = False
   148     elif l[:2] not in ['by: ', 'at:']:
   149       add = True
   151     if add:
   152       lines.append(l)
   155   err = '\n'.join(lines)
   157   return err
   160 def clean_output(err):
   161   err = re.sub(r"^js\(\d+,0x[0-9a-f]+\) malloc: \*\*\* error for object 0x[0-9a-f]+: pointer being freed was not allocated\n\*\*\* set a breakppoint in malloc_error_break to debug\n$", "pointer being freed was not allocated", err, flags=re.MULTILINE)
   163   return err
   166 #####################################################################
   167 # Consts, etc
   168 #####################################################################
   170 command_template = 'shell/js' \
   171                  + ' -m -j -p' \
   172                  + ' -e "const platform=\'darwin\'; const libdir=\'../jit-test/lib/\';"' \
   173                  + ' -f ../jit-test/lib/prolog.js' \
   174                  + ' -f {0}'
   177 # Blacklists are things we don't want to see in our logs again (though we do
   178 # want to count them when they happen). Whitelists we do want to see in our
   179 # logs again, principally because the information we have isn't enough.
   181 blacklist = {}
   182 add_to_blacklist(r"('', '', 1)") # 1 means OOM if the shell hasn't launched yet.
   183 add_to_blacklist(r"('', 'out of memory\n', 1)")
   185 whitelist = set()
   186 whitelist.add(r"('', 'out of memory\n', -11)") # -11 means OOM
   187 whitelist.add(r"('', 'out of memory\nout of memory\n', -11)")
   191 #####################################################################
   192 # Program
   193 #####################################################################
   195 # Options
   196 parser = OptionParser(usage=usage)
   197 parser.add_option("-r", "--regression", action="store", metavar="REGRESSION_COUNT", help=help,
   198                   type="int", dest="regression", default=None)
   200 (OPTIONS, args) = parser.parse_args()
   203 if OPTIONS.regression != None:
   204   # TODO: This should be expanded as we get a better hang of the OOM problems.
   205   # For now, we'll just check that the number of OOMs in one short file does not
   206   # increase.
   207   files = ["../jit-test/tests/arguments/args-createontrace.js"]
   208 else:
   209   files = get_js_files()
   211   # Use a command-line arg to reduce the set of files
   212   if len (args):
   213     files = [f for f in files if f.find(args[0]) != -1]
   216 if OPTIONS.regression == None:
   217   # Don't use a logfile, this is automated for tinderbox.
   218   log = file("../OOM_log", "w")
   221 num_failures = 0
   222 for f in files:
   224   # Run it once to establish boundaries
   225   command = (command_template + ' -O').format(f)
   226   out, err, exit = run(command)
   227   max = re.match(".*OOM max count: (\d+).*", out, flags=re.DOTALL).groups()[0]
   228   max = int(max)
   230   # OOMs don't recover well for the first 20 allocations or so.
   231   # TODO: revisit this.
   232   for i in range(20, max): 
   234     if OPTIONS.regression == None:
   235       print("Testing allocation {0}/{1} in {2}".format(i,max,f))
   236     else:
   237       sys.stdout.write('.') # something short for tinderbox, no space or \n
   239     command = (command_template + ' -A {0}').format(f, i)
   240     out, err, exit = run(command)
   242     # Success (5 is SM's exit code for controlled errors)
   243     if exit == 5 and err.find("out of memory") != -1:
   244       continue
   246     # Failure
   247     else:
   249       if OPTIONS.regression != None:
   250         # Just count them
   251         num_failures += 1
   252         continue
   254       #########################################################################
   255       # The regression tests ends above. The rest of this is for running  the
   256       # script manually.
   257       #########################################################################
   259       problem = str((out, err, exit))
   260       if in_blacklist(problem) and problem not in whitelist:
   261         add_to_blacklist(problem)
   262         continue
   264       add_to_blacklist(problem)
   267       # Get valgrind output for a good stack trace
   268       vcommand = "valgrind --dsymutil=yes -q --log-file=OOM_valgrind_log_file " + command
   269       run(vcommand)
   270       vout = file("OOM_valgrind_log_file").read()
   271       vout = clean_voutput(vout)
   272       sans_alloc_sites = remove_failed_allocation_backtraces(vout)
   274       # Don't print duplicate information
   275       if in_blacklist(sans_alloc_sites):
   276         add_to_blacklist(sans_alloc_sites)
   277         continue
   279       add_to_blacklist(sans_alloc_sites)
   281       log.write ("\n")
   282       log.write ("\n")
   283       log.write ("=========================================================================")
   284       log.write ("\n")
   285       log.write ("An allocation failure at\n\tallocation {0}/{1} in {2}\n\t"
   286                  "causes problems (detected using bug 624094)"
   287                  .format(i, max, f))
   288       log.write ("\n")
   289       log.write ("\n")
   291       log.write ("Command (from obj directory, using patch from bug 624094):\n  " + command)
   292       log.write ("\n")
   293       log.write ("\n")
   294       log.write ("stdout, stderr, exitcode:\n  " + problem)
   295       log.write ("\n")
   296       log.write ("\n")
   298       double_free = err.find("pointer being freed was not allocated") != -1
   299       oom_detected = err.find("out of memory") != -1
   300       multiple_oom_detected = err.find("out of memory\nout of memory") != -1
   301       segfault_detected = exit == -11
   303       log.write ("Diagnosis: ")
   304       log.write ("\n")
   305       if multiple_oom_detected:
   306         log.write ("  - Multiple OOMs reported")
   307         log.write ("\n")
   308       if segfault_detected:
   309         log.write ("  - segfault")
   310         log.write ("\n")
   311       if not oom_detected:
   312         log.write ("  - No OOM checking")
   313         log.write ("\n")
   314       if double_free:
   315         log.write ("  - Double free")
   316         log.write ("\n")
   318       log.write ("\n")
   320       log.write ("Valgrind info:\n" + vout)
   321       log.write ("\n")
   322       log.write ("\n")
   323       log.flush()
   325   if OPTIONS.regression == None:
   326     count_lines()
   328 print()
   330 # Do the actual regression check
   331 if OPTIONS.regression != None:
   332   expected_num_failures = OPTIONS.regression
   334   if num_failures != expected_num_failures:
   336     print("TEST-UNEXPECTED-FAIL |", end='')
   337     if num_failures > expected_num_failures:
   338       print("More out-of-memory errors were found ({0}) than expected ({1}). "
   339             "This probably means an allocation site has been added without a "
   340             "NULL-check. If this is unavoidable, you can account for it by "
   341             "updating Makefile.in.".format(num_failures, expected_num_failures),
   342             end='')
   343     else:
   344       print("Congratulations, you have removed {0} out-of-memory error(s) "
   345             "({1} remain)! Please account for it by updating Makefile.in." 
   346             .format(expected_num_failures - num_failures, num_failures),
   347             end='')
   348     sys.exit(-1)
   349   else:
   350     print('TEST-PASS | find_OOM_errors | Found the expected number of OOM '
   351           'errors ({0})'.format(expected_num_failures))

mercurial