OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # memcheck_analyze.py | 6 # memcheck_analyze.py |
7 | 7 |
8 ''' Given a valgrind XML file, parses errors and uniques them.''' | 8 ''' Given a valgrind XML file, parses errors and uniques them.''' |
9 | 9 |
10 import gdb_helper | 10 import gdb_helper |
11 | 11 |
12 import logging | 12 import logging |
13 import optparse | 13 import optparse |
14 import os | 14 import os |
15 import re | 15 import re |
16 import subprocess | 16 import subprocess |
17 import sys | 17 import sys |
18 import time | 18 import time |
19 from xml.dom.minidom import parse | 19 from xml.dom.minidom import parse |
20 from xml.parsers.expat import ExpatError | 20 from xml.parsers.expat import ExpatError |
21 | 21 |
22 import common | 22 import common |
23 | 23 |
24 # Global symbol table (yuck) | 24 # Global symbol table (yuck) |
25 TheAddressTable = None | 25 TheAddressTable = None |
26 | 26 |
27 # Contains the time when the we started analyzing the first log file. | |
28 # This variable is used to skip incomplete logs after some timeout. | |
29 # TODO(timurrrr): Currently, this needs to be a global variable | |
30 # because analyzer can be called multiple times (e.g. ui_tests) | |
31 # unless we re-factor the analyze system to avoid creating multiple analyzers. | |
32 AnalyzeStartTime = None | |
33 | |
34 # Max time to wait for memcheck logs to complete. | |
35 LOG_COMPLETION_TIMEOUT = 180.0 | |
36 | |
37 # These are functions (using C++ mangled names) that we look for in stack | 27 # These are functions (using C++ mangled names) that we look for in stack |
38 # traces. We don't show stack frames while pretty printing when they are below | 28 # traces. We don't show stack frames while pretty printing when they are below |
39 # any of the following: | 29 # any of the following: |
40 _TOP_OF_STACK_POINTS = [ | 30 _TOP_OF_STACK_POINTS = [ |
41 # Don't show our testing framework. | 31 # Don't show our testing framework. |
42 "testing::Test::Run()", | 32 "testing::Test::Run()", |
43 # Also don't show the internals of libc/pthread. | 33 # Also don't show the internals of libc/pthread. |
44 "start_thread" | 34 "start_thread" |
45 ] | 35 ] |
46 | 36 |
(...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
212 self._backtraces.append([description, frames]) | 202 self._backtraces.append([description, frames]) |
213 description = None | 203 description = None |
214 stack = None | 204 stack = None |
215 frames = None | 205 frames = None |
216 elif node.localName == "suppression": | 206 elif node.localName == "suppression": |
217 self._suppression = getCDATAOf(node, "rawtext"); | 207 self._suppression = getCDATAOf(node, "rawtext"); |
218 | 208 |
219 def __str__(self): | 209 def __str__(self): |
220 ''' Pretty print the type and backtrace(s) of this specific error, | 210 ''' Pretty print the type and backtrace(s) of this specific error, |
221 including suppression (which is just a mangled backtrace).''' | 211 including suppression (which is just a mangled backtrace).''' |
222 output = self._kind + "\n" | 212 output = "" |
223 if (self._commandline): | 213 if (self._commandline): |
224 output += self._commandline + "\n" | 214 output += self._commandline + "\n" |
225 | 215 |
| 216 output += self._kind + "\n" |
226 for backtrace in self._backtraces: | 217 for backtrace in self._backtraces: |
227 output += backtrace[0] + "\n" | 218 output += backtrace[0] + "\n" |
228 filter = subprocess.Popen("c++filt -n", stdin=subprocess.PIPE, | 219 filter = subprocess.Popen("c++filt -n", stdin=subprocess.PIPE, |
229 stdout=subprocess.PIPE, | 220 stdout=subprocess.PIPE, |
230 stderr=subprocess.STDOUT, | 221 stderr=subprocess.STDOUT, |
231 shell=True, | 222 shell=True, |
232 close_fds=True) | 223 close_fds=True) |
233 buf = "" | 224 buf = "" |
234 for frame in backtrace[1]: | 225 for frame in backtrace[1]: |
235 buf += (frame[FUNCTION_NAME] or frame[INSTRUCTION_POINTER]) + "\n" | 226 buf += (frame[FUNCTION_NAME] or frame[INSTRUCTION_POINTER]) + "\n" |
(...skipping 12 matching lines...) Expand all Loading... |
248 frame[INSTRUCTION_POINTER]) | 239 frame[INSTRUCTION_POINTER]) |
249 if foo[0] != None: | 240 if foo[0] != None: |
250 output += (" (" + foo[0] + ":" + foo[1] + ")") | 241 output += (" (" + foo[0] + ":" + foo[1] + ")") |
251 elif frame[SRC_FILE_DIR] != "": | 242 elif frame[SRC_FILE_DIR] != "": |
252 output += (" (" + frame[SRC_FILE_DIR] + "/" + frame[SRC_FILE_NAME] + | 243 output += (" (" + frame[SRC_FILE_DIR] + "/" + frame[SRC_FILE_NAME] + |
253 ":" + frame[SRC_LINE] + ")") | 244 ":" + frame[SRC_LINE] + ")") |
254 else: | 245 else: |
255 output += " (" + frame[OBJECT_FILE] + ")" | 246 output += " (" + frame[OBJECT_FILE] + ")" |
256 output += "\n" | 247 output += "\n" |
257 | 248 |
258 # TODO(dank): stop synthesizing suppressions once everyone has | 249 assert self._suppression != None, "Your Valgrind doesn't generate " \ |
259 # valgrind-3.5 and we can rely on xml | 250 "suppressions - is it too old?" |
260 if (self._suppression == None): | |
261 output += "Suppression:\n" | |
262 for frame in backtrace[1]: | |
263 output += " fun:" + (frame[FUNCTION_NAME] or "*") + "\n" | |
264 | 251 |
265 if (self._suppression != None): | 252 output += "Suppression (hash=#%X#):" % self.__hash__() |
266 output += "Suppression:" | 253 # Widen suppression slightly to make portable between mac and linux |
267 # Widen suppression slightly to make portable between mac and linux | 254 supp = self._suppression; |
268 supp = self._suppression; | 255 supp = supp.replace("fun:_Znwj", "fun:_Znw*") |
269 supp = supp.replace("fun:_Znwj", "fun:_Znw*") | 256 supp = supp.replace("fun:_Znwm", "fun:_Znw*") |
270 supp = supp.replace("fun:_Znwm", "fun:_Znw*") | 257 # Split into lines so we can enforce length limits |
271 # Split into lines so we can enforce length limits | 258 supplines = supp.split("\n") |
272 supplines = supp.split("\n") | |
273 | 259 |
274 # Truncate at line 26 (VG_MAX_SUPP_CALLERS plus 2 for name and type) | 260 # Truncate at line 26 (VG_MAX_SUPP_CALLERS plus 2 for name and type) |
275 # or at the first 'boring' caller. | 261 # or at the first 'boring' caller. |
276 # (https://bugs.kde.org/show_bug.cgi?id=199468 proposes raising | 262 # (https://bugs.kde.org/show_bug.cgi?id=199468 proposes raising |
277 # VG_MAX_SUPP_CALLERS, but we're probably fine with it as is.) | 263 # VG_MAX_SUPP_CALLERS, but we're probably fine with it as is.) |
278 # TODO(dkegel): add more boring callers | 264 # TODO(dkegel): add more boring callers |
279 newlen = 26; | 265 newlen = 26; |
280 try: | 266 try: |
281 newlen = min(newlen, supplines.index(" fun:_ZN11MessageLoop3RunEv")) | 267 newlen = min(newlen, supplines.index(" fun:_ZN11MessageLoop3RunEv")) |
282 except ValueError: | 268 except ValueError: |
283 pass | 269 pass |
284 if (len(supplines) > newlen): | 270 if (len(supplines) > newlen): |
285 supplines = supplines[0:newlen] | 271 supplines = supplines[0:newlen] |
286 supplines.append("}") | 272 supplines.append("}") |
287 | 273 |
288 output += "\n".join(supplines) + "\n" | 274 output += "\n".join(supplines) + "\n" |
289 | 275 |
290 return output | 276 return output |
291 | 277 |
292 def UniqueString(self): | 278 def UniqueString(self): |
293 ''' String to use for object identity. Don't print this, use str(obj) | 279 ''' String to use for object identity. Don't print this, use str(obj) |
294 instead.''' | 280 instead.''' |
295 rep = self._kind + " " | 281 rep = self._kind + " " |
296 for backtrace in self._backtraces: | 282 for backtrace in self._backtraces: |
297 for frame in backtrace[1]: | 283 for frame in backtrace[1]: |
298 rep += frame[FUNCTION_NAME] | 284 rep += frame[FUNCTION_NAME] |
(...skipping 14 matching lines...) Expand all Loading... |
313 f.seek(0) | 299 f.seek(0) |
314 while True: | 300 while True: |
315 line = f.readline() | 301 line = f.readline() |
316 if line == "": | 302 if line == "": |
317 return False | 303 return False |
318 if '</valgrindoutput>' in line: | 304 if '</valgrindoutput>' in line: |
319 # valgrind often has garbage after </valgrindoutput> upon crash | 305 # valgrind often has garbage after </valgrindoutput> upon crash |
320 f.truncate() | 306 f.truncate() |
321 return True | 307 return True |
322 | 308 |
323 class MemcheckAnalyze: | 309 class MemcheckAnalyzer: |
324 ''' Given a set of Valgrind XML files, parse all the errors out of them, | 310 ''' Given a set of Valgrind XML files, parse all the errors out of them, |
325 unique them and output the results.''' | 311 unique them and output the results.''' |
326 | 312 |
327 SANITY_TEST_SUPPRESSIONS_LINUX = { | 313 SANITY_TEST_SUPPRESSIONS_LINUX = { |
328 "Memcheck sanity test 01 (memory leak).": 1, | 314 "Memcheck sanity test 01 (memory leak).": 1, |
329 "Memcheck sanity test 02 (malloc/read left).": 1, | 315 "Memcheck sanity test 02 (malloc/read left).": 1, |
330 "Memcheck sanity test 03 (malloc/read right).": 1, | 316 "Memcheck sanity test 03 (malloc/read right).": 1, |
331 "Memcheck sanity test 04 (malloc/write left).": 1, | 317 "Memcheck sanity test 04 (malloc/write left).": 1, |
332 "Memcheck sanity test 05 (malloc/write right).": 1, | 318 "Memcheck sanity test 05 (malloc/write right).": 1, |
333 "Memcheck sanity test 06 (new/read left).": 1, | 319 "Memcheck sanity test 06 (new/read left).": 1, |
(...skipping 13 matching lines...) Expand all Loading... |
347 "Memcheck sanity test 06 (new/read left).": 1, | 333 "Memcheck sanity test 06 (new/read left).": 1, |
348 "Memcheck sanity test 07 (new/read right).": 1, | 334 "Memcheck sanity test 07 (new/read right).": 1, |
349 "Memcheck sanity test 10 (write after free).": 1, | 335 "Memcheck sanity test 10 (write after free).": 1, |
350 "Memcheck sanity test 11 (write after delete).": 1, | 336 "Memcheck sanity test 11 (write after delete).": 1, |
351 "bug_49253 Memcheck sanity test 12 (array deleted without []) on Mac.": 1, | 337 "bug_49253 Memcheck sanity test 12 (array deleted without []) on Mac.": 1, |
352 "bug_49253 Memcheck sanity test 13 (single element deleted with []) on Mac
.": 1, | 338 "bug_49253 Memcheck sanity test 13 (single element deleted with []) on Mac
.": 1, |
353 "bug_49253 Memcheck sanity test 04 (malloc/write left) or Memcheck sanity
test 05 (malloc/write right) on Mac.": 2, | 339 "bug_49253 Memcheck sanity test 04 (malloc/write left) or Memcheck sanity
test 05 (malloc/write right) on Mac.": 2, |
354 "bug_49253 Memcheck sanity test 08 (new/write left) or Memcheck sanity tes
t 09 (new/write right) on Mac.": 2, | 340 "bug_49253 Memcheck sanity test 08 (new/write left) or Memcheck sanity tes
t 09 (new/write right) on Mac.": 2, |
355 } | 341 } |
356 | 342 |
357 def __init__(self, source_dir, files, show_all_leaks=False, use_gdb=False): | 343 # Max time to wait for memcheck logs to complete. |
358 '''Reads in a set of files. | 344 LOG_COMPLETION_TIMEOUT = 180.0 |
| 345 |
| 346 def __init__(self, source_dir, show_all_leaks=False, use_gdb=False): |
| 347 '''Create a parser for Memcheck logs. |
359 | 348 |
360 Args: | 349 Args: |
361 source_dir: Path to top of source tree for this build | 350 source_dir: Path to top of source tree for this build |
| 351 show_all_leaks: Whether to show even less important leaks |
| 352 use_gdb: Whether to use gdb to resolve source filenames and line numbers |
| 353 in the report stacktraces |
| 354 ''' |
| 355 self._source_dir = source_dir |
| 356 self._show_all_leaks = show_all_leaks |
| 357 self._use_gdb = use_gdb |
| 358 |
| 359 # Contains the set of unique errors |
| 360 self._errors = set() |
| 361 |
| 362 # Contains the time when the we started analyzing the first log file. |
| 363 # This variable is used to skip incomplete logs after some timeout. |
| 364 self._analyze_start_time = None |
| 365 |
| 366 |
| 367 def Report(self, files, check_sanity=False): |
| 368 '''Reads in a set of files and prints Memcheck report. |
| 369 |
| 370 Args: |
362 files: A list of filenames. | 371 files: A list of filenames. |
363 show_all_leaks: whether to show even less important leaks | 372 check_sanity: if true, search for SANITY_TEST_SUPPRESSIONS |
364 ''' | 373 ''' |
365 | |
366 # Beyond the detailed errors parsed by ValgrindError above, | 374 # Beyond the detailed errors parsed by ValgrindError above, |
367 # the xml file contain records describing suppressions that were used: | 375 # the xml file contain records describing suppressions that were used: |
368 # <suppcounts> | 376 # <suppcounts> |
369 # <pair> | 377 # <pair> |
370 # <count>28</count> | 378 # <count>28</count> |
371 # <name>pango_font_leak_todo</name> | 379 # <name>pango_font_leak_todo</name> |
372 # </pair> | 380 # </pair> |
373 # <pair> | 381 # <pair> |
374 # <count>378</count> | 382 # <count>378</count> |
375 # <name>bug_13243</name> | 383 # <name>bug_13243</name> |
376 # </pair> | 384 # </pair> |
377 # </suppcounts | 385 # </suppcounts |
378 # Collect these and print them at the end. | 386 # Collect these and print them at the end. |
379 # | 387 # |
380 # With our patch for https://bugs.kde.org/show_bug.cgi?id=205000 in, | 388 # With our patch for https://bugs.kde.org/show_bug.cgi?id=205000 in, |
381 # the file also includes records of the form | 389 # the file also includes records of the form |
382 # <load_obj><obj>/usr/lib/libgcc_s.1.dylib</obj><ip>0x27000</ip></load_obj> | 390 # <load_obj><obj>/usr/lib/libgcc_s.1.dylib</obj><ip>0x27000</ip></load_obj> |
383 # giving the filename and load address of each binary that was mapped | 391 # giving the filename and load address of each binary that was mapped |
384 # into the process. | 392 # into the process. |
385 | 393 |
386 global TheAddressTable | 394 global TheAddressTable |
387 if use_gdb: | 395 if self._use_gdb: |
388 TheAddressTable = gdb_helper.AddressTable() | 396 TheAddressTable = gdb_helper.AddressTable() |
389 self._errors = set() | 397 else: |
390 self._suppcounts = {} | 398 TheAddressTable = None |
| 399 cur_report_errors = set() |
| 400 suppcounts = {} |
391 badfiles = set() | 401 badfiles = set() |
392 | 402 |
393 global AnalyzeStartTime | 403 if self._analyze_start_time == None: |
394 if AnalyzeStartTime == None: | 404 self._analyze_start_time = time.time() |
395 AnalyzeStartTime = time.time() | 405 start_time = self._analyze_start_time |
396 self._parse_failed = False | 406 |
| 407 parse_failed = False |
397 for file in files: | 408 for file in files: |
398 # Wait up to three minutes for valgrind to finish writing all files, | 409 # Wait up to three minutes for valgrind to finish writing all files, |
399 # but after that, just skip incomplete files and warn. | 410 # but after that, just skip incomplete files and warn. |
400 f = open(file, "r+") | 411 f = open(file, "r+") |
401 pid = re.match(".*\.([0-9]+)$", file) | 412 pid = re.match(".*\.([0-9]+)$", file) |
402 if pid: | 413 if pid: |
403 pid = pid.groups()[0] | 414 pid = pid.groups()[0] |
404 found = False | 415 found = False |
405 running = True | 416 running = True |
406 firstrun = True | 417 firstrun = True |
407 origsize = os.path.getsize(file) | 418 origsize = os.path.getsize(file) |
408 while (running and not found and | 419 while (running and not found and |
409 (firstrun or | 420 (firstrun or |
410 ((time.time() - AnalyzeStartTime) < LOG_COMPLETION_TIMEOUT))): | 421 ((time.time() - start_time) < self.LOG_COMPLETION_TIMEOUT))): |
411 firstrun = False | 422 firstrun = False |
412 f.seek(0) | 423 f.seek(0) |
413 if pid: | 424 if pid: |
414 # Make sure the process is still running so we don't wait for | 425 # Make sure the process is still running so we don't wait for |
415 # 3 minutes if it was killed. See http://crbug.com/17453 | 426 # 3 minutes if it was killed. See http://crbug.com/17453 |
416 ps_out = subprocess.Popen("ps p %s" % pid, shell=True, | 427 ps_out = subprocess.Popen("ps p %s" % pid, shell=True, |
417 stdout=subprocess.PIPE).stdout | 428 stdout=subprocess.PIPE).stdout |
418 if ps_out.readlines() < 2: | 429 if ps_out.readlines() < 2: |
419 running = False | 430 running = False |
420 found = find_and_truncate(f) | 431 found = find_and_truncate(f) |
421 if not running and not found: | 432 if not running and not found: |
422 logging.warn("Valgrind process PID = %s is not running but " | 433 logging.warn("Valgrind process PID = %s is not running but " |
423 "its XML log has not been finished correctly." % pid) | 434 "its XML log has not been finished correctly." % pid) |
424 if running and not found: | 435 if running and not found: |
425 time.sleep(1) | 436 time.sleep(1) |
426 f.close() | 437 f.close() |
427 if not found: | 438 if not found: |
428 badfiles.add(file) | 439 badfiles.add(file) |
429 else: | 440 else: |
430 newsize = os.path.getsize(file) | 441 newsize = os.path.getsize(file) |
431 if origsize > newsize+1: | 442 if origsize > newsize+1: |
432 logging.warn(str(origsize - newsize) + | 443 logging.warn(str(origsize - newsize) + |
433 " bytes of junk were after </valgrindoutput> in %s!" % | 444 " bytes of junk were after </valgrindoutput> in %s!" % |
434 file) | 445 file) |
435 try: | 446 try: |
436 parsed_file = parse(file); | 447 parsed_file = parse(file); |
437 except ExpatError, e: | 448 except ExpatError, e: |
438 self._parse_failed = True | 449 parse_failed = True |
439 logging.warn("could not parse %s: %s" % (file, e)) | 450 logging.warn("could not parse %s: %s" % (file, e)) |
440 lineno = e.lineno - 1 | 451 lineno = e.lineno - 1 |
441 context_lines = 5 | 452 context_lines = 5 |
442 context_start = max(0, lineno - context_lines) | 453 context_start = max(0, lineno - context_lines) |
443 context_end = lineno + context_lines + 1 | 454 context_end = lineno + context_lines + 1 |
444 context_file = open(file, "r") | 455 context_file = open(file, "r") |
445 for i in range(0, context_start): | 456 for i in range(0, context_start): |
446 context_file.readline() | 457 context_file.readline() |
447 for i in range(context_start, context_end): | 458 for i in range(context_start, context_end): |
448 context_data = context_file.readline().rstrip() | 459 context_data = context_file.readline().rstrip() |
(...skipping 15 matching lines...) Expand all Loading... |
464 for node in preamble.getElementsByTagName("line"): | 475 for node in preamble.getElementsByTagName("line"): |
465 if node.localName == "line": | 476 if node.localName == "line": |
466 for x in node.childNodes: | 477 for x in node.childNodes: |
467 if x.nodeType == node.TEXT_NODE and "Command" in x.data: | 478 if x.nodeType == node.TEXT_NODE and "Command" in x.data: |
468 commandline = x.data | 479 commandline = x.data |
469 break | 480 break |
470 | 481 |
471 raw_errors = parsed_file.getElementsByTagName("error") | 482 raw_errors = parsed_file.getElementsByTagName("error") |
472 for raw_error in raw_errors: | 483 for raw_error in raw_errors: |
473 # Ignore "possible" leaks for now by default. | 484 # Ignore "possible" leaks for now by default. |
474 if (show_all_leaks or | 485 if (self._show_all_leaks or |
475 getTextOf(raw_error, "kind") != "Leak_PossiblyLost"): | 486 getTextOf(raw_error, "kind") != "Leak_PossiblyLost"): |
476 error = ValgrindError(source_dir, raw_error, commandline) | 487 error = ValgrindError(self._source_dir, raw_error, commandline) |
477 self._errors.add(error) | 488 if error not in cur_report_errors: |
| 489 # We haven't seen such errors doing this report yet... |
| 490 if error in self._errors: |
| 491 # ... but we saw it in earlier reports, e.g. previous UI test |
| 492 cur_report_errors.add("This error was already printed " |
| 493 "in some other test, see 'hash=#%X#'" % \ |
| 494 error.__hash__()) |
| 495 else: |
| 496 # ... and we haven't seen it in other tests as well |
| 497 self._errors.add(error) |
| 498 cur_report_errors.add(error) |
478 | 499 |
479 suppcountlist = parsed_file.getElementsByTagName("suppcounts") | 500 suppcountlist = parsed_file.getElementsByTagName("suppcounts") |
480 if len(suppcountlist) > 0: | 501 if len(suppcountlist) > 0: |
481 suppcountlist = suppcountlist[0] | 502 suppcountlist = suppcountlist[0] |
482 for node in suppcountlist.getElementsByTagName("pair"): | 503 for node in suppcountlist.getElementsByTagName("pair"): |
483 count = getTextOf(node, "count"); | 504 count = getTextOf(node, "count"); |
484 name = getTextOf(node, "name"); | 505 name = getTextOf(node, "name"); |
485 if name in self._suppcounts: | 506 if name in suppcounts: |
486 self._suppcounts[name] += int(count) | 507 suppcounts[name] += int(count) |
487 else: | 508 else: |
488 self._suppcounts[name] = int(count) | 509 suppcounts[name] = int(count) |
489 | 510 |
490 if len(badfiles) > 0: | 511 if len(badfiles) > 0: |
491 logging.warn("valgrind didn't finish writing %d files?!" % len(badfiles)) | 512 logging.warn("valgrind didn't finish writing %d files?!" % len(badfiles)) |
492 for file in badfiles: | 513 for file in badfiles: |
493 logging.warn("Last 20 lines of %s :" % file) | 514 logging.warn("Last 20 lines of %s :" % file) |
494 os.system("tail -n 20 '%s' 1>&2" % file) | 515 os.system("tail -n 20 '%s' 1>&2" % file) |
495 | 516 |
496 def Report(self, check_sanity=False): | 517 if parse_failed: |
497 if self._parse_failed: | |
498 logging.error("FAIL! Couldn't parse Valgrind output file") | 518 logging.error("FAIL! Couldn't parse Valgrind output file") |
499 return -2 | 519 return -2 |
500 | 520 |
501 is_sane = False | 521 is_sane = False |
502 print "-----------------------------------------------------" | 522 print "-----------------------------------------------------" |
503 print "Suppressions used:" | 523 print "Suppressions used:" |
504 print " count name" | 524 print " count name" |
505 | 525 |
506 if common.IsLinux(): | 526 if common.IsLinux(): |
507 remaining_sanity_supp = MemcheckAnalyze.SANITY_TEST_SUPPRESSIONS_LINUX | 527 remaining_sanity_supp = MemcheckAnalyzer.SANITY_TEST_SUPPRESSIONS_LINUX |
508 elif common.IsMac(): | 528 elif common.IsMac(): |
509 remaining_sanity_supp = MemcheckAnalyze.SANITY_TEST_SUPPRESSIONS_MAC | 529 remaining_sanity_supp = MemcheckAnalyzer.SANITY_TEST_SUPPRESSIONS_MAC |
510 else: | 530 else: |
511 remaining_sanity_supp = {} | 531 remaining_sanity_supp = {} |
512 if check_sanity: | 532 if check_sanity: |
513 logging.warn("No sanity test list for platform %s", sys.platform) | 533 logging.warn("No sanity test list for platform %s", sys.platform) |
514 | 534 |
515 for (name, count) in sorted(self._suppcounts.items(), | 535 for (name, count) in sorted(suppcounts.items(), |
516 key=lambda (k,v): (v,k)): | 536 key=lambda (k,v): (v,k)): |
517 print "%7d %s" % (count, name) | 537 print "%7d %s" % (count, name) |
518 if name in remaining_sanity_supp and remaining_sanity_supp[name] == count: | 538 if name in remaining_sanity_supp and remaining_sanity_supp[name] == count: |
519 del remaining_sanity_supp[name] | 539 del remaining_sanity_supp[name] |
520 if len(remaining_sanity_supp) == 0: | 540 if len(remaining_sanity_supp) == 0: |
521 is_sane = True | 541 is_sane = True |
522 print "-----------------------------------------------------" | 542 print "-----------------------------------------------------" |
523 sys.stdout.flush() | 543 sys.stdout.flush() |
524 | 544 |
525 retcode = 0 | 545 retcode = 0 |
526 if self._errors: | 546 if self._errors: |
527 logging.error("FAIL! There were %s errors: " % len(self._errors)) | 547 logging.error("FAIL! There were %s errors: " % len(self._errors)) |
528 | 548 |
529 global TheAddressTable | |
530 if TheAddressTable != None: | 549 if TheAddressTable != None: |
531 TheAddressTable.ResolveAll() | 550 TheAddressTable.ResolveAll() |
532 | 551 |
533 for error in self._errors: | 552 for error in cur_report_errors: |
534 logging.error(error) | 553 logging.error(error) |
535 | 554 |
536 retcode = -1 | 555 retcode = -1 |
537 | 556 |
538 # Report tool's insanity even if there were errors. | 557 # Report tool's insanity even if there were errors. |
539 if check_sanity and not is_sane: | 558 if check_sanity and not is_sane: |
540 logging.error("FAIL! Sanity check failed!") | 559 logging.error("FAIL! Sanity check failed!") |
541 logging.info("The following test errors were not handled: ") | 560 logging.info("The following test errors were not handled: ") |
542 for (name, count) in sorted(remaining_sanity_supp.items(), | 561 for (name, count) in sorted(remaining_sanity_supp.items(), |
543 key=lambda (k,v): (v,k)): | 562 key=lambda (k,v): (v,k)): |
544 logging.info("%7d %s" % (count, name)) | 563 logging.info("%7d %s" % (count, name)) |
545 retcode = -3 | 564 retcode = -3 |
546 | 565 |
547 if retcode != 0: | 566 if retcode != 0: |
548 return retcode | 567 return retcode |
549 | 568 |
550 logging.info("PASS! No errors found!") | 569 logging.info("PASS! No errors found!") |
551 return 0 | 570 return 0 |
552 | 571 |
553 def _main(): | 572 def _main(): |
554 '''For testing only. The MemcheckAnalyze class should be imported instead.''' | 573 '''For testing only. The MemcheckAnalyzer class should be imported instead.''' |
555 retcode = 0 | 574 retcode = 0 |
556 parser = optparse.OptionParser("usage: %prog [options] <files to analyze>") | 575 parser = optparse.OptionParser("usage: %prog [options] <files to analyze>") |
557 parser.add_option("", "--source_dir", | 576 parser.add_option("", "--source_dir", |
558 help="path to top of source tree for this build" | 577 help="path to top of source tree for this build" |
559 "(used to normalize source paths in baseline)") | 578 "(used to normalize source paths in baseline)") |
560 | 579 |
561 (options, args) = parser.parse_args() | 580 (options, args) = parser.parse_args() |
562 if len(args) == 0: | 581 if len(args) == 0: |
563 parser.error("no filename specified") | 582 parser.error("no filename specified") |
564 filenames = args | 583 filenames = args |
565 | 584 |
566 analyzer = MemcheckAnalyze(options.source_dir, filenames, use_gdb=True) | 585 analyzer = MemcheckAnalyzer(options.source_dir, use_gdb=True) |
567 retcode = analyzer.Report() | 586 retcode = analyzer.Report(filenames) |
568 | 587 |
569 sys.exit(retcode) | 588 sys.exit(retcode) |
570 | 589 |
571 if __name__ == "__main__": | 590 if __name__ == "__main__": |
572 _main() | 591 _main() |
OLD | NEW |