1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/build/valgrind/output_handler.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,105 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +from __future__ import print_function, unicode_literals 1.9 + 1.10 +import re 1.11 + 1.12 +class OutputHandler(object): 1.13 + ''' 1.14 + A class for handling Valgrind output. 1.15 + 1.16 + Valgrind errors look like this: 1.17 + 1.18 + ==60741== 40 (24 direct, 16 indirect) bytes in 1 blocks are definitely lost in loss record 2,746 of 5,235 1.19 + ==60741== at 0x4C26B43: calloc (vg_replace_malloc.c:593) 1.20 + ==60741== by 0x63AEF65: PR_Calloc (prmem.c:443) 1.21 + ==60741== by 0x69F236E: PORT_ZAlloc_Util (secport.c:117) 1.22 + ==60741== by 0x69F1336: SECITEM_AllocItem_Util (secitem.c:28) 1.23 + ==60741== by 0xA04280B: ffi_call_unix64 (in /builds/slave/m-in-l64-valgrind-000000000000/objdir/toolkit/library/libxul.so) 1.24 + ==60741== by 0xA042443: ffi_call (ffi64.c:485) 1.25 + 1.26 + For each such error, this class extracts most or all of the first (error 1.27 + kind) line, plus the function name in each of the first few stack entries. 1.28 + With this data it constructs and prints a TEST-UNEXPECTED-FAIL message that 1.29 + TBPL will highlight. 1.30 + 1.31 + It buffers these lines from which text is extracted so that the 1.32 + TEST-UNEXPECTED-FAIL message can be printed before the full error. 1.33 + 1.34 + Parsing the Valgrind output isn't ideal, and it may break in the future if 1.35 + Valgrind changes the format of the messages, or introduces new error kinds. 1.36 + To protect against this, we also count how many lines containing 1.37 + "<insert_a_suppression_name_here>" are seen. Thanks to the use of 1.38 + --gen-suppressions=yes, exactly one of these lines is present per error. If 1.39 + the count of these lines doesn't match the error count found during 1.40 + parsing, then the parsing has missed one or more errors and we can fail 1.41 + appropriately. 1.42 + ''' 1.43 + 1.44 + def __init__(self): 1.45 + # The regexps in this list match all of Valgrind's errors. Note that 1.46 + # Valgrind is English-only, so we don't have to worry about 1.47 + # localization. 1.48 + self.re_error = \ 1.49 + r'==\d+== (' + \ 1.50 + r'(Use of uninitialised value of size \d+)|' + \ 1.51 + r'(Conditional jump or move depends on uninitialised value\(s\))|' + \ 1.52 + r'(Syscall param .* contains uninitialised byte\(s\))|' + \ 1.53 + r'(Syscall param .* points to (unaddressable|uninitialised) byte\(s\))|' + \ 1.54 + r'((Unaddressable|Uninitialised) byte\(s\) found during client check request)|' + \ 1.55 + r'(Invalid free\(\) / delete / delete\[\] / realloc\(\))|' + \ 1.56 + r'(Mismatched free\(\) / delete / delete \[\])|' + \ 1.57 + r'(Invalid (read|write) of size \d+)|' + \ 1.58 + r'(Jump to the invalid address stated on the next line)|' + \ 1.59 + r'(Source and destination overlap in .*)|' + \ 1.60 + r'(.* bytes in .* blocks are .* lost)' + \ 1.61 + r')' 1.62 + # Match identifer chars, plus ':' for namespaces, and '\?' in order to 1.63 + # match "???" which Valgrind sometimes produces. 1.64 + self.re_stack_entry = r'^==\d+==.*0x[A-Z0-9]+: ([A-Za-z0-9_:\?]+)' 1.65 + self.re_suppression = r' *<insert_a_suppression_name_here>' 1.66 + self.error_count = 0 1.67 + self.suppression_count = 0 1.68 + self.number_of_stack_entries_to_get = 0 1.69 + self.curr_failure_msg = None 1.70 + self.buffered_lines = None 1.71 + 1.72 + def __call__(self, line): 1.73 + if self.number_of_stack_entries_to_get == 0: 1.74 + # Look for the start of a Valgrind error. 1.75 + m = re.search(self.re_error, line) 1.76 + if m: 1.77 + self.error_count += 1 1.78 + self.number_of_stack_entries_to_get = 4 1.79 + self.curr_failure_msg = 'TEST-UNEXPECTED-FAIL | valgrind-test | ' + m.group(1) + " at " 1.80 + self.buffered_lines = [line] 1.81 + else: 1.82 + print(line) 1.83 + 1.84 + else: 1.85 + # We've recently found a Valgrind error, and are now extracting 1.86 + # details from the first few stack entries. 1.87 + self.buffered_lines.append(line) 1.88 + m = re.match(self.re_stack_entry, line) 1.89 + if m: 1.90 + self.curr_failure_msg += m.group(1) 1.91 + else: 1.92 + self.curr_failure_msg += '?!?' 1.93 + 1.94 + self.number_of_stack_entries_to_get -= 1 1.95 + if self.number_of_stack_entries_to_get != 0: 1.96 + self.curr_failure_msg += ' / ' 1.97 + else: 1.98 + # We've finished getting the first few stack entries. Print the 1.99 + # failure message and the buffered lines, and then reset state. 1.100 + print('\n' + self.curr_failure_msg + '\n') 1.101 + for b in self.buffered_lines: 1.102 + print(b) 1.103 + self.curr_failure_msg = None 1.104 + self.buffered_lines = None 1.105 + 1.106 + if re.match(self.re_suppression, line): 1.107 + self.suppression_count += 1 1.108 +