Home | History | Annotate | Download | only in valgrind
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Runs an exe through Valgrind and puts the intermediate files in a
      6 directory.
      7 """
      8 
      9 import datetime
     10 import glob
     11 import logging
     12 import optparse
     13 import os
     14 import re
     15 import shutil
     16 import stat
     17 import subprocess
     18 import sys
     19 import tempfile
     20 
     21 import common
     22 
     23 import drmemory_analyze
     24 import memcheck_analyze
     25 import tsan_analyze
     26 
     27 class BaseTool(object):
     28   """Abstract class for running Valgrind-, PIN-based and other dynamic
     29   error detector tools.
     30 
     31   Always subclass this and implement ToolCommand with framework- and
     32   tool-specific stuff.
     33   """
     34 
     35   def __init__(self):
     36     temp_parent_dir = None
     37     self.log_parent_dir = ""
     38     if common.IsWindows():
     39       # gpu process on Windows Vista+ runs at Low Integrity and can only
     40       # write to certain directories (http://crbug.com/119131)
     41       #
     42       # TODO(bruening): if scripts die in middle and don't clean up temp
     43       # dir, we'll accumulate files in profile dir.  should remove
     44       # really old files automatically.
     45       profile = os.getenv("USERPROFILE")
     46       if profile:
     47         self.log_parent_dir = profile + "\\AppData\\LocalLow\\"
     48         if os.path.exists(self.log_parent_dir):
     49           self.log_parent_dir = common.NormalizeWindowsPath(self.log_parent_dir)
     50           temp_parent_dir = self.log_parent_dir
     51     # Generated every time (even when overridden)
     52     self.temp_dir = tempfile.mkdtemp(prefix="vg_logs_", dir=temp_parent_dir)
     53     self.log_dir = self.temp_dir # overridable by --keep_logs
     54     self.option_parser_hooks = []
     55     # TODO(glider): we may not need some of the env vars on some of the
     56     # platforms.
     57     self._env = {
     58       "G_SLICE" : "always-malloc",
     59       "NSS_DISABLE_UNLOAD" : "1",
     60       "NSS_DISABLE_ARENA_FREE_LIST" : "1",
     61       "GTEST_DEATH_TEST_USE_FORK": "1",
     62     }
     63 
     64   def ToolName(self):
     65     raise NotImplementedError, "This method should be implemented " \
     66                                "in the tool-specific subclass"
     67 
     68   def Analyze(self, check_sanity=False):
     69     raise NotImplementedError, "This method should be implemented " \
     70                                "in the tool-specific subclass"
     71 
     72   def RegisterOptionParserHook(self, hook):
     73     # Frameworks and tools can add their own flags to the parser.
     74     self.option_parser_hooks.append(hook)
     75 
     76   def CreateOptionParser(self):
     77     # Defines Chromium-specific flags.
     78     self._parser = optparse.OptionParser("usage: %prog [options] <program to "
     79                                          "test>")
     80     self._parser.disable_interspersed_args()
     81     self._parser.add_option("-t", "--timeout",
     82                       dest="timeout", metavar="TIMEOUT", default=10000,
     83                       help="timeout in seconds for the run (default 10000)")
     84     self._parser.add_option("", "--build-dir",
     85                             help="the location of the compiler output")
     86     self._parser.add_option("", "--source-dir",
     87                             help="path to top of source tree for this build"
     88                                  "(used to normalize source paths in baseline)")
     89     self._parser.add_option("", "--gtest_filter", default="",
     90                             help="which test case to run")
     91     self._parser.add_option("", "--gtest_repeat",
     92                             help="how many times to run each test")
     93     self._parser.add_option("", "--gtest_print_time", action="store_true",
     94                             default=False,
     95                             help="show how long each test takes")
     96     self._parser.add_option("", "--ignore_exit_code", action="store_true",
     97                             default=False,
     98                             help="ignore exit code of the test "
     99                                  "(e.g. test failures)")
    100     self._parser.add_option("", "--keep_logs", action="store_true",
    101                             default=False,
    102                             help="store memory tool logs in the <tool>.logs "
    103                                  "directory instead of /tmp.\nThis can be "
    104                                  "useful for tool developers/maintainers.\n"
    105                                  "Please note that the <tool>.logs directory "
    106                                  "will be clobbered on tool startup.")
    107 
    108     # To add framework- or tool-specific flags, please add a hook using
    109     # RegisterOptionParserHook in the corresponding subclass.
    110     # See ValgrindTool and ThreadSanitizerBase for examples.
    111     for hook in self.option_parser_hooks:
    112       hook(self, self._parser)
    113 
    114   def ParseArgv(self, args):
    115     self.CreateOptionParser()
    116 
    117     # self._tool_flags will store those tool flags which we don't parse
    118     # manually in this script.
    119     self._tool_flags = []
    120     known_args = []
    121 
    122     """ We assume that the first argument not starting with "-" is a program
    123     name and all the following flags should be passed to the program.
    124     TODO(timurrrr): customize optparse instead
    125     """
    126     while len(args) > 0 and args[0][:1] == "-":
    127       arg = args[0]
    128       if (arg == "--"):
    129         break
    130       if self._parser.has_option(arg.split("=")[0]):
    131         known_args += [arg]
    132       else:
    133         self._tool_flags += [arg]
    134       args = args[1:]
    135 
    136     if len(args) > 0:
    137       known_args += args
    138 
    139     self._options, self._args = self._parser.parse_args(known_args)
    140 
    141     self._timeout = int(self._options.timeout)
    142     self._source_dir = self._options.source_dir
    143     if self._options.keep_logs:
    144       # log_parent_dir has trailing slash if non-empty
    145       self.log_dir = self.log_parent_dir + "%s.logs" % self.ToolName()
    146       if os.path.exists(self.log_dir):
    147         shutil.rmtree(self.log_dir)
    148       os.mkdir(self.log_dir)
    149       logging.info("Logs are in " + self.log_dir)
    150 
    151     self._ignore_exit_code = self._options.ignore_exit_code
    152     if self._options.gtest_filter != "":
    153       self._args.append("--gtest_filter=%s" % self._options.gtest_filter)
    154     if self._options.gtest_repeat:
    155       self._args.append("--gtest_repeat=%s" % self._options.gtest_repeat)
    156     if self._options.gtest_print_time:
    157       self._args.append("--gtest_print_time")
    158 
    159     return True
    160 
    161   def Setup(self, args):
    162     return self.ParseArgv(args)
    163 
    164   def ToolCommand(self):
    165     raise NotImplementedError, "This method should be implemented " \
    166                                "in the tool-specific subclass"
    167 
    168   def Cleanup(self):
    169     # You may override it in the tool-specific subclass
    170     pass
    171 
    172   def Execute(self):
    173     """ Execute the app to be tested after successful instrumentation.
    174     Full execution command-line provided by subclassers via proc."""
    175     logging.info("starting execution...")
    176     proc = self.ToolCommand()
    177     for var in self._env:
    178       common.PutEnvAndLog(var, self._env[var])
    179     return common.RunSubprocess(proc, self._timeout)
    180 
    181   def RunTestsAndAnalyze(self, check_sanity):
    182     exec_retcode = self.Execute()
    183     analyze_retcode = self.Analyze(check_sanity)
    184 
    185     if analyze_retcode:
    186       logging.error("Analyze failed.")
    187       logging.info("Search the log for '[ERROR]' to see the error reports.")
    188       return analyze_retcode
    189 
    190     if exec_retcode:
    191       if self._ignore_exit_code:
    192         logging.info("Test execution failed, but the exit code is ignored.")
    193       else:
    194         logging.error("Test execution failed.")
    195         return exec_retcode
    196     else:
    197       logging.info("Test execution completed successfully.")
    198 
    199     if not analyze_retcode:
    200       logging.info("Analysis completed successfully.")
    201 
    202     return 0
    203 
    204   def Main(self, args, check_sanity, min_runtime_in_seconds):
    205     """Call this to run through the whole process: Setup, Execute, Analyze"""
    206     start_time = datetime.datetime.now()
    207     retcode = -1
    208     if self.Setup(args):
    209       retcode = self.RunTestsAndAnalyze(check_sanity)
    210       shutil.rmtree(self.temp_dir, ignore_errors=True)
    211       self.Cleanup()
    212     else:
    213       logging.error("Setup failed")
    214     end_time = datetime.datetime.now()
    215     runtime_in_seconds = (end_time - start_time).seconds
    216     hours = runtime_in_seconds / 3600
    217     seconds = runtime_in_seconds % 3600
    218     minutes = seconds / 60
    219     seconds = seconds % 60
    220     logging.info("elapsed time: %02d:%02d:%02d" % (hours, minutes, seconds))
    221     if (min_runtime_in_seconds > 0 and
    222         runtime_in_seconds < min_runtime_in_seconds):
    223       logging.error("Layout tests finished too quickly. "
    224                     "It should have taken at least %d seconds. "
    225                     "Something went wrong?" % min_runtime_in_seconds)
    226       retcode = -1
    227     return retcode
    228 
    229   def Run(self, args, module, min_runtime_in_seconds=0):
    230     MODULES_TO_SANITY_CHECK = ["base"]
    231 
    232     # TODO(timurrrr): this is a temporary workaround for http://crbug.com/47844
    233     if self.ToolName() == "tsan" and common.IsMac():
    234       MODULES_TO_SANITY_CHECK = []
    235 
    236     check_sanity = module in MODULES_TO_SANITY_CHECK
    237     return self.Main(args, check_sanity, min_runtime_in_seconds)
    238 
    239 
    240 class ValgrindTool(BaseTool):
    241   """Abstract class for running Valgrind tools.
    242 
    243   Always subclass this and implement ToolSpecificFlags() and
    244   ExtendOptionParser() for tool-specific stuff.
    245   """
    246   def __init__(self):
    247     super(ValgrindTool, self).__init__()
    248     self.RegisterOptionParserHook(ValgrindTool.ExtendOptionParser)
    249 
    250   def UseXML(self):
    251     # Override if tool prefers nonxml output
    252     return True
    253 
    254   def SelfContained(self):
    255     # Returns true iff the tool is distibuted as a self-contained
    256     # .sh script (e.g. ThreadSanitizer)
    257     return False
    258 
    259   def ExtendOptionParser(self, parser):
    260     parser.add_option("", "--suppressions", default=[],
    261                             action="append",
    262                             help="path to a valgrind suppression file")
    263     parser.add_option("", "--indirect", action="store_true",
    264                             default=False,
    265                             help="set BROWSER_WRAPPER rather than "
    266                                  "running valgrind directly")
    267     parser.add_option("", "--indirect_webkit_layout", action="store_true",
    268                             default=False,
    269                             help="set --wrapper rather than running Dr. Memory "
    270                                  "directly.")
    271     parser.add_option("", "--trace_children", action="store_true",
    272                             default=False,
    273                             help="also trace child processes")
    274     parser.add_option("", "--num-callers",
    275                             dest="num_callers", default=30,
    276                             help="number of callers to show in stack traces")
    277     parser.add_option("", "--generate_dsym", action="store_true",
    278                           default=False,
    279                           help="Generate .dSYM file on Mac if needed. Slow!")
    280 
    281   def Setup(self, args):
    282     if not BaseTool.Setup(self, args):
    283       return False
    284     if common.IsMac():
    285       self.PrepareForTestMac()
    286     return True
    287 
    288   def PrepareForTestMac(self):
    289     """Runs dsymutil if needed.
    290 
    291     Valgrind for Mac OS X requires that debugging information be in a .dSYM
    292     bundle generated by dsymutil.  It is not currently able to chase DWARF
    293     data into .o files like gdb does, so executables without .dSYM bundles or
    294     with the Chromium-specific "fake_dsym" bundles generated by
    295     build/mac/strip_save_dsym won't give source file and line number
    296     information in valgrind.
    297 
    298     This function will run dsymutil if the .dSYM bundle is missing or if
    299     it looks like a fake_dsym.  A non-fake dsym that already exists is assumed
    300     to be up-to-date.
    301     """
    302     test_command = self._args[0]
    303     dsym_bundle = self._args[0] + '.dSYM'
    304     dsym_file = os.path.join(dsym_bundle, 'Contents', 'Resources', 'DWARF',
    305                              os.path.basename(test_command))
    306     dsym_info_plist = os.path.join(dsym_bundle, 'Contents', 'Info.plist')
    307 
    308     needs_dsymutil = True
    309     saved_test_command = None
    310 
    311     if os.path.exists(dsym_file) and os.path.exists(dsym_info_plist):
    312       # Look for the special fake_dsym tag in dsym_info_plist.
    313       dsym_info_plist_contents = open(dsym_info_plist).read()
    314 
    315       if not re.search('^\s*<key>fake_dsym</key>$', dsym_info_plist_contents,
    316                        re.MULTILINE):
    317         # fake_dsym is not set, this is a real .dSYM bundle produced by
    318         # dsymutil.  dsymutil does not need to be run again.
    319         needs_dsymutil = False
    320       else:
    321         # fake_dsym is set.  dsym_file is a copy of the original test_command
    322         # before it was stripped.  Copy it back to test_command so that
    323         # dsymutil has unstripped input to work with.  Move the stripped
    324         # test_command out of the way, it will be restored when this is
    325         # done.
    326         saved_test_command = test_command + '.stripped'
    327         os.rename(test_command, saved_test_command)
    328         shutil.copyfile(dsym_file, test_command)
    329         shutil.copymode(saved_test_command, test_command)
    330 
    331     if needs_dsymutil:
    332       if self._options.generate_dsym:
    333         # Remove the .dSYM bundle if it exists.
    334         shutil.rmtree(dsym_bundle, True)
    335 
    336         dsymutil_command = ['dsymutil', test_command]
    337 
    338         # dsymutil is crazy slow.  Ideally we'd have a timeout here,
    339         # but common.RunSubprocess' timeout is only checked
    340         # after each line of output; dsymutil is silent
    341         # until the end, and is then killed, which is silly.
    342         common.RunSubprocess(dsymutil_command)
    343 
    344         if saved_test_command:
    345           os.rename(saved_test_command, test_command)
    346       else:
    347         logging.info("No real .dSYM for test_command.  Line numbers will "
    348                      "not be shown.  Either tell xcode to generate .dSYM "
    349                      "file, or use --generate_dsym option to this tool.")
    350 
    351   def ToolCommand(self):
    352     """Get the valgrind command to run."""
    353     # Note that self._args begins with the exe to be run.
    354     tool_name = self.ToolName()
    355 
    356     # Construct the valgrind command.
    357     if self.SelfContained():
    358       proc = ["valgrind-%s.sh" % tool_name]
    359     else:
    360       if 'CHROME_VALGRIND' in os.environ:
    361         path = os.path.join(os.environ['CHROME_VALGRIND'], "bin", "valgrind")
    362       else:
    363         path = "valgrind"
    364       proc = [path, "--tool=%s" % tool_name]
    365 
    366     proc += ["--num-callers=%i" % int(self._options.num_callers)]
    367 
    368     if self._options.trace_children:
    369       proc += ["--trace-children=yes"]
    370       proc += ["--trace-children-skip='*dbus-daemon*'"]
    371       proc += ["--trace-children-skip='*dbus-launch*'"]
    372       proc += ["--trace-children-skip='*perl*'"]
    373       proc += ["--trace-children-skip='*python*'"]
    374       # This is really Python, but for some reason Valgrind follows it.
    375       proc += ["--trace-children-skip='*lsb_release*'"]
    376 
    377     proc += self.ToolSpecificFlags()
    378     proc += self._tool_flags
    379 
    380     suppression_count = 0
    381     for suppression_file in self._options.suppressions:
    382       if os.path.exists(suppression_file):
    383         suppression_count += 1
    384         proc += ["--suppressions=%s" % suppression_file]
    385 
    386     if not suppression_count:
    387       logging.warning("WARNING: NOT USING SUPPRESSIONS!")
    388 
    389     logfilename = self.log_dir + ("/%s." % tool_name) + "%p"
    390     if self.UseXML():
    391       proc += ["--xml=yes", "--xml-file=" + logfilename]
    392     else:
    393       proc += ["--log-file=" + logfilename]
    394 
    395     # The Valgrind command is constructed.
    396 
    397     # Valgrind doesn't play nice with the Chrome sandbox.  Empty this env var
    398     # set by runtest.py to disable the sandbox.
    399     if os.environ.get("CHROME_DEVEL_SANDBOX", None):
    400       logging.info("Removing CHROME_DEVEL_SANDBOX fron environment")
    401       os.environ["CHROME_DEVEL_SANDBOX"] = ''
    402 
    403     # Handle --indirect_webkit_layout separately.
    404     if self._options.indirect_webkit_layout:
    405       # Need to create the wrapper before modifying |proc|.
    406       wrapper = self.CreateBrowserWrapper(proc, webkit=True)
    407       proc = self._args
    408       proc.append("--wrapper")
    409       proc.append(wrapper)
    410       return proc
    411 
    412     if self._options.indirect:
    413       wrapper = self.CreateBrowserWrapper(proc)
    414       os.environ["BROWSER_WRAPPER"] = wrapper
    415       logging.info('export BROWSER_WRAPPER=' + wrapper)
    416       proc = []
    417     proc += self._args
    418     return proc
    419 
    420   def ToolSpecificFlags(self):
    421     raise NotImplementedError, "This method should be implemented " \
    422                                "in the tool-specific subclass"
    423 
    424   def CreateBrowserWrapper(self, proc, webkit=False):
    425     """The program being run invokes Python or something else that can't stand
    426     to be valgrinded, and also invokes the Chrome browser. In this case, use a
    427     magic wrapper to only valgrind the Chrome browser. Build the wrapper here.
    428     Returns the path to the wrapper. It's up to the caller to use the wrapper
    429     appropriately.
    430     """
    431     command = " ".join(proc)
    432     # Add the PID of the browser wrapper to the logfile names so we can
    433     # separate log files for different UI tests at the analyze stage.
    434     command = command.replace("%p", "$$.%p")
    435 
    436     (fd, indirect_fname) = tempfile.mkstemp(dir=self.log_dir,
    437                                             prefix="browser_wrapper.",
    438                                             text=True)
    439     f = os.fdopen(fd, "w")
    440     f.write('#!/bin/bash\n'
    441             'echo "Started Valgrind wrapper for this test, PID=$$" >&2\n')
    442 
    443     f.write('DIR=`dirname $0`\n'
    444             'TESTNAME_FILE=$DIR/testcase.$$.name\n\n')
    445 
    446     if webkit:
    447       # Webkit layout_tests pass the URL as the first line of stdin.
    448       f.write('tee $TESTNAME_FILE | %s "$@"\n' % command)
    449     else:
    450       # Try to get the test case name by looking at the program arguments.
    451       # i.e. Chromium ui_tests used --test-name arg.
    452       # TODO(timurrrr): This doesn't handle "--test-name Test.Name"
    453       # TODO(timurrrr): ui_tests are dead. Where do we use the non-webkit
    454       # wrapper now? browser_tests? What do they do?
    455       f.write('for arg in $@\ndo\n'
    456               '  if [[ "$arg" =~ --test-name=(.*) ]]\n  then\n'
    457               '    echo ${BASH_REMATCH[1]} >$TESTNAME_FILE\n'
    458               '  fi\n'
    459               'done\n\n'
    460               '%s "$@"\n' % command)
    461 
    462     f.close()
    463     os.chmod(indirect_fname, stat.S_IRUSR|stat.S_IXUSR)
    464     return indirect_fname
    465 
    466   def CreateAnalyzer(self):
    467     raise NotImplementedError, "This method should be implemented " \
    468                                "in the tool-specific subclass"
    469 
    470   def GetAnalyzeResults(self, check_sanity=False):
    471     # Glob all the files in the log directory
    472     filenames = glob.glob(self.log_dir + "/" + self.ToolName() + ".*")
    473 
    474     # If we have browser wrapper, the logfiles are named as
    475     # "toolname.wrapper_PID.valgrind_PID".
    476     # Let's extract the list of wrapper_PIDs and name it ppids
    477     ppids = set([int(f.split(".")[-2]) \
    478                 for f in filenames if re.search("\.[0-9]+\.[0-9]+$", f)])
    479 
    480     analyzer = self.CreateAnalyzer()
    481     if len(ppids) == 0:
    482       # Fast path - no browser wrapper was set.
    483       return analyzer.Report(filenames, None, check_sanity)
    484 
    485     ret = 0
    486     for ppid in ppids:
    487       testcase_name = None
    488       try:
    489         f = open(self.log_dir + ("/testcase.%d.name" % ppid))
    490         testcase_name = f.read().strip()
    491         f.close()
    492         wk_layout_prefix="third_party/WebKit/LayoutTests/"
    493         wk_prefix_at = testcase_name.rfind(wk_layout_prefix)
    494         if wk_prefix_at != -1:
    495           testcase_name = testcase_name[wk_prefix_at + len(wk_layout_prefix):]
    496       except IOError:
    497         pass
    498       print "====================================================="
    499       print " Below is the report for valgrind wrapper PID=%d." % ppid
    500       if testcase_name:
    501         print " It was used while running the `%s` test." % testcase_name
    502       else:
    503         print " You can find the corresponding test"
    504         print " by searching the above log for 'PID=%d'" % ppid
    505       sys.stdout.flush()
    506 
    507       ppid_filenames = [f for f in filenames \
    508                         if re.search("\.%d\.[0-9]+$" % ppid, f)]
    509       # check_sanity won't work with browser wrappers
    510       assert check_sanity == False
    511       ret |= analyzer.Report(ppid_filenames, testcase_name)
    512       print "====================================================="
    513       sys.stdout.flush()
    514 
    515     if ret != 0:
    516       print ""
    517       print "The Valgrind reports are grouped by test names."
    518       print "Each test has its PID printed in the log when the test was run"
    519       print "and at the beginning of its Valgrind report."
    520       print "Hint: you can search for the reports by Ctrl+F -> `=#`"
    521       sys.stdout.flush()
    522 
    523     return ret
    524 
    525 
    526 # TODO(timurrrr): Split into a separate file.
    527 class Memcheck(ValgrindTool):
    528   """Memcheck
    529   Dynamic memory error detector for Linux & Mac
    530 
    531   http://valgrind.org/info/tools.html#memcheck
    532   """
    533 
    534   def __init__(self):
    535     super(Memcheck, self).__init__()
    536     self.RegisterOptionParserHook(Memcheck.ExtendOptionParser)
    537 
    538   def ToolName(self):
    539     return "memcheck"
    540 
    541   def ExtendOptionParser(self, parser):
    542     parser.add_option("--leak-check", "--leak_check", type="string",
    543                       default="yes",  # --leak-check=yes is equivalent of =full
    544                       help="perform leak checking at the end of the run")
    545     parser.add_option("", "--show_all_leaks", action="store_true",
    546                       default=False,
    547                       help="also show less blatant leaks")
    548     parser.add_option("", "--track_origins", action="store_true",
    549                       default=False,
    550                       help="Show whence uninitialized bytes came. 30% slower.")
    551 
    552   def ToolSpecificFlags(self):
    553     ret = ["--gen-suppressions=all", "--demangle=no"]
    554     ret += ["--leak-check=%s" % self._options.leak_check]
    555 
    556     if self._options.show_all_leaks:
    557       ret += ["--show-reachable=yes"]
    558     else:
    559       ret += ["--show-possibly-lost=no"]
    560 
    561     if self._options.track_origins:
    562       ret += ["--track-origins=yes"]
    563 
    564     # TODO(glider): this is a temporary workaround for http://crbug.com/51716
    565     # Let's see whether it helps.
    566     if common.IsMac():
    567       ret += ["--smc-check=all"]
    568 
    569     return ret
    570 
    571   def CreateAnalyzer(self):
    572     use_gdb = common.IsMac()
    573     return memcheck_analyze.MemcheckAnalyzer(self._source_dir,
    574                                             self._options.show_all_leaks,
    575                                             use_gdb=use_gdb)
    576 
    577   def Analyze(self, check_sanity=False):
    578     ret = self.GetAnalyzeResults(check_sanity)
    579 
    580     if ret != 0:
    581       logging.info("Please see http://dev.chromium.org/developers/how-tos/"
    582                    "using-valgrind for the info on Memcheck/Valgrind")
    583     return ret
    584 
    585 
    586 class PinTool(BaseTool):
    587   """Abstract class for running PIN tools.
    588 
    589   Always subclass this and implement ToolSpecificFlags() and
    590   ExtendOptionParser() for tool-specific stuff.
    591   """
    592   def PrepareForTest(self):
    593     pass
    594 
    595   def ToolSpecificFlags(self):
    596     raise NotImplementedError, "This method should be implemented " \
    597                                "in the tool-specific subclass"
    598 
    599   def ToolCommand(self):
    600     """Get the PIN command to run."""
    601 
    602     # Construct the PIN command.
    603     pin_cmd = os.getenv("PIN_COMMAND")
    604     if not pin_cmd:
    605       raise RuntimeError, "Please set PIN_COMMAND environment variable " \
    606                           "with the path to pin.exe"
    607     proc = pin_cmd.split(" ")
    608 
    609     proc += self.ToolSpecificFlags()
    610 
    611     # The PIN command is constructed.
    612 
    613     # PIN requires -- to separate PIN flags from the executable name.
    614     # self._args begins with the exe to be run.
    615     proc += ["--"]
    616 
    617     proc += self._args
    618     return proc
    619 
    620 
    621 class ThreadSanitizerBase(object):
    622   """ThreadSanitizer
    623   Dynamic data race detector for Linux, Mac and Windows.
    624 
    625   http://code.google.com/p/data-race-test/wiki/ThreadSanitizer
    626 
    627   Since TSan works on both Valgrind (Linux, Mac) and PIN (Windows), we need
    628   to have multiple inheritance
    629   """
    630 
    631   INFO_MESSAGE="Please see http://dev.chromium.org/developers/how-tos/" \
    632                "using-valgrind/threadsanitizer for the info on " \
    633                "ThreadSanitizer"
    634 
    635   def __init__(self):
    636     super(ThreadSanitizerBase, self).__init__()
    637     self.RegisterOptionParserHook(ThreadSanitizerBase.ExtendOptionParser)
    638 
    639   def ToolName(self):
    640     return "tsan"
    641 
    642   def UseXML(self):
    643     return False
    644 
    645   def SelfContained(self):
    646     return True
    647 
    648   def ExtendOptionParser(self, parser):
    649     parser.add_option("", "--hybrid", default="no",
    650                       dest="hybrid",
    651                       help="Finds more data races, may give false positive "
    652                       "reports unless the code is annotated")
    653     parser.add_option("", "--announce-threads", default="yes",
    654                       dest="announce_threads",
    655                       help="Show the the stack traces of thread creation")
    656     parser.add_option("", "--free-is-write", default="no",
    657                       dest="free_is_write",
    658                       help="Treat free()/operator delete as memory write. "
    659                       "This helps finding more data races, but (currently) "
    660                       "this may give false positive reports on std::string "
    661                       "internals, see http://code.google.com/p/data-race-test"
    662                       "/issues/detail?id=40")
    663 
    664   def EvalBoolFlag(self, flag_value):
    665     if (flag_value in ["1", "true", "yes"]):
    666       return True
    667     elif (flag_value in ["0", "false", "no"]):
    668       return False
    669     raise RuntimeError, "Can't parse flag value (%s)" % flag_value
    670 
    671   def ToolSpecificFlags(self):
    672     ret = []
    673 
    674     ignore_files = ["ignores.txt"]
    675     for platform_suffix in common.PlatformNames():
    676       ignore_files.append("ignores_%s.txt" % platform_suffix)
    677     for ignore_file in ignore_files:
    678       fullname =  os.path.join(self._source_dir,
    679           "tools", "valgrind", "tsan", ignore_file)
    680       if os.path.exists(fullname):
    681         fullname = common.NormalizeWindowsPath(fullname)
    682         ret += ["--ignore=%s" % fullname]
    683 
    684     # This should shorten filepaths for local builds.
    685     ret += ["--file-prefix-to-cut=%s/" % self._source_dir]
    686 
    687     # This should shorten filepaths on bots.
    688     ret += ["--file-prefix-to-cut=build/src/"]
    689     ret += ["--file-prefix-to-cut=out/Release/../../"]
    690 
    691     # This should shorten filepaths for functions intercepted in TSan.
    692     ret += ["--file-prefix-to-cut=scripts/tsan/tsan/"]
    693     ret += ["--file-prefix-to-cut=src/tsan/tsan/"]
    694 
    695     ret += ["--gen-suppressions=true"]
    696 
    697     if self.EvalBoolFlag(self._options.hybrid):
    698       ret += ["--hybrid=yes"] # "no" is the default value for TSAN
    699 
    700     if self.EvalBoolFlag(self._options.announce_threads):
    701       ret += ["--announce-threads"]
    702 
    703     if self.EvalBoolFlag(self._options.free_is_write):
    704       ret += ["--free-is-write=yes"]
    705     else:
    706       ret += ["--free-is-write=no"]
    707 
    708 
    709     # --show-pc flag is needed for parsing the error logs on Darwin.
    710     if platform_suffix == 'mac':
    711       ret += ["--show-pc=yes"]
    712     ret += ["--show-pid=no"]
    713 
    714     boring_callers = common.BoringCallers(mangled=False, use_re_wildcards=False)
    715     # TODO(timurrrr): In fact, we want "starting from .." instead of "below .."
    716     for bc in boring_callers:
    717       ret += ["--cut_stack_below=%s" % bc]
    718 
    719     return ret
    720 
    721 
    722 class ThreadSanitizerPosix(ThreadSanitizerBase, ValgrindTool):
    723   def ToolSpecificFlags(self):
    724     proc = ThreadSanitizerBase.ToolSpecificFlags(self)
    725     # The -v flag is needed for printing the list of used suppressions and
    726     # obtaining addresses for loaded shared libraries on Mac.
    727     proc += ["-v"]
    728     return proc
    729 
    730   def CreateAnalyzer(self):
    731     use_gdb = common.IsMac()
    732     return tsan_analyze.TsanAnalyzer(use_gdb)
    733 
    734   def Analyze(self, check_sanity=False):
    735     ret = self.GetAnalyzeResults(check_sanity)
    736 
    737     if ret != 0:
    738       logging.info(self.INFO_MESSAGE)
    739     return ret
    740 
    741 
    742 class ThreadSanitizerWindows(ThreadSanitizerBase, PinTool):
    743 
    744   def __init__(self):
    745     super(ThreadSanitizerWindows, self).__init__()
    746     self.RegisterOptionParserHook(ThreadSanitizerWindows.ExtendOptionParser)
    747 
    748   def ExtendOptionParser(self, parser):
    749     parser.add_option("", "--suppressions", default=[],
    750                       action="append",
    751                       help="path to TSan suppression file")
    752 
    753 
    754   def ToolSpecificFlags(self):
    755     add_env = {
    756       "CHROME_ALLOCATOR" : "WINHEAP",
    757     }
    758     for k,v in add_env.iteritems():
    759       logging.info("export %s=%s", k, v)
    760       os.putenv(k, v)
    761 
    762     proc = ThreadSanitizerBase.ToolSpecificFlags(self)
    763     # On PIN, ThreadSanitizer has its own suppression mechanism
    764     # and --log-file flag which work exactly on Valgrind.
    765     suppression_count = 0
    766     for suppression_file in self._options.suppressions:
    767       if os.path.exists(suppression_file):
    768         suppression_count += 1
    769         suppression_file = common.NormalizeWindowsPath(suppression_file)
    770         proc += ["--suppressions=%s" % suppression_file]
    771 
    772     if not suppression_count:
    773       logging.warning("WARNING: NOT USING SUPPRESSIONS!")
    774 
    775     logfilename = self.log_dir + "/tsan.%p"
    776     proc += ["--log-file=" + common.NormalizeWindowsPath(logfilename)]
    777 
    778     # TODO(timurrrr): Add flags for Valgrind trace children analog when we
    779     # start running complex tests (e.g. UI) under TSan/Win.
    780 
    781     return proc
    782 
    783   def Analyze(self, check_sanity=False):
    784     filenames = glob.glob(self.log_dir + "/tsan.*")
    785     analyzer = tsan_analyze.TsanAnalyzer()
    786     ret = analyzer.Report(filenames, None, check_sanity)
    787     if ret != 0:
    788       logging.info(self.INFO_MESSAGE)
    789     return ret
    790 
    791 
    792 class DrMemory(BaseTool):
    793   """Dr.Memory
    794   Dynamic memory error detector for Windows.
    795 
    796   http://dev.chromium.org/developers/how-tos/using-drmemory
    797   It is not very mature at the moment, some things might not work properly.
    798   """
    799 
    800   def __init__(self, full_mode, pattern_mode):
    801     super(DrMemory, self).__init__()
    802     self.full_mode = full_mode
    803     self.pattern_mode = pattern_mode
    804     self.RegisterOptionParserHook(DrMemory.ExtendOptionParser)
    805 
    806   def ToolName(self):
    807     return "drmemory"
    808 
    809   def ExtendOptionParser(self, parser):
    810     parser.add_option("", "--suppressions", default=[],
    811                       action="append",
    812                       help="path to a drmemory suppression file")
    813     parser.add_option("", "--follow_python", action="store_true",
    814                       default=False, dest="follow_python",
    815                       help="Monitor python child processes.  If off, neither "
    816                       "python children nor any children of python children "
    817                       "will be monitored.")
    818     parser.add_option("", "--indirect", action="store_true",
    819                       default=False,
    820                       help="set BROWSER_WRAPPER rather than "
    821                            "running Dr. Memory directly on the harness")
    822     parser.add_option("", "--indirect_webkit_layout", action="store_true",
    823                       default=False,
    824                       help="set --wrapper rather than running valgrind "
    825                       "directly.")
    826     parser.add_option("", "--use_debug", action="store_true",
    827                       default=False, dest="use_debug",
    828                       help="Run Dr. Memory debug build")
    829     parser.add_option("", "--trace_children", action="store_true",
    830                             default=True,
    831                             help="TODO: default value differs from Valgrind")
    832 
    833   def ToolCommand(self):
    834     """Get the tool command to run."""
    835     # WINHEAP is what Dr. Memory supports as there are issues w/ both
    836     # jemalloc (http://code.google.com/p/drmemory/issues/detail?id=320) and
    837     # tcmalloc (http://code.google.com/p/drmemory/issues/detail?id=314)
    838     add_env = {
    839       "CHROME_ALLOCATOR" : "WINHEAP",
    840       "JSIMD_FORCEMMX"   : "1",  # http://code.google.com/p/drmemory/issues/detail?id=540
    841     }
    842     for k,v in add_env.iteritems():
    843       logging.info("export %s=%s", k, v)
    844       os.putenv(k, v)
    845 
    846     drmem_cmd = os.getenv("DRMEMORY_COMMAND")
    847     if not drmem_cmd:
    848       raise RuntimeError, "Please set DRMEMORY_COMMAND environment variable " \
    849                           "with the path to drmemory.exe"
    850     proc = drmem_cmd.split(" ")
    851 
    852     # By default, don't run python (this will exclude python's children as well)
    853     # to reduce runtime.  We're not really interested in spending time finding
    854     # bugs in the python implementation.
    855     # With file-based config we must update the file every time, and
    856     # it will affect simultaneous drmem uses by this user.  While file-based
    857     # config has many advantages, here we may want this-instance-only
    858     # (http://code.google.com/p/drmemory/issues/detail?id=334).
    859     drconfig_cmd = [ proc[0].replace("drmemory.exe", "drconfig.exe") ]
    860     drconfig_cmd += ["-quiet"] # suppress errors about no 64-bit libs
    861     run_drconfig = True
    862     if self._options.follow_python:
    863       logging.info("Following python children")
    864       # -unreg fails if not already registered so query for that first
    865       query_cmd = drconfig_cmd + ["-isreg", "python.exe"]
    866       query_proc = subprocess.Popen(query_cmd, stdout=subprocess.PIPE,
    867                                     shell=True)
    868       (query_out, query_err) = query_proc.communicate()
    869       if re.search("exe not registered", query_out):
    870         run_drconfig = False # all set
    871       else:
    872         drconfig_cmd += ["-unreg", "python.exe"]
    873     else:
    874       logging.info("Excluding python children")
    875       drconfig_cmd += ["-reg", "python.exe", "-norun"]
    876     if run_drconfig:
    877       drconfig_retcode = common.RunSubprocess(drconfig_cmd, self._timeout)
    878       if drconfig_retcode:
    879         logging.error("Configuring whether to follow python children failed " \
    880                       "with %d.", drconfig_retcode)
    881         raise RuntimeError, "Configuring python children failed "
    882 
    883     suppression_count = 0
    884     supp_files = self._options.suppressions
    885     if self.full_mode:
    886       supp_files += [s.replace(".txt", "_full.txt") for s in supp_files]
    887     for suppression_file in supp_files:
    888       if os.path.exists(suppression_file):
    889         suppression_count += 1
    890         proc += ["-suppress", common.NormalizeWindowsPath(suppression_file)]
    891 
    892     if not suppression_count:
    893       logging.warning("WARNING: NOT USING SUPPRESSIONS!")
    894 
    895     # Un-comment to dump Dr.Memory events on error
    896     #proc += ["-dr_ops", "-dumpcore_mask", "-dr_ops", "0x8bff"]
    897 
    898     # Un-comment and comment next line to debug Dr.Memory
    899     #proc += ["-dr_ops", "-no_hide"]
    900     #proc += ["-dr_ops", "-msgbox_mask", "-dr_ops", "15"]
    901     #Proc += ["-dr_ops", "-stderr_mask", "-dr_ops", "15"]
    902     # Ensure we see messages about Dr. Memory crashing!
    903     proc += ["-dr_ops", "-stderr_mask", "-dr_ops", "12"]
    904 
    905     if self._options.use_debug:
    906       proc += ["-debug"]
    907 
    908     proc += ["-logdir", common.NormalizeWindowsPath(self.log_dir)]
    909 
    910     if self.log_parent_dir:
    911       # gpu process on Windows Vista+ runs at Low Integrity and can only
    912       # write to certain directories (http://crbug.com/119131)
    913       symcache_dir = os.path.join(self.log_parent_dir, "drmemory.symcache")
    914     elif self._options.build_dir:
    915       # The other case is only possible with -t cmdline.
    916       # Anyways, if we omit -symcache_dir the -logdir's value is used which
    917       # should be fine.
    918       symcache_dir = os.path.join(self._options.build_dir, "drmemory.symcache")
    919     if symcache_dir:
    920       if not os.path.exists(symcache_dir):
    921         try:
    922           os.mkdir(symcache_dir)
    923         except OSError:
    924           logging.warning("Can't create symcache dir?")
    925       if os.path.exists(symcache_dir):
    926         proc += ["-symcache_dir", common.NormalizeWindowsPath(symcache_dir)]
    927 
    928     # Use -no_summary to suppress DrMemory's summary and init-time
    929     # notifications.  We generate our own with drmemory_analyze.py.
    930     proc += ["-batch", "-no_summary"]
    931 
    932     # Un-comment to disable interleaved output.  Will also suppress error
    933     # messages normally printed to stderr.
    934     #proc += ["-quiet", "-no_results_to_stderr"]
    935 
    936     proc += ["-callstack_max_frames", "40"]
    937 
    938     # disable leak scan for now
    939     proc += ["-no_count_leaks", "-no_leak_scan"]
    940 
    941     # make callstacks easier to read
    942     proc += ["-callstack_srcfile_prefix",
    943              "build\\src,chromium\\src,crt_build\\self_x86"]
    944     proc += ["-callstack_modname_hide",
    945              "*drmemory*,chrome.dll"]
    946 
    947     boring_callers = common.BoringCallers(mangled=False, use_re_wildcards=False)
    948     # TODO(timurrrr): In fact, we want "starting from .." instead of "below .."
    949     proc += ["-callstack_truncate_below", ",".join(boring_callers)]
    950 
    951     if self.pattern_mode:
    952       proc += ["-pattern", "0xf1fd", "-no_count_leaks", "-redzone_size", "0x20"]
    953     elif not self.full_mode:
    954       proc += ["-light"]
    955 
    956     proc += self._tool_flags
    957 
    958     # DrM i#850/851: The new -callstack_use_top_fp_selectively has bugs.
    959     proc += ["-no_callstack_use_top_fp_selectively"]
    960 
    961     # Dr.Memory requires -- to separate tool flags from the executable name.
    962     proc += ["--"]
    963 
    964     if self._options.indirect or self._options.indirect_webkit_layout:
    965       # TODO(timurrrr): reuse for TSan on Windows
    966       wrapper_path = os.path.join(self._source_dir,
    967                                   "tools", "valgrind", "browser_wrapper_win.py")
    968       wrapper = " ".join(["python", wrapper_path] + proc)
    969       self.CreateBrowserWrapper(wrapper)
    970       logging.info("browser wrapper = " + " ".join(proc))
    971       if self._options.indirect_webkit_layout:
    972         proc = self._args
    973         # Layout tests want forward slashes.
    974         wrapper = wrapper.replace('\\', '/')
    975         proc += ["--wrapper", wrapper]
    976         return proc
    977       else:
    978         proc = []
    979 
    980     # Note that self._args begins with the name of the exe to be run.
    981     self._args[0] = common.NormalizeWindowsPath(self._args[0])
    982     proc += self._args
    983     return proc
    984 
    985   def CreateBrowserWrapper(self, command):
    986     os.putenv("BROWSER_WRAPPER", command)
    987 
    988   def Analyze(self, check_sanity=False):
    989     # Use one analyzer for all the log files to avoid printing duplicate reports
    990     #
    991     # TODO(timurrrr): unify this with Valgrind and other tools when we have
    992     # http://code.google.com/p/drmemory/issues/detail?id=684
    993     analyzer = drmemory_analyze.DrMemoryAnalyzer()
    994 
    995     ret = 0
    996     if not self._options.indirect and not self._options.indirect_webkit_layout:
    997       filenames = glob.glob(self.log_dir + "/*/results.txt")
    998 
    999       ret = analyzer.Report(filenames, None, check_sanity)
   1000     else:
   1001       testcases = glob.glob(self.log_dir + "/testcase.*.logs")
   1002       # If we have browser wrapper, the per-test logdirs are named as
   1003       # "testcase.wrapper_PID.name".
   1004       # Let's extract the list of wrapper_PIDs and name it ppids.
   1005       # NOTE: ppids may contain '_', i.e. they are not ints!
   1006       ppids = set([f.split(".")[-2] for f in testcases])
   1007 
   1008       for ppid in ppids:
   1009         testcase_name = None
   1010         try:
   1011           f = open("%s/testcase.%s.name" % (self.log_dir, ppid))
   1012           testcase_name = f.read().strip()
   1013           f.close()
   1014         except IOError:
   1015           pass
   1016         print "====================================================="
   1017         print " Below is the report for drmemory wrapper PID=%s." % ppid
   1018         if testcase_name:
   1019           print " It was used while running the `%s` test." % testcase_name
   1020         else:
   1021           # TODO(timurrrr): hm, the PID line is suppressed on Windows...
   1022           print " You can find the corresponding test"
   1023           print " by searching the above log for 'PID=%s'" % ppid
   1024         sys.stdout.flush()
   1025         ppid_filenames = glob.glob("%s/testcase.%s.logs/*/results.txt" %
   1026                                    (self.log_dir, ppid))
   1027         ret |= analyzer.Report(ppid_filenames, testcase_name, False)
   1028         print "====================================================="
   1029         sys.stdout.flush()
   1030 
   1031     logging.info("Please see http://dev.chromium.org/developers/how-tos/"
   1032                  "using-drmemory for the info on Dr. Memory")
   1033     return ret
   1034 
   1035 
   1036 # RaceVerifier support. See
   1037 # http://code.google.com/p/data-race-test/wiki/RaceVerifier for more details.
   1038 class ThreadSanitizerRV1Analyzer(tsan_analyze.TsanAnalyzer):
   1039   """ TsanAnalyzer that saves race reports to a file. """
   1040 
   1041   TMP_FILE = "rvlog.tmp"
   1042 
   1043   def __init__(self, source_dir, use_gdb):
   1044     super(ThreadSanitizerRV1Analyzer, self).__init__(use_gdb)
   1045     self.out = open(self.TMP_FILE, "w")
   1046 
   1047   def Report(self, files, testcase, check_sanity=False):
   1048     reports = self.GetReports(files)
   1049     for report in reports:
   1050       print >>self.out, report
   1051     if len(reports) > 0:
   1052       logging.info("RaceVerifier pass 1 of 2, found %i reports" % len(reports))
   1053       return -1
   1054     return 0
   1055 
   1056   def CloseOutputFile(self):
   1057     self.out.close()
   1058 
   1059 
   1060 class ThreadSanitizerRV1Mixin(object):
   1061   """RaceVerifier first pass.
   1062 
   1063   Runs ThreadSanitizer as usual, but hides race reports and collects them in a
   1064   temporary file"""
   1065 
   1066   def __init__(self):
   1067     super(ThreadSanitizerRV1Mixin, self).__init__()
   1068     self.RegisterOptionParserHook(ThreadSanitizerRV1Mixin.ExtendOptionParser)
   1069 
   1070   def ExtendOptionParser(self, parser):
   1071     parser.set_defaults(hybrid="yes")
   1072 
   1073   def CreateAnalyzer(self):
   1074     use_gdb = common.IsMac()
   1075     self.analyzer = ThreadSanitizerRV1Analyzer(self._source_dir, use_gdb)
   1076     return self.analyzer
   1077 
   1078   def Cleanup(self):
   1079     super(ThreadSanitizerRV1Mixin, self).Cleanup()
   1080     self.analyzer.CloseOutputFile()
   1081 
   1082 
   1083 class ThreadSanitizerRV2Mixin(object):
   1084   """RaceVerifier second pass."""
   1085 
   1086   def __init__(self):
   1087     super(ThreadSanitizerRV2Mixin, self).__init__()
   1088     self.RegisterOptionParserHook(ThreadSanitizerRV2Mixin.ExtendOptionParser)
   1089 
   1090   def ExtendOptionParser(self, parser):
   1091     parser.add_option("", "--race-verifier-sleep-ms",
   1092                             dest="race_verifier_sleep_ms", default=10,
   1093                             help="duration of RaceVerifier delays")
   1094 
   1095   def ToolSpecificFlags(self):
   1096     proc = super(ThreadSanitizerRV2Mixin, self).ToolSpecificFlags()
   1097     proc += ['--race-verifier=%s' % ThreadSanitizerRV1Analyzer.TMP_FILE,
   1098              '--race-verifier-sleep-ms=%d' %
   1099              int(self._options.race_verifier_sleep_ms)]
   1100     return proc
   1101 
   1102   def Cleanup(self):
   1103     super(ThreadSanitizerRV2Mixin, self).Cleanup()
   1104     os.unlink(ThreadSanitizerRV1Analyzer.TMP_FILE)
   1105 
   1106 
   1107 class ThreadSanitizerRV1Posix(ThreadSanitizerRV1Mixin, ThreadSanitizerPosix):
   1108   pass
   1109 
   1110 
   1111 class ThreadSanitizerRV2Posix(ThreadSanitizerRV2Mixin, ThreadSanitizerPosix):
   1112   pass
   1113 
   1114 
   1115 class ThreadSanitizerRV1Windows(ThreadSanitizerRV1Mixin,
   1116                                 ThreadSanitizerWindows):
   1117   pass
   1118 
   1119 
   1120 class ThreadSanitizerRV2Windows(ThreadSanitizerRV2Mixin,
   1121                                 ThreadSanitizerWindows):
   1122   pass
   1123 
   1124 
   1125 class RaceVerifier(object):
   1126   """Runs tests under RaceVerifier/Valgrind."""
   1127 
   1128   MORE_INFO_URL = "http://code.google.com/p/data-race-test/wiki/RaceVerifier"
   1129 
   1130   def RV1Factory(self):
   1131     if common.IsWindows():
   1132       return ThreadSanitizerRV1Windows()
   1133     else:
   1134       return ThreadSanitizerRV1Posix()
   1135 
   1136   def RV2Factory(self):
   1137     if common.IsWindows():
   1138       return ThreadSanitizerRV2Windows()
   1139     else:
   1140       return ThreadSanitizerRV2Posix()
   1141 
   1142   def ToolName(self):
   1143     return "tsan"
   1144 
   1145   def Main(self, args, check_sanity, min_runtime_in_seconds):
   1146     logging.info("Running a TSan + RaceVerifier test. For more information, " +
   1147                  "see " + self.MORE_INFO_URL)
   1148     cmd1 = self.RV1Factory()
   1149     ret = cmd1.Main(args, check_sanity, min_runtime_in_seconds)
   1150     # Verify race reports, if there are any.
   1151     if ret == -1:
   1152       logging.info("Starting pass 2 of 2. Running the same binary in " +
   1153                    "RaceVerifier mode to confirm possible race reports.")
   1154       logging.info("For more information, see " + self.MORE_INFO_URL)
   1155       cmd2 = self.RV2Factory()
   1156       ret = cmd2.Main(args, check_sanity, min_runtime_in_seconds)
   1157     else:
   1158       logging.info("No reports, skipping RaceVerifier second pass")
   1159     logging.info("Please see " + self.MORE_INFO_URL + " for more information " +
   1160                  "on RaceVerifier")
   1161     return ret
   1162 
   1163   def Run(self, args, module, min_runtime_in_seconds=0):
   1164    return self.Main(args, False, min_runtime_in_seconds)
   1165 
   1166 
   1167 class EmbeddedTool(BaseTool):
   1168   """Abstract class for tools embedded directly into the test binary.
   1169   """
   1170   # TODO(glider): need to override Execute() and support process chaining here.
   1171 
   1172   def ToolCommand(self):
   1173     # In the simplest case just the args of the script.
   1174     return self._args
   1175 
   1176 
   1177 class Asan(EmbeddedTool):
   1178   """AddressSanitizer, a memory error detector.
   1179 
   1180   More information at
   1181   http://dev.chromium.org/developers/testing/addresssanitizer
   1182   """
   1183   def __init__(self):
   1184     super(Asan, self).__init__()
   1185     self._timeout = 1200
   1186     if common.IsMac():
   1187       self._env["DYLD_NO_PIE"] = "1"
   1188 
   1189 
   1190   def ToolName(self):
   1191     return "asan"
   1192 
   1193   def ToolCommand(self):
   1194     # TODO(glider): use pipes instead of the ugly wrapper here once they
   1195     # are supported.
   1196     procs = [os.path.join(self._source_dir, "tools", "valgrind",
   1197                               "asan", "asan_wrapper.sh")]
   1198     procs.extend(self._args)
   1199     return procs
   1200 
   1201   def Analyze(sels, unused_check_sanity):
   1202     return 0
   1203 
   1204 
   1205 class ToolFactory:
   1206   def Create(self, tool_name):
   1207     if tool_name == "memcheck":
   1208       return Memcheck()
   1209     if tool_name == "tsan":
   1210       if common.IsWindows():
   1211         return ThreadSanitizerWindows()
   1212       else:
   1213         return ThreadSanitizerPosix()
   1214     if tool_name == "drmemory" or tool_name == "drmemory_light":
   1215       # TODO(timurrrr): remove support for "drmemory" when buildbots are
   1216       # switched to drmemory_light OR make drmemory==drmemory_full the default
   1217       # mode when the tool is mature enough.
   1218       return DrMemory(False, False)
   1219     if tool_name == "drmemory_full":
   1220       return DrMemory(True, False)
   1221     if tool_name == "drmemory_pattern":
   1222       return DrMemory(False, True)
   1223     if tool_name == "tsan_rv":
   1224       return RaceVerifier()
   1225     if tool_name == "asan":
   1226       return Asan()
   1227     try:
   1228       platform_name = common.PlatformNames()[0]
   1229     except common.NotImplementedError:
   1230       platform_name = sys.platform + "(Unknown)"
   1231     raise RuntimeError, "Unknown tool (tool=%s, platform=%s)" % (tool_name,
   1232                                                                  platform_name)
   1233 
   1234 def CreateTool(tool):
   1235   return ToolFactory().Create(tool)
   1236