build/valgrind/output_handler.py

changeset 0
6474c204b198
     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 +

mercurial