Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: tools/valgrind/tsan_analyze.py

Issue 839143002: Roll Chrome into Mojo. (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Rebase Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/valgrind/tsan/README ('k') | tools/valgrind/valgrind.sh » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 # tsan_analyze.py
7
8 ''' Given a ThreadSanitizer output file, parses errors and uniques them.'''
9
10 import gdb_helper
11
12 from collections import defaultdict
13 import hashlib
14 import logging
15 import optparse
16 import os
17 import re
18 import subprocess
19 import sys
20 import time
21
22 import common
23
24 # Global symbol table (ugh)
25 TheAddressTable = None
26
27 class _StackTraceLine(object):
28 def __init__(self, line, address, binary):
29 self.raw_line_ = line
30 self.address = address
31 self.binary = binary
32 def __str__(self):
33 global TheAddressTable
34 file, line = TheAddressTable.GetFileLine(self.binary, self.address)
35 if (file is None) or (line is None):
36 return self.raw_line_
37 else:
38 return self.raw_line_.replace(self.binary, '%s:%s' % (file, line))
39
40 class TsanAnalyzer(object):
41 ''' Given a set of ThreadSanitizer output files, parse all the errors out of
42 them, unique them and output the results.'''
43
44 LOAD_LIB_RE = re.compile('--[0-9]+-- ([^(:]*) \((0x[0-9a-f]+)\)')
45 TSAN_LINE_RE = re.compile('==[0-9]+==\s*[#0-9]+\s*'
46 '([0-9A-Fa-fx]+):'
47 '(?:[^ ]* )*'
48 '([^ :\n]+)'
49 '')
50 THREAD_CREATION_STR = ("INFO: T.* "
51 "(has been created by T.* at this point|is program's main thread)")
52
53 SANITY_TEST_SUPPRESSION = ("ThreadSanitizer sanity test "
54 "(ToolsSanityTest.DataRace)")
55 TSAN_RACE_DESCRIPTION = "Possible data race"
56 TSAN_WARNING_DESCRIPTION = ("Unlocking a non-locked lock"
57 "|accessing an invalid lock"
58 "|which did not acquire this lock")
59 RACE_VERIFIER_LINE = "Confirmed a race|unexpected race"
60 TSAN_ASSERTION = "Assertion failed: "
61
62 def __init__(self, use_gdb=False):
63 '''Reads in a set of files.'''
64
65 self._use_gdb = use_gdb
66 self._cur_testcase = None
67
68 def ReadLine(self):
69 self.line_ = self.cur_fd_.readline()
70 self.stack_trace_line_ = None
71 if not self._use_gdb:
72 return
73 global TheAddressTable
74 match = TsanAnalyzer.LOAD_LIB_RE.match(self.line_)
75 if match:
76 binary, ip = match.groups()
77 TheAddressTable.AddBinaryAt(binary, ip)
78 return
79 match = TsanAnalyzer.TSAN_LINE_RE.match(self.line_)
80 if match:
81 address, binary_name = match.groups()
82 stack_trace_line = _StackTraceLine(self.line_, address, binary_name)
83 TheAddressTable.Add(stack_trace_line.binary, stack_trace_line.address)
84 self.stack_trace_line_ = stack_trace_line
85
86 def ReadSection(self):
87 """ Example of a section:
88 ==4528== WARNING: Possible data race: {{{
89 ==4528== T20 (L{}):
90 ==4528== #0 MyTest::Foo1
91 ==4528== #1 MyThread::ThreadBody
92 ==4528== Concurrent write happened at this point:
93 ==4528== T19 (L{}):
94 ==4528== #0 MyTest::Foo2
95 ==4528== #1 MyThread::ThreadBody
96 ==4528== }}}
97 ------- suppression -------
98 {
99 <Put your suppression name here>
100 ThreadSanitizer:Race
101 fun:MyTest::Foo1
102 fun:MyThread::ThreadBody
103 }
104 ------- end suppression -------
105 """
106 result = [self.line_]
107 if re.search("{{{", self.line_):
108 while not re.search('}}}', self.line_):
109 self.ReadLine()
110 if self.stack_trace_line_ is None:
111 result.append(self.line_)
112 else:
113 result.append(self.stack_trace_line_)
114 self.ReadLine()
115 if re.match('-+ suppression -+', self.line_):
116 # We need to calculate the suppression hash and prepend a line like
117 # "Suppression (error hash=#0123456789ABCDEF#):" so the buildbot can
118 # extract the suppression snippet.
119 supp = ""
120 while not re.match('-+ end suppression -+', self.line_):
121 self.ReadLine()
122 supp += self.line_
123 self.ReadLine()
124 if self._cur_testcase:
125 result.append("The report came from the `%s` test.\n" % \
126 self._cur_testcase)
127 result.append("Suppression (error hash=#%016X#):\n" % \
128 (int(hashlib.md5(supp).hexdigest()[:16], 16)))
129 result.append(" For more info on using suppressions see "
130 "http://dev.chromium.org/developers/how-tos/using-valgrind/threadsan itizer#TOC-Suppressing-data-races\n")
131 result.append(supp)
132 else:
133 self.ReadLine()
134
135 return result
136
137 def ReadTillTheEnd(self):
138 result = [self.line_]
139 while self.line_:
140 self.ReadLine()
141 result.append(self.line_)
142 return result
143
144 def ParseReportFile(self, filename):
145 '''Parses a report file and returns a list of ThreadSanitizer reports.
146
147
148 Args:
149 filename: report filename.
150 Returns:
151 list of (list of (str iff self._use_gdb, _StackTraceLine otherwise)).
152 '''
153 ret = []
154 self.cur_fd_ = open(filename, 'r')
155
156 while True:
157 # Read ThreadSanitizer reports.
158 self.ReadLine()
159 if not self.line_:
160 break
161
162 while True:
163 tmp = []
164 while re.search(TsanAnalyzer.RACE_VERIFIER_LINE, self.line_):
165 tmp.append(self.line_)
166 self.ReadLine()
167 while re.search(TsanAnalyzer.THREAD_CREATION_STR, self.line_):
168 tmp.extend(self.ReadSection())
169 if re.search(TsanAnalyzer.TSAN_RACE_DESCRIPTION, self.line_):
170 tmp.extend(self.ReadSection())
171 ret.append(tmp) # includes RaceVerifier and thread creation stacks
172 elif (re.search(TsanAnalyzer.TSAN_WARNING_DESCRIPTION, self.line_) and
173 not common.IsWindows()): # workaround for http://crbug.com/53198
174 tmp.extend(self.ReadSection())
175 ret.append(tmp)
176 else:
177 break
178
179 tmp = []
180 if re.search(TsanAnalyzer.TSAN_ASSERTION, self.line_):
181 tmp.extend(self.ReadTillTheEnd())
182 ret.append(tmp)
183 break
184
185 match = re.search("used_suppression:\s+([0-9]+)\s(.*)", self.line_)
186 if match:
187 count, supp_name = match.groups()
188 count = int(count)
189 self.used_suppressions[supp_name] += count
190 self.cur_fd_.close()
191 return ret
192
193 def GetReports(self, files):
194 '''Extracts reports from a set of files.
195
196 Reads a set of files and returns a list of all discovered
197 ThreadSanitizer race reports. As a side effect, populates
198 self.used_suppressions with appropriate info.
199 '''
200
201 global TheAddressTable
202 if self._use_gdb:
203 TheAddressTable = gdb_helper.AddressTable()
204 else:
205 TheAddressTable = None
206 reports = []
207 self.used_suppressions = defaultdict(int)
208 for file in files:
209 reports.extend(self.ParseReportFile(file))
210 if self._use_gdb:
211 TheAddressTable.ResolveAll()
212 # Make each line of each report a string.
213 reports = map(lambda(x): map(str, x), reports)
214 return [''.join(report_lines) for report_lines in reports]
215
216 def Report(self, files, testcase, check_sanity=False):
217 '''Reads in a set of files and prints ThreadSanitizer report.
218
219 Args:
220 files: A list of filenames.
221 check_sanity: if true, search for SANITY_TEST_SUPPRESSIONS
222 '''
223
224 # We set up _cur_testcase class-wide variable to avoid passing it through
225 # about 5 functions.
226 self._cur_testcase = testcase
227 reports = self.GetReports(files)
228 self._cur_testcase = None # just in case, shouldn't be used anymore
229
230 common.PrintUsedSuppressionsList(self.used_suppressions)
231
232
233 retcode = 0
234 if reports:
235 sys.stdout.flush()
236 sys.stderr.flush()
237 logging.info("FAIL! Found %i report(s)" % len(reports))
238 for report in reports:
239 logging.info('\n' + report)
240 sys.stdout.flush()
241 retcode = -1
242
243 # Report tool's insanity even if there were errors.
244 if (check_sanity and
245 TsanAnalyzer.SANITY_TEST_SUPPRESSION not in self.used_suppressions):
246 logging.error("FAIL! Sanity check failed!")
247 retcode = -3
248
249 if retcode != 0:
250 return retcode
251
252 logging.info("PASS: No reports found")
253 return 0
254
255
256 def main():
257 '''For testing only. The TsanAnalyzer class should be imported instead.'''
258 parser = optparse.OptionParser("usage: %prog <files to analyze>")
259
260 (options, args) = parser.parse_args()
261 if not args:
262 parser.error("no filename specified")
263 filenames = args
264
265 logging.getLogger().setLevel(logging.INFO)
266 analyzer = TsanAnalyzer(use_gdb=True)
267 return analyzer.Report(filenames, None)
268
269
270 if __name__ == '__main__':
271 sys.exit(main())
OLDNEW
« no previous file with comments | « tools/valgrind/tsan/README ('k') | tools/valgrind/valgrind.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698