diff options
Diffstat (limited to 'build/valgrind/output_handler.py')
-rw-r--r-- | build/valgrind/output_handler.py | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/build/valgrind/output_handler.py b/build/valgrind/output_handler.py new file mode 100644 index 0000000000..613f7f8c1f --- /dev/null +++ b/build/valgrind/output_handler.py @@ -0,0 +1,117 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import logging +import re + + +class OutputHandler(object): + """ + A class for handling Valgrind output. + + Valgrind errors look like this: + + ==60741== 40 (24 direct, 16 indirect) bytes in 1 blocks are definitely lost in loss record 2,746 of 5,235 + ==60741== at 0x4C26B43: calloc (vg_replace_malloc.c:593) + ==60741== by 0x63AEF65: PR_Calloc (prmem.c:443) + ==60741== by 0x69F236E: PORT_ZAlloc_Util (secport.c:117) + ==60741== by 0x69F1336: SECITEM_AllocItem_Util (secitem.c:28) + ==60741== by 0xA04280B: ffi_call_unix64 (in /builds/slave/m-in-l64-valgrind-000000000000/objdir/toolkit/library/libxul.so) + ==60741== by 0xA042443: ffi_call (ffi64.c:485) + + For each such error, this class extracts most or all of the first (error + kind) line, plus the function name in each of the first few stack entries. + With this data it constructs and prints a TEST-UNEXPECTED-FAIL message that + TBPL will highlight. + + It buffers these lines from which text is extracted so that the + TEST-UNEXPECTED-FAIL message can be printed before the full error. + + Parsing the Valgrind output isn't ideal, and it may break in the future if + Valgrind changes the format of the messages, or introduces new error kinds. + To protect against this, we also count how many lines containing + "<insert_a_suppression_name_here>" are seen. Thanks to the use of + --gen-suppressions=yes, exactly one of these lines is present per error. If + the count of these lines doesn't match the error count found during + parsing, then the parsing has missed one or more errors and we can fail + appropriately. + """ # NOQA: E501 + + def __init__(self, logger): + # The regexps in this list match all of Valgrind's errors. Note that + # Valgrind is English-only, so we don't have to worry about + # localization. + self.logger = logger + self.re_error = ( + r"==\d+== (" + + r"(Use of uninitialised value of size \d+)|" + + r"(Conditional jump or move depends on uninitialised value\(s\))|" + + r"(Syscall param .* contains uninitialised byte\(s\))|" + + r"(Syscall param .* points to (unaddressable|uninitialised) byte\(s\))|" + + r"((Unaddressable|Uninitialised) byte\(s\) found during client check request)|" + + r"(Invalid free\(\) / delete / delete\[\] / realloc\(\))|" + + r"(Mismatched free\(\) / delete / delete \[\])|" + + r"(Invalid (read|write) of size \d+)|" + + r"(Jump to the invalid address stated on the next line)|" + + r"(Source and destination overlap in .*)|" + + r"(.* bytes in .* blocks are .* lost)" + + r")" + ) + # Match identifer chars, plus ':' for namespaces, and '\?' in order to + # match "???" which Valgrind sometimes produces. + self.re_stack_entry = r"^==\d+==.*0x[A-Z0-9]+: ([A-Za-z0-9_:\?]+)" + self.re_suppression = r" *<insert_a_suppression_name_here>" + self.error_count = 0 + self.suppression_count = 0 + self.number_of_stack_entries_to_get = 0 + self.curr_error = None + self.curr_location = None + self.buffered_lines = None + + def log(self, line): + self.logger(logging.INFO, "valgrind-output", {"line": line}, "{line}") + + def __call__(self, line): + if self.number_of_stack_entries_to_get == 0: + # Look for the start of a Valgrind error. + m = re.search(self.re_error, line) + if m: + self.error_count += 1 + self.number_of_stack_entries_to_get = 4 + self.curr_error = m.group(1) + self.curr_location = "" + self.buffered_lines = [line] + else: + self.log(line) + + else: + # We've recently found a Valgrind error, and are now extracting + # details from the first few stack entries. + self.buffered_lines.append(line) + m = re.match(self.re_stack_entry, line) + if m: + self.curr_location += m.group(1) + else: + self.curr_location += "?!?" + + self.number_of_stack_entries_to_get -= 1 + if self.number_of_stack_entries_to_get != 0: + self.curr_location += " / " + else: + # We've finished getting the first few stack entries. Print the + # failure message and the buffered lines, and then reset state. + self.logger( + logging.ERROR, + "valgrind-error-msg", + {"error": self.curr_error, "location": self.curr_location}, + "TEST-UNEXPECTED-FAIL | valgrind-test | {error} at {location}", + ) + for b in self.buffered_lines: + self.log(b) + self.curr_error = None + self.curr_location = None + self.buffered_lines = None + + if re.match(self.re_suppression, line): + self.suppression_count += 1 |