build/valgrind/output_handler.py
author Emilio Cobos Álvarez <emilio@crisal.io>
Sun, 17 Dec 2017 22:29:10 +0100
changeset 396857 13faabcf8e965cca857e3cc8fdbea289a09c7ea0
parent 291943 1d6d31b41c2fb93f3172d6ef2ef560e8686cf74c
child 419627 c37fb2bf78d66c664f17af47a6d0f838e54850a2
permissions -rw-r--r--
Bug 1425769: Base class for ShadowRoot and Document to manage style state. r=smaug This also removes some confusing comments around nsIDocument regarding some kind of "special" stylesheets, which don't seem to exist anymore, and consolidates StyleSheetList so that we only have one implementation. I think that fixes a potential leak on the shadow root code (even though the API is v0 only), given the pointer from the ShadowRootStyleSheetList to the ShadowRoot wasn't being CCd. Also, more stuff could be renamed, methods removed, etc, feel free to suggest more cleanup, I've done mostly the minimal. Next steps are moving the stylesets there and stop using the proto binding sheet list / resources. MozReview-Commit-ID: D9hnDgPQAS5

# 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/.

from __future__ import print_function, unicode_literals

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.
    '''

    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