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 from 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     # crbug.com/413215, no heap mismatch check for Windows release build binary
    942     if common.IsWindows() and "Release" in self._options.build_dir:
    943         proc += ["-no_check_delete_mismatch"]
    944 
    945     # make callstacks easier to read
    946     proc += ["-callstack_srcfile_prefix",
    947              "build\\src,chromium\\src,crt_build\\self_x86"]
    948     proc += ["-callstack_modname_hide",
    949              "*drmemory*,chrome.dll"]
    950 
    951     boring_callers = common.BoringCallers(mangled=False, use_re_wildcards=False)
    952     # TODO(timurrrr): In fact, we want "starting from .." instead of "below .."
    953     proc += ["-callstack_truncate_below", ",".join(boring_callers)]
    954 
    955     if self.pattern_mode:
    956       proc += ["-pattern", "0xf1fd", "-no_count_leaks", "-redzone_size", "0x20"]
    957     elif not self.full_mode:
    958       proc += ["-light"]
    959 
    960     proc += self._tool_flags
    961 
    962     # Dr.Memory requires -- to separate tool flags from the executable name.
    963     proc += ["--"]
    964 
    965     if self._options.indirect or self._options.indirect_webkit_layout:
    966       # TODO(timurrrr): reuse for TSan on Windows
    967       wrapper_path = os.path.join(self._source_dir,
    968                                   "tools", "valgrind", "browser_wrapper_win.py")
    969       wrapper = " ".join(["python", wrapper_path] + proc)
    970       self.CreateBrowserWrapper(wrapper)
    971       logging.info("browser wrapper = " + " ".join(proc))
    972       if self._options.indirect_webkit_layout:
    973         proc = self._args
    974         # Layout tests want forward slashes.
    975         wrapper = wrapper.replace('\\', '/')
    976         proc += ["--wrapper", wrapper]
    977         return proc
    978       else:
    979         proc = []
    980 
    981     # Note that self._args begins with the name of the exe to be run.
    982     self._args[0] = common.NormalizeWindowsPath(self._args[0])
    983     proc += self._args
    984     return proc
    985 
    986   def CreateBrowserWrapper(self, command):
    987     os.putenv("BROWSER_WRAPPER", command)
    988 
    989   def Analyze(self, check_sanity=False):
    990     # Use one analyzer for all the log files to avoid printing duplicate reports
    991     #
    992     # TODO(timurrrr): unify this with Valgrind and other tools when we have
    993     # http://code.google.com/p/drmemory/issues/detail?id=684
    994     analyzer = drmemory_analyze.DrMemoryAnalyzer()
    995 
    996     ret = 0
    997     if not self._options.indirect and not self._options.indirect_webkit_layout:
    998       filenames = glob.glob(self.log_dir + "/*/results.txt")
    999 
   1000       ret = analyzer.Report(filenames, None, check_sanity)
   1001     else:
   1002       testcases = glob.glob(self.log_dir + "/testcase.*.logs")
   1003       # If we have browser wrapper, the per-test logdirs are named as
   1004       # "testcase.wrapper_PID.name".
   1005       # Let's extract the list of wrapper_PIDs and name it ppids.
   1006       # NOTE: ppids may contain '_', i.e. they are not ints!
   1007       ppids = set([f.split(".")[-2] for f in testcases])
   1008 
   1009       for ppid in ppids:
   1010         testcase_name = None
   1011         try:
   1012           f = open("%s/testcase.%s.name" % (self.log_dir, ppid))
   1013           testcase_name = f.read().strip()
   1014           f.close()
   1015         except IOError:
   1016           pass
   1017         print "====================================================="
   1018         print " Below is the report for drmemory wrapper PID=%s." % ppid
   1019         if testcase_name:
   1020           print " It was used while running the `%s` test." % testcase_name
   1021         else:
   1022           # TODO(timurrrr): hm, the PID line is suppressed on Windows...
   1023           print " You can find the corresponding test"
   1024           print " by searching the above log for 'PID=%s'" % ppid
   1025         sys.stdout.flush()
   1026         ppid_filenames = glob.glob("%s/testcase.%s.logs/*/results.txt" %
   1027                                    (self.log_dir, ppid))
   1028         ret |= analyzer.Report(ppid_filenames, testcase_name, False)
   1029         print "====================================================="
   1030         sys.stdout.flush()
   1031 
   1032     logging.info("Please see http://dev.chromium.org/developers/how-tos/"
   1033                  "using-drmemory for the info on Dr. Memory")
   1034     return ret
   1035 
   1036 
   1037 # RaceVerifier support. See
   1038 # http://code.google.com/p/data-race-test/wiki/RaceVerifier for more details.
   1039 class ThreadSanitizerRV1Analyzer(tsan_analyze.TsanAnalyzer):
   1040   """ TsanAnalyzer that saves race reports to a file. """
   1041 
   1042   TMP_FILE = "rvlog.tmp"
   1043 
   1044   def __init__(self, source_dir, use_gdb):
   1045     super(ThreadSanitizerRV1Analyzer, self).__init__(use_gdb)
   1046     self.out = open(self.TMP_FILE, "w")
   1047 
   1048   def Report(self, files, testcase, check_sanity=False):
   1049     reports = self.GetReports(files)
   1050     for report in reports:
   1051       print >>self.out, report
   1052     if len(reports) > 0:
   1053       logging.info("RaceVerifier pass 1 of 2, found %i reports" % len(reports))
   1054       return -1
   1055     return 0
   1056 
   1057   def CloseOutputFile(self):
   1058     self.out.close()
   1059 
   1060 
   1061 class ThreadSanitizerRV1Mixin(object):
   1062   """RaceVerifier first pass.
   1063 
   1064   Runs ThreadSanitizer as usual, but hides race reports and collects them in a
   1065   temporary file"""
   1066 
   1067   def __init__(self):
   1068     super(ThreadSanitizerRV1Mixin, self).__init__()
   1069     self.RegisterOptionParserHook(ThreadSanitizerRV1Mixin.ExtendOptionParser)
   1070 
   1071   def ExtendOptionParser(self, parser):
   1072     parser.set_defaults(hybrid="yes")
   1073 
   1074   def CreateAnalyzer(self):
   1075     use_gdb = common.IsMac()
   1076     self.analyzer = ThreadSanitizerRV1Analyzer(self._source_dir, use_gdb)
   1077     return self.analyzer
   1078 
   1079   def Cleanup(self):
   1080     super(ThreadSanitizerRV1Mixin, self).Cleanup()
   1081     self.analyzer.CloseOutputFile()
   1082 
   1083 
   1084 class ThreadSanitizerRV2Mixin(object):
   1085   """RaceVerifier second pass."""
   1086 
   1087   def __init__(self):
   1088     super(ThreadSanitizerRV2Mixin, self).__init__()
   1089     self.RegisterOptionParserHook(ThreadSanitizerRV2Mixin.ExtendOptionParser)
   1090 
   1091   def ExtendOptionParser(self, parser):
   1092     parser.add_option("", "--race-verifier-sleep-ms",
   1093                             dest="race_verifier_sleep_ms", default=10,
   1094                             help="duration of RaceVerifier delays")
   1095 
   1096   def ToolSpecificFlags(self):
   1097     proc = super(ThreadSanitizerRV2Mixin, self).ToolSpecificFlags()
   1098     proc += ['--race-verifier=%s' % ThreadSanitizerRV1Analyzer.TMP_FILE,
   1099              '--race-verifier-sleep-ms=%d' %
   1100              int(self._options.race_verifier_sleep_ms)]
   1101     return proc
   1102 
   1103   def Cleanup(self):
   1104     super(ThreadSanitizerRV2Mixin, self).Cleanup()
   1105     os.unlink(ThreadSanitizerRV1Analyzer.TMP_FILE)
   1106 
   1107 
   1108 class ThreadSanitizerRV1Posix(ThreadSanitizerRV1Mixin, ThreadSanitizerPosix):
   1109   pass
   1110 
   1111 
   1112 class ThreadSanitizerRV2Posix(ThreadSanitizerRV2Mixin, ThreadSanitizerPosix):
   1113   pass
   1114 
   1115 
   1116 class ThreadSanitizerRV1Windows(ThreadSanitizerRV1Mixin,
   1117                                 ThreadSanitizerWindows):
   1118   pass
   1119 
   1120 
   1121 class ThreadSanitizerRV2Windows(ThreadSanitizerRV2Mixin,
   1122                                 ThreadSanitizerWindows):
   1123   pass
   1124 
   1125 
   1126 class RaceVerifier(object):
   1127   """Runs tests under RaceVerifier/Valgrind."""
   1128 
   1129   MORE_INFO_URL = "http://code.google.com/p/data-race-test/wiki/RaceVerifier"
   1130 
   1131   def RV1Factory(self):
   1132     if common.IsWindows():
   1133       return ThreadSanitizerRV1Windows()
   1134     else:
   1135       return ThreadSanitizerRV1Posix()
   1136 
   1137   def RV2Factory(self):
   1138     if common.IsWindows():
   1139       return ThreadSanitizerRV2Windows()
   1140     else:
   1141       return ThreadSanitizerRV2Posix()
   1142 
   1143   def ToolName(self):
   1144     return "tsan"
   1145 
   1146   def Main(self, args, check_sanity, min_runtime_in_seconds):
   1147     logging.info("Running a TSan + RaceVerifier test. For more information, " +
   1148                  "see " + self.MORE_INFO_URL)
   1149     cmd1 = self.RV1Factory()
   1150     ret = cmd1.Main(args, check_sanity, min_runtime_in_seconds)
   1151     # Verify race reports, if there are any.
   1152     if ret == -1:
   1153       logging.info("Starting pass 2 of 2. Running the same binary in " +
   1154                    "RaceVerifier mode to confirm possible race reports.")
   1155       logging.info("For more information, see " + self.MORE_INFO_URL)
   1156       cmd2 = self.RV2Factory()
   1157       ret = cmd2.Main(args, check_sanity, min_runtime_in_seconds)
   1158     else:
   1159       logging.info("No reports, skipping RaceVerifier second pass")
   1160     logging.info("Please see " + self.MORE_INFO_URL + " for more information " +
   1161                  "on RaceVerifier")
   1162     return ret
   1163 
   1164   def Run(self, args, module, min_runtime_in_seconds=0):
   1165    return self.Main(args, False, min_runtime_in_seconds)
   1166 
   1167 
   1168 class EmbeddedTool(BaseTool):
   1169   """Abstract class for tools embedded directly into the test binary.
   1170   """
   1171   # TODO(glider): need to override Execute() and support process chaining here.
   1172 
   1173   def ToolCommand(self):
   1174     # In the simplest case just the args of the script.
   1175     return self._args
   1176 
   1177 
   1178 class Asan(EmbeddedTool):
   1179   """AddressSanitizer, a memory error detector.
   1180 
   1181   More information at
   1182   http://dev.chromium.org/developers/testing/addresssanitizer
   1183   """
   1184   def __init__(self):
   1185     super(Asan, self).__init__()
   1186     self._timeout = 1200
   1187     if common.IsMac():
   1188       self._env["DYLD_NO_PIE"] = "1"
   1189 
   1190 
   1191   def ToolName(self):
   1192     return "asan"
   1193 
   1194   def ToolCommand(self):
   1195     # TODO(glider): use pipes instead of the ugly wrapper here once they
   1196     # are supported.
   1197     procs = [os.path.join(self._source_dir, "tools", "valgrind",
   1198                               "asan", "asan_wrapper.sh")]
   1199     procs.extend(self._args)
   1200     return procs
   1201 
   1202   def Analyze(sels, unused_check_sanity):
   1203     return 0
   1204 
   1205 
   1206 class ToolFactory:
   1207   def Create(self, tool_name):
   1208     if tool_name == "memcheck":
   1209       return Memcheck()
   1210     if tool_name == "tsan":
   1211       if common.IsWindows():
   1212         return ThreadSanitizerWindows()
   1213       else:
   1214         return ThreadSanitizerPosix()
   1215     if tool_name == "drmemory" or tool_name == "drmemory_light":
   1216       # TODO(timurrrr): remove support for "drmemory" when buildbots are
   1217       # switched to drmemory_light OR make drmemory==drmemory_full the default
   1218       # mode when the tool is mature enough.
   1219       return DrMemory(False, False)
   1220     if tool_name == "drmemory_full":
   1221       return DrMemory(True, False)
   1222     if tool_name == "drmemory_pattern":
   1223       return DrMemory(False, True)
   1224     if tool_name == "tsan_rv":
   1225       return RaceVerifier()
   1226     if tool_name == "asan":
   1227       return Asan()
   1228     try:
   1229       platform_name = common.PlatformNames()[0]
   1230     except common.NotImplementedError:
   1231       platform_name = sys.platform + "(Unknown)"
   1232     raise RuntimeError, "Unknown tool (tool=%s, platform=%s)" % (tool_name,
   1233                                                                  platform_name)
   1234 
   1235 def CreateTool(tool):
   1236   return ToolFactory().Create(tool)
   1237