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.
8 ''' Given a ThreadSanitizer output file, parses errors and uniques them.'''
12 from collections
import defaultdict
24 # Global symbol table (ugh)
25 TheAddressTable
= None
27 class _StackTraceLine(object):
28 def __init__(self
, line
, address
, binary
):
30 self
.address
= address
33 global TheAddressTable
34 file, line
= TheAddressTable
.GetFileLine(self
.binary
, self
.address
)
35 if (file is None) or (line
is None):
38 return self
.raw_line_
.replace(self
.binary
, '%s:%s' % (file, line
))
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.'''
44 LOAD_LIB_RE
= re
.compile('--[0-9]+-- ([^(:]*) \((0x[0-9a-f]+)\)')
45 TSAN_LINE_RE
= re
.compile('==[0-9]+==\s*[#0-9]+\s*'
50 THREAD_CREATION_STR
= ("INFO: T.* "
51 "(has been created by T.* at this point|is program's main thread)")
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: "
62 def __init__(self
, use_gdb
=False):
63 '''Reads in a set of files.'''
65 self
._use
_gdb
= use_gdb
66 self
._cur
_testcase
= None
69 self
.line_
= self
.cur_fd_
.readline()
70 self
.stack_trace_line_
= None
73 global TheAddressTable
74 match
= TsanAnalyzer
.LOAD_LIB_RE
.match(self
.line_
)
76 binary
, ip
= match
.groups()
77 TheAddressTable
.AddBinaryAt(binary
, ip
)
79 match
= TsanAnalyzer
.TSAN_LINE_RE
.match(self
.line_
)
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
86 def ReadSection(self
):
87 """ Example of a section:
88 ==4528== WARNING: Possible data race: {{{
90 ==4528== #0 MyTest::Foo1
91 ==4528== #1 MyThread::ThreadBody
92 ==4528== Concurrent write happened at this point:
94 ==4528== #0 MyTest::Foo2
95 ==4528== #1 MyThread::ThreadBody
97 ------- suppression -------
99 <Put your suppression name here>
102 fun:MyThread::ThreadBody
104 ------- end suppression -------
106 result
= [self
.line_
]
107 if re
.search("{{{", self
.line_
):
108 while not re
.search('}}}', self
.line_
):
110 if self
.stack_trace_line_
is None:
111 result
.append(self
.line_
)
113 result
.append(self
.stack_trace_line_
)
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.
120 while not re
.match('-+ end suppression -+', self
.line_
):
124 if self
._cur
_testcase
:
125 result
.append("The report came from the `%s` test.\n" % \
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/threadsanitizer#TOC-Suppressing-data-races\n")
137 def ReadTillTheEnd(self
):
138 result
= [self
.line_
]
141 result
.append(self
.line_
)
144 def ParseReportFile(self
, filename
):
145 '''Parses a report file and returns a list of ThreadSanitizer reports.
149 filename: report filename.
151 list of (list of (str iff self._use_gdb, _StackTraceLine otherwise)).
154 self
.cur_fd_
= open(filename
, 'r')
157 # Read ThreadSanitizer reports.
164 while re
.search(TsanAnalyzer
.RACE_VERIFIER_LINE
, self
.line_
):
165 tmp
.append(self
.line_
)
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())
180 if re
.search(TsanAnalyzer
.TSAN_ASSERTION
, self
.line_
):
181 tmp
.extend(self
.ReadTillTheEnd())
185 match
= re
.search("used_suppression:\s+([0-9]+)\s(.*)", self
.line_
)
187 count
, supp_name
= match
.groups()
189 self
.used_suppressions
[supp_name
] += count
193 def GetReports(self
, files
):
194 '''Extracts reports from a set of files.
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.
201 global TheAddressTable
203 TheAddressTable
= gdb_helper
.AddressTable()
205 TheAddressTable
= None
207 self
.used_suppressions
= defaultdict(int)
209 reports
.extend(self
.ParseReportFile(file))
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
]
216 def Report(self
, files
, testcase
, check_sanity
=False):
217 '''Reads in a set of files and prints ThreadSanitizer report.
220 files: A list of filenames.
221 check_sanity: if true, search for SANITY_TEST_SUPPRESSIONS
224 # We set up _cur_testcase class-wide variable to avoid passing it through
226 self
._cur
_testcase
= testcase
227 reports
= self
.GetReports(files
)
228 self
._cur
_testcase
= None # just in case, shouldn't be used anymore
230 common
.PrintUsedSuppressionsList(self
.used_suppressions
)
237 logging
.info("FAIL! Found %i report(s)" % len(reports
))
238 for report
in reports
:
239 logging
.info('\n' + report
)
243 # Report tool's insanity even if there were errors.
245 TsanAnalyzer
.SANITY_TEST_SUPPRESSION
not in self
.used_suppressions
):
246 logging
.error("FAIL! Sanity check failed!")
252 logging
.info("PASS: No reports found")
257 '''For testing only. The TsanAnalyzer class should be imported instead.'''
258 parser
= optparse
.OptionParser("usage: %prog <files to analyze>")
260 (options
, args
) = parser
.parse_args()
262 parser
.error("no filename specified")
265 logging
.getLogger().setLevel(logging
.INFO
)
266 analyzer
= TsanAnalyzer(use_gdb
=True)
267 return analyzer
.Report(filenames
, None)
270 if __name__
== '__main__':