|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 from __future__ import print_function, unicode_literals |
|
6 |
|
7 import re |
|
8 |
|
9 class OutputHandler(object): |
|
10 ''' |
|
11 A class for handling Valgrind output. |
|
12 |
|
13 Valgrind errors look like this: |
|
14 |
|
15 ==60741== 40 (24 direct, 16 indirect) bytes in 1 blocks are definitely lost in loss record 2,746 of 5,235 |
|
16 ==60741== at 0x4C26B43: calloc (vg_replace_malloc.c:593) |
|
17 ==60741== by 0x63AEF65: PR_Calloc (prmem.c:443) |
|
18 ==60741== by 0x69F236E: PORT_ZAlloc_Util (secport.c:117) |
|
19 ==60741== by 0x69F1336: SECITEM_AllocItem_Util (secitem.c:28) |
|
20 ==60741== by 0xA04280B: ffi_call_unix64 (in /builds/slave/m-in-l64-valgrind-000000000000/objdir/toolkit/library/libxul.so) |
|
21 ==60741== by 0xA042443: ffi_call (ffi64.c:485) |
|
22 |
|
23 For each such error, this class extracts most or all of the first (error |
|
24 kind) line, plus the function name in each of the first few stack entries. |
|
25 With this data it constructs and prints a TEST-UNEXPECTED-FAIL message that |
|
26 TBPL will highlight. |
|
27 |
|
28 It buffers these lines from which text is extracted so that the |
|
29 TEST-UNEXPECTED-FAIL message can be printed before the full error. |
|
30 |
|
31 Parsing the Valgrind output isn't ideal, and it may break in the future if |
|
32 Valgrind changes the format of the messages, or introduces new error kinds. |
|
33 To protect against this, we also count how many lines containing |
|
34 "<insert_a_suppression_name_here>" are seen. Thanks to the use of |
|
35 --gen-suppressions=yes, exactly one of these lines is present per error. If |
|
36 the count of these lines doesn't match the error count found during |
|
37 parsing, then the parsing has missed one or more errors and we can fail |
|
38 appropriately. |
|
39 ''' |
|
40 |
|
41 def __init__(self): |
|
42 # The regexps in this list match all of Valgrind's errors. Note that |
|
43 # Valgrind is English-only, so we don't have to worry about |
|
44 # localization. |
|
45 self.re_error = \ |
|
46 r'==\d+== (' + \ |
|
47 r'(Use of uninitialised value of size \d+)|' + \ |
|
48 r'(Conditional jump or move depends on uninitialised value\(s\))|' + \ |
|
49 r'(Syscall param .* contains uninitialised byte\(s\))|' + \ |
|
50 r'(Syscall param .* points to (unaddressable|uninitialised) byte\(s\))|' + \ |
|
51 r'((Unaddressable|Uninitialised) byte\(s\) found during client check request)|' + \ |
|
52 r'(Invalid free\(\) / delete / delete\[\] / realloc\(\))|' + \ |
|
53 r'(Mismatched free\(\) / delete / delete \[\])|' + \ |
|
54 r'(Invalid (read|write) of size \d+)|' + \ |
|
55 r'(Jump to the invalid address stated on the next line)|' + \ |
|
56 r'(Source and destination overlap in .*)|' + \ |
|
57 r'(.* bytes in .* blocks are .* lost)' + \ |
|
58 r')' |
|
59 # Match identifer chars, plus ':' for namespaces, and '\?' in order to |
|
60 # match "???" which Valgrind sometimes produces. |
|
61 self.re_stack_entry = r'^==\d+==.*0x[A-Z0-9]+: ([A-Za-z0-9_:\?]+)' |
|
62 self.re_suppression = r' *<insert_a_suppression_name_here>' |
|
63 self.error_count = 0 |
|
64 self.suppression_count = 0 |
|
65 self.number_of_stack_entries_to_get = 0 |
|
66 self.curr_failure_msg = None |
|
67 self.buffered_lines = None |
|
68 |
|
69 def __call__(self, line): |
|
70 if self.number_of_stack_entries_to_get == 0: |
|
71 # Look for the start of a Valgrind error. |
|
72 m = re.search(self.re_error, line) |
|
73 if m: |
|
74 self.error_count += 1 |
|
75 self.number_of_stack_entries_to_get = 4 |
|
76 self.curr_failure_msg = 'TEST-UNEXPECTED-FAIL | valgrind-test | ' + m.group(1) + " at " |
|
77 self.buffered_lines = [line] |
|
78 else: |
|
79 print(line) |
|
80 |
|
81 else: |
|
82 # We've recently found a Valgrind error, and are now extracting |
|
83 # details from the first few stack entries. |
|
84 self.buffered_lines.append(line) |
|
85 m = re.match(self.re_stack_entry, line) |
|
86 if m: |
|
87 self.curr_failure_msg += m.group(1) |
|
88 else: |
|
89 self.curr_failure_msg += '?!?' |
|
90 |
|
91 self.number_of_stack_entries_to_get -= 1 |
|
92 if self.number_of_stack_entries_to_get != 0: |
|
93 self.curr_failure_msg += ' / ' |
|
94 else: |
|
95 # We've finished getting the first few stack entries. Print the |
|
96 # failure message and the buffered lines, and then reset state. |
|
97 print('\n' + self.curr_failure_msg + '\n') |
|
98 for b in self.buffered_lines: |
|
99 print(b) |
|
100 self.curr_failure_msg = None |
|
101 self.buffered_lines = None |
|
102 |
|
103 if re.match(self.re_suppression, line): |
|
104 self.suppression_count += 1 |
|
105 |